File Coverage

blib/lib/Sys/Export/Unix.pm
Criterion Covered Total %
statement 532 853 62.3
branch 185 388 47.6
condition 126 328 38.4
subroutine 62 83 74.7
pod 35 35 100.0
total 940 1687 55.7


line stmt bran cond sub pod time code
1             package Sys::Export::Unix;
2              
3             # ABSTRACT: Export subsets of a UNIX system
4             our $VERSION = '0.005_002'; # TRIAL VERSION
5              
6              
7 10     10   946557 use v5.26;
  10         29  
8 10     10   38 use warnings;
  10         13  
  10         436  
9 10     10   44 use experimental qw( signatures );
  10         26  
  10         56  
10 10     10   988 use Carp qw( croak carp );
  10         29  
  10         550  
11 10     10   44 use Cwd qw( abs_path );
  10         12  
  10         390  
12 10     10   58 use Scalar::Util qw( blessed looks_like_number );
  10         22  
  10         362  
13 10     10   34 use List::Util qw( max );
  10         25  
  10         546  
14 10     10   4031 use Sys::Export qw( :isa :stat_modes :stat_tests map_or_load_file );
  10         26  
  10         58  
15 10     10   59 use File::Temp ();
  10         12  
  10         112  
16 10     10   4177 use POSIX ();
  10         54590  
  10         283  
17 10     10   69 use Sys::Export::LogAny;
  10         12  
  10         68  
18             require Sys::Export::LazyFileData;
19             require Sys::Export::Exporter;
20             our @CARP_NOT= qw( Sys::Export );
21             our @ISA= qw( Sys::Export::Exporter );
22             our $have_unix_mknod= !!eval { require Unix::Mknod; };
23              
24             sub new {
25 13     13 1 1449683 my $class= shift;
26 13 50 33     159 my %attrs= @_ == 1 && isa_hash $_[0]? %{$_[0]}
  0 50       0  
27             : !(@_ & 1)? @_
28             : croak "Expected hashref or even-length list";
29              
30 13 50       128 defined $attrs{src} or croak "Require 'src' attribute";
31             # must end with trailing '/'
32 13 100       122 $attrs{src} .= '/' unless $attrs{src} =~ m{/\z};
33             my $src_abs= abs_path($attrs{src})
34 13 50       341 or croak "src directory '$attrs{src}' does not exist";
35 13 100       86 $src_abs .= '/' unless $src_abs =~ m{/\z};
36 13         36 $attrs{src_abs}= $src_abs;
37              
38 13 50       35 defined $attrs{dst} or croak "Require 'dst' attribute";
39 13 100       65 if (isa_export_dst $attrs{dst}) {
    50          
40 6         14 $attrs{_dst}= $attrs{dst};
41             } elsif (isa_array $attrs{dst}) {
42 0         0 my @spec= @{$attrs{dst}};
  0         0  
43 0         0 my $type= shift @spec;
44 0 0       0 if (uc $type eq 'CPIO') {
45 0         0 require Sys::Export::CPIO;
46 0         0 $attrs{_dst}= Sys::Export::CPIO->new(@spec);
47             } else {
48 0         0 croak "Unknown -dst type '$type'";
49             }
50             } else {
51 7 50       39 my $dst_abs= abs_path($attrs{dst} =~ s,(?<=[^/])$,/,r)
52             or croak "dst directory '$attrs{dst}' does not exist";
53 7 50       204 length $dst_abs > 1
54             or croak "cowardly refusing to export to '$dst_abs'";
55 7         3108 require Sys::Export::Unix::WriteFS;
56             $attrs{_dst}= Sys::Export::Unix::WriteFS->new(
57             dst => $attrs{dst},
58             tmp => $attrs{tmp},
59             on_collision => $attrs{on_collision},
60 7         65 );
61             }
62             # default tmp dir to whatever dst chose, if it has a preference
63             $attrs{tmp} //= $attrs{_dst}->tmp
64 13 100 33     147 if $attrs{_dst}->can('tmp');
65             # otherwise use system tmp dir
66 13   66     65 $attrs{tmp} //= File::Temp->newdir;
67 13   33     2429 $attrs{log} //= Sys::Export::LogAny->get_logger;
68              
69             # Upgrade src_userdb and dst_userdb if provided as hashrefs
70 13         1604 for (qw( src_userdb dst_userdb )) {
71 26 100 66     87 if (defined $attrs{$_} && !isa_userdb($attrs{$_})) {
72 1         698 require Sys::Export::Unix::UserDB;
73 1         9 $attrs{$_}= Sys::Export::Unix::UserDB->new($attrs{$_});
74             }
75             }
76              
77 13         42 my $self= bless \%attrs, $class;
78              
79             # Run the accessor logic to coerce the parameter properly
80 13         39 for my $method (qw( src_exe_path src_lib_path log )) {
81 39 100       173 $self->$method(delete $self->{$method}) if exists $self->{$method};
82             }
83             # Special cases - call the method once for each key/value pair
84 13         24 for my $method (qw( rewrite_path rewrite_user rewrite_group )) {
85 39 50       83 my $r= delete $self->{$method}
86             or next;
87             $self->$method($_ => $r->{$_})
88 0         0 for keys %$r;
89             }
90 13         42 return $self;
91             }
92              
93              
94 7     7 1 695 sub src($self) { $self->{src} }
  7         13  
  7         7  
  7         64  
95 61     61 1 59 sub src_abs($self) { $self->{src_abs} }
  61         64  
  61         60  
  61         720  
96 4     4 1 17 sub dst($self) { $self->{dst} } # sometimes a path string
  4         4  
  4         5  
  4         36  
97 51     51   59 sub _dst($self) { $self->{_dst} } # always an object
  51         52  
  51         66  
  51         180  
98 23 50   23 1 15519 sub dst_abs($self) { $self->{_dst}->can('dst_abs')? $self->{_dst}->dst_abs : undef }
  23         28  
  23         25  
  23         129  
99 0     0 1 0 sub tmp($self) { $self->{tmp} }
  0         0  
  0         0  
  0         0  
100 0   0 0 1 0 sub src_path_set($self) { $self->{src_path_set} //= {} }
  0         0  
  0         0  
  0         0  
101 3   50 3 1 1418 sub dst_path_set($self) { $self->{dst_path_set} //= {} }
  3         4  
  3         3  
  3         14  
102 1   50 1 1 1 sub dst_uid_used($self) { $self->{dst_uid_used} //= {} }
  1         2  
  1         1  
  1         4  
103 1   50 1 1 2 sub dst_gid_used($self) { $self->{dst_gid_used} //= {} }
  1         1  
  1         1  
  1         7  
104 0     0 1 0 sub src_userdb($self) { $self->{src_userdb} }
  0         0  
  0         0  
  0         0  
105 1     1 1 1 sub dst_userdb($self) { $self->{dst_userdb} }
  1         2  
  1         1  
  1         13  
106             sub log {
107 144 100   144 1 278 if (@_ > 1) {
108 13 50 33     107 croak "Expected Log::Any-compatible object" unless blessed($_[1]) && $_[1]->can('infof');
109 13         39 $_[0]{log}= $_[1];
110             }
111 144         482 $_[0]{log};
112             }
113              
114 8     8   9 sub _distinct_abs_directories($self, $warn, @list) {
  8         36  
  8         16  
  8         15  
  8         9  
115 8         12 for (@list) {
116 48         111 my $abs= $self->_src_abs_path($_); # resolve symlinks
117 48 100 66     103 if (defined $abs && -d $self->src_abs . $abs) {
118 25         42 $_= $abs;
119             } else {
120 23 100       48 $self->log->warn("No such directory $_") if $warn;
121 23         374 $_= undef;
122             }
123             }
124 8         10 my %seen;
125 8   100     112 return [ grep defined && !$seen{$_}++, @list ]
126             }
127              
128 2     2 1 4 sub src_exe_path($self, @value) {
  2         3  
  2         3  
  2         2  
129             $self->src_exe_path_list(
130 2 0       5 ref $value[0] eq 'ARRAY'? @{ $value[0] }
  0 50       0  
131             : map split(/:/, $_, -1), @value
132             ) if @value;
133 2         6 return join ':', map "/$_", $self->src_exe_path_list;
134             }
135              
136 4     4 1 5 sub src_exe_path_list($self, @value) {
  4         4  
  4         4  
  4         6  
137 4 50       6 if (@value) {
138 0         0 $self->{src_exe_path}= $self->_distinct_abs_directories(1, @value);
139             }
140 4   66     4 return @{ $self->{src_exe_path} //= $self->_build_src_exe_path };
  4         17  
141             }
142              
143 2     2   3 sub _build_src_exe_path($self) {
  2         2  
  2         3  
144 2         6 my @exe_path= qw( usr/local/sbin usr/local/bin usr/sbin usr/bin sbin bin );
145             push @exe_path, grep s{^/}{}, split /:/, $ENV{PATH}
146 2 100       4 if $self->src_abs eq '/';
147 2         5 return $self->_distinct_abs_directories(0, @exe_path);
148             }
149              
150             # back-compat
151             *src_exe_PATH= *src_exe_path;
152             *src_exe_PATH_list= *src_exe_path_list;
153              
154 0     0 1 0 sub src_lib_path($self, @value) {
  0         0  
  0         0  
  0         0  
155             $self->src_lib_path_list(
156 0 0       0 ref $value[0] eq 'ARRAY'? @{ $value[0] }
  0 0       0  
157             : map split(/:/, $_, -1), @value
158             ) if @value;
159 0         0 return join ':', map "/$_", $self->src_lib_path_list;
160             }
161              
162 3     3 1 6 sub src_lib_path_list($self, @value) {
  3         5  
  3         3  
  3         3  
163 3 50       7 if (@value) {
164 0         0 $self->{src_lib_path}= $self->_distinct_abs_directories(1, @value);
165             }
166 3   33     3 return @{ $self->{src_lib_path} //= $self->_build_src_lib_path };
  3         19  
167             }
168              
169 3     3   39 sub _build_src_lib_path($self) {
  3         3  
  3         3  
170 3         10 return $self->_distinct_abs_directories(0,
171             qw( usr/local/lib64 usr/local/lib usr/lib64 usr/lib lib64 lib )
172             );
173             }
174              
175             #=attribute _can_run_in_src
176             #
177             #This is a boolean that indicates whether executables in the source directory can be executed
178             #on this host. This is always true if "src" is '/', since perl wouldn't be able to run in this
179             #environment to run Sys::Export if that weren't true. If src is any other path, this module
180             #needs 'chroot' permission, and tests using C<< chroot $srcdir /bin/sh -c 'exit 0' >>.
181             #
182             #=cut
183              
184 1     1   1 sub _can_run_in_src($self) {
  1         1  
  1         2  
185 1   33     7 $self->{can_run_in_src} //= ($self->src_abs eq '/' or eval { $self->_run_in_src('sh','-c','exit 0'); 1 });
      33        
186             }
187 0     0   0 sub _run_in_src($self, $cmd, @args) {
  0         0  
  0         0  
  0         0  
  0         0  
188 0         0 my $src_abs= $self->src_abs;
189 0 0 0     0 if ($cmd !~ m,/, && !-x $src_abs . $cmd) { # not an absolute path
190 0   0     0 my $path= $self->src_which($cmd)
191             // croak "Can't locate '$cmd' under $src_abs in PATH=".$self->src_exe_PATH;
192 0         0 $cmd= $path;
193             }
194 0   0     0 pipe(my $err_r, my $err_w) // croak "pipe: $!";
195 0   0     0 my $pid= fork() // croak "fork: $!";
196 0 0       0 if (!$pid) {
197 0         0 eval {
198 0 0       0 if ($src_abs ne '/') {
199 0   0     0 chdir $src_abs // die "chdir($src_abs): $!";
200 0   0     0 chroot $src_abs // die "chroot($src_abs): $!";
201             }
202 0   0     0 exec($cmd, @args) // die "exec: $!";
203             };
204 0         0 $err_w->print($@);
205 0         0 $err_w->close;
206 0         0 POSIX::_exit(1);
207             }
208             else {
209 0         0 local $/;
210 0         0 my $err= <$err_r>;
211 0 0       0 if (length $err) {
212 0         0 waitpid($pid, 0);
213 0         0 die $err;
214             }
215             # else command is running
216 0         0 waitpid($pid, 0);
217 0         0 return $?;
218             }
219             }
220              
221              
222 24     24 1 27 sub path_rewrite_regex($self) {
  24         26  
  24         30  
223 24   66     49 $self->{path_rewrite_regex} //= do {
224 3   100     11 my $rw= $self->{path_rewrite_map} // {};
225 3 100       19 !keys %$rw? qr/(*FAIL)/
226 1         53 : qr/(@{[ join '|', map quotemeta, reverse sort keys %$rw ]})/;
227             };
228             }
229              
230             # a hashref tracking files with link-count higher than 1, so that hardlinks can be preserved.
231             # the keys are "$dev:$ino"
232 2   100 2   1 sub _link_map($self) { $self->{link_map} //= {} }
  2         3  
  2         3  
  2         12  
233              
234             # a hashref listing all the interpreters that have been discovered for programs
235             # and scripts copied to dst. The keys are the relative source path.
236 0   0 0   0 sub _elf_interpreters($self) { $self->{elf_interpreters} //= {} }
  0         0  
  0         0  
  0         0  
237              
238             # Can we use strace (or similar) on binaries in src to see all files they touch?
239 1     1   9 sub _can_trace_deps($self) {
  1         1  
  1         2  
240 1   33     5 $self->{_can_trace_deps} //= do {
241 1 50 33     1 eval { $self->{_trace_deps} //= $self->_build__trace_deps }
  1         21  
242             or $self->log->debug("Error building _trace_deps function: $@");
243 1         68 !!$self->{_trace_deps};
244             };
245             }
246              
247 0     0   0 sub _trace_deps($self, @argv) {
  0         0  
  0         0  
  0         0  
248 0   0     0 ($self->{_trace_deps} //= $self->_build__trace_deps)->($self, @argv);
249             }
250              
251             sub _build__trace_deps {
252             # ::Linux subclass overrides this, for strace support.
253 0     0   0 croak "No options available for tracing runtime dependencies";
254             }
255              
256 12     12   25739 sub DESTROY($self, @) {
  12         22  
  12         13  
257 12 50       253 $self->finish if $self->{_delayed_apply_stat};
258             }
259              
260              
261 0     0 1 0 sub on_collision($self, @value) {
  0         0  
  0         0  
  0         0  
262 0 0       0 $self->{on_collision}= $value[0] if @value;
263             $self->{on_collision}
264 0         0 }
265              
266 31     31   35 sub _log_action($self, $verb, $src, $dst, @notes) {
  31         31  
  31         35  
  31         32  
  31         33  
  31         31  
  31         43  
267 31 50       50 if ($self->log->is_info) {
268             # Track the width of the previous 10 filenames to provide easy-to-read consistent indenting
269 0   0     0 my $widths= ($self->{_log_name_widths} //= []);
270 0         0 unshift @$widths, length($src);
271 0 0       0 pop @$widths if @$widths > 10;
272 0         0 my $width= max(24, @$widths);
273             # Then round width to a multiple of 8
274 0         0 $width= ($width + 7) & ~7;
275 0         0 $self->log->infof("%3s %-*s -> %s", $verb, $width, $src, $dst);
276 0         0 $self->log->infof(" %s", $_) for @notes;
277             }
278             }
279              
280              
281 2     2 1 5229 sub rewrite_path($self, $orig, $new) {
  2         2  
  2         3  
  2         2  
  2         3  
282 2   100     8 my $rw= ($self->{path_rewrite_map} //= {});
283 2         5 $orig =~ s,^/,,;
284 2         5 $new =~ s,^/,,;
285 2 50 33     8 $orig !~ m,^[.]+/, && $new !~ m,^[.]+/,
286             or croak "Paths for rewrite_path must be logically absolute ($orig => $new)";
287             croak "Conflicting rewrite supplied for '$orig'"
288 2 50 33     5 if exists $rw->{$orig} && $rw->{$orig} ne $new;
289 2         4 $rw->{$orig}= $new;
290 2         4 delete $self->{path_rewrite_regex}; # lazy-built
291 2         4 $self;
292             }
293              
294 16     16   18 sub _has_rewrites($self) {
  16         15  
  16         14  
295 16 100       48 $self->{path_rewrite_map} && %{$self->{path_rewrite_map}}
  9         37  
296             }
297              
298             # Resolve symlinks in paths within $root/ treating absolute links as references to $root.
299             # This returns undef if:
300             # * the path doesn't exist at any point during resolution
301             # * 'stat' fails at any point in the path (maybe for permissions)
302             # * it resolves more than 256 symlinks
303             # * readlink fails
304             # Un-intuitively, this returns a string without a leading '/' because that's what I need below.
305 74     74   77 sub _chroot_abs_path($self, $root, $path) {
  74         70  
  74         75  
  74         67  
  74         110  
306 74 100       177 my @base= $root eq '/'? ('') : split '/', $root;
307 74         138 my @abs= @base;
308 74   100     352 my @parts= grep length && $_ ne '.', split '/', $path;
309 74         115 my $lim= 256;
310 74         100 while (@parts) {
311 185         216 my $part= shift @parts;
312 185         281 my $abs= join '/', @abs, $part;
313 185 100       1974 my (undef, undef, $mode)= lstat $abs
314             or return undef;
315 162 100       366 if ($part eq '..') {
    100          
316             # In Linux at least, ".." from root directory loops back to itself
317 2 50       6 pop @abs if @abs > @base;
318             }
319             elsif (S_ISLNK($mode)) {
320 15 50       26 return undef if --$lim <= 0;
321 15 50       288 defined (my $newpath= readlink $abs) or return undef;
322 15 100       118 @abs= @base if $newpath =~ m,^/,;
323 15   66     85 unshift @parts, grep length && $_ ne '.', split '/', $newpath;
324             }
325             else {
326 145         262 push @abs, $part;
327             }
328             }
329 51         107 my $abs= join '/', @abs[scalar @base .. $#abs];
330 51 100       134 $self->log->tracef("Absolute path of '%s' within root '%s' is '%s'", $path, $root, $abs)
331             if $abs ne $path;
332 51         306 return $abs;
333             }
334              
335 74     74   1447 sub _src_abs_path($self, $path) {
  74         81  
  74         80  
  74         64  
336 74         129 $self->_chroot_abs_path($self->{src_abs}, $path);
337             }
338              
339             # Like src_abs_path, this performs a logical abs_path on all path components *except* the final
340             # one, so the parent directories will be resolved to an absolute path, but the leaf directory
341             # entry is not resolved and doesn't even need to exist.
342 26     26   29 sub _src_parent_abs_path($self, $path) {
  26         30  
  26         32  
  26         25  
343             # Determine the final path component, ignoring '.'
344 26   100     195 my @path= grep length && $_ ne '.', split '/', $path;
345 26 100 50     79 return $path[0] // '' unless @path > 1;
346 17         68 my $parent= $self->_src_abs_path(join '/', @path[0 .. $#path - 1]);
347 17 50       67 return defined $parent? "$parent/$path[-1]" : undef;
348             }
349              
350              
351 0     0 1 0 sub rewrite_user($self, $src, $dst) {
  0         0  
  0         0  
  0         0  
  0         0  
352             croak "A rewrite already exists for $src"
353 0 0       0 if $self->{_user_rewrite_map}{$src};
354              
355 0 0       0 if (!isa_int($dst)) {
356 0   0     0 my $dst_userdb= ($self->{dst_userdb} //= $self->_build_dst_userdb);
357 0 0       0 my $u= $dst_userdb->user($dst)
358             or croak "No user '$dst' in dst_userdb";
359 0         0 $dst= $u->uid;
360             }
361 0 0       0 if (!isa_int($src)) {
362             # The name must exist in src userdb
363 0   0     0 my $src_userdb= ($self->{src_userdb} //= $self->_build_src_userdb);
364 0 0       0 my $u= $src_userdb->user($src)
365             or croak "No user '$src' in src_userdb";
366 0         0 $self->{_user_rewrite_map}{$src}= $dst;
367 0         0 $src= $u->uid;
368             }
369 0         0 $self->{_user_rewrite_map}{$src}= $dst;
370             }
371              
372 0     0 1 0 sub rewrite_group($self, $src, $dst) {
  0         0  
  0         0  
  0         0  
  0         0  
373             croak "A rewrite already exists for $src"
374 0 0       0 if $self->{_group_rewrite_map}{$src};
375              
376 0 0       0 if (!isa_int($dst)) {
377 0   0     0 my $dst_userdb= ($self->{dst_userdb} //= $self->_build_dst_userdb);
378 0 0       0 my $g= $dst_userdb->group($dst)
379             or croak "No group '$dst' in dst_userdb";
380 0         0 $dst= $g->gid;
381             }
382 0 0       0 if (!isa_int($src)) {
383             # The name must exist in src userdb
384 0   0     0 my $src_userdb= ($self->{src_userdb} //= $self->_build_src_userdb);
385 0 0       0 my $g= $src_userdb->group($src)
386             or croak "No group '$src' in src_userdb";
387 0         0 $self->{_group_rewrite_map}{$src}= $dst;
388 0         0 $src= $g->gid;
389             }
390 0         0 $self->{_group_rewrite_map}{$src}= $dst;
391             }
392              
393 0     0   0 sub _build_src_userdb($self) {
  0         0  
  0         0  
394             # The default source UserDB pulls from src/etc/passwd and auto_imports users from the host
395 0         0 require Sys::Export::Unix::UserDB;
396 0         0 my $udb= Sys::Export::Unix::UserDB->new(auto_import => 1);
397 0 0       0 $udb->load($self->src_abs . 'etc')
398             if -f $self->src_abs . 'etc/passwd';
399 0         0 $udb;
400             }
401              
402 1     1   1 sub _build_dst_userdb($self) {
  1         2  
  1         1  
403             # The default dest UserDB uses any dst/etc/passwd and auto_imports users from src_userdb
404 1         5 require Sys::Export::Unix::UserDB;
405             my $udb= Sys::Export::Unix::UserDB->new(
406 1   33     15 auto_import => ($self->{src_userdb} //= $self->_build_src_userdb)
407             );
408 1 50 33     3 $udb->load($self->_dst->dst_abs . 'etc')
409             if defined $self->_dst->can('dst_abs') && -f $self->_dst->dst_abs . 'etc/passwd';
410             # make sure the rewrite hashes exist, used as a flag that rerites need to occur.
411 1   50     25 $self->{_user_rewrite_map} //= {};
412 1   50     5 $self->{_group_rewrite_map} //= {};
413 1         13 $udb;
414             }
415              
416              
417             sub add {
418 25     25 1 15299 my $self= shift;
419             # If called recursively, append to TODO list instead of immediately adding
420 25 100       65 if (ref $self->{add}) {
421 5         7 push @{ $self->{add} }, @_;
  5         11  
422 5         9 return $self;
423             }
424 20         47 my @add= @_;
425 20         67 local $self->{add}= \@add;
426 20         27 my $dst_userdb;
427 20         52 while (@add) {
428 43         173 my $next= shift @add;
429 43         51 my %file;
430 43 100       133 if (isa_hash $next) {
    100          
431 13         90 %file= %$next;
432             } elsif (isa_array $next) {
433 9         27 %file= Sys::Export::expand_stat_shorthand(@$next);
434             } else {
435 21         52 %file= ( src_path => $next );
436             }
437 43 0       113 $self->log->debug("Exporting".(defined $file{src_path}? " $file{src_path}" : '').(defined $file{name}? " to $file{name}":''))
    0          
    50          
438             if $self->log->is_debug;
439             # Need to abs-path the parent dir of this path in case src_path follows
440             # symlinks through absolute paths, e.g. "/usr/bin/mount", if /usr/bin is a symlink to
441             # "/bin" rather than "../bin" it will fail whenever ->src is not pointed to '/'.
442             $file{real_src_path} //= $self->_src_parent_abs_path($file{src_path})
443 43 100 66     500 if defined $file{src_path};
444             # Translate src to dst if user didn't supply a 'name'
445 43 100 66     112 if (!defined $file{name} || !defined $file{mode}) {
446 21         65 my $src_path= $file{src_path};
447 21 0       44 defined $src_path or croak(!defined $file{mode}? "Require src_path to determine 'mode'" : "Require 'name' (or 'src_path' to derive name)");
    50          
448             # ignore repeat requests
449 21 100 66     59 if (exists $self->{src_path_set}{$src_path} && !defined $file{name}) {
450 1         8 $self->log->debugf(" (already exported '%s')", $src_path);
451 1         8 next;
452             }
453 20         59 my $real_src_path= $file{real_src_path};
454 20 50       53 if (!defined $real_src_path) {
    100          
455 0         0 croak "No such path $src_path";
456             } elsif ($real_src_path ne $src_path) {
457 6         10 $self->log->debugf("Resolved to '%s'", $real_src_path);
458             # ignore repeat requests
459 6 100       47 if (exists $self->{src_path_set}{$real_src_path}) {
460 2         7 $self->{src_path_set}{$src_path}= $self->{src_path_set}{$real_src_path};
461 2         4 $self->log->debugf(" (already exported '%s')", $real_src_path);
462 2         13 next;
463             }
464             }
465             # If mode wasn't supplied, get it from src filesystem
466 18 50       46 if (!defined $file{mode}) {
467 18         25 my %stat;
468 18 50       333 @stat{qw( dev ino mode nlink uid gid rdev size atime mtime ctime )}= lstat($self->{src_abs}.$real_src_path)
469             or croak "lstat '$self->{src_abs}$real_src_path': $!";
470 18         140 %file= ( %stat, %file );
471             }
472              
473 18 50 33     54 if (defined $file{uid} || defined $file{gid}) {
474             # Remap the UID/GID if that feature was requested
475             @file{'uid','gid'}= $self->get_dst_uid_gid($file{uid}//0, $file{gid}//0, " in source filesystem at '$src_path'")
476 18 50 0     66 if $self->{_user_rewrite_map} || $self->{_group_rewrite_map};
      0        
      33        
477             }
478 18         26 $file{src_path}= $real_src_path;
479 18   33     82 $file{data_path} //= $self->{src_abs} . $real_src_path;
480 18   33     76 $file{name} //= $self->get_dst_for_src($real_src_path);
481 18         75 $self->{src_path_set}{$real_src_path}= $file{name};
482 18 100       50 $self->{src_path_set}{$src_path}= $file{name} if $real_src_path ne $src_path;
483             }
484             $file{data_path}= $self->{src_abs} . $file{real_src_path}
485             if !defined $file{data} && !defined $file{data_path} && defined $file{real_src_path}
486 40 50 100     137 && -e $self->{src_abs} . $file{real_src_path};
      66        
      33        
487 40   100     81 $file{nlink} //= 1;
488              
489 40 100 66     71 if (defined $file{user} && !defined $file{uid}) {
490 1   33     10 $dst_userdb //= ($self->{dst_userdb} //= $self->_build_dst_userdb);
      33        
491             my $u= $dst_userdb->user($file{user})
492 1   33     4 // croak "Unknown user '$file{user}' for file '$file{name}'";
493 1         3 $file{uid}= $u->uid;
494             }
495 40 100       112 ++$self->{dst_uid_used}{$file{uid}} if defined $file{uid};
496              
497 40 100 66     77 if (defined $file{group} && !defined $file{gid}) {
498 1   0     2 $dst_userdb //= ($self->{dst_userdb} //= $self->_build_dst_userdb);
      33        
499             my $g= $dst_userdb->group($file{group})
500 1   33     3 // croak "Unknown group '$file{group}' for file '$file{name}'";
501 1         3 $file{gid}= $g->gid;
502             }
503 40 100       82 ++$self->{dst_gid_used}{$file{gid}} if defined $file{gid};
504              
505             # Has this destination already been written?
506 40 50       78 if (exists $self->{dst_path_set}{$file{name}}) {
507 0         0 my $orig= $self->{dst_path_set}{$file{name}};
508             # If the destination is ::WriteFS, let it handle the collision below
509 0 0       0 unless ($self->_dst->can('dst_abs')) {
510 0   0     0 my $action= $self->on_collision // 'ignore_if_same';
511 0 0       0 $action= $action->($file{name}, \%file)
512             if ref $action eq 'CODE';
513 0 0       0 if ($action eq 'ignore_if_same') {
514 0 0 0     0 $action= ($file{src_path}//'') eq $orig? 'ignore' : 'croak';
515             }
516 0 0       0 if ($action eq 'ignore') {
    0          
    0          
517 0         0 $self->log->debugf("Already exported to '%s' previously from '%s'", $file{name}, $orig);
518 0         0 next;
519             } elsif ($action eq 'overwrite') {
520 0         0 $self->log->debugf("Overwriting '%s'", $file{name});
521             # let dst handle overwrite...
522             } elsif ($action eq 'croak') {
523 0 0       0 croak "Already exported '$file{name}'".(length $orig? " which came from $orig":"");
524             } else {
525 0         0 croak "unhandled on_collision action '$action'";
526             }
527             }
528             }
529             # Else make sure the parent directory *has* been written
530             else {
531 40         247 my $dst_parent= $file{name} =~ s,/?[^/]+$,,r;
532 40 100 100     116 if (length $dst_parent && !exists $self->{dst_path_set}{$dst_parent}) {
533 9         20 $self->log->debugf(" parent dir '%s' is not exported yet", $dst_parent);
534             # if writing to a real dir, check whether it already exists by some other means
535 9 50 66     72 if ($self->_dst->can('dst_abs') && -d $self->_dst->dst_abs . $dst_parent) {
536 0         0 $self->log->debugf(" %s%s already exists in the filesystem", $self->_dst->dst_abs, $dst_parent);
537             # no need to do anything, but record that we have it
538 0         0 $self->{dst_path_set}{$dst_parent}= undef;
539             }
540             else {
541             # Determine which directory to copy permissions from
542             my $src_parent= !defined $file{src_path}? undef
543 9 100       90 : $file{src_path} =~ s,/?[^/]+$,,r;
544             # If no rewrites, src_parent is the same as dst_parent
545 9 100 100     21 if (!$self->_has_rewrites) {
    100          
546 3   33     6 $src_parent //= $dst_parent;
547 3         9 $self->log->debugf(" will export %s first", $src_parent);
548             }
549             elsif (!length $src_parent || $self->get_dst_for_src($src_parent) ne $dst_parent) {
550             # No src_path means we don't have an origin for this file, so no official
551             # origin for its parent directory, either. But, maybe a directory of the
552             # same name exists in src_path.
553             # If so, use it, else create a generic directory.
554 4         8 my %dir= ( name => $dst_parent );
555 4 100 66     100 if ((@dir{qw( dev ino mode nlink uid gid rdev size atime mtime ctime )}
556             = lstat $self->{src_abs} . $dst_parent)
557             && S_ISDIR($dir{mode})
558             ) {
559 1         2 $src_parent= \%dir;
560 1         2 $self->log->debugf(" will export %s first, using permissions from %s%s", $dst_parent, $self->{src_abs}, $dst_parent);
561             } else {
562 3         11 $src_parent= { name => $dst_parent, mode => (S_IFDIR | 0755) };
563 3         6 $self->log->debugf(" will export %s first, using default 0755 permissions", $dst_parent);
564             }
565             }
566 9         64 unshift @add, $src_parent, \%file;
567 9         27 next;
568             }
569             }
570             }
571 31         61 $self->{dst_path_set}{$file{name}}= $file{src_path};
572              
573 31   33     51 my $mode= $file{mode} // croak "attribute 'mode' is required, for $file{name}";
574 31 100 66     141 if (S_ISREG($mode)) { $self->_export_file(\%file) }
  14 100       37  
    100          
    100          
    50          
    50          
    0          
575 10         28 elsif (S_ISDIR($mode)) { $self->_export_dir(\%file) }
576 5         14 elsif (S_ISLNK($mode)) { $self->_export_symlink(\%file) }
577 1         14 elsif (S_ISBLK($mode) || S_ISCHR($mode)) { $self->_export_devnode(\%file) }
578 0         0 elsif (S_ISFIFO($mode)) { $self->_export_fifo(\%file) }
579 1         10 elsif (S_ISSOCK($mode)) { $self->_export_socket(\%file) }
580 0         0 elsif (S_ISWHT($mode)) { $self->_export_whiteout(\%file) }
581             else {
582 0   0     0 croak "Unhandled dir-ent type ".($mode & S_IFMT).' at "'.($file{src_path} // $file{data_path} // $file{name}).'"'
      0        
583             }
584             }
585 20         672 $self;
586             }
587              
588              
589 6     6 1 7 sub src_glob($self, @patterns) {
  6         6  
  6         8  
  6         6  
590 6         12 my $src_abs= $self->src_abs;
591             # remove leading '/' from patterns
592 6         14 s{^/}{} for @patterns;
593 6         7 my @ret;
594             push @ret, map substr($_, length $src_abs), glob "$src_abs$_"
595 6         436 for @patterns;
596 6         37 return @ret;
597             }
598              
599              
600 9 100   9   61 my sub isa_filter { ref $_[0] eq 'Regexp' || ref $_[0] eq 'CODE' }
601 5     5 1 11662 sub src_find($self, @paths) {
  5         8  
  5         9  
  5         6  
602 5         8 my $filter;
603             # The filter must be either the first or last argument
604 5 100       15 if (isa_filter $paths[0]) {
    100          
605 1         2 $filter= shift @paths;
606             } elsif (isa_filter $paths[-1]) {
607 3         5 $filter= pop @paths;
608             }
609 5         19 my ($src_abs, @ret, @todo, %seen)= ( $self->src_abs );
610             # If filter is a regexp-ref, upgrade it to a sub
611 5 100       13 if (ref $filter eq 'Regexp') {
612 2         6 my $qr= $filter;
613 2     38   11 $filter= sub { $_ =~ $qr };
  38         215  
614             }
615             my $process= sub {
616 56     56   90 my %file= ( src_path => $_[0] );
617 56         109 local $_= $src_abs . $_[0];
618 56 50       116 return if $seen{$_}++; # within this call to src_find, don't return duplicates
619 56 50       725 if (@file{qw( dev ino mode nlink uid gid rdev size atime mtime ctime )}= lstat) {
620 56         335 my $is_dir= -d;
621 56 100 100     133 push @ret, \%file if length $_[0] && (!defined $filter || $filter->(\%file));
      100        
622 56 100 66     252 if ($is_dir && !delete $file{prune}) {
623 13 50       340 if (opendir my $dh, $src_abs . $_[0]) {
624 13 100       75 push @todo, [ length $_[0]? $_[0].'/' : '', $dh ];
625             } else {
626 0         0 carp "Can't open $_: $!";
627             }
628             }
629             } else {
630 0         0 carp "Can't stat $_: $!";
631             }
632 5         20 };
633 5 100       13 push @paths, '' unless @paths;
634 5         10 for my $path (@paths) {
635 5   50     9 $path //= '';
636 5         14 $path =~ s,^/,,; # remove leading slash
637 5         9 $process->($path);
638 5         13 while (@todo) {
639 90         377 my $ent= readdir $todo[-1][1];
640 90 100 100     204 if (!defined $ent) {
    100          
641 13         70 closedir $todo[-1][1];
642 13         45 pop @todo;
643             }
644             elsif ($ent ne '.' && $ent ne '..') {
645 51         88 $process->($todo[-1][0] . $ent);
646             }
647             }
648             }
649 5         99 return @ret;
650             }
651              
652              
653 2     2 1 872 sub src_which($self, $name) {
  2         4  
  2         15  
  2         2  
654 2 50       8 $name =~ m,/, and croak '->src_which($name) should not include a path separator';
655 2         6 for ($self->src_exe_PATH_list) {
656 7 100       15 return "$_/$name" if -x $self->src_abs . "$_/$name";
657             }
658 0         0 return undef;
659             }
660              
661              
662 1     1 1 1 sub skip($self, @paths) {
  1         2  
  1         3  
  1         1  
663 1         2 for my $path (@paths) {
664             $path= $path->{src_path} // $path->{name}
665 23 50 33     30 // croak "Hashrefs passed to ->skip must include 'src_path' or 'name'"
      0        
666             if isa_hash $path;
667 23   100     75 $self->{src_path_set}{$path =~ s,^/,,r} //= undef;
668             }
669 1         22 $self;
670             }
671              
672              
673 2     2 1 11 sub finish($self) {
  2         3  
  2         4  
674 2         5 $self->_dst->finish;
675 2         20 undef $self->{tmp}; # allow File::Temp to free tmp dir
676 2         842 $self;
677             }
678              
679              
680 22     22 1 26 sub get_dst_for_src($self, $path) {
  22         24  
  22         22  
  22         18  
681 22         65 my $rre= $self->path_rewrite_regex;
682 22         218 my $rewrote= $path =~ s/^$rre/$self->{path_rewrite_map}{$1}/er;
  8         41  
683 22 100       58 $self->log->tracef(" rewrote '%s' to '%s'", $path, $rewrote)
684             if $path ne $rewrote;
685 22         108 return $rewrote;
686             }
687              
688              
689 0     0 1 0 sub get_dst_uid_gid($self, $uid, $gid, $context='') {
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
690             # If dst_userdb is defined, convert these source uid/gid to names, then find the name
691             # in dst_userdb, then write those uid/gid. But, if _user_rewrite_map has an entry for
692             # the UID or user, then go with that.
693 0         0 my $dst_userdb= $self->{dst_userdb};
694 0 0 0     0 if ($dst_userdb || $self->{_user_rewrite_map} || $self->{_group_rewrite_map}) {
      0        
695 0         0 my $dst_uid= $self->{_user_rewrite_map}{$uid};
696 0         0 my $dst_gid= $self->{_group_rewrite_map}{$gid};
697 0 0 0     0 if ($dst_userdb && !defined $dst_uid) {
698 0   0     0 my $src_userdb= ($self->{src_userdb} //= $self->_build_src_userdb);
699 0 0       0 my $src_user= $src_userdb->user($uid)
700             or croak "Unknown UID $uid$context";
701 0         0 $dst_uid= $self->{_user_rewrite_map}{$src_user->name};
702 0 0       0 if (!defined $dst_uid) {
703 0 0       0 my $dst_user= $dst_userdb->user($src_user->name)
704             or croak "User ".$src_user->name." not found in dst_userdb$context";
705 0         0 $dst_uid= $dst_user->uid;
706             }
707             # cache it
708 0         0 $self->{_user_rewrite_map}{$src_user->name}= $dst_uid;
709 0         0 $self->{_user_rewrite_map}{$uid}= $dst_uid;
710             }
711 0 0 0     0 if ($dst_userdb && !defined $dst_gid) {
712 0   0     0 my $src_userdb= ($self->{src_userdb} //= $self->_build_src_userdb);
713 0 0       0 my $src_group= $src_userdb->group($gid)
714             or croak "Unknown GID $gid$context";
715 0         0 $dst_gid= $self->{_group_rewrite_map}{$src_group->name};
716 0 0       0 if (!defined $dst_gid) {
717 0 0       0 my $dst_group= $dst_userdb->group($src_group->name)
718             or croak "Group ".$src_group->name." not found in dst_userdb$context";
719 0         0 $dst_gid= $dst_group->gid;
720             }
721             # cache it
722 0         0 $self->{_group_rewrite_map}{$src_group->name}= $dst_gid;
723 0         0 $self->{_group_rewrite_map}{$gid}= $dst_gid;
724             }
725 0         0 return ($dst_uid, $dst_gid);
726             }
727 0         0 return ($uid, $gid);
728             }
729              
730 14     14   17 sub _export_file($self, $file) {
  14         16  
  14         13  
  14         13  
731             # If the file has a link count > 1, check to see if we already have it in the destination
732 14         15 my $prev;
733 14 50 66     72 if ($file->{nlink} > 1 && $file->{dev} && $file->{ino}) {
      33        
734 1 50       3 if (defined($prev= $self->_link_map->{"$file->{dev}:$file->{ino}"})) {
735 0         0 $self->log->debugf("Already exported inode %s:%s as '%s'", $file->{dev}, $file->{ino}, $prev);
736             # make a link of that file instead of copying again
737 0         0 $self->_log_action("LNK", $prev, $file->{name});
738             # ensure the dst realizes it is a hardlink by sending it without data
739 0         0 delete $file->{data};
740 0         0 delete $file->{data_path};
741             }
742             else {
743 1         4 $self->_link_map->{"$file->{dev}:$file->{ino}"}= $file->{name};
744             }
745             }
746 14 50       25 if (!defined $prev) {
747             # Normalize data to a scalar-ref
748 14 100       50 if (defined $file->{data_path}) {
    50          
749 6         53 $file->{data}= Sys::Export::LazyFileData->new($file->{data_path});
750             } elsif (!ref $file->{data}) {
751             defined $file->{data}
752 8 50       25 or croak "For regular files, must specify ->{data} or ->{data_path}";
753 8         16 $file->{data}= \delete $file->{data};
754             }
755 14         19 my @notes;
756             $self->process_file($file, \@notes)
757 14 100       45 if length $file->{src_path};
758 14   100     59 $self->_log_action("CPY", $file->{src_path} // '(data)', $file->{name}, @notes);
759             }
760 14         115 $self->_dst->add($file);
761             }
762              
763              
764 6     6 1 8 sub process_file($self, $file, $notes) {
  6         6  
  6         7  
  6         7  
  6         16  
765             # Check for ELF signature or script-interpreter
766 6 50       33 if (substr(${$file->{data}}, 0, 4) eq "\x7fELF") {
  6 100       94  
767 0         0 $self->log->tracef("Detected ELF signature in '%s'", $file->{name});
768 0         0 $self->process_elf_file($file, $notes);
769 6         14 } elsif (${$file->{data}} =~ m,^#!\s*/(\S+),) {
770 1         3 $self->log->tracef("Detected script interpreter '%s' in '%s'", $1, $file->{name});
771 1         13 $self->process_script_file($file, $notes);
772             }
773             }
774              
775 0     0   0 sub _resolve_src_library($self, $libname, $rpath) {
  0         0  
  0         0  
  0         0  
  0         0  
776 0   0     0 my @paths= ((grep length, split /:/, ($rpath//'')), $self->src_lib_path_list);
777 0         0 for my $path (@paths) {
778 0         0 $path =~ s,^/,,; # remove leading slash because src_abs ends with slash
779 0 0       0 $path =~ s,(?<=[^/])\z,/, if length $path; # add trailing slash if it isn't the root
780 0 0       0 if (-e $self->{src_abs} . $path . $libname) {
781 0         0 $self->log->tracef(" found %s at %s%s", $libname, $path, $libname);
782 0         0 return $path . $libname;
783             }
784             }
785 0         0 return ();
786             }
787              
788              
789 0     0 1 0 sub process_elf_file($self, $file, $notes) {
  0         0  
  0         0  
  0         0  
  0         0  
790 0         0 require Sys::Export::ELF;
791 0         0 my $elf= Sys::Export::ELF::unpack(${$file->{data}});
  0         0  
792 0         0 my ($interpreter, @libs);
793 0 0       0 if ($elf->{dynamic}) {
794 0         0 $self->log->debugf("Dynamic-linked ELF file: '%s' (src_path=%s)", $file->{name}, $file->{src_path});
795 0 0       0 if ($elf->{needed_libraries}) {
796 0         0 for (@{$elf->{needed_libraries}}) {
  0         0  
797 0   0     0 my $lib= $self->_resolve_src_library($_, $elf->{rpath}) // carp("Can't find lib $_ needed for $file->{src_path}");
798 0 0       0 push @libs, $lib if $lib;
799             }
800 0         0 $self->add(@libs);
801             }
802 0 0       0 if (length $elf->{interpreter}) {
803 0         0 $elf->{interpreter} =~ s,^/,,;
804 0         0 $self->_elf_interpreters->{$elf->{interpreter}}= 1;
805 0         0 $interpreter= $elf->{interpreter};
806 0         0 $self->add($interpreter);
807             }
808 0         0 $self->log->debugf(" interpreter = %s, libs = %s", $interpreter, \@libs);
809             # Is any path rewriting requested?
810 0 0 0     0 if ($self->_has_rewrites && (defined $interpreter || @libs)) {
      0        
811             # If any dep gets its path rewritten, need to modify interpreter and/or rpath
812 0         0 my $rre= $self->path_rewrite_regex;
813 0         0 my @patchelf;
814 0 0 0     0 if (defined $interpreter && $interpreter =~ m/^$rre/) {
815             # the interpreter and rpath need to be absolute URLs, but within the logical root
816             # of 'dst'. They're already relative to 'dst', so just prefix a slash.
817 0         0 push @patchelf, '--set-interpreter' => '/'.$self->get_dst_for_src($interpreter);
818             }
819 0 0       0 if (grep m/^$rre/, @libs) {
820 0         0 my %rpath;
821 0         0 for (@libs) {
822 0         0 my $dst_lib= $self->get_dst_for_src($_);
823 0         0 $dst_lib =~ s,[^/]+$,,; # path of lib
824 0         0 $rpath{$dst_lib}= 1;
825             }
826 0 0       0 if (keys %rpath) {
827 0         0 push @patchelf, '--set-rpath' => join(':', map "/$_", keys %rpath);
828             }
829             }
830 0 0       0 if (@patchelf) {
831 0         0 $self->log->debug(join ' ', " patchelf rewrites: ", @patchelf);
832             # Create a temporary file so we can run patchelf on it
833 0         0 my $tmp= File::Temp->new(DIR => $self->tmp, UNLINK => 0);
834 0         0 _syswrite_all($tmp, $file->{data});
835 0         0 $tmp->close;
836 0         0 $self->_patchelf($tmp, @patchelf);
837 0         0 $file->{data}= map_or_load_file("$tmp");
838 0         0 push @$notes, '+patchelf';
839             } else {
840 0         0 $self->log->debug(" no interpreter/lib paths affected by rewrites");
841             }
842             }
843             }
844             }
845              
846              
847 1     1 1 2 sub process_script_file($self, $file, $notes) {
  1         1  
  1         1  
  1         2  
  1         2  
848             # Make sure the interpreter is added, and also rewrite its path
849 1 50       1 my ($interp)= (${$file->{data}} =~ m,^#!\s*/(\S+),)
  1         3  
850             or return;
851 1         15 $self->add($interp);
852 1 50       2 if ($self->_has_rewrites) {
853             # rewrite the interpreter, if needed
854 1         10 my $rre= $self->path_rewrite_regex;
855 1         2 my $dst_interp= $interp;
856 1 50       38 if ($dst_interp =~ s,^$rre,$self->{path_rewrite_map}{$1},e) {
  1         7  
857             # note file->{data} could be a read-only memory map
858 1         1 my $data= ${$file->{data}} =~ s,^(#!\s*)(\S+),$1/$dst_interp,r;
  1         3  
859 1         2 $file->{data}= \$data;
860 1         3 push @$notes, '+rewrite interpreter';
861             }
862             }
863 1 50       10 if ($interp =~ m,^(usr/)?bin/env\z,) { # /usr/bin/env, request for interpreter from $PATH...
864 0 0       0 my ($name)= (${$file->{data}} =~ m,^#!\s*/\S+\s*(\S+),)
  0         0  
865             or return;
866 0 0       0 if (defined (my $path= $self->src_which($name))) {
867 0         0 $self->add($path);
868 0         0 $interp= $path;
869 0         0 $self->log->tracef("Detected secondary script interpreter '%s' in '%s'", $path, $file->{name});
870             } else {
871 0         0 $self->log->tracef("Detected secondary script interpreter '%s' in '%s' but can't locate it", $name, $file->{name});
872             }
873             }
874              
875 1         3 $file->{interpreter}= $interp;
876 1 50 0     10 if ($interp =~ m,/perl[0-9.]*\z,) {
    50          
    0          
877 0         0 $self->process_perl_file($file, $notes);
878             }
879             elsif ($interp =~ m,/(bash|ash|dash|sh)\z,) {
880 1         4 $self->process_shell_file($file, $notes);
881             }
882 0         0 elsif ($self->_has_rewrites && ${$file->{data}} =~ $self->path_rewrite_regex) {
883 0         0 warn "$file->{src_path} is a script referencing a rewritten path, but don't know how to process it\n";
884 0         0 push @$notes, "+can't rewrite!";
885             }
886             }
887              
888              
889 1     1 1 2 sub process_shell_file($self, $file, $notes) {
  1         1  
  1         1  
  1         1  
  1         1  
890             # This takes the bold step of attempting to rewrite paths seen in the script
891 1 50       3 if ($self->_has_rewrites) {
892 1         2 my $rre= $self->path_rewrite_regex;
893             # Scan the source for paths that need rewritten
894 1 50       1 if (${$file->{data}} =~ $rre) {
  1         8  
895 1         1 my ($interp_line, $body)= split "\n", ${$file->{data}}, 2;
  1         4  
896             # only replace path matches when following certain characters which
897             # indicate the start of a path.
898 1         41 $body =~ s/(?<=[ '"><\n#])$rre/$self->{path_rewrite_map}{$1}/ge;
  0         0  
899 1         4 $file->{data}= \"$interp_line\n$body";
900 1         4 push @$notes, '+rewrite paths';
901             }
902             }
903             }
904              
905              
906 0     0   0 sub _get_perl_script_deps($self, $file) {
  0         0  
  0         0  
  0         0  
907 0   0     0 my $interp= $file->{interpreter} // $self->src_which('perl');
908 0         0 $self->log->tracef("Checking perl script %s for dependencies", $file->{name});
909             # We can run the actual perl interpreter with '-c' on this file, so long as
910             # src_path is defined and an strace implementation is available.
911 0 0 0     0 if ($self->_can_trace_deps && length $file->{src_path}) {
912             # If file appears to be a module, ensure module's own path root is in perl's @INC
913 0         0 my @inc;
914 0 0       0 if ($file->{src_path} =~ /.pm\z/) {
915 0 0       0 if (${$file->{data}} =~ /^(package|class) (\S+)/m) {
  0         0  
916 0         0 my $path= ($1 =~ s,::,/,gr).'.pm';
917 0 0 0     0 if (substr($file->{src_path}//'', -length $path) eq $path) {
918 0         0 push @inc, substr($file->{src_path}, 0, -length $path);
919             }
920             }
921             }
922 0         0 my @cmd= ($interp, '-c', (map "-I$_", @inc), $file->{src_path} );
923 0         0 $self->log->debugf("Tracing perl deps with %s", \@cmd);
924 0         0 my $deps= eval { $self->_trace_deps(@cmd) };
  0         0  
925 0 0       0 return sort keys %$deps if defined $deps;
926 0         0 $self->log->debugf("strace failed: %s", $@);
927             }
928            
929 0         0 $self->log->trace("strace unavailable, falling back to source scan");
930 0         0 return;
931             }
932              
933 0     0 1 0 sub process_perl_file($self, $file, $notes) {
  0         0  
  0         0  
  0         0  
  0         0  
934 0         0 $self->add($self->_get_perl_script_deps($file));
935 0 0 0     0 if ($self->_has_rewrites && ${$file->{data}} =~ $self->path_rewrite_regex) {
  0         0  
936 0         0 warn "$file->{src_path} is a script referencing a rewritten path, but don't know how to process it\n";
937 0         0 push @$notes, "+can't rewrite!";
938             }
939             }
940              
941 10     10   19 sub _export_dir($self, $dir) {
  10         51  
  10         13  
  10         11  
942 10   100     42 $self->_log_action('DIR', $dir->{src_path} // '(default)', $dir->{name});
943 10         69 $self->_dst->add($dir);
944             }
945              
946 5     5   8 sub _export_symlink($self, $file) {
  5         27  
  5         5  
  5         6  
947 5 50       11 if (!defined $file->{data}) {
948             length $file->{data_path}
949 5 50       12 or croak "Symlink must contain 'data' or 'data_path'";
950 5 50       155 defined( my $target= readlink($file->{data_path}) )
951             or croak "readlink($file->{data_path}): $!";
952 5         13 $file->{data}= $target;
953             # Symlink referenced a source file, so also export the symlink target
954             # If target is relative and the data_path wasn't inside the src_abs tree, then not
955             # sensible to export it.
956 5 50 66     29 if ($target !~ m,^/, and substr($file->{data_path}, 0, length $self->{src_abs}) ne $self->{src_abs}) {
957             $self->log->debugf('Symlink %s read from %s which is outside %s; not adding symlink target %s',
958 0         0 $file->{name}, $file->{data_path}, $self->{src_abs}, $target);
959             }
960             else {
961             # make relative path absolute
962 5 100       25 $target= (substr($file->{data_path}, length $self->{src_abs}) =~ s,[^/]*\z,,r) . $target
963             unless $target =~ m,^/,;
964 5         11 my $abs_target= $self->_src_parent_abs_path($target);
965             # Only queue it if it exists. Exporting dangling symlinks is not an error
966 5 100 66     71 if (defined $abs_target && lstat $self->{src_abs} . $abs_target) {
967 4         19 $self->log->debugf("Queueing target '%s' of symlink '%s'", $target, $file->{name});
968 4         78 $self->add($target);
969             } else {
970 1         6 $self->log->debugf("Symlink '%s' target '%s' doesn't exist", $file->{name}, $target);
971             }
972             }
973             }
974              
975 5 100 66     19 if ($self->_has_rewrites && length $file->{src_path}) {
976             # Absolute links just need a simple rewrite on the target
977 1 50       4 if ($file->{data} =~ m,^/,) {
978 0         0 $file->{data}= $self->get_dst_for_src($file->{data});
979             }
980             # Relative links are tricky. A "100%" solution might actually be impossible, because
981             # users could intend for all sorts of different behavior with symlinks, but at least try
982             # to DWIM here.
983             else {
984             # Example: /usr/local/bin/foo -> ../../bin/bar, but both paths are being rewritten to /bin
985             # The correct symlink is then just /bin/foo -> bar
986             # Example: /usr/local/share/mydata -> ../../../opt/mydata, but /opt/mydata is a
987             # symlink to /opt/mydata-1.2.3, and /usr/local/share is getting rewritten to /share.
988             # The user may want this double redirection to remain so that mydata can be swapped
989             # for different versions, so can't just resolve everything to an absolute path.
990             # The correct symlink should probably be /share/mydata -> ../opt/mydata
991             # Example: /usr/local/share/mydata/lib -> ../../../../opt/mydata/current/../lib
992             # where /usr/local/share is getting rewritten and /opt/mydata is getting rewritten,
993             # and /opt/mydata/current is a symlink that breaks assumptions about '..'
994             # The correct symlink should probably be /share/mydata/lib -> ../../opt/mydata/current/../lib
995             # Note that /opt/mydata/current symlink might not even exist in dst yet (to be able
996             # to resolve it) and resolving the one in src might not be what the user wants.
997            
998             # I think the answer here is to consume all leading '..' in the symlink path
999             # (src_path is already absolute, so no danger of '..' meaning something different)
1000             # then add all following non-'..' to arrive at a new src_target, then rewrite that to
1001             # the corresponding dst_target, then create a relative path from the dst symlink to
1002             # that dst_path, then append any additional portions of the original symlink as-is.
1003 1         10 my @src_parts= split '/', $file->{src_path};
1004 1         2 pop @src_parts; # discard name of symlink itself
1005 1         4 my @target_parts= grep $_ ne '.', split '/', $file->{data};
1006 1   33     5 while (@target_parts && $target_parts[0] eq '..') {
1007 0         0 shift @target_parts;
1008 0         0 pop @src_parts;
1009             }
1010 1   66     8 while (@target_parts && $target_parts[0] ne '..') {
1011 1         3 push @src_parts, shift @target_parts;
1012             }
1013 1         5 my @dst_target= split '/', $self->get_dst_for_src(join '/', @src_parts);
1014             # now construct a relative path from $file->{name} to $dst_target
1015 1         3 my @dst_parts= split '/', $file->{name};
1016 1         2 pop @dst_parts; # discard name of symlink itself
1017             # remove common prefix
1018 1   66     11 while (@dst_parts && @dst_target && $dst_parts[0] eq $dst_target[0]) {
      66        
1019 3         3 shift @dst_parts;
1020 3         8 shift @dst_target;
1021             }
1022             # assemble '..' for each remaining piece of dst_parts, then the path to dst-target,
1023             # then the remainder of original path components (if any)
1024 1         5 $file->{data}= join '/', (('..') x scalar @dst_parts), @dst_target, @target_parts;
1025             }
1026             }
1027              
1028 5         19 $self->_log_action('SYM', '"'.$file->{data}.'"', $file->{name});
1029 5         37 $self->_dst->add($file);
1030             }
1031              
1032 1     1   5 sub _export_devnode($self, $file) {
  1         6  
  1         3  
  1         5  
1033 1 50 33     13 if (defined $file->{rdev} && (!defined $file->{rdev_major} || !defined $file->{rdev_minor})) {
      33        
1034 1         10 my ($major,$minor)= Sys::Export::Unix::_dev_major_minor($file->{rdev});
1035 1   33     9 $file->{rdev_major} //= $major;
1036 1   33     12 $file->{rdev_minor} //= $minor;
1037             }
1038 1 50       50 $self->_log_action(S_ISBLK($file->{mode})? 'BLK' : 'CHR', "$file->{rdev_major}:$file->{rdev_minor}", $file->{name});
1039 1         26 $self->_dst->add($file);
1040             }
1041              
1042 0     0   0 sub _export_fifo($self, $file) {
  0         0  
  0         0  
  0         0  
1043 0         0 $self->_log_action("FIO", "(fifo)", $file->{name});
1044 0         0 $self->_dst->add($file);
1045             }
1046              
1047 1     1   2 sub _export_socket($self, $file) {
  1         2  
  1         2  
  1         1  
1048 1         9 $self->_log_action("SOK", "(socket)", $file->{name});
1049 1         14 $self->_dst->add($file);
1050             }
1051              
1052 0     0   0 sub _export_whiteout($self, $file) {
  0         0  
  0         0  
  0         0  
1053 0         0 $self->_log_action("WHT", "(whiteout)", $file->{name});
1054 0         0 $self->_dst->add($file);
1055             }
1056              
1057 5     5   8 sub _syswrite_all($tmp, $content_ref) {
  5         6  
  5         7  
  5         7  
1058 5         18 my $ofs= 0;
1059 5         22 again:
1060             my $wrote= $tmp->syswrite($$content_ref, length($$content_ref) - $ofs, $ofs);
1061 5 50       171 if ($ofs+$wrote != length $$content_ref) {
1062 0 0 0     0 if ($wrote > 0) { $ofs += $wrote; goto again; }
  0 0       0  
  0         0  
1063 0         0 elsif ($!{EAGAIN} || $!{EINTR}) { goto again; }
1064 0         0 else { die "syswrite($tmp): $!" }
1065             }
1066 5 50       19 $tmp->close or die "close($tmp): $!";
1067             }
1068              
1069 3     3   221 sub _linux_major_minor($dev) {
  3         6  
  3         6  
1070 10     10   117155 use integer;
  10         138  
  10         52  
1071 3         42 ( (($dev >> 8) & 0xfff) | (($dev >> 31 >> 1) & 0xfffff000) ),
1072             ( ($dev & 0xff) | (($dev >> 12) & 0xffffff00) )
1073             }
1074 2     2   1861 sub _system_mknod($path, $mode, $major, $minor) {
  2         4  
  2         3  
  2         2  
  2         3  
  2         3  
1075 2 50       23 my @args= ("mknod", ($^O eq 'linux'? ("-m", sprintf("0%o", $mode & 0xFFF)) : ()),
    50          
1076             $path, S_ISBLK($mode)? "b":"c", $major, $minor);
1077 2 50       10508 system(@args) == 0
1078             or croak "mknod @args failed";
1079             }
1080              
1081             if ($have_unix_mknod) {
1082             eval q{
1083             sub _mknod_or_die($path, $mode, $major, $minor) {
1084             Unix::Mknod::mknod($path, $mode, Unix::Mknod::makedev($major, $minor))
1085             or Carp::croak("mknod($path): $!");
1086             my @stat= stat $path
1087             or Carp::croak("mknod($path) failed silently");
1088             # Sometimes mknod just creates a normal file when user lacks permission for device nodes
1089             ($stat[2] & Fcntl::S_IFMT()) == ($mode & Fcntl::S_IFMT()) or do { unlink $path; Carp::croak("mknod failed to create mode $mode at $path"); };
1090             1;
1091             }
1092             sub _dev_major_minor($dev) { Unix::Mknod::major($dev), Unix::Mknod::minor($dev) }
1093             1;
1094             } or die "$@";
1095             } else {
1096             *_mknod_or_die= *_system_mknod;
1097             *_dev_major_minor= *_linux_major_minor;
1098             }
1099              
1100             sub _capture_cmd {
1101 0     0     require IPC::Open3;
1102 0           require Symbol;
1103 0 0         my $pid= IPC::Open3::open3(undef, my $out_fh, my $err_fh= Symbol::gensym(), @_)
1104             or die "running @_ failed";
1105 0           waitpid($pid, 0);
1106 0           my $wstat= $?;
1107 0           local $/= undef;
1108 0           my $out= <$out_fh>;
1109 0           my $err= <$err_fh>;
1110 0           return ($out, $err, $wstat);
1111             }
1112              
1113             our $patchelf;
1114 0     0     sub _patchelf($self, $path, @args) {
  0            
  0            
  0            
  0            
1115 0           $self->log->tracef(" patchelf %s %s", \@args, $path);
1116 0 0         unless ($patchelf) {
1117 0           chomp($patchelf= `which patchelf`);
1118 0 0         croak "Missing tool 'patchelf'"
1119             unless $patchelf;
1120             }
1121 0           my ($out, $err, $wstat)= _capture_cmd($patchelf, @args, $path);
1122 0 0         $wstat == 0
1123             or croak "patchelf '$path' failed: $err";
1124 0           1;
1125             }
1126              
1127             # Avoiding dependency on namespace::clean
1128             delete @{Sys::Export::Unix::}{qw(
1129             croak carp abs_path blessed looks_like_number max isa_hash isa_array isa_data_ref isa_handle
1130             isa_int isa_pow2 isa_export_dst isa_exporter isa_group isa_user isa_userdb S_IFMT
1131             map_or_load_file
1132             S_ISREG S_ISDIR S_ISLNK S_ISBLK S_ISCHR S_ISFIFO S_ISSOCK S_ISWHT
1133             S_IFREG S_IFDIR S_IFLNK S_IFBLK S_IFCHR S_IFIFO S_IFSOCK S_IFWHT
1134             )};
1135             1;
1136              
1137             __END__