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 48     48   165540 use v5.12.5;
  48         222  
48 48     48   268 use warnings;
  48         101  
  48         2377  
49              
50             our $VERSION = '1.14.2.2'; # TRIAL VERSION
51              
52             require Rex::Exporter;
53 48     48   2455 use Data::Dumper;
  48         22215  
  48         2297  
54 48     48   308 use Fcntl;
  48         141  
  48         13582  
55 48     48   1731 use Rex::Helper::File::Spec;
  48         125  
  48         628  
56 48     48   2781 use Rex::Helper::SSH2;
  48         106  
  48         2988  
57 48     48   366 use Rex::Helper::Path;
  48         98  
  48         2974  
58 48     48   320 use Rex::Commands;
  48         177  
  48         335  
59 48     48   381 use Rex::Interface::Fs;
  48         115  
  48         330  
60 48     48   1353 use Rex::Interface::Exec;
  48         108  
  48         776  
61 48     48   1453 use Rex::Interface::File;
  48         109  
  48         318  
62 48     48   1228 use File::Basename;
  48         113  
  48         3523  
63 48     48   558 use Rex::Commands::MD5;
  48         105  
  48         367  
64              
65 48     48   291 use vars qw(@EXPORT);
  48         128  
  48         1847  
66 48     48   355 use base qw(Rex::Exporter);
  48         138  
  48         4518  
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 48     48   329 use vars qw(%file_handles);
  48         109  
  48         230793  
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 434 my ( $from, $to ) = @_;
94 14         302 $from = resolv_path($from);
95 14         66 $to = resolv_path($to);
96              
97             Rex::get_current_connection()->{reporter}
98 14         47 ->report_resource_start( type => "symlink", name => $to );
99              
100 14         565 my $fs = Rex::Interface::Fs->create;
101 14 100 66     76 if ( $fs->is_symlink($to) && $fs->readlink($to) eq $from ) {
102 11         162 Rex::get_current_connection()->{reporter}->report( changed => 0, );
103             }
104             else {
105 3 50       131 $fs->ln( $from, $to ) or die("Can't link $from -> $to");
106             Rex::get_current_connection()->{reporter}
107 3         29 ->report( changed => 1, message => "Symlink created: $from -> $to." );
108             }
109              
110             Rex::get_current_connection()->{reporter}
111 14         163 ->report_resource_end( type => "symlink", name => $to );
112              
113 14         358 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 18777 my @files = @_;
138              
139 7         32 my $f;
140 7 50       44 if ( ref $files[0] eq "ARRAY" ) {
141 0         0 $f = $files[0];
142             }
143             else {
144 7         29 $f = \@files;
145             }
146              
147 7 50       29 if ( scalar @{$f} == 1 ) {
  7         36  
148 7         34 my $file = resolv_path $f->[0];
149 7         146 my $fs = Rex::Interface::Fs->create;
150              
151             Rex::get_current_connection()->{reporter}
152 7         49 ->report_resource_start( type => "unlink", name => $file );
153              
154 7 50 33     55 if ( $fs->is_file($file) || $fs->is_symlink($file) ) {
155 7         75 $fs->unlink($file);
156              
157 7         203 my $tmp_path = Rex::Config->get_tmp_dir;
158 7 50       144 if ( $file !~ m/^\Q$tmp_path\E[\/\\][a-z]+\.tmp$/ ) { # skip tmp rex files
159             Rex::get_current_connection()->{reporter}
160 7         39 ->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         41 ->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 3132 my @dirs = @_;
206              
207 2         10 my $d;
208 2 50       26 if ( ref $dirs[0] eq "ARRAY" ) {
209 0         0 $d = $dirs[0];
210             }
211             else {
212 2         20 $d = \@dirs;
213             }
214              
215 2 50       10 if ( scalar @{$d} == 1 ) {
  2         23  
216 2         24 my $dir = resolv_path $d->[0];
217 2         27 my $fs = Rex::Interface::Fs->create;
218              
219             Rex::get_current_connection()->{reporter}
220 2         20 ->report_resource_start( type => "rmdir", name => $dir );
221              
222 2 50 33     37 if ( !$fs->is_dir($dir) && $dir !~ m/[\*\[]/ ) {
223 0         0 Rex::get_current_connection()->{reporter}->report( changed => 0, );
224             }
225             else {
226 2         39 $fs->rmdir($dir);
227 2 50       37 if ( $fs->is_dir($dir) ) {
228 0         0 die "Can't remove $dir.";
229             }
230              
231             Rex::get_current_connection()->{reporter}
232 2         40 ->report( changed => 1, message => "Directory $dir removed." );
233             }
234              
235             Rex::get_current_connection()->{reporter}
236 2         18 ->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 13385 Rex::Logger::debug("Creating directory $_[0]");
286 5         21 my $dir = shift;
287 5         72 $dir = resolv_path($dir);
288              
289 5         31 my $options = {@_};
290              
291 5   100 3   76 $options->{on_change} //= sub { };
292              
293             Rex::get_current_connection()->{reporter}
294 5         41 ->report_resource_start( type => "mkdir", name => $dir );
295              
296 5         99 my $fs = Rex::Interface::Fs->create;
297              
298 5         14 my $not_created = 0;
299 5         26 my %old_stat;
300 5         20 my $changed = 0;
301              
302 5 50       76 if ( $fs->is_dir($dir) ) {
303 0         0 $not_created = 1;
304 0         0 %old_stat = &stat($dir);
305             }
306              
307 5   50     62 my $mode = $options->{"mode"} || 755;
308 5   50     64 my $owner = $options->{"owner"} || "";
309 5   50     58 my $group = $options->{"group"} || "";
310 5   50     64 my $not_recursive = $options->{"not_recursive"} || 0;
311              
312 5 50       31 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       185 if ( !Rex::Helper::File::Spec->file_name_is_absolute($dir) ) {
324 2         53 $dir = Rex::Helper::File::Spec->rel2abs($dir);
325             }
326              
327 5         43 my @directories = __splitdir($dir);
328 5         25 my $path_so_far = shift @directories;
329              
330 5         21 for my $part (@directories) {
331 16         121 $path_so_far = Rex::Helper::File::Spec->join( $path_so_far, $part );
332              
333 16 50 66     88 if ( !is_dir($path_so_far) && !is_file($path_so_far) ) {
334 5 50       38 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       26 &chown( $owner, $path_so_far ) if $owner;
340 5 50       36 &chgrp( $group, $path_so_far ) if $group;
341 5 50       60 &chmod( $mode, $path_so_far ) if $mode;
342             }
343             }
344             }
345              
346 5         156 my %new_stat = &stat($dir);
347              
348 5 50       71 if ( !$not_created ) {
349             Rex::get_current_connection()->{reporter}
350 5         46 ->report( changed => 1, message => "Directory created." );
351 5         29 $changed = 1;
352             }
353              
354 5 50 33     57 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     46 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     54 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       42 if ( $changed == 0 ) {
373 0         0 Rex::get_current_connection()->{reporter}->report( changed => 0, );
374             }
375             else {
376 5         71 $options->{on_change}->($dir);
377             }
378              
379             Rex::get_current_connection()->{reporter}
380 5         48 ->report_resource_end( type => "mkdir", name => $dir );
381              
382 5         128 return 1;
383             }
384              
385             sub __splitdir {
386 6     6   5688 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 26 my ( $mode, $file, @opts ) = @_;
455 5         21 $file = resolv_path($file);
456              
457 5         33 my $fs = Rex::Interface::Fs->create;
458 5 50       49 $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 14 my $path = shift;
587 2         43 $path = resolv_path($path);
588              
589 2         45 my $fs = Rex::Interface::Fs->create;
590 2         29 my @ret = $fs->ls($path);
591              
592 2         29 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 3269 my ($file) = @_;
636 7         129 $file = resolv_path($file);
637 7         39 my %ret;
638              
639 7         153 Rex::Logger::debug("Getting fs stat from $file");
640              
641 7         189 my $fs = Rex::Interface::Fs->create;
642              
643             # may return undef, so capture into a list first.
644 7         90 my @stat = $fs->stat($file);
645 7 100 66     74 die("Can't stat $file") if ( !defined $stat[0] && scalar @stat == 1 );
646              
647 6 50       50 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         94 %ret = @stat;
653              
654 6         95 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 485     485 1 2943 my ($file) = @_;
676 485         2072 $file = resolv_path($file);
677              
678 485         2256 my $fs = Rex::Interface::Fs->create;
679 485         2751 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 1758 my ($path) = @_;
701 271         3966 $path = resolv_path($path);
702              
703 271         9116 my $fs = Rex::Interface::Fs->create;
704 271         2924 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 142 my ($path) = @_;
727 20         189 $path = resolv_path($path);
728              
729 20         480 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 8 my ($file) = @_;
752 2         9 $file = resolv_path($file);
753 2         14 Rex::Logger::debug("Checking if $file is readable");
754              
755 2         16 my $fs = Rex::Interface::Fs->create;
756 2         7 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   9296 my @lines = @_;
894 2         13 chomp @lines;
895              
896 2         5 my $ret = {};
897              
898 2         4 shift @lines;
899 2         6 my $current_fs = "";
900              
901 2         6 for my $line (@lines) {
902 7         43 my ( $fs, $size, $used, $free, $use_per, $mounted_on ) =
903             split( /\s+/, $line, 6 );
904 7 100       21 $current_fs = $fs if $fs;
905              
906 7 100       14 if ( !$size ) {
907 1         5 next;
908             }
909              
910 6         30 $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         11 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;