File Coverage

lib/Slaughter/API/generic.pm
Criterion Covered Total %
statement 188 311 60.4
branch 59 170 34.7
condition 34 74 45.9
subroutine 23 32 71.8
pod 21 21 100.0
total 325 608 53.4


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2              
3             =head1 NAME
4            
5             Slaughter::API::generic - Perl Automation Tool Helper generic implementation
6            
7             =cut
8              
9             =head1 SYNOPSIS
10            
11             This module implements most of our primitives in a portable fashion, allowing
12             other modules in the C<Slaughter::API::> namespace to implement the rest.
13            
14             When this module is loaded it promotes each of the subroutines in the package
15             into the C<main::> namespace, to allow calling code to use the functions directly
16             without needing an OO-interface.
17            
18             =cut
19              
20             =head1 METHODS
21            
22             Now follows documentation on the available methods.
23            
24             =cut
25              
26              
27 8     8   27 use strict;
  8         8  
  8         191  
28 8     8   24 use warnings;
  8         8  
  8         230  
29              
30              
31             package Slaughter::API::generic;
32              
33              
34             #
35             # Standard libraries we require.
36             #
37 8     8   30 use File::Basename qw! basename dirname !;
  8         9  
  8         530  
38 8     8   28 use File::Find;
  8         5  
  8         338  
39 8     8   26 use File::Path qw/ mkpath /;
  8         9  
  8         274  
40 8     8   2941 use File::Temp qw/ tempfile /;
  8         77581  
  8         444  
41 8     8   4276 use Text::Template;
  8         18932  
  8         318  
42              
43              
44             #
45             # The modules we use, and the internal functions are defined
46             # in this module.
47             #
48 8     8   1946 use Slaughter::Private;
  8         13  
  8         296  
49              
50             #
51             # The version of our release.
52             #
53             our $VERSION = "3.0.5";
54              
55              
56             =head2 import
57            
58             Export all subs in this package into the main namespace.
59            
60             =cut
61              
62             sub import
63             {
64             ## no critic
65 8     8   30     no strict 'refs';
  8         7  
  8         18635  
66             ## use critic
67              
68 8     8   16     my $caller = caller;
69              
70 8         8     while ( my ( $name, $symbol ) = each %{ __PACKAGE__ . '::' } )
  256         635  
71                 {
72 248 100       313         next if $name eq 'BEGIN'; # don't export BEGIN blocks
73 240 100       272         next if $name eq 'import'; # don't export this sub
74 232 100       126         next unless *{ $symbol }{ CODE }; # export subs only
  232         350  
75              
76 216         233         my $imported = $caller . '::' . $name;
77 216         119         *{ $imported } = \*{ $symbol };
  216         501  
  216         177  
78                 }
79             }
80              
81              
82              
83             =head2 Alert
84            
85             The alert primitive is used to send an email. Sample usage is:
86            
87             =for example begin
88            
89             Alert( Message => "Server on fire: $hostname",
90             To => 'steve[at]steve.org.uk',
91             Subject => "Alert: $fqdn" );
92            
93             =for example end
94            
95             The following parameters are available:
96            
97             =over
98            
99             =item From [default: "root"]
100            
101             The sender address of the email.
102            
103             =item Message [mandatory]
104            
105             The content of the message to send
106            
107             =item Sendmail [default: "/usr/lib/sendmail -t"]
108            
109             The path to the sendmail binary.
110            
111             =item Subject [mandatory]
112            
113             The subject to send.
114            
115             =item To [mandatory]
116            
117             The recipient of the message.
118            
119             =back
120            
121             =cut
122              
123             sub Alert
124             {
125 0     0 1 0     my (%params) = (@_);
126              
127 0   0     0     my $message = $params{ 'Message' } || "No message";
128 0   0     0     my $subject = $params{ 'Subject' } || "No subject";
129 0   0     0     my $to = $params{ 'To' } || $params{ 'Email' } || "root";
130 0   0     0     my $from = $params{ 'From' } || "root";
131 0   0     0     my $sendmail = $params{ 'Sendmail' } || "/usr/lib/sendmail -t";
132              
133 0 0       0     open( my $handle, "|-", "$sendmail -f $from" ) or
134                   die "Failed to sendmail: $!";
135 0         0     print $handle <<EOF;
136             To: $to
137             From: $from
138             Subject: $subject
139            
140             $message
141             EOF
142 0         0     close($handle);
143              
144             }
145              
146              
147              
148              
149             =head2 AppendIfMissing
150            
151             This primitive will open a local file, and append a line to it if it is not
152             already present.
153            
154             =for example begin
155            
156             AppendIfMissing( File => "/etc/hosts.allow",
157             Line => "All: 1.2.3.4" );
158            
159             =for example end
160            
161             The following parameters are available:
162            
163             =over
164            
165             =item File [mandatory]
166            
167             The filename which should be examined and potentially updated.
168            
169             =item Line [mandatory]
170            
171             The line which should be searched for and potentially appended.
172            
173             =back
174            
175             =cut
176              
177             sub AppendIfMissing
178             {
179 6     6 1 1358     my (%params) = (@_);
180              
181 6         7     my $line = $params{ 'Line' };
182 6         4     my $file = $params{ 'File' };
183 6         5     my $found = 0;
184              
185 6 50       133     if ( open( my $handle, "<", $file ) )
186                 {
187              
188 6         53         foreach my $read (<$handle>)
189                     {
190 29         19             chomp($read);
191              
192 29 100       35             if ( $line eq $read )
193                         {
194 5         10                 $found = 1;
195                         }
196                     }
197 6         27         close($handle);
198                 }
199              
200              
201             #
202             # If it wasn't found append
203             #
204 6 100       11     if ( !$found )
205                 {
206 1 50       18         if ( open( my $handle, ">>", $file ) )
207                     {
208 1         3             print $handle $line . "\n";
209 1         15             close($handle);
210 1         5             return 1;
211                     }
212                     else
213                     {
214 0         0             return -1;
215                     }
216                 }
217 5         15     return 0;
218             }
219              
220              
221              
222              
223             =head2 CommentLinesMatching
224            
225             This primitive will open a local file, and comment out any line which matches
226             the specified regular expression.
227            
228             =for example begin
229            
230             if ( CommentLinesMatching( Pattern => "telnet|ftp",
231             File => "/etc/inetd.conf" ) )
232             {
233             RunCommand( Cmd => "/etc/init.d/inetd restart" );
234             }
235            
236             =for example end
237            
238             The following parameters are available:
239            
240             =over
241            
242             =item Comment [default: "#"]
243            
244             The value to comment out the line with.
245            
246             =item File [mandatory]
247            
248             The filename which should be examined and potentially updated.
249            
250             =item Pattern [mandatory]
251            
252             The regular expression to match with.
253            
254             =back
255            
256             The return value of this function is the number of lines updated,
257             or -1 if the file could not be opened.
258            
259             =cut
260              
261             sub CommentLinesMatching
262             {
263 1     1 1 2     my (%params) = (@_);
264              
265 1         2     my $pattern = $params{ 'Pattern' };
266 1   50     9     my $comment = $params{ 'Comment' } || "#";
267 1         1     my $file = $params{ 'File' };
268              
269 1 50       23     if ( open( my $handle, "<", $file ) )
270                 {
271 1         1         my @lines;
272 1         1         my $found = 0;
273              
274 1         9         foreach my $read (<$handle>)
275                     {
276 4         4             chomp($read);
277              
278 4 100       21             if ( $read =~ /$pattern/ )
279                         {
280 1         2                 $read = $comment . $read;
281 1         1                 $found += 1;
282                         }
283 4         4             push( @lines, $read );
284                     }
285 1         6         close($handle);
286              
287             #
288             # Now write out the modified file.
289             #
290 1 50       2         if ($found)
291                     {
292 1 50       48             if ( open( my $handle, ">", $file ) )
293                         {
294 1         2                 foreach my $line (@lines)
295                             {
296 4         7                     print $handle $line . "\n";
297                             }
298 1         17                 close($handle);
299              
300 1         5                 return $found;
301                         }
302                     }
303                     else
304                     {
305 0         0             return 0;
306                     }
307                 }
308                 else
309                 {
310 0         0         return -1;
311                 }
312             }
313              
314              
315              
316             =head2 DeleteFilesMatching
317            
318             This primitive will delete files with names matching a particular
319             pattern, recursively.
320            
321             =for example begin
322            
323             #
324             # Delete *.dpkg-old - recursively
325             #
326             DeleteFilesMatching( Root => "/etc",
327             Pattern => "\\.dpkg-old\$" );
328            
329             =for example end
330            
331             The following parameters are available:
332            
333             =over
334            
335             =item Root [mandatory]
336            
337             The root directory from which the search begins.
338            
339             =item Pattern [mandatory]
340            
341             The regular expression applied to filenames.
342            
343             The return value of this function is the number of files deleted.
344            
345             =back
346            
347             =cut
348              
349             sub DeleteFilesMatching
350             {
351 2     2 1 862     my (%params) = (@_);
352              
353 2   50     5     my $root = $params{ 'Root' } || return;
354 2   50     4     my $pattern = $params{ 'Pattern' } || return;
355 2         2     my $removed = 0;
356              
357 2 50       3     $::verbose && print "Removing files matching $pattern from $root\n";
358              
359             #
360             # Reference to our routine.
361             #
362                 my $wanted = sub {
363 9     9   10         my $file = $File::Find::name;
364 9 100       289         if ( basename($file) =~ /$pattern/ )
365                     {
366 3         185             unlink($file);
367              
368 3         4             $removed += 1;
369 3 50       30             $::verbose &&
370                           print "\tRemoving $file\n";
371                     }
372 2         10     };
373              
374             #
375             #
376             #
377 2         97     File::Find::find( { wanted => $wanted, no_chdir => 1 }, $root );
378              
379 2         10     return ($removed);
380             }
381              
382              
383              
384              
385             =head2 DeleteOldFiles
386            
387             This primitive will delete files older than the given number of
388             days from the specified directory.
389            
390             Note unlike L</DeleteFilesMatching> this function is not recursive.
391            
392             =for example begin
393            
394             #
395             # Delete files older than ten days from /tmp.
396             #
397             DeleteFilesMatching( Root => "/tmp",
398             Age => 10 );
399            
400             =for example end
401            
402             The following parameters are available:
403            
404             =over 8
405            
406             =item Age [mandatory]
407            
408             The age of files which should be deleted.
409            
410             =item Root [mandatory]
411            
412             The root directory from which the search begins.
413            
414             =back
415            
416             The return value of this function is the number of files deleted.
417            
418             =cut
419              
420             sub DeleteOldFiles
421             {
422 0     0 1 0     my (%params) = (@_);
423              
424 0   0     0     my $root = $params{ 'Root' } || return;
425 0   0     0     my $age = $params{ 'Age' } || return;
426 0         0     my $removed = 0;
427              
428 0 0       0     $::verbose && print "Removing files older than $age days from $root\n";
429              
430             #
431             # Find each file.
432             #
433 0         0     foreach my $file ( sort( glob( $root . "/*" ) ) )
434                 {
435              
436             # skip directories
437 0 0       0         next if ( -d $file );
438              
439 0         0         my $fage = -M $file;
440              
441 0 0       0         if ( $fage >= $age )
442                     {
443 0 0       0             $::verbose &&
444                           print "\tRemoving $file age $fage is >= $age\n";
445              
446 0         0             unlink($file);
447 0         0             $removed += 1;
448                     }
449                 }
450              
451 0 0       0     $::verbose && print "\tRemoved $removed files\n";
452              
453 0         0     return $removed;
454             }
455              
456              
457              
458             =head2 IdenticalContents
459            
460             The IdenticalContents primitive is used to compare whether two
461             filenames have identical contents.
462            
463             The following is an example of usage:
464            
465             =for example begin
466            
467             #
468             # If the current contents don't match then move into place.
469             #
470             if (
471             1 != IdenticalContents( File1 => $tmp,
472             File2 => $dest ) )
473             {
474             system( "cp", $tmp, $dest );
475             }
476             else
477             {
478             unlink( $tmp );
479             }
480            
481             =for example end
482            
483             The following parameters are available:
484            
485             =over
486            
487             =item File1 [mandatory]
488            
489             The first file to complare.
490            
491             =item File2 [mandatory]
492            
493             The second file to compare.
494            
495             =back
496            
497             The return value will depend on the matching:
498            
499             -1 Returned on error; either missing parameters, or non-existing files.
500            
501             0 The files are different.
502            
503             1 The files are identical.
504            
505             =cut
506              
507             sub IdenticalContents
508             {
509 3     3 1 2683     my (%params) = (@_);
510              
511             #
512             # The files we'll compare
513             #
514 3         7     my $a = $params{ 'File1' };
515 3         4     my $b = $params{ 'File2' };
516              
517 3 50 33     12     if ( !$a || !$b )
518                 {
519 0 0       0         $::verbose && print "\tMissing File1 or File2.\n";
520 0         0         return -1;
521                 }
522              
523             #
524             # Missing files are an error
525             #
526 3 50       39     return -1 unless ( -e $a );
527 3 100       29     return -1 unless ( -e $b );
528              
529             #
530             # Same size? If not then they can't have the same
531             # contents.
532             #
533 2         13     my $size_a = -s $a;
534 2         12     my $size_b = -s $b;
535 2 100       8     return 0 if ( $size_a != $size_b );
536              
537             #
538             # Same hash?
539             #
540 1         6     my $sum_a = Slaughter::Private::checksumFile($a);
541 1         2     my $sum_b = Slaughter::Private::checksumFile($b);
542 1 50       6     return 0 if ( $sum_a ne $sum_b );
543              
544             #
545             # OK they're "identical".
546             #
547 1         7     return 1;
548             }
549              
550              
551              
552             =head2 FetchFile
553            
554             The FetchFile primitive is used to copy a file from the remote server
555             to the local system. The file will have be moved into place if the
556             local file is missing OR if it exists but contains different contents
557             to the remote version.
558            
559             The following is an example of usage:
560            
561             =for example begin
562            
563             if ( FetchFile( Source => "/etc/motd",
564             Dest => "/etc/motd",
565             Owner => "root",
566             Group => "root",
567             Mode => "644" ) )
568             {
569             # File was created/updated.
570             }
571             else
572             {
573             # File already existed locally with the same contents.
574             }
575            
576             =for example end
577            
578             The following parameters are available:
579            
580             =over
581            
582             =item Dest [mandatory]
583            
584             The destination file to write to, on the local system.
585            
586             =item Expand [default: false]
587            
588             This is used to enable template-expansion, documented later.
589            
590             =item Group
591            
592             The unix group which should own the file.
593            
594             =item Mode
595            
596             The Unix mode to set for the file. B<NOTE> If this doesn't start with "0" it will
597             be passed through the perl "oct" function.
598            
599             =item Owner
600            
601             The Unix owner who should own the file.
602            
603             =item Source [default: value of Dest]
604            
605             The path to the remote file. This is relative to the /files/ prefix beneath
606             the transport root. If no value is specified the destination path is used.
607            
608             =back
609            
610             When a file fetch is attempted several variations are attempted, not just the
611             literal filename. The first file which exists and matches is returned, and the
612             fetch is aborted:
613            
614             =over 8
615            
616             =item /etc/motd.$fqdn
617            
618             =item /etc/motd.$hostname
619            
620             =item /etc/motd.$os
621            
622             =item /etc/motd.$arch
623            
624             =item /etc/motd
625            
626             =back
627            
628             Template template expansion involves the use of the L<Text::Template> module, of
629             "Expand => true". This will convert the following text:
630            
631             =for example begin
632            
633             # This is the config file for SSHD on {$fqdn}
634            
635             =for example end
636            
637             To the following, assuming the local host is called "precious.my.flat":
638            
639             =for example begin
640            
641             # This is the config file for SSHD on precious.my.flat
642            
643             =for example end
644            
645             The return value of this function is will depend upon the
646             action carried out:
647            
648             -1 - Returned on error; either missing parameters, or failure to perform the fetch.
649            
650             0 - The fetch resulted in no change.
651            
652             1 - The local file was replaced with the remote one.
653            
654             =cut
655              
656             sub FetchFile
657             {
658 0     0 1 0     my (%params) = (@_);
659              
660 0         0     my $dst = $params{ 'Dest' };
661 0   0     0     my $src = $params{ 'Source' } || $dst;
662              
663 0 0       0     if ( !$dst )
664                 {
665 0 0       0         $::verbose && print "\tMissing destination file.\n";
666 0         0         return -1;
667                 }
668              
669 0 0       0     $::verbose && print "FetchFile( $src, $dst );\n";
670              
671             #
672             # Fetch the source.
673             #
674 0         0     my $content = Slaughter::Private::fetchFromTransport($src);
675              
676 0 0       0     if ( !defined($content) )
677                 {
678 0 0       0         $::verbose && print "\tFailed to fetch.\n";
679 0         0         return 1;
680                 }
681              
682              
683             #
684             # If we're to expand content do so.
685             #
686 0 0 0     0     if ( ( defined $params{ 'Expand' } ) && ( $params{ 'Expand' } =~ /true/i ) )
687                 {
688 0 0       0         $::verbose && print "\tExpanding content with Text::Template\n";
689              
690 0         0         my $template =
691                       Text::Template->new( TYPE => 'string',
692                                            SOURCE => $content );
693              
694 0         0         $content = $template->fill_in( HASH => \%::template,
695                                                    PACKAGE => "main", );
696              
697 0 0       0         if ( !$content )
698                     {
699 0         0             print "Template expansion failed " . $Text::Template::ERROR . "\n";
700 0         0             return -1;
701                     }
702              
703                 }
704                 else
705                 {
706 0 0       0         $::verbose &&
707                       print "\tUsing contents literally; no template expansion\n";
708                 }
709              
710              
711             #
712             # OK now we want to write out the content to a temporary location.
713             #
714 0         0     my ( $handle, $name ) = File::Temp::tempfile();
715 0 0       0     open my $fh, ">", $name or
716                   return;
717 0         0     print $fh $content;
718 0         0     close($fh);
719              
720              
721             #
722             # We have the file, does it differ from the live filesystem?
723             # Or is the local copy missing?
724             #
725             # If so we'll move the new file into place.
726             #
727 0         0     my $replace = 0;
728              
729 0 0       0     if ( !-e $dst )
730                 {
731 0 0       0         $::verbose && print "\tDestination not already present.\n";
732 0         0         $replace = 1;
733                 }
734                 else
735                 {
736 0         0         my $cur = Slaughter::Private::checksumFile($dst);
737 0         0         my $new = Slaughter::Private::checksumFile($name);
738              
739 0 0       0         if ( $new ne $cur )
740                     {
741 0         0             $replace = 1;
742              
743 0 0       0             $::verbose && print "\tContents don't match - will replace\n";
744                     }
745                     else
746                     {
747 0 0       0             $::verbose &&
748                           print "\tCurrent file equals new one - not replacing\n";
749                     }
750                 }
751              
752             #
753             # Replace
754             #
755 0 0       0     if ($replace)
756                 {
757 0 0       0         if ( -e $dst )
758                     {
759              
760             #
761             # If we've been given "Backup" then we backup, otherwise
762             # we just remove the old file.
763             #
764 0   0     0             my $backup = $params{ 'Backup' } || "true";
765              
766 0 0       0             if ( $backup =~ /true/i )
767                         {
768 0 0       0                 $::verbose && print "\tMoving existing file out of the way.\n";
769 0         0                 RunCommand( Cmd => "mv $dst $dst.old" );
770                         }
771                         else
772                         {
773 0 0       0                 $::verbose &&
774                               print "\tOverwriting existing file without creating backup\n";
775                         }
776                     }
777              
778              
779             #
780             # Ensure the destination directory exists.
781             #
782 0         0         my $dir = dirname($dst);
783 0 0       0         if ( !-d $dir )
784                     {
785 0         0             mkpath( $dir, { verbose => 0 } );
786                     }
787              
788              
789 0 0       0         $::verbose && print "\tReplacing $dst\n";
790 0         0         RunCommand( Cmd => "mv $name $dst" );
791                 }
792              
793             #
794             # Change Owner/Group/Mode if we should
795             #
796 0         0     SetPermissions( File => $dst,
797                                 Owner => $params{ 'Owner' },
798                                 Group => $params{ 'Group' },
799                                 Mode => $params{ 'Mode' } );
800              
801             #
802             # If we didn't replace then we'll remove the temporary file
803             # which would otherwise be orphaned.
804             #
805 0 0       0     if ( -e $name )
806                 {
807 0         0         unlink($name);
808                 }
809              
810 0         0     return ($replace);
811             }
812              
813              
814              
815              
816             =head2 FileMatches
817            
818             This allows you to test whether the contents of a given file match
819             either a literal line of text, or a regular expression.
820            
821             =for example begin
822            
823             if ( FileMatches( File => "/etc/sudoers",
824             Pattern => "steve" ) )
825             {
826             # OK "steve" is in sudoers. Somewhere.
827             }
828            
829             =for example end
830            
831             The following parameters are available:
832            
833             =over
834            
835            
836             =item File [mandatory]
837            
838             The name of the file to test.
839            
840             =item Line [or Pattern mandatory]
841            
842             A line to look for within the file literally.
843            
844             =item Pattern [or Line mandatory]
845            
846             A regular expression to match against the file contents.
847            
848             =back
849            
850             The return value of this function will be the number of matches
851             found - regardless of whether a regular expression or literal
852             match is in use.
853            
854             =cut
855              
856             sub FileMatches
857             {
858 16     16 1 2529     my (%params) = (@_);
859              
860 16   50     31     my $file = $params{ 'File' } || return;
861 16   100     29     my $pattern = $params{ 'Pattern' } || undef;
862 16   100     42     my $line = $params{ 'Line' } || undef;
863 16         12     my $count = 0;
864              
865 16 50 66     34     if ( !defined($line) && !defined($pattern) )
866                 {
867 0         0         return -1;
868                 }
869              
870             #
871             # Open
872             #
873 16 50       353     if ( open( my $handle, "<", $file ) )
874                 {
875 16         151         foreach my $read (<$handle>)
876                     {
877 73         48             chomp($read);
878              
879 73 100 100     113             if ( defined($line) && ( $line eq $read ) )
880                         {
881 2         2                 $count += 1;
882                         }
883 73 100 100     338             if ( defined($pattern) && ( $read =~ /$pattern/ ) )
884                         {
885 7         8                 $count += 1;
886                         }
887                     }
888 16         81         close($handle);
889              
890 16         84         return ($count);
891                 }
892                 else
893                 {
894 0         0         return -1;
895                 }
896             }
897              
898              
899              
900              
901             =head2 FindBinary
902            
903             This method allows you to search for an executable upon your
904             system $PATH, or a supplied alternative string.
905            
906             =for example begin
907            
908             if ( FindBinary( Binary => "ls" ) )
909             {
910             # we have ls!
911             }
912            
913             =for example end
914            
915             The following parameters are available:
916            
917             =over
918            
919            
920             =item Binary [mandatory]
921            
922             The name of the binary file to find.
923            
924             =item Path [default: $ENV{'PATH'}]
925            
926             This is assumed to be a semi-colon deliminated list of directories to search
927             for the binary within.
928            
929             =back
930            
931             If the binary is found the full path will be returned, otherwise undef.
932            
933             =cut
934              
935             sub FindBinary
936             {
937 6     6 1 15     my (%params) = (@_);
938              
939 6   50     22     my $binary = $params{ 'Binary' } || $params{ 'binary' } || return;
940 6   50     18     my $path = $params{ 'Path' } ||
941                   $params{ 'path' } ||
942                   $ENV{ 'PATH' } ||
943                   "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin";
944 6         5     my $result = undef;
945              
946              
947 6         15     foreach my $dir ( split( /:/, $path ) )
948                 {
949 6 100 66     154         if ( ( -d $dir ) && ( -x ( $dir . "/" . $binary ) ) && ( !$result ) )
      66        
950                     {
951 3         8             $result = $dir . "/" . $binary;
952                     }
953                 }
954              
955 6         25     $result;
956             }
957              
958              
959             =head2 InstallPackage
960            
961             This method is a stub which does nothing but output a line of text to
962             inform the caller that the method is not implemented.
963            
964             For an implementation, and documentation, please consult C<Slaughter::API::linux>.
965            
966             =cut
967              
968             sub InstallPackage
969             {
970 0     0 1 0     print "InstallPackage - not implemented for $^O\n";
971             }
972              
973              
974              
975             =head2 LogMessage
976            
977             This primitive is used to store a log-worthy message. Whenever slaughter
978             finishes executing it will output a summary of all log-messages which were
979             encountered, sorted by priority.
980            
981             =for example begin
982            
983             LogMessage( Message => "Server on fire: $hostname",
984             Level => "normal" );
985            
986             =for example end
987            
988             The following parameters are available:
989            
990             =over
991            
992             =item Level [default: "normal"]
993            
994             The log-level of the message. You may choose whichever level you prefer.
995            
996             =item Message [mandatory]
997            
998             The content of the message to send
999            
1000             =back
1001            
1002             =cut
1003              
1004             sub LogMessage
1005             {
1006 4     4 1 2022     my (%params) = (@_);
1007              
1008             #
1009             # Get the log-level & message contents.
1010             #
1011 4   50     9     my $level = $params{ 'Level' } || "normal";
1012 4   50     6     my $msg = $params{ 'Message' } || "no message";
1013              
1014             #
1015             # Store in the global-hash. Post-execution these will be
1016             # dumped, via the slaughter wrapper-code.
1017             #
1018 4         3     push( @{ $::LOG{ $level } }, $msg );
  4         9  
1019             }
1020              
1021              
1022              
1023              
1024             =head2 Mounts
1025            
1026             Return a list of all the mounted filesystems upon the current system.
1027            
1028             =for example begin
1029            
1030             my @mounts = Mounts();
1031            
1032             =for example end
1033            
1034             No parameters are required or supported in this method, and the
1035             return value is an array of all mounted filesystems upon this
1036             host.
1037            
1038             B<NOTE>: This primitive invoke C<mount> and parses the output. This
1039             is reasonably portable, but will fail upon systems which have no "mount"
1040             binary. In that case the method will output a stub message to complain
1041             that the function is not implemented.
1042            
1043             =cut
1044              
1045             sub Mounts
1046             {
1047 0     0 1 0     my $path = FindBinary( Binary => "mount" );
1048              
1049 0 0       0     if ($path)
1050                 {
1051 0         0         my @results;
1052              
1053 0 0       0         open my $handle, "-|", $path or
1054                       die "Failed to run mount: $!";
1055              
1056 0         0         while ( my $line = <$handle> )
1057                     {
1058 0         0             chomp($line);
1059              
1060 0 0       0             if ( $line =~ /^([^ \t]+)[ \t]+on[ \t]+([^ \t]+)/ )
1061                         {
1062 0         0                 my ( $dev, $point ) = ( $1, $2 );
1063 0 0       0                 push( @results, $point ) if ( $dev =~ /dev/ );
1064                         }
1065                     }
1066 0         0         close($handle);
1067              
1068 0         0         return (@results);
1069              
1070                 }
1071                 else
1072                 {
1073 0         0         print "Mounts - not implemented for $^O\n";
1074                 }
1075             }
1076              
1077              
1078              
1079             =head2 PackageInstalled
1080            
1081            
1082             This method is a stub which does nothing but output a line of text to
1083             inform the caller that the method is not implemented.
1084            
1085             For an implementation, and documentation, please consult C<Slaughter::API::linux>.
1086            
1087             =cut
1088              
1089             sub PackageInstalled
1090             {
1091 0     0 1 0     print "PackageInstalled - not implemented for $^O\n";
1092             }
1093              
1094              
1095             =head2 PercentageUsed
1096            
1097             Return the percentage of space used in in the given mounted-device.
1098            
1099             =for example begin
1100            
1101             foreach my $point ( Mounts() )
1102             {
1103             if ( PercentageUsed( Path => $point ) > 80 )
1104             {
1105             Alert( To => "root",
1106             From => "root",
1107             Subject => "$server is running out of space on $point",
1108             Message => "This is a friendly warning." );
1109             }
1110             }
1111            
1112             =for example end
1113            
1114             The following parameters are supported:
1115            
1116             =over 8
1117            
1118             =item Path
1119            
1120             The mount-point to the filesystem in question.
1121            
1122             =back
1123            
1124             The return value will be a percentage in the range 0-100.
1125            
1126             B<NOTE>: This primitive invokes C<df> and parses the output. This
1127             is reasonably portable, but will fail upon systems which have no "df"
1128             binary. In that case the method will output a stub message to complain
1129             that the function is not implemented.
1130            
1131             =cut
1132              
1133             sub PercentageUsed
1134             {
1135 0     0 1 0     my (%params) = (@_);
1136              
1137             #
1138             # Ensure we have a 'df' binary.
1139             #
1140 0         0     my $path = FindBinary( Binary => "df" );
1141 0 0       0     if ( !$path )
1142                 {
1143 0         0         print "PercentageUsed - not implemented for $^O\n";
1144                 }
1145              
1146              
1147             #
1148             # The mount-point
1149             #
1150 0   0     0     my $point = $params{ 'Path' } || "/";
1151 0         0     my $perc = 0;
1152              
1153              
1154             #
1155             # Call df to get the output, use posix mode.
1156             #
1157 0         0     my $out = `$path -P $point`;
1158              
1159 0         0     foreach my $line ( split( /[\r\n]/, $out ) )
1160                 {
1161 0 0       0         next unless ( $line =~ /%/ );
1162              
1163 0 0       0         if ( $line =~ /[ \t]([0-9]*)%[ \t]/ )
1164                     {
1165 0         0             $perc = $1;
1166                     }
1167                 }
1168              
1169 0         0     return ($perc);
1170              
1171             }
1172              
1173              
1174             =head2 RemovePackage
1175            
1176             This method is a stub which does nothing but output a line of text to
1177             inform the caller that the method is not implemented.
1178            
1179             For an implementation, and documentation, please consult C<Slaughter::API::linux>.
1180            
1181             =cut
1182              
1183             sub RemovePackage
1184             {
1185 0     0 1 0     print "RemovePackage - not implemented for $^O\n";
1186             }
1187              
1188              
1189              
1190             =head2 ReplaceRegexp
1191            
1192             This primitive will open a local file, and replace any lines matching a given
1193             regular expression.
1194            
1195             =for example begin
1196            
1197             ReplaceRegexp( File => "/etc/ssh/sshd_config",
1198             Pattern => "^PermitRootLogin.*yes.*",
1199             Replace => "PermitRootLogin no" );
1200            
1201             =for example end
1202            
1203             The following parameters are available:
1204            
1205             =over
1206            
1207             =item File [mandatory]
1208            
1209             The filename which should be examined and potentially updated.
1210            
1211             =item Pattern [mandatory]
1212            
1213             The pattern to test and potentially replace.
1214            
1215             =item Replace [mandatory]
1216            
1217             The replacement text to use.
1218            
1219             =back
1220            
1221             The return value of this function is the number of lines updated,
1222             0 if none, or -1 if the file could not be opened.
1223            
1224             =cut
1225              
1226             sub ReplaceRegexp
1227             {
1228 3     3 1 9     my (%params) = (@_);
1229              
1230 3         3     my $pattern = $params{ 'Pattern' };
1231 3   50     6     my $replace = $params{ 'Replace' } || "";
1232 3         4     my $file = $params{ 'File' };
1233 3         2     my $found = 0;
1234              
1235 3 50       66     if ( open( my $handle, "<", $file ) )
1236                 {
1237 3         2         my @lines;
1238              
1239             # Read and replace as appropriate.
1240 3         23         foreach my $read (<$handle>)
1241                     {
1242 15         12             chomp($read);
1243 15         13             my $orig = $read;
1244              
1245 15 100       18             if ( $replace =~ /\$/ )
1246                         {
1247 5         23                 $read =~ s/$pattern/$replace/gee;
  1         37  
1248                         }
1249                         else
1250                         {
1251 10         26                 $read =~ s/$pattern/$replace/g;
1252                         }
1253              
1254 15 100       22             $found += 1 if ( $read ne $orig );
1255              
1256 15         16             push( @lines, $read );
1257                     }
1258 3         17         close($handle);
1259              
1260             # Now write out the possibly modified fils.
1261 3 50       4         if ($found)
1262                     {
1263 3 50       133             if ( open( my $handle, ">", $file ) )
1264                         {
1265 3         4                 foreach my $line (@lines)
1266                             {
1267 15         49                     print $handle $line . "\n";
1268                             }
1269 3         56                 close($handle);
1270              
1271 3         14                 return $found;
1272                         }
1273                     }
1274                     else
1275                     {
1276 0         0             return 0;
1277                     }
1278                 }
1279                 else
1280                 {
1281 0         0         return -1;
1282                 }
1283             }
1284              
1285              
1286              
1287              
1288             =head2 RunCommand
1289            
1290             This primitive will execute a system command.
1291            
1292             =for example begin
1293            
1294             RunCommand( Cmd => "/usr/bin/id" );
1295            
1296             =for example end
1297            
1298             The following parameters are available:
1299            
1300             =over
1301            
1302             =item Cmd [mandatory]
1303            
1304             The command to execute. If no redirection is present in the command to execute
1305             then STDERR will be redirected to STDOUT automatically.
1306            
1307             =back
1308            
1309             The return value of this function is the result of the perl system function.
1310            
1311             =cut
1312              
1313             sub RunCommand
1314             {
1315 3     3 1 4885     my (%params) = (@_);
1316              
1317 3   50     12     my $cmd = $params{ 'Cmd' } || return;
1318              
1319             #
1320             # Capture STDERR as well as STDOUT.
1321             #
1322 3 50       16     if ( $cmd !~ />/ )
1323                 {
1324 0         0         $cmd .= " 1>&2";
1325                 }
1326              
1327 3 50       10     $::verbose && print "runCommand( $cmd )\n";
1328              
1329 3         7049     return ( system($cmd ) );
1330             }
1331              
1332              
1333              
1334             =head2 SetPermissions
1335            
1336             This method allows the file owner,group, and mode-bits of a local file
1337             to be changed.
1338            
1339             =for example begin
1340            
1341             SetPermissions( File => "/etc/motd" ,
1342             Owner => "root",
1343             Group => "root",
1344             Mode => "644" );
1345            
1346             =for example end
1347            
1348             The following parameters are supported:
1349            
1350             =over 8
1351            
1352             =item File [mandatory]
1353            
1354             The filename to work with.
1355            
1356             =item Group
1357            
1358             The group to set as the owner for the file.
1359            
1360             =item User
1361            
1362             The username to set as the files owner.
1363            
1364             =item Mode
1365            
1366             The permissions bits to set for the file. B<NOTE> if this doesn't start with a leading
1367             "0" then it will be passed through the "oct" function - this allows you to use the
1368             obvious construct :
1369            
1370             =for example begin
1371            
1372             Mode => "755"
1373            
1374             =for example end
1375            
1376             =back
1377            
1378             =cut
1379              
1380             sub SetPermissions
1381             {
1382 9     9 1 21     my (%params) = (@_);
1383              
1384 9   50     17     my $file = $params{ 'File' } || return;
1385 9   100     22     my $group = $params{ 'Group' } || undef;
1386 9   100     18     my $owner = $params{ 'Owner' } || undef;
1387 9   100     22     my $mode = $params{ 'Mode' } || undef;
1388              
1389             # file missing is an error
1390 9 50       113     return (-1) if ( !-e $file );
1391              
1392             # Numeric values
1393 9         6     my $uid = undef;
1394 9         7     my $gid = undef;
1395              
1396             # invalid user?
1397 9 100       12     if ( defined($owner) )
1398                 {
1399 3         523         $uid = getpwnam($owner);
1400 3 50       23         return -2 if ( !defined($uid) );
1401              
1402 0 0       0         $::verbose && print "Owner:$owner -> UID:$uid\n";
1403                 }
1404              
1405             # invalid group?
1406 6 100       10     if ( defined($group) )
1407                 {
1408 3         148         $gid = getgrnam($group);
1409 3 50       22         return -2 if ( !defined($gid) );
1410 0 0       0         $::verbose && print "Group:$group -> GID:$gid\n";
1411                 }
1412              
1413 3         4     my $changed = 0;
1414              
1415 3 50       5     if ( $params{ 'Owner' } )
1416                 {
1417              
1418             #
1419             # Find the current UID/GID of the file, so we
1420             # can change just the owner.
1421             #
1422 0         0         my ( $dev, $ino, $mode, $nlink, $orig_uid,
1423                          $orig_gid, $rdev, $size, $atime, $mtime,
1424                          $ctime, $blksize, $blocks
1425                        ) = stat($file);
1426              
1427 0 0       0         $::verbose && print "\tSetting owner to $owner/$uid\n";
1428 0         0         chown( $uid, $orig_gid, $file );
1429              
1430 0         0         $changed += 1;
1431                 }
1432 3 50       6     if ( $params{ 'Group' } )
1433                 {
1434              
1435             #
1436             # Find the current UID/GID of the file, so we
1437             # can change just the group.
1438             #
1439 0         0         my ( $dev, $ino, $mode, $nlink, $orig_uid,
1440                          $orig_gid, $rdev, $size, $atime, $mtime,
1441                          $ctime, $blksize, $blocks
1442                        ) = stat($file);
1443              
1444 0 0       0         $::verbose && print "\tSetting group to $group/$gid\n";
1445 0         0         chown( $orig_uid, $gid, $file );
1446              
1447 0         0         $changed += 1;
1448                 }
1449 3 50       5     if ( $params{ 'Mode' } )
1450                 {
1451 3 50       4         $::verbose && print "\tSetting mode to $mode\n";
1452 3         4         my $mode = $params{ 'Mode' };
1453 3 50       9         if ( $mode !~ /^0/ )
1454                     {
1455 0         0             $mode = oct("0$mode");
1456 0 0       0             $::verbose && print "\tOctal mode is now $mode\n";
1457                     }
1458 3         64         chmod( $mode, $file );
1459 3         4         $changed += 1;
1460                 }
1461              
1462 3         11     return ($changed);
1463             }
1464              
1465              
1466              
1467             =head2 UserDetails
1468            
1469             This primitive will return a hash of data about the local Unix user
1470             specified, if it exists.
1471            
1472             =for example begin
1473            
1474             if ( UserExists( User => "skx" ) )
1475             {
1476             my %data = UserDetails( User => "skx" );
1477             }
1478            
1479             =for example end
1480            
1481             The following parameters are available:
1482            
1483             =over
1484            
1485             =item User [mandatory]
1486            
1487             The unix username to retrieve details of.
1488            
1489             =back
1490            
1491             The return value of this function is a hash of data conprising of the
1492             following Keys/Values
1493            
1494             =over
1495            
1496             =item Home
1497            
1498             The user's home directory
1499            
1500             =item UID
1501            
1502             The user's UID
1503            
1504             =item GID
1505            
1506             The user's GID
1507            
1508             =item Quota
1509            
1510             The user's quota.
1511            
1512             =item Comment
1513            
1514             The user's comment
1515            
1516             =item Shell
1517            
1518             The user's login shell.
1519            
1520             =item Login
1521            
1522             The user's username.
1523            
1524             =back
1525            
1526             Undef will be returned on failure.
1527            
1528             =cut
1529              
1530             sub UserDetails
1531             {
1532 1     1 1 256     my (%params) = (@_);
1533              
1534              
1535 1         50     my ( $name, $pwcode, $uid, $gid, $quota, $comment, $gcos, $home, $logprog )
1536                   = getpwnam( $params{ 'User' } );
1537              
1538             #
1539             # This is undef.
1540             #
1541 1 50       4     return $name if ( !defined($name) );
1542              
1543             #
1544             # Return the values as a hash
1545             #
1546 1         8     return ( { Home => $home,
1547                            UID => $uid,
1548                            GID => $gid,
1549                            Quota => $quota,
1550                            Comment => $comment,
1551                            Shell => $logprog,
1552                            Login => $name
1553                          } );
1554             }
1555              
1556              
1557              
1558             =head2 UserExists
1559            
1560             This primitive will test to see whether the given local user exists.
1561            
1562             =for example begin
1563            
1564             if ( UserExists( User => "skx" ) )
1565             {
1566             # skx exists
1567             }
1568            
1569             =for example end
1570            
1571             The following parameters are available:
1572            
1573             =over
1574            
1575             =item User [mandatory]
1576            
1577             The unix username to test for.
1578            
1579             =back
1580            
1581             The return value of this function is 1 if the user exists, and 0 otherwise.
1582            
1583             =cut
1584              
1585              
1586             sub UserExists
1587             {
1588 1     1 1 1321     my (%params) = (@_);
1589              
1590 1         74     my ( $login, $pass, $uid, $gid ) = getpwnam( $params{ 'User' } );
1591              
1592 1 50       5     if ( !defined($login) )
1593                 {
1594 0         0         return 0;
1595                 }
1596                 else
1597                 {
1598 1         3         return 1;
1599                 }
1600             }
1601              
1602              
1603              
1604              
1605             =head2 UserCreate
1606            
1607             Create a new user for the system.
1608            
1609             =for example begin
1610            
1611             # TODO
1612            
1613             =for example end
1614            
1615             The following parameters are required:
1616            
1617             =over 8
1618            
1619             =item Login
1620            
1621             The username to create.
1622            
1623             =item UID
1624            
1625             The UID for the user.
1626            
1627             =item GID
1628            
1629             The primary GID for the user.
1630            
1631             =back
1632            
1633             You may optionally specify the GCos field to use.
1634            
1635             =cut
1636              
1637             sub UserCreate
1638             {
1639 0     0 1       print "UserCreate - not implemented for $^O\n";
1640             }
1641              
1642              
1643              
1644             1;
1645              
1646              
1647              
1648             =head1 AUTHOR
1649            
1650             Steve Kemp <steve@steve.org.uk>
1651            
1652             =cut
1653              
1654             =head1 LICENSE
1655            
1656             Copyright (c) 2010-2015 by Steve Kemp. All rights reserved.
1657            
1658             This module is free software;
1659             you can redistribute it and/or modify it under
1660             the same terms as Perl itself.
1661             The LICENSE file contains the full text of the license.
1662            
1663             =cut
1664