File Coverage

lib/Provision/Unix/Utility.pm
Criterion Covered Total %
statement 28 30 93.3
branch n/a
condition n/a
subroutine 10 10 100.0
pod n/a
total 38 40 95.0


line stmt bran cond sub pod time code
1             package Provision::Unix::Utility;
2             {
3             $Provision::Unix::Utility::VERSION = '1.07';
4             }
5             # ABSTRACT: utility subroutines for sysadmin tasks
6              
7 2     2   3796 use strict;
  2         5  
  2         76  
8 2     2   13 use warnings;
  2         3  
  2         60  
9              
10 2     2   10 use Cwd;
  2         5  
  2         153  
11 2     2   10 use English qw( -no_match_vars );
  2         5  
  2         16  
12 2     2   987 use File::Basename;
  2         5  
  2         250  
13 2     2   2408 use File::Copy;
  2         6306  
  2         153  
14 2     2   16 use File::Path 2.08 qw/ make_path /;
  2         54  
  2         118  
15 2     2   12 use File::Spec;
  2         4  
  2         41  
16 2     2   1041 use File::stat;
  2         10472  
  2         17  
17 2     2   1167 use Params::Validate qw(:all);
  0            
  0            
18             use Scalar::Util qw( openhandle );
19             use URI;
20              
21             use lib 'lib';
22             use vars qw/ $log %std_opts /;
23              
24             sub new {
25             my $class = shift;
26             my %p = validate( @_,
27             { 'log' => { type => OBJECT, optional => 1 },
28             fatal => { type => BOOLEAN, optional => 1, default => 1 },
29             debug => { type => BOOLEAN, optional => 1, default => 1 },
30             }
31             );
32              
33             $log = $p{'log'};
34             if ( ! $log ) {
35             my @bits = split '::', $class; pop @bits;
36             my $parent_class = join '::', grep { defined $_ } @bits;
37             ## no critic ( ProhibitStringyEval )
38             eval "require $parent_class";
39             ## use critic
40             $log = $parent_class->new();
41             };
42              
43             my $debug = $log->get_debug; # inherit from our parent
44             my $fatal = $log->get_fatal;
45             $debug = $p{debug} if defined $p{debug}; # explicity overridden
46             $fatal = $p{fatal} if defined $p{fatal};
47              
48             my $self = {
49             'log' => $log,
50             debug => $debug,
51             fatal => $fatal,
52             };
53             bless $self, $class;
54              
55             # globally scoped hash, populated with defaults as requested by the caller
56             %std_opts = (
57             'test_ok' => { type => BOOLEAN, optional => 1 },
58             'fatal' => { type => BOOLEAN, optional => 1, default => $fatal },
59             'debug' => { type => BOOLEAN, optional => 1, default => $debug },
60             );
61              
62             $log->audit( $class . sprintf( " loaded by %s, %s, %s", caller ) );
63             return $self;
64             }
65              
66             sub ask {
67             my $self = shift;
68             my $question = shift;
69             my %p = validate(
70             @_,
71             { default => { type => SCALAR|UNDEF, optional => 1 },
72             timeout => { type => SCALAR, optional => 1 },
73             password => { type => BOOLEAN, optional => 1, default => 0 },
74             test_ok => { type => BOOLEAN, optional => 1 },
75             }
76             );
77              
78             my $pass = $p{password};
79             my $default = $p{default};
80              
81             if ( ! $self->is_interactive() ) {
82             $log->audit( "not running interactively, can not prompt!");
83             return $default;
84             }
85              
86             return $log->error( "ask called with \'$question\' which looks unsafe." )
87             if $question !~ m{\A \p{Any}* \z}xms;
88              
89             my $response;
90              
91             return $p{test_ok} if defined $p{test_ok};
92              
93             PROMPT:
94             print "Please enter $question";
95             print " [$default]" if ( $default && !$pass );
96             print ": ";
97              
98             system "stty -echo" if $pass;
99              
100             if ( $p{timeout} ) {
101             eval {
102             local $SIG{ALRM} = sub { die "alarm\n" };
103             alarm $p{timeout};
104             $response = <STDIN>;
105             alarm 0;
106             };
107             if ($EVAL_ERROR) {
108             $EVAL_ERROR eq "alarm\n" ? print "timed out!\n" : warn;
109             }
110             }
111             else {
112             $response = <STDIN>;
113             }
114              
115             if ( $pass ) {
116             print "Please enter $question (confirm): ";
117             my $response2 = <STDIN>;
118             unless ( $response eq $response2 ) {
119             print "\nPasswords don't match, try again.\n";
120             goto PROMPT;
121             }
122             system "stty echo";
123             print "\n";
124             }
125              
126             chomp $response;
127              
128             return $response if defined $response; # if they typed something, return it
129             return $default if defined $default; # return the default, if available
130             return ''; # return empty handed
131             }
132              
133             sub archive_file {
134             my $self = shift;
135             my $file = shift or return $log->error("missing filename in request");
136             my %p = validate( @_,
137             { %std_opts,
138             'sudo' => { type => BOOLEAN, optional => 1, default => 1 },
139             'mode' => { type => SCALAR, optional => 1 },
140             destdir => { type => SCALAR, optional => 1 },
141             }
142             );
143              
144             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
145              
146             return $log->error( "file ($file) is missing!", %args )
147             if !-e $file;
148              
149             my $archive = $file . time;
150              
151             if ( $p{destdir} && -d $p{destdir} ) {
152             my ($vol,$dirs,$file_wo_path) = File::Spec->splitpath( $archive );
153             $archive = File::Spec->catfile( $p{destdir}, $file_wo_path );
154             };
155              
156             # see if we can write to both files (new & archive) with current user
157             if ( $self->is_writable( $file, %args )
158             && $self->is_writable( $archive, %args ) ) {
159              
160             # we have permission, use perl's native copy
161             copy( $file, $archive );
162             if ( -e $archive ) {
163             $log->audit("archive_file: $file backed up to $archive");
164             $self->chmod( file => $file, mode => $p{mode}, %args ) if $p{mode};
165             return $archive;
166             };
167             }
168              
169             # we failed with existing permissions, try to escalate
170             $self->archive_file_sudo( $file ) if ( $p{sudo} && $< != 0 );
171              
172             return $log->error( "backup of $file to $archive failed: $!", %args)
173             if ! -e $archive;
174              
175             $self->chmod( file => $file, mode => $p{mode}, %args ) if $p{mode};
176              
177             $log->audit("$file backed up to $archive");
178             return $archive;
179             }
180              
181             sub archive_file_sudo {
182             my $self = shift;
183             my ($file, $archive) = @_;
184              
185             my $sudo = $self->sudo();
186             my $cp = $self->find_bin( 'cp',fatal=>0 );
187              
188             if ( $sudo && $cp ) {
189             return $self->syscmd( "$sudo $cp $file $archive",fatal=>0 );
190             }
191             $log->error( "archive_file: sudo or cp was missing, could not escalate.",fatal=>0);
192             return;
193             };
194              
195             sub chmod {
196             my $self = shift;
197             my %p = validate(
198             @_,
199             { 'file' => { type => SCALAR, optional => 1, },
200             'file_or_dir' => { type => SCALAR, optional => 1, },
201             'dir' => { type => SCALAR, optional => 1, },
202             'mode' => { type => SCALAR, optional => 0, },
203             'sudo' => { type => BOOLEAN, optional => 1, default => 0 },
204             'fatal' => { type => BOOLEAN, optional => 1, default => 1 },
205             'debug' => { type => BOOLEAN, optional => 1, default => 1 },
206             'test_ok' => { type => BOOLEAN, optional => 1 },
207             }
208             );
209              
210             my $mode = $p{mode};
211             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
212              
213             my $file = $p{file} || $p{file_or_dir} || $p{dir}
214             or return $log->error( "invalid params to chmod in ". ref $self );
215              
216             if ( $p{sudo} ) {
217             my $chmod = $self->find_bin( 'chmod', debug => 0 );
218             my $sudo = $self->sudo();
219             $self->syscmd( "$sudo $chmod $mode $file", debug => 0 )
220             or return $log->error( "couldn't chmod $file: $!", %args );
221             }
222              
223             # note the conversion of ($mode) to an octal value. Very important!
224             CORE::chmod( oct($mode), $file ) or
225             return $log->error( "couldn't chmod $file: $!", %args);
226              
227             $log->audit("chmod $mode $file");
228             }
229              
230             sub chown {
231             my $self = shift;
232             my $file = shift;
233             my %p = validate( @_,
234             { 'uid' => { type => SCALAR },
235             'gid' => { type => SCALAR },
236             'sudo' => { type => BOOLEAN, optional => 1 },
237             %std_opts,
238             }
239             );
240              
241             my ( $uid, $gid, $sudo ) = ( $p{uid}, $p{gid}, $p{sudo} );
242             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
243              
244             $file or return $log->error( "missing file or dir", %args );
245             return $log->error( "file $file does not exist!", %args ) if ! -e $file;
246              
247             $log->audit("chown: preparing to chown $uid $file");
248              
249             # sudo forces system chown instead of the perl builtin
250             return $self->chown_system( $file,
251             %args,
252             user => $uid,
253             group => $gid,
254             ) if $sudo;
255              
256             my ( $nuid, $ngid ); # if uid or gid is not numeric, convert it
257              
258             if ( $uid =~ /\A[0-9]+\z/ ) {
259             $nuid = int($uid);
260             $log->audit(" using $nuid from int($uid)");
261             }
262             else {
263             $nuid = getpwnam($uid);
264             return $log->error( "failed to get uid for $uid", %args) if ! defined $nuid;
265             $log->audit(" converted $uid to a number: $nuid");
266             }
267              
268             if ( $gid =~ /\A[0-9\-]+\z/ ) {
269             $ngid = int( $gid );
270             $log->audit(" using $ngid from int($gid)");
271             }
272             else {
273             $ngid = getgrnam( $gid );
274             return $log->error( "failed to get gid for $gid", %args) if ! defined $ngid;
275             $log->audit(" converted $gid to numeric: $ngid");
276             }
277              
278             chown( $nuid, $ngid, $file )
279             or return $log->error( "couldn't chown $file: $!",%args);
280              
281             return 1;
282             }
283              
284             sub chown_system {
285             my $self = shift;
286             my $dir = shift;
287             my %p = validate( @_,
288             { 'user' => { type => SCALAR, optional => 0, },
289             'group' => { type => SCALAR, optional => 1, },
290             'recurse' => { type => BOOLEAN, optional => 1, },
291             %std_opts,
292             }
293             );
294              
295             my ( $user, $group, $recurse ) = ( $p{user}, $p{group}, $p{recurse} );
296             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
297              
298             $dir or return $log->error( "missing file or dir", %args );
299             my $cmd = $self->find_bin( 'chown', %args );
300              
301             $cmd .= " -R" if $recurse;
302             $cmd .= " $user";
303             $cmd .= ":$group" if $group;
304             $cmd .= " $dir";
305              
306             $log->audit( "cmd: $cmd" );
307              
308             $self->syscmd( $cmd, %args ) or
309             return $log->error( "couldn't chown with $cmd: $!", %args);
310              
311             my $mess;
312             $mess .= "Recursively " if $recurse;
313             $mess .= "changed $dir to be owned by $user";
314             $log->audit( $mess );
315              
316             return 1;
317             }
318              
319             sub clean_tmp_dir {
320             my $self = shift;
321             my %p = validate(
322             @_,
323             { 'dir' => { type => SCALAR, optional => 0, },
324             'fatal' => { type => BOOLEAN, optional => 1, default => 1 },
325             'debug' => { type => BOOLEAN, optional => 1, default => 1 },
326             }
327             );
328              
329             my $dir = $p{dir};
330             my ($debug, $fatal) = ($p{debug}, $p{fatal});
331              
332             my $before = cwd; # remember where we started
333              
334             return $log->error( "couldn't chdir to $dir: $!", fatal => $fatal )
335             if !chdir $dir;
336              
337             foreach ( $self->get_dir_files( dir => $dir ) ) {
338             next unless $_;
339              
340             my ($file) = $_ =~ /^(.*)$/;
341              
342             $log->audit( "deleting file $file" );
343              
344             if ( -f $file ) {
345             unlink $file or
346             $self->file_delete( file => $file, debug => $debug );
347             }
348             elsif ( -d $file ) {
349             use File::Path;
350             rmtree $file or return $log->error( "couldn't delete $file");
351             }
352             else {
353             $log->audit( "Cannot delete unknown entity: $file" );
354             }
355             }
356              
357             chdir $before;
358             return 1;
359             }
360              
361             sub cwd_source_dir {
362             my $self = shift;
363             my $dir = shift or die "missing dir in request\n";
364             my %p = validate( @_,
365             { 'src' => { type => SCALAR, optional => 1, },
366             'sudo' => { type => BOOLEAN, optional => 1, },
367             %std_opts,
368             }
369             );
370              
371             my ( $src, $sudo, ) = ( $p{src}, $p{sudo}, );
372             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
373              
374             return $log->error( "Something (other than a directory) is at $dir and " .
375             "that's my build directory. Please remove it and try again!", %args )
376             if ( -e $dir && !-d $dir );
377              
378             if ( !-d $dir ) {
379              
380             _try_mkdir( $dir ); # use the perl builtin mkdir
381              
382             if ( !-d $dir ) {
383             $log->audit( "trying again with system mkdir...");
384             $self->mkdir_system( dir => $dir, %args);
385              
386             if ( !-d $dir ) {
387             $log->audit( "trying one last time with $sudo mkdir -p....");
388             $self->mkdir_system( dir => $dir, sudo => 1, %args)
389             or return $log->error("Couldn't create $dir.", %args);
390             }
391             }
392             }
393              
394             chdir $dir or return $log->error( "failed to cd to $dir: $!", %args);
395             return 1;
396             }
397              
398             sub _try_mkdir {
399             my ( $dir ) = @_;
400             mkpath( $dir, 0, oct('0755') )
401             or return $log->error( "mkdir $dir failed: $!");
402             $log->audit( "created $dir");
403             return 1;
404             }
405              
406             sub extract_archive {
407             my $self = shift;
408             my $archive = shift or die "missing archive name";
409             my %p = validate( @_, { %std_opts } );
410              
411             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
412             my $r;
413              
414             if ( !-e $archive ) {
415             if ( -e "$archive.tar.gz" ) { $archive = "$archive.tar.gz" }
416             elsif ( -e "$archive.tgz" ) { $archive = "$archive.tgz" }
417             elsif ( -e "$archive.tar.bz2" ) { $archive = "$archive.tar.bz2" }
418             else {
419             return $log->error( "file $archive is missing!", %args );
420             }
421             }
422              
423             $log->audit("found $archive");
424              
425             $ENV{PATH} = '/bin:/usr/bin'; # do this or taint checks will blow up on ``
426              
427             return $log->error( "unknown archive type: $archive", %args )
428             if $archive !~ /[bz2|gz]$/;
429              
430             # find these binaries, we need them to inspect and expand the archive
431             my $tar = $self->find_bin( 'tar', %args );
432             my $file = $self->find_bin( 'file', %args );
433              
434             my %types = (
435             gzip => { bin => 'gunzip', content => 'gzip', },
436             bzip => { bin => 'bunzip2', content => 'b(un)?zip2', },
437             # on BSD bunzip2, on Linux bzip2
438             );
439              
440             my $type
441             = $archive =~ /bz2$/ ? 'bzip'
442             : $archive =~ /gz$/ ? 'gzip'
443             : return $log->error( 'unknown archive type', %args);
444              
445             # make sure the archive contents match the file extension
446             return $log->error( "$archive not a $type compressed file", %args)
447             unless grep ( /$types{$type}{content}/, `$file $archive` );
448              
449             my $bin = $self->find_bin( $types{$type}{bin}, %args);
450              
451             $self->syscmd( "$bin -c $archive | $tar -xf -" ) or return;
452              
453             $log->audit( "extracted $archive" );
454             return 1;
455             }
456              
457             sub file_delete {
458             my $self = shift;
459             my %p = validate( @_,
460             { 'file' => { type => SCALAR },
461             'sudo' => { type => BOOLEAN, optional => 1, default => 0 },
462             %std_opts,
463             }
464             );
465              
466             my $file = $p{file};
467             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
468              
469             return $log->error( "$file does not exist", %args ) if !-e $file;
470              
471             if ( -w $file ) {
472             $log->audit( "write permission to $file: ok" );
473              
474             unlink $file or return $log->error( "failed to delete $file", %args );
475              
476             $log->audit( "deleted: $file" );
477             return 1;
478             }
479              
480             if ( !$p{sudo} ) { # all done
481             return -e $file ? undef : 1;
482             }
483              
484             my $err = "trying with system rm";
485             my $rm_command = $self->find_bin( "rm", %args );
486             $rm_command .= " -f $file";
487              
488             if ( $< != 0 ) { # we're not running as root
489             my $sudo = $self->sudo( %args );
490             $rm_command = "$sudo $rm_command";
491             $err .= " (sudo)";
492             }
493              
494             $self->syscmd( $rm_command, %args )
495             or return $log->error( $err, %args );
496              
497             return -e $file ? 0 : 1;
498             }
499              
500             sub file_is_newer {
501             my $self = shift;
502             my %p = validate(
503             @_,
504             { f1 => { type => SCALAR },
505             f2 => { type => SCALAR },
506             %std_opts,
507             }
508             );
509              
510             my ( $file1, $file2 ) = ( $p{f1}, $p{f2} );
511              
512             # get file attributes via stat
513             # (dev,ino,mode,nlink,uid,gid,rdev,size,atime,mtime,ctime,blksize,blocks)
514              
515             $log->audit( "checking age of $file1 and $file2" );
516              
517             my $stat1 = stat($file1)->mtime;
518             my $stat2 = stat($file2)->mtime;
519              
520             $log->audit( "timestamps are $stat1 and $stat2");
521              
522             return 1 if ( $stat2 > $stat1 );
523             return;
524              
525             # I could just:
526             #
527             # if ( stat($f1)[9] > stat($f2)[9] )
528             #
529             # but that forces the reader to read the man page for stat
530             # to see what's happening
531             }
532              
533             sub file_read {
534             my $self = shift;
535             my $file = shift or return $log->error("missing filename in request");
536             my %p = validate(
537             @_,
538             { 'max_lines' => { type => SCALAR, optional => 1 },
539             'max_length' => { type => SCALAR, optional => 1 },
540             'fatal' => { type => BOOLEAN, optional => 1, default => 1 },
541             'debug' => { type => BOOLEAN, optional => 1, default => 1 },
542             }
543             );
544              
545             my ( $max_lines, $max_length ) = ( $p{max_lines}, $p{max_length} );
546             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
547              
548             return $log->error( "$file does not exist!", %args) if !-e $file;
549             return $log->error( "$file is not readable", %args ) if !-r $file;
550              
551             open my $FILE, '<', $file or
552             return $log->error( "could not open $file: $OS_ERROR", %args );
553              
554             my ( $line, @lines );
555              
556             if ( ! $max_lines) {
557             chomp( @lines = <$FILE> );
558             close $FILE;
559             return @lines;
560             # TODO: make max_length work with slurp mode, without doing something ugly like
561             # reading in the entire line and then truncating it.
562             };
563              
564             while ( my $i < $max_lines ) {
565             if ($max_length) { $line = substr <$FILE>, 0, $max_length; }
566             else { $line = <$FILE>; };
567             push @lines, $line;
568             $i++;
569             }
570             chomp @lines;
571             close $FILE;
572             return @lines;
573             }
574              
575             sub file_mode {
576             my $self = shift;
577             my %p = validate(
578             @_,
579             { 'file' => { type => SCALAR },
580             'fatal' => { type => BOOLEAN, optional => 1, default => 1 },
581             'debug' => { type => BOOLEAN, optional => 1, default => 0 },
582             }
583             );
584              
585             my $file = $p{file};
586             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
587              
588             return $log->error( "file '$file' does not exist!", %args)
589             if !-e $file;
590              
591             # one way to get file mode (using File::mode)
592             # my $raw_mode = stat($file)->[2];
593             ## no critic
594             my $mode = sprintf "%04o", stat($file)->[2] & 07777;
595              
596             # another way to get it
597             # my $st = stat($file);
598             # my $mode = sprintf "%lo", $st->mode & 07777;
599              
600             $log->audit( "file $file has mode: $mode" );
601             return $mode;
602             }
603              
604             sub file_write {
605             my $self = shift;
606             my $file = shift or return $log->error("missing filename in request");
607             my %p = validate(
608             @_,
609             { 'lines' => { type => ARRAYREF },
610             'append' => { type => BOOLEAN, optional => 1, default => 0 },
611             'mode' => { type => SCALAR, optional => 1 },
612             'fatal' => { type => BOOLEAN, optional => 1, default => $self->{fatal} },
613             'debug' => { type => BOOLEAN, optional => 1, default => $self->{debug} },
614             }
615             );
616              
617             my $append = $p{append};
618             my $lines = $p{lines};
619             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
620              
621             return $log->error( "oops, $file is a directory", %args) if -d $file;
622             return $log->error( "oops, $file is not writable", %args )
623             if ( ! $self->is_writable( $file, %args) );
624              
625             my $m = "wrote";
626             my $write_mode = '>'; # (over)write
627              
628             if ( $append ) {
629             $m = "appended";
630             $write_mode = '>>';
631             if ( -f $file ) {
632             copy $file, "$file.tmp" or return $log->error(
633             "couldn't create $file.tmp for safe append", %args );
634             };
635             };
636              
637             open my $HANDLE, $write_mode, "$file.tmp"
638             or return $log->error( "file_write: couldn't open $file: $!", %args );
639              
640             my $c = 0;
641             foreach ( @$lines ) { chomp; print $HANDLE "$_\n"; $c++ };
642             close $HANDLE or return $log->error( "couldn't close $file: $!", %args );
643              
644             $log->audit( "file_write: $m $c lines to $file", %args );
645              
646             move( "$file.tmp", $file )
647             or return $log->error(" unable to update $file", %args);
648              
649             # set file permissions mode if requested
650             $self->chmod( file => $file, mode => $p{mode}, %args )
651             or return if $p{mode};
652              
653             return 1;
654             }
655              
656             sub files_diff {
657             my $self = shift;
658             my %p = validate(
659             @_,
660             { f1 => { type => SCALAR },
661             f2 => { type => SCALAR },
662             type => { type => SCALAR, optional => 1, default => 'text' },
663             fatal => { type => BOOLEAN, optional => 1, default => $self->{fatal} },
664             debug => { type => BOOLEAN, optional => 1, default => $self->{debug} },
665             }
666             );
667              
668             my ( $f1, $f2, $type, $debug ) = ( $p{f1}, $p{f2}, $p{type}, $p{debug} );
669             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
670              
671             if ( !-e $f1 || !-e $f2 ) {
672             $log->error( "$f1 or $f2 does not exist!", %args );
673             return -1;
674             };
675              
676             return $self->files_diff_md5( $f1, $f2, \%args)
677             if $type ne "text";
678              
679             ### TODO
680             # use file here to make sure files are ASCII
681             #
682             $log->audit("comparing ascii files $f1 and $f2 using diff", %args);
683              
684             my $diff = $self->find_bin( 'diff', %args );
685             my $r = `$diff $f1 $f2`;
686             chomp $r;
687             return $r;
688             };
689              
690             sub files_diff_md5 {
691             my $self = shift;
692             my ($f1, $f2, $args) = @_;
693              
694             $log->audit("comparing $f1 and $f2 using md5", %$args);
695              
696             eval { require Digest::MD5 };
697             return $log->error( "couldn't load Digest::MD5!", %$args )
698             if $EVAL_ERROR;
699              
700             $log->audit( "\t Digest::MD5 loaded", %$args );
701              
702             my @md5sums;
703              
704             foreach my $f ( $f1, $f2 ) {
705             my ( $sum, $changed );
706              
707             # if the md5 file exists
708             if ( -f "$f.md5" ) {
709             $sum = $self->file_read( "$f.md5", %$args );
710             $log->audit( " md5 file for $f exists", %$args );
711             }
712              
713             # if the md5 file is missing, invalid, or older than the file, recompute it
714             if ( ! -f "$f.md5" or $sum !~ /[0-9a-f]+/i or
715             $self->file_is_newer( f1 => "$f.md5", f2 => $f, %$args )
716             )
717             {
718             my $ctx = Digest::MD5->new;
719             open my $FILE, '<', $f;
720             $ctx->addfile(*$FILE);
721             $sum = $ctx->hexdigest;
722             close $FILE;
723             $changed++;
724             $log->audit(" calculated md5: $sum", %$args);
725             }
726              
727             push( @md5sums, $sum );
728             $self->file_write( "$f.md5", lines => [$sum], %$args ) if $changed;
729             }
730              
731             return if $md5sums[0] eq $md5sums[1];
732             return 1;
733             }
734              
735             sub find_bin {
736             my $self = shift;
737             my $bin = shift or die "missing argument to find_bin\n";
738             my %p = validate(
739             @_,
740             { 'dir' => { type => SCALAR, optional => 1, },
741             %std_opts,
742             },
743             );
744              
745             my $prefix = "/usr/local";
746             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
747              
748             if ( $bin =~ /^\// && -x $bin ) { # we got a full path
749             $log->audit( "find_bin: found $bin", %args );
750             return $bin;
751             };
752              
753             my @prefixes;
754             push @prefixes, $p{dir} if $p{dir};
755             push @prefixes, qw"
756             /usr/local/bin /usr/local/sbin/ /opt/local/bin /opt/local/sbin
757             $prefix/mysql/bin /bin /usr/bin /sbin /usr/sbin
758             ";
759             push @prefixes, cwd;
760              
761             my $found;
762             foreach my $prefix ( @prefixes ) {
763             if ( -x "$prefix/$bin" ) {
764             $found = "$prefix/$bin" and last;
765             };
766             };
767              
768             if ($found) {
769             $log->audit( "find_bin: found $found", %args);
770             return $found;
771             }
772              
773             return $log->error( "find_bin: could not find $bin", %args);
774             }
775              
776             sub fstab_list {
777             my $self = shift;
778             my %p = validate(
779             @_,
780             { 'fatal' => { type => BOOLEAN, optional => 1, default => 1 },
781             'debug' => { type => BOOLEAN, optional => 1, default => 1 },
782             }
783             );
784              
785             if ( $OSNAME eq "darwin" ) {
786             return ['fstab not used on Darwin!'];
787             }
788              
789             my $fstab = "/etc/fstab";
790             if ( !-e $fstab ) {
791             print "fstab_list: FAILURE: $fstab does not exist!\n" if $p{debug};
792             return;
793             }
794              
795             my $grep = $self->find_bin( "grep", debug => 0 );
796             my @fstabs = `$grep -v cdr $fstab`;
797              
798             # foreach my $fstab (@fstabs)
799             # {}
800             # my @fields = split(" ", $fstab);
801             # #print "device: $fields[0] mount: $fields[1]\n";
802             # {};
803             # print "\n\n END of fstabs\n\n";
804              
805             return \@fstabs;
806             }
807              
808             sub get_cpan_config {
809              
810             my $ftp = `which ftp`; chomp $ftp;
811             my $gzip = `which gzip`; chomp $gzip;
812             my $unzip = `which unzip`; chomp $unzip;
813             my $tar = `which tar`; chomp $tar;
814             my $make = `which make`; chomp $make;
815             my $wget = `which wget`; chomp $wget;
816              
817             return
818             {
819             'build_cache' => q[10],
820             'build_dir' => qq[$ENV{HOME}/.cpan/build],
821             'cache_metadata' => q[1],
822             'cpan_home' => qq[$ENV{HOME}/.cpan],
823             'ftp' => $ftp,
824             'ftp_proxy' => q[],
825             'getcwd' => q[cwd],
826             'gpg' => q[],
827             'gzip' => $gzip,
828             'histfile' => qq[$ENV{HOME}/.cpan/histfile],
829             'histsize' => q[100],
830             'http_proxy' => q[],
831             'inactivity_timeout' => q[5],
832             'index_expire' => q[1],
833             'inhibit_startup_message' => q[1],
834             'keep_source_where' => qq[$ENV{HOME}/.cpan/sources],
835             'lynx' => q[],
836             'make' => $make,
837             'make_arg' => q[],
838             'make_install_arg' => q[],
839             'makepl_arg' => q[],
840             'ncftp' => q[],
841             'ncftpget' => q[],
842             'no_proxy' => q[],
843             'pager' => q[less],
844             'prerequisites_policy' => q[follow],
845             'scan_cache' => q[atstart],
846             'shell' => q[/bin/csh],
847             'tar' => $tar,
848             'term_is_latin' => q[1],
849             'unzip' => $unzip,
850             'urllist' => [ 'http://www.perl.com/CPAN/', 'ftp://cpan.cs.utah.edu/pub/CPAN/', 'ftp://mirrors.kernel.org/pub/CPAN', 'ftp://osl.uoregon.edu/CPAN/', 'http://cpan.yahoo.com/' ],
851             'wget' => $wget,
852             };
853              
854             }
855              
856             sub get_dir_files {
857             my $self = shift;
858             my %p = validate(
859             @_,
860             { 'dir' => { type => SCALAR, optional => 0, },
861             'fatal' => { type => BOOLEAN, optional => 1, default => 1 },
862             'debug' => { type => BOOLEAN, optional => 1, default => 1 },
863             }
864             );
865              
866             my ( $dir, $fatal, $debug ) = ( $p{dir}, $p{fatal}, $p{debug} );
867             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
868              
869             my @files;
870              
871             return $log->error( "dir $dir is not a directory!", %args)
872             if ! -d $dir;
873              
874             opendir D, $dir or return $log->error( "couldn't open $dir: $!", %args );
875              
876             while ( defined( my $f = readdir(D) ) ) {
877             next if $f =~ /^\.\.?$/;
878             push @files, "$dir/$f";
879             }
880              
881             closedir(D);
882              
883             return @files;
884             }
885              
886             sub get_my_ips {
887              
888             ############################################
889             # Usage : @list_of_ips_ref = $util->get_my_ips();
890             # Purpose : get a list of IP addresses on local interfaces
891             # Returns : an arrayref of IP addresses
892             # Parameters : only - can be one of: first, last
893             # : exclude_locahost (all 127.0 addresses)
894             # : exclude_internals (192.168, 10., 169., 172.)
895             # : exclude_ipv6
896             # Comments : exclude options are boolean and enabled by default.
897             # tested on Mac OS X and FreeBSD
898              
899             my $self = shift;
900             my %p = validate(
901             @_,
902             { 'only' => { type => SCALAR, optional => 1, default => 0 },
903             'exclude_localhost' =>
904             { type => BOOLEAN, optional => 1, default => 1 },
905             'exclude_internals' =>
906             { type => BOOLEAN, optional => 1, default => 1 },
907             'exclude_ipv6' =>
908             { type => BOOLEAN, optional => 1, default => 1 },
909             'fatal' => { type => BOOLEAN, optional => 1, default => 1 },
910             'debug' => { type => BOOLEAN, optional => 1, default => 1 },
911             }
912             );
913              
914             my $debug = $p{debug};
915             my $only = $p{only};
916              
917             my $ifconfig = $self->find_bin( "ifconfig", debug => 0 );
918              
919             my $once = 0;
920              
921             TRY:
922             my @ips = grep {/inet/} `$ifconfig`; chomp @ips;
923             @ips = grep {!/inet6/} @ips if $p{exclude_ipv6};
924             @ips = grep {!/inet 127\.0\.0/} @ips if $p{exclude_localhost};
925             @ips = grep {!/inet (192\.168\.|10\.|172\.16\.|169\.254\.)/} @ips
926             if $p{exclude_internals};
927              
928             # this keeps us from failing if the box has only internal IPs
929             if ( @ips < 1 || $ips[0] eq "" ) {
930             warn "you really don't have any public IPs?!" if $debug;
931             $p{exclude_internals} = 0;
932             $once++;
933             goto TRY if ( $once < 2 );
934             }
935              
936             foreach ( @ips ) { ($_) = $_ =~ m/inet ([\d\.]+)\s/; };
937              
938             return [ $ips[0] ] if $only eq 'first';
939             return [ $ips[-1] ] if $only eq 'last';
940             return \@ips;
941             }
942              
943             sub get_the_date {
944             my $self = shift;
945             my %p = validate(
946             @_,
947             { 'bump' => { type => SCALAR, optional => 1, },
948             'fatal' => { type => BOOLEAN, optional => 1, default => $self->{fatal} },
949             'debug' => { type => BOOLEAN, optional => 1, default => $self->{debug} },
950             }
951             );
952              
953             my $bump = $p{bump} || 0;
954             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
955              
956             my $time = time;
957             my $mess = "get_the_date time: " . time;
958              
959             $bump = $bump * 86400 if $bump;
960             my $offset_time = time - $bump;
961             $mess .= ", (selected $offset_time)" if $time != $offset_time;
962              
963             # load Date::Format to get the time2str function
964             eval { require Date::Format };
965             if ( !$EVAL_ERROR ) {
966              
967             my $ss = Date::Format::time2str( "%S", ($offset_time) );
968             my $mn = Date::Format::time2str( "%M", ($offset_time) );
969             my $hh = Date::Format::time2str( "%H", ($offset_time) );
970             my $dd = Date::Format::time2str( "%d", ($offset_time) );
971             my $mm = Date::Format::time2str( "%m", ($offset_time) );
972             my $yy = Date::Format::time2str( "%Y", ($offset_time) );
973             my $lm = Date::Format::time2str( "%m", ( $offset_time - 2592000 ) );
974              
975             $log->audit( "$mess, $yy/$mm/$dd $hh:$mn", %args);
976             return $dd, $mm, $yy, $lm, $hh, $mn, $ss;
977             }
978              
979             # 0 1 2 3 4 5 6 7 8
980             # ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
981             # localtime(time);
982             # 4 = month + 1 ( see perldoc localtime)
983             # 5 = year + 1900 ""
984              
985             my @fields = localtime($offset_time);
986              
987             my $ss = sprintf( "%02i", $fields[0] ); # seconds
988             my $mn = sprintf( "%02i", $fields[1] ); # minutes
989             my $hh = sprintf( "%02i", $fields[2] ); # hours (24 hour clock)
990              
991             my $dd = sprintf( "%02i", $fields[3] ); # day of month
992             my $mm = sprintf( "%02i", $fields[4] + 1 ); # month
993             my $yy = ( $fields[5] + 1900 ); # year
994              
995             $log->audit( "$mess, $yy/$mm/$dd $hh:$mn", %args );
996             return $dd, $mm, $yy, undef, $hh, $mn, $ss;
997             }
998              
999             sub get_mounted_drives {
1000             my $self = shift;
1001             my %p = validate( @_, { %std_opts } );
1002             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
1003              
1004             my $mount = $self->find_bin( 'mount', %args );
1005              
1006             -x $mount or return $log->error( "I couldn't find mount!", %args );
1007              
1008             $ENV{PATH} = "";
1009             my %hash;
1010             foreach (`$mount`) {
1011             my ( $d, $m ) = $_ =~ /^(.*) on (.*) \(/;
1012              
1013             #if ( $m =~ /^\// && $d =~ /^\// ) # mount drives that begin with /
1014             if ( $m && $m =~ /^\// ) { # only mounts that begin with /
1015             $log->audit( "adding: $m \t $d" ) if $p{debug};
1016             $hash{$m} = $d;
1017             }
1018             }
1019             return \%hash;
1020             }
1021              
1022             sub get_url {
1023             my $self = shift;
1024             my $url = shift;
1025             my %p = validate(
1026             @_,
1027             { dir => { type => SCALAR, optional => 1 },
1028             timeout => { type => SCALAR, optional => 1 },
1029             %std_opts,
1030             }
1031             );
1032              
1033             my $dir = $p{dir};
1034             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
1035              
1036             my ($ua, $response);
1037             ## no critic ( ProhibitStringyEval )
1038             eval "require LWP::Simple";
1039             ## use critic
1040             return $self->get_url_system( $url, %p ) if $EVAL_ERROR;
1041              
1042             my $uri = URI->new($url);
1043             my @parts = $uri->path_segments;
1044             my $file = $parts[-1]; # everything after the last / in the URL
1045             my $file_path = $file;
1046             $file_path = "$dir/$file" if $dir;
1047              
1048             $log->audit( "fetching $url" );
1049             eval { $response = LWP::Simple::mirror($url, $file_path ); };
1050              
1051             if ( $response ) {
1052             if ( $response == 404 ) {
1053             return $log->error( "file not found ($url)", %args );
1054             }
1055             elsif ($response == 304 ) {
1056             $log->audit( "result 304: file is up-to-date" );
1057             }
1058             elsif ( $response == 200 ) {
1059             $log->audit( "result 200: file download ok" );
1060             }
1061             else {
1062             $log->error( "unhandled response: $response", fatal => 0 );
1063             };
1064             };
1065              
1066             return if ! -e $file_path;
1067             return $response;
1068             }
1069              
1070             sub get_url_system {
1071             my $self = shift;
1072             my $url = shift;
1073             my %p = validate(
1074             @_,
1075             { dir => { type => SCALAR, optional => 1 },
1076             timeout => { type => SCALAR, optional => 1, },
1077             %std_opts,
1078             }
1079             );
1080              
1081             my $dir = $p{dir};
1082             my $debug = $p{debug};
1083             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
1084              
1085             my ($fetchbin, $found);
1086             if ( $OSNAME eq "freebsd" ) {
1087             $fetchbin = $self->find_bin( 'fetch', %args);
1088             if ( $fetchbin && -x $fetchbin ) {
1089             $found = $fetchbin;
1090             $found .= " -q" if !$debug;
1091             }
1092             }
1093             elsif ( $OSNAME eq "darwin" ) {
1094             $fetchbin = $self->find_bin( 'curl', %args );
1095             if ( $fetchbin && -x $fetchbin ) {
1096             $found = "$fetchbin -O";
1097             $found .= " -s " if !$debug;
1098             }
1099             }
1100              
1101             if ( !$found ) {
1102             $fetchbin = $self->find_bin( 'wget', %args);
1103             $found = $fetchbin if $fetchbin && -x $fetchbin;
1104             }
1105              
1106             return $log->error( "Failed to fetch $url.\n\tCouldn't find wget. Please install it.", %args )
1107             if !$found;
1108              
1109             my $fetchcmd = "$found $url";
1110              
1111             my $timeout = $p{timeout} || 0;
1112             if ( ! $timeout ) {
1113             $self->syscmd( $fetchcmd, %args ) or return;
1114             my $uri = URI->new($url);
1115             my @parts = $uri->path_segments;
1116             my $file = $parts[-1]; # everything after the last / in the URL
1117             if ( -e $file && $dir && -d $dir ) {
1118             $log->audit("moving file $file to $dir" );
1119             move $file, "$dir/$file";
1120             return 1;
1121             };
1122             };
1123              
1124             my $r;
1125             eval {
1126             local $SIG{ALRM} = sub { die "alarm\n" };
1127             alarm $timeout;
1128             $r = $self->syscmd( $fetchcmd, %args );
1129             alarm 0;
1130             };
1131              
1132             if ($EVAL_ERROR) { # propagate unexpected errors
1133             print "timed out!\n" if $EVAL_ERROR eq "alarm\n";
1134             return $log->error( $EVAL_ERROR, %args );
1135             }
1136              
1137             return $log->error( "error executing $fetchcmd", %args) if !$r;
1138             return 1;
1139             }
1140              
1141             sub install_if_changed {
1142             my $self = shift;
1143             my %p = validate(
1144             @_,
1145             { newfile => { type => SCALAR, optional => 0, },
1146             existing=> { type => SCALAR, optional => 0, },
1147             mode => { type => SCALAR, optional => 1, },
1148             uid => { type => SCALAR, optional => 1, },
1149             gid => { type => SCALAR, optional => 1, },
1150             sudo => { type => BOOLEAN, optional => 1, default => 0 },
1151             notify => { type => BOOLEAN, optional => 1, },
1152             email => { type => SCALAR, optional => 1, default => 'postmaster' },
1153             clean => { type => BOOLEAN, optional => 1, default => 1 },
1154             archive => { type => BOOLEAN, optional => 1, default => 0 },
1155             fatal => { type => BOOLEAN, optional => 1, default => $self->{fatal} },
1156             debug => { type => BOOLEAN, optional => 1, default => $self->{debug} },
1157             },
1158             );
1159              
1160             my ( $newfile, $existing, $mode, $uid, $gid, $email) = (
1161             $p{newfile}, $p{existing}, $p{mode}, $p{uid}, $p{gid}, $p{email} );
1162             my ($debug, $sudo, $notify ) = ($p{debug}, $p{sudo}, $p{notify} );
1163             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
1164              
1165             if ( $newfile !~ /\// ) {
1166             # relative filename given
1167             $log->audit( "relative filename given, use complete paths "
1168             . "for more predicatable results!\n"
1169             . "working directory is " . cwd() );
1170             }
1171              
1172             return $log->error( "file ($newfile) does not exist", %args )
1173             if !-e $newfile;
1174              
1175             return $log->error( "file ($newfile) is not a file", %args )
1176             if !-f $newfile;
1177              
1178             # make sure existing and new are writable
1179             if ( !$self->is_writable( $existing, fatal => 0 )
1180             || !$self->is_writable( $newfile, fatal => 0 ) ) {
1181              
1182             # root does not have permission, sudo won't do any good
1183             return $log->error("no write permission", %args) if $UID == 0;
1184              
1185             if ( $sudo ) {
1186             $sudo = $self->find_bin( 'sudo', %args ) or
1187             return $log->error( "you are not root, sudo was not found, and you don't have permission to write to $newfile or $existing" );
1188             }
1189             }
1190              
1191             my $diffie;
1192             if ( -f $existing ) {
1193             $diffie = $self->files_diff( %args,
1194             f1 => $newfile,
1195             f2 => $existing,
1196             type => "text",
1197             ) or do {
1198             $log->audit( "$existing is already up-to-date.", %args);
1199             unlink $newfile if $p{clean};
1200             return 2;
1201             };
1202             };
1203              
1204             $log->audit("checking $existing", %args);
1205              
1206             $self->chown( $newfile,
1207             uid => $uid,
1208             gid => $gid,
1209             sudo => $sudo,
1210             %args
1211             )
1212             if ( $uid && $gid ); # set file ownership on the new file
1213              
1214             # set file permissions on the new file
1215             $self->chmod(
1216             file_or_dir => $existing,
1217             mode => $mode,
1218             sudo => $sudo,
1219             %args
1220             )
1221             if ( -e $existing && $mode );
1222              
1223             $self->install_if_changed_notify( $notify, $email, $existing, $diffie);
1224             $self->archive_file( $existing, %args) if ( -e $existing && $p{archive} );
1225             $self->install_if_changed_copy( $sudo, $newfile, $existing, $p{clean}, \%args );
1226              
1227             $self->chown( $existing,
1228             uid => $uid,
1229             gid => $gid,
1230             sudo => $sudo,
1231             %args
1232             ) if ( $uid && $gid ); # set ownership on new existing file
1233              
1234             $self->chmod(
1235             file_or_dir => $existing,
1236             mode => $mode,
1237             sudo => $sudo,
1238             %args
1239             )
1240             if $mode; # set file permissions (paranoid)
1241              
1242             $log->audit( " updated $existing" );
1243             return 1;
1244             }
1245              
1246             sub install_if_changed_copy {
1247             my $self = shift;
1248             my ( $sudo, $newfile, $existing, $clean, $args ) = @_;
1249              
1250             # install the new file
1251             if ($sudo) {
1252             my $cp = $self->find_bin( 'cp', %$args );
1253              
1254             # back up the existing file
1255             $self->syscmd( "$sudo $cp $existing $existing.bak", %$args)
1256             if -e $existing;
1257              
1258             # install the new one
1259             if ( $clean ) {
1260             my $mv = $self->find_bin( 'mv' );
1261             $self->syscmd( "$sudo $mv $newfile $existing", %$args);
1262             }
1263             else {
1264             $self->syscmd( "$sudo $cp $newfile $existing",%$args);
1265             }
1266             }
1267             else {
1268              
1269             # back up the existing file
1270             copy( $existing, "$existing.bak" ) if -e $existing;
1271              
1272             if ( $clean ) {
1273             move( $newfile, $existing ) or
1274             return $log->error( "failed copy $newfile to $existing", %$args);
1275             }
1276             else {
1277             copy( $newfile, $existing ) or
1278             return $log->error( "failed copy $newfile to $existing", %$args );
1279             }
1280             }
1281             };
1282              
1283             sub install_if_changed_notify {
1284              
1285             my ($self, $notify, $email, $existing, $diffie) = @_;
1286              
1287             return if ! $notify;
1288             return if ! -f $existing;
1289              
1290             # email diffs to admin
1291              
1292             eval { require Mail::Send; };
1293              
1294             return $log->error( "could not send notice, Mail::Send is not installed!", fatal => 0)
1295             if $EVAL_ERROR;
1296              
1297             my $msg = Mail::Send->new;
1298             $msg->subject("$existing updated by $0");
1299             $msg->to($email);
1300             my $email_message = $msg->open;
1301              
1302             print $email_message "This message is to notify you that $existing has been altered. The difference between the new file and the old one is:\n\n$diffie";
1303              
1304             $email_message->close;
1305             };
1306              
1307             sub install_from_source {
1308             my $self = shift;
1309             my %p = validate(
1310             @_,
1311             { 'site' => { type => SCALAR, optional => 0, },
1312             'url' => { type => SCALAR, optional => 0, },
1313             'package' => { type => SCALAR, optional => 0, },
1314             'targets' => { type => ARRAYREF, optional => 1, },
1315             'patches' => { type => ARRAYREF, optional => 1, },
1316             'patch_url' => { type => SCALAR, optional => 1, },
1317             'patch_args' => { type => SCALAR, optional => 1, },
1318             'source_dir' => { type => SCALAR, optional => 1, },
1319             'source_sub_dir' => { type => SCALAR, optional => 1, },
1320             'bintest' => { type => SCALAR, optional => 1, },
1321             %std_opts,
1322             },
1323             );
1324              
1325             return $p{test_ok} if defined $p{test_ok};
1326              
1327             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
1328             my ( $site, $url, $package, $targets, $patches, $debug, $bintest ) =
1329             ( $p{site}, $p{url}, $p{package},
1330             $p{targets}, $p{patches}, $p{debug}, $p{bintest} );
1331              
1332             my $patch_args = $p{patch_args} || '';
1333             my $src = $p{source_dir} || "/usr/local/src";
1334             $src .= "/$p{source_sub_dir}" if $p{source_sub_dir};
1335              
1336             my $original_directory = cwd;
1337              
1338             $self->cwd_source_dir( $src, %args );
1339              
1340             if ( $bintest && $self->find_bin( $bintest, fatal => 0, debug => 0 ) ) {
1341             return if ! $self->yes_or_no(
1342             "$bintest exists, suggesting that "
1343             . "$package is installed. Do you want to reinstall?",
1344             timeout => 60,
1345             );
1346             }
1347              
1348             $log->audit( "install_from_source: building $package in $src");
1349              
1350             $self->install_from_source_cleanup($package,$src) or return;
1351             $self->install_from_source_get_files($package,$site,$url,$p{patch_url},$patches) or return;
1352              
1353             $self->extract_archive( $package )
1354             or return $log->error( "Couldn't expand $package: $!", %args );
1355              
1356             # cd into the package directory
1357             my $sub_path;
1358             if ( -d $package ) {
1359             chdir $package or
1360             return $log->error( "FAILED to chdir $package!", %args );
1361             }
1362             else {
1363              
1364             # some packages (like daemontools) unpack within an enclosing directory
1365             $sub_path = `find ./ -name $package`; # tainted data
1366             chomp $sub_path;
1367             ($sub_path) = $sub_path =~ /^([-\w\/.]+)$/; # untaint it
1368              
1369             $log->audit( "found sources in $sub_path" ) if $sub_path;
1370             return $log->error( "FAILED to find $package sources!",fatal=>0)
1371             unless ( -d $sub_path && chdir($sub_path) );
1372             }
1373              
1374             $self->install_from_source_apply_patches($src, $patches, $patch_args) or return;
1375              
1376             # set default build targets if none are provided
1377             if ( !@$targets[0] ) {
1378             $log->audit( "\tusing default targets (./configure, make, make install)" );
1379             @$targets = ( "./configure", "make", "make install" );
1380             }
1381              
1382             my $msg = "install_from_source: using targets\n";
1383             foreach (@$targets) { $msg .= "\t$_\n" };
1384             $log->audit( $msg ) if $debug;
1385              
1386             # build the program
1387             foreach my $target (@$targets) {
1388              
1389             if ( $target =~ /^cd (.*)$/ ) {
1390             $log->audit( "cwd: " . cwd . " -> " . $1 );
1391             chdir($1) or return $log->error( "couldn't chdir $1: $!", %args);
1392             next;
1393             }
1394              
1395             $self->syscmd( $target, debug => $debug ) or
1396             return $log->error( "pwd: " . cwd . "\n$target failed: $!", %args );
1397             }
1398              
1399             # clean up the build sources
1400             chdir $src;
1401             $self->syscmd( "rm -rf $package", debug => $debug ) if -d $package;
1402              
1403             $self->syscmd( "rm -rf $package/$sub_path", %args )
1404             if defined $sub_path && -d "$package/$sub_path";
1405              
1406             chdir $original_directory;
1407             return 1;
1408             }
1409              
1410             sub install_from_source_apply_patches {
1411             my $self = shift;
1412             my ($src, $patches,$patch_args) = @_;
1413              
1414             return 1 if ! $patches;
1415             return 1 if ! $patches->[0];
1416              
1417             my $patchbin = $self->find_bin( "patch" );
1418             foreach my $patch (@$patches) {
1419             $self->syscmd( "$patchbin $patch_args < $src/$patch" )
1420             or return $log->error("failed to apply patch $patch");
1421             }
1422             return 1;
1423             };
1424              
1425             sub install_from_source_cleanup {
1426             my $self = shift;
1427             my ($package,$src) = @_;
1428              
1429             # make sure there are no previous sources in the way
1430             return 1 if ! -d $package;
1431              
1432             $self->source_warning(
1433             package => $package,
1434             clean => 1,
1435             src => $src,
1436             ) or return $log->error( "OK then, skipping install.", fatal => 0);
1437              
1438             print "install_from_source: removing previous build sources.\n";
1439             return $self->syscmd( "rm -rf $package-*" );
1440             };
1441              
1442             sub install_from_source_get_files {
1443             my $self = shift;
1444             my ($package,$site,$url,$patch_url,$patches) = @_;
1445              
1446             $self->sources_get(
1447             package => $package,
1448             site => $site,
1449             path => $url,
1450             ) or return;
1451              
1452             if ( ! $patches || ! $patches->[0] ) {
1453             $log->audit( "install_from_source: no patches to fetch." );
1454             return 1;
1455             };
1456              
1457             return $log->error( "oops! You supplied patch names to apply without a URL!")
1458             if ! $patch_url;
1459              
1460              
1461             foreach my $patch (@$patches) {
1462             next if ! $patch;
1463             next if -e $patch;
1464              
1465             $log->audit( "install_from_source: fetching patch from $url");
1466             my $url = "$patch_url/$patch";
1467             $self->get_url( $url )
1468             or return $log->error( "could not fetch $url" );
1469             };
1470              
1471             return 1;
1472             };
1473              
1474             sub install_package {
1475             my ($self, $app, $info) = @_;
1476              
1477             if ( lc($OSNAME) eq 'freebsd' ) {
1478              
1479             my $portname = $info->{port}
1480             or return $log->error( "skipping install of $app b/c port dir not set.", fatal => 0);
1481              
1482             if (`/usr/sbin/pkg_info | /usr/bin/grep $app`) {
1483             print "$app is installed.\n";
1484             return 1;
1485             }
1486              
1487             print "installing $app\n";
1488             my $portdir = glob("/usr/ports/*/$portname");
1489              
1490             return $log->error( "oops, couldn't find port $app at '$portname'")
1491             if ( ! -d $portdir || ! chdir $portdir );
1492              
1493             system "make install clean"
1494             and return $log->error( "'make install clean' failed for port $app", fatal => 0);
1495             return 1;
1496             };
1497              
1498             if ( lc($OSNAME) eq 'linux' ) {
1499             my $rpm = $info->{rpm} or return $log->error("skipping install of $app b/c rpm not set", fatal => 0);
1500             my $yum = '/usr/bin/yum';
1501             return $log->error( "couldn't find yum, skipping install.", fatal => 0)
1502             if ! -x $yum;
1503             return system "$yum install $rpm";
1504             };
1505              
1506             $log->error(" no package support for $OSNAME ");
1507             }
1508              
1509             sub install_module {
1510             my ($self, $module, %info) = @_;
1511              
1512             my $debug = defined $info{debug} ? $info{debug} : 1;
1513              
1514             ## no critic ( ProhibitStringyEval )
1515             eval "use $module";
1516             ## use critic
1517             if ( ! $EVAL_ERROR ) {
1518             $log->audit( "$module is already installed.",debug=>$debug );
1519             };
1520              
1521             if ( lc($OSNAME) eq 'darwin' ) {
1522             $self->install_module_darwin( $module ) and return 1;
1523             }
1524             elsif ( lc($OSNAME) eq 'freebsd' ) {
1525             $self->install_module_freebsd( $module, \%info) and return 1;
1526             }
1527             elsif ( lc($OSNAME) eq 'linux' ) {
1528             $self->install_module_linux( $module, \%info) and return 1;
1529             };
1530              
1531             $self->install_module_cpan( $module );
1532              
1533             ## no critic ( ProhibitStringyEval )
1534             eval "use $module";
1535             ## use critic
1536             if ( ! $EVAL_ERROR ) {
1537             $log->audit( "$module is installed." );
1538             return 1;
1539             };
1540             return;
1541             }
1542              
1543             sub install_module_cpan {
1544             my $self = shift;
1545             my ($module, $version) = @_;
1546              
1547             print " from CPAN...";
1548             require CPAN;
1549            
1550             # some Linux distros break CPAN by auto/preconfiguring it with no URL mirrors.
1551             # this works around that annoying little habit
1552             no warnings;
1553             $CPAN::Config = get_cpan_config();
1554             use warnings;
1555              
1556             if ( $module eq 'Provision::Unix' && $version ) {
1557             $module =~ s/\:\:/\-/g;
1558             $module = "M/MS/MSIMERSON/$module-$version.tar.gz";
1559             }
1560             CPAN::Shell->install($module);
1561             }
1562              
1563             sub install_module_darwin {
1564             my $self = shift;
1565             my $module = shift;
1566              
1567             my $dport = '/opt/local/bin/port';
1568             return $log->error( "Darwin ports is not installed!", fatal => 0)
1569             if ! -x $dport;
1570              
1571             my $port = "p5-$module";
1572             $port =~ s/::/-/g;
1573             system "sudo $dport install $port" or return 1;
1574             return;
1575             };
1576              
1577             sub install_module_freebsd {
1578             my $self = shift;
1579             my ($module, $info) = @_;
1580              
1581             my $portname = $info->{port}; # optional override
1582             if ( ! $portname ) {
1583             $portname = "p5-$module";
1584             $portname =~ s/::/-/g;
1585             };
1586              
1587             my $r = `/usr/sbin/pkg_info | /usr/bin/grep $portname`;
1588             return $log->audit( "$module is installed as $r") if $r;
1589              
1590             my $portdir = glob("/usr/ports/*/$portname");
1591              
1592             if ( $portdir && -d $portdir && chdir $portdir ) {
1593             $log->audit( "installing $module from ports ($portdir)" );
1594             system "make clean && make install clean";
1595             return 1;
1596             }
1597             return;
1598             }
1599              
1600             sub install_module_from_src {
1601             my $self = shift;
1602             my %p = validate( @_, {
1603             module => { type=>SCALAR, optional=>0, },
1604             archive => { type=>SCALAR, optional=>0, },
1605             site => { type=>SCALAR, optional=>0, },
1606             url => { type=>SCALAR, optional=>0, },
1607             src => { type=>SCALAR, optional=>1, default=>'/usr/local/src' },
1608             targets => { type=>ARRAYREF,optional=>1, },
1609             %std_opts,
1610             },
1611             );
1612              
1613             my ( $module, $site, $url, $src, $targets )
1614             = ( $p{module}, $p{site}, $p{url}, $p{src}, $p{targets} );
1615             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
1616              
1617             $self->cwd_source_dir( $src, %args );
1618              
1619             $log->audit( "checking for previous build attempts.");
1620             if ( -d $module ) {
1621             if ( ! $self->source_warning( package=>$module, src=>$src, %args ) ) {
1622             print "\nokay, skipping install.\n";
1623             return;
1624             }
1625             $self->syscmd( cmd => "rm -rf $module", %args );
1626             }
1627              
1628             $self->sources_get(
1629             site => $site,
1630             path => $url,
1631             package => $p{'archive'} || $module,
1632             %args,
1633             ) or return;
1634              
1635             $self->extract_archive( $module ) or return;
1636              
1637             my $found;
1638             print "looking for $module in $src...";
1639             foreach my $file ( $self->get_dir_files( dir => $src ) ) {
1640              
1641             next if ! -d $file; # only check directories
1642             next if $file !~ /$module/;
1643              
1644             print "found: $file\n";
1645             $found++;
1646             chdir $file;
1647              
1648             unless ( @$targets[0] && @$targets[0] ne "" ) {
1649             $log->audit( "using default targets." );
1650             $targets = [ "perl Makefile.PL", "make", "make install" ];
1651             }
1652              
1653             print "building with targets " . join( ", ", @$targets ) . "\n";
1654             foreach (@$targets) {
1655             return $log->error( "$_ failed!", %args)
1656             if ! $self->syscmd( cmd => $_ , %args);
1657             }
1658              
1659             chdir('..');
1660             $self->syscmd( cmd => "rm -rf $file", debug=>0);
1661             last;
1662             }
1663              
1664             return $found;
1665             }
1666              
1667             sub install_module_linux {
1668             my $self = shift;
1669             my ($module, $info ) = @_;
1670             my $rpm = $info->{rpm};
1671             if ( $rpm ) {
1672             my $portname = "perl-$rpm";
1673             $portname =~ s/::/-/g;
1674             my $yum = '/usr/bin/yum';
1675             system "$yum -y install $portname" if -x $yum;
1676             }
1677             };
1678              
1679             sub is_interactive {
1680              
1681             ## no critic
1682             # borrowed from IO::Interactive
1683             my $self = shift;
1684             my ($out_handle) = ( @_, select ); # Default to default output handle
1685              
1686             # Not interactive if output is not to terminal...
1687             return if not -t $out_handle;
1688              
1689             # If *ARGV is opened, we're interactive if...
1690             if ( openhandle * ARGV ) {
1691              
1692             # ...it's currently opened to the magic '-' file
1693             return -t *STDIN if defined $ARGV && $ARGV eq '-';
1694              
1695             # ...it's at end-of-file and the next file is the magic '-' file
1696             return @ARGV > 0 && $ARGV[0] eq '-' && -t *STDIN if eof *ARGV;
1697              
1698             # ...it's directly attached to the terminal
1699             return -t *ARGV;
1700             };
1701              
1702             # If *ARGV isn't opened, it will be interactive if *STDIN is attached
1703             # to a terminal and either there are no files specified on the command line
1704             # or if there are files and the first is the magic '-' file
1705             return -t *STDIN && ( @ARGV == 0 || $ARGV[0] eq '-' );
1706             }
1707              
1708             sub is_process_running {
1709             my ( $self, $process ) = @_;
1710              
1711             ## no critic ( ProhibitStringyEval )
1712             eval "require Proc::ProcessTable";
1713             ## use critic
1714             if ( ! $EVAL_ERROR ) {
1715             my $i = 0;
1716             my $t = Proc::ProcessTable->new();
1717             if ( scalar @{ $t->table } ) {
1718             foreach my $p ( @{ $t->table } ) {
1719             $i++ if ( $p->cmndline =~ m/$process/i );
1720             };
1721             return $i;
1722             };
1723             };
1724              
1725             my $ps = $self->find_bin( 'ps', debug => 0 );
1726              
1727             if ( lc($OSNAME) =~ /solaris/i ) { $ps .= ' -ef'; }
1728             elsif ( lc($OSNAME) =~ /irix/i ) { $ps .= ' -ef'; }
1729             elsif ( lc($OSNAME) =~ /linux/i ) { $ps .= ' -efw'; }
1730             else { $ps .= ' axww'; };
1731              
1732             my @procs = `$ps`;
1733             chomp @procs;
1734             return scalar grep {/$process/i} @procs;
1735             }
1736              
1737             sub is_readable {
1738             my $self = shift;
1739             my $file = shift or die "missing file or dir name\n";
1740             my %p = validate( @_, { %std_opts } );
1741              
1742             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
1743              
1744             -e $file or return $log->error( "$file does not exist.", %args);
1745             -r $file or return $log->error( "$file is not readable by you ("
1746             . getpwuid($>)
1747             . "). You need to fix this, using chown or chmod.", %args);
1748              
1749             return 1;
1750             }
1751              
1752             sub is_writable {
1753             my $self = shift;
1754             my $file = shift or die "missing file or dir name\n";
1755              
1756             my %p = validate( @_, { %std_opts } );
1757             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
1758              
1759             my $nl = "\n";
1760             $nl = "<br>" if ( $ENV{GATEWAY_INTERFACE} );
1761              
1762             if ( !-e $file ) {
1763              
1764             my ( $base, $path, $suffix ) = fileparse($file);
1765              
1766             return $log->error( "is_writable: $path not writable by "
1767             . getpwuid($>)
1768             . "$nl$nl", %args) if (-e $path && !-w $path);
1769             return 1;
1770             }
1771              
1772             return $log->error( " $file not writable by " . getpwuid($>) . "$nl$nl", %args ) if ! -w $file;
1773              
1774             $log->audit( "$file is writable" );
1775             return 1;
1776             }
1777              
1778             sub logfile_append {
1779             my $self = shift;
1780             my %p = validate(
1781             @_,
1782             { 'file' => { type => SCALAR, optional => 0, },
1783             'lines' => { type => ARRAYREF, optional => 0, },
1784             'prog' => { type => BOOLEAN, optional => 1, default => 0, },
1785             %std_opts,
1786             },
1787             );
1788              
1789             my ( $file, $lines ) = ( $p{file}, $p{lines} );
1790             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
1791              
1792             my ( $dd, $mm, $yy, $lm, $hh, $mn, $ss ) = $self->get_the_date( %args );
1793              
1794             open my $LOG_FILE, '>>', $file
1795             or return $log->error( "couldn't open $file: $OS_ERROR", %args);
1796              
1797             print $LOG_FILE "$yy-$mm-$dd $hh:$mn:$ss $p{prog} ";
1798              
1799             my $i;
1800             foreach (@$lines) { print $LOG_FILE "$_ "; $i++ }
1801              
1802             print $LOG_FILE "\n";
1803             close $LOG_FILE;
1804              
1805             $log->audit( "logfile_append wrote $i lines to $file", %args );
1806             return 1;
1807             }
1808              
1809             sub mail_toaster {
1810             my $self = shift;
1811             $self->install_module( 'Mail::Toaster' );
1812             }
1813              
1814             sub mkdir_system {
1815             my $self = shift;
1816             my %p = validate(
1817             @_,
1818             { 'dir' => { type => SCALAR, optional => 0, },
1819             'mode' => { type => SCALAR, optional => 1, },
1820             'sudo' => { type => BOOLEAN, optional => 1, default => 0 },
1821             %std_opts,
1822             }
1823             );
1824              
1825             my ( $dir, $mode ) = ( $p{dir}, $p{mode} );
1826             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
1827              
1828             return $log->audit( "mkdir_system: $dir already exists.") if -d $dir;
1829              
1830             my $mkdir = $self->find_bin( 'mkdir', %args) or return;
1831              
1832             # if we are root, just do it (no sudo nonsense)
1833             if ( $< == 0 ) {
1834             $self->syscmd( "$mkdir -p $dir", %args) or return;
1835             $self->chmod( dir => $dir, mode => $mode, %args ) if $mode;
1836              
1837             return 1 if -d $dir;
1838             return $log->error( "failed to create $dir", %args);
1839             }
1840              
1841             if ( $p{sudo} ) {
1842             my $sudo = $self->sudo();
1843              
1844             $log->audit( "trying $sudo $mkdir -p $dir");
1845             $self->syscmd( "$sudo $mkdir -p $dir", %args);
1846              
1847             $log->audit( "setting ownership to $<.");
1848             my $chown = $self->find_bin( 'chown', %args);
1849             $self->syscmd( "$sudo $chown $< $dir", %args);
1850              
1851             $self->chmod( dir => $dir, mode => $mode, sudo => $sudo, %args)
1852             if $mode;
1853             return -d $dir ? 1 : 0;
1854             }
1855              
1856             $log->audit( "trying mkdir -p $dir" );
1857              
1858             # no root and no sudo, just try and see what happens
1859             $self->syscmd( "$mkdir -p $dir", %args ) or return;
1860              
1861             $self->chmod( dir => $dir, mode => $mode, %args) if $mode;
1862              
1863             return $log->audit( "mkdir_system created $dir" ) if -d $dir;
1864             return $log->error( '', %args );
1865             }
1866              
1867             sub path_parse {
1868              
1869             # code left here for reference, use File::Basename instead
1870             my ( $self, $dir ) = @_;
1871              
1872             # if it ends with a /, chop if off
1873             if ( $dir =~ q{/$} ) { chop $dir }
1874              
1875             # get the position of the last / in the path
1876             my $rindex = rindex( $dir, "/" );
1877              
1878             # grabs everything up to the last /
1879             my $updir = substr( $dir, 0, $rindex );
1880             $rindex++;
1881              
1882             # matches from the last / char +1 to the end of string
1883             my $curdir = substr( $dir, $rindex );
1884              
1885             return $updir, $curdir;
1886             }
1887              
1888             sub check_pidfile {
1889             my $self = shift;
1890             my $file = shift;
1891             my %p = validate( @_, { %std_opts } );
1892              
1893             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
1894              
1895             return $log->error( "missing filename", %args) if ! $file;
1896             return $log->error( "$file is not a regular file", %args)
1897             if ( -e $file && !-f $file );
1898              
1899             # test if file & enclosing directory is writable, revert to /tmp if not
1900             $self->is_writable( $file, %args)
1901             or do {
1902             my ( $base, $path, $suffix ) = fileparse($file);
1903             $log->audit( "NOTICE: using /tmp for file, $path is not writable!", %args);
1904             $file = "/tmp/$base";
1905             };
1906              
1907             # if it does not exist
1908             if ( !-e $file ) {
1909             $log->audit( "writing process id $PROCESS_ID to $file...");
1910             $self->file_write( $file, lines => [$PROCESS_ID], %args) and return $file;
1911             };
1912              
1913             my $age = time() - stat($file)->mtime;
1914              
1915             if ( $age < 1200 ) { # less than 20 minutes old
1916             return $log->error( "check_pidfile: $file is " . $age / 60
1917             . " minutes old and might still be running. If it is not running,"
1918             . " please remove the file (rm $file).", %args);
1919             }
1920             elsif ( $age < 3600 ) { # 1 hour
1921             return $log->error( "check_pidfile: $file is " . $age / 60
1922             . " minutes old and might still be running. If it is not running,"
1923             . " please remove the pidfile. (rm $file)", %args);
1924             }
1925             else {
1926             $log->audit( "check_pidfile: $file is $age seconds old, ignoring.", %args);
1927             }
1928              
1929             return $file;
1930             }
1931              
1932             sub provision_unix {
1933             my $self = shift;
1934             $self->install_module( 'Provision::Unix' );
1935             }
1936              
1937             sub regexp_test {
1938             my $self = shift;
1939             my %p = validate(
1940             @_,
1941             { 'exp' => { type => SCALAR },
1942             'string' => { type => SCALAR },
1943             'pbp' => { type => BOOLEAN, optional => 1, default => 0 },
1944             'debug' => { type => BOOLEAN, optional => 1, default => $self->{debug} },
1945             },
1946             );
1947              
1948             my $debug = $p{debug};
1949             my ( $exp, $string, $pbp ) = ( $p{exp}, $p{string}, $p{pbp} );
1950              
1951             if ($pbp) {
1952             if ( $string =~ m{($exp)}xms ) {
1953             print "\t Matched pbp: |$`<$&>$'|\n" if $debug;
1954             return $1;
1955             }
1956             else {
1957             print "\t No match.\n" if $debug;
1958             return;
1959             }
1960             }
1961              
1962             if ( $string =~ m{($exp)} ) {
1963             print "\t Matched: |$`<$&>$'|\n" if $debug;
1964             return $1;
1965             }
1966              
1967             print "\t No match.\n" if $debug;
1968             return;
1969             }
1970              
1971             sub sources_get {
1972             my $self = shift;
1973             my %p = validate(
1974             @_,
1975             { 'package' => { type => SCALAR, optional => 0 },
1976             site => { type => SCALAR, optional => 0 },
1977             path => { type => SCALAR, optional => 1 },
1978             %std_opts,
1979             },
1980             );
1981              
1982             my ( $package, $site, $path ) = ( $p{package}, $p{site}, $p{path} );
1983             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
1984              
1985             $log->audit( "sources_get: fetching $package from site $site\n\t path: $path");
1986              
1987             my @extensions = qw/ tar.gz tgz tar.bz2 tbz2 /;
1988              
1989             my $filet = $self->find_bin( 'file', %args) or return;
1990             my $grep = $self->find_bin( 'grep', %args) or return;
1991              
1992             foreach my $ext (@extensions) {
1993              
1994             my $tarball = "$package.$ext";
1995             next if !-e $tarball;
1996             $log->audit( " found $tarball!") if -e $tarball;
1997              
1998             if (`$filet $tarball | $grep compress`) {
1999             $self->yes_or_no( "$tarball exists, shall I use it?: ")
2000             and return $log->audit( " ok, using existing archive: $tarball");
2001             }
2002              
2003             $self->file_delete( file => $tarball, %args );
2004             }
2005              
2006             foreach my $ext (@extensions) {
2007             my $tarball = "$package.$ext";
2008              
2009             $log->audit( "sources_get: fetching $site$path/$tarball");
2010              
2011             $self->get_url( "$site$path/$tarball", fatal => 0)
2012             or return $log->error( "couldn't fetch $site$path/$tarball", %args);
2013              
2014             next if ! -e $tarball;
2015              
2016             $log->audit( " sources_get: testing $tarball ");
2017              
2018             if (`$filet $tarball | $grep zip`) {
2019             $log->audit( " sources_get: looks good!");
2020             return 1;
2021             };
2022              
2023             $log->audit( " oops, is not [b|g]zipped data!");
2024             $self->file_delete( file => $tarball, %args);
2025             }
2026              
2027             return $log->error( "unable to get $package", %args );
2028             }
2029              
2030             sub source_warning {
2031             my $self = shift;
2032             my %p = validate(
2033             @_,
2034             { 'package' => { type => SCALAR, },
2035             'clean' => { type => BOOLEAN, optional => 1, default => 1 },
2036             'src' => {
2037             type => SCALAR,
2038             optional => 1,
2039             default => "/usr/local/src"
2040             },
2041             'timeout' => { type => SCALAR, optional => 1, default => 60 },
2042             %std_opts,
2043             },
2044             );
2045              
2046             my ( $package, $src ) = ( $p{package}, $p{src} );
2047             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
2048              
2049             return $log->audit( "$package sources not present.", %args ) if !-d $package;
2050              
2051             if ( -e $package ) {
2052             print "
2053             $package sources are already present, indicating that you've already
2054             installed $package. If you want to reinstall it, remove the existing
2055             sources (rm -r $src/$package) and re-run this script\n\n";
2056             return if !$p{clean};
2057             }
2058              
2059             if ( !$self->yes_or_no( "\n\tMay I remove the sources for you?", timeout => $p{timeout} ) ) {
2060             print "\nOK then, skipping $package install.\n\n";
2061             return;
2062             };
2063              
2064             $log->audit( " wd: " . cwd );
2065             $log->audit( " deleting $src/$package");
2066              
2067             return $log->error( "failed to delete $package: $OS_ERROR", %args )
2068             if ! rmtree "$src/$package";
2069             return 1;
2070             }
2071              
2072             sub sudo {
2073             my $self = shift;
2074             my %p = validate( @_, { %std_opts } );
2075              
2076             # if we are running as root via $<
2077             if ( $REAL_USER_ID == 0 ) {
2078             $log->audit( "sudo: you are root, sudo isn't necessary.");
2079             return ''; # return an empty string, purposefully
2080             }
2081              
2082             my $sudo;
2083             my $path_to_sudo = $self->find_bin( 'sudo', fatal => 0 );
2084              
2085             # sudo is installed
2086             if ( $path_to_sudo && -x $path_to_sudo ) {
2087             $log->audit( "sudo: sudo was found at $path_to_sudo.");
2088             return "$path_to_sudo -p 'Password for %u@%h:'";
2089             }
2090              
2091             $log->audit( "\nWARNING: Couldn't find sudo. This may not be a problem but some features require root permissions and will not work without them. Having sudo can allow legitimate and limited root permission to non-root users. Some features of Mail::Toaster may not work as expected without it.\n");
2092              
2093             # try installing sudo
2094             $self->yes_or_no( "may I try to install sudo?", timeout => 20 ) or do {
2095             print "very well then, skipping along.\n";
2096             return "";
2097             };
2098              
2099             -x $self->find_bin( "sudo", fatal => 0 ) or
2100             $self->install_from_source(
2101             package => 'sudo-1.6.9p17',
2102             site => 'http://www.courtesan.com',
2103             url => '/sudo/',
2104             targets => [ './configure', 'make', 'make install' ],
2105             patches => '',
2106             debug => 1,
2107             );
2108              
2109             # can we find it now?
2110             $path_to_sudo = $self->find_bin( "sudo" );
2111              
2112             if ( !-x $path_to_sudo ) {
2113             print "sudo install failed!";
2114             return '';
2115             }
2116              
2117             return "$path_to_sudo -p 'Password for %u@%h:'";
2118             }
2119              
2120             sub syscmd {
2121             my $self = shift;
2122             my $cmd = shift or die "missing command!\n";
2123             my %p = validate(
2124             @_,
2125             { 'timeout' => { type => SCALAR, optional => 1 },
2126             %std_opts,
2127             },
2128             );
2129              
2130             my %args = ( debug => $p{debug}, fatal => $p{fatal} );
2131              
2132             $log->audit("syscmd: $cmd");
2133              
2134             my ( $is_safe, $tainted, $bin, @args );
2135              
2136             # separate the program from its arguments
2137             if ( $cmd =~ m/\s+/xm ) {
2138             ($cmd) = $cmd =~ /^\s*(.*?)\s*$/; # trim lead/trailing whitespace
2139             @args = split /\s+/, $cmd; # split on whitespace
2140             $bin = shift @args;
2141             $is_safe++;
2142             $log->audit("\tprogram: $bin, args : " . join ' ', @args );
2143             }
2144             else {
2145             # does not not contain a ./ pattern
2146             if ( $cmd !~ m{\./} ) { $bin = $cmd; $is_safe++; };
2147             }
2148              
2149             if ( $is_safe && !$bin ) {
2150             return $log->error("command is not safe! BAILING OUT!", %args);
2151             }
2152              
2153             my $message;
2154             $message .= "syscmd: bin is <$bin>" if $bin;
2155             $message .= " (safe)" if $is_safe;
2156             $log->audit($message, %args );
2157              
2158             if ( $bin && !-e $bin ) { # $bin is set, but we have not found it
2159             $bin = $self->find_bin( $bin, fatal => 0, debug => 0 )
2160             or return $log->error( "$bin was not found", %args);
2161             }
2162             unshift @args, $bin;
2163              
2164             require Scalar::Util;
2165             $tainted++ if Scalar::Util::tainted($cmd);
2166              
2167             my $before_path = $ENV{PATH};
2168              
2169             # instead of dying, maybe try setting a
2170             # very restrictive PATH? I'll err on the side of safety
2171             # $ENV{PATH} = '';
2172             return $log->error( "syscmd request has tainted data", %args)
2173             if ( $tainted && !$is_safe );
2174              
2175             if ($is_safe) {
2176             my $prefix = "/usr/local"; # restrict the path
2177             $prefix = "/opt/local" if -d "/opt/local";
2178             $ENV{PATH} = "/bin:/sbin:/usr/bin:/usr/sbin:$prefix/bin:$prefix/sbin";
2179             }
2180              
2181             my $r;
2182             eval {
2183             if ( defined $p{timeout} ) {
2184             local $SIG{ALRM} = sub { die "alarm\n" };
2185             alarm $p{timeout};
2186             };
2187             #$r = system $cmd;
2188             $r = `$cmd 2>&1`;
2189             alarm 0 if defined $p{timeout};
2190             };
2191              
2192             if ($EVAL_ERROR) {
2193             if ( $EVAL_ERROR eq "alarm\n" ) {
2194             $log->audit("timed out");
2195             }
2196             else {
2197             return $log->error( "unknown error '$EVAL_ERROR'", %args);
2198             }
2199             }
2200             $ENV{PATH} = $before_path; # set PATH back to original value
2201              
2202             my @caller = caller;
2203             return $self->syscmd_exit_code( $r, $CHILD_ERROR, \@caller, \%args );
2204             }
2205              
2206             sub syscmd_exit_code {
2207             my $self = shift;
2208             my ($r, $err, $caller, $args) = @_;
2209              
2210             $log->audit( "r: $r" );
2211              
2212             my $exit_code = sprintf ("%d", $err >> 8);
2213             return 1 if $exit_code == 0; # success
2214              
2215             #print 'error # ' . $ERRNO . "\n"; # $! == $ERRNO
2216             $log->error( "$err: $r",fatal=>0);
2217              
2218             if ( $err == -1 ) { # check $? for "normal" errors
2219             $log->error( "failed to execute: $ERRNO", fatal=>0);
2220             }
2221             elsif ( $err & 127 ) { # check for core dump
2222             printf "child died with signal %d, %s coredump\n", ( $? & 127 ),
2223             ( $? & 128 ) ? 'with' : 'without';
2224             }
2225              
2226             return $log->error( "$err: $r", location => join( ", ", @$caller ), %$args );
2227             };
2228              
2229             sub yes_or_no {
2230             my $self = shift;
2231             my $question = shift;
2232             my %p = validate(
2233             @_,
2234             { 'timeout' => { type => SCALAR, optional => 1 },
2235             'debug' => { type => BOOLEAN, optional => 1, default => 1 },
2236             'force' => { type => BOOLEAN, optional => 1, default => 0 },
2237             },
2238             );
2239              
2240              
2241             # for 'make test' testing
2242             return 1 if $question eq "test";
2243              
2244             # force if interactivity testing is not working properly.
2245             if ( !$p{force} && !$self->is_interactive ) {
2246             warn "not running interactively, can't prompt!";
2247             return;
2248             }
2249              
2250             my $response;
2251              
2252             print "\nYou have $p{timeout} seconds to respond.\n" if $p{timeout};
2253             print "\n\t\t$question";
2254              
2255             # I wish I knew why this is not working correctly
2256             # eval { local $SIG{__DIE__}; require Term::ReadKey };
2257             # if ($@) { #
2258             # require Term::ReadKey;
2259             # Term::ReadKey->import();
2260             # print "yay, Term::ReadKey is present! Are you pleased? (y/n):\n";
2261             # use Term::Readkey;
2262             # ReadMode 4;
2263             # while ( not defined ($key = ReadKey(-1)))
2264             # { # no key yet }
2265             # print "Got key $key\n";
2266             # ReadMode 0;
2267             # };
2268              
2269             if ( $p{timeout} ) {
2270             eval {
2271             local $SIG{ALRM} = sub { die "alarm\n" };
2272             alarm $p{timeout};
2273             do {
2274             print "(y/n): ";
2275             $response = lc(<STDIN>);
2276             chomp($response);
2277             } until ( $response eq "n" || $response eq "y" );
2278             alarm 0;
2279             };
2280              
2281             if ($@) {
2282             $@ eq "alarm\n" ? print "timed out!\n" : warn;
2283             }
2284              
2285             return ($response && $response eq "y") ? 1 : 0;
2286             }
2287              
2288             do {
2289             print "(y/n): ";
2290             $response = lc(<STDIN>);
2291             chomp($response);
2292             } until ( $response eq "n" || $response eq "y" );
2293              
2294             return ($response eq "y") ? 1 : 0;
2295             }
2296              
2297             1;
2298              
2299             __END__
2300              
2301             =pod
2302              
2303             =encoding UTF-8
2304              
2305             =head1 NAME
2306              
2307             Provision::Unix::Utility - utility subroutines for sysadmin tasks
2308              
2309             =head1 VERSION
2310              
2311             version 1.07
2312              
2313             =head1 SYNOPSIS
2314              
2315             use Provision::Unix::Utility;
2316             my $util = Provision::Unix::Utility->new;
2317              
2318             $util->file_write($file, lines=> @lines);
2319              
2320             This is just one of the many handy little methods I have amassed here. Rather than try to remember all of the best ways to code certain functions and then attempt to remember them, I have consolidated years of experience and countless references from Learning Perl, Programming Perl, Perl Best Practices, and many other sources into these subroutines.
2321              
2322             =head1 DESCRIPTION
2323              
2324             This Utility module is my most frequently used one. Each method has documentation but in general, all methods accept as input a list of key value pairs (named parameters).
2325              
2326             =head1 DIAGNOSTICS
2327              
2328             All methods set and return error codes (0 = fail, 1 = success) unless otherwise stated.
2329              
2330             Unless otherwise mentioned, all methods accept two additional parameters:
2331              
2332             debug - to print status and verbose error messages, set debug=>1.
2333             fatal - die on errors. This is the default, set fatal=>0 to override.
2334              
2335             =head1 DEPENDENCIES
2336              
2337             Perl.
2338             Scalar::Util - built-in as of perl 5.8
2339              
2340             Almost nothing else. A few of the methods do require certain things, like extract_archive requires tar and file. But in general, this package (Provision::Unix::Utility) should run flawlessly on any UNIX-like system. Because I recycle this package in other places (not just Provision::Unix), I avoid creating dependencies here.
2341              
2342             =head1 METHODS
2343              
2344             =over
2345              
2346             =item new
2347              
2348             To use any of the methods below, you must first create a utility object. The methods can be accessed via the utility object.
2349              
2350             ############################################
2351             # Usage : use Provision::Unix::Utility;
2352             # : my $util = Provision::Unix::Utility->new;
2353             # Purpose : create a new Provision::Unix::Utility object
2354             # Returns : a bona fide object
2355             # Parameters : none
2356             ############################################
2357              
2358             =item ask
2359              
2360             Get a response from the user. If the user responds, their response is returned. If not, then the default response is returned. If no default was supplied, 0 is returned.
2361              
2362             ############################################
2363             # Usage : my $ask = $util->ask( "Would you like fries with that",
2364             # default => "SuperSized!",
2365             # timeout => 30
2366             # );
2367             # Purpose : prompt the user for information
2368             #
2369             # Returns : S - the users response (if not empty) or
2370             # : S - the default ask or
2371             # : S - an empty string
2372             #
2373             # Parameters
2374             # Required : S - question - what to ask
2375             # Optional : S - default - a default answer
2376             # : I - timeout - how long to wait for a response
2377             # Throws : no exceptions
2378             # See Also : yes_or_no
2379              
2380             =item extract_archive
2381              
2382             Decompresses a variety of archive formats using your systems built in tools.
2383              
2384             ############### extract_archive ##################
2385             # Usage : $util->extract_archive( 'example.tar.bz2' );
2386             # Purpose : test the archiver, determine its contents, and then
2387             # use the best available means to expand it.
2388             # Returns : 0 - failure, 1 - success
2389             # Parameters : S - archive - a bz2, gz, or tgz file to decompress
2390              
2391             =item cwd_source_dir
2392              
2393             Changes the current working directory to the supplied one. Creates it if it does not exist. Tries to create the directory using perl's builtin mkdir, then the system mkdir, and finally the system mkdir with sudo.
2394              
2395             ############ cwd_source_dir ###################
2396             # Usage : $util->cwd_source_dir( "/usr/local/src" );
2397             # Purpose : prepare a location to build source files in
2398             # Returns : 0 - failure, 1 - success
2399             # Parameters : S - dir - a directory to build programs in
2400              
2401             =item check_homedir_ownership
2402              
2403             Checks the ownership on all home directories to see if they are owned by their respective users in /etc/password. Offers to repair the permissions on incorrectly owned directories. This is useful when someone that knows better does something like "chown -R user /home /user" and fouls things up.
2404              
2405             ######### check_homedir_ownership ############
2406             # Usage : $util->check_homedir_ownership();
2407             # Purpose : repair user homedir ownership
2408             # Returns : 0 - failure, 1 - success
2409             # Parameters :
2410             # Optional : I - auto - no prompts, just fix everything
2411             # See Also : sysadmin
2412              
2413             Comments: Auto mode should be run with great caution. Run it first to see the results and then, if everything looks good, run in auto mode to do the actual repairs.
2414              
2415             =item check_pidfile
2416              
2417             check_pidfile is a process management method. It will check to make sure an existing pidfile does not exist and if not, it will create the pidfile.
2418              
2419             $pidfile = $util->check_pidfile( "/var/run/program.pid" );
2420              
2421             The above example is all you need to do to add process checking (avoiding multiple daemons running at the same time) to a program or script. This is used in toaster-watcher.pl. toaster-watcher normally completes a run in a few seconds and is run every 5 minutes.
2422              
2423             However, toaster-watcher can be configured to do things like expire old messages from maildirs and feed spam through a processor like sa-learn. This can take a long time on a large mail system so we don't want multiple instances of toaster-watcher running.
2424              
2425             result:
2426             the path to the pidfile (on success).
2427              
2428             Example:
2429              
2430             my $pidfile = $util->check_pidfile( "/var/run/changeme.pid" );
2431             unless ($pidfile) {
2432             warn "WARNING: couldn't create a process id file!: $!\n";
2433             exit 0;
2434             };
2435              
2436             do_a_bunch_of_cool_stuff;
2437             unlink $pidfile;
2438              
2439             =item chown_system
2440              
2441             The advantage this sub has over a Pure Perl implementation is that it can utilize sudo to gain elevated permissions that we might not otherwise have.
2442              
2443             ############### chown_system #################
2444             # Usage : $util->chown_system( "/tmp/example", user=>'matt' );
2445             # Purpose : change the ownership of a file or directory
2446             # Returns : 0 - failure, 1 - success
2447             # Parameters : S - dir - the directory to chown
2448             # : S - user - a system username
2449             # Optional : S - group - a sytem group name
2450             # : I - recurse - include all files/folders in directory?
2451             # Comments : Uses the system chown binary
2452             # See Also : n/a
2453              
2454             =item clean_tmp_dir
2455              
2456             ############## clean_tmp_dir ################
2457             # Usage : $util->clean_tmp_dir( dir=>$dir );
2458             # Purpose : clean up old build stuff before rebuilding
2459             # Returns : 0 - failure, 1 - success
2460             # Parameters : S - $dir - a directory or file.
2461             # Throws : die on failure
2462             # Comments : Running this will delete its contents. Be careful!
2463              
2464             =item get_mounted_drives
2465              
2466             ############# get_mounted_drives ############
2467             # Usage : my $mounts = $util->get_mounted_drives();
2468             # Purpose : Uses mount to fetch a list of mounted drive/partitions
2469             # Returns : a hashref of mounted slices and their mount points.
2470              
2471             =item archive_file
2472              
2473             ############### archive_file #################
2474             # Purpose : Make a backup copy of a file by copying the file to $file.timestamp.
2475             # Usage : my $archived_file = $util->archive_file( $file );
2476             # Returns : the filename of the backup file, or 0 on failure.
2477             # Parameters : S - file - the filname to be backed up
2478             # Comments : none
2479              
2480             =item chmod
2481              
2482             Set the permissions (ugo-rwx) of a file. Will use the native perl methods (by default) but can also use system calls and prepend sudo if additional permissions are needed.
2483              
2484             $util->chmod(
2485             file_or_dir => '/etc/resolv.conf',
2486             mode => '0755',
2487             sudo => $sudo
2488             )
2489              
2490             arguments required:
2491             file_or_dir - a file or directory to alter permission on
2492             mode - the permissions (numeric)
2493              
2494             arguments optional:
2495             sudo - the output of $util->sudo
2496             fatal - die on errors? (default: on)
2497             debug
2498              
2499             result:
2500             0 - failure
2501             1 - success
2502              
2503             =item chown
2504              
2505             Set the ownership (user and group) of a file. Will use the native perl methods (by default) but can also use system calls and prepend sudo if additional permissions are needed.
2506              
2507             $util->chown(
2508             file_or_dir => '/etc/resolv.conf',
2509             uid => 'root',
2510             gid => 'wheel',
2511             sudo => 1
2512             );
2513              
2514             arguments required:
2515             file_or_dir - a file or directory to alter permission on
2516             uid - the uid or user name
2517             gid - the gid or group name
2518              
2519             arguments optional:
2520             file - alias for file_or_dir
2521             dir - alias for file_or_dir
2522             sudo - the output of $util->sudo
2523             fatal - die on errors? (default: on)
2524             debug
2525              
2526             result:
2527             0 - failure
2528             1 - success
2529              
2530             =item file_delete
2531              
2532             ############################################
2533             # Usage : $util->file_delete( file=>$file );
2534             # Purpose : Deletes a file.
2535             # Returns : 0 - failure, 1 - success
2536             # Parameters
2537             # Required : file - a file path
2538             # Comments : none
2539             # See Also :
2540              
2541             Uses unlink if we have appropriate permissions, otherwise uses a system rm call, using sudo if it is not being run as root. This sub will try very hard to delete the file!
2542              
2543             =item get_url
2544              
2545             $util->get_url( $url, debug=>1 );
2546              
2547             Use the standard URL fetching utility (fetch, curl, wget) for your OS to download a file from the $url handed to us.
2548              
2549             arguments required:
2550             url - the fully qualified URL
2551              
2552             arguments optional:
2553             timeout - the maximum amount of time to try
2554             fatal
2555             debug
2556              
2557             result:
2558             1 - success
2559             0 - failure
2560              
2561             =item file_is_newer
2562              
2563             compares the mtime on two files to determine if one is newer than another.
2564              
2565             =item file_mode
2566              
2567             usage:
2568             my @lines = "1", "2", "3"; # named array
2569             $util->file_write ( "/tmp/foo", lines=>\@lines );
2570             or
2571             $util->file_write ( "/tmp/foo", lines=>['1','2','3'] ); # anon arrayref
2572              
2573             required arguments:
2574             mode - the files permissions mode
2575              
2576             arguments optional:
2577             fatal
2578             debug
2579              
2580             result:
2581             0 - failure
2582             1 - success
2583              
2584             =item file_read
2585              
2586             Reads in a file, and returns it in an array. All lines in the array are chomped.
2587              
2588             my @lines = $util->file_read( $file, max_lines=>100 )
2589              
2590             arguments required:
2591             file - the file to read in
2592              
2593             arguments optional:
2594             max_lines - integer - max number of lines
2595             max_length - integer - maximum length of a line
2596             fatal
2597             debug
2598              
2599             result:
2600             0 - failure
2601             success - returns an array with the files contents, one line per array element
2602              
2603             =item file_write
2604              
2605             usage:
2606             my @lines = "1", "2", "3"; # named array
2607             $util->file_write ( "/tmp/foo", lines=>\@lines );
2608             or
2609             $util->file_write ( "/tmp/foo", lines=>['1','2','3'] ); # anon arrayref
2610              
2611             required arguments:
2612             file - the file path you want to write to
2613             lines - an arrayref. Each array element will be a line in the file
2614              
2615             arguments optional:
2616             fatal
2617             debug
2618              
2619             result:
2620             0 - failure
2621             1 - success
2622              
2623             =item files_diff
2624              
2625             Determine if the files are different. $type is assumed to be text unless you set it otherwise. For anthing but text files, we do a MD5 checksum on the files to determine if they are different or not.
2626              
2627             $util->files_diff( f1=>$file1,f2=>$file2,type=>'text',debug=>1 );
2628              
2629             if ( $util->files_diff( f1=>"foo", f2=>"bar" ) )
2630             {
2631             print "different!\n";
2632             };
2633              
2634             required arguments:
2635             f1 - the first file to compare
2636             f2 - the second file to compare
2637              
2638             arguments optional:
2639             type - the type of file (text or binary)
2640             fatal
2641             debug
2642              
2643             result:
2644             0 - files are the same
2645             1 - files are different
2646             -1 - error.
2647              
2648             =item find_bin
2649              
2650             Check all the "normal" locations for a binary that should be on the system and returns the full path to the binary.
2651              
2652             $util->find_bin( 'dos2unix', dir=>'/opt/local/bin' );
2653              
2654             Example:
2655              
2656             my $apachectl = $util->find_bin( "apachectl", dir=>"/usr/local/sbin" );
2657              
2658              
2659             arguments required:
2660             bin - the name of the program (its filename)
2661              
2662             arguments optional:
2663             dir - a directory to check first
2664             fatal
2665             debug
2666              
2667             results:
2668             0 - failure
2669             success will return the full path to the binary.
2670              
2671             =item get_file
2672              
2673             an alias for get_url for legacy purposes. Do not use.
2674              
2675             =item get_my_ips
2676              
2677             returns an arrayref of IP addresses on local interfaces.
2678              
2679             =item is_process_running
2680              
2681             Verify if a process is running or not.
2682              
2683             $util->is_process_running($process) ? print "yes" : print "no";
2684              
2685             $process is the name as it would appear in the process table.
2686              
2687             =item is_readable
2688              
2689             ############################################
2690             # Usage : $util->is_readable( file=>$file );
2691             # Purpose : ????
2692             # Returns : 0 = no (not reabable), 1 = yes
2693             # Parameters : S - file - a path name to a file
2694             # Throws : no exceptions
2695             # Comments : none
2696             # See Also : n/a
2697              
2698             result:
2699             0 - no (file is not readable)
2700             1 - yes (file is readable)
2701              
2702             =item is_writable
2703              
2704             If the file exists, it checks to see if it is writable. If the file does not exist, it checks to see if the enclosing directory is writable.
2705              
2706             ############################################
2707             # Usage : $util->is_writable("/tmp/boogers");
2708             # Purpose : make sure a file is writable
2709             # Returns : 0 - no (not writable), 1 - yes (is writeable)
2710             # Parameters : S - file - a path name to a file
2711             # Throws : no exceptions
2712              
2713             =item fstab_list
2714              
2715             ############ fstab_list ###################
2716             # Usage : $util->fstab_list;
2717             # Purpose : Fetch a list of drives that are mountable from /etc/fstab.
2718             # Returns : an arrayref
2719             # Comments : used in backup.pl
2720             # See Also : n/a
2721              
2722             =item get_dir_files
2723              
2724             $util->get_dir_files( dir=>$dir, debug=>1 )
2725              
2726             required arguments:
2727             dir - a directory
2728              
2729             optional arguments:
2730             fatal
2731             debug
2732              
2733             result:
2734             an array of files names contained in that directory.
2735             0 - failure
2736              
2737             =item get_the_date
2738              
2739             Returns the date split into a easy to work with set of strings.
2740              
2741             $util->get_the_date( bump=>$bump, debug=>$debug )
2742              
2743             required arguments:
2744             none
2745              
2746             optional arguments:
2747             bump - the offset (in days) to subtract from the date.
2748             debug
2749              
2750             result: (array with the following elements)
2751             $dd = day
2752             $mm = month
2753             $yy = year
2754             $lm = last month
2755             $hh = hours
2756             $mn = minutes
2757             $ss = seconds
2758              
2759             my ($dd, $mm, $yy, $lm, $hh, $mn, $ss) = $util->get_the_date();
2760              
2761             =item install_from_source
2762              
2763             usage:
2764              
2765             $util->install_from_source(
2766             package => 'simscan-1.07',
2767             site => 'http://www.inter7.com',
2768             url => '/simscan/',
2769             targets => ['./configure', 'make', 'make install'],
2770             patches => '',
2771             debug => 1,
2772             );
2773              
2774             Downloads and installs a program from sources.
2775              
2776             required arguments:
2777             conf - hashref - mail-toaster.conf settings.
2778             site -
2779             url -
2780             package -
2781              
2782             optional arguments:
2783             targets - arrayref - defaults to [./configure, make, make install].
2784             patches - arrayref - patch(es) to apply to the sources before compiling
2785             patch_args -
2786             source_sub_dir - a subdirectory within the sources build directory
2787             bintest - check the usual places for an executable binary. If found, it will assume the software is already installed and require confirmation before re-installing.
2788             debug
2789             fatal
2790              
2791             result:
2792             1 - success
2793             0 - failure
2794              
2795             =item install_from_source_php
2796              
2797             Downloads a PHP program and installs it. This function is not completed due to lack o interest.
2798              
2799             =item is_interactive
2800              
2801             tests to determine if the running process is attached to a terminal.
2802              
2803             =item logfile_append
2804              
2805             $util->logfile_append( file=>$file, lines=>\@lines )
2806              
2807             Pass a filename and an array ref and it will append a timestamp and the array contents to the file. Here's a working example:
2808              
2809             $util->logfile_append( file=>$file, prog=>"proggy", lines=>["Starting up", "Shutting down"] )
2810              
2811             That will append a line like this to the log file:
2812              
2813             2004-11-12 23:20:06 proggy Starting up
2814             2004-11-12 23:20:06 proggy Shutting down
2815              
2816             arguments required:
2817             file - the log file to append to
2818             prog - the name of the application
2819             lines - arrayref - elements are events to log.
2820              
2821             arguments optional:
2822             fatal
2823             debug
2824              
2825             result:
2826             1 - success
2827             0 - failure
2828              
2829             =item mailtoaster
2830              
2831             $util->mailtoaster();
2832              
2833             Downloads and installs Mail::Toaster.
2834              
2835             =item mkdir_system
2836              
2837             $util->mkdir_system( dir => $dir, debug=>$debug );
2838              
2839             creates a directory using the system mkdir binary. Can also make levels of directories (-p) and utilize sudo if necessary to escalate.
2840              
2841             =item regexp_test
2842              
2843             Prints out a string with the regexp match bracketed. Credit to Damien Conway from Perl Best Practices.
2844              
2845             Example:
2846             $util->regexp_test(
2847             exp => 'toast',
2848             string => 'mailtoaster rocks',
2849             );
2850              
2851             arguments required:
2852             exp - the regular expression
2853             string - the string you are applying the regexp to
2854              
2855             result:
2856             printed string highlighting the regexp match
2857              
2858             =item source_warning
2859              
2860             Checks to see if the old build sources are present. If they are, offer to remove them.
2861              
2862             Usage:
2863              
2864             $util->source_warning(
2865             package => "Provision-Unix-0.96",
2866             clean => 1,
2867             src => "/usr/local/src"
2868             );
2869              
2870             arguments required:
2871             package - the name of the packages directory
2872              
2873             arguments optional:
2874             src - the source directory to build in (/usr/local/src)
2875             clean - do we try removing the existing sources? (enabled)
2876             timeout - how long to wait for an answer (60 seconds)
2877              
2878             result:
2879             1 - removed
2880             0 - failure, package exists and needs to be removed.
2881              
2882             =item sources_get
2883              
2884             Tries to download a set of sources files from the site and url provided. It will try first fetching a gzipped tarball and if that files, a bzipped tarball. As new formats are introduced, I will expand the support for them here.
2885              
2886             usage:
2887             $self->sources_get(
2888             package => 'simscan-1.07',
2889             site => 'http://www.inter7.com',
2890             path => '/simscan/',
2891             )
2892              
2893             arguments required:
2894             package - the software package name
2895             site - the host to fetch it from
2896             url - the path to the package on $site
2897              
2898             arguments optional:
2899             conf - hashref - values from toaster-watcher.conf
2900             debug
2901              
2902             This sub proved quite useful during 2005 as many packages began to be distributed in bzip format instead of the traditional gzip.
2903              
2904             =item sudo
2905              
2906             my $sudo = $util->sudo();
2907              
2908             $util->syscmd( "$sudo rm /etc/root-owned-file" );
2909              
2910             Often you want to run a script as an unprivileged user. However, the script may need elevated privileges for a plethora of reasons. Rather than running the script suid, or as root, configure sudo allowing the script to run system commands with appropriate permissions.
2911              
2912             If sudo is not installed and you're running as root, it'll offer to install sudo for you. This is recommended, as is properly configuring sudo.
2913              
2914             arguments required:
2915              
2916             arguments optional:
2917             debug
2918              
2919             result:
2920             0 - failure
2921             on success, the full path to the sudo binary
2922              
2923             =item syscmd
2924              
2925             Just a little wrapper around system calls, that returns any failure codes and prints out the error(s) if present. A bit of sanity testing is also done to make sure the command to execute is safe.
2926              
2927             my $r = $util->syscmd( "gzip /tmp/example.txt" );
2928             $r ? print "ok!\n" : print "not ok.\n";
2929              
2930             arguments required:
2931             cmd - the command to execute
2932              
2933             arguments optional:
2934             debug
2935             fatal
2936              
2937             result
2938             the exit status of the program you called.
2939              
2940             =item _try_mkdir
2941              
2942             try creating a directory using perl's builtin mkdir.
2943              
2944             =item yes_or_no
2945              
2946             my $r = $util->yes_or_no(
2947             "Would you like fries with that?",
2948             timeout => 30
2949             );
2950              
2951             $r ? print "fries are in the bag\n" : print "no fries!\n";
2952              
2953             arguments required:
2954             none.
2955              
2956             arguments optional:
2957             question - the question to ask
2958             timeout - how long to wait for an answer (in seconds)
2959              
2960             result:
2961             0 - negative (or null)
2962             1 - success (affirmative)
2963              
2964             =back
2965              
2966             =head1 TODO
2967              
2968             make all errors raise exceptions
2969             write test cases for every method
2970             comments. always needs more comments.
2971              
2972             =head1 SEE ALSO
2973              
2974             The following are all man/perldoc pages:
2975              
2976             Provision::Unix
2977              
2978             =head1 AUTHOR
2979              
2980             Matt Simerson <msimerson@cpan.org>
2981              
2982             =head1 COPYRIGHT AND LICENSE
2983              
2984             This software is copyright (c) 2014 by The Network People, Inc..
2985              
2986             This is free software; you can redistribute it and/or modify it under
2987             the same terms as the Perl 5 programming language system itself.
2988              
2989             =cut