File Coverage

blib/lib/Sys/Export/Linux.pm
Criterion Covered Total %
statement 67 162 41.3
branch 13 74 17.5
condition 3 29 10.3
subroutine 12 16 75.0
pod 5 5 100.0
total 100 286 34.9


line stmt bran cond sub pod time code
1             package Sys::Export::Linux;
2              
3             # ABSTRACT: Export subsets of a Linux system
4             our $VERSION = '0.005_002'; # TRIAL VERSION
5              
6              
7 3     3   566249 use v5.26;
  3         10  
8 3     3   13 use warnings;
  3         4  
  3         135  
9 3     3   11 use experimental qw( signatures );
  3         5  
  3         16  
10 3     3   322 use parent 'Sys::Export::Unix';
  3         6  
  3         28  
11 3     3   144 use Cwd 'abs_path';
  3         4  
  3         115  
12 3     3   9 use Carp;
  3         4  
  3         105  
13 3     3   9 use Sys::Export 'filedata';
  3         4  
  3         14  
14 3     3   11 use Sys::Export::LogAny '$log';
  3         3  
  3         17  
15              
16             sub _build__trace_deps {
17 1     1   3 my $self= shift;
18 1 50       5 if ($self->_can_run_in_src) {
19             # Seems Solaris has an 'strace' but it isn't compatible enough to pass tests.
20 1 50       4 $^O eq 'linux' or croak "Only Linux strace is supported";
21             # Are we going to attempt chrooting?
22 1 50       2 if ($self->src_abs ne '/') {
23 0   0     0 $self->{cmd_path_chroot} //= do {
24 0         0 chomp(my $chroot= `which chroot`);
25 0 0       0 -x $chroot or croak "chroot command not available or not executable";
26 0 0       0 $self->{_log_trace}->("chroot binary at $chroot") if $self->{_log_trace};
27 0         0 $chroot;
28             };
29             }
30 1   33     5 $self->{cmd_path_strace} //= do {
31 1         6218 chomp(my $strace= `which strace`);
32 1 50       555 -x $strace or croak "strace command not available or not executable";
33 0 0       0 $self->{_log_trace}->("strace binary at $strace") if $self->{_log_trace};
34 0         0 $strace;
35             };
36 0         0 return $self->can('_trace_deps_linux_strace');
37             }
38 0         0 $self->next::method(@_);
39             }
40              
41 0     0   0 sub _trace_deps_linux_strace($self, @argv) {
  0         0  
  0         0  
  0         0  
42             # Are we going to attempt chrooting?
43 0 0       0 unshift @argv, $self->{cmd_path_chroot}, $self->src_abs
44             unless $self->src_abs eq '/';
45             # Tell strace to write to a pipe, while redirecting command output to /dev/null
46 0 0       0 open my $devnull, '+<', '/dev/null' or croak "open(/dev/null): $!";
47 0   0     0 pipe(my $r, my $w) // croak "pipe: $!";
48 0   0     0 pipe(my $err_r, my $err_w) // croak "pipe: $!";
49 0   0     0 my $pid= fork // croak "fork: $!";
50 0 0       0 if (!$pid) {
51 0         0 close $r;
52 0         0 close $err_r;
53 0         0 eval {
54 0 0       0 chdir $self->src_abs or die "chdir: $!";
55 0 0       0 POSIX::dup2(fileno $devnull, 0) or die "dup2(->0): $!";
56 0 0       0 POSIX::dup2(fileno $devnull, 1) or die "dup2(->1): $!";
57 0 0       0 POSIX::dup2(fileno $devnull, 2) or die "dup2(->2): $!";
58 0 0       0 POSIX::dup2(fileno $w, 3) or die "dup2(->3): $!";
59 0         0 $^F= 3;
60 0         0 unshift @argv, $self->{cmd_path_strace}, -o => "/proc/self/fd/3", -e => 'trace=open,openat';
61 0 0       0 exec @argv
62             or die "exec(@argv): $!";
63             };
64 0         0 $err_w->print($@);
65 0         0 $err_w->close;
66 0         0 POSIX::_exit(2); # forcibly exit rather than bubble up the stack
67             }
68             else {
69 0         0 close $w;
70 0         0 close $err_w;
71 0         0 my %deps;
72 0         0 my $err= do { local $/; <$err_r> };
  0         0  
  0         0  
73 0 0       0 if (length $err) {
74 0         0 waitpid($pid, 0);
75 0         0 die $err;
76             }
77 0 0       0 $self->{_log_trace}->("Reading strace output") if $self->{_log_trace};
78 0         0 while (<$r>) {
79 0 0       0 $self->{_log_trace}->($_) if $self->{_log_trace};
80 0 0       0 $deps{$1}= 1 if /^open(?:at)?\(.*?"(.*?)",.*?= [0-9]/;
81             }
82 0 0       0 $self->{_log_trace}->("Done reading strace") if $self->{_log_trace};
83 0         0 waitpid($pid,0);
84 0         0 my $wstat= $?;
85 0 0       0 $self->{_log_trace}->("Command exited with $wstat") if $self->{_log_trace};
86 0 0       0 croak "straced command failed: wstat = $wstat"
87             if $wstat;
88 0         0 return \%deps;
89             }
90             }
91              
92              
93 14     14 1 27 sub parse_ld_so_conf($self, $conf_path= 'etc/ld.so.conf') {
  14         15  
  14         15  
  14         14  
94 14         28 my $data= filedata($self->src_abs . $conf_path);
95 14         19 my @libs;
96 14         52 for (split /\n/, $$data) {
97 46         46 chomp;
98 46 100       104 next if /^\s*(#|\z)/;
99 32 100       73 if (/^\s*include (\S+)/) {
    50          
100 6         15 my $pattern= $1;
101             # relative paths are relative to the config file's parent directory
102 6 100       39 my $prefix= $pattern =~ s{^/}{}? '' : ($conf_path =~ s{[^/]+\z}{}r);
103             push @libs, $self->parse_ld_so_conf($_)
104 6         29 for $self->src_glob($prefix.$pattern);
105             }
106             elsif (m{^/}) {
107 26         47 push @libs, substr($_, 1);
108             }
109             else {
110 0         0 $log->warn("parse_ld_so_conf: unknown syntax at '$_'");
111             }
112             }
113 14         118 return @libs;
114             }
115              
116 3     3   4 sub _build_src_lib_path($self) {
  3         3  
  3         3  
117 3         13 my $paths= $self->next::method();
118             # ld.so.conf may or may not be used on this host
119 3 50       6 if (defined $self->_src_abs_path('etc/ld.so.conf')) {
120 3 50       4 eval { $paths= $self->_distinct_abs_directories(1, @$paths, $self->parse_ld_so_conf) }
  3         9  
121             or $self->log->warn("Failed to parse ld.so.conf: $@");
122             }
123 3         39 return $paths;
124             }
125              
126              
127 0     0 1 0 sub parse_nsswitch_conf($self, $conf_path= 'etc/nsswitch.conf') {
  0         0  
  0         0  
  0         0  
128 0         0 my $data= filedata($self->src_abs . $conf_path);
129 0         0 my @db_conf;
130 0         0 for (split /\n/, $$data) {
131 0         0 chomp;
132 0 0       0 next if /^\s*(#|\z)/;
133 0 0       0 if (m{^\s*([^\s:]+)\s*:\s*(\S.+)}) {
134 0         0 push @db_conf, $1 => [ split /\s+/, $2 ];
135             }
136             else {
137 0         0 $log->warn("parse_nsswitch_conf: unknown syntax at '$_'");
138             }
139             }
140 0         0 return @db_conf;
141             }
142              
143              
144 0     0 1 0 sub add_nsswitch_libs($self, @module_names) {
  0         0  
  0         0  
  0         0  
145 0 0       0 if (!@module_names) {
146 0         0 my %seen;
147 0         0 for ($self->parse_nsswitch_conf) {
148 0 0       0 next unless ref eq 'ARRAY';
149 0         0 ++$seen{$_} for @$_;
150             }
151 0         0 @module_names= sort keys %seen;
152             }
153 0         0 my $abs= $self->src_abs;
154 0         0 my @libpath= $self->src_lib_path_list;
155 0         0 ns_module: for (@module_names) {
156 0         0 my $pattern= 'libnss_'.$_.'.*';
157 0         0 for my $libdir (@libpath) {
158 0         0 $log->tracef("Look for %s in %s", $pattern, $libdir);
159 0 0       0 if (my @match= glob $abs . $pattern) {
160 0         0 $self->add(map substr($_, length $abs), @match);
161 0         0 next ns_module;
162             }
163             }
164 0         0 $log->warn("glibc nss module not found: $_");
165             }
166             }
167              
168              
169 1     1 1 7 sub add_passwd($self, %options) {
  1         22  
  1         2  
  1         2  
170             # If the dst_userdb hasn't been created, create it by filtering the src_userdb by which
171             # group and user ids have been seen during the export.
172             my $db= $self->dst_userdb // Sys::Export::Unix::UserDB->new(
173 1   0     6 auto_import => ($self->{src_userdb} //= $self->_build_src_userdb),
      33        
174             );
175 1         5 $db->group($_) for keys $self->dst_gid_used->%*;
176 1         6 $db->user($_) for keys $self->dst_uid_used->%*;
177 1         4 $db->save(\my %contents);
178 1   50     5 my $etc_path= $options{etc_path} // 'etc';
179 1         7 $self->add([ dir755 => "$etc_path", { uid => 0, gid => 0 }]);
180 1         7 $self->add([ file644 => "$etc_path/passwd", $contents{passwd}, { uid => 0, gid => 0 }]);
181 1         6 $self->add([ file600 => "$etc_path/shadow", $contents{shadow}, { uid => 0, gid => 0 }]);
182 1         5 $self->add([ file644 => "$etc_path/group", $contents{group}, { uid => 0, gid => 0 }]);
183 1         3 $self;
184             }
185              
186              
187 0     0 1   sub add_localtime($self, $tz_name) {
  0            
  0            
  0            
188 0 0 0       if (exists $self->{dst_path_set}{"usr/share/zoneinfo/$tz_name"}
    0 0        
    0          
189             || ($self->_dst->can('dst_abs') && -f $self->_dst->dst_abs . $tz_name)
190             ) {
191             # zoneinfo is exported, and includes this timezone, so symlink to it
192 0           $self->add([ sym => "etc/localtime" => "../usr/share/zoneinfo/$tz_name" ]);
193             }
194             elsif (defined (my $src_path= $self->_src_abs_path("usr/share/zoneinfo/$tz_name"))) {
195 0           $self->add([ file644 => 'etc/localtime', { data_path => $self->src_abs . $src_path } ]);
196             }
197             elsif (defined (my $path= abs_path("/usr/share/zoneinfo/$tz_name"))) {
198 0           $self->add([ file644 => 'etc/localtime', { data_path => $path } ]);
199             }
200             else {
201 0           croak "Can't find 'usr/share/zoneinfo/$tz_name' in destination, source, or host filesystem";
202             }
203             }
204              
205             # Avoiding dependency on namespace::clean
206             delete @{Sys::Export::Linux::}{qw( croak carp confess abs_path filedata )};
207             1;
208              
209             __END__