File Coverage

blib/lib/Sys/Export/CPIO.pm
Criterion Covered Total %
statement 64 89 71.9
branch 18 52 34.6
condition 23 50 46.0
subroutine 11 14 78.5
pod 6 6 100.0
total 122 211 57.8


line stmt bran cond sub pod time code
1             package Sys::Export::CPIO;
2              
3             # ABSTRACT: Write CPIO archives needed for Linux initrd
4             our $VERSION = '0.005'; # VERSION
5              
6 1     1   185653 use v5.26;
  1         3  
7 1     1   4 use warnings;
  1         2  
  1         38  
8 1     1   4 use experimental qw( signatures );
  1         15  
  1         5  
9 1     1   106 use Fcntl qw( S_IFDIR S_IFMT );
  1         2  
  1         44  
10 1     1   3 use Scalar::Util 'blessed';
  1         1  
  1         37  
11 1     1   419 use Encode ();
  1         11916  
  1         49  
12 1     1   9 use Carp;
  1         3  
  1         1191  
13             our @CARP_NOT= qw( Sys::Export Sys::Export::Unix );
14             require Sys::Export::Unix; # for _dev_major_minor
15              
16              
17 1     1 1 146471 sub new($class, $f, @attrs) {
  1         2  
  1         1  
  1         2  
  1         2  
18 1 50       4 croak "Expected even-length key/value list after filename" if @attrs & 1;
19 1         2 my $filename;
20             my $fh= blessed $f && $f->can('print')? $f
21 1 50 33     4 : do { $filename= $f; open my $x, '>:raw', $f or die "open($f): $!"; $x };
  1 50       2  
  1         15  
  1         2  
22 1         21 my $self= bless {
23             fh => $fh,
24             seen_inode => {},
25             ino => 0,
26             virtual_inodes => 1,
27             filename => $filename,
28             autoclose => defined $filename,
29             }, $class;
30              
31 1         3 while (@attrs) {
32 0         0 my ($attr, $val)= splice(@attrs, 0, 2);
33 0         0 $self->$attr($val);
34             }
35              
36 1         3 $self;
37             }
38              
39              
40             sub autoclose {
41 0 0   0 1 0 $_[0]{autoclose}= !!$_[1] if @_ > 1; # cast to boolean
42             $_[0]{autoclose}
43 0         0 }
44              
45             sub filename {
46 0 0   0 1 0 $_[0]{filename}= $_[1] if @_ > 1;
47             $_[0]{filename}
48 0         0 }
49              
50             sub virtual_inodes {
51 3 50   3 1 6 $_[0]{virtual_inodes}= !!$_[1] if @_ > 1; # cast to boolean
52             $_[0]{virtual_inodes}
53 3         9 }
54              
55              
56 3     3 1 5842 sub add($self, $fileinfo) {
  3         5  
  3         3  
  3         4  
57             my ($dev, $dev_major, $dev_minor, $ino, $mode, $nlink, $uid, $gid, $rdev, $rdev_major, $rdev_minor, $mtime)
58 3         4 = @{$fileinfo}{qw( dev dev_major dev_minor ino mode nlink uid gid rdev rdev_major rdev_minor mtime )};
  3         11  
59 3         7 my $name= _fileinfo_get_name_bytes($fileinfo);
60             # best-effort to extract major/minor from dev and rdev, unless user specified them
61 3 50 0     8 ($dev_major, $dev_minor)= Sys::Export::Unix::_dev_major_minor($dev)
      33        
62             if defined $dev and !defined $dev_major || !defined $dev_minor;
63 3 100 33     13 ($rdev_major, $rdev_minor)= Sys::Export::Unix::_dev_major_minor($rdev)
      66        
64             if defined $rdev and !defined $rdev_major || !defined $rdev_minor;
65 3 50       6 defined $mode or croak "require 'mode'";
66 3 50       6 defined $name or croak "require 'name'";
67              
68             # Die on encoding mistakes
69 3         4 my ($size, $data_ref)= (0);
70 3 50       7 if (defined $fileinfo->{data}) {
71 0 0       0 $data_ref= ref $fileinfo->{data}? $fileinfo->{data} : \$fileinfo->{data};
72 0 0 0     0 croak "->{data} must be 8-bit, but encountered wide character at $name"
73             if utf8::is_utf8($$data_ref) && !utf8::downgrade($$data_ref, 1);
74 0         0 $size= length $$data_ref;
75             }
76             # Handle hard links
77 3 50 66     22 if ($nlink && $nlink > 1 && ($mode & S_IFMT) != S_IFDIR) {
    50 66        
78 0         0 my $hardlink_key= "$dev_major:$dev_minor:$ino";
79 0 0       0 if ($self->virtual_inodes) {
80             # the previous virtual inode is stored in the seen_inode hash
81 0 0       0 if ($ino= $self->{seen_inode}{$hardlink_key}) {
82 0         0 $size= 0;
83             } else {
84 0         0 $ino= $self->{seen_inode}{$hardlink_key}= ++$self->{ino};
85             }
86 0         0 ($dev_major, $dev_minor)= (0,0);
87             }
88             else {
89 0 0       0 $size= 0 if $self->{seen_inode}{$hardlink_key}++;
90             }
91             }
92             elsif ($self->virtual_inodes) {
93 3         5 ($dev_major, $dev_minor, $ino)= (0,0);
94             }
95 3   33     30 $ino //= ++$self->{ino};
96              
97 3   50     43 my $header= sprintf "070701%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%s\0%s",
      50        
      100        
      100        
      50        
      50        
      100        
      100        
98             $ino, $mode, $uid//0, $gid//0, $nlink//1, $mtime//0, $size,
99             $dev_major//0, $dev_minor//0, $rdev_major//0, $rdev_minor//0,
100             1+length $name, 0, $name,
101             "\0"x((4 - ((13*8+6+length($name)+1) & 3)) & 3); # pad to multiple of 4
102 3 50       5 die "BUG" if length $header & 3;
103              
104 3 50       17 $self->{fh}->print($header) || die "write: $!";
105             # This is written in multiple parts like this because $fileinfo->{data} might be a memory-
106             # mapped file, and optimal to pass that directly back to fprint without a perl-side
107             # concatenation.
108 3 50 0     24 $self->{fh}->print($$data_ref) || die "write: $!"
109             if $size;
110 3 50 0     4 $self->{fh}->print("\0"x(4-($size & 3))) || die "write: $!"
111             if $size & 3; # pad to multiple of 4
112 3         7 $self;
113             }
114              
115 3     3   5 sub _fileinfo_get_name_bytes($f) {
  3         3  
  3         2  
116 3 50       8 if (defined (my $name= $f->{name})) {
    0          
117 3 50 33     11 !utf8::is_utf8($name) or utf8::downgrade($name, 1)
118             or croak "->{name} must be encoded as bytes, but encountered wide character at $name";
119 3         5 return $name;
120             } elsif (defined $f->{uname}) {
121 0           return Encode::encode('UTF-8', $f->{uname}, Encode::FB_CROAK|Encode::LEAVE_SRC);
122             } else {
123 0           croak "Must specify either 'name' or 'uname'";
124             }
125             }
126              
127              
128 0     0 1   sub finish($self) {
  0            
  0            
129 0           $self->add({ mode => 0, ino => 0, name => 'TRAILER!!!' });
130 0           $self->{fh}->flush;
131 0 0         $self->{fh}->close if $self->autoclose;
132 0           $self;
133             }
134              
135             # Avoiding dependency on namespace::clean
136             delete @{Sys::Export::CPIO::}{qw( S_IFDIR S_IFMT blessed carp croak confess )};
137             1;
138              
139             __END__