ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MP/bin/aemp
Revision: 1.25
Committed: Sun Aug 30 19:49:47 2009 UTC (14 years, 9 months ago) by root
Branch: MAIN
Changes since 1.24: +29 -16 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 #!/opt/bin/perl
2    
3     =head1 NAME
4    
5     aemp - AnyEvent:MP utility
6    
7     =head1 SYNOPSIS
8    
9     aemp command args...
10    
11     # protocol commands
12 root 1.19 aemp snd <port> <arg...> # send a message
13     aemp mon <port> # wait till port is killed
14     aemp rpc <port> <arg...> # send message, append reply
15     aemp eval <node> <expr...> # evaluate expression
16 root 1.22 aemp trace <nodeid> # trace the network topology
17 root 1.8
18     # run a node
19 root 1.19 aemp run initialise_args... # run a node
20 root 1.8
21 root 1.23 # node configuration: node ID
22 root 1.19 aemp setnodeid <nodeid> # configure the real node id
23     aemp delnodeid # reset node id to default (= inherit)
24 root 1.1
25     # node configuration: secret
26 root 1.19 aemp gensecret # generate a random shared secret
27     aemp setsecret <secret> # set the shared secret
28     aemp delsecret # remove the secret (= inherit)
29 root 1.1
30     # node configuration: TLS
31 root 1.19 aemp gencert # generate a random certificate
32     aemp setcert <file> # set a certificate (key.pem + certificate.pem)
33     aemp delcert # remove certificate (= inherit)
34 root 1.8
35 root 1.17 # node configuration: seed addresses for bootstrapping
36 root 1.19 aemp setseeds <host:port>,... # set seeds
37     aemp delseeds # clear all seeds (= inherit)
38     aemp addseed <host:port> # add a seed
39     aemp delseed <host:port> # remove seed
40 root 1.17
41     # node configuration: bind addresses
42 root 1.19 aemp setbinds <host:port>,... # set binds
43     aemp delbinds # clear all binds (= inherit)
44     aemp addbind <host:port> # add a bind address
45     aemp delbind <host:port> # remove a bind address
46 root 1.8
47     # node configuration: services
48 root 1.19 aemp setservices initfunc,... # set service functions
49     aemp delservices # clear all services (= inherit)
50     aemp addservice <initfunc> # add an instance of a service
51     aemp delservice <initfunc> # delete one instance of a service
52 root 1.6
53 root 1.10 # profile-specific configuration
54     aemp profile <name> <command>... # apply command to profile only
55 root 1.24 aemp parent <name> # specify a parent profile
56 root 1.25 aemp delparent # clear parent again
57 root 1.19 aemp delprofile <name> # eradicate the named profile
58     aemp showprofile <name> # display given profile
59 root 1.25 aemp showconfig <name> ... # display effective config
60 root 1.10
61 root 1.1 =head1 DESCRIPTION
62    
63 root 1.8 With aemp you can configure various aspects of AnyEvent::MP and its
64 root 1.22 protocol, send various messages and even run a node.
65 root 1.1
66 root 1.23 The F<aemp> utility works like F<cvs>, F<svn> or other commands: the first
67     argument defines which operation (subcommand) is requested, after which
68     arguments for this operation are expected. When a subcommand does not eat
69     all remaining arguments, the remaining arguments will again be interpreted
70     as subcommand and so on.
71    
72     This means you can chain multiple commands, which is handy for profile
73     configuration, e.g.:
74    
75     aemp gensecret profile xyzzy setbinds 4040,4041 setnodeid anon/
76    
77     =head2 RUNNING A NODE
78    
79     This can be used to run a node - together with some services, this makes
80     it unnecesary to write any wrapper programs.
81    
82     =over 4
83    
84     =item run <profile> <...>
85    
86     Runs a node by calling C<AnyEvent::MP::initialise_node> with the given
87     arguments. The node runs under L<AnyEvent::Watchdog>, can be restarted
88     (and autorestarted, see the L<AnyEvent::Watchdog> manual).
89    
90     Care has been taken to load (almost) no modules other than
91     L<AnyEvent::Watchdog> and the modules it loads, so everything (including
92     the L<AnyEvent::MP> modules themselves) will be freshly loaded on restart,
93     which makes upgrading everything except the perl binary easy.
94    
95     =back
96    
97     =head2 PROTOCOL COMMANDS
98    
99     These commands actually communicate with other nodes. They all use a node
100 root 1.25 profile name of C<aemp> (specifying a dfeault node ID of C<anon/> and a
101     binds list containing C<*:*> only).
102 root 1.23
103     They all use a timeout of five seconds, after which they give up.
104    
105     =over 4
106    
107     =item snd <port> <arguments...>
108    
109     Simply send a message to the given port - where you get the port ID from
110     is your problem.
111    
112     Exits after ensuring that the message has been delivered to its node.
113    
114     Most useful to take avdantage of some undocumented functionality inside
115     nodes, such as node ports being able to call any method:
116    
117     aemp snd doomed AnyEvent::Watchdog::restart 1
118    
119     =item rpc <port> <arg...>
120    
121     Like F<aemp snd>, but appends a local reply port to the message and waits
122     for a message to it.
123    
124     Any return values will be JSON-encoded and printed separated by commas
125     (kind of like a JSON array without []-brackets).
126    
127     Example: ask the (undocumented) time service of a node for it'S current
128     time.
129    
130     aemp rpc mynode time
131    
132     =item mon <port>
133    
134     Monitors the port and exits when it's monitorign callback is called. Most
135     useful to monitor node ports.
136    
137     Example: monitor some node.
138    
139     aemp mon doomed
140    
141     =item eval <node> <expr...>
142    
143     Joins all remaining arguments into a string and evaluates it on the given
144     node. Return values are handled as with F<aemp rpc>.
145    
146     Example: find the unix process ID of the node called posicks.
147    
148     aemp eval posicks '$$'
149    
150     =item trace <node>
151    
152     Asks the given node for all currently connected nodes, then asks those
153     nodes for the same, thus tracing all node connections.
154    
155     =cut
156    
157     =head2 CONFIGURATION/NODE ID/SECRET/CERTIFICATE
158    
159     These commands deal with rather basic settings, the node ID, the shared
160     secret and the TLS certificate.
161    
162     =over 4
163    
164     =item setnodeid <nodeid>
165    
166     Set the node ID to the given string.
167    
168     =item delnodeid
169    
170     Removes the node ID again, which means it is inherited again from it's
171     parent profile, or stays unset.
172    
173     =item gensecret
174    
175     Generates a random shared secret and sets it. The shared secret is used to
176     authenticate nodes to each other when TLS is not required.
177    
178     =item setsecret <secret>
179    
180     Sets the shared secret tot he given string, which can be anything.
181    
182     =item delsecret
183    
184     Removes the shared secret again, which means it is inherited again from
185     it's parent profile, or stays unset.
186    
187     =item gencert
188    
189     Generates a self-signed certficate and key, and sets it. This works
190     similarly to a shared secret: when all nodes have it, TLS will be used to
191     authenticate and encrypt all traffic.
192    
193     =item setcert <file>
194    
195     Set a node certificate (and optionally any CA certificates) from the given
196     file. The file must contain the key, followed by the certificate, followed
197     by any CA certificates you want to trust, all in PEM format.
198    
199     See L<AnyEvent::TLS> for some more details - this sets the C<cert> and
200     C<ca_cert> options.
201    
202     =item delcert
203    
204     Removes the certificate(s) again, which means it is inherited again from
205     it's parent profile, or stays unset.
206    
207     =back
208    
209     =head2 CONFIGURATION/SEEDS
210    
211     To discover the network you have to specify some seed addresses, which are
212     basically C<host:port> pairs where you expect some long-running nodes. It
213     does no harm to have a node as its own seed (they will eventually be
214     ignored).
215    
216     =item setseeds <host:port>,...
217    
218     Sets or replaces the list of seeds, which must be specified as a
219     comma-separated list of C<host:port> pairs. The C<host> can be a hostname,
220     an IP address, or C<*> to signify all local host addresses. If the
221     C<:port> is omitted, then the default port of C<4040> is assumed.
222    
223     An empty list is allowed.
224    
225     Example: use C<doomed> with default port as only seednode.
226    
227     aemp setseeds doomed
228    
229     =item delseeds
230    
231     Removes the seed list again, which means it is inherited again from it's
232     parent profile, or stays unset.
233    
234     =item addseed <host:port>
235    
236     Adds a single seed address.
237    
238     =item delseed <host:port>
239    
240     Deletes the given seed address, if it exists.
241    
242     =back
243    
244     =head2 CONFIGURATION/BINDS
245    
246     To be able to be reached from other nodes, a node must I<bind> itself
247     to some listening socket(s). The list of these can either bs specified
248     manually, or AnyEvent::MP can guess them. Nodes without any binds are
249     possible to some extent.
250    
251     =over 4
252    
253     =item setbinds <host:port>,...
254    
255     Sets the list of bind addresses explicitly - see the F<aemp setseeds>
256     command for the exact syntax. In addition, a value of C<0> for the port
257     means to use a dynamically-assigned port.
258    
259 root 1.25 Note that the C<*>, C<*:*> or C<*:port> values are very useful here.
260 root 1.23
261     Example: bind on the default port (4040) on all local interfaces.
262    
263     aemp setbinds "*"
264    
265     Example: bind on a random port on all local interfaces.
266    
267 root 1.25 aemp setbinds "*:*"
268 root 1.23
269     Example: resolve "doomed.mydomain" and try to bind on port C<4040> of all
270     IP addressess returned.
271    
272     aep setbinds doomed.mydomain
273    
274     =item delbinds
275    
276     Removes the bind list again, which means it is inherited again from it's
277     parent profile, or stays unset.
278    
279     =item addbind <host:port>
280    
281     Adds a single bind address.
282    
283     =item delbind <host:port>
284    
285     Deletes the given bind address, if it exists.
286    
287     =back
288    
289     =head2 CONFIGURATION/SERVICES
290    
291     Services are modules (or functions) that are automatically loaded (or
292     executed) when a node starts. They are especially useful when used in
293     conjunction with F<aemp run>, to configure which services a node should
294     run.
295    
296     =over 4
297    
298     =item setservices <initfunc>...
299    
300     Sets or replaces the list of services, which must be specified as a
301     comma-separated list.
302    
303     Each entry in the list is interpreted as either a module name to
304     load (when it ends with C<::>) or a function to call (all other
305     cases). The algorithm to find the function is the same as used for C<<
306     L<AnyEvent::MP>::spawn >>.
307    
308     =item delservices
309    
310     Removes the service list again, which means it is inherited again from
311     it's parent profile, or stays unset.
312    
313     =item addservice <initfunc>
314    
315     Adds a single service.
316    
317     =item delservice <initfunc>
318    
319     Deletes the given service, if it exists.
320    
321     =back
322    
323     =head2 CONFIGURATION/PROFILE MANAGEMENT
324    
325     All the above configuration functions by default affect the I<global
326     default configuration>, which is basically used to augment every profile
327     and node configuration.
328    
329     =over 4
330    
331     =item profile <name> ...
332    
333     This subcommand makes the following subcommands act only on a specific
334     named profile, instead of on the global default. The profile is created if
335     necessary.
336    
337     Example: create a C<server> profile, give it a random node name, some seed
338     nodes and bind it on an unspecified port on all local interfaces. You
339     should add some services then and run the node...
340    
341 root 1.25 aemp server setnodeid anon/ setseeds doomed,10.0.0.2:5000 setbinds "*:*"
342 root 1.23
343     =item delprofile <name>
344    
345     Deletes the profile of the given name.
346    
347 root 1.24 =item parent <name>
348    
349     Sets the parent profile to use - values not specified in a profile will be
350     taken from the parent profile (even recursively, with the global default
351     config being the default parent). This is useful to configure profile
352     I<classes> and then to inherit from them for individual nodes.
353    
354     Note that you can specify circular parent chains and even a parent for the
355     global configuration. Neither will do you any good, however.
356    
357     Example: inherit all values not specified in the C<doomed> profile from
358     the C<server> profile.
359    
360     aemp profile doomed parent server
361    
362     =item delparent
363    
364     Removes the parent again from the profile, if any was set, so the profile
365     inherits directly from the global default config again.
366    
367 root 1.23 =item showprofile <name>
368    
369     Shows the values of the given profile, and only those, no inherited
370     values.
371    
372 root 1.25 =item showconfig <name> <key value...>
373 root 1.23
374 root 1.24 Shows the I<effective> config, i.e. the values as used by a node started
375 root 1.25 with the given profile name. Any additional key-value pairs specified
376     augment the configuration, just as with C<initialise_node>.
377 root 1.24
378 root 1.25 If all arguments are omitted, show the global default config.
379 root 1.23
380     =back
381    
382 root 1.1 =cut
383    
384     use common::sense;
385    
386 root 1.19 # should come before anything else, so all modules
387     # will be loaded on each restart
388 root 1.22 BEGIN {
389 root 1.8 if ($ARGV[0] eq "run") {
390     shift;
391    
392 root 1.13 # d'oh
393 root 1.22 require AnyEvent::Watchdog;
394 root 1.19 # now we can load extra modules
395    
396 root 1.22 AnyEvent::Watchdog::autorestart (1);
397     AnyEvent::Watchdog::heartbeat (300);
398 root 1.13
399 root 1.8 require AnyEvent;
400 root 1.25 require AnyEvent::MP::Kernel;
401     AnyEvent::MP::Kernel::initialise_node (@ARGV);
402 root 1.8
403 root 1.16 AnyEvent::detect () eq "AnyEvent::Impl::EV"
404 root 1.8 ? EV::loop ()
405     : AE::cv ()->recv;
406     }
407 root 1.22 }
408 root 1.8
409 root 1.1 use Carp ();
410    
411 root 1.17 use JSON::XS;
412    
413 root 1.1 use AnyEvent;
414 root 1.4 use AnyEvent::Util;
415    
416 root 1.8 use AnyEvent::MP;
417 root 1.1 use AnyEvent::MP::Config;
418    
419     sub my_run_cmd {
420     my ($cmd) = @_;
421    
422     my $cv = &run_cmd;
423     my $status = $cv->recv;
424    
425     $status
426     and die "@$cmd: command failed with exit status $status.";
427     }
428    
429     sub gen_cert {
430 root 1.2 my_run_cmd [qw(openssl req
431     -new -nodes -x509 -days 3650
432     -newkey rsa:2048 -keyout /dev/fd/3
433     -batch -subj /CN=AnyEvent::MP
434     )],
435 root 1.5 "<", "/dev/null",
436 root 1.1 ">" , \my $cert,
437     "3>", \my $key,
438 root 1.4 "2>", "/dev/null";
439 root 1.1
440     "$cert$key"
441     }
442    
443 root 1.25 sub init {
444     initialise_node aemp => nodeid => "anon/", binds => ["*:*"];
445     }
446    
447 root 1.10 our $cfg = AnyEvent::MP::Config::config;
448 root 1.8 our $profile = $cfg;
449 root 1.1
450 root 1.11 sub trace {
451     my ($node) = @_;
452     my $cv = AE::cv;
453     my %seen;
454    
455     my $to = AE::timer 15, 0, sub {
456     warn "timeout\n";
457     $cv->();
458     };
459    
460 root 1.25 init;
461 root 1.11
462     my $reply = port {
463 root 1.12 my ($node, @neigh) = @_;
464 root 1.11
465 root 1.12 @neigh = grep $_ ne $NODE, @neigh;
466    
467     print +(join " ", $node, @neigh), "\n";
468    
469     for (@neigh) {
470 root 1.11 unless ($seen{$_}++) {
471     $cv->begin;
472     snd $_, up_nodes => $SELF => $_;
473     }
474     }
475    
476     $cv->end;
477     };
478    
479     $cv->begin;
480     snd $reply, seed => $node;
481    
482     $cv->recv;
483     }
484    
485 root 1.10 sub docmd;
486    
487 root 1.1 our %CMD = (
488     snd => sub {
489 root 1.17 my $port = shift @ARGV;
490 root 1.25 init;
491 root 1.1
492 root 1.7 snd $port, @ARGV; @ARGV = ();
493 root 1.1
494     my $cv = AE::cv;
495 root 1.7 my $to = AE::timer 5, 0, sub { $cv->("timeout") };
496 root 1.1 mon $port, $cv;
497 root 1.19 my $reply = port sub { &$cv };
498     snd node_of $port, snd => $reply, "message sent successfully";
499 root 1.1
500     print join " ", $cv->recv, "\n";
501     },
502    
503 root 1.7 rpc => sub {
504 root 1.17 my $port = shift @ARGV;
505 root 1.25 init;
506 root 1.7
507     my $cv = AE::cv;
508     my $to = AE::timer 5, 0, sub { $cv->("timeout") };
509 root 1.19 snd $port, @ARGV, port { &$cv }; @ARGV = ();
510 root 1.7 mon $port, $cv;
511    
512 root 1.23 print +(substr JSON::XS->new->encode ([$cv->recv]), 1, -1), "\n";
513 root 1.7 },
514    
515 root 1.1 mon => sub {
516 root 1.17 my $port = shift @ARGV;
517 root 1.25 init;
518 root 1.1
519     mon $port, my $cv = AE::cv;
520     print join " ", $cv->recv, "\n";
521     },
522    
523 root 1.19 eval => sub {
524     my $node = node_of shift @ARGV;
525     my $expr = join " ", @ARGV; @ARGV = ();
526 root 1.25 init;
527 root 1.19
528     my $cv = AE::cv;
529     my $to = AE::timer 5, 0, sub { $cv->("timeout") };
530     AnyEvent::MP::Kernel::eval_on $node, $expr, port { &$cv };
531     mon $node, $cv;
532    
533     my ($err, @res) = $cv->recv;
534    
535     die $err if length $err;
536    
537     print +(substr JSON::XS->new->encode (\@res), 1, -1), "\n";
538     },
539    
540 root 1.11 trace => sub {
541     @ARGV >= 1
542 root 1.17 or die "node id missing\n";
543 root 1.11
544 root 1.17 trace shift @ARGV;
545 root 1.11 },
546    
547 root 1.17 setnodeid => sub {
548 root 1.8 @ARGV >= 1
549     or die "shared secret missing\n";
550    
551 root 1.17 $profile->{nodeid} = shift @ARGV;
552 root 1.8 ++$cfg->{dirty};
553     },
554 root 1.17 delnodeid => sub {
555     delete $profile->{nodeid};
556 root 1.8 ++$cfg->{dirty};
557     },
558    
559 root 1.1 setsecret => sub {
560 root 1.8 @ARGV >= 1
561 root 1.1 or die "shared secret missing\n";
562    
563 root 1.8 $profile->{secret} = shift @ARGV;
564 root 1.1 ++$cfg->{dirty};
565     },
566     gensecret => sub {
567 root 1.18 $profile->{secret} = AnyEvent::MP::Kernel::alnumbits AnyEvent::MP::Kernel::nonce 64;
568 root 1.1 ++$cfg->{dirty};
569     },
570 root 1.17 delsecret => sub {
571 root 1.8 delete $profile->{secret};
572 root 1.1 ++$cfg->{dirty};
573     },
574    
575     setcert => sub {
576 root 1.8 @ARGV >= 1
577 root 1.1 or die "key+certificate pem filename missing\n";
578    
579     open my $fh, "<", $ARGV[0]
580     or die "$ARGV[0]: $!";
581    
582     local $/;
583 root 1.8 $profile->{cert} = <$fh>;
584 root 1.1 ++$cfg->{dirty};
585     },
586     gencert => sub {
587 root 1.8 $profile->{cert} = gen_cert;
588 root 1.1 ++$cfg->{dirty};
589     },
590 root 1.17 delcert => sub {
591 root 1.8 delete $profile->{cert};
592 root 1.1 ++$cfg->{dirty};
593     },
594 root 1.6
595 root 1.17 setbinds => sub {
596 root 1.19 @ARGV >= 1
597     or die "bind addresses missing\n";
598     $profile->{binds} = [split /,/, shift @ARGV];
599 root 1.17 ++$cfg->{dirty};
600     },
601     delbinds => sub {
602     delete $profile->{binds};
603     ++$cfg->{dirty};
604     },
605     addbind => sub {
606     @ARGV >= 1
607     or die "bind address missing\n";
608     my $bind = shift @ARGV;
609    
610     @{ $profile->{binds} } = grep $_ ne $bind, @{ $profile->{binds} };
611     push @{ $profile->{binds} }, $bind;
612     ++$cfg->{dirty};
613     },
614     delbind => sub {
615     @ARGV >= 1
616     or die "bind address missing\n";
617     my $bind = shift @ARGV;
618    
619     @{ $profile->{binds} } = grep $_ ne $bind, @{ $profile->{binds} };
620     ++$cfg->{dirty};
621     },
622    
623 root 1.6 setseeds => sub {
624 root 1.19 @ARGV >= 1
625     or die "seed addresses missing\n";
626     $profile->{seeds} = [split /,/, shift @ARGV];
627 root 1.6 ++$cfg->{dirty};
628     },
629 root 1.17 delseeds => sub {
630     delete $profile->{seeds};
631     ++$cfg->{dirty};
632     },
633 root 1.6 addseed => sub {
634 root 1.10 @ARGV >= 1
635 root 1.17 or die "seed address missing\n";
636 root 1.6 my $seed = shift @ARGV;
637 root 1.10
638 root 1.8 @{ $profile->{seeds} } = grep $_ ne $seed, @{ $profile->{seeds} };
639     push @{ $profile->{seeds} }, $seed;
640     ++$cfg->{dirty};
641     },
642     delseed => sub {
643 root 1.10 @ARGV >= 1
644 root 1.17 or die "seed address missing\n";
645 root 1.8 my $seed = shift @ARGV;
646 root 1.10
647 root 1.8 @{ $profile->{seeds} } = grep $_ ne $seed, @{ $profile->{seeds} };
648     ++$cfg->{dirty};
649     },
650    
651     setservices => sub {
652 root 1.19 @ARGV >= 1
653     or die "service specifications missing\n";
654     $profile->{services} = [split /,/, shift @ARGV];
655 root 1.6 ++$cfg->{dirty};
656     },
657 root 1.17 delservices => sub {
658     delete $profile->{services};
659     ++$cfg->{dirty};
660     },
661 root 1.9 addservice => sub {
662 root 1.10 @ARGV >= 1
663     or die "service specification missing\n";
664 root 1.8 my $service = shift @ARGV;
665     push @{ $profile->{services} }, $service;
666     ++$cfg->{dirty};
667     },
668 root 1.9 delservice => sub {
669 root 1.10 @ARGV >= 1
670     or die "service specification missing\n";
671 root 1.8 my $service = shift @ARGV;
672     for (0 .. $#{ $profile->{services} }) {
673     next unless $profile->{services}[$_] eq $service;
674     splice @{ $profile->{services} }, $_, 1;
675     last;
676     }
677 root 1.6 ++$cfg->{dirty};
678     },
679 root 1.10
680     profile => sub {
681 root 1.19 @ARGV >= 1
682     or die "profile name is missing\n";
683 root 1.10 my $name = shift @ARGV;
684    
685     $profile = $cfg->{profile}{$name} ||= {};
686 root 1.14 ++$cfg->{dirty};
687 root 1.10 },
688     delprofile => sub {
689     @ARGV >= 1
690     or die "profile name is missing\n";
691     my $name = shift @ARGV;
692    
693     delete $cfg->{profile}{$name};
694 root 1.14 ++$cfg->{dirty};
695 root 1.10 },
696 root 1.24 parent => sub {
697     @ARGV >= 1
698     or die "profile name is missing\n";
699    
700     $profile->{parent} = shift @ARGV;
701     ++$cfg->{dirty};
702     },
703     delparent => sub {
704     delete $profile->{parent};
705     ++$cfg->{dirty};
706     },
707 root 1.17 showprofile => sub {
708     @ARGV >= 1
709     or die "profile name is missing\n";
710     my $name = shift @ARGV;
711    
712     print JSON::XS->new->pretty->encode ($cfg->{profile}{$name} || {});
713     },
714     showconfig => sub {
715     my $name = @ARGV ? shift @ARGV : AnyEvent::MP::Kernel::_nodename;
716    
717 root 1.25 my $profile = AnyEvent::MP::Config::find_profile $name, @ARGV;
718     @ARGV = ();
719 root 1.24
720     # make it look nicer:
721     delete $profile->{profile};
722     delete $profile->{parent};
723    
724     print JSON::XS->new->pretty->encode ($profile);
725 root 1.17 },
726 root 1.25
727     # undocumented
728     _resolve => sub {
729     print +(join ",", (AnyEvent::MP::Kernel::_resolve shift @ARGV)->recv), "\n";
730     },
731 root 1.1 );
732    
733     sub docmd {
734     my $cmd = shift @ARGV;
735    
736     $CMD{$cmd}
737 root 1.23 or die "$cmd: no such aemp command (try perldoc aemp, or man aemp)";
738 root 1.1
739     $CMD{$cmd}();
740     }
741    
742     @ARGV
743 root 1.23 or die "Usage: aemp subcommand ... (try perldoc aemp, or man aemp)\n";
744 root 1.1
745 root 1.19 docmd while @ARGV;
746 root 1.1
747