File Coverage

blib/lib/Sysadm/Install.pm
Criterion Covered Total %
statement 328 492 66.6
branch 117 276 42.3
condition 16 53 30.1
subroutine 46 63 73.0
pod 38 47 80.8
total 545 931 58.5


line stmt bran cond sub pod time code
1             ###############################################
2             package Sysadm::Install;
3             ###############################################
4              
5 18     18   507523 use 5.006;
  18         79  
6 18     18   103 use strict;
  18         30  
  18         693  
7 18     18   96 use warnings;
  18         41  
  18         1055  
8              
9             our $VERSION = '0.48';
10              
11 18     18   14149 use File::Copy;
  18         112981  
  18         2912  
12 18     18   168 use File::Path;
  18         72  
  18         1401  
13 18     18   13311 use File::Which;
  18         22602  
  18         1693  
14 18     18   22624 use Log::Log4perl qw(:easy);
  18         1216131  
  18         141  
15 18     18   12102 use Log::Log4perl::Util;
  18         36  
  18         1265  
16 18     18   121 use File::Basename;
  18         42  
  18         2231  
17 18     18   16729 use File::Spec::Functions qw(rel2abs abs2rel);
  18         18773  
  18         1992  
18 18     18   140 use Cwd;
  18         39  
  18         1738  
19 18     18   21727 use File::Temp qw(tempfile);
  18         463270  
  18         9899  
20              
21             our $DRY_RUN;
22             our $CONFIRM;
23             our $DRY_RUN_MSG;
24             our $DATA_SNIPPED_LEN = 60;
25              
26             dry_run(0);
27             confirm(0);
28              
29             ###############################################
30             sub dry_run {
31             ###############################################
32 20     20 0 429 my($on) = @_;
33              
34 20 100       107 if($on) {
35 1         2 $DRY_RUN = 1;
36 1         3 $DRY_RUN_MSG = "(skipped - dry run)";
37             } else {
38 19         47 $DRY_RUN = 0;
39 19         65 $DRY_RUN_MSG = "";
40             }
41             }
42              
43             ###############################################
44             sub confirm {
45             ###############################################
46 18     18 0 44 my($on) = @_;
47              
48 18         47 $CONFIRM = $on;
49             }
50              
51             ###########################################
52             sub _confirm {
53             ###########################################
54 32     32   69 my($msg) = @_;
55              
56 32 100       115 if($DRY_RUN) {
57 1         5 INFO "$msg $DRY_RUN_MSG";
58 1 50       10 return 0 if $DRY_RUN;
59             }
60              
61 31 50       82 if($CONFIRM) {
62 0         0 my $answer = ask("$msg ([y]/n)", "y");
63 0 0       0 if($answer =~ /^\s*y\s*$/) {
64 0         0 INFO $msg;
65 0         0 return 1;
66             }
67              
68 0         0 INFO "$msg (*CANCELLED* as requested)";
69 0         0 return 0;
70             }
71              
72 31         120 return 1;
73             }
74              
75             our @EXPORTABLE = qw(
76             cp rmf mkd cd make
77             cdback download untar
78             pie slurp blurt mv tap
79             plough qquote quote perm_cp owner_cp
80             perm_get perm_set
81             sysrun untar_in pick ask
82             hammer say
83             sudo_me bin_find
84             fs_read_open fs_write_open pipe_copy
85             snip password_read nice_time
86             def_or blurt_atomic
87             is_utf8_data utf8_available
88             printable home_dir
89             );
90              
91             our %EXPORTABLE = map { $_ => 1 } @EXPORTABLE;
92              
93             our @DIR_STACK;
94              
95             ##################################################
96             sub import {
97             ##################################################
98 18     18   237 my($class) = shift;
99              
100 18     18   192 no strict qw(refs);
  18         27  
  18         136747  
101              
102 18         55 my $caller_pkg = caller();
103              
104 18         52 my(%tags) = map { $_ => 1 } @_;
  17         89  
105              
106             # Export all
107 18 100       120 if(exists $tags{':all'}) {
108 16         45 %tags = map { $_ => 1 } @EXPORTABLE;
  640         1090  
109             }
110              
111 18         277 for my $func (keys %tags) {
112             LOGDIE __PACKAGE__ .
113 641 50       1321 "doesn't export \"$func\"" unless exists $EXPORTABLE{$func};
114 641         561 *{"$caller_pkg\::$func"} = *{$func};
  641         9695  
  641         1193  
115             }
116             }
117              
118             =pod
119              
120             =head1 NAME
121              
122             Sysadm::Install - Typical installation tasks for system administrators
123              
124             =head1 SYNOPSIS
125              
126             use Sysadm::Install qw(:all);
127              
128             my $INST_DIR = '/home/me/install/';
129              
130             cd($INST_DIR);
131             cp("/deliver/someproj.tgz", ".");
132             untar("someproj.tgz");
133             cd("someproj");
134              
135             # Write out ...
136             blurt("Builder: Mike\nDate: Today\n", "build.dat");
137              
138             # Slurp back in ...
139             my $data = slurp("build.dat");
140              
141             # or edit in place ...
142             pie(sub { s/Today/scalar localtime()/ge; $_; }, "build.dat");
143              
144             make("test install");
145              
146             # run a cmd and tap into stdout and stderr
147             my($stdout, $stderr, $exit_code) = tap("ls", "-R");
148              
149             =head1 DESCRIPTION
150              
151             Have you ever wished for your installation shell scripts to run
152             reproducibly, without much programming fuzz, and even with optional
153             logging enabled? Then give up shell programming, use Perl.
154              
155             C executes shell-like commands performing typical
156             installation tasks: Copying files, extracting tarballs, calling C.
157             It has a C policy, meticulously checking the result
158             of every operation and calling C immediately if anything fails.
159              
160             C also supports a I mode, in which it
161             logs everything, but suppresses any write actions. Dry run mode
162             is enabled by calling C. To switch
163             back to normal, call C.
164              
165             As of version 0.17, C supports a I mode,
166             in which it interactively asks the user before running any of its
167             functions (just like C). I mode is enabled by calling
168             C. To switch
169             back to normal, call C.
170              
171             C is fully Log4perl-enabled. To start logging, just
172             initialize C. C acts as a wrapper class,
173             meaning that file names and line numbers are reported from the calling
174             program's point of view.
175              
176             =head2 FUNCTIONS
177              
178             =over 4
179              
180             =item C
181              
182             Copy a file from C<$source> to C<$target>. C can be a directory.
183             Note that C doesn't copy file permissions. If you want the target
184             file to reflect the source file's user rights, use C
185             shown below.
186              
187             =cut
188              
189             ###############################################
190             sub cp {
191             ###############################################
192              
193 2     2 1 817 local $Log::Log4perl::caller_depth =
194             $Log::Log4perl::caller_depth + 1;
195              
196 2 50       13 _confirm("cp $_[0] $_[1]") or return 1;
197              
198 2         15 INFO "cp $_[0] $_[1]";
199              
200 2 100       25 File::Copy::copy @_ or
201             LOGCROAK("Cannot copy $_[0] to $_[1] ($!)");
202             }
203              
204             =pod
205              
206             =item C
207              
208             Move a file from C<$source> to C<$target>. C can be a directory.
209              
210             =cut
211              
212             ###############################################
213             sub mv {
214             ###############################################
215              
216 0     0 1 0 local $Log::Log4perl::caller_depth =
217             $Log::Log4perl::caller_depth + 1;
218              
219 0 0       0 _confirm("mv $_[0] $_[1]") or return 1;
220              
221 0         0 INFO "mv $_[0] $_[1]";
222              
223 0 0       0 File::Copy::move @_ or
224             LOGCROAK("Cannot move $_[0] to $_[1] ($!)");
225             }
226              
227             =pod
228              
229             =item C
230              
231             Download a file specified by C<$url> and store it under the
232             name returned by C.
233              
234             =cut
235              
236             ###############################################
237             sub download {
238             ###############################################
239 1     1 1 9 my($url) = @_;
240              
241 1         2 local $Log::Log4perl::caller_depth =
242             $Log::Log4perl::caller_depth + 1;
243              
244 1         8 INFO "download $url";
245              
246 1 50       74 _confirm("Downloading $url => ", basename($url)) or return 1;
247              
248 1         764 require LWP::UserAgent;
249 1         45386 require HTTP::Request;
250 1         7 require HTTP::Status;
251              
252 1         21 my $ua = LWP::UserAgent->new();
253 1         5432 my $request = HTTP::Request->new(GET => $url);
254 1         10946 my $response = $ua->request($request, basename($_[0]));
255 1         30319 my $rc = $response->code();
256            
257 1 50       21 if($rc != HTTP::Status::RC_OK()) {
258 1         10 LOGCROAK("Cannot download $_[0] (",
259             $response->message(),
260             ")");
261             }
262              
263 0         0 return 1;
264             }
265              
266             =pod
267              
268             =item C
269              
270             Untar the tarball in C<$tarball>, which typically adheres to the
271             C convention.
272             But regardless of whether the
273             archive actually contains a top directory C,
274             this function will behave if it had one. If it doesn't have one,
275             a new directory is created before the unpacking takes place. Unpacks
276             the tarball into the current directory, no matter where the tarfile
277             is located.
278             Please note that if you're
279             using a compressed tarball (.tar.gz or .tgz), you'll need
280             IO::Zlib installed.
281              
282             =cut
283              
284             ###############################################
285             sub untar {
286             ###############################################
287              
288 3     3 1 5878 local $Log::Log4perl::caller_depth =
289             $Log::Log4perl::caller_depth + 1;
290              
291 3 50 33     40 LOGCROAK("untar called without defined tarfile") unless
292             @_ == 1 and defined $_[0];
293              
294 3 50       29 _confirm "untar $_[0]" or return 1;
295              
296 3         20 my($nice, $topdir, $namedir) = archive_sniff($_[0]);
297              
298 3         14 check_zlib($_[0]);
299 3         28 require Archive::Tar;
300 3         25 my $arch = Archive::Tar->new($_[0]);
301              
302 3         15479 my @extracted = ();
303              
304 3 100 66     35 if($nice and $topdir eq $namedir) {
    50          
305 1         11 DEBUG "Nice archive, extracting to subdir $topdir";
306 1         18 @extracted = $arch->extract();
307             } elsif($nice) {
308 0         0 DEBUG "Not-so-nice archive topdir=$topdir namedir=$namedir";
309             # extract as topdir
310 0         0 @extracted = $arch->extract();
311 0 0       0 rename $topdir, $namedir or
312             LOGCROAK("Can't rename $topdir, $namedir");
313             } else {
314 2 50       7 LOGCROAK("no topdir") unless defined $topdir;
315 2         12 DEBUG "Not-so-nice archive (no topdir), extracting to subdir $topdir";
316 2         125 $topdir = basename $topdir;
317 2         8 mkd($topdir);
318 2         9 cd($topdir);
319 2         11 @extracted = $arch->extract();
320 2         17731 cdback();
321             }
322              
323 3 50       7232 if( !@extracted ) {
324 0         0 LOGCROAK "Archive $_[0] was empty.";
325             }
326              
327 3         119 return $topdir;
328             }
329              
330             =pod
331              
332             =item C
333              
334             Untar the tarball in C<$tgz_file> in directory C<$dir>. Create
335             C<$dir> if it doesn't exist yet.
336              
337             =cut
338              
339             ###############################################
340             sub untar_in {
341             ###############################################
342 0     0 1 0 my($tar_file, $dir) = @_;
343              
344 0         0 local $Log::Log4perl::caller_depth =
345             $Log::Log4perl::caller_depth + 1;
346              
347 0 0 0     0 LOGCROAK("not enough arguments") if
348             ! defined $tar_file or ! defined $dir;
349              
350 0 0       0 _confirm "Untarring $tar_file in $dir" or return 1;
351              
352 0 0       0 mkd($dir) unless -d $dir;
353              
354 0         0 my $tar_file_abs = rel2abs($tar_file);
355              
356 0         0 cd($dir);
357              
358 0         0 check_zlib($tar_file_abs);
359 0         0 require Archive::Tar;
360 0         0 my $arch = Archive::Tar->new("$tar_file_abs");
361 0 0       0 $arch->extract() or
362             LOGCROAK("Extract failed: ($!)");
363 0         0 cdback();
364             }
365              
366             =pod
367              
368             =item C
369              
370             Ask the user to pick an item from a displayed list. C<$prompt>
371             is the text displayed, C<$options> is a referenc to an array of
372             choices, and C<$default> is the number (starting from 1, not 0)
373             of the default item. For example,
374              
375             pick("Pick a fruit", ["apple", "pear", "pineapple"], 3);
376              
377             will display the following:
378              
379             [1] apple
380             [2] pear
381             [3] pineapple
382             Pick a fruit [3]>
383              
384             If the user just hits I, "pineapple" (the default value) will
385             be returned. Note that 3 marks the 3rd element of the list, and is
386             I an index value into the array.
387              
388             If the user enters C<1>, C<2> or C<3>, the corresponding text string
389             (C<"apple">, C<"pear">, C<"pineapple"> will be returned by
390             C.
391              
392             If the optional C<$opts> hash has C<{ tty =E 1 }> set, then
393             the user response will be expected from the console, not STDIN.
394              
395             =cut
396              
397             ##################################################
398             sub pick {
399             ##################################################
400 0     0 1 0 my ($prompt, $options, $default, $opts) = @_;
401              
402 0         0 local $Log::Log4perl::caller_depth =
403             $Log::Log4perl::caller_depth + 1;
404              
405 0         0 my $default_int;
406             my %files;
407              
408 0 0 0     0 if(@_ < 3 or ref($options) ne "ARRAY") {
409 0         0 LOGCROAK("pick called with wrong #/type of args");
410             }
411            
412             {
413 0         0 my $count = 0;
  0         0  
414              
415 0         0 my $user_prompt = "";
416              
417 0         0 foreach (@$options) {
418 0         0 $user_prompt .= "[" . ++$count . "] $_\n";
419 0 0       0 $default_int = $count if $count eq $default;
420 0         0 $files{$count} = $_;
421             }
422            
423 0         0 $user_prompt .= "$prompt [$default_int]> ";
424 0         0 my $input = user_input($user_prompt, $opts);
425              
426 0 0 0     0 $input = $default_int if !defined $input or !length($input);
427              
428 0 0 0     0 redo if $input !~ /^\d+$/ or
      0        
429             $input == 0 or
430             $input > scalar @$options;
431 0         0 return "$files{$input}";
432             }
433             }
434              
435             =pod
436              
437             =item C
438              
439             Ask the user to either hit I and select the displayed default
440             or to type in another string.
441              
442             If the optional C<$opts> hash has C<{ tty =E 1 }> set, then
443             the user response will be expected from the console, not STDIN.
444              
445             =cut
446              
447             ##################################################
448             sub ask {
449             ##################################################
450 0     0 1 0 my ($prompt, $default, $opts) = @_;
451              
452 0 0       0 $opts = {} if !defined $opts;
453              
454 0         0 local $Log::Log4perl::caller_depth =
455             $Log::Log4perl::caller_depth + 1;
456              
457 0 0       0 if(@_ < 2) {
458 0         0 LOGCROAK("ask() called with wrong # of args");
459             }
460              
461 0         0 my $value = user_input("$prompt [$default]> ", $opts);
462 0 0       0 $value = $default if $value eq "";
463              
464 0         0 return $value;
465             }
466              
467             ##################################################
468             sub user_prompt {
469             ##################################################
470 0     0 0 0 my ($prompt, $opts) = @_;
471              
472 0 0       0 $opts = {} if !defined $opts;
473              
474 0         0 my $fh = *STDERR;
475 0         0 my $old_stderr;
476 0 0       0 if( $opts->{ tty } ) {
477 0 0       0 open $old_stderr, ">&", \*STDERR or
478             die "Can't dup STDERR: $!";
479 0 0       0 open $fh, ">>", '/dev/tty' or
480             die "Cannot open /dev/tty ($!)";
481             }
482              
483 0 0       0 print $fh $prompt
484             or die "Couldn't write to $fh: ($!)";
485              
486 0 0       0 if( $opts->{ tty } ) {
487 0         0 close $fh;
488 0 0       0 open STDERR, ">&", $old_stderr or
489             die "Can't reset STDERR";
490             }
491              
492 0         0 return 1;
493             }
494              
495             ##################################################
496             sub user_input {
497             ##################################################
498 0     0 0 0 my ($prompt, $opts) = @_;
499              
500 0 0       0 $opts = {} if !defined $opts;
501              
502 0         0 user_prompt( $prompt );
503              
504 0         0 my $fh = *STDIN;
505              
506 0 0       0 if( $opts->{ tty } ) {
507 0 0       0 open $fh, "<", '/dev/tty' or
508             die "Cannot open /dev/tty ($!)";
509             }
510              
511 0         0 my $input = <$fh>;
512 0 0       0 chomp $input if defined $input;
513              
514 0         0 return $input;
515             }
516              
517             =pod
518              
519             =item C
520              
521             Create a directory of arbitrary depth, just like C.
522              
523             =cut
524              
525             ###############################################
526             sub mkd {
527             ###############################################
528              
529 5     5 1 1394 local $Log::Log4perl::caller_depth =
530             $Log::Log4perl::caller_depth + 1;
531              
532 5 50       39 _confirm "mkd @_" or return 1;
533              
534 5         40 INFO "mkpath @_";
535              
536 5 100       1196 mkpath @_ or
537             LOGCROAK("Cannot mkdir @_ ($!)");
538             }
539              
540             =pod
541              
542             =item C
543              
544             Delete a directory and all of its descendents, just like C
545             in the shell.
546              
547             =cut
548              
549             ###############################################
550             sub rmf {
551             ###############################################
552              
553 4     4 1 11796 local $Log::Log4perl::caller_depth =
554             $Log::Log4perl::caller_depth + 1;
555              
556 4 50       24 _confirm "rmf $_[0]" or return 1;
557              
558 4 100       83 if(!-e $_[0]) {
559 2         11 DEBUG "$_[0] doesn't exist - ignored";
560 2         18 return;
561             }
562              
563 2         21 INFO "rmtree @_";
564              
565 2 50       2936 rmtree $_[0] or
566             LOGCROAK("Cannot rmtree $_[0] ($!)");
567             }
568              
569             =pod
570              
571             =item C
572              
573             chdir to the given directory. If you don't want to have cd() modify
574             the internal directory stack (used for subsequent cdback() calls),
575             set the stack_update parameter to a false value:
576              
577             cd($dir, {stack_update => 0});
578              
579             =cut
580              
581             ###############################################
582             sub cd {
583             ###############################################
584              
585 19     19 1 12152 local $Log::Log4perl::caller_depth =
586             $Log::Log4perl::caller_depth + 1;
587              
588 19         123 INFO "cd $_[0]";
589              
590 19         1120 my $opts = { stack_update => 1 };
591 19 100       110 $opts = $_[1] if ref $_[1] eq "HASH";
592              
593 19 100       80 if ($opts->{stack_update}) {
594 12         88 my $cwd = getcwd();
595 12 50       45 if(! defined $cwd) {
596 0         0 LOGCROAK("Cannot getcwd ($!)"); ;
597             }
598 12         37 push @DIR_STACK, $cwd;
599             }
600              
601 19 100       679 chdir($_[0]) or
602             LOGCROAK("Cannot cd $_[0] ($!)");
603             }
604              
605             =pod
606              
607             =item C
608              
609             chdir back to the last directory before a previous C. If the
610             option C is set, it goes all the way back to the beginning of the
611             directory stack, i.e. no matter how many cd() calls were made in between,
612             it'll go back to the original directory:
613              
614             # go all the way back
615             cdback( { reset => 1 } );
616              
617             =cut
618              
619             ###############################################
620             sub cdback {
621             ###############################################
622 7     7 1 2024 my( $opts ) = @_;
623              
624 7 100       53 $opts = {} if !defined $opts;
625              
626 7         35 local $Log::Log4perl::caller_depth =
627             $Log::Log4perl::caller_depth + 1;
628              
629 7 50       47 LOGCROAK("cd stack empty") unless @DIR_STACK;
630              
631 7 100       32 if( $opts->{ reset } ) {
632 1         8 @DIR_STACK = ( $DIR_STACK[0] );
633             }
634              
635 7         32 my $old_dir = pop @DIR_STACK;
636              
637 7 50       37 LOGCROAK("Directory stack empty")
638             if ! defined $old_dir;
639              
640 7         100 INFO "cdback to $old_dir";
641 7         134 cd($old_dir, {stack_update => 0});
642             }
643              
644             =pod
645              
646             =item C
647              
648             Call C in the shell.
649              
650             =cut
651              
652             ###############################################
653             sub make {
654             ###############################################
655              
656 0     0 1 0 local $Log::Log4perl::caller_depth =
657             $Log::Log4perl::caller_depth + 1;
658              
659 0 0       0 _confirm "make @_" or return 1;
660              
661 0         0 INFO "make @_";
662              
663 0 0       0 system("make @_") and
664             LOGCROAK("Cannot make @_ ($!)");
665             }
666              
667             =pod
668              
669             =cut
670              
671             ###############################################
672             sub check_zlib {
673             ###############################################
674 6     6 0 25 my($tar_file) = @_;
675              
676 6 50 33     59 if($tar_file =~ /\.tar\.gz\b|\.tgz\b/ and
677             !Log::Log4perl::Util::module_available("IO::Zlib")) {
678              
679 0         0 LOGCROAK("$tar_file: Compressed tarballs can ",
680             "only be processed with IO::Zlib installed.");
681             }
682             }
683            
684             #######################################
685             sub archive_sniff {
686             #######################################
687 3     3 0 8 my($name) = @_;
688              
689 3         12 local $Log::Log4perl::caller_depth =
690             $Log::Log4perl::caller_depth + 1;
691              
692 3         26 DEBUG "Sniffing archive '$name'";
693              
694 3         74 my ($dir) = ($name =~ /(.*?)\.(tar\.gz|tgz|tar)$/);
695            
696 3 50       14 return 0 unless defined $dir;
697              
698 3         244 $dir = basename($dir);
699 3         19 DEBUG "dir=$dir";
700              
701 3         23 my $topdir;
702              
703 3         12 check_zlib($name);
704              
705 3         28 require Archive::Tar;
706 3         34 my $tar = Archive::Tar->new($name);
707              
708 3         18226 my @names = $tar->list_files(["name"]);
709            
710 3 50       289 LOGCROAK("Archive $name is empty") unless @names;
711              
712 3         18 (my $archdir = $names[0]) =~ s#/.*##;
713              
714 3         26 DEBUG "archdir=$archdir";
715              
716 3         45 for my $name (@names) {
717 7 50       22 next if $name eq "./";
718 7         11 $name =~ s#^\./##;
719 7         23 ($topdir = $name) =~ s#/.*##;
720 7 100       21 if($topdir ne $archdir) {
721 2         38 return (0, $dir, $dir);
722             }
723             }
724              
725 1         10 DEBUG "Return $topdir $dir";
726              
727 1         31 return (1, $topdir, $dir);
728             }
729              
730             =pod
731              
732             =item C
733              
734             Simulate "perl -pie 'do something' file". Edits files in-place. Expects
735             a reference to a subroutine as its first argument. It will read out the
736             file C<$filename> line by line and calls the subroutine setting
737             a localized C<$_> to the current line. The return value of the subroutine
738             will replace the previous value of the line.
739              
740             Example:
741              
742             # Replace all 'foo's by 'bar' in test.dat
743             pie(sub { s/foo/bar/g; $_; }, "test.dat");
744              
745             Works with one or more file names.
746              
747             If the files are known to contain UTF-8 encoded data, and you want it
748             to be read/written as a Unicode strings, use the C option:
749              
750             pie(sub { s/foo/bar/g; $_; }, "test.dat", { utf8 => 1 });
751              
752             =cut
753              
754             ###############################################
755             sub pie {
756             ###############################################
757 1     1 1 468 my($coderef, @files) = @_;
758              
759 1         5 my $options = {};
760              
761 1 50 33     13 if(defined $files[-1] and
762             ref $files[-1] eq "HASH") {
763 0         0 $options = pop @files;
764             }
765              
766 1         3 local $Log::Log4perl::caller_depth =
767             $Log::Log4perl::caller_depth + 1;
768              
769 1         4 for my $file (@files) {
770              
771 1 50       7 _confirm "editing $file in-place" or next;
772              
773 1         3 my $out = "";
774              
775 1 50       36 open FILE, "<$file" or
776             LOGCROAK("Cannot open $file ($!)");
777              
778 1 50       5 if( $options->{utf8} ) {
779 0         0 binmode FILE, ":utf8";
780             }
781              
782 1         24 while() {
783 3         19 $out .= $coderef->($_);
784             }
785 1         23 close FILE;
786              
787 1         4 blurt($out, $file, $options);
788             }
789             }
790              
791             =pod
792              
793             =item C
794              
795             Simulate "perl -ne 'do something' file". Iterates over all lines
796             of all input files and calls the subroutine provided as the first argument.
797              
798             Example:
799              
800             # Print all lines containing 'foobar'
801             plough(sub { print if /foobar/ }, "test.dat");
802              
803             Works with one or more file names.
804              
805             If the files are known to contain UTF-8 encoded data, and you want it
806             to be read into Unicode strings, use the C option:
807              
808             plough(sub { print if /foobar/ }, "test.dat", { utf8 => 1 });
809              
810             =cut
811              
812             ###############################################
813             sub plough {
814             ###############################################
815 1     1 1 782 my($coderef, @files) = @_;
816              
817 1         3 my $options = {};
818              
819 1 50 33     10 if(defined $files[-1] and
820             ref $files[-1] eq "HASH") {
821 0         0 $options = pop @files;
822             }
823              
824 1         3 local $Log::Log4perl::caller_depth =
825             $Log::Log4perl::caller_depth + 1;
826              
827 1         3 for my $file (@files) {
828              
829 1 50       5 _confirm "Ploughing through $file" or next;
830              
831 1         4 my $out = "";
832              
833 1 50       31 open FILE, "<$file" or
834             LOGCROAK("Cannot open $file ($!)");
835              
836 1 50       6 if( $options->{utf8} ) {
837 0         0 binmode FILE, ":utf8";
838             }
839              
840 1         11 while() {
841 3         17 $coderef->($_);
842             }
843 1         14 close FILE;
844             }
845             }
846              
847             =pod
848              
849             =item C
850              
851             Slurps in the file and returns a scalar with the file's content. If
852             called without argument, data is slurped from STDIN or from any files
853             provided on the command line (like EE operates).
854              
855             If the file is known to contain UTF-8 encoded data and you want to
856             read it in as a Unicode string, use the C option:
857              
858             my $unicode_string = slurp( $file, {utf8 => 1} );
859              
860             =cut
861              
862             ###############################################
863             sub slurp {
864             ###############################################
865 9     9 1 1016 my($file, $options) = @_;
866              
867 9 100       48 $options = {} unless defined $options;
868              
869 9         40 local $Log::Log4perl::caller_depth =
870             $Log::Log4perl::caller_depth + 1;
871              
872 9         25 my $from_file = defined($file);
873              
874 9         64 local $/ = undef;
875              
876 9         15 my $data;
877              
878 9 100       35 if($from_file) {
879 8         120 INFO "Slurping data from $file";
880 8 50       471 open FILE, "<$file" or
881             LOGCROAK("Cannot open $file ($!)");
882 8         32 binmode FILE; # Win32 wants that
883 8 100       35 if( exists $options->{utf8} ) {
884 1         8 binmode FILE, ":utf8";
885             }
886 8         175 $data = ;
887 8         68 close FILE;
888 8         65 DEBUG "Read ", snip($data, $DATA_SNIPPED_LEN), " from $file";
889             } else {
890 1         5 INFO "Slurping data from <>";
891 1         46 $data = <>;
892 1         5 DEBUG "Read ", snip($data, $DATA_SNIPPED_LEN), " from <>";
893             }
894              
895 9         146 return $data;
896             }
897              
898             =pod
899              
900             =item C
901              
902             Opens a new file, prints the data in C<$data> to it and closes the file. If
903             C<$options-E{append}> is set to a true value, data will be appended to the
904             file. Default is false, existing files will be overwritten.
905              
906             If the string is a Unicode string, use the C option:
907              
908             blurt( $unicode_string, $file, {utf8 => 1} );
909              
910             =cut
911              
912             ###############################################
913             sub blurt {
914             ###############################################
915 7     7 1 1085 my($data, $file, $options) = @_;
916              
917             # legacy signature
918 7 50 66     47 if(defined $options and ref $options eq "") {
919 0         0 $options = { append => 1 };
920             }
921              
922 7 100       26 $options = {} unless defined $options;
923              
924 7         20 local $Log::Log4perl::caller_depth =
925             $Log::Log4perl::caller_depth + 1;
926              
927 7 50       31 $options->{append} = 0 unless defined $options->{append};
928              
929 7 50       53 _confirm(($options->{append} ? "Appending" : "Writing") . " " .
    100          
930             length($data) . " bytes to $file") or return 1;
931              
932 6 50       524 open FILE, ">" . ($options->{append} ? ">" : "") . $file
    50          
933             or
934             LOGCROAK("Cannot open $file for writing ($!)");
935              
936 6         21 binmode FILE; # Win32 wants that
937              
938 6 50       21 if( $options->{utf8} ) {
939 0         0 binmode FILE, ":utf8";
940             }
941              
942 6 50       71 print FILE $data
943             or
944             LOGCROAK("Cannot write to $file ($!)");
945 6 50       288 close FILE
946             or
947             LOGCROAK("Cannot close $file ($!)");
948              
949 6         30 DEBUG "Wrote ", snip($data, $DATA_SNIPPED_LEN), " to $file";
950             }
951              
952             =pod
953              
954             =item C
955              
956             Write the data in $data to a file $file, guaranteeing that the operation
957             will either complete fully or not at all. This is accomplished by first
958             writing to a temporary file which is then rename()ed to the target file.
959              
960             Unlike in C, there is no C<$append> mode in C.
961              
962             If the string is a Unicode string, use the C option:
963              
964             blurt_atomic( $unicode_string, $file, {utf8 => 1} );
965              
966             =cut
967              
968             ###############################################
969             sub blurt_atomic {
970             ###############################################
971 1     1 1 830 my($data, $file, $options) = @_;
972              
973 1 50       6 _confirm("Writing atomically " .
974             length($data) . " bytes to $file") or return 1;
975              
976 1 50       4 $options = {} unless defined $options;
977              
978 1         59 my($fh, $tmpname) = tempfile(DIR => dirname($file));
979              
980 1         429 blurt($data, $tmpname, $options);
981              
982 1         13 close $fh;
983              
984 1 50       125 rename $tmpname, $file or
985             LOGDIE "Can't rename $tmpname to $file";
986              
987 1         5 DEBUG "Wrote ", snip($data, $DATA_SNIPPED_LEN), " atomically to $file";
988             }
989              
990             =pod
991              
992             =item C<($stdout, $stderr, $exit_code) = tap($cmd, @args)>
993              
994             Run a command $cmd in the shell, and pass it @args as args.
995             Capture STDOUT and STDERR, and return them as strings. If
996             C<$exit_code> is 0, the command succeeded. If it is different,
997             the command failed and $exit_code holds its exit code.
998              
999             Please note that C is limited to single shell
1000             commands, it won't work with output redirectors (C/tmp/foo>
1001             2E&1).
1002              
1003             In default mode, C will concatenate the command and args
1004             given and create a shell command line by redirecting STDERR to a temporary
1005             file. C, for example, will result in
1006              
1007             'ls' '/tmp' 2>/tmp/sometempfile |
1008              
1009             Note that all commands are protected by single quotes to make sure
1010             arguments containing spaces are processed as singles, and no globbing
1011             happens on wildcards. Arguments containing single quotes or backslashes
1012             are escaped properly.
1013              
1014             If quoting is undesirable, C accepts an option hash as
1015             its first parameter,
1016              
1017             tap({no_quotes => 1}, "ls", "/tmp/*");
1018              
1019             which will suppress any quoting:
1020              
1021             ls /tmp/* 2>/tmp/sometempfile |
1022              
1023             Or, if you prefer double quotes, use
1024              
1025             tap({double_quotes => 1}, "ls", "/tmp/$VAR");
1026              
1027             wrapping all args so that shell variables are interpolated properly:
1028              
1029             "ls" "/tmp/$VAR" 2>/tmp/sometempfile |
1030              
1031             Another option is "utf8" which runs the command in a terminal set to
1032             UTF8.
1033              
1034             Error handling: By default, tap() won't raise an error if the command's
1035             return code is nonzero, indicating an error reported by the shell. If
1036             bailing out on errors is requested to avoid return code checking by
1037             the script, use the raise_error option:
1038              
1039             tap({raise_error => 1}, "ls", "doesn't exist");
1040              
1041             In DEBUG mode, C logs the entire stdout/stderr output, which
1042             can get too verbose at times. To limit the number of bytes logged, use
1043             the C and C options
1044              
1045             tap({stdout_limit => 10}, "echo", "123456789101112");
1046              
1047             =cut
1048              
1049             ###############################################
1050             sub tap {
1051             ###############################################
1052 5     5 1 5724 my(@args) = @_;
1053              
1054 5         18 my $options = {};
1055              
1056 5 50 33     64 if(defined $args[-1] and
1057             ref $args[-1] eq "HASH") {
1058 0         0 $options = pop @args;
1059             }
1060              
1061 5         17 local $Log::Log4perl::caller_depth =
1062             $Log::Log4perl::caller_depth + 1;
1063              
1064 5 50       57 _confirm "tapping @args" or return 1;
1065              
1066 5         14 my $opts = {};
1067              
1068 5 100       27 $opts = shift @args if ref $args[0] eq "HASH";
1069              
1070 5         58 my $tmpfh = File::Temp->new(UNLINK => 1, SUFFIX => '.dat');
1071 5         3294 my $tmpfile = $tmpfh->filename();
1072              
1073 5         63 DEBUG "tempfile $tmpfile created";
1074              
1075 5         52 my $cmd;
1076              
1077 5 50       34 if($opts->{no_quotes}) {
    50          
1078 0         0 $cmd = join ' ', @args;
1079             } elsif($opts->{double_quotes}) {
1080 0         0 $cmd = join ' ', map { qquote($_, ":shell") } @args;
  0         0  
1081             } else {
1082             # Default mode: Single quotes
1083 5         40 $cmd = join ' ', map { quote($_, ":shell") } @args;
  9         33  
1084             }
1085            
1086 5         18 $cmd = "$cmd 2>$tmpfile |";
1087 5         30 INFO "tapping $cmd";
1088              
1089 5 50       23679 open PIPE, $cmd or
1090             LOGCROAK("open $cmd | failed ($!)");
1091            
1092 5 50       34 if( $options->{utf8} ) {
1093 0         0 binmode PIPE, ":utf8";
1094             }
1095              
1096 5         13237 my $stdout = join '', ;
1097 5         162 close PIPE;
1098              
1099 5         43 my $exit_code = $?;
1100              
1101 5         75 my $stderr = slurp($tmpfile, $options);
1102              
1103 5 50       27 if( $opts->{ stderr_limit } ) {
1104 0         0 $stderr = snip( $stderr, $opts->{ stderr_limit } );
1105             }
1106              
1107 5 50 66     45 if($exit_code != 0 and $opts->{raise_error}) {
1108 1         18 LOGCROAK("tap $cmd | failed ($stderr)");
1109             }
1110              
1111 4 100       16 if( $opts->{ stdout_limit } ) {
1112 1         13 $stdout = snip( $stdout, $opts->{ stdout_limit } );
1113             }
1114              
1115 4         34 DEBUG "tap $cmd results: rc=$exit_code stderr=[$stderr] stdout=[$stdout]";
1116              
1117 4         99 return ($stdout, $stderr, $exit_code);
1118             }
1119              
1120             =pod
1121              
1122             =item C<$quoted_string = qquote($string, [$metachars])>
1123              
1124             Put a string in double quotes and escape all sensitive characters so
1125             there's no unwanted interpolation.
1126             E.g., if you have something like
1127              
1128             print "foo!\n";
1129              
1130             and want to put it into a double-quoted string, it will look like
1131              
1132             "print \"foo!\\n\""
1133              
1134             Sometimes, not only backslashes and double quotes need to be escaped,
1135             but also the target environment's meta chars. A string containing
1136              
1137             print "$<\n";
1138              
1139             needs to have the '$' escaped like
1140              
1141             "print \"\$<\\n\";"
1142              
1143             if you want to reuse it later in a shell context:
1144              
1145             $ perl -le "print \"\$<\\n\";"
1146             1212
1147              
1148             C supports escaping these extra characters with its second,
1149             optional argument, consisting of a string listing all escapable characters:
1150              
1151             my $script = 'print "$< rocks!\\n";';
1152             my $escaped = qquote($script, '!$'); # Escape for shell use
1153             system("perl -e $escaped");
1154              
1155             => 1212 rocks!
1156              
1157             And there's a shortcut for shells: By specifying ':shell' as the
1158             metacharacters string, qquote() will actually use '!$`'.
1159              
1160             For example, if you wanted to run the perl code
1161              
1162             print "foobar\n";
1163              
1164             via
1165              
1166             perl -e ...
1167              
1168             on a box via ssh, you would use
1169              
1170             use Sysadm::Install qw(qquote);
1171              
1172             my $cmd = 'print "foobar!\n"';
1173             $cmd = "perl -e " . qquote($cmd, ':shell');
1174             $cmd = "ssh somehost " . qquote($cmd, ':shell');
1175              
1176             print "$cmd\n";
1177             system($cmd);
1178              
1179             and get
1180              
1181             ssh somehost "perl -e \"print \\\"foobar\\\!\\\\n\\\"\""
1182              
1183             which runs on C without hickup and prints C.
1184              
1185             Sysadm::Install comes with a script C (installed in bin),
1186             which takes arbitrary perl code on STDIN and transforms it into
1187             a one-liner:
1188              
1189             $ one-liner
1190             Type perl code, terminate by CTRL-D
1191             print "hello\n";
1192             print "world\n";
1193             ^D
1194             perl -e "print \"hello\\n\"; print \"world\\n\"; "
1195              
1196             =cut
1197              
1198             ###############################################
1199             sub qquote {
1200             ###############################################
1201 3     3 1 13085 my($str, $metas) = @_;
1202              
1203 3         81 $str =~ s/([\\"])/\\$1/g;
1204              
1205 3 50       20 if(defined $metas) {
1206 3 100       14 $metas = '!$`' if $metas eq ":shell";
1207 3         13 $metas =~ s/\]/\\]/g;
1208 3         247 $str =~ s/([$metas])/\\$1/g;
1209             }
1210              
1211 3         23 return "\"$str\"";
1212             }
1213              
1214             =pod
1215              
1216             =item C<$quoted_string = quote($string, [$metachars])>
1217              
1218             Similar to C, just puts a string in single quotes and
1219             escapes what needs to be escaped.
1220              
1221             Note that shells typically don't support escaped single quotes within
1222             single quotes, which means that
1223              
1224             $ echo 'foo\'bar'
1225             >
1226              
1227             is invalid and the shell waits until it finds a closing quote.
1228             Instead, there is an evil trick which gives the desired result:
1229              
1230             $ echo 'foo'\''bar' # foo, single quote, \, 2 x single quote, bar
1231             foo'bar
1232              
1233             It uses the fact that shells interpret back-to-back strings as one.
1234             The construct above consists of three back-to-back strings:
1235              
1236             (1) 'foo'
1237             (2) '
1238             (3) 'bar'
1239              
1240             which all get concatenated to a single
1241              
1242             foo'bar
1243              
1244             If you call C with C<$metachars> set to ":shell", it will
1245             perform that magic behind the scenes:
1246              
1247             print quote("foo'bar");
1248             # prints: 'foo'\''bar'
1249              
1250             =cut
1251              
1252             ###############################################
1253             sub quote {
1254             ###############################################
1255 11     11 1 2069 my($str, $metas) = @_;
1256              
1257 11 100 66     92 if(defined $metas and $metas eq ":shell") {
1258 10         35 $str =~ s/([\\])/\\$1/g;
1259 10         28 $str =~ s/(['])/'\\''/g;
1260             } else {
1261 1         27 $str =~ s/([\\'])/\\$1/g;
1262             }
1263              
1264 11 50 66     87 if(defined $metas and $metas ne ":shell") {
1265 0         0 $metas =~ s/\]/\\]/g;
1266 0         0 $str =~ s/([$metas])/\\$1/g;
1267             }
1268              
1269 11         81 return "\'$str\'";
1270             }
1271              
1272             =pod
1273              
1274             =item C
1275              
1276             Read the C<$src> file's user permissions and modify all
1277             C<$dst> files to reflect the same permissions.
1278              
1279             =cut
1280              
1281             ######################################
1282             sub perm_cp {
1283             ######################################
1284             # Lifted from Ben Okopnik's
1285             # http://www.linuxgazette.com/issue87/misc/tips/cpmod.pl.txt
1286              
1287 1     1 1 381 local $Log::Log4perl::caller_depth =
1288             $Log::Log4perl::caller_depth + 1;
1289              
1290 1 50       9 _confirm "perm_cp @_" or return 1;
1291              
1292 1 50       5 LOGCROAK("usage: perm_cp src dst ...") if @_ < 2;
1293              
1294 1         7 my $perms = perm_get($_[0]);
1295 1         5 perm_set($_[1], $perms);
1296             }
1297              
1298             =pod
1299              
1300             =item C
1301              
1302             Read the C<$src> file/directory's owner uid and group gid and apply
1303             it to $dst.
1304              
1305             For example: copy uid/gid of the containing directory to a file
1306             therein:
1307              
1308             use File::Basename;
1309              
1310             owner_cp( dirname($file), $file );
1311              
1312             Usually requires root privileges, just like chown does.
1313              
1314             =cut
1315              
1316             ######################################
1317             sub owner_cp {
1318             ######################################
1319 0     0 1 0 my($src, @dst) = @_;
1320              
1321 0         0 local $Log::Log4perl::caller_depth =
1322             $Log::Log4perl::caller_depth + 1;
1323              
1324 0 0       0 _confirm "owner_cp @_" or return 1;
1325              
1326 0 0       0 LOGCROAK("usage: owner_cp src dst ...") if @_ < 2;
1327              
1328 0         0 my($uid, $gid) = (stat($src))[4,5];
1329              
1330 0 0 0     0 if(!defined $uid or !defined $gid ) {
1331 0         0 LOGCROAK("stat of $src failed: $!");
1332 0         0 return undef;
1333             }
1334              
1335 0 0       0 if(!chown $uid, $gid, @dst ) {
1336 0         0 LOGCROAK("chown of ", join(" ", @dst), " failed: $!");
1337 0         0 return undef;
1338             }
1339              
1340 0         0 return 1;
1341             }
1342              
1343             =pod
1344              
1345             =item C<$perms = perm_get($filename)>
1346              
1347             Read the C<$filename>'s user permissions and owner/group.
1348             Returns an array ref to be
1349             used later when calling C.
1350              
1351             =cut
1352              
1353             ######################################
1354             sub perm_get {
1355             ######################################
1356 1     1 1 2 my($filename) = @_;
1357              
1358 1         4 local $Log::Log4perl::caller_depth =
1359             $Log::Log4perl::caller_depth + 1;
1360              
1361 1 50       25 my @stats = (stat $filename)[2,4,5] or
1362            
1363             LOGCROAK("Cannot stat $filename ($!)");
1364              
1365 1         12 INFO "perm_get $filename (@stats)";
1366              
1367 1         12 return \@stats;
1368             }
1369              
1370             =pod
1371              
1372             =item C
1373              
1374             Set file permissions and owner of C<$filename>
1375             according to C<$perms>, which was previously
1376             acquired by calling C.
1377              
1378             =cut
1379              
1380             ######################################
1381             sub perm_set {
1382             ######################################
1383 1     1 1 3 my($filename, $perms) = @_;
1384              
1385 1         3 local $Log::Log4perl::caller_depth =
1386             $Log::Log4perl::caller_depth + 1;
1387              
1388 1 50       9 _confirm "perm_set $filename (@$perms)" or return 1;
1389              
1390 1 50       34 chown($perms->[1], $perms->[2], $filename) or
1391            
1392             LOGCROAK("Cannot chown $filename ($!)");
1393 1 50       27 chmod($perms->[0] & 07777, $filename) or
1394            
1395             LOGCROAK("Cannot chmod $filename ($!)");
1396             }
1397              
1398             =pod
1399              
1400             =item C
1401              
1402             Run a shell command via C and die() if it fails. Also
1403             works with a list of arguments, which are then interpreted as program
1404             name plus arguments, just like C does it.
1405              
1406             =cut
1407              
1408             ######################################
1409             sub sysrun {
1410             ######################################
1411 0     0 1 0 my(@cmds) = @_;
1412              
1413 0         0 local $Log::Log4perl::caller_depth =
1414             $Log::Log4perl::caller_depth + 1;
1415              
1416 0 0       0 _confirm "sysrun: @cmds" or return 1;
1417              
1418 0 0       0 LOGCROAK("usage: sysrun cmd ...") if @_ < 1;
1419              
1420 0 0       0 system(@cmds) and
1421             LOGCROAK("@cmds failed ($!)");
1422             }
1423              
1424             =pod
1425              
1426             =item C
1427              
1428             Run a command in the shell and simulate a user hammering the
1429             ENTER key to accept defaults on prompts.
1430              
1431             =cut
1432              
1433             ######################################
1434             sub hammer {
1435             ######################################
1436 0     0 1 0 my(@cmds) = @_;
1437              
1438 0         0 require Expect;
1439              
1440 0         0 local $Log::Log4perl::caller_depth =
1441             $Log::Log4perl::caller_depth + 1;
1442              
1443 0 0       0 _confirm "Hammer: @cmds" or return 1;
1444              
1445 0         0 my $exp = Expect->new();
1446 0         0 $exp->raw_pty(0);
1447              
1448 0         0 INFO "spawning: @cmds";
1449 0         0 $exp->spawn(@cmds);
1450              
1451 0         0 $exp->send_slow(0.1, "\n") for 1..199;
1452 0         0 $exp->expect(undef);
1453             }
1454              
1455             =pod
1456              
1457             =item C
1458              
1459             Alias for C, just like Perl6 is going to provide it.
1460              
1461             =cut
1462              
1463             ######################################
1464             sub say {
1465             ######################################
1466 0     0 1 0 print @_, "\n";
1467             }
1468              
1469             =pod
1470              
1471             =item C
1472              
1473             Check if the current script is running as root. If yes, continue. If not,
1474             restart the current script with all command line arguments is restarted
1475             under sudo:
1476              
1477             sudo scriptname args ...
1478              
1479             Make sure to call this before any C<@ARGV>-modifying functions like
1480             C have kicked in.
1481              
1482             =cut
1483              
1484             ######################################
1485             sub sudo_me {
1486             ######################################
1487 0     0 1 0 my($argv) = @_;
1488              
1489 0         0 local $Log::Log4perl::caller_depth =
1490             $Log::Log4perl::caller_depth + 1;
1491              
1492 0 0       0 _confirm "sudo_me" or return 1;
1493              
1494 0 0       0 $argv = \@ARGV unless $argv;
1495              
1496             # If we're not running as root,
1497             # re-invoke the script via sudo
1498 0 0       0 if($> != 0) {
1499 0         0 DEBUG "Not running as root, calling sudo $0 @$argv";
1500 0         0 my $sudo = bin_find("sudo");
1501 0 0       0 LOGCROAK("Can't find sudo in PATH") unless $sudo;
1502 0 0       0 exec($sudo, $0, @$argv) or
1503             LOGCROAK("exec failed!");
1504             }
1505             }
1506              
1507             =pod
1508              
1509             =item C
1510              
1511             Search all directories in $PATH (the ENV variable) for an executable
1512             named $program and return the full path of the first hit. Returns
1513             C if the program can't be found.
1514              
1515             =cut
1516              
1517             ######################################
1518             sub bin_find {
1519             ######################################
1520 5     5 1 1344 my($exe) = @_;
1521              
1522             # File::Which returns a list in list context, we just want the first
1523             # match.
1524 5         28 return scalar File::Which::which( $exe );
1525             }
1526              
1527             =pod
1528              
1529             =item C
1530              
1531             Opens a file handle to read the output of the following process:
1532              
1533             cd $dir; find ./ -xdev -print0 | cpio -o0 |
1534              
1535             This can be used to capture a file system structure.
1536              
1537             =cut
1538              
1539             ######################################
1540             sub fs_read_open {
1541             ######################################
1542 1     1 1 277 my($dir, $options) = @_;
1543              
1544 1 50       6 $options = {} unless defined $options;
1545              
1546 1         2 local $Log::Log4perl::caller_depth =
1547             $Log::Log4perl::caller_depth + 1;
1548              
1549 1         9 my $find = bin_find("find");
1550 1 50       118 LOGCROAK("Cannot find 'find'") unless defined $find;
1551              
1552 1         2 my $cpio = bin_find("cpio");
1553 1 50       96 LOGCROAK("Cannot find 'cpio'") unless defined $cpio;
1554              
1555 1         5 cd $dir;
1556            
1557 1         3 my $cmd = "$find . -xdev -print0 | $cpio -o0 --quiet 2>/dev/null ";
1558              
1559 1         3 DEBUG "Reading from $cmd";
1560 1 50       2904 open my $in, "$cmd |" or
1561             LOGCROAK("Cannot open $cmd");
1562              
1563 1 50       9 binmode $in, ":utf8" if $options->{utf8};
1564              
1565 1         17 cdback;
1566              
1567 1         9 return $in;
1568             }
1569              
1570             =pod
1571              
1572             =item C
1573              
1574             Opens a file handle to write to a
1575              
1576             | (cd $dir; cpio -i0)
1577              
1578             process to restore a file system structure. To be used in conjunction
1579             with I.
1580              
1581             =cut
1582              
1583             ######################################
1584             sub fs_write_open {
1585             ######################################
1586 1     1 1 9212 my($dir, $options) = @_;
1587              
1588 1 50       6 $options = {} unless defined $options;
1589              
1590 1         4 local $Log::Log4perl::caller_depth =
1591             $Log::Log4perl::caller_depth + 1;
1592              
1593 1         6 my $cpio = bin_find("cpio");
1594 1 50       173 LOGCROAK("Cannot find 'cpio'") unless defined $cpio;
1595              
1596 1 50       13 mkd $dir unless -d $dir;
1597              
1598 1         8 cd $dir;
1599              
1600 1         3 my $cmd = "$cpio -i0 --quiet";
1601              
1602 1         20 DEBUG "Writing to $cmd in dir $dir";
1603 1 50       2383 open my $out, "| $cmd" or
1604             LOGCROAK("Cannot open $cmd");
1605              
1606 1 50       13 binmode $out, ":utf8" if $options->{utf8};
1607              
1608 1         18 cdback;
1609              
1610 1         82 return $out;
1611             }
1612              
1613             =pod
1614              
1615             =item C
1616              
1617             Reads from $in and writes to $out, using sysread and syswrite. The
1618             buffer size used defaults to 4096, but can be set explicitly.
1619              
1620             =cut
1621              
1622             ######################################
1623             sub pipe_copy {
1624             ######################################
1625 0     0 1 0 my($in, $out, $bufsize) = @_;
1626              
1627 0         0 local $Log::Log4perl::caller_depth =
1628             $Log::Log4perl::caller_depth + 1;
1629              
1630 0   0     0 $bufsize ||= 4096;
1631 0         0 my $bytes = 0;
1632              
1633 0         0 INFO "Opening pipe (bufsize=$bufsize)";
1634 0         0 my $ret;
1635 0         0 while($ret = sysread($in, my $buf, $bufsize)) {
1636 0         0 $bytes += length $buf;
1637 0 0       0 if (!defined syswrite $out, $buf) {
1638 0         0 LOGCROAK("Write to pipe failed: ($!)");
1639             }
1640             }
1641 0 0       0 if (!defined $ret) {
1642 0         0 LOGCROAK("Read from pipe failed: ($!)");
1643             }
1644 0         0 INFO "Closed pipe (bufsize=$bufsize, transferred=$bytes)";
1645             }
1646              
1647             =pod
1648              
1649             =item C
1650              
1651             Format the data string in C<$data> so that it's only (roughly) $maxlen
1652             characters long and only contains printable characters.
1653              
1654             If C<$data> is longer than C<$maxlen>, it will be
1655             formatted like
1656              
1657             (22)[abcdef[snip=11]stuvw]
1658              
1659             indicating the length of the original string, the beginning, the
1660             end, and the number of 'snipped' characters.
1661              
1662             If C<$data> is shorter than $maxlen, it will be returned unmodified
1663             (except for unprintable characters replaced, see below).
1664              
1665             If C<$data> contains unprintable character's they are replaced by
1666             "." (the dot).
1667              
1668             =cut
1669              
1670             ###########################################
1671             sub snip {
1672             ###########################################
1673 24     24 1 63 my($data, $maxlen) = @_;
1674              
1675 24 100       178 if(length $data <= $maxlen) {
1676 20         73 return printable($data);
1677             }
1678              
1679 4 100       20 $maxlen = 12 if $maxlen < 12;
1680 4         18 my $sniplen = int(($maxlen - 8) / 2);
1681              
1682 4         11 my $start = substr($data, 0, $sniplen);
1683 4         9 my $end = substr($data, -$sniplen);
1684 4         11 my $snipped = length($data) - 2*$sniplen;
1685              
1686 4         25 return lenformat("$start\[snip=$snipped]$end", length $data);
1687             }
1688            
1689             ###########################################
1690             sub lenformat {
1691             ###########################################
1692 4     4 0 10 my($data, $orglen) = @_;
1693              
1694 4   33     22 return "(" . ($orglen || length($data)) . ")[" .
1695             printable($data) . "]";
1696             }
1697              
1698             ###########################################
1699             sub printable {
1700             ###########################################
1701 25     25 0 787 my($data) = @_;
1702              
1703 25         888 $data =~ s/[^ \w.;!?@#$%^&*()+\\|~`',><[\]{}="-]/./g;
1704 25         216 return $data;
1705             }
1706              
1707             =pod
1708              
1709             =item C
1710              
1711             Reads in a password to be typed in by the user in noecho mode.
1712             A call to password_read("password: ") results in
1713              
1714             password: ***** (stars aren't actually displayed)
1715              
1716             This function will switch the terminal back into normal mode
1717             after the user hits the 'Return' key.
1718              
1719             If the optional C<$opts> hash has C<{ tty =E 1 }> set, then
1720             the prompt will be redirected to the console instead of STDOUT.
1721              
1722             =cut
1723              
1724             ###########################################
1725             sub password_read {
1726             ###########################################
1727 0     0 1 0 my($prompt, $opts) = @_;
1728              
1729 18     18   14803 use Term::ReadKey;
  18         47468  
  18         10915  
1730 0         0 ReadMode 'noecho';
1731 0         0 $| = 1;
1732 0         0 user_prompt($prompt, $opts);
1733 0         0 my $pw = ReadLine 0;
1734 0         0 chomp $pw;
1735 0         0 ReadMode 'restore';
1736 0         0 user_prompt("\n", $opts);
1737              
1738 0         0 return $pw;
1739             }
1740              
1741             =pod
1742              
1743             =item C
1744              
1745             Format the time in a human-readable way, less wasteful than the
1746             'scalar localtime' formatting.
1747              
1748             print nice_time(), "\n";
1749             # 2007/04/01 10:51:24
1750              
1751             It uses the system time by default, but it can also accept epoch seconds:
1752              
1753             print nice_time(1170000000), "\n";
1754             # 2007/01/28 08:00:00
1755              
1756             It uses localtime() under the hood, so the outcome of the above will
1757             depend on your local time zone setting.
1758              
1759             =cut
1760              
1761             ###########################################
1762             sub nice_time {
1763             ###########################################
1764 0     0 1 0 my($time) = @_;
1765              
1766 0 0       0 $time = time() unless defined $time;
1767              
1768 0         0 my ($sec,$min,$hour,$mday,$mon,$year,
1769             $wday,$yday,$isdst) = localtime($time);
1770              
1771 0         0 return sprintf("%d/%02d/%02d %02d:%02d:%02d",
1772             $year+1900, $mon+1, $mday,
1773             $hour, $min, $sec);
1774             }
1775              
1776             =item C
1777              
1778             Perl-5.9 added the //= construct, which helps assigning values to
1779             undefined variables. Instead of writing
1780              
1781             if(!defined $foo) {
1782             $foo = $default;
1783             }
1784              
1785             you can just write
1786              
1787             $foo //= $default;
1788              
1789             However, this is not available on older perl versions (although there's
1790             source filter solutions). Often, people use
1791              
1792             $foo ||= $default;
1793              
1794             instead which is wrong if $foo contains a value that evaluates as false.
1795             So Sysadm::Install, the everything-and-the-kitchen-sink under the CPAN
1796             modules, provides the function C which can be used like
1797              
1798             def_or($foo, $default);
1799              
1800             to accomplish the same as
1801              
1802             $foo //= $default;
1803              
1804             How does it work, how does $foo get a different value, although it's
1805             apparently passed in by value? Modifying $_[0] within the subroutine
1806             is an old Perl trick to do exactly that.
1807              
1808             =cut
1809              
1810             ###########################################
1811             sub def_or($$) {
1812             ###########################################
1813 3 100   3 1 2638 if(! defined $_[0]) {
1814 1         4 $_[0] = $_[1];
1815             }
1816             }
1817              
1818             =item C
1819              
1820             Check if the given string has the utf8 flag turned on. Works just like
1821             Encode.pm's is_utf8() function, except that it silently returns a
1822             false if Encode isn't available, for example when an ancient perl
1823             without proper utf8 support is used.
1824              
1825             =cut
1826              
1827             ###############################################
1828             sub is_utf8_data {
1829             ###############################################
1830 0     0 1 0 my($data) = @_;
1831              
1832 0 0       0 if( !utf8_available() ) {
1833 0         0 return 0;
1834             }
1835              
1836 0         0 return Encode::is_utf8( $data );
1837             }
1838              
1839             =item C
1840              
1841             Check if we're using a perl with proper utf8 support, by verifying the
1842             Encode.pm module is available for loading.
1843              
1844             =cut
1845              
1846             ###############################################
1847             sub utf8_available {
1848             ###############################################
1849              
1850 0     0 0 0 eval "use Encode";
1851              
1852 0 0       0 if($@) {
1853 0         0 return 0;
1854             }
1855              
1856 0         0 return 1;
1857             }
1858              
1859             =item C
1860              
1861             Return the path to the home directory of the current user.
1862              
1863             =cut
1864              
1865             ###############################################
1866             sub home_dir {
1867             ###############################################
1868              
1869 1     1 1 561 my( $home ) = glob "~";
1870              
1871 1         4 return $home;
1872             }
1873              
1874             =pod
1875              
1876             =back
1877              
1878             =head1 AUTHOR
1879              
1880             Mike Schilli, Em@perlmeister.comE
1881              
1882             =head1 COPYRIGHT AND LICENSE
1883              
1884             Copyright (C) 2004-2007 by Mike Schilli
1885              
1886             This library is free software; you can redistribute it and/or modify
1887             it under the same terms as Perl itself, either Perl version 5.8.3 or,
1888             at your option, any later version of Perl 5 you may have available.
1889              
1890             =cut
1891              
1892             1;