File Coverage

blib/lib/Daemon/Control.pm
Criterion Covered Total %
statement 78 336 23.2
branch 33 216 15.2
condition 3 38 7.8
subroutine 20 43 46.5
pod 18 26 69.2
total 152 659 23.0


line stmt bran cond sub pod time code
1             package Daemon::Control;
2              
3 2     2   66876 use strict;
  2         12  
  2         53  
4 2     2   10 use warnings;
  2         4  
  2         60  
5 2     2   986 use POSIX qw(_exit setsid setuid setgid getuid getgid);
  2         11468  
  2         10  
6 2     2   2892 use File::Spec;
  2         4  
  2         73  
7 2     2   11 use File::Path qw( make_path );
  2         5  
  2         165  
8 2     2   11 use Cwd 'abs_path';
  2         4  
  2         306  
9             require 5.008001; # Supporting 5.8.1+
10              
11             our $VERSION = '0.001009'; # 0.1.9
12             $VERSION = eval $VERSION;
13              
14             my @accessors = qw(
15             pid color_map name program program_args directory quiet
16             path scan_name stdout_file stderr_file pid_file fork data
17             lsb_start lsb_stop lsb_sdesc lsb_desc redirect_before_fork init_config
18             kill_timeout umask resource_dir help init_code
19             prereq_no_process foreground reload_signal stop_signals
20             );
21              
22             my $cmd_opt = "[start|stop|restart|reload|status|foreground|show_warnings|get_init_file|help]";
23              
24             # Accessor building
25              
26             for my $method ( @accessors ) {
27             my $accessor = sub {
28 24     24   33 my $self = shift;
29 24 100       49 $self->{$method} = shift if @_;
30 24         148 return $self->{$method};
31             };
32             {
33 2     2   12 no strict 'refs';
  2         9  
  2         7580  
34             *$method = $accessor;
35             }
36             }
37              
38             # As a result of not using a real object system for
39             # this, I don't get after user => sub { } style things,
40             # so I'm making my own triggers for user and group.
41              
42             sub user {
43 3     3 1 4 my $self = shift;
44              
45 3 50       7 if ( @_ ) {
46 0         0 $self->{user} = shift;
47 0         0 delete $self->{uid};
48             }
49              
50 3         86 return $self->{user};
51             }
52              
53             sub group {
54 4     4 1 8 my $self = shift;
55              
56 4 50       9 if ( @_ ) {
57 0         0 $self->{group} = shift;
58 0         0 delete $self->{gid};
59             }
60              
61 4         86 return $self->{group};
62             }
63              
64             sub uid {
65 1     1 1 1092 my $self = shift;
66              
67 1 50       5 return $self->{uid} = shift if @_;
68              
69 1 50       5 $self->_set_uid_from_name unless exists $self->{uid};
70              
71             return $self->{uid}
72 0         0 }
73              
74             sub gid {
75 1     1 1 672 my $self = shift;
76              
77 1 50       5 return $self->{gid} = shift if @_;
78              
79 1 50       6 $self->_set_gid_from_name unless exists $self->{gid};
80              
81             return $self->{gid}
82 0         0 }
83              
84             sub new {
85 1     1 0 910 my ( $class, @in ) = @_;
86              
87 1 50       6 my $args = ref $in[0] eq 'HASH' ? $in[0] : { @in };
88              
89             # Create the object with defaults.
90 1         11 my $self = bless {
91             color_map => { red => 31, green => 32 },
92             redirect_before_fork => 1,
93             kill_timeout => 1,
94             quiet => 0,
95             umask => 0,
96             foreground => 0,
97             reload_signal => 'HUP',
98             stop_signals => [ qw(TERM TERM INT KILL) ],
99             }, $class;
100              
101 1         5 for my $accessor ( @accessors, qw(uid gid user group) ) {
102 33 100       65 if ( exists $args->{$accessor} ) {
103 13         55 $self->{$accessor} = delete $args->{$accessor};
104             }
105             }
106              
107             # Shortcut caused by setting foreground or using the ENV to do it.
108 1 50 33     6 if ( ( $self->foreground == 1 ) || ( $ENV{DC_FOREGROUND} ) ) {
109 0         0 $self->fork( 0 );
110 0         0 $self->quiet( 1 );
111             }
112              
113 1 50       5 die "Unknown arguments to the constructor: " . join( " ", keys %$args )
114             if keys( %$args );
115              
116 1         5 return $self;
117             }
118              
119             sub with_plugins {
120 0     0 1 0 my ( $class, @in ) = @_;
121              
122             # ->with_plugins()->new is just ->new...
123 0 0       0 return $class unless @in;
124              
125             # Make sure we have Role::Tiny installed.
126 0         0 local $@;
127 0         0 eval "require Role::Tiny";
128 0 0       0 if ( $@ ) {
129 0         0 die "Error: Role::Tiny is required for with_plugins to function.\n";
130             }
131              
132             # Take an array or arrayref as an argument
133             # and mutate it into a list like this:
134             # 'Module' -> Becomes -> 'Root::Module'
135             # '+Module' -> Becomes -> 'Module'
136             my @plugins = map {
137 0 0       0 substr( $_, 0, 1 ) eq '+'
138             ? substr( $_, 1 )
139             : "Daemon::Control::Plugin::$_"
140 0 0       0 } ref $in[0] eq 'ARRAY' ? @{ $in[0] } : @in;
  0         0  
141              
142              
143             # Compose the plugins into our class, and return for the user
144             # to call ->new().
145 0         0 return Role::Tiny->create_class_with_roles( $class, @plugins );
146             }
147              
148             # Set the uid, triggered from getting the uid if the user has changed.
149             sub _set_uid_from_name {
150 1     1   3 my ( $self ) = @_;
151 1 50       3 return unless defined $self->user;
152              
153 1         3 my $uid = getpwnam( $self->user );
154 1 50       10 die "Error: Couldn't get uid for non-existent user " . $self->user
155             unless defined $uid;
156 0         0 $self->trace( "Set UID => $uid" );
157 0         0 $self->uid( $uid );
158             }
159              
160             # Set the uid, triggered from getting the gid if the group has changed.
161             sub _set_gid_from_name {
162 1     1   4 my ( $self ) = @_;
163              
164             # Grab the GID if we have a UID but no GID.
165 1 50 33     7 if ( !defined $self->group && defined $self->uid ) {
166 0         0 my ( $gid ) = ( (getpwuid( $self->uid ))[3] );
167 0         0 $self->gid( $gid );
168 0         0 $self->trace( "Implicit GID => $gid" );
169 0         0 return $gid;
170             }
171              
172 1 50       3 return unless defined $self->group;
173              
174 1         2 my $gid = getgrnam( $self->group );
175 1 50       9 die "Error: Couldn't get gid for non-existent group " . $self->group
176             unless defined $gid;
177 0         0 $self->trace( "Set GID => $gid" );
178 0         0 $self->gid( $gid );
179              
180             }
181              
182             sub redirect_filehandles {
183 0     0 0 0 my ( $self ) = @_;
184              
185 0 0       0 if ( $self->stdout_file ) {
186 0         0 my $file = $self->stdout_file;
187 0 0       0 $file = $file eq '/dev/null' ? File::Spec->devnull : $file;
188              
189 0 0       0 if ( ref $file eq 'ARRAY' ) {
190 0         0 my $mode = shift @$file;
191 0 0       0 open STDOUT, $mode, @$file ? @$file : ()
    0          
192             or die "Failed to open STDOUT with args $mode @$file: $!";
193              
194 0         0 $self->trace("STDOUT redirected to open(STDOUT $mode @$file)");
195             }
196             else {
197 0 0       0 open STDOUT, ">>", $file
198             or die "Failed to open STDOUT to $file: $!";
199 0         0 $self->trace( "STDOUT redirected to $file" );
200             }
201             }
202 0 0       0 if ( $self->stderr_file ) {
203 0         0 my $file = $self->stderr_file;
204 0 0       0 $file = $file eq '/dev/null' ? File::Spec->devnull : $file;
205              
206 0 0       0 if ( ref $file eq 'ARRAY' ) {
207 0         0 my $mode = shift @$file;
208 0 0       0 open STDERR, $mode, @$file ? @$file : ()
    0          
209             or die "Failed to open STDERR with args $mode @$file: $!";
210              
211 0         0 $self->trace("STDERR redirected to open(STDERR $mode @$file)");
212             }
213             else {
214 0 0       0 open STDERR, ">>", $file
215             or die "Failed to open STDERR to $file: $!";
216 0         0 $self->trace("STDERR redirected to $file");
217             }
218             }
219             }
220              
221             sub _create_resource_dir {
222 0     0   0 my ( $self ) = @_;
223 0         0 $self->_create_dir($self->resource_dir);
224             }
225              
226             sub _create_dir {
227 0     0   0 my ( $self, $dir ) = @_;
228              
229 0 0       0 return 0 unless defined $dir;
230 0 0       0 return 1 unless length($dir);
231              
232 0 0       0 if ( -d $dir ) {
233 0         0 $self->trace( "Dir exists (" . $dir . ") - no need to create" );
234 0         0 return 1;
235             }
236              
237 0         0 my ( $created ) = make_path(
238             $dir,
239             {
240             uid => $self->uid,
241             group => $self->gid,
242             error => \my $errors,
243             }
244             );
245              
246 0 0       0 if ( @$errors ) {
247 0         0 for my $error ( @$errors ) {
248 0         0 my ( $file, $msg ) = %$error;
249 0         0 die "Error creating $file: $msg";
250             }
251             }
252              
253 0 0       0 if ( $created eq $dir ) {
254 0         0 $self->trace( "Created dir (" . $dir . ")" );
255 0         0 return 1;
256             }
257              
258 0         0 $self->trace( "_create_dir() for $dir failed and I don't know why" );
259 0         0 return 0;
260             }
261              
262             sub _double_fork {
263 0     0   0 my ( $self ) = @_;
264 0         0 my $pid = fork();
265              
266 0         0 $self->trace( "_double_fork()" );
267 0 0       0 if ( $pid == 0 ) { # Child, launch the process here.
    0          
268 0         0 setsid(); # Become the process leader.
269 0         0 my $new_pid = fork();
270 0 0       0 if ( $new_pid == 0 ) { # Our double fork.
    0          
271              
272 0 0       0 if ( $self->gid ) {
273 0         0 setgid( $self->gid );
274 0         0 $self->trace( "setgid(" . $self->gid . ")" );
275             }
276              
277 0 0       0 if ( $self->uid ) {
278 0         0 setuid( $self->uid );
279              
280 0   0     0 $ENV{USER} = $self->user || getpwuid($self->uid);
281 0         0 $ENV{HOME} = ((getpwuid($self->uid))[7]);
282              
283 0         0 $self->trace( "setuid(" . $self->uid . ")" );
284 0         0 $self->trace( "\$ENV{USER} => " . $ENV{USER} );
285 0         0 $self->trace( "\$ENV{HOME} => " . $ENV{HOME} );
286             }
287              
288 0 0       0 if ( $self->umask ) {
289 0         0 umask( $self->umask);
290 0         0 $self->trace( "umask(" . $self->umask . ")" );
291             }
292              
293 0         0 open( STDIN, "<", File::Spec->devnull );
294              
295 0 0       0 if ( $self->redirect_before_fork ) {
296 0         0 $self->redirect_filehandles;
297             }
298              
299 0         0 $self->_launch_program;
300             } elsif ( not defined $new_pid ) {
301 0         0 warn "Cannot fork: $!";
302             } else {
303 0         0 $self->pid( $new_pid );
304 0         0 $self->trace("Set PID => $new_pid" );
305 0         0 $self->write_pid;
306 0         0 _exit 0;
307             }
308             } elsif ( not defined $pid ) { # We couldn't fork. =(
309 0         0 warn "Cannot fork: $!";
310             } else { # In the parent, $pid = child's PID, return it.
311 0         0 waitpid( $pid, 0 );
312             }
313 0         0 return $self;
314             }
315              
316 0     0   0 sub _foreground { shift->_launch_program }
317              
318             sub _fork {
319 0     0   0 my ( $self ) = @_;
320 0         0 my $pid = fork();
321              
322 0         0 $self->trace( "_fork()" );
323 0 0       0 if ( $pid == 0 ) { # Child, launch the process here.
    0          
324 0         0 $self->_launch_program;
325             } elsif ( not defined $pid ) {
326 0         0 warn "Cannot fork: $!";
327             } else { # In the parent, $pid = child's PID, return it.
328 0         0 $self->pid( $pid );
329 0         0 $self->trace("Set PID => $pid" );
330 0         0 $self->write_pid;
331             }
332 0         0 return $self;
333             }
334              
335             sub _launch_program {
336 0     0   0 my ($self) = @_;
337              
338 0 0       0 if ( $self->directory ) {
339 0         0 chdir( $self->directory );
340 0         0 $self->trace( "chdir(" . $self->directory . ")" );
341             }
342              
343 0 0       0 my @args = @{$self->program_args || [ ]};
  0         0  
344              
345 0 0       0 if ( ref $self->program eq 'CODE' ) {
346 0         0 $self->program->( $self, @args );
347             } else {
348 0 0       0 exec ( $self->program, @args )
349             or die "Failed to exec " . $self->program . " "
350             . join( " ", @args ) . ": $!";
351             }
352 0         0 return 0;
353             }
354              
355             sub write_pid {
356 0     0 1 0 my ( $self ) = @_;
357              
358             # Create the PID file as the user we currently are,
359             # and change the permissions to our target UID/GID.
360              
361 0         0 $self->_write_pid;
362              
363 0 0 0     0 if ( $self->uid && $self->gid ) {
364 0         0 chown $self->uid, $self->gid, $self->pid_file;
365 0         0 $self->trace("PID => chown(" . $self->uid . ", " . $self->gid .")");
366             }
367             }
368              
369             sub _write_pid {
370 0     0   0 my ( $self ) = @_;
371              
372 0         0 my ($volume, $dir, $file) = File::Spec->splitpath($self->pid_file);
373 0 0       0 return 0 if not $self->_create_dir($dir);
374              
375 0 0       0 open my $sf, ">", $self->pid_file
376             or die "Failed to write " . $self->pid_file . ": $!";
377 0         0 print $sf $self->pid;
378 0         0 close $sf;
379 0         0 $self->trace( "Wrote pid (" . $self->pid . ") to pid file (" . $self->pid_file . ")" );
380 0         0 return $self;
381             }
382              
383             sub read_pid {
384 0     0 1 0 my ( $self ) = @_;
385              
386             # If we don't have a PID file, we're going to set it
387             # to 0 -- this will prevent killing normal processes,
388             # and make is_running return false.
389 0 0       0 if ( ! -f $self->pid_file ) {
390 0         0 $self->pid( 0 );
391 0         0 return 0;
392             }
393              
394 0 0       0 open my $lf, "<", $self->pid_file
395             or die "Failed to read " . $self->pid_file . ": $!";
396 0         0 my $pid = do { local $/; <$lf> };
  0         0  
  0         0  
397 0         0 close $lf;
398 0         0 $self->pid( $pid );
399 0         0 return $pid;
400             }
401              
402             sub pid_running {
403 0     0 0 0 my ( $self, $pid ) = @_;
404              
405 0   0     0 $pid ||= $self->read_pid;
406              
407 0 0       0 return 0 unless $self->pid >= 1;
408 0 0       0 return 0 unless kill 0, $self->pid;
409              
410 0 0       0 if ( $self->scan_name ) {
411 0 0       0 open my $lf, "-|", "ps", "-w", "-w", "-p", $self->pid, "-o", "command="
412             or die "Failed to get pipe to ps for scan_name.";
413 0         0 while ( my $line = <$lf> ) {
414 0 0       0 return 1 if $line =~ $self->scan_name;
415             }
416 0         0 return 0;
417             }
418             # Scan name wasn't used, testing normal PID.
419 0         0 return kill 0, $self->pid;
420             }
421              
422             sub process_running {
423 0     0 0 0 my ( $self, $pattern ) = @_;
424              
425 0 0       0 my $psopt = $^O =~ m/bsd$/ ? '-ax' : '-u ' . $self->user;
426 0         0 my $ps = `LC_ALL=C command ps $psopt -o pid,args`;
427 0         0 $ps =~ s/^\s+//mg;
428 0         0 my @pids;
429 0         0 for my $line (split /\n/, $ps)
430             {
431 0 0       0 next if $line =~ m/^\D/;
432 0         0 my ($pid, $command, $args) = split /\s+/, $line, 3;
433              
434 0 0       0 next if $pid eq $$;
435 0 0 0     0 push @pids, $pid
      0        
436             if $command =~ $pattern
437             or defined $args and $args =~ $pattern;
438             }
439 0         0 return @pids;
440             }
441              
442             sub pretty_print {
443 0     0 1 0 my ( $self, $message, $color ) = @_;
444              
445 0 0       0 return if $self->quiet;
446              
447 0   0     0 $color ||= "green"; # Green is no color.
448 0   0     0 my $code = $self->color_map->{$color} ||= "32"; # Green is invalid.
449 0         0 local $| = 1;
450 0         0 printf( "%-49s %30s\n", $self->name, "\033[$code" ."m[$message]\033[0m" );
451             }
452              
453             # Callable Functions
454              
455             sub do_foreground {
456 0     0 1 0 my ( $self ) = @_;
457              
458             # Short cut to...
459 0         0 $self->fork( 0 );
460 0         0 $self->quiet( 1 );
461 0         0 return $self->do_start;
462             }
463              
464             sub do_start {
465 0     0 1 0 my ( $self ) = @_;
466              
467             # Optionally check if a process is already running with the same name
468 0 0       0 if ($self->prereq_no_process)
469             {
470 0         0 my $program = $self->program;
471 0 0       0 my $pattern = $self->prereq_no_process eq '1'
472             ? qr/\b${program}\b/
473             : $self->prereq_no_process;
474 0         0 my @pids = $self->process_running($pattern);
475 0 0       0 if (@pids)
476             {
477 0         0 $self->pretty_print( 'Duplicate Running? (pid ' . join(', ', @pids) . ')', "red" );
478 0         0 return 1;
479             }
480             }
481              
482             # Make sure the PID file exists.
483 0 0       0 if ( ! -f $self->pid_file ) {
484 0         0 $self->pid( 0 ); # Make PID invalid.
485 0         0 $self->write_pid();
486             }
487              
488             # Duplicate Check
489 0         0 $self->read_pid;
490 0 0 0     0 if ( $self->pid && $self->pid_running ) {
491 0         0 $self->pretty_print( "Duplicate Running", "red" );
492 0         0 return 1;
493             }
494              
495 0         0 $self->_create_resource_dir;
496              
497 0 0       0 $self->fork( 2 ) unless defined $self->fork;
498 0 0       0 $self->_double_fork if $self->fork == 2;
499 0 0       0 $self->_fork if $self->fork == 1;
500 0 0       0 $self->_foreground if $self->fork == 0;
501 0         0 $self->pretty_print( "Started" );
502 0         0 return 0;
503             }
504              
505             sub do_show_warnings {
506 1     1 0 1210 my ( $self ) = @_;
507              
508 1 50       5 if ( ! $self->fork ) {
509 1         5 warn "Fork undefined. Defaulting to fork => 2.\n";
510             }
511              
512 1 50       4 if ( ! $self->stdout_file ) {
513 0         0 warn "stdout_file undefined. Will not redirect file handle.\n";
514             }
515              
516 1 50       4 if ( ! $self->stderr_file ) {
517 0         0 warn "stderr_file undefined. Will not redirect file handle.\n";
518             }
519             }
520              
521             sub do_stop {
522 0     0 1 0 my ( $self ) = @_;
523              
524 0         0 $self->read_pid;
525 0         0 my $start_pid = $self->pid;
526              
527             # Probably don't want to send anything to init(1).
528 0 0       0 return 1 unless $start_pid > 1;
529              
530 0 0       0 if ( $self->pid_running($start_pid) ) {
531             SIGNAL:
532 0         0 foreach my $signal (@{ $self->stop_signals }) {
  0         0  
533 0         0 $self->trace( "Sending $signal signal to pid $start_pid..." );
534 0         0 kill $signal => $start_pid;
535              
536 0         0 for (1..$self->kill_timeout)
537             {
538             # abort early if the process is now stopped
539 0         0 $self->trace("checking if pid $start_pid is still running...");
540 0 0       0 last if not $self->pid_running($start_pid);
541 0         0 sleep 1;
542             }
543 0 0       0 last unless $self->pid_running($start_pid);
544             }
545 0 0       0 if ( $self->pid_running($start_pid) ) {
546 0         0 $self->pretty_print( "Failed to Stop", "red" );
547 0         0 return 1;
548             }
549 0         0 $self->pretty_print( "Stopped" );
550             } else {
551 0         0 $self->pretty_print( "Not Running", "red" );
552             }
553              
554             # Clean up the PID file on stop, unless the pid
555             # doesn't match $start_pid (perhaps a standby
556             # worker stepped in to take over from the one
557             # that was just terminated).
558              
559 0 0       0 if ( $self->pid_file ) {
560 0 0       0 unlink($self->pid_file) if $self->read_pid == $start_pid;
561             }
562 0         0 return 0;
563             }
564              
565             sub do_restart {
566 0     0 1 0 my ( $self ) = @_;
567 0         0 $self->read_pid;
568              
569 0 0       0 if ( $self->pid_running ) {
570 0         0 $self->do_stop;
571             }
572 0         0 $self->do_start;
573 0         0 return 0;
574             }
575              
576             sub do_status {
577 0     0 1 0 my ( $self ) = @_;
578 0         0 $self->read_pid;
579              
580 0 0 0     0 if ( $self->pid && $self->pid_running ) {
581 0         0 $self->pretty_print( "Running" );
582 0         0 return 0;
583             } else {
584 0         0 $self->pretty_print( "Not Running", "red" );
585 0         0 return 3;
586             }
587             }
588              
589             sub do_reload {
590 0     0 1 0 my ( $self ) = @_;
591 0         0 $self->read_pid;
592              
593 0 0 0     0 if ( $self->pid && $self->pid_running ) {
594 0         0 kill $self->reload_signal, $self->pid;
595 0         0 $self->pretty_print( "Reloaded" );
596 0         0 return 0;
597             } else {
598 0         0 $self->pretty_print( "Not Running", "red" );
599 0         0 return 1;
600             }
601             }
602              
603             sub do_get_init_file {
604 1     1 1 1099 shift->dump_init_script;
605 1         3 return 0;
606             }
607              
608             sub do_help {
609 1     1 0 1402 my ( $self ) = @_;
610              
611 1         7 print "Syntax: $0 $cmd_opt\n\n";
612 1 50       4 print $self->help if $self->help;
613 1         3 return 0;
614             }
615              
616             sub dump_init_script {
617 1     1 1 3 my ( $self ) = @_;
618 1 50       4 if ( ! $self->data ) {
619 1         1 my $data;
620 1         6 while ( my $line = ) {
621 26 100       47 last if $line =~ /^__END__$/;
622 25         51 $data .= $line;
623             }
624 1         4 $self->data( $data );
625             }
626              
627             # So, instead of expanding run_template to use a real DSL
628             # or making TT a dependancy, I'm just going to fake template
629             # IF logic.
630 1 50       4 my $init_source_file = $self->init_config
631             ? $self->run_template(
632             '[ -r [% FILE %] ] && . [% FILE %]',
633             { FILE => $self->init_config } )
634             : "";
635              
636 1 50 50     4 $self->data( $self->run_template(
    50          
    50          
    50          
    50          
    50          
    50          
637             $self->data,
638             {
639             HEADER => 'Generated at ' . scalar(localtime)
640             . ' with Daemon::Control ' . ($self->VERSION || 'DEV'),
641             NAME => $self->name ? $self->name : "",
642             REQUIRED_START => $self->lsb_start ? $self->lsb_start : "",
643             REQUIRED_STOP => $self->lsb_stop ? $self->lsb_stop : "",
644             SHORT_DESCRIPTION => $self->lsb_sdesc ? $self->lsb_sdesc : "",
645             DESCRIPTION => $self->lsb_desc ? $self->lsb_desc : "",
646             SCRIPT => $self->path ? $self->path : abs_path($0),
647             INIT_SOURCE_FILE => $init_source_file,
648             INIT_CODE_BLOCK => $self->init_code ? $self->init_code : "",
649             }
650             ));
651 1         6 print $self->data;
652             }
653              
654             sub run_template {
655 1     1 0 3 my ( $self, $content, $config ) = @_;
656              
657 1         40 $content =~ s/\[% (.*?) %\]/$config->{$1}/g;
658              
659 1         7 return $content;
660             }
661              
662              
663              
664             sub run_command {
665 0     0 1   my ( $self, $arg ) = @_;
666              
667             # Error Checking.
668 0 0         if ( ! $self->program ) {
669 0           die "Error: program must be defined.";
670             }
671 0 0         if ( ! $self->pid_file ) {
672 0           die "Error: pid_file must be defined.";
673             }
674 0 0         if ( ! $self->name ) {
675 0           die "Error: name must be defined.";
676             }
677              
678 0   0       my $called_with = $arg || "help";
679 0           $called_with =~ s/^[-]+//g; # Allow people to do --command too.
680              
681 0 0         my $action = "do_" . ($called_with ? $called_with : "" );
682              
683 0           my $allowed_actions = "Must be called with an action: $cmd_opt";
684              
685 0 0         if ( $self->can($action) ) {
    0          
686 0           return $self->$action;
687             } elsif ( ! $called_with ) {
688 0           die $allowed_actions
689             } else {
690 0           die "Error: undefined action $called_with. $allowed_actions";
691             }
692              
693             }
694              
695             # Application Code.
696             sub run {
697 0     0 1   exit shift->run_command( @ARGV );
698             }
699              
700             sub trace {
701 0     0 0   my ( $self, $message ) = @_;
702              
703 0 0         return unless $ENV{DC_TRACE};
704              
705 0 0         print "[TRACE] $message\n" if $ENV{DC_TRACE} == 1;
706 0 0         print STDERR "[TRACE] $message\n" if $ENV{DC_TRACE} == 2;
707             }
708              
709             1;
710              
711             __DATA__