File Coverage

blib/lib/Sys/Export.pm
Criterion Covered Total %
statement 77 86 89.5
branch 38 52 73.0
condition 13 21 61.9
subroutine 23 29 79.3
pod 18 20 90.0
total 169 208 81.2


line stmt bran cond sub pod time code
1             package Sys::Export;
2              
3             our $VERSION = '0.003'; # VERSION
4             # ABSTRACT: Export a subset of an OS file tree, for chroot/initrd
5              
6 9     9   754994 use v5.26;
  9         39  
7 9     9   50 use warnings;
  9         22  
  9         634  
8 9     9   76 use experimental qw( signatures );
  9         20  
  9         60  
9 9     9   1620 use Carp;
  9         27  
  9         798  
10 9     9   75 use Scalar::Util qw( blessed looks_like_number );
  9         21  
  9         510  
11 9     9   52 use Exporter ();
  9         17  
  9         2150  
12             BEGIN {
13             # Fcntl happily exports macros that don't exist, then fails at runtime.
14             # Replace non-existent test macros with 'false', and nonexistent modes with 0.
15 9     9   87 require Fcntl;
16 153 100       4865 eval { Fcntl->can($_)->($_ =~ /_IS/? (0) : ()); 1 }? Fcntl->import($_) : eval "sub $_ { 0 }"
  135         33105  
17 9 100   4 0 62 for qw( S_ISREG S_ISDIR S_ISLNK S_ISBLK S_ISCHR S_ISFIFO S_ISSOCK S_ISWHT
  4     0 0 48  
  0         0  
18             S_IFREG S_IFDIR S_IFLNK S_IFBLK S_IFCHR S_IFIFO S_IFSOCK S_IFWHT S_IFMT );
19             }
20             our @EXPORT_OK= qw(
21             isa_exporter isa_export_dst isa_userdb isa_user isa_group exporter isa_hash isa_array isa_int
22             add skip find finish rewrite_path rewrite_user rewrite_group expand_stat_shorthand
23             S_ISREG S_ISDIR S_ISLNK S_ISBLK S_ISCHR S_ISFIFO S_ISSOCK S_ISWHT
24             S_IFREG S_IFDIR S_IFLNK S_IFBLK S_IFCHR S_IFIFO S_IFSOCK S_IFWHT S_IFMT
25             );
26             our %EXPORT_TAGS= (
27             basic_methods => [qw( exporter add skip find finish rewrite_path rewrite_user rewrite_group )],
28             isa => [qw( isa_exporter isa_export_dst isa_userdb isa_user isa_group isa_hash isa_array isa_int )],
29             stat_modes => [qw( S_IFREG S_IFDIR S_IFLNK S_IFBLK S_IFCHR S_IFIFO S_IFSOCK S_IFWHT S_IFMT )],
30             stat_tests => [qw( S_ISREG S_ISDIR S_ISLNK S_ISBLK S_ISCHR S_ISFIFO S_ISSOCK S_ISWHT )],
31             );
32             my ($is_module_name, $require_module);
33              
34             # optional dependency on Module::Runtime. This way if there's any bug in my cheap
35             # substitute, the fix is to just install the official module.
36             if (eval { require Module::Runtime; }) {
37             $is_module_name= \&Module::Runtime::is_module_name;
38             $require_module= \&Module::Runtime::require_module;
39             } else {
40             $is_module_name= sub { $_[0] =~ /^[A-Z_a-z][0-9A-Z_a-z]*(?:::[0-9A-Z_a-z]+)*\z/ };
41             $require_module= sub { require( ($_[0] =~ s{::}{/}gr).'.pm' ) };
42             }
43              
44              
45             sub import {
46 15     15   58 my $class= $_[0];
47 15         40 my $caller= caller;
48 15         31 my %ctor_opts;
49 15         97 for (my $i= 1; $i < $#_; ++$i) {
50 28 50       156 if (ref $_[$i] eq 'HASH') {
    100          
51 0         0 %ctor_opts= ( %ctor_opts, %{ splice(@_, $i--, 1) } );
  0         0  
52             }
53             elsif ($_[$i] =~ /^-(type|src|dst|tmp|src_userdb|dst_userdb|rewrite_path|rewrite_user|rewrite_group)\z/) {
54 3         10 $ctor_opts{$1}= (splice @_, $i--, 2)[1];
55             }
56             }
57 15 100       91 if (keys %ctor_opts) {
58 1         3 init_global_exporter(%ctor_opts);
59             # caller requested the global exporter instance, so also include the standard methods
60             # unless it looks like they were more selective about what to import.
61 1 50       10 push @_, 'exporter', ':basic_methods'
62             unless grep /^(add|:.*methods)\z/, @_;
63             }
64 15         186051 goto \&Exporter::import;
65             }
66              
67             our $exporter;
68 0     0 1 0 sub exporter { $exporter }
69              
70             our %osname_to_class= (
71             linux => 'Linux',
72             );
73              
74 1     1 1 1 sub init_global_exporter(%config) {
  1         2  
  1         6  
75 1   33     3 my $type= delete $config{type} // $^O;
76             # remap known OS names
77 1   33     13 my $class= $osname_to_class{$type} // $type;
78             # prefix bare names with namespace
79 1 50       5 $class= "Sys::Export::$class" unless $class =~ /::/;
80 1 50       2 $is_module_name->($class) or croak "Invalid module name '$class'";
81             # if it fails, die with 'croak'
82 1 50       41 eval { $require_module->($class) } or croak "$@";
  1         3  
83             # now construct one
84 1         6 $exporter= $class->new(%config);
85             }
86              
87              
88 3     3 1 17 sub add { $exporter->add(@_) }
89 1     1 1 4 sub skip { $exporter->skip(@_) }
90 1     1 1 8 sub find { $exporter->src_find(@_) }
91 1     1 1 3 sub finish { $exporter->finish(@_) }
92 0     0 1 0 sub rewrite_path { $exporter->rewrite_path(@_) }
93 0     0 1 0 sub rewrite_user { $exporter->rewrite_user(@_) }
94 0     0 1 0 sub rewrite_group { $exporter->rewrite_group(@_) }
95              
96              
97 76     76 1 272 sub isa_hash :prototype($) { ref $_[0] eq 'HASH' }
98 56     56 1 211 sub isa_array :prototype($) { ref $_[0] eq 'ARRAY' }
99 25 50   25 1 128 sub isa_int :prototype($) { looks_like_number($_[0]) && int($_[0]) == $_[0] }
100 0 0   0 1 0 sub isa_exporter :prototype($) { blessed($_[0]) && $_[0]->isa('Sys::Export::Exporter') }
101 7 100 66 7 1 146 sub isa_export_dst :prototype($) { blessed($_[0]) && $_[0]->can('add') && $_[0]->can('finish') }
102 11 100 66 11 1 79 sub isa_userdb :prototype($) { blessed($_[0]) && $_[0]->can('user') && $_[0]->can('group') }
103 18 100   18 1 90 sub isa_user :prototype($) { blessed($_[0]) && $_[0]->isa('Sys::Export::Unix::UserDB::User') }
104 18 100   18 1 103 sub isa_group :prototype($) { blessed($_[0]) && $_[0]->isa('Sys::Export::Unix::UserDB::Group') }
105              
106              
107 4     4   8 sub _parse_major_minor_data($attrs, $data) {
  4         8  
  4         8  
  4         5  
108 4 100       26 @{$attrs}{'rdev_major','rdev_minor'}= isa_array $data? @$data : split(/[,:]/, $data);
  4         17  
109             }
110             our %_mode_alias= (
111             file => [ S_IFREG, sub { 0666 & ~umask } ],
112             dir => [ S_IFDIR, sub { 0777 & ~umask } ],
113             sym => [ S_IFLNK, sub { 0777 } ],
114             blk => [ S_IFBLK, sub { 0666 & ~umask }, \&_parse_major_minor_data, ],
115             chr => [ S_IFCHR, sub { 0666 & ~umask }, \&_parse_major_minor_data, ],
116             fifo => [ S_IFIFO, sub { 0666 & ~umask } ],
117             sock => [ S_IFSOCK, sub { 0666 & ~umask } ],
118             );
119             our @_mode_by_int;
120             $_mode_by_int[$_->[0]]= $_ for values %_mode_alias;
121             $_mode_by_int[0]= undef; # don't map 0 to any mode
122              
123             sub expand_stat_shorthand {
124 25 100 66 25 1 154603 @_= @{$_[0]} if @_ == 1 && isa_array $_[0];
  16         69  
125 25 100 100     91 my %attrs= @_ > 2 && isa_hash $_[-1]? %{ pop @_ } : ();
  9         26  
126 25         61 my ($mode, $name, $data)= @_;
127 25         52 my $mode_desc;
128 25 50       51 if (isa_int $mode) {
129 0 0       0 $mode_desc= $_mode_by_int[$mode & S_IFMT]
130             or carp sprintf("Numeric mode %x doesn't match any known node types", $mode);
131             }
132             else {
133 25 50       159 $mode =~ /^([a-z]+)([0-7]+)?\z/
134             or croak "Invalid mode '$mode': expected number, or prefix file/dir/sym/blk/chr/fifo/sock followed by octal permissions";
135 25 50       105 $mode_desc= $_mode_alias{$1}
136             or croak "Unknown mode alias '$1'";
137 25 100       99 $mode= $mode_desc->[0] | (defined $2? oct($2) : $mode_desc->[1]->());
138             }
139 25         65 $attrs{mode}= $mode;
140 25 50       54 length $name or croak "Name must be nonzero length";
141 25         47 $attrs{name}= $name;
142 25 100       78 if (defined $data) {
143 16 100 66     75 if ($mode_desc && $mode_desc->[2]) {
144 4         14 $mode_desc->[2]->(\%attrs, $data);
145             } else {
146 12         25 $attrs{data}= $data;
147             }
148             }
149 25         175 return %attrs;
150             }
151              
152             1;
153              
154             __END__