File Coverage

lib/SMB/File.pm
Criterion Covered Total %
statement 127 154 82.4
branch 73 102 71.5
condition 21 32 65.6
subroutine 26 36 72.2
pod 1 24 4.1
total 248 348 71.2


line stmt bran cond sub pod time code
1             # SMB-Perl library, Copyright (C) 2014 Mikhael Goikhman, migo@cpan.org
2             #
3             # This program is free software: you can redistribute it and/or modify
4             # it under the terms of the GNU General Public License as published by
5             # the Free Software Foundation, either version 3 of the License, or
6             # (at your option) any later version.
7             #
8             # This program is distributed in the hope that it will be useful,
9             # but WITHOUT ANY WARRANTY; without even the implied warranty of
10             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11             # GNU General Public License for more details.
12             #
13             # You should have received a copy of the GNU General Public License
14             # along with this program. If not, see .
15              
16             package SMB::File;
17              
18 3     3   68618 use strict;
  3         7  
  3         101  
19 3     3   16 use warnings;
  3         8  
  3         93  
20              
21 3     3   1760 use parent 'SMB';
  3         560  
  3         16  
22              
23 3     3   3076 use Time::HiRes qw(stat);
  3         5869  
  3         19  
24 3     3   680 use File::Basename qw(basename);
  3         7  
  3         258  
25 3     3   16 use File::Glob qw(bsd_glob GLOB_NOCASE GLOB_BRACE);
  3         7  
  3         352  
26 3     3   22 use Fcntl qw(:mode O_DIRECTORY O_RDONLY O_RDWR O_CREAT O_EXCL O_TRUNC);
  3         19  
  3         1126  
27 3     3   3131 use POSIX qw(strftime);
  3         29643  
  3         19  
28              
29 3     3   4717 use SMB::Time qw(from_nttime to_nttime);
  3         8  
  3         281  
30              
31             use constant {
32 3         878 ATTR_READONLY => 0x00000001,
33             ATTR_HIDDEN => 0x00000002,
34             ATTR_SYSTEM => 0x00000004,
35             ATTR_DIRECTORY => 0x00000010,
36             ATTR_ARCHIVE => 0x00000020,
37             ATTR_DEVICE => 0x00000040,
38             ATTR_NORMAL => 0x00000080,
39             ATTR_TEMPORARY => 0x00000100,
40             ATTR_SPARSE_FILE => 0x00000200,
41             ATTR_REPARSE_POINT => 0x00000400,
42             ATTR_COMPRESSED => 0x00000800,
43             ATTR_OFFLINE => 0x00001000,
44             ATTR_NOT_CONTENT_INDEXED => 0x00002000,
45             ATTR_ENCRYPTED => 0x00004000,
46              
47             DISPOSITION_SUPERSEDE => 0, # exists ? supersede : create
48             DISPOSITION_OPEN => 1, # exists ? open : fail
49             DISPOSITION_CREATE => 2, # exists ? fail : create
50             DISPOSITION_OPEN_IF => 3, # exists ? open : create
51             DISPOSITION_OVERWRITE => 4, # exists ? overwrite : fail
52             DISPOSITION_OVERWRITE_IF => 5, # exists ? overwrite : create
53              
54             ACTION_NONE => -1,
55             ACTION_SUPERSEDED => 0, # existing file was deleted and new file was created in its place
56             ACTION_OPENED => 1, # existing file was opened
57             ACTION_CREATED => 2, # new file was created
58             ACTION_OVERWRITTEN => 3, # new file was overwritten
59 3     3   19 };
  3         5  
60              
61 3     3   1020 use SMB::OpenFile;
  3         6  
  3         6235  
62              
63             sub from_ntattr ($) {
64 21   50 21 0 94 my $attr = shift || 0;
65              
66             return
67 21 50       1523 $attr & ATTR_DIRECTORY ? O_DIRECTORY :
    50          
68             $attr & ATTR_READONLY ? O_RDONLY : O_RDWR;
69             }
70              
71             sub to_ntattr ($) {
72 8   50 8 0 17 my $mode = shift || 0;
73              
74 8 100       104 return 0
    100          
    50          
    50          
75             | (S_ISREG($mode) ? ATTR_NORMAL : 0)
76             | (S_ISDIR($mode) ? ATTR_DIRECTORY : 0)
77             # | (S_ISBLK($mode) ? ATTR_DEVICE : 0)
78             | (S_ISCHR($mode) ? ATTR_DEVICE : 0)
79             | ($mode & S_IWUSR ? 0 : ATTR_READONLY)
80             ;
81             }
82              
83             sub new ($%) {
84 9     9 1 179 my $class = shift;
85 9         29 my %options = @_;
86              
87 9   50     30 my $name = delete $options{name} // die "No name in constructor";
88 9         18 $name =~ s!\\!\/!g;
89 9         14 $name =~ s!/{2,}!/!g;
90 9         13 $name =~ s!/$!!;
91 9   50     24 my $root = $options{share_root} //= undef;
92 9         35 my $filename = undef;
93 9 50       24 if ($root) {
94 9 50       212 die "No share_root directory ($root)" unless -d $root;
95 9         150 while ($root =~ s=(^|/)(?!\.\./)[^/]+/\.\./=$1=) {}
96 9 100       36 $filename = $name eq '' ? $root : "$root/$name";
97 9         17 $filename =~ s!/{2,}!/!g;
98 9 50       25 $filename = '.' if $filename eq '';
99             }
100 9   50     52 my $is_ipc = $options{is_ipc} ||= 0;
101 9 100 66     387 my @stat = !$is_ipc && $filename && -e $filename ? stat($filename) : ();
102 9   33     28 my $is_srv = $is_ipc && $name =~ /^(?:srvsvc|wkssvc)$/;
103              
104 9 100 100     125 my $self = $class->SUPER::new(
    100 66        
    100          
    100          
    100          
    100          
    100          
    100          
105             name => $name,
106             filename => $filename, # server-side file only
107             creation_time => @stat ? to_nttime($stat[10]) : 0,
108             last_access_time => @stat ? to_nttime($stat[ 8]) : 0,
109             last_write_time => @stat ? to_nttime($stat[ 9]) : 0,
110             change_time => @stat ? to_nttime($stat[ 9]) : 0,
111             allocation_size => @stat ? ($stat[12] || 0) * 512: 0,
112             end_of_file => @stat ? $stat[ 7] : 0,
113             attributes => @stat ? to_ntattr($stat[ 2]) : 0,
114             exists => @stat || $is_srv ? 1 : 0,
115             opens => 0,
116             %options,
117             );
118              
119 9         99 return $self;
120             }
121              
122             sub update ($$$$$$$$;$) {
123 0     0 0 0 my $self = shift;
124              
125 0         0 $self->creation_time(shift);
126 0         0 $self->last_access_time(shift);
127 0         0 $self->last_write_time(shift);
128 0         0 $self->change_time(shift);
129 0         0 my $size1 = shift;
130 0         0 my $size2 = shift;
131 0         0 $self->attributes(shift);
132 0         0 my $is_eof_first = shift;
133 0 0       0 $self->allocation_size($is_eof_first ? $size2 : $size1);
134 0 0       0 $self->end_of_file ($is_eof_first ? $size1 : $size2);
135              
136 0 0       0 $self->exists($self->creation_time ? 1 : 0);
137             }
138              
139             sub is_directory ($) {
140 4     4 0 6 my $self = shift;
141              
142 4 50       24 return $self->is_ipc ? 0 : $self->attributes & ATTR_DIRECTORY ? 1 : 0;
    50          
143             }
144              
145             sub to_string ($;$) {
146 0     0 0 0 my $time = shift;
147 0   0     0 my $format = shift || "%4Y-%2m-%2d %2H:%2M";
148              
149 0         0 return strftime($format, localtime $time);
150             }
151              
152 0     0 0 0 sub ctime { from_nttime($_[0]->creation_time) }
153 0     0 0 0 sub atime { from_nttime($_[0]->last_access_time) }
154 0     0 0 0 sub wtime { from_nttime($_[0]->last_write_time) }
155 0     0 0 0 sub mtime { from_nttime($_[0]->change_time) }
156 0     0 0 0 sub ctime_string { to_string($_[0]->ctime, $_[1]) }
157 0     0 0 0 sub atime_string { to_string($_[0]->atime, $_[1]) }
158 0     0 0 0 sub wtime_string { to_string($_[0]->wtime, $_[1]) }
159 0     0 0 0 sub mtime_string { to_string($_[0]->mtime, $_[1]) }
160              
161             sub add_openfile ($$$) {
162 17     17 0 27 my $self = shift;
163 17         22 my $action = shift;
164 17         20 my $handle = shift;
165              
166 17         97 my $openfile = SMB::OpenFile->new($self, $action, $handle);
167              
168 17         30 $self->{opens}++;
169 17         47 $self->exists(1);
170              
171 17         52 return $openfile;
172             }
173              
174             sub delete_openfile ($$) {
175 17     17 0 28 my $self = shift;
176 17         20 my $openfile = shift;
177              
178 17 50       43 close($openfile->handle)
179             if $openfile->handle;
180              
181 17         111 --$self->{opens};
182             }
183              
184             sub _fail_exists ($$) {
185 4     4   10 my $self = shift;
186 4         7 my $exists = shift;
187              
188 4         47 $self->err("Can't open file [$self->{filename}]: $!");
189 4   100     33 $self->exists($exists || 0);
190              
191 4         21 return undef;
192             }
193              
194             sub supersede ($) {
195 3     3 0 6 my $self = shift;
196              
197 3 100       10 return $self->create unless -e $self->filename;
198              
199 2         6 my $filename = $self->filename;
200 2         7 my $tmp_filename = sprintf "%s.%06d", $self->filename, rand(1000000);
201              
202 2 50       175 rename($filename, $tmp_filename)
203             or return $self->_fail_exists(1);
204 2         7 my $openfile = $self->create;
205 2 50       7 unless ($openfile) {
206 0 0       0 rename($tmp_filename, $filename)
207             or $self->err("Can't rename tmp file ($tmp_filename) to orig file ($filename)");
208 0         0 return $self->_fail_exists(0);
209             }
210 2 50       184 unlink($tmp_filename)
211             or $self->err("Can't remove tmp file ($tmp_filename)");
212              
213 2         8 $openfile->action(ACTION_SUPERSEDED);
214              
215 2         6 return $openfile;
216             }
217              
218             sub open ($) {
219 8     8 0 23 my $self = shift;
220              
221 8 100       98 sysopen(my $fh, $self->filename, from_ntattr($self->attributes))
222             or return $self->_fail_exists(0);
223              
224 6         25 $self->add_openfile($fh, ACTION_OPENED);
225             }
226              
227             sub create ($) {
228 8     8 0 11 my $self = shift;
229              
230 8 50       43 sysopen(my $fh, $self->filename, from_ntattr($self->attributes) | O_CREAT | O_EXCL)
    100          
231             or return $self->_fail_exists(-e $self->filename ? 1 : 0);
232              
233 7         30 $self->add_openfile($fh, ACTION_CREATED);
234             }
235              
236             sub overwrite ($) {
237 5     5 0 13 my $self = shift;
238              
239             # no idea why O_TRUNC fails on Windows
240 5 50       25 my $mode = $^O eq 'MSWin32' ? 0 : O_TRUNC;
241              
242 5 100       27 sysopen(my $fh, $self->filename, from_ntattr($self->attributes) | $mode)
243             or return $self->_fail_exists(0);
244              
245 4 50       18 truncate($fh, 0) if $^O eq 'MSWin32';
246              
247 4         12 $self->add_openfile($fh, ACTION_OVERWRITTEN);
248             }
249              
250             sub open_if ($) {
251 4     4 0 7 my $self = shift;
252              
253 4 100       11 return -e $self->filename ? $self->open : $self->create;
254             }
255              
256             sub overwrite_if ($) {
257 3     3 0 5 my $self = shift;
258              
259 3 100       7 return -e $self->filename ? $self->overwrite : $self->create;
260             }
261              
262             sub open_by_disposition ($$) {
263 6     6 0 11 my $self = shift;
264 6         8 my $disposition = shift;
265              
266 6 50       38 return $self->add_openfile(ACTION_OPENED, undef)
267             if $self->is_ipc;
268              
269 6 100       20 return $self->supersede if $disposition == DISPOSITION_SUPERSEDE;
270 5 100       16 return $self->open if $disposition == DISPOSITION_OPEN;
271 4 100       13 return $self->create if $disposition == DISPOSITION_CREATE;
272 3 100       11 return $self->open_if if $disposition == DISPOSITION_OPEN_IF;
273 2 100       8 return $self->overwrite if $disposition == DISPOSITION_OVERWRITE;
274 1 50       6 return $self->overwrite_if if $disposition == DISPOSITION_OVERWRITE_IF;
275              
276 0         0 warn "Invalid disposition $disposition, can not open file\n";
277 0         0 return;
278             }
279              
280             sub find_files ($%) {
281 4     4 0 1961 my $self = shift;
282 4         11 my %params = @_;
283              
284 4 50       13 return unless $self->is_directory;
285              
286 4   100     18 my $pattern = $params{pattern} || '*';
287 4         8 my $want_all = $pattern eq '*';
288 4   100     16 my $start_idx = $params{start_idx} || 0;
289 4         9 my $files = $self->{files}; # cached
290 4         14 my $name = $self->name;
291              
292             # fix pattern if needed
293 4         7 my $pattern0 = $pattern;
294 4         12 $pattern0 =~ s/^\*/{.*,*}/;
295              
296 4 100 100     43 unless ($want_all && $files) {
297 3 50       9 my @filenames = map { -e $_ && basename($_) } bsd_glob($self->filename . "/$pattern0", GLOB_NOCASE | GLOB_BRACE);
  7         406  
298 3         27 $self->msg("Find [$self->{filename}/$pattern] - " . scalar(@filenames) . " files");
299 3 50       10 $files = [ map { SMB::File->new(
  7         47  
300             name => $name eq '' ? $_ : "$name/$_",
301             share_root => $self->share_root,
302             ) } @filenames ];
303 3 100       11 $self->{files} = $files if $want_all;
304             }
305              
306 4 100       17 return $start_idx ? [ @{$files}[$start_idx .. (@$files - 1)] ] : $files;
  1         7  
307             }
308              
309             1;