File Coverage

blib/lib/Sysadm/Install.pm
Criterion Covered Total %
statement 40 456 8.7
branch 0 256 0.0
condition 0 53 0.0
subroutine 14 56 25.0
pod n/a
total 54 821 6.5


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