File Coverage

blib/lib/Sys/Export/Unix.pm
Criterion Covered Total %
statement 537 854 62.8
branch 186 390 47.6
condition 129 334 38.6
subroutine 63 83 75.9
pod 35 35 100.0
total 950 1696 56.0


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