File Coverage

blib/lib/Sys/Export/CPIO.pm
Criterion Covered Total %
statement 56 76 73.6
branch 15 40 37.5
condition 23 46 50.0
subroutine 10 13 76.9
pod 6 6 100.0
total 110 181 60.7


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.003'; # VERSION
5              
6 1     1   304734 use v5.26;
  1         5  
7 1     1   5 use warnings;
  1         4  
  1         61  
8 1     1   7 use experimental qw( signatures );
  1         2  
  1         6  
9 1     1   205 use Fcntl qw( S_IFDIR S_IFMT );
  1         2  
  1         77  
10 1     1   6 use Scalar::Util 'blessed';
  1         2  
  1         89  
11 1     1   6 use Carp;
  1         2  
  1         1646  
12             our @CARP_NOT= qw( Sys::Export Sys::Export::Unix );
13             require Sys::Export::Unix; # for _dev_major_minor
14              
15              
16 1     1 1 285243 sub new($class, $f, @attrs) {
  1         4  
  1         3  
  1         2  
  1         2  
17 1 50       6 croak "Expected even-length key/value list after filename" if @attrs & 1;
18 1         2 my $filename;
19             my $fh= blessed $f && $f->can('print')? $f
20 1 50 33     6 : do { $filename= $f; open my $x, '>:raw', $f or die "open($f): $!"; $x };
  1 50       2  
  1         27  
  1         4  
21 1         10 my $self= bless {
22             fh => $fh,
23             seen_inode => {},
24             ino => 0,
25             virtual_inodes => 1,
26             filename => $filename,
27             autoclose => defined $filename,
28             }, $class;
29              
30 1         4 while (@attrs) {
31 0         0 my ($attr, $val)= splice(@attrs, 0, 2);
32 0         0 $self->$attr($val);
33             }
34              
35 1         4 $self;
36             }
37              
38              
39             sub autoclose {
40 0 0   0 1 0 $_[0]{autoclose}= !!$_[1] if @_ > 1; # cast to boolean
41             $_[0]{autoclose}
42 0         0 }
43              
44             sub filename {
45 0 0   0 1 0 $_[0]{filename}= $_[1] if @_ > 1;
46             $_[0]{filename}
47 0         0 }
48              
49             sub virtual_inodes {
50 3 50   3 1 10 $_[0]{virtual_inodes}= !!$_[1] if @_ > 1; # cast to boolean
51             $_[0]{virtual_inodes}
52 3         16 }
53              
54              
55 3     3 1 9974 sub add($self, $fileinfo) {
  3         6  
  3         8  
  3         6  
56             my ($dev, $dev_major, $dev_minor, $ino, $mode, $nlink, $uid, $gid, $rdev, $rdev_major, $rdev_minor, $mtime, $name)
57 3         7 = @{$fileinfo}{qw( dev dev_major dev_minor ino mode nlink uid gid rdev rdev_major rdev_minor mtime name )};
  3         19  
58             # best-effort to extract major/minor from dev and rdev, unless user specified them
59 3 50 0     13 ($dev_major, $dev_minor)= Sys::Export::Unix::_dev_major_minor($dev)
      33        
60             if defined $dev and !defined $dev_major || !defined $dev_minor;
61 3 100 33     24 ($rdev_major, $rdev_minor)= Sys::Export::Unix::_dev_major_minor($rdev)
      66        
62             if defined $rdev and !defined $rdev_major || !defined $rdev_minor;
63 3 50       10 defined $mode or croak "require 'mode'";
64 3 50       9 defined $name or croak "require 'name'";
65              
66 3   50     18 my $size= length($fileinfo->{data}) // 0;
67             # Handle hard links
68 3 50 66     37 if ($nlink && $nlink > 1 && ($mode & S_IFMT) != S_IFDIR) {
    50 66        
69 0         0 my $hardlink_key= "$dev_major:$dev_minor:$ino";
70 0 0       0 if ($self->virtual_inodes) {
71             # the previous virtual inode is stored in the seen_inode hash
72 0 0       0 if ($ino= $self->{seen_inode}{$hardlink_key}) {
73 0         0 $size= 0;
74             } else {
75 0         0 $ino= $self->{seen_inode}{$hardlink_key}= ++$self->{ino};
76             }
77 0         0 ($dev_major, $dev_minor)= (0,0);
78             }
79             else {
80 0 0       0 $size= 0 if $self->{seen_inode}{$hardlink_key}++;
81             }
82             }
83             elsif ($self->virtual_inodes) {
84 3         9 ($dev_major, $dev_minor, $ino)= (0,0);
85             }
86 3   33     18 $ino //= ++$self->{ino};
87              
88 3   50     72 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        
89             $ino, $mode, $uid//0, $gid//0, $nlink//1, $mtime//0, $size,
90             $dev_major//0, $dev_minor//0, $rdev_major//0, $rdev_minor//0,
91             1+length $name, 0, $name,
92             "\0"x((4 - ((13*8+6+length($name)+1) & 3)) & 3); # pad to multiple of 4
93 3 50       10 die "BUG" if length $header & 3;
94              
95 3 50       27 $self->{fh}->print($header) || die "write: $!";
96             # This is written in multiple parts like this because $fileinfo->{data} might be a File::Map,
97             # and optimal to pass that directly back to fprint without a perl-side concatenation.
98 3 50 0     66 $self->{fh}->print($fileinfo->{data}) || die "write: $!"
99             if $size;
100 3 50 0     10 $self->{fh}->print("\0"x(4-($size & 3))) || die "write: $!"
101             if $size & 3; # pad to multiple of 4
102 3         12 $self;
103             }
104              
105              
106 0     0 1   sub finish($self) {
  0            
  0            
107 0           $self->add({ mode => 0, ino => 0, name => 'TRAILER!!!' });
108 0           $self->{fh}->flush;
109 0 0         $self->{fh}->close if $self->autoclose;
110 0           $self;
111             }
112              
113             # Avoiding dependency on namespace::clean
114 1     1   9 { no strict 'refs';
  1         5  
  1         121  
115             delete @{"Sys::Export::CPIO::"}{qw(
116             S_IFDIR S_IFMT blessed carp croak
117             )}
118             }
119              
120             1;
121              
122             __END__