File Coverage

lib/File/Information/Inode.pm
Criterion Covered Total %
statement 68 459 14.8
branch 16 244 6.5
condition 10 258 3.8
subroutine 13 32 40.6
pod 4 4 100.0
total 111 997 11.1


line stmt bran cond sub pod time code
1             # Copyright (c) 2024-2025 Philipp Schafft <lion@cpan.org>
2              
3             # licensed under Artistic License 2.0 (see LICENSE file)
4              
5             # ABSTRACT: generic module for extracting information from filesystems
6              
7              
8             package File::Information::Inode;
9              
10 3     3   42 use v5.10;
  3         12  
11 3     3   16 use strict;
  3         7  
  3         95  
12 3     3   19 use warnings;
  3         7  
  3         211  
13              
14 3     3   20 use parent 'File::Information::Base';
  3         6  
  3         22  
15              
16 3     3   366 use Carp;
  3         23  
  3         268  
17 3     3   126 use File::Spec;
  3         7  
  3         238  
18 3     3   26 use Fcntl qw(S_ISREG S_ISDIR S_ISLNK S_ISBLK S_ISCHR S_ISFIFO S_ISSOCK S_IWUSR S_IWGRP S_IWOTH SEEK_SET);
  3         9  
  3         479  
19              
20 3     3   21 use Data::Identifier v0.08;
  3         72  
  3         23  
21 3     3   171 use Data::Identifier::Generate;
  3         31  
  3         47421  
22              
23             our $VERSION = v0.16;
24              
25             my $HAVE_XATTR = eval {require File::ExtAttr; 1;};
26             my $HAVE_FILE_VALUEFILE = eval {require File::ValueFile::Simple::Reader; 1;};
27             my $HAVE_CONFIG_INI_READER = eval {require Config::INI::Reader; 1;};
28              
29             my %_ntfs_attributes = (
30             FILE_ATTRIBUTE_READONLY => 0x0001,
31             FILE_ATTRIBUTE_HIDDEN => 0x0002,
32             FILE_ATTRIBUTE_SYSTEM => 0x0004,
33             FILE_ATTRIBUTE_ARCHIVE => 0x0020,
34             FILE_ATTRIBUTE_TEMPORARY => 0x0100,
35             FILE_ATTRIBUTE_COMPRESSED => 0x0800,
36             FILE_ATTRIBUTE_OFFLINE => 0x1000,
37             FILE_ATTRIBUTE_NOT_CONTENT_INDEXED => 0x2000,
38             );
39              
40             my %_tagpool_directory_setting_tagmap; # define here, but only load (below) if we $HAVE_FILE_VALUEFILE
41              
42             my %_magic_map = (
43             # image/*
44             "\xff\xd8\xff" => 'image/jpeg',
45             "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a" => 'image/png',
46             'GIF87a' => 'image/gif',
47             'GIF89a' => 'image/gif',
48             "\0\0\1\0" => 'image/vnd.microsoft.icon',
49             # audio/*
50             'fLaC' => 'audio/flac',
51             # application/*
52             '%PDF-' => 'application/pdf',
53             "PK\x03\x04" => 'application/zip',
54             '%!PS-Adobe-' => 'application/postscript',
55             );
56              
57             my %_wk_tagged_as_tags = (
58             (map {$_ => {for => 'write-mode'}} qw(7b177183-083c-4387-abd3-8793eb647373 3877b2ef-6c77-423f-b15f-76508fbd48ed 4dc9fd07-7ef3-4215-8874-31d78ed55c22)),
59             (map {$File::Information::Base::_mediatypes{$_} => {for => 'mediatype', mediatype => $_}} keys %File::Information::Base::_mediatypes),
60             'f418cdb9-64a7-4f15-9a18-63f7755c5b47' => {for => 'finalmode', implies => [qw(7b177183-083c-4387-abd3-8793eb647373)]},
61             'cb9c2c8a-b6bd-4733-80a4-5bd65af6b957' => {for => 'finalmode'},
62             );
63              
64             my %_URLZONE = (
65             # tag-ise 66294283-0a5d-4e78-a4b0-91df2c82068d # URLZONE-namespace
66             0 => {ise => 'd0e96897-b82f-5696-aa8e-8c29a16ab613', displayname => 'URLZONE_LOCAL_MACHINE'},
67             1 => {ise => 'cb576748-97f3-5fd7-80db-3682a94c67aa', displayname => 'URLZONE_INTRANET'},
68             2 => {ise => '445acf47-7049-5af1-8ed9-fecb54a8c517', displayname => 'URLZONE_TRUSTED'},
69             3 => {ise => 'a80b2f16-0db7-5536-a3ee-be8d85d123bd', displayname => 'URLZONE_INTERNET'},
70             4 => {ise => '73ef6c11-cdef-5547-be38-aa2cede0d4ea', displayname => 'URLZONE_UNTRUSTED'},
71             );
72              
73             my %_properties = (
74             (map {$_ => {loader => \&_load_stat}}qw(st_dev st_ino st_mode st_nlink st_uid st_gid st_rdev st_size st_blksize st_blocks st_atime st_mtime st_ctime stat_readonly stat_cachehash)),
75             magic_mediatype => {loader => \&_load_magic, rawtype => 'mediatype'},
76             magic_valuefile_version => {loader => \&_load_magic, rawtype => 'uuid'},
77             magic_valuefile_format => {loader => \&_load_magic, rawtype => 'ise'},
78             db_inode_tag => {loader => \&_load_db, rawtype => 'Data::TagDB::Tag'},
79             content_sha_3_512_uuid => {loader => \&_load_contentise, rawtype => 'uuid'},
80             content_sha_1_160_sha_3_512_uuid => {loader => \&_load_contentise, rawtype => 'uuid'},
81             store_file => {loader => \&_load_fstore, rawtype => 'File::FStore::File'},
82             shebang_line => {loader => \&_load_shebang},
83             shebang_interpreter => {loader => \&_load_shebang, rawtype => 'filename'},
84             );
85              
86             $_properties{$_}{rawtype} = 'unixts' foreach qw(st_atime st_mtime st_ctime);
87             $_properties{$_}{rawtype} = 'bool' foreach qw(stat_readonly);
88              
89             if ($HAVE_XATTR) {
90             $_properties{'xattr_'.$_} = {loader => \&_load_xattr, xattr_key => $_} foreach qw(mime_type charset creator);
91             $_properties{'xattr_mime_type'}{rawtype} = 'mediatype';
92              
93             $_properties{'xattr_xdg_'.($_ =~ tr/.-/__/r)} = {loader => \&_load_xattr, xattr_key => 'xdg.'.$_} foreach qw(comment origin.url origin.email.subject origin.email.from origin.email.message-id language creator publisher);
94              
95             $_properties{'xattr_dublincore_'.($_ =~ tr/.-/__/r)} = {loader => \&_load_xattr, xattr_key => 'dublincore.'.$_} foreach qw(title creator subject description publisher contributor date type format identifier source language relation coverage rights);
96              
97             $_properties{'xattr_utag_'.($_ =~ tr/.-/__/r)} = {loader => \&_load_xattr, rawtype => 'ise', xattr_key => 'utag.'.$_} foreach qw(ise write-mode final-mode);
98             $_properties{'xattr_utag_final_'.($_ =~ tr/.-/__/r)} = {loader => \&_load_xattr, lifecycle => 'final', xattr_key => 'utag.final.'.$_} foreach qw(file.size file.encoding file.hash);
99             $_properties{'xattr_utag_final_file_encoding'}{parts} = [qw(ise mediatype)];
100             $_properties{'xattr_utag_final_file_hash'}{parsing} = 'utag';
101             $_properties{'xattr_utag_final_file_hash_size'} = {loader => \&_load_redirect, redirect => 'xattr_utag_final_file_hash'};
102              
103             $_properties{'ntfs_'.lc($_)} = {loader => \&_load_ntfs_xattr, ntfs_attribute => $_, rawtype => 'bool'} foreach keys %_ntfs_attributes;
104             }
105              
106             if ($HAVE_FILE_VALUEFILE) {
107             my $config = {loader => \&_load_tagpool_directory};
108             $_properties{'tagpool_directory_'.$_} = {%{$config}} foreach qw(title comment description inode mtime pool_uuid timestamp);
109             $_properties{'tagpool_directory_setting_'.($_ =~ tr/-/_/r)} = {%{$config}} foreach qw(thumbnail-uri thumbnail-mode update-mode add-mode file-tags tag-mode tag-implies entry-sort-order tag tag-root tag-parent tag-type entry-display-name entry-sort-key);
110             $_properties{'tagpool_directory_'.$_}{rawtype} = 'unixts' foreach qw(mtime timestamp);
111             $_properties{'tagpool_directory_'.$_}{rawtype} = 'uuid' foreach qw(pool_uuid);
112             $_properties{'tagpool_directory_setting_'.($_ =~ tr/-/_/r)}{rawtype} = 'ise' foreach qw(tag tag-root tag-parent tag-type);
113             $_properties{'tagpool_directory_throw_option_'.$_} = {%{$config}} foreach qw(linkname linktype filter);
114              
115             $_properties{'tagpool_file_'.($_ =~ tr/-/_/r)} = {loader => \&_load_tagpool_file} foreach qw(title comment description mtime timestamp inode size actual-size original-url original-description-url pool-name-suffix original-filename uuid mediatype write-mode finalmode thumbnail tags);
116             $_properties{'tagpool_file_'.$_}{rawtype} = 'unixts' foreach qw(mtime timestamp);
117             $_properties{'tagpool_file_'.($_ =~ tr/-/_/r)}{rawtype} = 'uuid' foreach qw(uuid write-mode finalmode tags);
118             $_properties{'tagpool_file_'.($_ =~ tr/-/_/r)}{rawtype} = 'mediatype' foreach qw(mediatype);
119             $_properties{'tagpool_file_'.($_ =~ tr/-/_/r)}{rawtype} = 'filename' foreach qw(thumbnail);
120              
121              
122             %_tagpool_directory_setting_tagmap = (
123             'thumbnail-mode' => {
124             'file-uri' => 'e4c80ac0-7c71-4548-9e84-9422bf1dae11',
125             'tag-uri' => '0025b1b2-20db-40e6-9345-baf0f9b5e166',
126             'tag' => '30c09ebd-bc14-48a3-8c0f-2d66c3d6e429',
127             'throw-filter' => 'c4438812-6011-42ee-984a-183745d9b013',
128             },
129             'update-mode' => {
130             'add' => 'dd1ff55a-fd87-428d-bd7e-57fc56488e72',
131             'throw' => '41217e01-4468-4d54-b613-902835ae0596',
132             },
133             'add-mode' => {
134             'all' => '65de001a-9063-4591-8b67-99ee1f91c4dd',
135             'no-boring' => 'db7c2ac0-4205-4f99-8556-c48cbb51138e',
136             'none' => '36fd66fd-b07f-4010-b796-05b488826571',
137             },
138             'file-tags' => {
139             'root' => '908c9015-b760-441e-85bf-ba98b5ff452b',
140             'level' => '53e36ce9-8afb-425e-9cae-2016cbdc27fe',
141             'root-and-level' => 'f8733429-8dc8-493b-8b91-958c6485afeb',
142             'parent-and-level' => 'e2cbc030-447a-4ee3-8adc-5b84c0400038',
143             'root-and-parent-and-level' => 'fe58aa1a-4cd7-49ca-a11d-ceab5223ccd9',
144             },
145             'tag-mode' => {
146             'random' => '02110f2e-b2c1-45a8-910b-0210f87cb7a1',
147             'named-random' => '7c6b6534-bd85-40c6-99f0-c0d308f790b6',
148             'namebased' => '39a2be03-7d07-41c4-93da-815c5f5d6f8d',
149             },
150             'tag-implies' => {
151             'root' => '60384e20-8d88-4171-970b-560ddafc1f95',
152             'parent' => '5e5acf8e-4e07-4ce9-8516-a014a7fbf91a',
153             'root-and-parent' => '112db395-84c3-4711-b99f-b5c6d6051781',
154             },
155             'entry-sort-order' => {
156             'asc' => '994e3f9c-79c1-40d1-892f-d66d406538a1',
157             'desc' => '54140078-a52a-4693-9f66-30b4ac4f1da4',
158             },
159             );
160              
161             foreach my $setting (values %_tagpool_directory_setting_tagmap) {
162             foreach my $entry (values %{$setting}) {
163             $entry = {ise => $entry} unless ref $entry;
164             }
165             }
166             }
167              
168             {
169             my %_wk = (
170             # tagpool-sysfile-type:
171             'e6d6bb07-1a6a-46f6-8c18-5aa6ea24d7cb' => {displayname => 'regular'},
172             '577c3095-922b-4569-805d-a5df94686b35' => {displayname => 'directory'},
173             '76ae899c-ad0c-4bbc-b693-485f91779b9f' => {displayname => 'symlink'},
174             'f1765bfc-96d5-4ff3-ba2e-16a2a9f24cb3' => {displayname => 'blockdevice'},
175             '241431a9-c83f-4bce-93ff-0024021cd754' => {displayname => 'characterdevice'},
176             '3d680b7b-115c-486a-a186-4ad77facc52e' => {displayname => 'fifo'},
177             '3d1cb160-5fc5-4d8e-a8d3-3b0ec85bb000' => {displayname => 'socket'},
178              
179             # write-mode:
180             '7b177183-083c-4387-abd3-8793eb647373' => {displayname => 'none'},
181             '3877b2ef-6c77-423f-b15f-76508fbd48ed' => {displayname => 'random access'},
182             '4dc9fd07-7ef3-4215-8874-31d78ed55c22' => {displayname => 'append only'},
183              
184             # Final states:
185             'f418cdb9-64a7-4f15-9a18-63f7755c5b47' => {displayname => 'final'},
186             'cb9c2c8a-b6bd-4733-80a4-5bd65af6b957' => {displayname => 'auto-final'},
187              
188             # ValueFile:
189             '54bf8af4-b1d7-44da-af48-5278d11e8f32' => {displayname => 'ValueFile'},
190             'e5da6a39-46d5-48a9-b174-5c26008e208e' => {displayname => 'tagpool-source-format'},
191             'afdb46f2-e13f-4419-80d7-c4b956ed85fa' => {displayname => 'tagpool-taglist-format-v1'},
192             '25990339-3913-4b5a-8bcf-5042ef6d8b5e' => {displayname => 'tagpool-httpd-htdirectories-format'},
193             '11431b85-41cd-4be5-8d88-a769ebbd603f' => {displayname => 'tagpool-directory-info-format'},
194              
195             #'' => {displayname => ''},
196             );
197              
198             foreach my $setting (values %_tagpool_directory_setting_tagmap) {
199             foreach my $key (keys %{$setting}) {
200             my $value = $setting->{$key};
201             $value->{displayname} //= $key;
202             $_wk{$value->{ise}} = $value;
203             }
204             }
205              
206             while (my ($mediatype, $ise) = each %File::Information::Base::_mediatypes) {
207             ($_wk{$ise} //= {})->{displayname} //= $mediatype;
208             }
209              
210              
211             while (my ($key, $value) = each %_wk) {
212             Data::Identifier->new(ise => $key, %{$value})->register;
213             }
214              
215             foreach my $value (values %_URLZONE) {
216             Data::Identifier->new(ise => $value->{ise}, displayname => $value->{displayname})->register;
217             }
218             }
219              
220             if ($HAVE_CONFIG_INI_READER) {
221             $_properties{'zonetransfer_'.lc($_)} = {loader => \&_load_zonetransfer, zonetransfer_key => $_} foreach qw(HostIpAddress ZoneId ReferrerUrl HostUrl);
222             }
223              
224             {
225             my %_S_IS_to_tagpool_ise = (
226             S_ISREG => 'e6d6bb07-1a6a-46f6-8c18-5aa6ea24d7cb',
227             S_ISDIR => '577c3095-922b-4569-805d-a5df94686b35',
228             S_ISLNK => '76ae899c-ad0c-4bbc-b693-485f91779b9f',
229             S_ISBLK => 'f1765bfc-96d5-4ff3-ba2e-16a2a9f24cb3',
230             S_ISCHR => '241431a9-c83f-4bce-93ff-0024021cd754',
231             S_ISFIFO => '3d680b7b-115c-486a-a186-4ad77facc52e',
232             S_ISSOCK => '3d1cb160-5fc5-4d8e-a8d3-3b0ec85bb000',
233             );
234              
235             $_properties{tagpool_inode_type} = {loader => sub {
236             my ($self, undef, %opts) = @_;
237             if ($opts{lifecycle} eq 'current') {
238             my $mode = $self->get('st_mode', default => undef, as => 'raw');
239             my $ise;
240              
241             if (defined($mode)) {
242             foreach my $key (keys %_S_IS_to_tagpool_ise) {
243             my $func = __PACKAGE__->can($key);
244             if (defined $func) {
245             if (eval {$func->($mode)}) {
246             $ise = $_S_IS_to_tagpool_ise{$key};
247             last;
248             }
249             }
250             }
251             }
252              
253             if (defined $ise) {
254             (($self->{properties_values} //= {})->{current} //= {})->{tagpool_inode_type} = {raw => $ise};
255             }
256             }
257             }, rawtype => 'ise'},
258             }
259              
260             sub _new {
261 2     2   11 my ($pkg, %opts) = @_;
262 2         16 my $self = $pkg->SUPER::_new(%opts, properties => \%_properties);
263              
264 2 50       7 croak 'No handle is given' unless defined $self->{handle};
265              
266 2         10 return $self;
267             }
268              
269              
270             #@returns File::Information::Filesystem
271             sub filesystem {
272 0     0 1 0 my ($self, %opts) = @_;
273 0   0     0 my $filesystem = $self->{filesystem} //= eval {
274 0         0 my $instance = $self->instance;
275 0         0 my $st_dev = $self->get('st_dev');
276 0         0 $instance->_filesystem_for($st_dev);
277             };
278              
279 0 0       0 return $filesystem if defined $filesystem;
280 0 0       0 return $opts{default} if exists $opts{default};
281 0         0 croak 'Cannot locate filesystem for inode';
282             }
283              
284              
285             sub tagpool {
286 0     0 1 0 my ($self) = @_;
287 0   0     0 my $tagpools = $self->{_tagpools} //= do {
288 0         0 my $pools = $self->instance->_tagpool;
289 0         0 [map {$pools->{$_}} keys %{$self->_tagpool_paths}]
  0         0  
  0         0  
290             };
291              
292 0 0 0     0 return wantarray ? @{$tagpools} : ($tagpools->[0] // croak 'Not part of any tagpool');
  0         0  
293             }
294              
295              
296             sub peek {
297 0     0 1 0 my ($self, %opts) = @_;
298 0   0     0 my $wanted = $opts{wanted} || 0;
299 0   0     0 my $required = $opts{required} || 0;
300 0         0 my $buffer;
301              
302 0 0 0     0 if (defined($self->{_peek_buffer}) && length($self->{_peek_buffer}) >= $required) {
303 0         0 return $self->{_peek_buffer};
304             }
305              
306 0 0       0 $wanted = $required if $required > $wanted;
307 0 0       0 $wanted = 4096 if $wanted < 4096; # enforce some minimum
308              
309 0 0       0 croak 'Requested peek too big: '.$wanted if $wanted > 65536;
310              
311 0         0 $self->_get_fh->read($buffer, $wanted);
312              
313 0 0       0 croak 'Cannot peek required amount of data' if length($buffer) < $required;
314              
315 0         0 return $self->{_peek_buffer} = $buffer;
316             }
317              
318              
319             sub open_handle {
320 0     0 1 0 my ($self, $mode) = @_;
321 0         0 my @sa;
322             my @sb;
323              
324 0 0 0     0 open(my $handle, $mode // '<', $self->{path} // croak 'Open not supported on this object') or croak 'Cannot open inode: '.$!;
      0        
325              
326             # (Re)stat() late so any effects of the open are taken into account:
327 0         0 @sa = stat($self->{handle});
328 0         0 @sb = stat($handle);
329              
330 0         0 for (my $i = 0; $i < 13; $i++) {
331 0   0     0 my $va = $sa[$i] // '<undef>';
332 0   0     0 my $vb = $sb[$i] // '<undef>';
333              
334 0 0       0 croak 'Race lost' unless $va eq $vb;
335             }
336              
337 0         0 return $handle;
338             }
339              
340             # ----------------
341              
342             sub _get_fh {
343 0     0   0 my ($self) = @_;
344 0         0 my $fh = $self->{handle};
345              
346 0 0       0 $fh->seek(0, SEEK_SET) or croak $!;
347              
348 0         0 return $fh;
349             }
350              
351             sub _tagpool_paths {
352 0     0   0 my ($self) = @_;
353              
354 0 0       0 unless (defined $self->{_tagpool_paths}) {
355 0         0 my File::Information $instance = $self->instance;
356 0         0 my $sysfile_cache = $instance->_tagpool_sysfile_cache;
357 0         0 my @stat;
358             my %paths;
359 0         0 my $found;
360              
361 0 0       0 return unless scalar @{$instance->_tagpool_path};
  0         0  
362              
363 0         0 @stat = eval {stat($self->{handle})};
  0         0  
364 0 0 0     0 return $self->{_tagpool_paths} = {} unless scalar(@stat) && S_ISREG($stat[2]);
365              
366             # Try the cache first:
367             {
368 0         0 my $key = $stat[1].'@'.$stat[0];
  0         0  
369              
370 0         0 foreach my $pool_path (keys %{$sysfile_cache}) {
  0         0  
371 0         0 $found = $sysfile_cache->{$pool_path}{$key};
372 0 0       0 if (defined $found) {
373 0         0 $paths{$pool_path} = $found;
374             }
375             }
376             }
377              
378             # Then guess:
379 0 0       0 unless (defined($found)) {
380 0 0       0 if (defined $self->{path}) {
381             outer:
382 0         0 foreach my $uuid ($self->{path} =~ /([0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12})/g) {
383 0         0 foreach my $pool_path (@{$instance->_tagpool_path}) {
  0         0  
384 0         0 my $info_path = File::Spec->catdir($pool_path => 'data', 'info.'.$uuid);
385 0         0 my $info;
386              
387 0 0       0 next unless -f $info_path;
388 0         0 $info = eval {
389 0         0 my $reader = File::ValueFile::Simple::Reader->new($info_path, supported_formats => [], supported_features => []);
390 0         0 $reader->read_as_simple_tree;
391             };
392              
393 0 0 0     0 if (defined($info) && defined($info->{'pool-name-suffix'})) {
394 0   0     0 my $local_cache = $sysfile_cache->{$pool_path} //= {};
395 0         0 my @c_stat = stat(File::Spec->catfile($pool_path, 'data', $info->{'pool-name-suffix'}));
396              
397 0 0       0 next unless scalar @c_stat;
398              
399 0         0 $local_cache->{$c_stat[1].'@'.$c_stat[0]} = $info->{'pool-name-suffix'};
400              
401 0 0 0     0 if ($c_stat[0] eq $stat[0] && $c_stat[1] eq $stat[1]) {
402 0         0 $found = $info->{'pool-name-suffix'};
403 0         0 $paths{$pool_path} = $found;
404             }
405             }
406             }
407             }
408             }
409             }
410              
411             # Then try the pool:
412 0 0       0 unless (defined($found)) {
413             outer:
414 0         0 foreach my $pool_path (@{$instance->_tagpool_path}) {
  0         0  
415 0         0 my $data_path = File::Spec->catdir($pool_path => 'data');
416 0   0     0 my $local_cache = $sysfile_cache->{$pool_path} //= {};
417              
418 0 0       0 next if $local_cache->{complete};
419              
420 0 0       0 if (opendir(my $dir, $data_path)) {
421 0         0 my @c_stat = stat($dir);
422              
423 0 0       0 next if $c_stat[0] ne $stat[0];
424              
425 0         0 while (my $entry = readdir($dir)) {
426 0 0       0 $entry =~ /^file\./ or next; # skip everything that is not a file.* to begin with.
427              
428 0         0 @c_stat = stat(File::Spec->catfile($data_path, $entry));
429 0 0       0 next unless scalar @c_stat;
430              
431 0         0 $local_cache->{$c_stat[1].'@'.$c_stat[0]} = $entry;
432              
433 0 0 0     0 if ($c_stat[0] eq $stat[0] && $c_stat[1] eq $stat[1]) {
434 0         0 $found = $entry;
435 0         0 $paths{$pool_path} = $found;
436             }
437             }
438              
439 0         0 $local_cache->{complete} = 1;
440             }
441             }
442             }
443              
444 0         0 $self->{_tagpool_paths} = \%paths;
445             }
446              
447 0         0 return $self->{_tagpool_paths};
448             }
449              
450             sub _load_stat {
451 2     2   20 my ($self, undef, %opts) = @_;
452 2 50 33     14 if ($opts{lifecycle} eq 'current' && !$self->{_loaded_stat}) {
453 2   50     11 my $pv = ($self->{properties_values} //= {})->{current} //= {};
      50        
454 2         5 my @values = eval {stat($self->{handle})};
  2         33  
455 2         13 my @keys = qw(st_dev st_ino st_mode st_nlink st_uid st_gid st_rdev st_size st_atime st_mtime st_ctime st_blksize st_blocks);
456              
457 2 50       7 if (scalar @values) {
458 2         8 for (my $i = 0; $i < scalar(@keys); $i++) {
459 26         37 my $value = $values[$i];
460 26         38 my $key = $keys[$i];
461              
462 26 50       51 next if $key eq ':skip';
463 26 50       54 next if $value eq '';
464 26 100 66     109 next if $value == 0 && ($key eq 'st_ino' || $key eq 'st_rdev' || $key eq 'st_blksize');
      66        
465 24 50       80 next if $value < 0;
466              
467 24         94 $pv->{$key} = {raw => $values[$i]};
468             }
469              
470 2         7 $pv->{stat_readonly} = {raw => !($values[2] & (S_IWUSR|S_IWGRP|S_IWOTH))};
471 2 50 33     16 $pv->{stat_cachehash} = {raw => $values[1].'@'.$values[0]} if $values[1] > 0 && $values[0] ne '';
472             }
473              
474 2         17 $self->{_loaded_stat} = 1;
475             }
476             }
477              
478             sub _load_contentise {
479 0     0   0 my ($self, $key, %opts) = @_;
480 0         0 my $lifecycle = $opts{lifecycle};
481 0   0     0 my $pv = ($self->{properties_values} //= {})->{$lifecycle} //= {};
      0        
482 0         0 my $digest_sha_1_160 = $self->digest('sha-1-160', as => 'utag', lifecycle => $lifecycle, default => undef);
483 0         0 my $digest_sha_3_512 = $self->digest('sha-3-512', as => 'utag', lifecycle => $lifecycle, default => undef);
484              
485 0 0       0 if (defined $digest_sha_3_512) {
486 0         0 my $id = Data::Identifier::Generate->generic(namespace => '66d488c0-3b19-4e6c-856f-79edf2484f37', input => $digest_sha_3_512);
487 0         0 $pv->{content_sha_3_512_uuid} = {raw => $id->uuid};
488             }
489              
490 0 0 0     0 if (defined($digest_sha_1_160) && defined($digest_sha_3_512)) {
491 0         0 my $digest = $digest_sha_1_160.' '.$digest_sha_3_512;
492 0         0 $digest =~ s/^v0 /v0m /;
493 0         0 my $id = Data::Identifier::Generate->generic(namespace => '66d488c0-3b19-4e6c-856f-79edf2484f37', input => $digest);
494 0         0 $pv->{content_sha_1_160_sha_3_512_uuid} = {raw => $id->uuid};
495             }
496             }
497              
498             sub _load_xattr {
499 0     0   0 my ($self, $key, %opts) = @_;
500 0         0 my $info = $self->{properties}{$key};
501 0   0     0 my $lifecycle = $info->{lifecycle} // 'current';
502 0   0     0 my $pv = ($self->{properties_values} //= {})->{$lifecycle} //= {};
      0        
503 0         0 my $value;
504             my $fh;
505              
506 0 0 0     0 return unless ($opts{lifecycle} // 'current') eq $lifecycle;
507              
508 0 0       0 croak 'Not supported, requires File::ExtAttr' unless $HAVE_XATTR;
509              
510 0   0     0 $self->{_loaded_xattr} //= {};
511 0 0       0 return if $self->{_loaded_xattr}{$key};
512 0         0 $self->{_loaded_xattr}{$key} = 1;
513              
514 0         0 $fh = File::Information::Inode::_DUMMY_FOR_XATTR->new($self->{handle});
515 0         0 $value = eval {File::ExtAttr::getfattr($fh, $info->{xattr_key})};
  0         0  
516              
517 0 0 0     0 return unless defined($value) && length($value);
518              
519 0         0 $pv->{$key} = {raw => $value};
520              
521 0 0       0 if (defined(my $parts = $info->{parts})) {
522 0         0 my @values = split(/\s+/, $value);
523 0         0 my $out = $pv->{$key};
524              
525 0         0 for (my $i = 0; $i < scalar(@{$parts}); $i++) {
  0         0  
526 0 0 0     0 if (defined($values[$i]) && length($values[$i])) {
527 0         0 $out->{$parts->[$i]} = $values[$i];
528             }
529             }
530 0         0 $out->{rawtype} = 'multipart';
531             }
532              
533 0 0       0 if (defined(my $parsing = $info->{parsing})) {
534 0 0       0 if ($parsing eq 'utag') {
535 0         0 my $v = $value;
536 0         0 my %digest;
537             my $given_size;
538              
539 0         0 $given_size = $self->_set_digest_utag($lifecycle => $v, $given_size);
540              
541 0 0       0 $pv->{xattr_utag_final_file_hash_size} = {raw => $given_size} if defined $given_size;
542 0   0     0 $self->{digest} //= {};
543              
544             {
545 0   0     0 my $digests = $self->{digest}{$lifecycle} //= {};
  0         0  
546 0         0 foreach my $algo (keys %digest) {
547 0   0     0 $digests->{$algo} //= $digest{$algo};
548             }
549             }
550             }
551             }
552             }
553              
554             # Bad workaround for File::ExtAttr
555             package File::Information::Inode::_DUMMY_FOR_XATTR {
556             sub new {
557 0     0   0 my ($pkg, $fh) = @_;
558 0         0 return bless \$fh;
559             }
560             sub isa {
561 0     0   0 my ($self, $pkg) = @_;
562 0 0       0 return 1 if $pkg eq 'IO::Handle';
563 0         0 return $self->SUPER::isa($pkg);
564             }
565             sub fileno {
566 0     0   0 my ($self) = @_;
567 0         0 return ${$self}->fileno;
  0         0  
568             }
569             }
570              
571             sub _load_tagpool_directory {
572 0     0   0 my ($self) = @_;
573 0   0     0 my $pv = $self->{properties_values} //= {};
574 0         0 my $tree;
575              
576 0 0       0 return if $self->{_loaded_tagpool_directory};
577 0         0 $self->{_loaded_tagpool_directory} = 1;
578              
579 0         0 eval {
580 0         0 my @stat = stat($self->{handle});
581              
582 0 0 0     0 if (scalar(@stat) && S_ISDIR($stat[2])) {
583 0   0     0 my $c = $pv->{current} //= {};
584 0         0 $c->{tagpool_directory_timestamp} = {raw => time()};
585              
586 0         0 $c->{tagpool_directory_inode} = {raw => $stat[1]};
587 0         0 $c->{tagpool_directory_mtime} = {raw => $stat[9]};
588             }
589             };
590              
591 0 0       0 return unless defined $self->{path};
592 0 0       0 return unless $HAVE_FILE_VALUEFILE;
593              
594 0         0 eval {
595 0         0 my $path = File::Spec->catfile($self->{path}, '.tagpool-info', 'directory');
596 0         0 my $reader = File::ValueFile::Simple::Reader->new($path, supported_formats => '11431b85-41cd-4be5-8d88-a769ebbd603f', supported_features => []);
597 0         0 $tree = $reader->read_as_simple_tree;
598             };
599              
600 0 0       0 if (defined $tree) {
601 0         0 foreach my $key (qw(title comment description)) {
602 0         0 my $value = $tree->{$key};
603 0 0 0     0 if (defined($value) && !ref($value) && length($value)) {
      0        
604 0   0     0 $pv->{current} //= {};
605 0         0 $pv->{current}{'tagpool_directory_'.$key} = {raw => $value};
606             }
607             }
608              
609 0         0 foreach my $key (qw(inode mtime pool-uuid timestamp)) {
610 0         0 foreach my $lifecycle (qw(initial last)) {
611 0         0 my $value = $tree->{$lifecycle.'-'.$key};
612 0 0 0     0 if (defined($value) && !ref($value) && length($value)) {
      0        
613 0   0     0 my $c = $pv->{$lifecycle} //= {};
614              
615 0         0 $c->{'tagpool_directory_'.($key =~ tr/-/_/r)} = {raw => $value};
616             }
617             }
618             }
619              
620 0 0       0 if (defined(my $setting = $tree->{'directory-setting'})) {
621 0         0 foreach my $key (qw(thumbnail-uri thumbnail-mode update-mode add-mode file-tags tag-mode tag-implies entry-sort-order tag tag-root tag-parent tag-type entry-display-name entry-sort-key)) {
622 0         0 my $value = $setting->{$key};
623 0 0 0     0 if (defined($value) && !ref($value) && length($value)) {
      0        
624 0         0 my $val = {raw => $value};
625 0   0     0 $pv->{current} //= {};
626 0         0 $pv->{current}{'tagpool_directory_setting_'.($key =~ tr/-/_/r)} = $val;
627              
628             # Add ise if known:
629 0 0       0 if (defined(my $info = $_tagpool_directory_setting_tagmap{$key})) {
630 0 0       0 if (defined(my $entry = $info->{$value})) {
631 0         0 $val->{ise} = $entry->{ise};
632             }
633             }
634             }
635             }
636             }
637              
638 0 0       0 if (defined(my $option = $tree->{'throw-option'})) {
639 0         0 foreach my $key (qw(linkname linktype filter)) {
640 0         0 my $value = $option->{$key};
641 0 0 0     0 if (defined($value) && !ref($value) && length($value)) {
      0        
642 0   0     0 $pv->{current} //= {};
643 0         0 $pv->{current}{'tagpool_directory_throw_option_'.$key} = {raw => $value};
644             }
645             }
646             }
647             }
648             }
649              
650             sub _load_tagpool_file {
651 8     8   17 my ($self) = @_;
652 8         48 my File::Information $instance = $self->instance;
653 8         28 my $sysfile_cache = $instance->_tagpool_sysfile_cache;
654 8   50     20 my $pv = $self->{properties_values} //= {};
655 8         18 my @stat;
656             my $found;
657 8         0 my $in_pool;
658              
659 8 100       27 return if $self->{_loaded_tagpool_file};
660 2         6 $self->{_loaded_tagpool_file} = 1;
661              
662 2 50       4 return unless scalar @{$instance->_tagpool_path};
  2         5  
663              
664 0         0 @stat = eval {stat($self->{handle})};
  0         0  
665 0 0 0     0 return unless scalar(@stat) && S_ISREG($stat[2]);
666              
667             {
668 0   0     0 my $c = $pv->{current} //= {};
  0         0  
669 0         0 $c->{tagpool_file_timestamp} = {raw => time()};
670              
671 0         0 $c->{tagpool_file_inode} = {raw => $stat[1]};
672 0         0 $c->{tagpool_file_size} = {raw => $stat[7]};
673 0         0 $c->{tagpool_file_mtime} = {raw => $stat[9]};
674             }
675              
676             # Try to find the file:
677 0         0 ($in_pool, $found) = %{$self->_tagpool_paths};
  0         0  
678              
679 0 0 0     0 return unless defined($in_pool) && defined($found);
680              
681 0 0       0 if ($found =~ /^file\.([0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12})(?:\..*)?$/) {
682 0         0 my $uuid = $1;
683 0         0 my $info = eval {
684 0         0 my $path = File::Spec->catfile($in_pool, 'data' => 'info.'.$uuid);
685 0         0 my $reader = File::ValueFile::Simple::Reader->new($path, supported_formats => [], supported_features => []);
686 0         0 $reader->read_as_simple_tree;
687             };
688 0         0 my $tags = eval {
689 0         0 my $path = File::Spec->catfile($in_pool, 'data' => 'tags.'.$uuid);
690 0         0 my $reader = File::ValueFile::Simple::Reader->new($path, supported_formats => [], supported_features => []);
691 0         0 $reader->read_as_hash_of_arrays;
692             };
693 0 0 0     0 if (defined($info) && defined($tags)) {
694 0   0     0 $pv->{current} //= {};
695 0         0 $pv->{current}{tagpool_file_uuid} = {raw => $uuid};
696              
697 0         0 foreach my $key (qw(title comment description original-url original-description-url pool-name-suffix original-filename)) {
698 0         0 my $value = $info->{$key};
699 0 0 0     0 if (defined($value) && !ref($value) && length($value)) {
      0        
700 0         0 $pv->{current}{'tagpool_file_'.($key =~ tr/-/_/r)} = {raw => $value};
701             }
702             }
703              
704 0         0 foreach my $key (qw(mtime timestamp inode size actual-size)) {
705 0         0 foreach my $lifecycle (qw(initial last final)) {
706 0         0 my $value = $info->{$lifecycle.'-'.$key};
707 0 0 0     0 if (defined($value) && !ref($value) && length($value)) {
      0        
708 0   0     0 my $c = $pv->{$lifecycle} //= {};
709              
710 0         0 $c->{'tagpool_file_'.($key =~ tr/-/_/r)} = {raw => $value};
711             }
712             }
713             }
714              
715             # Digest:
716 0         0 foreach my $key (keys %{$info}) {
  0         0  
717 0 0       0 if (my ($lifecycle, $tagpool_name) = $key =~ /^(initial|last|final)-hash-(.+)$/) {
718 0 0       0 my $utag_name = $File::Information::Base::_digest_name_converter{$tagpool_name} or next;
719 0         0 my $value = $info->{$key};
720 0 0       0 my ($size) = $utag_name =~ /-([0-9]+)$/ or next;
721              
722 0 0       0 next unless $value =~ /^[0-9a-f]+$/;
723 0 0       0 next unless length($value) == ($size / 4);
724 0   0     0 $self->{digest} //= {};
725 0   0     0 $self->{digest}{$lifecycle} //= {};
726 0         0 $self->{digest}{$lifecycle}{$utag_name} = $value;
727             }
728             }
729              
730             # Tags:
731             {
732 0   0     0 my @next = @{$tags->{'tagged-as'} // []};
  0         0  
733              
734 0         0 $pv->{current}{tagpool_file_tags} = [map {{raw => $_}} @next];
  0         0  
735              
736 0         0 while (scalar(@next)) {
737 0         0 my @current = @next;
738 0         0 @next = ();
739              
740 0         0 foreach my $tag (@current) {
741 0         0 my $info = $_wk_tagged_as_tags{$tag};
742 0 0 0     0 next unless defined($info) && defined($info->{for});
743              
744 0 0       0 if ($info->{for} eq 'write-mode') {
    0          
    0          
745 0         0 $pv->{current}{tagpool_file_write_mode} = {raw => $tag};
746             } elsif ($info->{for} eq 'mediatype') {
747 0         0 $pv->{current}{tagpool_file_mediatype} = {raw => $info->{mediatype}, ise => $tag};
748             } elsif ($info->{for} eq 'finalmode') {
749 0         0 $pv->{current}{tagpool_file_finalmode} = {raw => $tag};
750             } else {
751 0         0 croak 'BUG!';
752             }
753              
754 0 0       0 push(@next, @{$info->{implies}}) if defined $info->{implies};
  0         0  
755             }
756             }
757             }
758              
759             # Media Type:
760             {
761 0         0 my $value = readlink(File::Spec->catfile($in_pool, qw(cache mimetype file), $uuid));
  0         0  
762 0 0 0     0 if (defined($value) && length($value)) {
763 0   0     0 $pv->{current}{tagpool_file_mediatype} //= {raw => $value};
764             }
765             }
766              
767             # Write mode:
768             {
769 0         0 my $value = readlink(File::Spec->catfile($in_pool, qw(cache write-mode file), $uuid));
  0         0  
770 0 0 0     0 if (defined($value) && length($value)) {
771 0   0     0 $pv->{current}{tagpool_file_write_mode} //= {raw => $value};
772             }
773             }
774              
775             {
776 0         0 my $value = File::Spec->catfile($in_pool, qw(cache thumbnail file), $uuid.'.png');
  0         0  
  0         0  
777 0         0 my @c_stat = stat($value);
778 0 0       0 if (scalar(@c_stat)) {
779 0 0       0 if ($stat[9] < $c_stat[9]) {
780 0   0     0 $pv->{current}{tagpool_file_thumbnail} //= {raw => $value};
781             }
782             }
783             }
784             }
785             }
786             }
787              
788             sub _load_magic {
789 0     0   0 my ($self) = @_;
790 0   0     0 my $pv = ($self->{properties_values} //= {})->{current} //= {};
      0        
791 0         0 my $data;
792             my $media_type;
793              
794 0 0       0 return if $self->{_loaded_magic};
795 0         0 $self->{_loaded_magic} = 1;
796              
797 0         0 $data = eval {$self->peek};
  0         0  
798              
799 0 0       0 return unless defined $data;
800              
801 0 0 0     0 if (substr($data, 0, 22) eq '<!DOCTYPE HTML PUBLIC ' || substr($data, 0, 22) eq '<!DOCTYPE html PUBLIC ' || substr($data, 0, 22) eq '<!DOCTYPE HTML SYSTEM ' || uc(substr($data, 0, 15)) eq '<!DOCTYPE HTML>' ||
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0          
802             lc(substr($data, 0, 6)) eq '<html>' ||
803             $data =~ /^<\?xml version="1\.0" encoding="utf-8"\?>\r?\n?<\!DOCTYPE html PUBLIC /) {
804 0         0 $media_type = 'text/html';
805             } elsif ($data =~ /^<\?xml version="1\.0" encoding="UTF-8"\?>\s*<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1\.0"[^>]+office:mimetype="(application\/vnd\.oasis\.opendocument\.(?:text|spreadsheet|presentation|graphics|chart|formula|image|text-master|(?:text|spreadsheet|presentation|graphics)-template))"[^>]*>/) {
806 0         0 $media_type = $1;
807             } elsif ($data =~ /^PK\003\004....\0\0................\010\0\0\0mimetype(application\/vnd\.oasis\.opendocument\.(?:text|spreadsheet|presentation|graphics|chart|formula|image|text-master|(?:text|spreadsheet|presentation|graphics)-template))PK\003\004/) {
808 0         0 $media_type = $1;
809             } elsif (substr($data, 0, 8) eq "!<arch>\n") {
810 0 0       0 if ($data =~ /^!<arch>\ndebian-binary [0-9 ]{12}0 0 [0-7 ]{8}[0-9] `\n/) {
811 0         0 $media_type = 'application/vnd.debian.binary-package';
812             } else {
813 0         0 $media_type = 'application/x-archive';
814             }
815             } elsif ($data =~ /^!!ValueFile ([0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12})\s+(!null|[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}|[0-2](?:\.(?:0|[1-9][0-9]*))+|[a-zA-Z][a-zA-Z0-9\+\.\-]+[^\s%]+)[\s\r\n]/) {
816 0         0 my ($version, $format) = ($1, $2);
817 0         0 $pv->{magic_valuefile_version} = {raw => $version};
818 0 0       0 $pv->{magic_valuefile_format} = {raw => $format} unless $format =~ /^!/;
819             } elsif ($data =~ /^\0([\x07-\x3f])VM\x0d\x0a\xc0\x0a/ && (ord($1) & 07) == 07) {
820 0         0 $media_type = 'application/vnd.sirtx.vmv0';
821             } elsif ($data =~ /^RIFF.{4}WEBPVP8/) {
822 0         0 $media_type = 'image/webp';
823             } else {
824 0         0 foreach my $magic (sort {length($b) <=> length($a)} keys %_magic_map) {
  0         0  
825 0 0       0 if (substr($data, 0, length($magic)) eq $magic) {
826 0         0 $media_type = $_magic_map{$magic};
827 0         0 last;
828             }
829             }
830             }
831              
832 0 0       0 $pv->{magic_mediatype} = {raw => $media_type} if defined $media_type;
833             }
834              
835             sub _load_db {
836 0     0   0 my ($self, $key, %opts) = @_;
837 0   0     0 my $pv = ($self->{properties_values} //= {})->{current} //= {};
      0        
838              
839 0 0       0 return if $self->{_loaded_db};
840 0         0 $self->{_loaded_db} = 1;
841              
842 0 0       0 if (defined(my $db = eval { $self->instance->db })) {
  0         0  
843 0         0 eval {
844 0         0 my $inode = $self->get('st_ino', as => 'raw');
845 0         0 my $fs = $self->filesystem->get('ise', as => 'Data::TagDB::Tag');
846 0         0 my $inode_number = $db->tag_by_id(uuid => 'd2526d8b-25fa-4584-806b-67277c01c0db');
847 0         0 my $also_on_filesystem = $db->tag_by_id(uuid => 'cd5bfb11-620b-4cce-92bd-85b7d010f070');
848 0         0 my $wk = $db->wk;
849 0         0 my $metadata = $db->metadata(relation => $wk->also_shares_identifier, type => $inode_number, data_raw => $inode);
850 0         0 my $res;
851              
852             #warn sprintf('inode=%s, inode_number=%s, fs=%s', $inode, $inode_number, $fs);
853             $metadata->foreach(sub {
854 0     0   0 my ($entry) = @_;
855 0         0 my $fs_relation = $db->relation(tag => $entry->tag, relation => $also_on_filesystem, related => $fs)->one;
856 0         0 $res = $entry->tag;
857             #warn $fs_relation;
858 0         0 });
859              
860 0 0       0 $pv->{db_inode_tag} = {raw => $res} if defined $res;
861             };
862             }
863             }
864              
865             sub _load_redirect {
866 0     0   0 my ($self, $key, %opts) = @_;
867 0         0 my $info = $self->{properties}{$key};
868              
869 0         0 $self->get($info->{redirect}, lifecycle => $opts{lifecycle}, default => undef, as => 'raw');
870             }
871              
872             sub _load_zonetransfer {
873 0     0   0 my ($self, $key, %opts) = @_;
874 0         0 my $info = $self->{properties}{$key};
875 0   0     0 my $pv = ($self->{properties_values} //= {})->{current} //= {};
      0        
876 0         0 my $raw;
877             my $parsed;
878              
879 0 0       0 return if $self->{_loaded_zonetransfer};
880 0         0 $self->{_loaded_zonetransfer} = 1;
881              
882 0 0       0 if ($HAVE_XATTR) {
883 0         0 my $fh = File::Information::Inode::_DUMMY_FOR_XATTR->new($self->{handle});
884 0         0 $raw = eval {File::ExtAttr::getfattr($fh, 'Zone.Identifier')};
  0         0  
885             }
886              
887 0 0 0     0 if (!defined($raw) && $^O eq 'MSWin32' && defined($self->{path})) {
      0        
888 0 0       0 if (open(my $ads, '<', sprintf('%s:Zone.Identifier', $self->{path}))) {
889 0         0 local $/ = undef;
890 0         0 $raw = <$ads>;
891 0         0 close($ads);
892             }
893             }
894              
895 0 0       0 return unless defined $raw;
896              
897 0         0 $parsed = Config::INI::Reader->read_string($raw);
898              
899 0 0       0 if (defined(my $ZoneTransfer = $parsed->{ZoneTransfer})) {
900 0         0 foreach my $key (qw(HostIpAddress ZoneId ReferrerUrl HostUrl)) {
901 0         0 my $value = $ZoneTransfer->{$key};
902              
903 0 0 0     0 next unless defined($value) && length($value);
904              
905 0         0 $pv->{'zonetransfer_'.lc($key)} = {raw => $value};
906              
907 0 0 0     0 if ($key eq 'ZoneId' && defined(my $zone = $_URLZONE{$value})) {
908 0   0     0 $pv->{'zonetransfer_'.lc($key)}{ise} //= $zone->{ise};
909             }
910             }
911             }
912             }
913              
914             sub _load_ntfs_xattr {
915 0     0   0 my ($self, $key, %opts) = @_;
916 0         0 my $info = $self->{properties}{$key};
917 0   0     0 my $pv = ($self->{properties_values} //= {})->{current} //= {};
      0        
918 0         0 my $attrb;
919              
920 0 0       0 return if $self->{_loaded_ntfs_xattr};
921 0         0 $self->{_loaded_ntfs_xattr} = 1;
922              
923 0 0       0 if ($HAVE_XATTR) {
924 0         0 my $fh = File::Information::Inode::_DUMMY_FOR_XATTR->new($self->{handle});
925 0         0 my $raw = eval {File::ExtAttr::getfattr($fh, 'ntfs_attrib_be', {namespace => 'system'})};
  0         0  
926 0 0       0 $attrb = unpack('N', $raw) if defined $raw;
927             }
928              
929 0 0       0 if (defined $attrb) {
930 0         0 foreach my $key (keys %_ntfs_attributes) {
931 0         0 $pv->{'ntfs_'.lc($key)} = {raw => ($attrb & $_ntfs_attributes{$key})};
932             }
933             }
934             }
935              
936             sub _load_fstore {
937 4     4   15 my ($self, $key, %opts) = @_;
938 4         11 my $dev;
939             my $inode;
940 4         0 my @candidates;
941              
942 4 100       14 return if $self->{_loaded_fstore};
943 2         5 $self->{_loaded_fstore} = 1;
944              
945 2         7 $dev = $self->get('st_dev', default => undef);
946 2         7 $inode = $self->get('st_ino', default => undef);
947              
948 2 50 33     11 return unless defined($dev) && defined($inode);
949              
950 2         7 foreach my $store ($self->instance->store(as => 'File::FStore')) {
951 0         0 foreach my $candidate ($store->query(properties => inode => $inode)) {
952 0         0 my @stat = $candidate->stat;
953              
954 0 0 0     0 if (defined($stat[0]) && length($stat[0]) && $stat[0] != 0) {
      0        
955 0 0 0     0 if (defined($stat[1]) && length($stat[1]) && $stat[1] > 0) {
      0        
956 0 0 0     0 if ($stat[0] == $dev && $stat[1] == $inode) {
957 0         0 push(@candidates, $candidate);
958             }
959             }
960             }
961             }
962             }
963              
964 2 50       23 if (scalar(@candidates)) {
965 0   0       my $pv_current = ($self->{properties_values} //= {})->{current} //= {};
      0        
966 0   0       my $pv_final = ($self->{properties_values} //= {})->{final} //= {};
      0        
967              
968 0           $pv_current->{store_file} = [map {{raw => $_}} @candidates];
  0            
969 0           $pv_final->{store_file} = [map {{raw => $_}} @candidates];
  0            
970             }
971             }
972              
973             sub _load_shebang {
974 0     0     my ($self, $key, %opts) = @_;
975 0   0       my $pv = ($self->{properties_values} //= {})->{current} //= {};
      0        
976              
977 0 0         return if $self->{_loaded_shebang};
978 0           $self->{_loaded_shebang} = 1;
979              
980 0 0         if ($self->peek =~ /^(#\!.+)\r?\n/) {
981 0           my $line = $1;
982 0           my $interpreter;
983              
984 0           $pv->{shebang_line} = {raw => $line};
985              
986 0 0         if ($line =~ m(^#\!(?:(?:/usr)?(?:/local)?/s?bin/)?env\s+(\S+)(\s.*)?$)) {
    0          
987 0           $interpreter = $1;
988 0           eval {
989 0           require File::Which;
990              
991 0           $interpreter = File::Which::which($interpreter);
992              
993             };
994             } elsif ($line =~ m(^#\!(\S+)(?:\s.*)?$)) {
995 0           $interpreter = $1;
996             }
997              
998 0 0 0       $pv->{shebang_interpreter} = {raw => $interpreter} if defined($interpreter) && length($interpreter);
999             }
1000             }
1001              
1002             1;
1003              
1004             __END__
1005              
1006             =pod
1007              
1008             =encoding UTF-8
1009              
1010             =head1 NAME
1011              
1012             File::Information::Inode - generic module for extracting information from filesystems
1013              
1014             =head1 VERSION
1015              
1016             version v0.16
1017              
1018             =head1 SYNOPSIS
1019              
1020             use File::Information;
1021              
1022             my File::Information $instance = File::Information->new(%config);
1023              
1024             my File::Information::Inode $inode = $instance->for_handle($handle);
1025              
1026             my File::Information::Inode $inode = $instance->for_link($path)->inode;
1027              
1028             B<Note:> This package inherits from L<File::Information::Base>.
1029              
1030             This module represents an inode on a filesystem. An inode contains basic file metadata (such as type and size) and the file's content.
1031             Inodes are commonly represented by an inode number (but this is subject to filesystem implementation and limitations).
1032             In order to access inodes they most commonly need to have at least one hardlink pointing to them.
1033             See also L<File::Information::Link>.
1034              
1035             =head1 METHODS
1036              
1037             =head2 filesystem
1038              
1039             my File::Information::Filesystem $filesystem = $inode->filesystem([ %opts ]);
1040              
1041             Provides access to the filesystem object for the filesystem this inode is on.
1042             Dies if no filesystem could be found.
1043              
1044             Takes the following options (all optional):
1045              
1046             =over
1047              
1048             =item C<default>
1049              
1050             The value to be returned when no filesystem could be found.
1051             This can also be C<undef> which switches
1052             from C<die>-ing when no value is available to returning C<undef>.
1053              
1054             =back
1055              
1056             =head2 tagpool
1057              
1058             my File::Information::Tagpool $tagpool = $inode->tagpool;
1059             # or:
1060             my @tagpool = $inode->tagpool;
1061              
1062             This method returns any tagpool instances this file is part of.
1063             If called in scalar context only one is returned and if none have been found this function C<die>s.
1064             If called in list context the list is returned and an empty list is returned in case none have been found.
1065              
1066             If called in scalar context it is not clear which is returned in case the file is part of multiple pools.
1067             However the result is cached and for the same instance of this object always the same tagpool instance is returned.
1068              
1069             =head2 peek
1070              
1071             my $data = $inode->peek( [ %opts ] );
1072              
1073             Peeks the first few bytes of a file. The main usage of this method is to check for magic numbers.
1074              
1075             The following options (all optional) are supported:
1076              
1077             =over
1078              
1079             =item C<wanted>
1080              
1081             The number of bytes wanted. If this number of bytes can't be provided less is returned.
1082              
1083             =item C<required>
1084              
1085             The number of bytes that are needed. If this number of bytes can't be provided the method C<die>s.
1086              
1087             =back
1088              
1089             =head2 open_handle
1090              
1091             my $handle = $inode->open_handle( [ $mode ] );
1092              
1093             (experimental since v0.15)
1094              
1095             This method opens a new file handle to this inode. This can be used to read or write data from or to this inode.
1096              
1097             C<$mode> is the same as C<MODE> in L<perlfunc/open>.
1098             If no C<$mode> is not given or undefined the file is opened for reading (as per C<E<lt>>).
1099              
1100             B<Note:>
1101             Future versions of this method might change their interface.
1102             However calling without any parameters is likely to be the most future-proof way.
1103              
1104             B<Note:>
1105             All considerations of L<perlfunc/binmode> apply to the freshly returned handle.
1106             Also seeking on the returned handle as well as closing it will not have an effect on other handles.
1107             Each returned handle is a fresh handle.
1108              
1109             B<Note:>
1110             If you want to read some data from the file consider to use L</peek> as it often provides a better alternative.
1111              
1112             B<Note:>
1113             Availability depends on the operating system, the filesystem, and the current state.
1114              
1115             =head1 AUTHOR
1116              
1117             Philipp Schafft <lion@cpan.org>
1118              
1119             =head1 COPYRIGHT AND LICENSE
1120              
1121             This software is Copyright (c) 2024-2025 by Philipp Schafft <lion@cpan.org>.
1122              
1123             This is free software, licensed under:
1124              
1125             The Artistic License 2.0 (GPL Compatible)
1126              
1127             =cut