File Coverage

blib/lib/Sysadm/Install.pm
Criterion Covered Total %
statement 328 489 67.0
branch 117 272 43.0
condition 16 53 30.1
subroutine 46 63 73.0
pod 38 47 80.8
total 545 924 58.9


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