File Coverage

lib/Rex/Commands/File.pm
Criterion Covered Total %
statement 397 505 78.6
branch 130 214 60.7
condition 81 165 49.0
subroutine 37 39 94.8
pod 12 13 92.3
total 657 936 70.1


line stmt bran cond sub pod time code
1             #
2             # (c) Jan Gehring
3             #
4              
5             =head1 NAME
6              
7             Rex::Commands::File - Transparent File Manipulation
8              
9             =head1 DESCRIPTION
10              
11             With this module you can manipulate files.
12              
13             =head1 SYNOPSIS
14              
15             task "read_passwd", "server01", sub {
16             my $fh = file_read "/etc/passwd";
17             for my $line ($fh->read_all) {
18             print $line;
19             }
20             $fh->close;
21             };
22              
23             task "read_passwd2", "server01", sub {
24             say cat "/etc/passwd";
25             };
26              
27              
28             task "write_passwd", "server01", sub {
29             my $fh = file_write "/etc/passwd";
30             $fh->write("root:*:0:0:root user:/root:/bin/sh\n");
31             $fh->close;
32             };
33              
34             delete_lines_matching "/var/log/auth.log", matching => "root";
35             delete_lines_matching "/var/log/auth.log", matching => qr{Failed};
36             delete_lines_matching "/var/log/auth.log",
37             matching => "root", qr{Failed}, "nobody";
38              
39             file "/path/on/the/remote/machine",
40             source => "/path/on/local/machine";
41              
42             file "/path/on/the/remote/machine",
43             content => "foo bar";
44              
45             file "/path/on/the/remote/machine",
46             source => "/path/on/local/machine",
47             owner => "root",
48             group => "root",
49             mode => 400,
50             on_change => sub { say shift, " was changed."; },
51             on_no_change => sub { say shift, " wasn't changed."; };
52              
53              
54             =head1 EXPORTED FUNCTIONS
55              
56             =cut
57              
58             package Rex::Commands::File;
59              
60 45     45   247306 use v5.12.5;
  45         437  
61 45     45   240 use warnings;
  45         94  
  45         1242  
62 45     45   222 use Fcntl;
  45         115  
  45         11327  
63              
64             our $VERSION = '1.14.3'; # VERSION
65              
66             require Rex::Exporter;
67 45     45   2173 use Data::Dumper;
  45         19846  
  45         2223  
68 45     45   2369 use Rex::Config;
  45         93  
  45         1534  
69 45     45   3259 use Rex::FS::File;
  45         112  
  45         412  
70 45     45   1544 use Rex::Commands::Upload;
  45         106  
  45         318  
71 45     45   277 use Rex::Commands::MD5;
  45         112  
  45         261  
72 45     45   468 use Rex::File::Parser::Data;
  45         122  
  45         564  
73 45     45   1324 use Rex::Helper::File::Spec;
  45         110  
  45         264  
74 45     45   1050 use Rex::Helper::System;
  45         102  
  45         396  
75 45     45   1156 use Rex::Helper::Path;
  45         95  
  45         3340  
76 45     45   250 use Rex::Hook;
  45         93  
  45         1595  
77 45     45   245 use Carp;
  45         88  
  45         2180  
78              
79 45     45   263 use Rex::Interface::Exec;
  45         121  
  45         511  
80 45     45   1056 use Rex::Interface::File;
  45         90  
  45         321  
81 45     45   1187 use Rex::Interface::Fs;
  45         93  
  45         258  
82             require Rex::CMDB;
83              
84 45     45   1918 use File::Basename qw(dirname basename);
  45         104  
  45         2395  
85              
86 45     45   264 use vars qw(@EXPORT);
  45         98  
  45         2009  
87 45     45   272 use base qw(Rex::Exporter);
  45         104  
  45         4595  
88              
89             @EXPORT = qw(file_write file_read file_append
90             cat sed
91             delete_lines_matching append_if_no_such_line delete_lines_according_to
92             file template append_or_amend_line
93             extract);
94              
95 45     45   328 use vars qw(%file_handles);
  45         114  
  45         174382  
96              
97             =head2 template($file [, %params])
98              
99             Parse a template and return the content.
100              
101             By default, it uses L. If any of the L or L<1.3|Rex#1.3> (or newer) feature flag is enabled, then L is used instead of this module (recommended).
102              
103             For more advanced functionality, you may use your favorite template engine via the L configuration option.
104              
105             Template variables may be passed either as hash or a hash reference. The following calls are equivalent:
106              
107             template( $template, variable => value );
108              
109             template( $template, { variable => value } );
110              
111             =head3 List of exposed template variables
112              
113             The following template variables are passed to the underlying templating engine, in order of precedence from low to high (variables of the same name are overridden by the next level aka "last one wins"):
114              
115             =over 4
116              
117             =item task parameters
118              
119             All task parameters coming from the command line via C>, or from calling a task as a function, like C value } )>>.
120              
121             =item resource parameters
122              
123             All resource parameters as returned by Cget_current_resource()-Eget_all_parameters>, when called inside a resource.
124              
125             =item explicit template variables
126              
127             All manually specified, explicit template variables passed to C.
128              
129             =item system information
130              
131             The results from all available L modules as returned by Cget('All')>.
132              
133             Pass C<__no_sys_info__ =E TRUE> as a template variable to disable including system information:
134              
135             my $content = template( $template, __no_sys_info__ => TRUE );
136              
137             =back
138              
139             =head3 Embedded templates
140              
141             Use C<__DATA__> to embed templates at the end of the file. Prefix embedded template names with C<@>. If embedding multiple templates, mark their end with C<@end>.
142              
143             =head4 Single template
144              
145             my $content = template( '@hello', name => 'world' ); # Hello, world!
146             __DATA__
147             @hello
148             Hello, <%= $name -%>!
149              
150             =head4 Multiple templates
151              
152             Use C<@end> to separate multiple templates inside C<__DATA__>.
153              
154             my $content = template( '@hello', name => 'world' ); # Hello, world!
155             my $alternative = template( '@hi', name => 'world' ); # Hi, world!
156              
157             __DATA__
158             @hello
159             Hello, <%= $name -%>!
160             @end
161              
162             @hi
163             Hi, <%= $name -%>!
164             @end
165              
166             =head3 File templates
167              
168             my $content = template("/files/templates/vhosts.tpl",
169             name => "test.lan",
170             webmaster => 'webmaster@test.lan');
171              
172             The file name specified is subject to "path_map" processing as documented
173             under the file() function to resolve to a physical file name.
174              
175             In addition to the "path_map" processing, if the B<-E> command line switch
176             is used to specify an environment name, existence of a file ending with
177             '.' is checked and has precedence over the file without one, if it
178             exists. E.g. if rex is started as:
179              
180             $ rex -E prod task1
181              
182             then in task1 defined as:
183              
184             task "task1", sub {
185             say template("files/etc/ntpd.conf");
186             };
187              
188             will print the content of 'files/etc/ntpd.conf.prod' if it exists.
189              
190             Note: the appended environment mechanism is always applied, after
191             the 'path_map' mechanism, if that is configured.
192              
193             =cut
194              
195             sub template {
196 19     19 1 7472 my ( $file, @params ) = @_;
197 19         47 my $param;
198              
199 19 100       108 if ( ref $params[0] eq "HASH" ) {
200 3         12 $param = $params[0];
201             }
202             else {
203 16         61 $param = {@params};
204             }
205              
206 19 50       111 if ( !exists $param->{server} ) {
207 19         146 $param->{server} = Rex::Commands::connection()->server;
208             }
209              
210 19         51 my $content;
211 19 100 66     236 if ( ref $file && ref $file eq 'SCALAR' ) {
212 16         42 $content = ${$file};
  16         90  
213             }
214             else {
215 3         11 $file = resolv_path($file);
216              
217 3 100 66     19 unless ( $file =~ m/^\// || $file =~ m/^\@/ ) {
218              
219             # path is relative and no template
220 1         7 Rex::Logger::debug("Relativ path $file");
221              
222 1         7 $file = Rex::Helper::Path::get_file_path( $file, caller() );
223              
224 1         7 Rex::Logger::debug("New filename: $file");
225             }
226              
227             # if there is a file called filename.environment then use this file
228             # ex:
229             # $content = template("files/hosts.tpl");
230             #
231             # rex -E live ...
232             # will first look if files/hosts.tpl.live is available, if not it will
233             # use files/hosts.tpl
234 3 50       19 if ( -f "$file." . Rex::Config->get_environment ) {
235 0         0 $file = "$file." . Rex::Config->get_environment;
236             }
237              
238 3 100       56 if ( -f $file ) {
    50          
239 1         3 $content = eval { local ( @ARGV, $/ ) = ($file); <>; };
  1         7  
  1         127  
240             }
241             elsif ( $file =~ m/^\@/ ) {
242 2         21 my @caller = caller(0);
243              
244 2         11 my $file_path = Rex::get_module_path( $caller[0] );
245              
246 2 50       18 if ( !-f $file_path ) {
247 2         8 my ($mod_name) = ( $caller[0] =~ m/^.*::(.*?)$/ );
248 2 50 33     115 if ( $mod_name && -f "$file_path/$mod_name.pm" ) {
    50          
    50          
    50          
    0          
249 0         0 $file_path = "$file_path/$mod_name.pm";
250             }
251             elsif ( -f "$file_path/__module__.pm" ) {
252 0         0 $file_path = "$file_path/__module__.pm";
253             }
254             elsif ( -f "$file_path/Module.pm" ) {
255 0         0 $file_path = "$file_path/Module.pm";
256             }
257             elsif ( -f $caller[1] ) {
258 2         8 $file_path = $caller[1];
259             }
260             elsif ( $caller[1] =~ m|^/loader/[^/]+/__Rexfile__.pm$| ) {
261 0         0 $file_path = $INC{"__Rexfile__.pm"};
262             }
263             }
264              
265 2         4 my $file_content = eval { local ( @ARGV, $/ ) = ($file_path); <>; };
  2         13  
  2         142  
266 2         20 my ($data) = ( $file_content =~ m/.*__DATA__(.*)/ms );
267 2         24 my $fp = Rex::File::Parser::Data->new( data => [ split( /\n/, $data ) ] );
268 2         6 my $snippet_to_read = substr( $file, 1 );
269 2         7 $content = $fp->read($snippet_to_read);
270             }
271             else {
272 0         0 die("$file not found");
273             }
274             }
275              
276 19         52 my %template_vars;
277 19 100       89 if ( !exists $param->{__no_sys_info__} ) {
278 13         47 %template_vars = _get_std_template_vars($param);
279             }
280             else {
281 6         30 delete $param->{__no_sys_info__};
282 6         19 %template_vars = %{$param};
  6         33  
283             }
284              
285             # configuration variables
286 19         294 my $config_values = Rex::Config->get_all;
287 19         81 for my $key ( keys %{$config_values} ) {
  19         224  
288 26 100       116 if ( !exists $template_vars{$key} ) {
289 25         183 $template_vars{$key} = $config_values->{$key};
290             }
291             }
292              
293 19 100 66     234 if ( Rex::CMDB::cmdb_active() && Rex::Config->get_register_cmdb_template ) {
294 4         22 my $data = Rex::CMDB::cmdb();
295 4         25 for my $key ( keys %{ $data->{value} } ) {
  4         38  
296 28 100       70 if ( !exists $template_vars{$key} ) {
297 26         73 $template_vars{$key} = $data->{value}->{$key};
298             }
299             }
300             }
301              
302 19         292 return Rex::Config->get_template_function()->( $content, \%template_vars );
303             }
304              
305             sub _get_std_template_vars {
306 13     13   32 my ($param) = @_;
307              
308 13 50       29 my %merge1 = %{ $param || {} };
  13         125  
309 13         44 my %merge2;
310              
311 13 50       53 if ( Rex::get_cache()->valid("system_information_info") ) {
312 0         0 %merge2 = %{ Rex::get_cache()->get("system_information_info") };
  0         0  
313             }
314             else {
315 13         60 %merge2 = Rex::Helper::System::info();
316 13         137 Rex::get_cache()->set( "system_information_info", \%merge2 );
317             }
318              
319 13         377 my %template_vars = ( %merge1, %merge2 );
320              
321 13         584 return %template_vars;
322             }
323              
324             =head2 file($file_name [, %options])
325              
326             This function is the successor of I. Please use this function to upload files to your server.
327              
328             task "prepare", "server1", "server2", sub {
329             file "/file/on/remote/machine",
330             source => "/file/on/local/machine";
331              
332             file "/etc/hosts",
333             content => template("templates/etc/hosts.tpl"),
334             owner => "user",
335             group => "group",
336             mode => 700,
337             on_change => sub { say "Something was changed." },
338             on_no_change => sub { say "Nothing has changed." };
339              
340             file "/etc/motd",
341             content => `fortune`;
342              
343             file "/etc/named.conf",
344             content => template("templates/etc/named.conf.tpl"),
345             no_overwrite => TRUE; # this file will not be overwritten if already exists.
346              
347             file "/etc/httpd/conf/httpd.conf",
348             source => "/files/etc/httpd/conf/httpd.conf",
349             on_change => sub { service httpd => "restart"; };
350              
351             file "/etc/named.d",
352             ensure => "directory", # this will create a directory
353             owner => "root",
354             group => "root";
355              
356             file "/etc/motd",
357             ensure => "absent"; # this will remove the file or directory
358              
359             };
360              
361             The first parameter is either a string or an array reference. In the latter case the
362             function is called for all strings in the array. Therefore, the following constructs
363             are equivalent:
364              
365             file '/tmp/test1', ensure => 'directory';
366             file '/tmp/test2', ensure => 'directory';
367              
368             file [ qw( /tmp/test1 /tmp/test2 ) ], ensure => 'directory'; # use array ref
369              
370             file [ glob('/tmp/test{1,2}') ], ensure => 'directory'; # explicit glob call for array contents
371              
372             Use the glob carefully as B (e.g. when using wildcards).
373              
374             The I is subject to a path resolution algorithm. This algorithm
375             can be configured using the I function to set the value of the
376             I variable to a hash containing path prefixes as its keys.
377             The associated values are arrays listing the prefix replacements in order
378             of (decreasing) priority.
379              
380             set "path_map", {
381             "files/" => [ "files/{environment}/{hostname}/_root_/",
382             "files/{environment}/_root_/" ]
383             };
384              
385             With this configuration, the file "files/etc/ntpd.conf" will be probed for
386             in the following locations:
387              
388             - files/{environment}/{hostname}/_root_/etc/ntpd.conf
389             - files/{environment}/_root_/etc/ntpd.conf
390             - files/etc/ntpd.conf
391              
392             Furthermore, if a path prefix matches multiple prefix entries in 'path_map',
393             e.g. "files/etc/ntpd.conf" matching both "files/" and "files/etc/", the
394             longer matching prefix(es) have precedence over shorter ones. Note that
395             keys without a trailing slash (i.e. "files/etc") will be treated as having
396             a trailing slash when matching the prefix ("files/etc/").
397              
398             If no file is found using the above procedure and I is relative,
399             it will search from the location of your I or the I<.pm> file if
400             you use Perl packages.
401              
402             All the possible variables ('{environment}', '{hostname}', ...) are documented
403             in the CMDB YAML documentation.
404              
405             =head3 Hooks
406              
407             This function supports the following L:
408              
409             =over 4
410              
411             =item before
412              
413             This gets executed before anything is done. All original parameters are passed to it, including the applied defaults (C 'present'>, resolved path for C).
414              
415             The return value of this hook overwrites the original parameters of the function call.
416              
417             =item before_change
418              
419             This gets executed right before the new file is written. All original parameters are passed to it, including the applied defaults (C 'present'>, resolved path for C).
420              
421             =item after_change
422              
423             This gets executed right after the file is written. All original parameters, including the applied defaults (C 'present'>, resolved path for C), and any returned results are passed to it.
424              
425             =item after
426              
427             This gets executed right before the C function returns. All original parameters, including the applied defaults (C 'present'>, resolved path for C), and any returned results are passed to it.
428              
429             =back
430              
431             =cut
432              
433             sub file {
434 53     53 1 62450 my ( $file, @options ) = @_;
435              
436 53 50       380 if ( ref $file eq "ARRAY" ) {
437 0         0 my @ret;
438              
439             # $file is an array, so iterate over these files
440 0         0 for my $f ( @{$file} ) {
  0         0  
441 0         0 push( @ret, file( $f, @options ) );
442             }
443              
444 0         0 return \@ret;
445             }
446              
447 53         545 my $option = {@options};
448              
449 53         411 $file = resolv_path($file);
450              
451 53         237 my ($is_directory);
452 53 100 100     568 if ( exists $option->{ensure} && $option->{ensure} eq "directory" ) {
453 2         14 $is_directory = 1;
454             }
455              
456 53 100 66     423 if ( exists $option->{source} && !$is_directory ) {
457 3         32 $option->{source} = resolv_path( $option->{source} );
458             }
459              
460             # default: ensure = present
461 53   100     893 $option->{ensure} ||= "present";
462              
463 53         1011 my $fs = Rex::Interface::Fs->create;
464              
465 53 100 100     981 if ( $option->{ensure} ne 'absent' && $fs->is_symlink($file) ) {
466 4         71 my $original_file = $file;
467 4         112 $file = resolve_symlink($file);
468 4         167 Rex::Logger::info(
469             "$original_file is a symlink, operating on $file instead", 'warn' );
470             }
471              
472             #### check and run before hook
473             eval {
474 53         270 my @new_args = Rex::Hook::run_hook( file => "before", $file, %{$option} );
  53         1852  
475 53 100       279 if (@new_args) {
476 3         36 ( $file, @options ) = @new_args;
477 3         23 $option = {@options};
478             }
479 53         262 1;
480 53 50       468 } or do {
481 0         0 die("Before hook failed. Cancelling file() action: $@");
482             };
483             ##############################
484              
485             Rex::get_current_connection()->{reporter}
486 53         215 ->report_resource_start( type => "file", name => $file );
487              
488 53 100 100     555 my $need_md5 = ( $option->{"on_change"} && !$is_directory ? 1 : 0 );
489 53   100 40   1494 my $on_change = $option->{"on_change"} || sub { };
490 53   50 12   891 my $on_no_change = $option->{"on_no_change"} || sub { };
491              
492 53         267 my $__ret = { changed => 0 };
493              
494 53         244 my ( $new_md5, $old_md5 );
495              
496 53 50 33     1004 if ( exists $option->{no_overwrite}
    100 0        
      100        
      66        
497             && $option->{no_overwrite}
498             && $fs->is_file($file) )
499             {
500 0         0 Rex::Logger::debug(
501             "File already exists and no_overwrite option given. Doing nothing.");
502 0         0 $__ret = { changed => 0 };
503              
504             Rex::get_current_connection()->{reporter}->report(
505 0         0 changed => 0,
506             message =>
507             "File already exists and no_overwrite option given. Doing nothing."
508             );
509             }
510              
511             elsif ( ( exists $option->{content} || exists $option->{source} )
512             && !$is_directory )
513             {
514              
515             # first upload file to tmp location, to get md5 sum.
516             # than we can decide if we need to replace the current (old) file.
517              
518 42         582 my $tmp_file_name = get_tmp_file_name($file);
519              
520 42 100       259 if ( exists $option->{content} ) {
    50          
521 39         197 my $fh = file_write($tmp_file_name);
522 39         1317 my @lines = split( qr{$/}, $option->{"content"} );
523 39         366 for my $line (@lines) {
524 240         1259 $fh->write( $line . $/ );
525             }
526 39         337 $fh->close;
527             }
528             elsif ( exists $option->{source} ) {
529             $option->{source} =
530 3         108 Rex::Helper::Path::get_file_path( $option->{source}, caller );
531              
532 3         47 upload $option->{source}, $tmp_file_name;
533             }
534              
535             # now get md5 sums
536 42         187 eval { $old_md5 = md5($file); };
  42         690  
537 42         363 $new_md5 = md5($tmp_file_name);
538              
539 42 100 66     1355 if ( $new_md5 && $old_md5 && $new_md5 eq $old_md5 ) {
      100        
540 4         149 Rex::Logger::debug(
541             "No need to overwrite existing file. Old and new files are the same. $old_md5 eq $new_md5."
542             );
543              
544             # md5 sums are the same, delete tmp.
545 4         128 $fs->unlink($tmp_file_name);
546 4         27 $need_md5 = 0; # we don't need to execute on_change hook
547              
548             Rex::get_current_connection()->{reporter}->report(
549 4         73 changed => 0,
550             message =>
551             "No need to overwrite existing file. Old and new files are the same. $old_md5 eq $new_md5."
552             );
553             }
554             else {
555 38   100     385 $old_md5 ||= "";
556 38         369 Rex::Logger::debug(
557             "Need to use the new file. md5 sums are different. <<$old_md5>> = <<$new_md5>>"
558             );
559              
560             #### check and run before_change hook
561 38         160 Rex::Hook::run_hook( file => "before_change", $file, %{$option} );
  38         1515  
562             ##############################
563              
564 38 50       360 if (Rex::is_sudo) {
565 0         0 my $current_options =
566             Rex::get_current_connection_object()->get_current_sudo_options;
567 0         0 Rex::get_current_connection_object()->push_sudo_options( {} );
568              
569 0 0       0 if ( exists $current_options->{user} ) {
570 0         0 $fs->chown( "$current_options->{user}:", $tmp_file_name );
571             }
572             }
573              
574 38         822 $fs->rename( $tmp_file_name, $file );
575 38 50       539 Rex::get_current_connection_object()->pop_sudo_options()
576             if (Rex::is_sudo);
577              
578 38         987 $__ret = { changed => 1 };
579              
580             Rex::get_current_connection()->{reporter}->report(
581 38         428 changed => 1,
582             message => "File updated. old md5: $old_md5, new md5: $new_md5"
583             );
584              
585             #### check and run after_change hook
586 38         197 Rex::Hook::run_hook( file => "after_change", $file, %{$option}, $__ret );
  38         1349  
587             ##############################
588              
589             }
590              
591             }
592              
593 53 50       488 if ( exists $option->{"ensure"} ) {
594 53 100       361 if ( $option->{ensure} eq "present" ) {
    100          
    50          
595 49 100       571 if ( !$fs->is_file($file) ) {
    100          
596              
597             #### check and run before_change hook
598 1         6 Rex::Hook::run_hook( file => "before_change", $file, %{$option} );
  1         14  
599             ##############################
600              
601 1         20 my $fh = file_write($file);
602 1         10 $fh->write("");
603 1         11 $fh->close;
604 1         4 $__ret = { changed => 1 };
605              
606             Rex::get_current_connection()->{reporter}->report(
607 1         9 changed => 1,
608             message => "file is now present, with no content",
609             );
610              
611             #### check and run after_change hook
612             Rex::Hook::run_hook(
613             file => "after_change",
614 1         3 $file, %{$option}, $__ret
  1         10  
615             );
616             ##############################
617              
618             }
619             elsif ( !$__ret->{changed} ) {
620 10         156 $__ret = { changed => 0 };
621 10         122 Rex::get_current_connection()->{reporter}->report( changed => 0, );
622             }
623             }
624             elsif ( $option->{ensure} eq "absent" ) {
625 2         15 $need_md5 = 0;
626              
627             #### check and run before_change hook
628 2         16 Rex::Hook::run_hook( file => "before_change", $file, %{$option} );
  2         32  
629             ##############################
630              
631 2 50       27 if ( $fs->is_file($file) ) {
    0          
632 2         42 $fs->unlink($file);
633 2         10 $__ret = { changed => 1 };
634             Rex::get_current_connection()->{reporter}->report(
635 2         18 changed => 1,
636             message => "File removed."
637             );
638             }
639             elsif ( $fs->is_dir($file) ) {
640 0         0 $fs->rmdir($file);
641 0         0 $__ret = { changed => 1 };
642             Rex::get_current_connection()->{reporter}->report(
643 0         0 changed => 1,
644             message => "Directory removed.",
645             );
646             }
647             else {
648 0         0 $__ret = { changed => 0 };
649 0         0 Rex::get_current_connection()->{reporter}->report( changed => 0, );
650             }
651              
652             #### check and run after_change hook
653 2         14 Rex::Hook::run_hook( file => "after_change", $file, %{$option}, $__ret );
  2         43  
654             ##############################
655              
656             }
657             elsif ( $option->{ensure} eq "directory" ) {
658 2         62 Rex::Logger::debug("file() should be a directory");
659 2         14 my %dir_option;
660 2 50       21 if ( exists $option->{owner} ) {
661 0         0 $dir_option{owner} = $option->{owner};
662             }
663 2 50       23 if ( exists $option->{group} ) {
664 0         0 $dir_option{group} = $option->{group};
665             }
666 2 50       14 if ( exists $option->{mode} ) {
667 0         0 $dir_option{mode} = $option->{mode};
668             }
669              
670 2         46 Rex::Commands::Fs::mkdir( $file, %dir_option, on_change => $on_change );
671             }
672             }
673              
674 53 100 100     738 if ( !exists $option->{content}
      100        
675             && !exists $option->{source}
676             && $option->{ensure} ne "absent" )
677             {
678              
679             # no content and no source, so just verify that the file is present
680 9 50 66     81 if ( !$fs->is_file($file) && !$is_directory ) {
681              
682             #### check and run before_change hook
683 0         0 Rex::Hook::run_hook( file => "before_change", $file, %{$option} );
  0         0  
684             ##############################
685              
686 0         0 my $fh = file_write($file);
687 0         0 $fh->write("");
688 0         0 $fh->close;
689              
690 0         0 my $f_type = "file is now present, with no content";
691 0 0 0     0 if ( exists $option->{ensure} && $option->{ensure} eq "directory" ) {
692 0         0 $f_type = "directory is now present";
693             }
694              
695             Rex::get_current_connection()->{reporter}->report(
696 0         0 changed => 1,
697             message => $f_type,
698             );
699              
700             #### check and run after_change hook
701 0         0 Rex::Hook::run_hook( file => "after_change", $file, %{$option}, $__ret );
  0         0  
702             ##############################
703              
704             }
705             }
706              
707 53 100       401 if ( $option->{ensure} ne "absent" ) {
708              
709 51 100       204 if ($need_md5) {
710 2         29 eval { $new_md5 = md5($file); };
  2         66  
711             }
712 51         424 my %stat_old = $fs->stat($file);
713              
714 51 100       505 if ( exists $option->{"mode"} ) {
715 28         701 $fs->chmod( $option->{"mode"}, $file );
716             }
717              
718 51 100       769 if ( exists $option->{"group"} ) {
719 25         618 $fs->chgrp( $option->{"group"}, $file );
720             }
721              
722 51 100       682 if ( exists $option->{"owner"} ) {
723 25         546 $fs->chown( $option->{"owner"}, $file );
724             }
725              
726 51         994 my %stat_new = $fs->stat($file);
727              
728 51 100 33     1404 if ( %stat_old && %stat_new && $stat_old{mode} ne $stat_new{mode} ) {
      66        
729             Rex::get_current_connection()->{reporter}->report(
730 15         116 changed => 1,
731             message =>
732             "File-System permissions changed from $stat_old{mode} to $stat_new{mode}.",
733             );
734             }
735              
736 51 50 33     1193 if ( %stat_old && %stat_new && $stat_old{uid} ne $stat_new{uid} ) {
      33        
737             Rex::get_current_connection()->{reporter}->report(
738 0         0 changed => 1,
739             message => "Owner changed from $stat_old{uid} to $stat_new{uid}.",
740             );
741             }
742              
743 51 50 33     1308 if ( %stat_old && %stat_new && $stat_old{gid} ne $stat_new{gid} ) {
      33        
744             Rex::get_current_connection()->{reporter}->report(
745 0         0 changed => 1,
746             message => "Group changed from $stat_old{gid} to $stat_new{gid}.",
747             );
748             }
749              
750             }
751              
752 53         413 my $on_change_done = 0;
753              
754 53 100       260 if ($need_md5) {
755 2 0 33     42 unless ( $old_md5 && $new_md5 && $old_md5 eq $new_md5 ) {
      33        
756 2   50     45 $old_md5 ||= "";
757 2   50     35 $new_md5 ||= "";
758              
759 2         48 Rex::Logger::debug("File $file has been changed... Running on_change");
760 2         22 Rex::Logger::debug("old: $old_md5");
761 2         43 Rex::Logger::debug("new: $new_md5");
762              
763 2         56 &$on_change($file);
764              
765 2         38 $on_change_done = 1;
766              
767             Rex::get_current_connection()->{reporter}->report(
768 2         25 changed => 1,
769             message => "Content changed.",
770             );
771              
772 2         15 $__ret = { changed => 1 };
773             }
774             }
775              
776 53 100 100     607 if ( $__ret->{changed} == 1 && $on_change_done == 0 ) {
    100          
777 39         463 &$on_change($file);
778             }
779             elsif ( $__ret->{changed} == 0 ) {
780 12         432 Rex::Logger::debug(
781             "File $file has not been changed... Running on_no_change");
782 12         158 &$on_no_change($file);
783             }
784              
785             #### check and run after hook
786 53         166 Rex::Hook::run_hook( file => "after", $file, %{$option}, $__ret );
  53         1127  
787             ##############################
788              
789             Rex::get_current_connection()->{reporter}
790 53         264 ->report_resource_end( type => "file", name => $file );
791              
792 53         3305 return $__ret->{changed};
793             }
794              
795             sub get_tmp_file_name {
796 44     44 0 2764 my $file = shift;
797              
798 44         5371 my $dirname = dirname($file);
799 44         1607 my $filename = ".rex.tmp." . basename($file);
800              
801 44 100       1699 my $tmp_file_name =
802             $dirname eq '.'
803             ? $filename
804             : Rex::Helper::File::Spec->catfile( $dirname, $filename );
805              
806 44         232 return $tmp_file_name;
807             }
808              
809             =head2 file_write($file_name)
810              
811             This function opens a file for writing (it will truncate the file if it already exists). It returns a Rex::FS::File object on success.
812              
813             On failure it will die.
814              
815             my $fh;
816             eval {
817             $fh = file_write("/etc/groups");
818             };
819              
820             # catch an error
821             if($@) {
822             print "An error occurred. $@.\n";
823             }
824              
825             # work with the filehandle
826             $fh->write("...");
827             $fh->close;
828              
829             =cut
830              
831             sub file_write {
832 42     42 1 1868 my ($file) = @_;
833 42         686 $file = resolv_path($file);
834              
835 42         859 Rex::Logger::debug("Opening file: $file for writing.");
836              
837 42         971 my $fh = Rex::Interface::File->create;
838 42 50       251 if ( !$fh->open( ">", $file ) ) {
839 0         0 Rex::Logger::debug("Can't open $file for writing.");
840 0         0 die("Can't open $file for writing.");
841             }
842              
843 42         1174 return Rex::FS::File->new( fh => $fh );
844             }
845              
846             =head2 file_append($file_name)
847              
848             =cut
849              
850             sub file_append {
851 1     1 1 2432 my ($file) = @_;
852 1         55 $file = resolv_path($file);
853              
854 1         52 Rex::Logger::debug("Opening file: $file for appending.");
855              
856 1         67 my $fh = Rex::Interface::File->create;
857              
858 1 50       29 if ( !$fh->open( ">>", $file ) ) {
859 0         0 Rex::Logger::debug("Can't open $file for appending.");
860 0         0 die("Can't open $file for appending.");
861             }
862              
863 1         85 return Rex::FS::File->new( fh => $fh );
864             }
865              
866             =head2 file_read($file_name)
867              
868             This function opens a file for reading. It returns a Rex::FS::File object on success.
869              
870             On failure it will die.
871              
872             my $fh;
873             eval {
874             $fh = file_read("/etc/groups");
875             };
876              
877             # catch an error
878             if($@) {
879             print "An error occurred. $@.\n";
880             }
881              
882             # work with the filehandle
883             my $content = $fh->read_all;
884             $fh->close;
885              
886             =cut
887              
888             sub file_read {
889 137     137 1 987 my ($file) = @_;
890 137         1104 $file = resolv_path($file);
891              
892 137         2508 Rex::Logger::debug("Opening file: $file for reading.");
893              
894 137         4071 my $fh = Rex::Interface::File->create;
895              
896 137 50       1019 if ( !$fh->open( "<", $file ) ) {
897 0         0 Rex::Logger::debug("Can't open $file for reading.");
898 0         0 die("Can't open $file for reading.");
899             }
900              
901 137         3337 return Rex::FS::File->new( fh => $fh );
902             }
903              
904             =head2 cat($file_name)
905              
906             This function returns the complete content of $file_name as a string.
907              
908             print cat "/etc/passwd";
909              
910             =cut
911              
912             sub cat {
913 86     86 1 12668 my ($file) = @_;
914 86         1049 $file = resolv_path($file);
915              
916 86         766 my $fh = file_read($file);
917 86 50       320 unless ($fh) {
918 0         0 die("Can't open $file for reading");
919             }
920 86         656 my $content = $fh->read_all;
921 86         568 $fh->close;
922              
923 86         724 return $content;
924             }
925              
926             =head2 delete_lines_matching($file, $regexp)
927              
928             Delete lines that match $regexp in $file.
929              
930             task "clean-logs", sub {
931             delete_lines_matching "/var/log/auth.log" => "root";
932             };
933              
934             =cut
935              
936             sub delete_lines_matching {
937 1     1 1 1696 my ( $file, @m ) = @_;
938 1         34 $file = resolv_path($file);
939              
940             Rex::get_current_connection()->{reporter}
941 1         27 ->report_resource_start( type => "delete_lines_matching", name => $file );
942              
943 1         14 for (@m) {
944 1 50       25 if ( ref($_) ne "Regexp" ) {
945 1         58 $_ = qr{\Q$_\E};
946             }
947             }
948              
949 1         41 my $fs = Rex::Interface::Fs->create;
950              
951 1         26 my %stat = $fs->stat($file);
952              
953 1 50       20 if ( !$fs->is_file($file) ) {
954 0         0 Rex::Logger::info("File: $file not found.");
955 0         0 die("$file not found");
956             }
957              
958 1 50       16 if ( !$fs->is_writable($file) ) {
959 0         0 Rex::Logger::info("File: $file not writable.");
960 0         0 die("$file not writable");
961             }
962              
963 1         15 my $nl = $/;
964 1         40 my @content = split( /$nl/, cat($file) );
965              
966 1         9 my $old_md5 = "";
967 1         7 eval { $old_md5 = md5($file); };
  1         31  
968              
969 1         22 my @new_content;
970              
971             OUT:
972 1         18 for my $line (@content) {
973             IN:
974 1         20 for my $match (@m) {
975 1 50       35 if ( $line =~ $match ) {
976 1         13 next OUT;
977             }
978             }
979              
980 0         0 push @new_content, $line;
981             }
982              
983             file $file,
984             content => join( $nl, @new_content ),
985             owner => $stat{uid},
986             group => $stat{gid},
987 1         49 mode => $stat{mode};
988              
989 1         21 my $new_md5 = "";
990 1         16 eval { $new_md5 = md5($file); };
  1         46  
991              
992 1 50       34 if ( $new_md5 ne $old_md5 ) {
993             Rex::get_current_connection()->{reporter}->report(
994 1         30 changed => 1,
995             message => "Content changed.",
996             );
997             }
998             else {
999 0         0 Rex::get_current_connection()->{reporter}->report( changed => 0, );
1000             }
1001              
1002             Rex::get_current_connection()->{reporter}
1003 1         16 ->report_resource_end( type => "delete_lines_matching", name => $file );
1004             }
1005              
1006             =head2 delete_lines_according_to($search, $file [, @options])
1007              
1008             This is the successor of the delete_lines_matching() function. This function also allows the usage of on_change and on_no_change hooks.
1009              
1010             It will search for $search in $file and remove the found lines. If on_change hook is present it will execute this if the file was changed.
1011              
1012             task "cleanup", "server1", sub {
1013             delete_lines_according_to qr{^foo:}, "/etc/passwd",
1014             on_change => sub {
1015             say "removed user foo.";
1016             };
1017             };
1018              
1019             =cut
1020              
1021             sub delete_lines_according_to {
1022 0     0 1 0 my ( $search, $file, @options ) = @_;
1023 0         0 $file = resolv_path($file);
1024              
1025 0         0 my $option = {@options};
1026 0   0     0 my $on_change = $option->{on_change} || undef;
1027 0   0     0 my $on_no_change = $option->{on_no_change} || undef;
1028              
1029 0         0 my ( $old_md5, $new_md5 );
1030              
1031 0 0       0 if ($on_change) {
1032 0         0 $old_md5 = md5($file);
1033             }
1034              
1035 0         0 delete_lines_matching( $file, $search );
1036              
1037 0 0 0     0 if ( $on_change || $on_no_change ) {
1038 0         0 $new_md5 = md5($file);
1039              
1040 0 0       0 if ( $old_md5 ne $new_md5 ) {
1041 0 0       0 &$on_change($file) if $on_change;
1042             }
1043             else {
1044 0 0       0 &$on_no_change($file) if $on_no_change;
1045             }
1046             }
1047              
1048             }
1049              
1050             =head2 append_if_no_such_line($file, $new_line [, @regexp])
1051              
1052             Append $new_line to $file if none in @regexp is found. If no regexp is
1053             supplied, the line is appended unless there is already an identical line
1054             in $file.
1055              
1056             task "add-group", sub {
1057             append_if_no_such_line "/etc/groups", "mygroup:*:100:myuser1,myuser2", on_change => sub { service sshd => "restart"; };
1058             };
1059              
1060             Since 0.42 you can use named parameters as well
1061              
1062             task "add-group", sub {
1063             append_if_no_such_line "/etc/groups",
1064             line => "mygroup:*:100:myuser1,myuser2",
1065             regexp => qr{^mygroup},
1066             on_change => sub {
1067             say "file was changed, do something.";
1068             };
1069              
1070             append_if_no_such_line "/etc/groups",
1071             line => "mygroup:*:100:myuser1,myuser2",
1072             regexp => [qr{^mygroup:}, qr{^ourgroup:}]; # this is an OR
1073             };
1074              
1075             =cut
1076              
1077             sub append_if_no_such_line {
1078 14     14 1 21294 _append_or_update( 'append_if_no_such_line', @_ );
1079             }
1080              
1081             =head2 append_or_amend_line($file, $line [, @regexp])
1082              
1083             Similar to L, but if the line in the regexp is
1084             found, it will be updated. Otherwise, it will be appended.
1085              
1086             task "update-group", sub {
1087             append_or_amend_line "/etc/groups",
1088             line => "mygroup:*:100:myuser3,myuser4",
1089             regexp => qr{^mygroup},
1090             on_change => sub {
1091             say "file was changed, do something.";
1092             },
1093             on_no_change => sub {
1094             say "file was not changed, do something.";
1095             };
1096             };
1097              
1098             =cut
1099              
1100             sub append_or_amend_line {
1101 5     5 1 9785 _append_or_update( 'append_or_amend_line', @_ );
1102             }
1103              
1104             sub _append_or_update {
1105 19     19   165 my $action = shift;
1106 19         90 my $file = shift;
1107              
1108 19         95 $file = resolv_path($file);
1109 19         108 my ( $new_line, @m );
1110              
1111             # check if parameters are in key => value format
1112 19         0 my ( $option, $on_change, $on_no_change );
1113              
1114             Rex::get_current_connection()->{reporter}
1115 19         70 ->report_resource_start( type => $action, name => $file );
1116              
1117             eval {
1118 45     45   450 no warnings;
  45         107  
  45         71652  
1119 19         155 $option = {@_};
1120              
1121             # if there is no line parameter, it is the old parameter format
1122             # so go dieing
1123 19 100       159 if ( !exists $option->{line} ) {
1124 4         49 die;
1125             }
1126 15         53 $new_line = $option->{line};
1127 15 100 100     307 if ( exists $option->{regexp} && ref $option->{regexp} eq "Regexp" ) {
    100          
1128 10         58 @m = ( $option->{regexp} );
1129             }
1130             elsif ( ref $option->{regexp} eq "ARRAY" ) {
1131 2         15 @m = @{ $option->{regexp} };
  2         9  
1132             }
1133 15   50     171 $on_change = $option->{on_change} || undef;
1134 15   50     104 $on_no_change = $option->{on_no_change} || undef;
1135 15         99 1;
1136 19 100       68 } or do {
1137 4         26 ( $new_line, @m ) = @_;
1138              
1139             # check if something in @m (the regexpes) is named on_change or on_no_change
1140 4         64 for my $option ( [ on_change => \$on_change ],
1141             [ on_no_change => \$on_no_change ] )
1142             {
1143 8         38 for ( my $i = 0 ; $i < $#m ; $i++ ) {
1144 8 100 66     52 if ( $m[$i] eq $option->[0] && ref( $m[ $i + 1 ] ) eq "CODE" ) {
1145 5         13 ${ $option->[1] } = $m[ $i + 1 ];
  5         14  
1146 5         21 splice( @m, $i, 2 );
1147 5         13 last;
1148             }
1149             }
1150             }
1151             };
1152              
1153 19 50       102 unless ( defined $new_line ) {
1154 0         0 my ( undef, undef, undef, $subroutine ) = caller(1);
1155 0         0 $subroutine =~ s/^.*:://;
1156 0         0 die "Undefined new line while trying to run $subroutine on $file";
1157             }
1158              
1159 19         350 my $fs = Rex::Interface::Fs->create;
1160              
1161 19         224 my %stat = $fs->stat($file);
1162              
1163 19         119 my ( $old_md5, $ret );
1164 19         115 $old_md5 = md5($file);
1165              
1166             # slow but secure way
1167 19         174 my $content;
1168             eval {
1169 19         456 $content = [ split( /\n/, cat($file) ) ];
1170 19         275 1;
1171 19 50       171 } or do {
1172 0         0 $ret = 1;
1173             };
1174              
1175 19 100       133 if ( !@m ) {
1176 5         237 push @m, qr{\Q$new_line\E};
1177             }
1178              
1179 19         87 my $found;
1180 19         63 for my $line ( 0 .. $#{$content} ) {
  19         187  
1181 129         251 for my $match (@m) {
1182 144 50       384 if ( ref($match) ne "Regexp" ) {
1183 0         0 $match = qr{$match};
1184             }
1185 144 100       810 if ( $content->[$line] =~ $match ) {
1186 9         70 $found = 1;
1187 9 100       107 last if $action eq 'append_if_no_such_line';
1188 3         57 $content->[$line] = "$new_line";
1189             }
1190             }
1191             }
1192              
1193 19         132 my $new_md5;
1194 19 100 100     306 if ( $action eq 'append_if_no_such_line' && $found ) {
1195 6         31 $new_md5 = $old_md5;
1196             }
1197             else {
1198 13 100       134 push @$content, "$new_line" unless $found;
1199              
1200             file $file,
1201             content => join( "\n", @$content ),
1202             owner => $stat{uid},
1203             group => $stat{gid},
1204 13         293 mode => $stat{mode};
1205 13         422 $new_md5 = md5($file);
1206             }
1207              
1208 19 100 66     552 if ( $on_change || $on_no_change ) {
1209 3 100 33     129 if ( $old_md5 && $new_md5 && $old_md5 ne $new_md5 ) {
    50 66        
1210 1 50       19 if ($on_change) {
1211 1   50     13 $old_md5 ||= "";
1212 1   50     20 $new_md5 ||= "";
1213              
1214 1         28 Rex::Logger::debug("File $file has been changed... Running on_change");
1215 1         31 Rex::Logger::debug("old: $old_md5");
1216 1         19 Rex::Logger::debug("new: $new_md5");
1217 1         20 &$on_change($file);
1218             }
1219             }
1220             elsif ($on_no_change) {
1221 2   50     19 $new_md5 ||= "";
1222              
1223 2         31 Rex::Logger::debug(
1224             "File $file has not been changed (md5 $new_md5)... Running on_no_change"
1225             );
1226 2         29 &$on_no_change($file);
1227             }
1228             }
1229              
1230 19 100 33     473 if ( $old_md5 && $new_md5 && $old_md5 ne $new_md5 ) {
      66        
1231             Rex::get_current_connection()->{reporter}->report(
1232 13         87 changed => 1,
1233             message => "Content changed.",
1234             );
1235             }
1236             else {
1237 6         43 Rex::get_current_connection()->{reporter}->report( changed => 0, );
1238             }
1239              
1240             Rex::get_current_connection()->{reporter}
1241 19         126 ->report_resource_end( type => $action, name => $file );
1242             }
1243              
1244             =head2 extract($file [, %options])
1245              
1246             This function extracts a file. The target directory optionally specified with the `to` option will be created automatically.
1247              
1248             Supported formats are .box, .tar, .tar.gz, .tgz, .tar.Z, .tar.bz2, .tbz2, .zip, .gz, .bz2, .war, .jar.
1249              
1250             task prepare => sub {
1251             extract "/tmp/myfile.tar.gz",
1252             owner => "root",
1253             group => "root",
1254             to => "/etc";
1255              
1256             extract "/tmp/foo.tgz",
1257             type => "tgz",
1258             mode => "g+rwX";
1259             };
1260              
1261             Can use the type=> option if the file suffix has been changed. (types are tar, tgz, tbz, zip, gz, bz2)
1262              
1263             =cut
1264              
1265             sub extract {
1266 0     0 1 0 my ( $file, %option ) = @_;
1267 0         0 $file = resolv_path($file);
1268              
1269 0         0 my $pre_cmd = "";
1270 0         0 my $to = ".";
1271 0         0 my $type = "";
1272              
1273 0 0       0 if ( $option{chdir} ) {
1274 0         0 $to = $option{chdir};
1275             }
1276              
1277 0 0       0 if ( $option{to} ) {
1278 0         0 $to = $option{to};
1279             }
1280 0         0 $to = resolv_path($to);
1281              
1282 0 0       0 if ( $option{type} ) {
1283 0         0 $type = $option{type};
1284             }
1285              
1286 0         0 Rex::Commands::Fs::mkdir($to);
1287 0         0 $pre_cmd = "cd $to; ";
1288              
1289 0         0 my $exec = Rex::Interface::Exec->create;
1290 0         0 my $cmd = "";
1291              
1292 0 0 0     0 if ( $type eq 'tgz'
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
      0        
      0        
      0        
1293             || $file =~ m/\.tar\.gz$/
1294             || $file =~ m/\.tgz$/
1295             || $file =~ m/\.tar\.Z$/ )
1296             {
1297 0         0 $cmd = "${pre_cmd}gunzip -c $file | tar -xf -";
1298             }
1299             elsif ( $type eq 'tbz' || $file =~ m/\.tar\.bz2/ || $file =~ m/\.tbz2/ ) {
1300 0         0 $cmd = "${pre_cmd}bunzip2 -c $file | tar -xf -";
1301             }
1302             elsif ( $type eq 'tar' || $file =~ m/\.(tar|box)/ ) {
1303 0         0 $cmd = "${pre_cmd}tar -xf $file";
1304             }
1305             elsif ( $type eq 'zip' || $file =~ m/\.(zip|war|jar)$/ ) {
1306 0         0 $cmd = "${pre_cmd}unzip -o $file";
1307             }
1308             elsif ( $type eq 'gz' || $file =~ m/\.gz$/ ) {
1309 0         0 $cmd = "${pre_cmd}gunzip -f $file";
1310             }
1311             elsif ( $type eq 'bz2' || $file =~ m/\.bz2$/ ) {
1312 0         0 $cmd = "${pre_cmd}bunzip2 -f $file";
1313             }
1314             else {
1315 0         0 Rex::Logger::info("File not supported.");
1316 0         0 die("File ($file) not supported.");
1317             }
1318              
1319 0         0 $exec->exec($cmd);
1320              
1321 0         0 my $fs = Rex::Interface::Fs->create;
1322 0 0       0 if ( $option{owner} ) {
1323 0         0 $fs->chown( $option{owner}, $to, recursive => 1 );
1324             }
1325              
1326 0 0       0 if ( $option{group} ) {
1327 0         0 $fs->chgrp( $option{group}, $to, recursive => 1 );
1328             }
1329              
1330 0 0       0 if ( $option{mode} ) {
1331 0         0 $fs->chmod( $option{mode}, $to, recursive => 1 );
1332             }
1333              
1334             }
1335              
1336             =head2 sed($search, $replace, $file [, %options])
1337              
1338             Search some string in a file and replace it.
1339              
1340             task sar => sub {
1341             # this will work line by line
1342             sed qr{search}, "replace", "/var/log/auth.log";
1343              
1344             # to use it in a multiline way
1345             sed qr{search}, "replace", "/var/log/auth.log",
1346             multiline => TRUE;
1347             };
1348              
1349             Like similar file management commands, it also supports C and C hooks.
1350              
1351             =cut
1352              
1353             sub sed {
1354 11     11 1 15986 my ( $search, $replace, $file, @option ) = @_;
1355 11         169 $file = resolv_path($file);
1356 11         76 my $options = {};
1357              
1358             Rex::get_current_connection()->{reporter}
1359 11         72 ->report_resource_start( type => "sed", name => $file );
1360              
1361 11 50       93 if ( ref( $option[0] ) ) {
1362 0         0 $options = $option[0];
1363             }
1364             else {
1365 11         60 $options = {@option};
1366             }
1367              
1368 11   50     208 my $on_change = $options->{"on_change"} || undef;
1369 11   50     124 my $on_no_change = $options->{"on_no_change"} || undef;
1370              
1371 11         42 my @content;
1372              
1373 11 100       68 if ( exists $options->{multiline} ) {
1374 1         45 $content[0] = cat($file);
1375 1         31 $content[0] =~ s/$search/$replace/gms;
1376             }
1377             else {
1378 10         70 @content = split( /\n/, cat($file) );
1379 10         67 for (@content) {
1380 109         385 s/$search/$replace/;
1381             }
1382             }
1383              
1384 11         241 my $fs = Rex::Interface::Fs->create;
1385 11         54 my %stat = $fs->stat($file);
1386              
1387             my $ret = file(
1388             $file,
1389             content => join( "\n", @content ),
1390             on_change => $on_change,
1391             on_no_change => $on_no_change,
1392             owner => $stat{uid},
1393             group => $stat{gid},
1394             mode => $stat{mode}
1395 11         199 );
1396              
1397             Rex::get_current_connection()->{reporter}
1398 11         226 ->report_resource_end( type => "sed", name => $file );
1399              
1400 11         627 return $ret;
1401             }
1402              
1403             1;