File Coverage

blib/lib/Sys/Export/Unix.pm
Criterion Covered Total %
statement 497 806 61.6
branch 179 370 48.3
condition 125 324 38.5
subroutine 57 77 74.0
pod 32 32 100.0
total 890 1609 55.3


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