File Coverage

lib/Rex/Commands/Fs.pm
Criterion Covered Total %
statement 174 393 44.2
branch 33 144 22.9
condition 17 79 21.5
subroutine 30 48 62.5
pod 29 29 100.0
total 283 693 40.8


line stmt bran cond sub pod time code
1             #
2             # (c) Jan Gehring
3             #
4              
5             =head1 NAME
6              
7             Rex::Commands::Fs - File system commands
8              
9             =head1 DESCRIPTION
10              
11             With this module you can do file system tasks like creating directories, deleting or moving files, and more.
12              
13             =head1 SYNOPSIS
14              
15             my @files = list_files "/etc";
16              
17             unlink("/tmp/file");
18              
19             rmdir("/tmp");
20             mkdir("/tmp");
21              
22             my %stat = stat("/etc/passwd");
23              
24             my $link = readlink("/path/to/a/link");
25             symlink("/source", "/dest");
26              
27             rename("oldname", "newname");
28              
29             chdir("/tmp");
30              
31             is_file("/etc/passwd");
32             is_dir("/etc");
33             is_writeable("/tmp");
34             is_writable("/tmp");
35              
36             chmod 755, "/tmp";
37             chown "user", "/tmp";
38             chgrp "group", "/tmp";
39              
40              
41             =head1 EXPORTED FUNCTIONS
42              
43             =cut
44              
45             package Rex::Commands::Fs;
46              
47 78     78   157520 use v5.12.5;
  78         383  
48 78     78   455 use warnings;
  78         200  
  78         4225  
49              
50             our $VERSION = '1.14.2.3'; # TRIAL VERSION
51              
52             require Rex::Exporter;
53 78     78   2496 use Data::Dumper;
  78         21242  
  78         3922  
54 78     78   528 use Fcntl;
  78         217  
  78         25554  
55 78     78   1924 use Rex::Helper::File::Spec;
  78         211  
  78         885  
56 78     78   3692 use Rex::Helper::SSH2;
  78         193  
  78         5455  
57 78     78   593 use Rex::Helper::Path;
  78         178  
  78         6626  
58 78     78   553 use Rex::Commands;
  78         204  
  78         508  
59 78     78   616 use Rex::Interface::Fs;
  78         192  
  78         661  
60 78     78   2255 use Rex::Interface::Exec;
  78         196  
  78         693  
61 78     78   2658 use Rex::Interface::File;
  78         186  
  78         580  
62 78     78   2171 use File::Basename;
  78         184  
  78         6170  
63 78     78   952 use Rex::Commands::MD5;
  78         190  
  78         687  
64              
65 78     78   536 use vars qw(@EXPORT);
  78         228  
  78         3248  
66 78     78   635 use base qw(Rex::Exporter);
  78         181  
  78         7247  
67              
68             @EXPORT = qw(list_files ls
69             unlink rm rmdir mkdir stat readlink symlink ln rename mv chdir cd cp
70             chown chgrp chmod
71             is_file is_dir is_readable is_writeable is_writable is_symlink
72             df du
73             mount umount
74             glob);
75              
76 78     78   571 use vars qw(%file_handles);
  78         195  
  78         381778  
77              
78             =head2 Changing content
79              
80             These commands are supposed to change the contents of the file system.
81              
82             =head3 symlink($from, $to)
83              
84             This function will create a symbolic link from C<$from> to C<$to>.
85              
86             task "symlink", "server01", sub {
87             symlink("/var/www/versions/1.0.0", "/var/www/html");
88             };
89              
90             =cut
91              
92             sub symlink {
93 14     14 1 383 my ( $from, $to ) = @_;
94 14         259 $from = resolv_path($from);
95 14         127 $to = resolv_path($to);
96              
97             Rex::get_current_connection()->{reporter}
98 14         51 ->report_resource_start( type => "symlink", name => $to );
99              
100 14         475 my $fs = Rex::Interface::Fs->create;
101 14 100 66     136 if ( $fs->is_symlink($to) && $fs->readlink($to) eq $from ) {
102 11         108 Rex::get_current_connection()->{reporter}->report( changed => 0, );
103             }
104             else {
105 3 50       90 $fs->ln( $from, $to ) or die("Can't link $from -> $to");
106             Rex::get_current_connection()->{reporter}
107 3         33 ->report( changed => 1, message => "Symlink created: $from -> $to." );
108             }
109              
110             Rex::get_current_connection()->{reporter}
111 14         148 ->report_resource_end( type => "symlink", name => $to );
112              
113 14         331 return 1;
114             }
115              
116             =head3 ln($from, $to)
117              
118             C is an alias for C
119              
120             =cut
121              
122             sub ln {
123 0     0 1 0 &symlink(@_);
124             }
125              
126             =head3 unlink($file)
127              
128             This function will remove the given C<$file>.
129              
130             task "unlink", "server01", sub {
131             unlink("/tmp/testfile");
132             };
133              
134             =cut
135              
136             sub unlink {
137 7     7 1 15206 my @files = @_;
138              
139 7         27 my $f;
140 7 50       39 if ( ref $files[0] eq "ARRAY" ) {
141 0         0 $f = $files[0];
142             }
143             else {
144 7         25 $f = \@files;
145             }
146              
147 7 50       23 if ( scalar @{$f} == 1 ) {
  7         28  
148 7         35 my $file = resolv_path $f->[0];
149 7         103 my $fs = Rex::Interface::Fs->create;
150              
151             Rex::get_current_connection()->{reporter}
152 7         38 ->report_resource_start( type => "unlink", name => $file );
153              
154 7 50 33     51 if ( $fs->is_file($file) || $fs->is_symlink($file) ) {
155 7         53 $fs->unlink($file);
156              
157 7         166 my $tmp_path = Rex::Config->get_tmp_dir;
158 7 50       118 if ( $file !~ m/^\Q$tmp_path\E[\/\\][a-z]+\.tmp$/ ) { # skip tmp rex files
159             Rex::get_current_connection()->{reporter}
160 7         36 ->report( changed => 1, message => "File $file removed." );
161             }
162             }
163             else {
164 0         0 Rex::get_current_connection()->{reporter}->report( changed => 0, );
165             }
166              
167             Rex::get_current_connection()->{reporter}
168 7         34 ->report_resource_end( type => "unlink", name => $file );
169             }
170             else {
171 0         0 &unlink($_) for @{$f};
  0         0  
172             }
173              
174             }
175              
176             =head3 rm($file)
177              
178             This is an alias for C.
179              
180             =cut
181              
182             sub rm {
183 0     0 1 0 &unlink(@_);
184             }
185              
186             =head3 rmdir($dir)
187              
188             This function will remove the given directory.
189              
190             task "rmdir", "server01", sub {
191             rmdir("/tmp");
192             };
193              
194              
195             With Rex-0.45 and newer, please use the L resource instead.
196              
197             task "prepare", sub {
198             file "/tmp",
199             ensure => "absent";
200             };
201              
202             =cut
203              
204             sub rmdir {
205 2     2 1 2267 my @dirs = @_;
206              
207 2         16 my $d;
208 2 50       15 if ( ref $dirs[0] eq "ARRAY" ) {
209 0         0 $d = $dirs[0];
210             }
211             else {
212 2         6 $d = \@dirs;
213             }
214              
215 2 50       5 if ( scalar @{$d} == 1 ) {
  2         18  
216 2         26 my $dir = resolv_path $d->[0];
217 2         21 my $fs = Rex::Interface::Fs->create;
218              
219             Rex::get_current_connection()->{reporter}
220 2         21 ->report_resource_start( type => "rmdir", name => $dir );
221              
222 2 50 33     29 if ( !$fs->is_dir($dir) && $dir !~ m/[\*\[]/ ) {
223 0         0 Rex::get_current_connection()->{reporter}->report( changed => 0, );
224             }
225             else {
226 2         33 $fs->rmdir($dir);
227 2 50       38 if ( $fs->is_dir($dir) ) {
228 0         0 die "Can't remove $dir.";
229             }
230              
231             Rex::get_current_connection()->{reporter}
232 2         35 ->report( changed => 1, message => "Directory $dir removed." );
233             }
234              
235             Rex::get_current_connection()->{reporter}
236 2         25 ->report_resource_end( type => "rmdir", name => $dir );
237             }
238             else {
239 0         0 &rmdir($_) for @{$d};
  0         0  
240             }
241             }
242              
243             =head3 mkdir($newdir)
244              
245             This function will create a new directory.
246              
247             The following options are supported:
248              
249             =over 4
250              
251             =item * owner
252              
253             =item * group
254              
255             =item * mode
256              
257             =item * on_change
258              
259             =back
260              
261             With Rex-0.45 and newer, please use the L resource instead.
262              
263             task "prepare", sub {
264             file "/tmp",
265             ensure => "directory",
266             owner => "root",
267             group => "root",
268             mode => 1777;
269             };
270              
271             Direct usage:
272              
273             task "mkdir", "server01", sub {
274             mkdir "/tmp";
275              
276             mkdir "/tmp",
277             owner => "root",
278             group => "root",
279             mode => 1777;
280             };
281              
282             =cut
283              
284             sub mkdir {
285 5     5 1 11708 Rex::Logger::debug("Creating directory $_[0]");
286 5         28 my $dir = shift;
287 5         64 $dir = resolv_path($dir);
288              
289 5         38 my $options = {@_};
290              
291 5   100 3   82 $options->{on_change} //= sub { };
292              
293             Rex::get_current_connection()->{reporter}
294 5         46 ->report_resource_start( type => "mkdir", name => $dir );
295              
296 5         97 my $fs = Rex::Interface::Fs->create;
297              
298 5         23 my $not_created = 0;
299 5         29 my %old_stat;
300 5         16 my $changed = 0;
301              
302 5 50       33 if ( $fs->is_dir($dir) ) {
303 0         0 $not_created = 1;
304 0         0 %old_stat = &stat($dir);
305             }
306              
307 5   50     52 my $mode = $options->{"mode"} || 755;
308 5   50     72 my $owner = $options->{"owner"} || "";
309 5   50     61 my $group = $options->{"group"} || "";
310 5   50     48 my $not_recursive = $options->{"not_recursive"} || 0;
311              
312 5 50       27 if ($not_recursive) {
313 0 0       0 if ( !$fs->mkdir($dir) ) {
314 0         0 Rex::Logger::debug("Can't create directory $dir");
315 0         0 die("Can't create directory $dir");
316             }
317              
318 0 0       0 &chown( $owner, $dir ) if $owner;
319 0 0       0 &chgrp( $group, $dir ) if $group;
320 0 0       0 &chmod( $mode, $dir ) if $mode;
321             }
322             else {
323 5 100       183 if ( !Rex::Helper::File::Spec->file_name_is_absolute($dir) ) {
324 2         47 $dir = Rex::Helper::File::Spec->rel2abs($dir);
325             }
326              
327 5         51 my @directories = __splitdir($dir);
328 5         17 my $path_so_far = shift @directories;
329              
330 5         23 for my $part (@directories) {
331 16         126 $path_so_far = Rex::Helper::File::Spec->join( $path_so_far, $part );
332              
333 16 50 66     84 if ( !is_dir($path_so_far) && !is_file($path_so_far) ) {
334 5 50       43 if ( !$fs->mkdir($path_so_far) ) {
335 0         0 Rex::Logger::debug("Can't create directory $dir");
336 0         0 die("Can't create directory $dir");
337             }
338              
339 5 50       28 &chown( $owner, $path_so_far ) if $owner;
340 5 50       16 &chgrp( $group, $path_so_far ) if $group;
341 5 50       48 &chmod( $mode, $path_so_far ) if $mode;
342             }
343             }
344             }
345              
346 5         131 my %new_stat = &stat($dir);
347              
348 5 50       63 if ( !$not_created ) {
349             Rex::get_current_connection()->{reporter}
350 5         56 ->report( changed => 1, message => "Directory created." );
351 5         15 $changed = 1;
352             }
353              
354 5 50 33     61 if ( %old_stat && $old_stat{uid} != $new_stat{uid} ) {
355             Rex::get_current_connection()->{reporter}
356 0         0 ->report( changed => 1, message => "Owner updated." );
357 0         0 $changed = 1;
358             }
359              
360 5 50 33     44 if ( %old_stat && $old_stat{gid} != $new_stat{gid} ) {
361             Rex::get_current_connection()->{reporter}
362 0         0 ->report( changed => 1, message => "Group updated." );
363 0         0 $changed = 1;
364             }
365              
366 5 50 33     43 if ( %old_stat && $old_stat{mode} ne $new_stat{mode} ) {
367             Rex::get_current_connection()->{reporter}
368 0         0 ->report( changed => 1, message => "Mode updated." );
369 0         0 $changed = 1;
370             }
371              
372 5 50       50 if ( $changed == 0 ) {
373 0         0 Rex::get_current_connection()->{reporter}->report( changed => 0, );
374             }
375             else {
376 5         81 $options->{on_change}->($dir);
377             }
378              
379             Rex::get_current_connection()->{reporter}
380 5         42 ->report_resource_end( type => "mkdir", name => $dir );
381              
382 5         165 return 1;
383             }
384              
385             sub __splitdir {
386 6     6   4660 return Rex::Helper::File::Spec->splitdir(shift);
387             }
388              
389             =head3 chown($owner, $path)
390              
391             Change the owner of a file or a directory.
392              
393             chown "www-data", "/var/www/html";
394              
395             chown "www-data", "/var/www/html",
396             recursive => 1;
397              
398              
399             This command will not be reported.
400              
401             If you want to use reports, please use the L resource instead.
402              
403             =cut
404              
405             sub chown {
406 0     0 1 0 my ( $user, $file, @opts ) = @_;
407              
408 0         0 $file = resolv_path($file);
409 0         0 my $fs = Rex::Interface::Fs->create;
410 0 0       0 $fs->chown( $user, $file, @opts ) or die("Can't chown $file");
411             }
412              
413             =head3 chgrp($group, $path)
414              
415             Change the group of a file or a directory.
416              
417             chgrp "nogroup", "/var/www/html";
418              
419             chgrp "nogroup", "/var/www/html",
420             recursive => 1;
421              
422              
423             This command will not be reported.
424              
425             If you want to use reports, please use the L resource instead.
426              
427             =cut
428              
429             sub chgrp {
430 0     0 1 0 my ( $group, $file, @opts ) = @_;
431 0         0 $file = resolv_path($file);
432              
433 0         0 my $fs = Rex::Interface::Fs->create;
434 0 0       0 $fs->chgrp( $group, $file, @opts ) or die("Can't chgrp $file");
435             }
436              
437             =head3 chmod($mode, $path)
438              
439             Change the permissions of a file or a directory.
440              
441             chmod 755, "/var/www/html";
442              
443             chmod 755, "/var/www/html",
444             recursive => 1;
445              
446              
447             This command will not be reported.
448              
449             If you want to use reports, please use the L resource instead.
450              
451             =cut
452              
453             sub chmod {
454 5     5 1 29 my ( $mode, $file, @opts ) = @_;
455 5         25 $file = resolv_path($file);
456              
457 5         36 my $fs = Rex::Interface::Fs->create;
458 5 50       46 $fs->chmod( $mode, $file, @opts ) or die("Can't chmod $file");
459             }
460              
461             =head3 rename($old, $new)
462              
463             This function will rename C<$old> to C<$new>. Will return 1 on success and 0 on failure.
464              
465             task "rename", "server01", sub {
466             rename("/tmp/old", "/tmp/new");
467             };
468              
469             =cut
470              
471             sub rename {
472 0     0 1 0 my ( $old, $new ) = @_;
473 0         0 $old = resolv_path($old);
474 0         0 $new = resolv_path($new);
475              
476             Rex::get_current_connection()->{reporter}
477 0         0 ->report_resource_start( type => "rename", name => "$old -> $new" );
478              
479 0         0 my $fs = Rex::Interface::Fs->create;
480              
481 0         0 my $old_present = 0;
482 0 0 0     0 if ( $fs->is_file($old) || $fs->is_dir($old) || $fs->is_symlink($old) ) {
      0        
483 0         0 $old_present = 1;
484             }
485              
486 0         0 Rex::Logger::debug("Renaming $old to $new");
487              
488 0 0       0 if ( !$fs->rename( $old, $new ) ) {
489 0         0 Rex::Logger::info("Rename failed ($old -> $new)");
490 0         0 die("Rename failed $old -> $new");
491             }
492              
493 0         0 my $new_present = 0;
494 0 0 0     0 if ( $fs->is_file($new) || $fs->is_dir($new) || $fs->is_symlink($new) ) {
      0        
495 0         0 $new_present = 1;
496             }
497              
498 0         0 my $old_absent = 0;
499 0 0 0     0 if ( !( $fs->is_file($old) || $fs->is_dir($old) || $fs->is_symlink($old) ) ) {
      0        
500 0         0 $old_absent = 1;
501             }
502              
503 0 0 0     0 if ( $old_present == 1 && $new_present == 1 && $old_absent == 1 ) {
      0        
504 0         0 Rex::get_current_connection()->{reporter}->report( changed => 1 );
505             }
506             else {
507 0         0 Rex::get_current_connection()->{reporter}->report( changed => 0 );
508             }
509              
510             Rex::get_current_connection()->{reporter}
511 0         0 ->report_resource_end( type => "rename", name => "$old -> $new" );
512             }
513              
514             =head3 mv($old, $new)
515              
516             C is an alias for C.
517              
518             =cut
519              
520             sub mv {
521 0     0 1 0 return &rename(@_);
522             }
523              
524             =head3 cp($source, $destination)
525              
526             C will copy C<$source> to C<$destination> recursively.
527              
528             task "cp", "server01", sub {
529             cp("/var/www", "/var/www.old");
530             };
531              
532             =cut
533              
534             sub cp {
535 0     0 1 0 my ( $source, $dest ) = @_;
536              
537 0         0 $source = resolv_path($source);
538 0         0 $dest = resolv_path($dest);
539              
540             Rex::get_current_connection()->{reporter}
541 0         0 ->report_resource_start( type => "cp", name => "$source -> $dest" );
542              
543 0         0 my $fs = Rex::Interface::Fs->create;
544              
545 0         0 my $new_present = 0;
546 0 0 0     0 if ( $fs->is_file($source) && $fs->is_dir($dest) ) {
547 0         0 $dest = "$dest/" . basename $source;
548             }
549              
550 0 0 0     0 if ( $fs->is_file($dest) || $fs->is_dir($dest) || $fs->is_symlink($dest) ) {
      0        
551 0         0 $new_present = 1;
552             }
553              
554 0 0       0 if ( !$fs->cp( $source, $dest ) ) {
555 0         0 die("Copy failed from $source to $dest");
556             }
557              
558 0 0       0 if ( $new_present == 0 ) {
559 0         0 Rex::get_current_connection()->{reporter}->report( changed => 1, );
560             }
561             else {
562 0         0 Rex::get_current_connection()->{reporter}->report( changed => 0, );
563             }
564              
565             Rex::get_current_connection()->{reporter}
566 0         0 ->report_resource_end( type => "cp", name => "$source -> $dest" );
567             }
568              
569             =head2 Not changing content
570              
571             These commands should not change the contents of the file system.
572              
573             =head3 list_files("/path");
574              
575             This function lists all entries (files, directories, ...) in a given directory and returns them as an array.
576              
577             task "ls-etc", "server01", sub {
578             my @tmp_files = grep { /\.tmp$/ } list_files("/etc");
579             };
580              
581             This command will not be reported.
582              
583             =cut
584              
585             sub list_files {
586 2     2 1 8 my $path = shift;
587 2         28 $path = resolv_path($path);
588              
589 2         39 my $fs = Rex::Interface::Fs->create;
590 2         22 my @ret = $fs->ls($path);
591              
592 2         23 return @ret;
593             }
594              
595             =head3 ls($path)
596              
597             Just an alias for C.
598              
599             =cut
600              
601             sub ls {
602 0     0 1 0 return list_files(@_);
603             }
604              
605             =head3 stat($file)
606              
607             This function will return a hash with the following information about a file or directory:
608              
609             =over 4
610              
611             =item mode
612              
613             =item size
614              
615             =item uid
616              
617             =item gid
618              
619             =item atime
620              
621             =item mtime
622              
623             =back
624              
625             task "stat", "server01", sub {
626             my %file_stat = stat("/etc/passwd");
627             };
628              
629              
630             This command will not be reported.
631              
632             =cut
633              
634             sub stat {
635 7     7 1 2387 my ($file) = @_;
636 7         116 $file = resolv_path($file);
637 7         38 my %ret;
638              
639 7         159 Rex::Logger::debug("Getting fs stat from $file");
640              
641 7         185 my $fs = Rex::Interface::Fs->create;
642              
643             # may return undef, so capture into a list first.
644 7         98 my @stat = $fs->stat($file);
645 7 100 66     133 die("Can't stat $file") if ( !defined $stat[0] && scalar @stat == 1 );
646              
647 6 50       58 if ( scalar @stat % 2 ) {
648 0         0 Rex::Logger::debug( 'stat output: ' . join ', ', @stat );
649 0         0 die('stat returned odd number of elements');
650             }
651              
652 6         86 %ret = @stat;
653              
654 6         133 return %ret;
655             }
656              
657             =head3 is_file($path)
658              
659             This function tests if C<$path> is a file. Returns 1 if true, 0 if false.
660              
661             task "isfile", "server01", sub {
662             if( is_file("/etc/passwd") ) {
663             say "it is a file.";
664             }
665             else {
666             say "hm, this is not a file.";
667             }
668             };
669              
670             This command will not be reported.
671              
672             =cut
673              
674             sub is_file {
675 697     697 1 5585 my ($file) = @_;
676 697         5655 $file = resolv_path($file);
677              
678 697         10412 my $fs = Rex::Interface::Fs->create;
679 697         5470 return $fs->is_file($file);
680             }
681              
682             =head3 is_dir($path)
683              
684             This function tests if C<$path> is a directory. Returns 1 if true, 0 if false.
685              
686             task "isdir", "server01", sub {
687             if( is_dir("/etc") ) {
688             say "it is a directory.";
689             }
690             else {
691             say "hm, this is not a directory.";
692             }
693             };
694              
695             This command will not be reported.
696              
697             =cut
698              
699             sub is_dir {
700 271     271 1 1805 my ($path) = @_;
701 271         2779 $path = resolv_path($path);
702              
703 271         9298 my $fs = Rex::Interface::Fs->create;
704 271         3435 return $fs->is_dir($path);
705              
706             }
707              
708             =head3 is_symlink($path)
709              
710             This function tests if C<$path> is a symbolic link. Returns 1 if true, 0 if false.
711              
712             task "issym", "server01", sub {
713             if( is_symlink("/etc/foo.txt") ) {
714             say "it is a symlink.";
715             }
716             else {
717             say "hm, this is not a symlink.";
718             }
719             };
720              
721             This command will not be reported.
722              
723             =cut
724              
725             sub is_symlink {
726 20     20 1 156 my ($path) = @_;
727 20         187 $path = resolv_path($path);
728              
729 20         379 my $fs = Rex::Interface::Fs->create;
730 20         133 return $fs->is_symlink($path);
731             }
732              
733             =head3 is_readable($path)
734              
735             This function tests if C<$path> is readable. It returns 1 if true, 0 if false.
736              
737             task "readable", "server01", sub {
738             if( is_readable("/etc/passwd") ) {
739             say "passwd is readable";
740             }
741             else {
742             say "not readable.";
743             }
744             };
745              
746             This command will not be reported.
747              
748             =cut
749              
750             sub is_readable {
751 2     2 1 5 my ($file) = @_;
752 2         7 $file = resolv_path($file);
753 2         12 Rex::Logger::debug("Checking if $file is readable");
754              
755 2         11 my $fs = Rex::Interface::Fs->create;
756 2         6 return $fs->is_readable($file);
757             }
758              
759             =head3 is_writable($path)
760              
761             This function tests if C<$path> is writable. It returns 1 if true, 0 if false.
762              
763             task "writable", "server01", sub {
764             if( is_writable("/etc/passwd") ) {
765             say "passwd is writable";
766             }
767             else {
768             say "not writable.";
769             }
770             };
771              
772             This command will not be reported.
773              
774             =cut
775              
776             sub is_writable {
777 0     0 1 0 my ($file) = @_;
778 0         0 $file = resolv_path($file);
779 0         0 Rex::Logger::debug("Checking if $file is writable");
780              
781 0         0 my $fs = Rex::Interface::Fs->create;
782 0         0 return $fs->is_writable($file);
783             }
784              
785             =head3 is_writeable($file)
786              
787             This is only an alias for C.
788              
789             =cut
790              
791             sub is_writeable {
792 0     0 1 0 is_writable(@_);
793             }
794              
795             =head3 readlink($link)
796              
797             If C<$link> is a symbolic link, returns the path it resolves to, and Cs otherwise.
798              
799             task "islink", "server01", sub {
800             my $link;
801             eval {
802             $link = readlink("/tmp/testlink");
803             };
804              
805             say "this is a link" if($link);
806             };
807              
808             This command will not be reported.
809              
810             =cut
811              
812             sub readlink {
813 0     0 1 0 my ($file) = @_;
814 0         0 $file = resolv_path($file);
815 0         0 Rex::Logger::debug("Reading link of $file");
816              
817 0         0 my $fs = Rex::Interface::Fs->create;
818 0         0 my $link = $fs->readlink($file);
819              
820 0 0       0 unless ($link) {
821 0         0 Rex::Logger::debug("readlink: $file is not a link.");
822 0         0 die("readlink: $file is not a link.");
823             }
824              
825 0         0 return $link;
826             }
827              
828             =head3 chdir($newdir)
829              
830             This function will change the working directory to C<$newdir>. This function currently works only locally.
831              
832             task "chdir", "server01", sub {
833             chdir("/tmp");
834             };
835              
836             This command will not be reported.
837              
838             =cut
839              
840             sub chdir {
841 0     0 1 0 Rex::Logger::debug("chdir behaviour will be changed in the future.");
842 0         0 CORE::chdir( $_[0] );
843             }
844              
845             =head3 cd($newdir)
846              
847             This is an alias of C.
848              
849             =cut
850              
851             sub cd {
852 0     0 1 0 &chdir( $_[0] );
853             }
854              
855             =head3 df([$device])
856              
857             This function returns a hash reference which reflects the output of C.
858              
859             task "df", "server01", sub {
860             my $df = df();
861             my $df_on_sda1 = df("/dev/sda1");
862             };
863              
864             This command will not be reported.
865              
866             =cut
867              
868             sub df {
869 0     0 1 0 my ($dev) = @_;
870              
871 0         0 my $ret = {};
872              
873 0   0     0 $dev ||= "";
874              
875 0         0 my $exec = Rex::Interface::Exec->create;
876 0         0 my ( $out, $err ) = $exec->exec("df $dev 2>/dev/null");
877              
878 0         0 my @lines = split( /\r?\n/, $out );
879              
880 0         0 $ret = _parse_df(@lines);
881              
882 0 0       0 if ($dev) {
883 0 0       0 if ( keys %$ret == 1 ) {
884 0         0 ($dev) = keys %$ret;
885             }
886 0         0 return $ret->{$dev};
887             }
888              
889 0         0 return $ret;
890             }
891              
892             sub _parse_df {
893 2     2   11689 my @lines = @_;
894 2         8 chomp @lines;
895              
896 2         4 my $ret = {};
897              
898 2         4 shift @lines;
899 2         4 my $current_fs = "";
900              
901 2         5 for my $line (@lines) {
902 7         40 my ( $fs, $size, $used, $free, $use_per, $mounted_on ) =
903             split( /\s+/, $line, 6 );
904 7 100       20 $current_fs = $fs if $fs;
905              
906 7 100       12 if ( !$size ) {
907 1         3 next;
908             }
909              
910 6         26 $ret->{$current_fs} = {
911             size => $size,
912             used => $used,
913             free => $free,
914             used_perc => $use_per,
915             mounted_on => $mounted_on
916             };
917             }
918              
919 2         7 return $ret;
920             }
921              
922             =head3 du($path)
923              
924             Returns the disk usage of C<$path>.
925              
926             task "du", "server01", sub {
927             say "size of /var/www: " . du("/var/www");
928             };
929              
930             This command will not be reported.
931              
932             =cut
933              
934             sub du {
935 0     0 1   my ($path) = @_;
936 0           $path = resolv_path($path);
937              
938 0           my $exec = Rex::Interface::Exec->create;
939 0           my @lines = $exec->exec("du -s $path");
940 0           my ($du) = ( $lines[0] =~ m/^(\d+)/ );
941              
942 0           return $du;
943             }
944              
945             =head3 mount($device, $mount_point, @options)
946              
947             Mount devices.
948              
949             task "mount", "server01", sub {
950             mount "/dev/sda5", "/tmp";
951             mount "/dev/sda6", "/mnt/sda6",
952             ensure => "present",
953             type => "ext3",
954             options => [qw/noatime async/],
955             on_change => sub { say "device mounted"; };
956             #
957             # mount persistent with entry in /etc/fstab
958              
959             mount "/dev/sda6", "/mnt/sda6",
960             ensure => "persistent",
961             type => "ext3",
962             options => [qw/noatime async/],
963             on_change => sub { say "device mounted"; };
964              
965             # to umount a device
966             mount "/dev/sda6", "/mnt/sda6",
967             ensure => "absent";
968              
969             };
970              
971             In order to be more aligned with C terminology, the previously used C option has been deprecated in favor of the C option. The C option is still supported and works as previously, but Rex prints a warning if it is being used. There's also a warning if both C and C options are specified, and in this case C will be used.
972              
973             =cut
974              
975             sub mount {
976 0     0 1   my ( $device, $mount_point, @options ) = @_;
977 0           my $option = {@options};
978              
979 0 0         if ( defined $option->{fs} ) {
980 0           Rex::Logger::info(
981             'The `fs` option of the mount command has been deprecated in favor of the `type` option. Please update your task.',
982             'warn'
983             );
984              
985 0 0         if ( !defined $option->{type} ) {
986 0           $option->{type} = $option->{fs};
987             }
988             else {
989 0           Rex::Logger::info(
990             'Both `fs` and `type` options have been specified for mount command. Preferring `type`.',
991             'warn'
992             );
993             }
994             }
995              
996 0           delete $option->{fs};
997              
998             Rex::get_current_connection()->{reporter}
999 0           ->report_resource_start( type => "mount", name => "$mount_point" );
1000              
1001 0   0       $option->{ensure} ||= "present"; # default
1002              
1003 0 0         if ( $option->{ensure} eq "absent" ) {
1004             &umount(
1005             $mount_point,
1006             device => $device,
1007             on_change =>
1008             ( exists $option->{on_change} ? $option->{on_change} : undef )
1009 0 0         );
1010             }
1011             else {
1012 0 0         if ( $option->{ensure} eq "persistent" ) {
1013 0           $option->{persistent} = 1;
1014             }
1015              
1016 0           my $changed = 0;
1017 0           my $exec = Rex::Interface::Exec->create;
1018              
1019 0           my ( $m_out, $m_err ) = $exec->exec("mount");
1020 0           my @mounted = split( /\r?\n/, $m_out );
1021 0           my ($already_mounted) = grep { m/$device on $mount_point/ } @mounted;
  0            
1022 0 0         if ($already_mounted) {
1023 0           Rex::Logger::debug("Device ($device) already mounted on $mount_point.");
1024 0           $changed = 0;
1025             }
1026              
1027             my $cmd = sprintf(
1028             "mount %s %s %s %s",
1029             $option->{type} ? "-t " . $option->{type} : "", # file system
1030             $option->{"options"}
1031 0 0         ? " -o " . join( ",", @{ $option->{"options"} } )
  0 0          
1032             : "",
1033             $device,
1034             $mount_point
1035             );
1036              
1037 0 0         unless ($already_mounted) {
1038 0           $exec->exec($cmd);
1039 0 0         if ( $? != 0 ) { die("Mount failed of $mount_point"); }
  0            
1040 0           $changed = 1;
1041             Rex::get_current_connection()->{reporter}->report(
1042 0           changed => 1,
1043             message => "Device $device mounted on $mount_point."
1044             );
1045             }
1046              
1047 0 0         if ( exists $option->{persistent} ) {
1048 0 0         if ( !exists $option->{type} ) {
1049              
1050             # no fs given, so get it from mount output
1051 0           my ( $out, $err ) = $exec->exec("mount");
1052 0           my @output = split( /\r?\n/, $out );
1053 0           my ($line) = grep { /^$device/ } @output;
  0            
1054 0           my ( $_d, $_o, $_p, $_t, $fs_type ) = split( /\s+/, $line );
1055 0           $option->{type} = $fs_type;
1056              
1057 0           my ($_options) = ( $line =~ m/\((.+?)\)/ );
1058 0           $option->{options} = $_options;
1059             }
1060              
1061 0           my $fh = Rex::Interface::File->create;
1062              
1063 0           my $old_md5 = md5("/etc/fstab");
1064              
1065 0 0         if ( !$fh->open( "<", "/etc/fstab" ) ) {
1066 0           Rex::Logger::debug("Can't open /etc/fstab for reading.");
1067 0           die("Can't open /etc/fstab for reading.");
1068             }
1069              
1070 0           my $f = Rex::FS::File->new( fh => $fh );
1071 0           my @content = $f->read_all;
1072 0           $f->close;
1073              
1074 0           my @new_content = grep { !/^$device\s/ } @content;
  0            
1075              
1076 0   0       $option->{options} ||= "defaults";
1077              
1078 0 0         if ( ref( $option->{options} ) eq "ARRAY" ) {
1079 0           my $mountops = join( ",", @{ $option->{"options"} } );
  0            
1080 0 0         if ( $option->{label} ) {
1081             push( @new_content,
1082             "LABEL="
1083             . $option->{label}
1084 0           . "\t$mount_point\t$option->{type}\t$mountops\t0 0\n" );
1085             }
1086             else {
1087 0           push( @new_content,
1088             "$device\t$mount_point\t$option->{type}\t$mountops\t0 0\n" );
1089             }
1090             }
1091             else {
1092 0 0         if ( $option->{label} ) {
1093             push( @new_content,
1094             "LABEL="
1095             . $option->{label}
1096 0           . "\t$mount_point\t$option->{type}\t$option->{options}\t0 0\n" );
1097             }
1098             else {
1099 0           push( @new_content,
1100             "$device\t$mount_point\t$option->{type}\t$option->{options}\t0 0\n"
1101             );
1102             }
1103             }
1104              
1105 0           $fh = Rex::Interface::File->create;
1106              
1107 0 0         if ( !$fh->open( ">", "/etc/fstab" ) ) {
1108 0           Rex::Logger::debug("Can't open /etc/fstab for writing.");
1109 0           die("Can't open /etc/fstab for writing.");
1110             }
1111              
1112 0           $f = Rex::FS::File->new( fh => $fh );
1113 0           $f->write( join( "\n", @new_content ) );
1114 0           $f->close;
1115              
1116 0           my $new_md5 = md5("/etc/fstab");
1117              
1118 0 0         if ( $new_md5 ne $old_md5 ) {
1119             Rex::get_current_connection()->{reporter}
1120 0           ->report( changed => 1, message => "File /etc/fstab updated." );
1121 0           $changed = 1;
1122             }
1123             }
1124              
1125 0 0         if ( $changed == 1 ) {
1126 0 0 0       if ( exists $option->{on_change} && ref $option->{on_change} eq "CODE" ) {
1127 0           $option->{on_change}->( $device, $mount_point );
1128             }
1129             }
1130             }
1131              
1132             Rex::get_current_connection()->{reporter}
1133 0           ->report_resource_end( type => "mount", name => "$mount_point" );
1134             }
1135              
1136             =head3 umount($mount_point)
1137              
1138             Unmount device.
1139              
1140             task "umount", "server01", sub {
1141             umount "/tmp";
1142             };
1143              
1144             =cut
1145              
1146             sub umount {
1147 0     0 1   my ( $mount_point, %option ) = @_;
1148              
1149 0           my $device;
1150              
1151 0 0         if ( exists $option{device} ) {
1152 0           $device = $option{device};
1153             }
1154              
1155 0           my $exec = Rex::Interface::Exec->create;
1156              
1157             Rex::get_current_connection()->{reporter}
1158 0           ->report_resource_start( type => "umount", name => "$mount_point" );
1159              
1160 0           my $changed = 0;
1161 0           my ( $m_out, $m_err ) = $exec->exec("mount");
1162 0           my @mounted = split( /\r?\n/, $m_out );
1163 0           my $already_mounted;
1164              
1165 0 0         if ($device) {
1166 0           ($already_mounted) = grep { m/$device on $mount_point/ } @mounted;
  0            
1167             }
1168             else {
1169 0           ($already_mounted) = grep { m/on $mount_point/ } @mounted;
  0            
1170             }
1171              
1172 0 0         if ($already_mounted) {
1173 0           $exec->exec("umount $mount_point");
1174 0 0         if ( $? != 0 ) { die("Umount failed of $mount_point"); }
  0            
1175 0           $changed = 1;
1176             }
1177              
1178 0 0         if ($changed) {
1179 0 0 0       if ( exists $option{on_change} && ref $option{on_change} eq "CODE" ) {
1180 0           $option{on_change}->( $mount_point, %option );
1181             }
1182             Rex::get_current_connection()->{reporter}
1183 0           ->report( changed => 1, message => "Unmounted $mount_point." );
1184             }
1185              
1186             Rex::get_current_connection()->{reporter}
1187 0           ->report_resource_end( type => "umount", name => "$mount_point" );
1188             }
1189              
1190             =head3 glob($glob)
1191              
1192             Returns the list of filename expansions for C<$glob> as L would do.
1193              
1194             task "glob", "server1", sub {
1195             my @files_with_p = grep { is_file($_) } glob("/etc/p*");
1196             };
1197              
1198             This command will not be reported.
1199              
1200             =cut
1201              
1202             sub glob {
1203 0     0 1   my ($glob) = @_;
1204 0           $glob = resolv_path($glob);
1205              
1206 0           my $fs = Rex::Interface::Fs->create;
1207 0           return $fs->glob($glob);
1208             }
1209              
1210             1;