line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package DataStore::CAS::FS::DirCodec::Unix; |
2
|
5
|
|
|
5
|
|
475
|
use 5.008; |
|
5
|
|
|
|
|
11
|
|
3
|
5
|
|
|
5
|
|
17
|
use strict; |
|
5
|
|
|
|
|
4
|
|
|
5
|
|
|
|
|
69
|
|
4
|
5
|
|
|
5
|
|
15
|
use warnings; |
|
5
|
|
|
|
|
4
|
|
|
5
|
|
|
|
|
88
|
|
5
|
5
|
|
|
5
|
|
13
|
use Try::Tiny; |
|
5
|
|
|
|
|
4
|
|
|
5
|
|
|
|
|
209
|
|
6
|
5
|
|
|
5
|
|
16
|
use Carp; |
|
5
|
|
|
|
|
3
|
|
|
5
|
|
|
|
|
212
|
|
7
|
5
|
|
|
5
|
|
35
|
use JSON 2.53 (); |
|
5
|
|
|
|
|
52
|
|
|
5
|
|
|
|
|
90
|
|
8
|
5
|
|
|
5
|
|
16
|
use Scalar::Util 'looks_like_number'; |
|
5
|
|
|
|
|
4
|
|
|
5
|
|
|
|
|
306
|
|
9
|
|
|
|
|
|
|
require DataStore::CAS::FS::Dir; |
10
|
|
|
|
|
|
|
require DataStore::CAS::FS::DirEnt; |
11
|
|
|
|
|
|
|
require DataStore::CAS::FS::InvalidUTF8; |
12
|
|
|
|
|
|
|
*decode_utf8= *DataStore::CAS::FS::InvalidUTF8::decode_utf8; |
13
|
|
|
|
|
|
|
|
14
|
5
|
|
|
5
|
|
335
|
use parent 'DataStore::CAS::FS::DirCodec'; |
|
5
|
|
|
|
|
194
|
|
|
5
|
|
|
|
|
18
|
|
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
our $VERSION= '0.011000'; |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
__PACKAGE__->register_format(unix => __PACKAGE__); |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
# ABSTRACT: Efficiently encode only the attributes of a UNIX stat() |
21
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
our $_json_coder; |
24
|
|
|
|
|
|
|
sub _build_json_coder { |
25
|
1
|
|
|
1
|
|
26
|
DataStore::CAS::FS::InvalidUTF8->add_json_filter( |
26
|
|
|
|
|
|
|
JSON->new->utf8->canonical->convert_blessed, 1 |
27
|
|
|
|
|
|
|
); |
28
|
|
|
|
|
|
|
} |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
our %_TypeToCode= ( |
31
|
|
|
|
|
|
|
file => ord('f'), dir => ord('d'), symlink => ord('l'), |
32
|
|
|
|
|
|
|
chardev => ord('c'), blockdev => ord('b'), |
33
|
|
|
|
|
|
|
pipe => ord('p'), socket => ord('s'), whiteout => ord('w'), |
34
|
|
|
|
|
|
|
); |
35
|
|
|
|
|
|
|
our %_CodeToType= map { $_TypeToCode{$_} => $_ } keys %_TypeToCode; |
36
|
|
|
|
|
|
|
our @_FieldOrder= qw( |
37
|
|
|
|
|
|
|
type name ref size modify_ts unix_uid unix_gid unix_mode metadata_ts |
38
|
|
|
|
|
|
|
access_ts unix_nlink unix_dev unix_inode unix_blocksize unix_blockcount |
39
|
|
|
|
|
|
|
); |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
sub encode { |
42
|
9
|
|
|
9
|
1
|
2697
|
my ($class, $entry_list, $metadata)= @_; |
43
|
9
|
50
|
|
|
|
24
|
$metadata= defined($metadata)? { %$metadata } : {}; |
44
|
|
|
|
|
|
|
defined $metadata->{_} |
45
|
9
|
50
|
|
|
|
20
|
and croak '$metadata{_} is reserved for the directory encoder'; |
46
|
9
|
|
|
|
|
6
|
my (%umap, %gmap); |
47
|
|
|
|
|
|
|
my @entries= map { |
48
|
9
|
50
|
|
|
|
15
|
my $e= ref $_ eq 'HASH'? $_ : $_->as_hash; |
|
16
|
|
|
|
|
28
|
|
49
|
|
|
|
|
|
|
defined $e->{type} |
50
|
16
|
100
|
|
|
|
173
|
or croak "'type' attribute is required"; |
51
|
|
|
|
|
|
|
my $code= $_TypeToCode{$e->{type}} |
52
|
15
|
100
|
|
|
|
119
|
or croak "Unknown directory entry type: ".$e->{type}; |
53
|
|
|
|
|
|
|
|
54
|
14
|
|
|
|
|
16
|
my $name= $e->{name}; |
55
|
14
|
100
|
|
|
|
107
|
defined $name |
56
|
|
|
|
|
|
|
or croak "'name' attribute is required"; |
57
|
13
|
100
|
|
|
|
13
|
_make_utf8($name) |
58
|
|
|
|
|
|
|
or croak "'name' must be a unicode scalar or an InvalidUTF8 instance"; |
59
|
|
|
|
|
|
|
|
60
|
12
|
|
|
|
|
12
|
my $ref= $e->{ref}; |
61
|
12
|
100
|
|
|
|
18
|
$ref= '' unless defined $ref; |
62
|
12
|
100
|
|
|
|
11
|
_make_utf8($ref) |
63
|
|
|
|
|
|
|
or croak "'ref' must be a unicode scalar or an InvalidUTF8 instance"; |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
$umap{$e->{unix_uid}}= $e->{unix_user} |
66
|
11
|
50
|
66
|
|
|
33
|
if defined $e->{unix_uid} && defined $e->{unix_user}; |
67
|
|
|
|
|
|
|
$gmap{$e->{unix_gid}}= $e->{unix_group} |
68
|
11
|
50
|
66
|
|
|
24
|
if defined $e->{unix_gid} && defined $e->{unix_group}; |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
my $int_attr_str= join(":", |
71
|
132
|
50
|
|
|
|
171
|
map { !defined $_? '' : looks_like_number($_)? $_ : croak "Invalid unix attribute number: $_" } |
|
|
100
|
|
|
|
|
|
72
|
11
|
|
|
|
|
16
|
@{$e}{@_FieldOrder[3..$#_FieldOrder]} |
|
11
|
|
|
|
|
42
|
|
73
|
|
|
|
|
|
|
); |
74
|
|
|
|
|
|
|
# As an optimization, all undef trailing fields can be chopped off. |
75
|
11
|
|
|
|
|
33
|
$int_attr_str =~ s/:+$//; |
76
|
|
|
|
|
|
|
|
77
|
11
|
50
|
|
|
|
20
|
croak "'name' too long: '$name'" if length($name) > 255; |
78
|
11
|
50
|
|
|
|
16
|
croak "'ref' too long: '$ref'" if length($ref) > 255; |
79
|
11
|
50
|
|
|
|
15
|
croak "Unix fields too long: '$int_attr_str'" if length($int_attr_str) > 255; |
80
|
11
|
|
|
|
|
38
|
pack('CCCC', length($name), length($ref), length($int_attr_str), $code).$name."\0".$ref."\0".$int_attr_str; |
81
|
|
|
|
|
|
|
} @$entry_list; |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
# Save the mapping of UID to User and GID to Group |
84
|
4
|
|
|
|
|
10
|
$metadata->{_}{umap}= \%umap; |
85
|
4
|
|
|
|
|
6
|
$metadata->{_}{gmap}= \%gmap; |
86
|
|
|
|
|
|
|
|
87
|
4
|
|
66
|
|
|
29
|
my $meta_json= ($_json_coder ||= _build_json_coder())->encode($metadata); |
88
|
|
|
|
|
|
|
my $ret= "CAS_Dir 04 unix\n" |
89
|
|
|
|
|
|
|
.pack('N', length($meta_json)).$meta_json |
90
|
4
|
|
|
|
|
21
|
.join('', sort { substr($a,4) cmp substr($b,4) } @entries); |
|
16
|
|
|
|
|
20
|
|
91
|
4
|
50
|
|
|
|
10
|
croak "Accidental unicode concatenation" |
92
|
|
|
|
|
|
|
if utf8::is_utf8($ret); |
93
|
4
|
|
|
|
|
14
|
$ret; |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
# Convert string in-place to utf-8 bytes, or return false. |
97
|
|
|
|
|
|
|
# A less speed-obfuscated version might read: |
98
|
|
|
|
|
|
|
# my $str= shift; |
99
|
|
|
|
|
|
|
# if (ref $str) { |
100
|
|
|
|
|
|
|
# return 0 unless ref($str)->can('TO_UTF8'); |
101
|
|
|
|
|
|
|
# $_[0]= $str->TO_UTF8; |
102
|
|
|
|
|
|
|
# return 1; |
103
|
|
|
|
|
|
|
# } elsif (utf8::is_utf8($str)) { |
104
|
|
|
|
|
|
|
# utf8::encode($_[0]); |
105
|
|
|
|
|
|
|
# return 1; |
106
|
|
|
|
|
|
|
# } else { |
107
|
|
|
|
|
|
|
# return !($_[0] =~ /[\x7F-\xFF]/); |
108
|
|
|
|
|
|
|
# } |
109
|
|
|
|
|
|
|
sub _make_utf8 { |
110
|
25
|
100
|
33
|
25
|
|
318
|
ref $_[0]? |
|
|
|
100
|
|
|
|
|
111
|
|
|
|
|
|
|
(ref($_[0])->can('TO_UTF8') && (($_[0]= $_[0]->TO_UTF8) || 1)) |
112
|
|
|
|
|
|
|
: &utf8::is_utf8 && (&utf8::encode || 1) || !($_[0] =~ /[\x80-\xFF]/); |
113
|
|
|
|
|
|
|
} |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
sub decode { |
117
|
4
|
|
|
4
|
1
|
309
|
my ($class, $params)= @_; |
118
|
|
|
|
|
|
|
$params->{format}= $class->_read_format($params) |
119
|
4
|
100
|
|
|
|
15
|
unless defined $params->{format}; |
120
|
4
|
|
|
|
|
4
|
my $handle= $params->{handle}; |
121
|
4
|
50
|
|
|
|
6
|
if (!$handle) { |
122
|
0
|
0
|
|
|
|
0
|
if (defined $params->{data}) { |
123
|
|
|
|
|
|
|
open($handle, '<', \$params->{data}) |
124
|
0
|
0
|
|
|
|
0
|
or croak "can't open handle to scalar"; |
125
|
|
|
|
|
|
|
} else { |
126
|
0
|
|
|
|
|
0
|
$handle= $params->{file}->open; |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
} |
129
|
|
|
|
|
|
|
|
130
|
4
|
|
|
|
|
13
|
my $header_len= $class->_calc_header_length($params->{format}); |
131
|
4
|
50
|
|
|
|
11
|
seek($handle, $header_len, 0) or croak "seek: $!"; |
132
|
|
|
|
|
|
|
|
133
|
4
|
|
|
|
|
3
|
my (@entries, $buf, $pos); |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
# first, pull out the metadata, which includes the UID map and GID map. |
136
|
4
|
|
|
|
|
9
|
$class->_readall($handle, $buf, 4); |
137
|
4
|
|
|
|
|
16
|
my ($dirmeta_len)= unpack('N', $buf); |
138
|
4
|
|
|
|
|
9
|
$class->_readall($handle, my $json, $dirmeta_len); |
139
|
4
|
|
33
|
|
|
36
|
my $meta= ($_json_coder ||= _build_json_coder())->decode($json); |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
# Quick sanity checks |
142
|
|
|
|
|
|
|
ref $meta->{_}{umap} and ref $meta->{_}{gmap} |
143
|
4
|
50
|
33
|
|
|
19
|
or croak "Incorrect directory metadata"; |
144
|
4
|
|
|
|
|
4
|
my $dirmeta= delete $meta->{_}; |
145
|
|
|
|
|
|
|
|
146
|
4
|
|
|
|
|
10
|
while (!eof $handle) { |
147
|
11
|
|
|
|
|
17
|
$class->_readall($handle, $buf, 4); |
148
|
11
|
|
|
|
|
35
|
my ($name_len, $ref_len, $meta_len, $code)= unpack('CCCC', $buf); |
149
|
11
|
|
|
|
|
35
|
$class->_readall($handle, $buf, $name_len+$ref_len+$meta_len+2); |
150
|
|
|
|
|
|
|
my @fields= ( |
151
|
|
|
|
|
|
|
$dirmeta, |
152
|
|
|
|
|
|
|
$code, |
153
|
|
|
|
|
|
|
DataStore::CAS::FS::InvalidUTF8->decode_utf8(substr($buf, 0, $name_len)), |
154
|
|
|
|
|
|
|
$ref_len? DataStore::CAS::FS::InvalidUTF8->decode_utf8(substr($buf, $name_len+1, $ref_len)) : undef, |
155
|
11
|
100
|
|
|
|
36
|
map { length($_)? $_ : undef } split(":", substr($buf, $name_len+$ref_len+2, $meta_len)), |
|
27
|
100
|
|
|
|
69
|
|
156
|
|
|
|
|
|
|
); |
157
|
11
|
|
|
|
|
50
|
push @entries, bless(\@fields, __PACKAGE__.'::Entry'); |
158
|
|
|
|
|
|
|
} |
159
|
4
|
|
|
|
|
7
|
close $handle; |
160
|
|
|
|
|
|
|
return DataStore::CAS::FS::Dir->new( |
161
|
|
|
|
|
|
|
file => $params->{file}, |
162
|
|
|
|
|
|
|
format => $params->{format}, |
163
|
4
|
|
|
|
|
45
|
metadata => $meta, |
164
|
|
|
|
|
|
|
entries => \@entries, |
165
|
|
|
|
|
|
|
); |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
package DataStore::CAS::FS::DirCodec::Unix::Entry; |
169
|
5
|
|
|
5
|
|
4647
|
use strict; |
|
5
|
|
|
|
|
7
|
|
|
5
|
|
|
|
|
83
|
|
170
|
5
|
|
|
5
|
|
13
|
use warnings; |
|
5
|
|
|
|
|
5
|
|
|
5
|
|
|
|
|
110
|
|
171
|
5
|
|
|
5
|
|
13
|
use parent 'DataStore::CAS::FS::DirEnt'; |
|
5
|
|
|
|
|
5
|
|
|
5
|
|
|
|
|
18
|
|
172
|
|
|
|
|
|
|
|
173
|
0
|
|
|
0
|
|
0
|
sub _dirmeta { $_[0][0] } |
174
|
11
|
|
|
11
|
|
35
|
sub type { $_CodeToType{$_[0][1]} } |
175
|
1
|
|
|
1
|
|
4
|
sub name { $_[0][2] } |
176
|
0
|
|
|
0
|
|
0
|
sub ref { $_[0][3] } |
177
|
0
|
|
|
0
|
|
0
|
sub size { $_[0][4] } |
178
|
0
|
|
|
0
|
|
0
|
sub modify_ts { $_[0][5] } |
179
|
0
|
|
|
0
|
|
0
|
sub unix_uid { $_[0][6] } |
180
|
0
|
|
|
0
|
|
0
|
sub unix_gid { $_[0][7] } |
181
|
0
|
|
|
0
|
|
0
|
sub unix_mode { $_[0][8] } |
182
|
0
|
|
|
0
|
|
0
|
sub metadata_ts { $_[0][9] } |
183
|
0
|
|
|
0
|
|
0
|
sub access_ts { $_[0][10] } |
184
|
0
|
|
|
0
|
|
0
|
sub unix_nlink { $_[0][11] } |
185
|
0
|
|
|
0
|
|
0
|
sub unix_dev { $_[0][12] } |
186
|
0
|
|
|
0
|
|
0
|
sub unix_inode { $_[0][13] } |
187
|
0
|
|
|
0
|
|
0
|
sub unix_blocksize { $_[0][14] } |
188
|
0
|
|
|
0
|
|
0
|
sub unix_blockcount { $_[0][15] } |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
*unix_mtime= *modify_ts; |
191
|
|
|
|
|
|
|
*unix_atime= *access_ts; |
192
|
|
|
|
|
|
|
*unix_ctime= *metadata_ts; |
193
|
0
|
|
|
0
|
|
0
|
sub unix_user { my $self= shift; $self->_dirmeta->{umap}{ $self->unix_uid } } |
|
0
|
|
|
|
|
0
|
|
194
|
0
|
|
|
0
|
|
0
|
sub unix_group { my $self= shift; $self->_dirmeta->{gmap}{ $self->unix_gid } } |
|
0
|
|
|
|
|
0
|
|
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
sub as_hash { |
197
|
11
|
|
|
11
|
|
13
|
my $self= shift; |
198
|
|
|
|
|
|
|
return { |
199
|
|
|
|
|
|
|
type => $self->type, |
200
|
11
|
|
|
|
|
15
|
map { $_FieldOrder[$_-1] => $self->[$_] } grep { defined $self->[$_] } 2 .. $#$self |
|
35
|
|
|
|
|
94
|
|
|
49
|
|
|
|
|
57
|
|
201
|
|
|
|
|
|
|
}; |
202
|
|
|
|
|
|
|
} |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
1; |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
__END__ |