| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Sys::Export::Unix::WriteFS; |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# ABSTRACT: An export target that writes files to a directory in the host filesystem |
|
4
|
|
|
|
|
|
|
our $VERSION = '0.003'; # VERSION |
|
5
|
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
|
|
7
|
4
|
|
|
4
|
|
53
|
use v5.26; |
|
|
4
|
|
|
|
|
19
|
|
|
8
|
4
|
|
|
4
|
|
54
|
use warnings; |
|
|
4
|
|
|
|
|
11
|
|
|
|
4
|
|
|
|
|
262
|
|
|
9
|
4
|
|
|
4
|
|
21
|
use experimental qw( signatures ); |
|
|
4
|
|
|
|
|
5
|
|
|
|
4
|
|
|
|
|
39
|
|
|
10
|
4
|
|
|
4
|
|
894
|
use Carp qw( croak carp ); |
|
|
4
|
|
|
|
|
9
|
|
|
|
4
|
|
|
|
|
455
|
|
|
11
|
|
|
|
|
|
|
our @CARP_NOT= qw( Sys::Export::Unix ); |
|
12
|
4
|
|
|
4
|
|
47
|
use Cwd qw( abs_path ); |
|
|
4
|
|
|
|
|
7
|
|
|
|
4
|
|
|
|
|
349
|
|
|
13
|
4
|
|
|
4
|
|
22
|
use Sys::Export qw( :stat_modes :stat_tests isa_hash ); |
|
|
4
|
|
|
|
|
7
|
|
|
|
4
|
|
|
|
|
26
|
|
|
14
|
|
|
|
|
|
|
require Sys::Export::Unix; |
|
15
|
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
sub new { |
|
17
|
5
|
|
|
5
|
1
|
11
|
my $class= shift; |
|
18
|
5
|
50
|
33
|
|
|
59
|
my %attrs= @_ == 1 && isa_hash $_[0]? %{$_[0]} |
|
|
0
|
50
|
|
|
|
0
|
|
|
19
|
|
|
|
|
|
|
: !(@_ & 1)? @_ |
|
20
|
|
|
|
|
|
|
: croak "Expected hashref or even-length list"; |
|
21
|
|
|
|
|
|
|
|
|
22
|
5
|
50
|
|
|
|
21
|
defined $attrs{dst} or croak "Require 'dst' attribute"; |
|
23
|
5
|
50
|
|
|
|
45
|
my $dst_abs= abs_path($attrs{dst} =~ s,(?<=[^/])$,/,r) |
|
24
|
|
|
|
|
|
|
or croak "dst directory '$attrs{dst}' does not exist"; |
|
25
|
5
|
50
|
|
|
|
272
|
length $dst_abs > 1 |
|
26
|
|
|
|
|
|
|
or croak "cowardly refusing to export to '$dst_abs'"; |
|
27
|
5
|
|
|
|
|
18
|
$attrs{dst_abs}= "$dst_abs/"; |
|
28
|
|
|
|
|
|
|
|
|
29
|
5
|
|
33
|
|
|
24
|
$attrs{tmp} //= do { |
|
30
|
5
|
|
|
|
|
62
|
my $tmp= File::Temp->newdir; |
|
31
|
|
|
|
|
|
|
# Make sure can rename() from this $tmp to $dst |
|
32
|
5
|
|
|
|
|
2305
|
my ($tmp_dev)= stat "$tmp/"; |
|
33
|
5
|
|
|
|
|
116
|
my ($dst_dev)= stat $attrs{dst}; |
|
34
|
|
|
|
|
|
|
$tmp= File::Temp->newdir(DIR => $attrs{dst_abs}) |
|
35
|
5
|
50
|
|
|
|
86
|
if $tmp_dev != $dst_dev; |
|
36
|
5
|
|
|
|
|
24
|
$tmp; |
|
37
|
|
|
|
|
|
|
}; |
|
38
|
|
|
|
|
|
|
|
|
39
|
5
|
|
|
|
|
13
|
my $self= bless \%attrs, $class; |
|
40
|
|
|
|
|
|
|
|
|
41
|
5
|
|
|
|
|
26
|
return $self; |
|
42
|
|
|
|
|
|
|
} |
|
43
|
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
|
|
45
|
0
|
|
|
0
|
1
|
0
|
sub dst($self) { $self->{dst} } |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
46
|
73
|
|
|
73
|
1
|
113
|
sub dst_abs($self) { $self->{dst_abs} } |
|
|
73
|
|
|
|
|
96
|
|
|
|
73
|
|
|
|
|
91
|
|
|
|
73
|
|
|
|
|
1194
|
|
|
47
|
16
|
|
|
16
|
1
|
55
|
sub tmp($self) { $self->{tmp} } |
|
|
16
|
|
|
|
|
29
|
|
|
|
16
|
|
|
|
|
20
|
|
|
|
16
|
|
|
|
|
95
|
|
|
48
|
|
|
|
|
|
|
|
|
49
|
0
|
|
|
0
|
1
|
0
|
sub on_collision($self, @value) { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
50
|
0
|
0
|
|
|
|
0
|
$self->{on_collision}= $value[0] if @value; |
|
51
|
|
|
|
|
|
|
$self->{on_collision} |
|
52
|
0
|
|
|
|
|
0
|
} |
|
53
|
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
# a hashref tracking files with link-count higher than 1, so that hardlinks can be preserved. |
|
55
|
|
|
|
|
|
|
# the keys are "$dev:$ino" |
|
56
|
4
|
|
100
|
4
|
|
7
|
sub _link_map($self) { $self->{link_map} //= {} } |
|
|
4
|
|
|
|
|
6
|
|
|
|
4
|
|
|
|
|
6
|
|
|
|
4
|
|
|
|
|
33
|
|
|
57
|
|
|
|
|
|
|
|
|
58
|
5
|
|
|
5
|
|
13
|
sub DESTROY($self, @) { |
|
|
5
|
|
|
|
|
14
|
|
|
|
5
|
|
|
|
|
11
|
|
|
59
|
5
|
100
|
|
|
|
64
|
$self->finish if $self->{_delayed_apply_stat}; |
|
60
|
|
|
|
|
|
|
} |
|
61
|
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
|
|
63
|
21
|
|
|
21
|
1
|
69
|
sub add($self, $file) { |
|
|
21
|
|
|
|
|
31
|
|
|
|
21
|
|
|
|
|
25
|
|
|
|
21
|
|
|
|
|
30
|
|
|
64
|
|
|
|
|
|
|
croak "Path must start with a name, not slash or dot: '$file->{name}'" |
|
65
|
21
|
50
|
|
|
|
164
|
if $file->{name} =~ m{(^|^\.|^\.\.)(/|\z)}; |
|
66
|
21
|
|
33
|
|
|
65
|
my $mode= $file->{mode} // croak "attribute 'mode' is required, for '$file->{name}'"; |
|
67
|
|
|
|
|
|
|
# Does it already exist? |
|
68
|
21
|
|
|
|
|
47
|
my $dst_abs= $self->dst_abs . $file->{name}; |
|
69
|
21
|
|
|
|
|
36
|
my %old; |
|
70
|
21
|
50
|
|
|
|
874
|
if (@old{qw( dev ino mode nlink uid gid rdev size atime mtime)}= lstat($dst_abs)) { |
|
71
|
|
|
|
|
|
|
# If the user says to ignore it, nothing to do: |
|
72
|
0
|
|
0
|
|
|
0
|
my $action= $self->on_collision // 'ignore_if_same'; |
|
73
|
0
|
0
|
|
|
|
0
|
$action= $action->($dst_abs, $file) |
|
74
|
|
|
|
|
|
|
if ref $action eq 'CODE'; |
|
75
|
0
|
0
|
|
|
|
0
|
return !!0 if $action eq 'ignore'; |
|
76
|
0
|
0
|
0
|
|
|
0
|
return !!0 if $action eq 'ignore_if_same' && $self->_croak_if_different($file, \%old); |
|
77
|
0
|
0
|
|
|
|
0
|
croak "Unknown on_collision action '$action'" unless $action eq 'overwrite'; |
|
78
|
|
|
|
|
|
|
# overwrite, but directory might not be empty, so handle that later |
|
79
|
0
|
0
|
|
|
|
0
|
unlink $dst_abs unless S_ISDIR($old{mode}); |
|
80
|
|
|
|
|
|
|
} |
|
81
|
|
|
|
|
|
|
return S_ISREG($mode)? $self->_add_file($file) |
|
82
|
|
|
|
|
|
|
: S_ISDIR($mode)? $self->_add_dir($file, (defined $old{mode}? \%old : undef)) |
|
83
|
|
|
|
|
|
|
: S_ISLNK($mode)? $self->_add_symlink($file) |
|
84
|
|
|
|
|
|
|
: (S_ISBLK($mode) || S_ISCHR($mode))? $self->_add_devnode($file) |
|
85
|
|
|
|
|
|
|
: S_ISFIFO($mode)? $self->_add_fifo($file) |
|
86
|
|
|
|
|
|
|
: S_ISSOCK($mode)? $self->_add_socket($file) |
|
87
|
|
|
|
|
|
|
: croak "Can't export ".(S_ISWHT($mode)? 'whiteout entries' : '(unknown)') |
|
88
|
21
|
50
|
66
|
|
|
231
|
.': "'.($file->{src_path} // $file->{data_path} // $file->{name}).'"' |
|
|
|
0
|
0
|
|
|
|
|
|
|
|
50
|
0
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
} |
|
90
|
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
our %_mode_name= ( |
|
92
|
|
|
|
|
|
|
S_IFREG , 'file', |
|
93
|
|
|
|
|
|
|
S_IFDIR , 'dir', |
|
94
|
|
|
|
|
|
|
S_IFLNK , 'symlink', |
|
95
|
|
|
|
|
|
|
S_IFBLK , 'block device', |
|
96
|
|
|
|
|
|
|
S_IFCHR , 'char device', |
|
97
|
|
|
|
|
|
|
S_IFIFO , 'fifo', |
|
98
|
|
|
|
|
|
|
S_IFSOCK , 'socket', |
|
99
|
|
|
|
|
|
|
(S_IFWHT? ( |
|
100
|
|
|
|
|
|
|
S_IFWHT , 'whiteout', |
|
101
|
|
|
|
|
|
|
):()), |
|
102
|
|
|
|
|
|
|
); |
|
103
|
0
|
|
0
|
0
|
|
0
|
sub _mode_name($mode) { $_mode_name{($mode & S_IFMT)} // '(unknown)' } |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
104
|
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
# Compare two dirents and croak if new is not equivalent to old |
|
106
|
0
|
|
|
0
|
|
0
|
sub _croak_if_different($self, $file, $old) { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
107
|
0
|
|
|
|
|
0
|
my $dst_abs= $self->dst_abs . $file->{name}; |
|
108
|
|
|
|
|
|
|
croak "Attempt to write "._mode_name($file->{mode})." overtop existing "._mode_name($old->{mode})." at $dst_abs" |
|
109
|
0
|
0
|
|
|
|
0
|
if ($file->{mode} & S_IFMT) != ($old->{mode} & S_IFMT); |
|
110
|
|
|
|
|
|
|
croak "Attempt to write ownership $file->{uid}:$file->{gid} to $dst_abs which was previously $old->{uid}:$old->{gid}" |
|
111
|
0
|
0
|
0
|
|
|
0
|
if (defined $file->{uid} && $file->{uid} != $old->{uid}) || (defined $file->{gid} && $file->{gid} != $old->{gid}); |
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
112
|
|
|
|
|
|
|
# For symlinks, compare only the content of the link. Permissions are ignored. |
|
113
|
0
|
0
|
|
|
|
0
|
if (S_ISLNK($file->{mode})) { |
|
114
|
0
|
|
|
|
|
0
|
my $targ= readlink $dst_abs; |
|
115
|
|
|
|
|
|
|
croak "Attempt to rewrite symlink $dst_abs from $targ to $file->{data}" |
|
116
|
0
|
0
|
|
|
|
0
|
if $targ ne $file->{data}; |
|
117
|
0
|
|
|
|
|
0
|
return !!0; |
|
118
|
|
|
|
|
|
|
} |
|
119
|
|
|
|
|
|
|
# For everything else, compare permissions |
|
120
|
|
|
|
|
|
|
croak "Attempt to write permissions ".($file->{mode} & ~S_IFMT)." overtop existing ".($old->{mode} & ~S_IFMT)." at $dst_abs" |
|
121
|
0
|
0
|
|
|
|
0
|
unless $file->{mode} == $old->{mode}; |
|
122
|
|
|
|
|
|
|
|
|
123
|
0
|
0
|
0
|
|
|
0
|
if (S_ISREG($file->{mode})) { |
|
|
|
0
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
# compare file contents |
|
125
|
|
|
|
|
|
|
croak "Attempt to overwrite $dst_abs with different content" |
|
126
|
|
|
|
|
|
|
if (defined $file->{size} && $file->{size} != $old->{size}) |
|
127
|
0
|
0
|
0
|
|
|
0
|
|| !_contents_same($file, $dst_abs); |
|
|
|
|
0
|
|
|
|
|
|
128
|
|
|
|
|
|
|
} |
|
129
|
|
|
|
|
|
|
elsif (S_ISBLK($file->{mode}) || S_ISCHR($file->{mode})) { |
|
130
|
|
|
|
|
|
|
# compare major/minor numbers |
|
131
|
|
|
|
|
|
|
croak "Attempt to overwrite $dst_abs with different major/minor value" |
|
132
|
0
|
0
|
|
|
|
0
|
if $file->{rdev} != $old->{rdev}; |
|
133
|
|
|
|
|
|
|
} |
|
134
|
0
|
|
|
|
|
0
|
1; |
|
135
|
|
|
|
|
|
|
} |
|
136
|
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
# Compare file contents for equality |
|
138
|
0
|
|
|
0
|
|
0
|
sub _contents_same($file, $dst_abs) { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
139
|
|
|
|
|
|
|
Sys::Export::Unix::_load_or_map_file($file->{data}, $file->{data_path}) |
|
140
|
0
|
0
|
|
|
|
0
|
unless defined $file->{data}; |
|
141
|
0
|
|
|
|
|
0
|
Sys::Export::Unix::_load_or_map_file(my $dst_data, $dst_abs); |
|
142
|
0
|
|
|
|
|
0
|
return $file->{data} eq $dst_data; |
|
143
|
|
|
|
|
|
|
} |
|
144
|
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
# compare device nodes for equality |
|
146
|
0
|
|
|
0
|
|
0
|
sub _rdev_same($file, $old) { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
147
|
|
|
|
|
|
|
# old will always have 'rdev' defined because we ran lstat. |
|
148
|
|
|
|
|
|
|
# file might not if the user specified rdev_major and rdev_minor |
|
149
|
0
|
0
|
0
|
|
|
0
|
if (defined $file->{rdev}) { |
|
|
|
0
|
|
|
|
|
|
|
150
|
0
|
|
|
|
|
0
|
return $file->{rdev} == $old->{rdev}; |
|
151
|
|
|
|
|
|
|
} elsif (defined $file->{rdev_major} && defined $file->{rdev_minor}) { |
|
152
|
0
|
|
|
|
|
0
|
my ($maj, $min)= Sys::Export::Unix::_dev_major_minor($old->{rdev}); |
|
153
|
0
|
|
0
|
|
|
0
|
return $file->{rdev_major} == $maj && $file->{rdev_minor} == $min; |
|
154
|
|
|
|
|
|
|
} else { |
|
155
|
0
|
|
|
|
|
0
|
return !!0; # not defined, so can't be same |
|
156
|
|
|
|
|
|
|
} |
|
157
|
|
|
|
|
|
|
} |
|
158
|
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
# Install a file into ->dst |
|
160
|
6
|
|
|
6
|
|
11
|
sub _add_file($self, $file) { |
|
|
6
|
|
|
|
|
10
|
|
|
|
6
|
|
|
|
|
13
|
|
|
|
6
|
|
|
|
|
9
|
|
|
161
|
6
|
|
|
|
|
13
|
my $dst= $self->dst_abs . $file->{name}; |
|
162
|
6
|
|
|
|
|
14
|
my $tmp= $file->{data_path}; |
|
163
|
|
|
|
|
|
|
# See if this is supposed to be a hardlink |
|
164
|
6
|
100
|
|
|
|
34
|
if ($file->{nlink} > 1) { |
|
165
|
1
|
50
|
|
|
|
4
|
if (defined(my $already= $self->_link_map->{"$file->{dev}:$file->{ino}"})) { |
|
166
|
|
|
|
|
|
|
# Yep, make a link of that file instead of copying again |
|
167
|
1
|
50
|
|
|
|
49
|
link($already, $dst) |
|
168
|
|
|
|
|
|
|
or croak "link($already, $dst): $!"; |
|
169
|
1
|
|
|
|
|
11
|
return !!1; |
|
170
|
|
|
|
|
|
|
} |
|
171
|
|
|
|
|
|
|
} |
|
172
|
|
|
|
|
|
|
# Record all file inodes in case a delayed hardlink is created by the caller |
|
173
|
|
|
|
|
|
|
$self->_link_map->{"$file->{dev}:$file->{ino}"}= $dst |
|
174
|
5
|
100
|
66
|
|
|
28
|
if defined $file->{dev} && defined $file->{ino}; |
|
175
|
|
|
|
|
|
|
# The caller may have created data_path within our ->tmp directory. |
|
176
|
|
|
|
|
|
|
# If not, first write the data into a temp file there. |
|
177
|
5
|
50
|
66
|
|
|
36
|
if (!defined $tmp || substr($tmp, 0, length $self->tmp) ne $self->tmp) { |
|
178
|
5
|
50
|
|
|
|
36
|
if (!defined $file->{data}) { |
|
179
|
|
|
|
|
|
|
defined $file->{data_path} |
|
180
|
0
|
0
|
|
|
|
0
|
or croak "No 'data' or 'data_path' for file $file->{name}"; |
|
181
|
0
|
|
|
|
|
0
|
Sys::Export::Unix::_load_or_map_file($file->{data}, $file->{data_path}); |
|
182
|
|
|
|
|
|
|
} |
|
183
|
5
|
|
|
|
|
25
|
$tmp= File::Temp->new(DIR => $self->tmp, UNLINK => 0); |
|
184
|
5
|
|
|
|
|
4382
|
Sys::Export::Unix::_syswrite_all($tmp, \$file->{data}); |
|
185
|
|
|
|
|
|
|
} |
|
186
|
|
|
|
|
|
|
# Apply matching permissions and ownership |
|
187
|
5
|
|
|
|
|
132
|
$self->_apply_stat("$tmp", $file); |
|
188
|
|
|
|
|
|
|
# Rename the temp file into place |
|
189
|
5
|
50
|
|
|
|
21
|
rename($tmp, $dst) or croak "rename($tmp, $dst): $!"; |
|
190
|
|
|
|
|
|
|
} |
|
191
|
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
# Install a dir into ->dst, unless it already exists |
|
193
|
8
|
|
|
8
|
|
15
|
sub _add_dir($self, $dir, $old) { |
|
|
8
|
|
|
|
|
14
|
|
|
|
8
|
|
|
|
|
51
|
|
|
|
8
|
|
|
|
|
13
|
|
|
|
8
|
|
|
|
|
12
|
|
|
194
|
8
|
|
|
|
|
19
|
my $dst_abs= $self->dst_abs . $dir->{name}; |
|
195
|
|
|
|
|
|
|
# If the directory already exists, just apply the permissions |
|
196
|
8
|
50
|
33
|
|
|
1059
|
mkdir($dst_abs) || croak "mkdir($dst_abs): $!" |
|
197
|
|
|
|
|
|
|
unless $old; |
|
198
|
8
|
|
|
|
|
46
|
$self->_apply_stat($dst_abs, $dir); |
|
199
|
|
|
|
|
|
|
} |
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
# Install a symlink into ->dst |
|
202
|
5
|
|
|
5
|
|
7
|
sub _add_symlink($self, $file) { |
|
|
5
|
|
|
|
|
7
|
|
|
|
5
|
|
|
|
|
14
|
|
|
|
5
|
|
|
|
|
5
|
|
|
203
|
5
|
|
|
|
|
11
|
my $dst_abs= $self->dst_abs . $file->{name}; |
|
204
|
|
|
|
|
|
|
length $file->{data} |
|
205
|
5
|
50
|
|
|
|
15
|
or croak "Missing symlink contents for $file->{name}"; |
|
206
|
5
|
50
|
|
|
|
441
|
symlink($file->{data}, $dst_abs) |
|
207
|
|
|
|
|
|
|
or croak "symlink($file->{data}, $dst_abs): $!"; |
|
208
|
5
|
|
|
|
|
41
|
$self->_apply_stat($dst_abs, $file); |
|
209
|
|
|
|
|
|
|
} |
|
210
|
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
# Install a device node into ->dst |
|
212
|
1
|
|
|
1
|
|
2
|
sub _add_devnode($self, $file) { |
|
|
1
|
|
|
|
|
4
|
|
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
4
|
|
|
213
|
1
|
50
|
33
|
|
|
19
|
if (defined $file->{rdev} && (!defined $file->{rdev_major} || !defined $file->{rdev_minor})) { |
|
|
|
|
33
|
|
|
|
|
|
214
|
0
|
|
|
|
|
0
|
my ($major,$minor)= Sys::Export::Unix::_dev_major_minor($file->{rdev}); |
|
215
|
0
|
|
0
|
|
|
0
|
$file->{rdev_major} //= $major; |
|
216
|
0
|
|
0
|
|
|
0
|
$file->{rdev_minor} //= $minor; |
|
217
|
|
|
|
|
|
|
} |
|
218
|
1
|
|
|
|
|
3
|
my $dst_abs= $self->dst_abs . $file->{name}; |
|
219
|
1
|
|
|
|
|
7
|
Sys::Export::Unix::_mknod_or_die($dst_abs, $file->{mode}, $file->{rdev_major}, $file->{rdev_minor}); |
|
220
|
1
|
|
|
|
|
35
|
$self->_apply_stat($dst_abs, $file); |
|
221
|
|
|
|
|
|
|
} |
|
222
|
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
# Install a fifo into ->dst |
|
224
|
0
|
|
|
0
|
|
0
|
sub _add_fifo($self, $file) { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
225
|
0
|
|
|
|
|
0
|
require POSIX; |
|
226
|
0
|
|
|
|
|
0
|
my $dst_abs= $self->dst_abs . $file->{name}; |
|
227
|
|
|
|
|
|
|
POSIX::mkfifo($dst_abs, $file->{mode}) |
|
228
|
0
|
0
|
|
|
|
0
|
or croak "mkfifo($dst_abs): $!"; |
|
229
|
0
|
|
|
|
|
0
|
$self->_apply_stat($dst_abs, $file); |
|
230
|
|
|
|
|
|
|
} |
|
231
|
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
# Bind a socket (thus creating it) in ->dst |
|
233
|
1
|
|
|
1
|
|
6
|
sub _add_socket($self, $file) { |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
2
|
|
|
234
|
1
|
|
|
|
|
16
|
require Socket; |
|
235
|
1
|
|
|
|
|
5
|
my $dst_abs= $self->dst_abs . $file->{name}; |
|
236
|
1
|
50
|
|
|
|
45
|
socket(my $s, Socket::AF_UNIX(), Socket::SOCK_STREAM(), 0) or die "socket: $!"; |
|
237
|
1
|
50
|
|
|
|
96
|
bind($s, Socket::pack_sockaddr_un($dst_abs)) or die "Failed to bind socket at $dst_abs: $!"; |
|
238
|
1
|
|
|
|
|
8
|
$self->_apply_stat($dst_abs, $file); |
|
239
|
|
|
|
|
|
|
} |
|
240
|
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
|
|
242
|
2
|
|
|
2
|
1
|
4
|
sub finish($self) { |
|
|
2
|
|
|
|
|
6
|
|
|
|
2
|
|
|
|
|
3
|
|
|
243
|
2
|
|
|
|
|
8
|
my $todo= delete $self->{_delayed_apply_stat}; |
|
244
|
|
|
|
|
|
|
# Reverse sort causes child directories to be updated before parents, |
|
245
|
|
|
|
|
|
|
# which is required for updating mtimes. |
|
246
|
|
|
|
|
|
|
$self->_delayed_apply_stat(@$_) |
|
247
|
2
|
|
|
|
|
21
|
for sort { $b->[0] cmp $a->[0] } @$todo; |
|
|
3
|
|
|
|
|
23
|
|
|
248
|
|
|
|
|
|
|
# free the temp directory if it was located within /dst_abs |
|
249
|
2
|
|
|
|
|
56
|
undef $self->{tmp}; |
|
250
|
|
|
|
|
|
|
} |
|
251
|
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
# Apply permissions and mtime to a path |
|
253
|
20
|
|
|
20
|
|
118
|
sub _apply_stat($self, $abs_path, $stat) { |
|
|
20
|
|
|
|
|
40
|
|
|
|
20
|
|
|
|
|
27
|
|
|
|
20
|
|
|
|
|
33
|
|
|
|
20
|
|
|
|
|
50
|
|
|
254
|
20
|
50
|
|
|
|
388
|
my ($mode, $uid, $gid, $atime, $mtime)= (lstat $abs_path)[2,4,5,8,9] |
|
255
|
|
|
|
|
|
|
or croak "Failed to stat file just created at '$abs_path': $!"; |
|
256
|
20
|
|
66
|
|
|
110
|
my $change_uid= defined $stat->{uid} && $stat->{uid} != $uid; |
|
257
|
20
|
|
66
|
|
|
62
|
my $change_gid= defined $stat->{gid} && $stat->{gid} != $gid; |
|
258
|
20
|
50
|
33
|
|
|
109
|
if ($change_uid || $change_gid) { |
|
259
|
|
|
|
|
|
|
# only UID 0 can change UID, and only GID 0 or GID in supplemental groups can change GID. |
|
260
|
0
|
0
|
0
|
|
|
0
|
$uid= -1 unless $change_uid && $> == 0; |
|
261
|
0
|
0
|
0
|
|
|
0
|
$gid= -1 unless $change_gid && ($) == 0 || grep $stat->{gid}, split / /, $) ); |
|
|
|
|
0
|
|
|
|
|
|
262
|
|
|
|
|
|
|
# Only attempt change if able |
|
263
|
0
|
0
|
0
|
|
|
0
|
POSIX::lchown($uid, $gid, $abs_path) or croak "lchown($uid, $gid, $abs_path): $!" |
|
|
|
|
0
|
|
|
|
|
|
264
|
|
|
|
|
|
|
if $uid >= 0 || $gid >= 0; |
|
265
|
|
|
|
|
|
|
} |
|
266
|
|
|
|
|
|
|
|
|
267
|
20
|
|
|
|
|
32
|
my @delayed; |
|
268
|
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
# Don't change permission bits on symlinks |
|
270
|
20
|
100
|
100
|
|
|
116
|
if (!S_ISLNK($mode) && ($mode & 0xFFF) != ($stat->{mode} & 0xFFF)) { |
|
271
|
|
|
|
|
|
|
# If changing permissions on a directory to something that removes our ability |
|
272
|
|
|
|
|
|
|
# to write to it, delay this change until the end. |
|
273
|
6
|
50
|
33
|
|
|
28
|
if (S_ISDIR($mode) && !(($stat->{mode} & 0222) && ($stat->{mode} & 0111))) { |
|
|
|
|
66
|
|
|
|
|
|
274
|
0
|
|
|
|
|
0
|
push @delayed, 'chmod'; |
|
275
|
|
|
|
|
|
|
} |
|
276
|
|
|
|
|
|
|
else { |
|
277
|
|
|
|
|
|
|
chmod $stat->{mode}&0xFFF, $abs_path |
|
278
|
6
|
50
|
|
|
|
153
|
or croak sprintf "chmod(0%o, %s): $!", $stat->{mode}&0xFFF, $abs_path; |
|
279
|
|
|
|
|
|
|
} |
|
280
|
|
|
|
|
|
|
} |
|
281
|
|
|
|
|
|
|
|
|
282
|
20
|
100
|
66
|
|
|
108
|
if (!S_ISLNK($mode) && (defined $stat->{mtime} || defined $stat->{atime})) { |
|
|
|
|
66
|
|
|
|
|
|
283
|
10
|
100
|
|
|
|
39
|
if (S_ISDIR($mode)) { |
|
284
|
|
|
|
|
|
|
# No point in applying mtime to a directory now, because it will get |
|
285
|
|
|
|
|
|
|
# changed when sub-entries get written. |
|
286
|
5
|
|
|
|
|
12
|
push @delayed, 'utime'; |
|
287
|
|
|
|
|
|
|
} |
|
288
|
|
|
|
|
|
|
else { |
|
289
|
5
|
50
|
|
|
|
94
|
utime $stat->{atime}, $stat->{mtime}, $abs_path |
|
290
|
|
|
|
|
|
|
or warn "utime($abs_path): $!"; |
|
291
|
|
|
|
|
|
|
} |
|
292
|
|
|
|
|
|
|
} |
|
293
|
|
|
|
|
|
|
|
|
294
|
20
|
100
|
|
|
|
228
|
push @{$self->{_delayed_apply_stat}}, [ $abs_path, $stat, @delayed ] |
|
|
5
|
|
|
|
|
88
|
|
|
295
|
|
|
|
|
|
|
if @delayed; |
|
296
|
|
|
|
|
|
|
} |
|
297
|
5
|
|
|
5
|
|
10
|
sub _delayed_apply_stat($self, $abs_path, $stat, @delayed) { |
|
|
5
|
|
|
|
|
14
|
|
|
|
5
|
|
|
|
|
10
|
|
|
|
5
|
|
|
|
|
10
|
|
|
|
5
|
|
|
|
|
9
|
|
|
|
5
|
|
|
|
|
8
|
|
|
298
|
5
|
50
|
|
|
|
36
|
if (grep $_ eq 'chmod', @delayed) { |
|
299
|
|
|
|
|
|
|
chmod $stat->{mode}&0xFFF, $abs_path |
|
300
|
0
|
0
|
|
|
|
0
|
or croak sprintf "chmod(0%o, %s): $!", $stat->{mode}&0xFFF, $abs_path; |
|
301
|
|
|
|
|
|
|
} |
|
302
|
5
|
50
|
|
|
|
18
|
if (grep $_ eq 'utime', @delayed) { |
|
303
|
5
|
50
|
|
|
|
102
|
utime $stat->{atime}, $stat->{mtime}, $abs_path |
|
304
|
|
|
|
|
|
|
or warn "utime($abs_path): $!"; |
|
305
|
|
|
|
|
|
|
} |
|
306
|
|
|
|
|
|
|
} |
|
307
|
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
# Avoiding dependency on namespace::clean |
|
309
|
4
|
|
|
4
|
|
35
|
{ no strict 'refs'; |
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
469
|
|
|
310
|
|
|
|
|
|
|
delete @{"Sys::Export::Unix::"}{qw( |
|
311
|
|
|
|
|
|
|
croak carp abs_path S_IFMT |
|
312
|
|
|
|
|
|
|
S_ISREG S_ISDIR S_ISLNK S_ISBLK S_ISCHR S_ISFIFO S_ISSOCK S_ISWHT |
|
313
|
|
|
|
|
|
|
S_IFREG S_IFDIR S_IFLNK S_IFBLK S_IFCHR S_IFIFO S_IFSOCK S_IFWHT |
|
314
|
|
|
|
|
|
|
)}; |
|
315
|
|
|
|
|
|
|
} |
|
316
|
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
1; |
|
318
|
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
__END__ |