line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Fuse::PerlSSH::FS; |
2
|
|
|
|
|
|
|
|
3
|
2
|
|
|
2
|
|
51085
|
use strict; |
|
2
|
|
|
|
|
4
|
|
|
2
|
|
|
|
|
132
|
|
4
|
2
|
|
|
2
|
|
10
|
use warnings; |
|
2
|
|
|
|
|
3
|
|
|
2
|
|
|
|
|
55
|
|
5
|
|
|
|
|
|
|
|
6
|
2
|
|
|
2
|
|
2442
|
use Data::Dumper; |
|
2
|
|
|
|
|
30299
|
|
|
2
|
|
|
|
|
275
|
|
7
|
2
|
|
|
2
|
|
2218
|
use IPC::PerlSSH; |
|
2
|
|
|
|
|
23907
|
|
|
2
|
|
|
|
|
67
|
|
8
|
2
|
|
|
2
|
|
2141
|
use Fuse ':xattr'; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
use POSIX qw(ENOENT ENOSYS EEXIST EPERM O_RDONLY O_RDWR O_APPEND O_CREAT EOPNOTSUPP); |
10
|
|
|
|
|
|
|
use Fcntl qw(S_ISBLK S_ISCHR S_ISFIFO); |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
our $VERSION = '0.13'; |
13
|
|
|
|
|
|
|
our $self; |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
sub new { |
16
|
|
|
|
|
|
|
my $class = shift; |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
$self = bless({ |
19
|
|
|
|
|
|
|
host => undef, |
20
|
|
|
|
|
|
|
port => 22, |
21
|
|
|
|
|
|
|
user => undef, |
22
|
|
|
|
|
|
|
root => '/', |
23
|
|
|
|
|
|
|
umask=> umask(), |
24
|
|
|
|
|
|
|
@_ |
25
|
|
|
|
|
|
|
}, $class); |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
die "Options to Fuse::PerlSSH::FS should be key/value pairs passed in as a hash (got an odd number of elements)" if @_ % 2 != 0; |
28
|
|
|
|
|
|
|
die "Fuse::PerlSSH::FS needs a host to work" if !$self->{host}; |
29
|
|
|
|
|
|
|
die "Fuse::PerlSSH::FS only accepts password interactively!" if $self->{password}; |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
$self->{root} = '/' if !$self->{root}; |
32
|
|
|
|
|
|
|
chop($self->{root}) if length($self->{root}) > 1 && $self->{root} =~ /\/$/; # chop trailing slashes |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
print STDERR '## Fuse::PerlSSH::FS::self'.Dumper($self) if $self->{debug}; |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
## setup ssh connection to remote host |
37
|
|
|
|
|
|
|
$self->_remote(); |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
## test remote capabilities |
40
|
|
|
|
|
|
|
eval { %{$self->{capabilities}} = $self->_remote->call("test_capabilities",$self->{root}); }; |
41
|
|
|
|
|
|
|
die "Fuse::PerlSSH::FS capabilities test failed! $@" if $@; |
42
|
|
|
|
|
|
|
unless($self->{capabilities}->{can_xattr}){ |
43
|
|
|
|
|
|
|
my $testfile = '/perlsshfs-xattr-test-'.time(); |
44
|
|
|
|
|
|
|
my $mknod = local_mknod($testfile, 33204,0); |
45
|
|
|
|
|
|
|
my $setxattr = local_setxattr($testfile, 'user.abc','123',0); |
46
|
|
|
|
|
|
|
my $val = local_getxattr($testfile, 'user.abc'); |
47
|
|
|
|
|
|
|
my $unlink = local_unlink($testfile, 'user.abc'); |
48
|
|
|
|
|
|
|
$self->{capabilities}->{can_xattr} = 2 if $val eq '123'; |
49
|
|
|
|
|
|
|
print STDERR "## test_capabilities xattr with testfile: mknod:$mknod, setxattr:$setxattr, val:$val, unlink:$unlink \n" if $self->{debug}; |
50
|
|
|
|
|
|
|
} |
51
|
|
|
|
|
|
|
print STDERR "## new: test_capabilities: ".Dumper($self->{capabilities}) if $self->{debug}; |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
return $self; |
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
sub _remote { |
57
|
|
|
|
|
|
|
my $self = shift || $self; |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
if( !$self->{ssh} ){ |
60
|
|
|
|
|
|
|
$self->{ssh} = IPC::PerlSSH->new( Host => $self->{host}, Port => $self->{port}, User => $self->{user} ); |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
if( $self->{ssh} ){ |
63
|
|
|
|
|
|
|
$self->{ssh}->store( |
64
|
|
|
|
|
|
|
test_connection => q{ return "HELO"; }, |
65
|
|
|
|
|
|
|
test_capabilities => q{ |
66
|
|
|
|
|
|
|
my $root = shift; |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
eval { require File::ExtAttr; }; |
69
|
|
|
|
|
|
|
my $xattr_module = $@ ? $@ : 1; |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
eval { require Filesys::DfPortable; }; |
72
|
|
|
|
|
|
|
my $dfportable_module = $@ ? $@ : 1; |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
my @mount = `mount`; |
75
|
|
|
|
|
|
|
my $can_xattr = 1 if $mount[0] =~ /,user_xattr/; |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
return ('remote_perl_version', $], 'xattr_module', $xattr_module, 'dfportable_module', $dfportable_module, 'can_xattr', $can_xattr); |
78
|
|
|
|
|
|
|
}, |
79
|
|
|
|
|
|
|
# remote_mknod => q{ require 'syscall.ph'; syscall(&SYS_mknod,$_[0],$_[1],$_[2]); }, # creates unusable socket files! (probably a problem with dec vs. oct mode, todo: lookup what the syscall requires..) |
80
|
|
|
|
|
|
|
remote_mknod => q{ # todo: replace with Unix::Mknod |
81
|
|
|
|
|
|
|
# todo: does not use mode/dev |
82
|
|
|
|
|
|
|
my $result = open(my $fh,'>', $_[0]) or die "Cannot mknod/open('$_[0]') - $!"; |
83
|
|
|
|
|
|
|
close($fh); |
84
|
|
|
|
|
|
|
return $result; |
85
|
|
|
|
|
|
|
}, |
86
|
|
|
|
|
|
|
remote_mkdir => q{ # because PerlSSH's mkdir does not propagate the 2nd param $mode (mask) |
87
|
|
|
|
|
|
|
my $result = mkdir($_[0],$_[1]) or die "Cannot mkdir('$_[0]','$_[1]') - $!"; |
88
|
|
|
|
|
|
|
return $result; |
89
|
|
|
|
|
|
|
}, |
90
|
|
|
|
|
|
|
remote_link => q{ return link($_[0],$_[1]) or die "Cannot link('$_[0]','$_[1]') - $!"; }, |
91
|
|
|
|
|
|
|
remote_truncate => q{ # because PerlSSH's truncate only works on filehandles |
92
|
|
|
|
|
|
|
return truncate($_[0],$_[1]) or die "Cannot truncate('$_[0]','$_[1]') - $!"; |
93
|
|
|
|
|
|
|
}, |
94
|
|
|
|
|
|
|
remote_readdir => q{ # because PerlSSH's readdir removes dotfiles |
95
|
|
|
|
|
|
|
opendir( my $dh, $_[0] ) or die "Cannot opendir('$_[0]') - $!"; |
96
|
|
|
|
|
|
|
my @ents = readdir($dh); |
97
|
|
|
|
|
|
|
return @ents; |
98
|
|
|
|
|
|
|
}, |
99
|
|
|
|
|
|
|
statfs => q{ |
100
|
|
|
|
|
|
|
# @_ = (root,method) |
101
|
|
|
|
|
|
|
my ($blocks,$bavail) = (10000000,5000000); |
102
|
|
|
|
|
|
|
if($_[1] eq 'dfportable'){ |
103
|
|
|
|
|
|
|
my $df = dfportable($_[0]); |
104
|
|
|
|
|
|
|
$blocks = $df->{blocks} if defined($df); |
105
|
|
|
|
|
|
|
$bavail = $df->{bavail} if defined($df); |
106
|
|
|
|
|
|
|
}elsif($_[1] eq 'df'){ |
107
|
|
|
|
|
|
|
my @df = `df $_[0]`; |
108
|
|
|
|
|
|
|
(my $fsystem,$blocks,my $bused,$bavail,my $capacity,my $mounted) = split(/\s+/,$df[1]); |
109
|
|
|
|
|
|
|
$blocks = $blocks if $df[1]; |
110
|
|
|
|
|
|
|
$bavail = $bavail if $df[1]; |
111
|
|
|
|
|
|
|
} |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
return (255,1000000,500000,$blocks,$bavail,1024); |
114
|
|
|
|
|
|
|
}, # a pseudo statfs |
115
|
|
|
|
|
|
|
remote_listxattr => q{ |
116
|
|
|
|
|
|
|
use File::ExtAttr; |
117
|
|
|
|
|
|
|
my @list = File::ExtAttr::listfattr($_[0]) or die "Cannot listfattr('$_[0]') - $!"; |
118
|
|
|
|
|
|
|
for(@list){ $_ = 'user.'.$_ if $_ !~ /\./; } # fix missing ns |
119
|
|
|
|
|
|
|
return @list; |
120
|
|
|
|
|
|
|
}, |
121
|
|
|
|
|
|
|
remote_getxattr => q{ |
122
|
|
|
|
|
|
|
use File::ExtAttr; |
123
|
|
|
|
|
|
|
die "Cannot getfattr('$_[0]','$_[1]') - no namespace" if $_[1] !~ /\./; |
124
|
|
|
|
|
|
|
my ($ns,$key) = split(/\./,$_[1],2); |
125
|
|
|
|
|
|
|
# return " (".$ns.":$key) ".File::ExtAttr::getfattr($_[0], $key, { namespace => $ns }) or die "Cannot getfattr('$_[0]','$_[1]') - $!"; |
126
|
|
|
|
|
|
|
return File::ExtAttr::getfattr($_[0], $key, { namespace => $ns }) or die "Cannot getfattr('$_[0]','$_[1]') - $!"; |
127
|
|
|
|
|
|
|
}, |
128
|
|
|
|
|
|
|
remote_setxattr => q{ |
129
|
|
|
|
|
|
|
use File::ExtAttr; |
130
|
|
|
|
|
|
|
die "Cannot setfattr('$_[0]','$_[1]','$_[2]','$_[3]') - no namespace" if $_[1] !~ /\./; |
131
|
|
|
|
|
|
|
my ($ns,$key) = split(/\./,$_[1],2); |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
# File::ExtAttr: %flags allows control of whether the attribute should be created or should replace an existing attribute's value. |
134
|
|
|
|
|
|
|
# If the key create is true, setfattr will fail if the attribute already exists. If the key replace is true, setfattr will fail if the attribute does not already exist. If neither is specified, then the attribute will be created (if necessary) or silently replaced. |
135
|
|
|
|
|
|
|
my %flags = (create => 1); |
136
|
|
|
|
|
|
|
%flags = (replace => 1) if $_[3] > 1; # OR-ed constants are XATTR_CREATE 1, XATTR_REPLACE 2 |
137
|
|
|
|
|
|
|
return File::ExtAttr::setfattr($_[0], $key, $_[2], { namespace => $ns, %flags }) or die "Cannot setfattr('$_[0]','$_[1]','$_[2]','$_[3]') - $!"; |
138
|
|
|
|
|
|
|
}, |
139
|
|
|
|
|
|
|
remote_removexattr => q{ |
140
|
|
|
|
|
|
|
use File::ExtAttr; |
141
|
|
|
|
|
|
|
die "Cannot delfattr('$_[0]','$_[1]') - no namespace" if $_[1] !~ /\./; |
142
|
|
|
|
|
|
|
my ($ns,$key) = split(/\./,$_[1],2); |
143
|
|
|
|
|
|
|
File::ExtAttr::delfattr($_[0], $key, { namespace => $ns }) or die "Cannot delfattr('$_[0]','$_[1]') - $!"; |
144
|
|
|
|
|
|
|
}, |
145
|
|
|
|
|
|
|
); |
146
|
|
|
|
|
|
|
$self->{ssh}->use_library('FS', qw( chown chmod lstat readlink rename rmdir symlink unlink utime ) ); |
147
|
|
|
|
|
|
|
$self->{ssh}->use_library('Fuse::PerlSSH::RemoteFunctions'); |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
my $rval; |
150
|
|
|
|
|
|
|
eval { $rval = $self->_remote->call("test_connection"); }; |
151
|
|
|
|
|
|
|
die "Fuse::PerlSSH::FS ssh connection not working!" if $@; |
152
|
|
|
|
|
|
|
print STDERR "## _remote: test_connection: ".Dumper($rval) if $self->{debug}; |
153
|
|
|
|
|
|
|
}else{ |
154
|
|
|
|
|
|
|
die "Fuse::PerlSSH::FS was not able to log in to host $self->{host} on port $self->{port} with user $self->{user}"; |
155
|
|
|
|
|
|
|
return undef; |
156
|
|
|
|
|
|
|
} |
157
|
|
|
|
|
|
|
} |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
return $self->{ssh}; |
160
|
|
|
|
|
|
|
} |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
sub mount { |
163
|
|
|
|
|
|
|
my $self = shift; |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
## check local mount point |
166
|
|
|
|
|
|
|
if(!-d $self->{mountpoint}){ |
167
|
|
|
|
|
|
|
die 'Fuse::PerlSSH::FS: Mountpoint '.$self->{mountpoint}.' does not exists!'; |
168
|
|
|
|
|
|
|
} |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
my %add_xattr; |
171
|
|
|
|
|
|
|
if($self->{no_xattr}){ |
172
|
|
|
|
|
|
|
print STDERR "## mount: xattr bindings omitted. --no-xattr option in effect.\n" if $self->{debug}; |
173
|
|
|
|
|
|
|
}elsif(!$self->{capabilities}->{can_xattr}){ |
174
|
|
|
|
|
|
|
print STDERR "## mount: xattr bindings omitted. Remote host seems to be incapable.\n" if $self->{debug}; |
175
|
|
|
|
|
|
|
}else{ |
176
|
|
|
|
|
|
|
%add_xattr = ( |
177
|
|
|
|
|
|
|
listxattr=> \&local_listxattr, |
178
|
|
|
|
|
|
|
getxattr => \&local_getxattr, |
179
|
|
|
|
|
|
|
setxattr => \&local_setxattr, |
180
|
|
|
|
|
|
|
removexattr=>\&local_removexattr, |
181
|
|
|
|
|
|
|
); |
182
|
|
|
|
|
|
|
} |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
my %fuse = ( |
185
|
|
|
|
|
|
|
mountpoint => $self->{mountpoint}, |
186
|
|
|
|
|
|
|
threaded => $self->{threaded} ? 1 : 0, |
187
|
|
|
|
|
|
|
debug => $self->{debug} > 1 ? 1 : 0, |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
readdir => \&local_readdir, |
190
|
|
|
|
|
|
|
getattr => \&local_getattr, |
191
|
|
|
|
|
|
|
mknod => \&local_mknod, |
192
|
|
|
|
|
|
|
mkdir => \&local_mkdir, |
193
|
|
|
|
|
|
|
rmdir => \&local_rmdir, |
194
|
|
|
|
|
|
|
rename => \&local_rename, |
195
|
|
|
|
|
|
|
unlink => \&local_unlink, |
196
|
|
|
|
|
|
|
open => \&local_open, |
197
|
|
|
|
|
|
|
read => \&local_read, |
198
|
|
|
|
|
|
|
write => \&local_write, |
199
|
|
|
|
|
|
|
release => \&local_release, |
200
|
|
|
|
|
|
|
symlink => \&local_symlink, |
201
|
|
|
|
|
|
|
link => \&local_link, |
202
|
|
|
|
|
|
|
readlink => \&local_readlink, |
203
|
|
|
|
|
|
|
utime => \&local_utime, |
204
|
|
|
|
|
|
|
truncate => \&local_truncate, |
205
|
|
|
|
|
|
|
ftruncate=> \&local_ftruncate, |
206
|
|
|
|
|
|
|
statfs => \&local_statfs, |
207
|
|
|
|
|
|
|
%add_xattr |
208
|
|
|
|
|
|
|
); |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
Fuse::main( %fuse ); |
211
|
|
|
|
|
|
|
return; |
212
|
|
|
|
|
|
|
} |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
sub umount { |
215
|
|
|
|
|
|
|
my $self = shift; |
216
|
|
|
|
|
|
|
print STDERR "## umount: sending 'exit'\n" if $self->{debug}; |
217
|
|
|
|
|
|
|
eval { $self->_remote->eval('exit 1'); }; |
218
|
|
|
|
|
|
|
} |
219
|
|
|
|
|
|
|
|
220
|
|
|
|
|
|
|
sub path { |
221
|
|
|
|
|
|
|
return $self->{root} if $_[0] eq '/'; |
222
|
|
|
|
|
|
|
return $self->{root} . $_[0]; |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
sub local_readdir { |
226
|
|
|
|
|
|
|
my $path = path(shift); |
227
|
|
|
|
|
|
|
print STDERR "## local_readdir: $path \n" if $self->{debug}; |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
my @dir; |
230
|
|
|
|
|
|
|
eval { @dir = _remote->call("remote_readdir", $path ); }; |
231
|
|
|
|
|
|
|
return -ENOENT() if $@; |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
return @dir ? (@dir, 0) : 0; |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
sub local_getattr { |
237
|
|
|
|
|
|
|
my $path = path(shift); |
238
|
|
|
|
|
|
|
print STDERR "## local_getattr: $path \n" if $self->{debug}; |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
## Fuse-perl docs say "FIXME: the "ino" field is currently ignored. I tried setting it to 0 in an example script, which consistently caused segfaults." |
241
|
|
|
|
|
|
|
## $stat[1] = 0; # in case we get segfaults |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
my @stat; |
244
|
|
|
|
|
|
|
eval { @stat = _remote->call("lstat", $path ); }; |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
return -ENOENT() if $@; # ENOENT = "file not found" |
247
|
|
|
|
|
|
|
return @stat; |
248
|
|
|
|
|
|
|
} |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
## Arguments: New directory pathname, numeric modes. Returns an errno. |
251
|
|
|
|
|
|
|
## Called to create a directory. |
252
|
|
|
|
|
|
|
sub local_mkdir { |
253
|
|
|
|
|
|
|
my $path = path(shift); |
254
|
|
|
|
|
|
|
my $mode = shift; |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
## pass the "mode as modified by umask" |
257
|
|
|
|
|
|
|
# $mode &= ~$self->{umask} if defined $mode; |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
print STDERR "## local_mkdir: $path perm:decimal($mode),octal(".sprintf("%o", $mode).")\n" if $self->{debug}; |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
my $result; |
262
|
|
|
|
|
|
|
eval { $result = _remote->call("remote_mkdir", $path, $mode ); }; |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
return -ENOENT() if $@; |
265
|
|
|
|
|
|
|
return $result ? 0 : -$!; |
266
|
|
|
|
|
|
|
} |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
## Arguments: Filename, numeric modes, numeric device Returns an errno (0 upon success, as usual). |
269
|
|
|
|
|
|
|
## This function is called for all non-directory, non-symlink nodes, not just devices. |
270
|
|
|
|
|
|
|
sub local_mknod { |
271
|
|
|
|
|
|
|
my $path = path(shift); |
272
|
|
|
|
|
|
|
my $mode = shift; |
273
|
|
|
|
|
|
|
my $dev = shift; |
274
|
|
|
|
|
|
|
print STDERR "## local_mknod: $path perm:decimal($mode),octal(".sprintf("%o", $mode)."), dev:$dev\n" if $self->{debug}; |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
# from Fuse::PDF: don't support special files |
277
|
|
|
|
|
|
|
# my $is_special = !S_ISREG($mode) && !S_ISDIR($mode) && !S_ISLNK($mode); |
278
|
|
|
|
|
|
|
# return -EIO() if $is_special; |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
# since this is called for ALL files, not just devices, I'll do some checks |
281
|
|
|
|
|
|
|
# and possibly run the real mknod command. |
282
|
|
|
|
|
|
|
$! = 0; |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
## pass the "mode as modified by umask" |
285
|
|
|
|
|
|
|
# $mode &= ~$self->{umask} if defined $mode; |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
my $result; |
288
|
|
|
|
|
|
|
eval { $result = _remote->call("remote_mknod", $path,$mode,$dev ); }; |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
return -ENOENT() if $@; |
291
|
|
|
|
|
|
|
return $result ? 0 : -$!; |
292
|
|
|
|
|
|
|
} |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
## Arguments: Pathname. Returns an errno. |
296
|
|
|
|
|
|
|
## Called to remove a directory. |
297
|
|
|
|
|
|
|
sub local_rmdir { |
298
|
|
|
|
|
|
|
my $path = path(shift); |
299
|
|
|
|
|
|
|
print STDERR "## local_rmdir: $path\n" if $self->{debug}; |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
my $result; |
302
|
|
|
|
|
|
|
eval { $result = _remote->call("rmdir", $path ); }; |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
return -ENOENT() if $@; |
305
|
|
|
|
|
|
|
return $result ? 0 : -$!; |
306
|
|
|
|
|
|
|
} |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
## Arguments: old filename, new filename. Returns an errno. |
309
|
|
|
|
|
|
|
## Called to rename a file, and/or move a file from one directory to another. |
310
|
|
|
|
|
|
|
sub local_rename { |
311
|
|
|
|
|
|
|
my $path = path(shift); |
312
|
|
|
|
|
|
|
my $newpath = path(shift); |
313
|
|
|
|
|
|
|
print STDERR "## local_rename: $path -> $newpath\n" if $self->{debug}; |
314
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
my $result; |
316
|
|
|
|
|
|
|
eval { $result = _remote->call("rename", $path, $newpath ); }; |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
return -ENOENT() if $@; |
319
|
|
|
|
|
|
|
return $result ? 0 : -$!; |
320
|
|
|
|
|
|
|
} |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
## Arguments: Filename. Returns an errno. |
324
|
|
|
|
|
|
|
## Called to remove a file, device, or symlink. |
325
|
|
|
|
|
|
|
sub local_unlink { |
326
|
|
|
|
|
|
|
my $path = path(shift); |
327
|
|
|
|
|
|
|
print STDERR "## local_unlink: $path\n" if $self->{debug}; |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
my $result; |
330
|
|
|
|
|
|
|
eval { $result = _remote->call("unlink", $path ); }; |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
return -ENOENT() if $@; |
333
|
|
|
|
|
|
|
return $result ? 0 : -$!; |
334
|
|
|
|
|
|
|
} |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
sub local_open { |
337
|
|
|
|
|
|
|
my $path = path(shift); |
338
|
|
|
|
|
|
|
my $mode = shift; |
339
|
|
|
|
|
|
|
# my $fileinfo = shift; |
340
|
|
|
|
|
|
|
print STDERR "## local_open: $path mode:$mode," if $self->{debug}; |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
my $fd; |
343
|
|
|
|
|
|
|
eval { $fd = _remote->call("sysopen", $mode, $path ); }; |
344
|
|
|
|
|
|
|
print STDERR " fd:$fd\n" if $self->{debug}; |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
if($@){ |
347
|
|
|
|
|
|
|
print STDERR "## local_open: remote_sysopen failed: $@\n" if $self->{debug}; |
348
|
|
|
|
|
|
|
return -ENOENT(); |
349
|
|
|
|
|
|
|
} |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
return -$! unless $fd; |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
return (0,$fd); |
354
|
|
|
|
|
|
|
} |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
## Arguments: Pathname, numeric requested size, numeric offset, file handle Returns a numeric errno, or a string scalar with up to $requestedsize bytes of data. |
357
|
|
|
|
|
|
|
## Called in an attempt to fetch a portion of the file. |
358
|
|
|
|
|
|
|
sub local_read { |
359
|
|
|
|
|
|
|
my ($path,$length,$offset,$fd) = @_; |
360
|
|
|
|
|
|
|
$path = path(shift); |
361
|
|
|
|
|
|
|
print STDERR "## local_read: $path length:$length, offset:$offset, fd:$fd\n" if $self->{debug}; |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
return -ENOENT() unless $fd; # as good as checking if the file exists, no handle, no file |
364
|
|
|
|
|
|
|
|
365
|
|
|
|
|
|
|
my $buf = -ENOSYS(); # init return_value with an error, in case we can't fill it with data |
366
|
|
|
|
|
|
|
eval { $buf = _remote->call("read", $fd, $length, $offset ); }; |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
return -ENOSYS() if $@; |
369
|
|
|
|
|
|
|
return $buf; |
370
|
|
|
|
|
|
|
} |
371
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
## Arguments: Pathname, scalar buffer, numeric offset, file handle. You can use length($buffer) to find the buffersize. Returns length($buffer) if successful (number of bytes written). |
373
|
|
|
|
|
|
|
## Called in an attempt to write (or overwrite) a portion of the file. Be prepared because $buffer could contain random binary data with NULs and all sorts of other wonderful stuff. |
374
|
|
|
|
|
|
|
sub local_write { |
375
|
|
|
|
|
|
|
my ($path,$buf,$offset,$fd) = @_; |
376
|
|
|
|
|
|
|
$path = path(shift); |
377
|
|
|
|
|
|
|
print STDERR "## local_write: $path buf-length:".length($buf).", offset:$offset, fd:$fd\n" if $self->{debug}; |
378
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
return -ENOSYS() unless $fd; # as good as checking if the file exists, no handle, no file |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
# write sadly does not return how many bytes were written |
382
|
|
|
|
|
|
|
eval { _remote->call( "write", $fd, $buf, $offset ) }; |
383
|
|
|
|
|
|
|
if($@){ |
384
|
|
|
|
|
|
|
print STDERR "## local_write: write failed: $@\n" if $self->{debug}; |
385
|
|
|
|
|
|
|
return -ENOSYS(); |
386
|
|
|
|
|
|
|
} |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
return length($buf); |
389
|
|
|
|
|
|
|
} |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
## Arguments: Pathname, numeric flags passed to open, file handle Returns an errno or 0 on success. |
392
|
|
|
|
|
|
|
## Called to indicate that there are no more references to the file. Called once for every file with the same pathname and flags as were passed to open. |
393
|
|
|
|
|
|
|
sub local_release { |
394
|
|
|
|
|
|
|
my $path = path(shift); |
395
|
|
|
|
|
|
|
my $mode = shift; |
396
|
|
|
|
|
|
|
my $fd = shift; |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
print STDERR "## local_release: $path mode:$mode, fd:$fd\n" if $self->{debug}; |
399
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
return -ENOSYS() unless $fd; |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
my $result; |
403
|
|
|
|
|
|
|
eval { $result = _remote->call( "close", $fd ); }; |
404
|
|
|
|
|
|
|
|
405
|
|
|
|
|
|
|
return -ENOENT() if $@; |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
return $result ? 0 : -$!; |
408
|
|
|
|
|
|
|
return 0; |
409
|
|
|
|
|
|
|
} |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
## Arguments: Existing filename, symlink name. Returns an errno. |
412
|
|
|
|
|
|
|
## Called to create a symbolic link. |
413
|
|
|
|
|
|
|
sub local_symlink { |
414
|
|
|
|
|
|
|
my $path = shift; |
415
|
|
|
|
|
|
|
my $sympath = path(shift); |
416
|
|
|
|
|
|
|
print STDERR "## local_symlink: $path <- $sympath\n" if $self->{debug}; |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
my $result; |
419
|
|
|
|
|
|
|
eval { $result = _remote->call("symlink", $path, $sympath ); }; |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
return -ENOENT() if $@; |
422
|
|
|
|
|
|
|
return $result ? 0 : -EEXIST(); # if symlink fails, most probably, because it exists |
423
|
|
|
|
|
|
|
} |
424
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
## Arguments: Existing filename, hardlink name. Returns an errno. |
426
|
|
|
|
|
|
|
## Called to create hard links. |
427
|
|
|
|
|
|
|
sub local_link { |
428
|
|
|
|
|
|
|
my $path = path(shift); |
429
|
|
|
|
|
|
|
my $linkpath = path(shift); |
430
|
|
|
|
|
|
|
print STDERR "## local_link: $path -> $linkpath\n" if $self->{debug}; |
431
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
my $result; |
433
|
|
|
|
|
|
|
eval { $result = _remote->call("remote_link", $path, $linkpath ); }; |
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
return -ENOENT() if $@; |
436
|
|
|
|
|
|
|
return $result ? 0 : -EEXIST(); # if link fails, most probably, because it exists |
437
|
|
|
|
|
|
|
} |
438
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
## Arguments: link pathname. Returns a scalar: either a numeric constant, or a text string. |
440
|
|
|
|
|
|
|
## This is called when dereferencing symbolic links, to learn the target. |
441
|
|
|
|
|
|
|
sub local_readlink { |
442
|
|
|
|
|
|
|
my $path = path(shift); |
443
|
|
|
|
|
|
|
print STDERR "## local_readlink: $path\n" if $self->{debug}; |
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
my $result; |
446
|
|
|
|
|
|
|
eval { $result = _remote->call("readlink", $path ); }; |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
return -ENOENT() if $@; |
449
|
|
|
|
|
|
|
return $result ? $result : -$!; |
450
|
|
|
|
|
|
|
} |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
## Arguments: Pathname, numeric actime, numeric modtime. Returns an errno. |
453
|
|
|
|
|
|
|
## Called to change access/modification times for a file/directory/device/symlink. |
454
|
|
|
|
|
|
|
sub local_utime { |
455
|
|
|
|
|
|
|
## arg order is reversed between perl (path(s) last, and what is passed-in here (path first), probably because the perl way is not atomic as it may die after a few files, and the fuse way makes it atomic: one file at a time |
456
|
|
|
|
|
|
|
my $path = path(shift); |
457
|
|
|
|
|
|
|
my ($atime,$mtime) = (shift,shift); |
458
|
|
|
|
|
|
|
print STDERR "## local_utime: $path atime:$atime,mtime:$mtime\n" if $self->{debug}; |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
my $result; |
461
|
|
|
|
|
|
|
eval { $result = _remote->call("utime", $atime, $mtime, $path ); }; |
462
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
return -ENOENT() if $@; |
464
|
|
|
|
|
|
|
return $result ? 0 : -$!; |
465
|
|
|
|
|
|
|
} |
466
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
sub local_truncate { |
468
|
|
|
|
|
|
|
my $path = path(shift); |
469
|
|
|
|
|
|
|
my $offset = shift; |
470
|
|
|
|
|
|
|
print STDERR "## local_truncate: $path offset:$offset\n" if $self->{debug}; |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
my $result; |
473
|
|
|
|
|
|
|
eval { $result = _remote->call("remote_truncate", $path, $offset ); }; |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
return -ENOENT() if $@; |
476
|
|
|
|
|
|
|
return $result ? 0 : -$!; |
477
|
|
|
|
|
|
|
} |
478
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
## Arguments: Pathname, numeric offset. Returns an errno. |
480
|
|
|
|
|
|
|
## Called to truncate a file, at the given offset. |
481
|
|
|
|
|
|
|
## sub x_truncate { return truncate(fixup(shift),shift) ? 0 : -$! ; } |
482
|
|
|
|
|
|
|
sub local_ftruncate { |
483
|
|
|
|
|
|
|
my $path = path(shift); |
484
|
|
|
|
|
|
|
my $offset = shift; |
485
|
|
|
|
|
|
|
my $fd = shift; |
486
|
|
|
|
|
|
|
print STDERR "## local_ftruncate: $path offset:$offset, fd:$fd\n" if $self->{debug}; |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
my $result; |
489
|
|
|
|
|
|
|
eval { $result = _remote->call("truncate", $fd, $offset ); }; # as PerlSSH's truncate *only* operates on filehandles, resolved via fd, it's effectively a ftruncate() (see FUSE docs for that) |
490
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
return -ENOENT() if $@; |
492
|
|
|
|
|
|
|
return $result ? 0 : -$!; |
493
|
|
|
|
|
|
|
} |
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
## Arguments: none, Returns any of the following: |
496
|
|
|
|
|
|
|
## -ENOANO() |
497
|
|
|
|
|
|
|
## or $namelen, $files, $files_free, $blocks, $blocks_avail, $blocksize |
498
|
|
|
|
|
|
|
## or -ENOANO(), $namelen, $files, $files_free, $blocks, $blocks_avail, $blocksize |
499
|
|
|
|
|
|
|
sub local_statfs { |
500
|
|
|
|
|
|
|
my @statfs; |
501
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
my $method = $self->{capabilities}->{dfportable_module} eq 1 ? 'dfportable' : 'df'; |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
eval { @statfs = _remote->call("statfs", $self->{root}, $method ); }; |
505
|
|
|
|
|
|
|
print STDERR "## local_statfs root:$self->{root}, method:$method, (@statfs)\n" if $self->{debug}; |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
return -ENOENT() if $@; |
508
|
|
|
|
|
|
|
return @statfs; |
509
|
|
|
|
|
|
|
} |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
sub local_listxattr { |
512
|
|
|
|
|
|
|
my $path = path(shift); |
513
|
|
|
|
|
|
|
print STDERR "## listxattr: $path \n" if $self->{debug}; |
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
my @list; |
516
|
|
|
|
|
|
|
eval { @list = _remote->call("remote_listxattr", $path ); }; |
517
|
|
|
|
|
|
|
return -ENOENT() if $@; |
518
|
|
|
|
|
|
|
|
519
|
|
|
|
|
|
|
print STDERR "## local_listxattr: list:@list\n" if $self->{debug}; |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
return @list ? (@list, 0) : 0; |
522
|
|
|
|
|
|
|
} |
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
sub local_getxattr { |
525
|
|
|
|
|
|
|
my $path = path(shift); |
526
|
|
|
|
|
|
|
my $key = shift; |
527
|
|
|
|
|
|
|
print STDERR "## local_getxattr: $path key:$key\n" if $self->{debug}; |
528
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
my $val; |
530
|
|
|
|
|
|
|
eval { $val = _remote->call("remote_getxattr", $path, $key ); }; |
531
|
|
|
|
|
|
|
print STDERR "## local_getxattr: eval:$@ \n" if $self->{debug}; |
532
|
|
|
|
|
|
|
return -EOPNOTSUPP() if $@ =~ / no namespace /; |
533
|
|
|
|
|
|
|
return -ENOSYS() if $@; |
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
print STDERR "## local_getxattr: ".$key."=".($val||'')." \n" if $self->{debug}; |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
return $val ? $val : 0; |
538
|
|
|
|
|
|
|
} |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
sub local_setxattr { |
541
|
|
|
|
|
|
|
my $path = path(shift); |
542
|
|
|
|
|
|
|
my $key = shift; |
543
|
|
|
|
|
|
|
my $val = shift; |
544
|
|
|
|
|
|
|
my $create_replace = shift; |
545
|
|
|
|
|
|
|
print STDERR "## local_setxattr: $path key:$key, val:$val, create|replace:$create_replace\n" if $self->{debug}; |
546
|
|
|
|
|
|
|
|
547
|
|
|
|
|
|
|
my $ret; |
548
|
|
|
|
|
|
|
eval { $ret = _remote->call("remote_setxattr", $path, $key, $val, $create_replace ); }; |
549
|
|
|
|
|
|
|
print STDERR "## local_setxattr: ret:".($ret||'').", eval:$@ \n" if $self->{debug}; |
550
|
|
|
|
|
|
|
return -EOPNOTSUPP() if $@ =~ / no namespace /; # we force the user only to supply a namespace |
551
|
|
|
|
|
|
|
# return -EEXIST() if !defined($ret); # If flags is set to XATTR_CREATE and the extended attribute already exists, this should fail with - EEXIST. |
552
|
|
|
|
|
|
|
# return -ENOATTR() if $@ =~ / no data available/; # If flags is set to XATTR_REPLACE and the extended attribute doesn't exist, this should fail with - ENOATTR. |
553
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
return -ENOSYS() if $@; |
555
|
|
|
|
|
|
|
|
556
|
|
|
|
|
|
|
return $ret ? 0 : $create_replace ? -ENOSYS() : -EEXIST(); # ENOATTR() is missing, thus ENOSYS |
557
|
|
|
|
|
|
|
} |
558
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
sub local_removexattr { |
560
|
|
|
|
|
|
|
my $path = path(shift); |
561
|
|
|
|
|
|
|
my $key = shift; |
562
|
|
|
|
|
|
|
print STDERR "## local_removexattr: $path key:$key\n" if $self->{debug}; |
563
|
|
|
|
|
|
|
|
564
|
|
|
|
|
|
|
my ($ret,$err); |
565
|
|
|
|
|
|
|
eval { ($ret,$err) = _remote->call("remote_removexattr", $path, $key ); }; |
566
|
|
|
|
|
|
|
print STDERR "## local_removexattr: ret:".($ret||'').", eval:$@ \n" if $self->{debug}; |
567
|
|
|
|
|
|
|
return -EOPNOTSUPP() if $@ =~ / no namespace /; |
568
|
|
|
|
|
|
|
return -ENOSYS() if $@; |
569
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
# return -ENOATTR() if !defined($ret); # if $@ =~ / no data available/; |
571
|
|
|
|
|
|
|
return $ret ? 0 : -ENOSYS(); # ENOATTR() is missing, thus ENOSYS |
572
|
|
|
|
|
|
|
} |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
1; |
576
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
__END__ |