File Coverage

blib/lib/Sys/Export/Unix.pm
Criterion Covered Total %
statement 453 691 65.5
branch 181 376 48.1
condition 114 266 42.8
subroutine 54 74 72.9
pod 24 24 100.0
total 826 1431 57.7


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