File Coverage

lib/Provision/Unix/Utility.pm
Criterion Covered Total %
statement 513 1074 47.7
branch 173 656 26.3
condition 24 148 16.2
subroutine 54 80 67.5
pod 36 57 63.1
total 800 2015 39.7


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