|
0 |
# Copyright (c) 2012 Terence Honles <terence@honles.com> (maintainer)
|
|
1 |
# Copyright (c) 2008 Giorgos Verigakis <verigak@gmail.com> (author)
|
|
2 |
#
|
|
3 |
# Permission to use, copy, modify, and distribute this software for any
|
|
4 |
# purpose with or without fee is hereby granted, provided that the above
|
|
5 |
# copyright notice and this permission notice appear in all copies.
|
|
6 |
#
|
|
7 |
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
8 |
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
9 |
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
10 |
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
11 |
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
12 |
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
13 |
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
14 |
|
|
15 |
from __future__ import print_function, absolute_import, division
|
|
16 |
|
|
17 |
from ctypes import *
|
|
18 |
from ctypes.util import find_library
|
|
19 |
from errno import *
|
|
20 |
from os import strerror
|
|
21 |
from platform import machine, system
|
|
22 |
from signal import signal, SIGINT, SIG_DFL
|
|
23 |
from stat import S_IFDIR
|
|
24 |
from traceback import print_exc
|
|
25 |
|
|
26 |
import logging
|
|
27 |
|
|
28 |
try:
|
|
29 |
from functools import partial
|
|
30 |
except ImportError:
|
|
31 |
# http://docs.python.org/library/functools.html#functools.partial
|
|
32 |
def partial(func, *args, **keywords):
|
|
33 |
def newfunc(*fargs, **fkeywords):
|
|
34 |
newkeywords = keywords.copy()
|
|
35 |
newkeywords.update(fkeywords)
|
|
36 |
return func(*(args + fargs), **newkeywords)
|
|
37 |
|
|
38 |
newfunc.func = func
|
|
39 |
newfunc.args = args
|
|
40 |
newfunc.keywords = keywords
|
|
41 |
return newfunc
|
|
42 |
|
|
43 |
try:
|
|
44 |
basestring
|
|
45 |
except NameError:
|
|
46 |
basestring = str
|
|
47 |
|
|
48 |
class c_timespec(Structure):
|
|
49 |
_fields_ = [('tv_sec', c_long), ('tv_nsec', c_long)]
|
|
50 |
|
|
51 |
class c_utimbuf(Structure):
|
|
52 |
_fields_ = [('actime', c_timespec), ('modtime', c_timespec)]
|
|
53 |
|
|
54 |
class c_stat(Structure):
|
|
55 |
pass # Platform dependent
|
|
56 |
|
|
57 |
_system = system()
|
|
58 |
_machine = machine()
|
|
59 |
|
|
60 |
if _system == 'Darwin':
|
|
61 |
_libiconv = CDLL(find_library('iconv'), RTLD_GLOBAL) # libfuse dependency
|
|
62 |
_libfuse_path = (find_library('fuse4x') or find_library('osxfuse') or
|
|
63 |
find_library('fuse'))
|
|
64 |
else:
|
|
65 |
_libfuse_path = find_library('fuse')
|
|
66 |
|
|
67 |
if not _libfuse_path:
|
|
68 |
raise EnvironmentError('Unable to find libfuse')
|
|
69 |
else:
|
|
70 |
_libfuse = CDLL(_libfuse_path)
|
|
71 |
|
|
72 |
if _system == 'Darwin' and hasattr(_libfuse, 'macfuse_version'):
|
|
73 |
_system = 'Darwin-MacFuse'
|
|
74 |
|
|
75 |
|
|
76 |
if _system in ('Darwin', 'Darwin-MacFuse', 'FreeBSD'):
|
|
77 |
ENOTSUP = 45
|
|
78 |
c_dev_t = c_int32
|
|
79 |
c_fsblkcnt_t = c_ulong
|
|
80 |
c_fsfilcnt_t = c_ulong
|
|
81 |
c_gid_t = c_uint32
|
|
82 |
c_mode_t = c_uint16
|
|
83 |
c_off_t = c_int64
|
|
84 |
c_pid_t = c_int32
|
|
85 |
c_uid_t = c_uint32
|
|
86 |
setxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte),
|
|
87 |
c_size_t, c_int, c_uint32)
|
|
88 |
getxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte),
|
|
89 |
c_size_t, c_uint32)
|
|
90 |
if _system == 'Darwin':
|
|
91 |
c_stat._fields_ = [
|
|
92 |
('st_dev', c_dev_t),
|
|
93 |
('st_mode', c_mode_t),
|
|
94 |
('st_nlink', c_uint16),
|
|
95 |
('st_ino', c_uint64),
|
|
96 |
('st_uid', c_uid_t),
|
|
97 |
('st_gid', c_gid_t),
|
|
98 |
('st_rdev', c_dev_t),
|
|
99 |
('st_atimespec', c_timespec),
|
|
100 |
('st_mtimespec', c_timespec),
|
|
101 |
('st_ctimespec', c_timespec),
|
|
102 |
('st_birthtimespec', c_timespec),
|
|
103 |
('st_size', c_off_t),
|
|
104 |
('st_blocks', c_int64),
|
|
105 |
('st_blksize', c_int32),
|
|
106 |
('st_flags', c_int32),
|
|
107 |
('st_gen', c_int32),
|
|
108 |
('st_lspare', c_int32),
|
|
109 |
('st_qspare', c_int64)]
|
|
110 |
else:
|
|
111 |
c_stat._fields_ = [
|
|
112 |
('st_dev', c_dev_t),
|
|
113 |
('st_ino', c_uint32),
|
|
114 |
('st_mode', c_mode_t),
|
|
115 |
('st_nlink', c_uint16),
|
|
116 |
('st_uid', c_uid_t),
|
|
117 |
('st_gid', c_gid_t),
|
|
118 |
('st_rdev', c_dev_t),
|
|
119 |
('st_atimespec', c_timespec),
|
|
120 |
('st_mtimespec', c_timespec),
|
|
121 |
('st_ctimespec', c_timespec),
|
|
122 |
('st_size', c_off_t),
|
|
123 |
('st_blocks', c_int64),
|
|
124 |
('st_blksize', c_int32)]
|
|
125 |
elif _system == 'Linux':
|
|
126 |
ENOTSUP = 95
|
|
127 |
c_dev_t = c_ulonglong
|
|
128 |
c_fsblkcnt_t = c_ulonglong
|
|
129 |
c_fsfilcnt_t = c_ulonglong
|
|
130 |
c_gid_t = c_uint
|
|
131 |
c_mode_t = c_uint
|
|
132 |
c_off_t = c_longlong
|
|
133 |
c_pid_t = c_int
|
|
134 |
c_uid_t = c_uint
|
|
135 |
setxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte),
|
|
136 |
c_size_t, c_int)
|
|
137 |
|
|
138 |
getxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte),
|
|
139 |
c_size_t)
|
|
140 |
|
|
141 |
if _machine == 'x86_64':
|
|
142 |
c_stat._fields_ = [
|
|
143 |
('st_dev', c_dev_t),
|
|
144 |
('st_ino', c_ulong),
|
|
145 |
('st_nlink', c_ulong),
|
|
146 |
('st_mode', c_mode_t),
|
|
147 |
('st_uid', c_uid_t),
|
|
148 |
('st_gid', c_gid_t),
|
|
149 |
('__pad0', c_int),
|
|
150 |
('st_rdev', c_dev_t),
|
|
151 |
('st_size', c_off_t),
|
|
152 |
('st_blksize', c_long),
|
|
153 |
('st_blocks', c_long),
|
|
154 |
('st_atimespec', c_timespec),
|
|
155 |
('st_mtimespec', c_timespec),
|
|
156 |
('st_ctimespec', c_timespec)]
|
|
157 |
elif _machine == 'mips':
|
|
158 |
c_stat._fields_ = [
|
|
159 |
('st_dev', c_dev_t),
|
|
160 |
('__pad1_1', c_ulong),
|
|
161 |
('__pad1_2', c_ulong),
|
|
162 |
('__pad1_3', c_ulong),
|
|
163 |
('st_ino', c_ulong),
|
|
164 |
('st_mode', c_mode_t),
|
|
165 |
('st_nlink', c_ulong),
|
|
166 |
('st_uid', c_uid_t),
|
|
167 |
('st_gid', c_gid_t),
|
|
168 |
('st_rdev', c_dev_t),
|
|
169 |
('__pad2_1', c_ulong),
|
|
170 |
('__pad2_2', c_ulong),
|
|
171 |
('st_size', c_off_t),
|
|
172 |
('__pad3', c_ulong),
|
|
173 |
('st_atimespec', c_timespec),
|
|
174 |
('__pad4', c_ulong),
|
|
175 |
('st_mtimespec', c_timespec),
|
|
176 |
('__pad5', c_ulong),
|
|
177 |
('st_ctimespec', c_timespec),
|
|
178 |
('__pad6', c_ulong),
|
|
179 |
('st_blksize', c_long),
|
|
180 |
('st_blocks', c_long),
|
|
181 |
('__pad7_1', c_ulong),
|
|
182 |
('__pad7_2', c_ulong),
|
|
183 |
('__pad7_3', c_ulong),
|
|
184 |
('__pad7_4', c_ulong),
|
|
185 |
('__pad7_5', c_ulong),
|
|
186 |
('__pad7_6', c_ulong),
|
|
187 |
('__pad7_7', c_ulong),
|
|
188 |
('__pad7_8', c_ulong),
|
|
189 |
('__pad7_9', c_ulong),
|
|
190 |
('__pad7_10', c_ulong),
|
|
191 |
('__pad7_11', c_ulong),
|
|
192 |
('__pad7_12', c_ulong),
|
|
193 |
('__pad7_13', c_ulong),
|
|
194 |
('__pad7_14', c_ulong)]
|
|
195 |
elif _machine == 'ppc':
|
|
196 |
c_stat._fields_ = [
|
|
197 |
('st_dev', c_dev_t),
|
|
198 |
('st_ino', c_ulonglong),
|
|
199 |
('st_mode', c_mode_t),
|
|
200 |
('st_nlink', c_uint),
|
|
201 |
('st_uid', c_uid_t),
|
|
202 |
('st_gid', c_gid_t),
|
|
203 |
('st_rdev', c_dev_t),
|
|
204 |
('__pad2', c_ushort),
|
|
205 |
('st_size', c_off_t),
|
|
206 |
('st_blksize', c_long),
|
|
207 |
('st_blocks', c_longlong),
|
|
208 |
('st_atimespec', c_timespec),
|
|
209 |
('st_mtimespec', c_timespec),
|
|
210 |
('st_ctimespec', c_timespec)]
|
|
211 |
elif _machine == 'aarch64':
|
|
212 |
c_stat._fields_ = [
|
|
213 |
('st_dev', c_dev_t),
|
|
214 |
('st_ino', c_ulong),
|
|
215 |
('st_mode', c_mode_t),
|
|
216 |
('st_nlink', c_uint),
|
|
217 |
('st_uid', c_uid_t),
|
|
218 |
('st_gid', c_gid_t),
|
|
219 |
('st_rdev', c_dev_t),
|
|
220 |
('__pad1', c_ulong),
|
|
221 |
('st_size', c_off_t),
|
|
222 |
('st_blksize', c_int),
|
|
223 |
('__pad2', c_int),
|
|
224 |
('st_blocks', c_long),
|
|
225 |
('st_atimespec', c_timespec),
|
|
226 |
('st_mtimespec', c_timespec),
|
|
227 |
('st_ctimespec', c_timespec)]
|
|
228 |
else:
|
|
229 |
# i686, use as fallback for everything else
|
|
230 |
c_stat._fields_ = [
|
|
231 |
('st_dev', c_dev_t),
|
|
232 |
('__pad1', c_ushort),
|
|
233 |
('__st_ino', c_ulong),
|
|
234 |
('st_mode', c_mode_t),
|
|
235 |
('st_nlink', c_uint),
|
|
236 |
('st_uid', c_uid_t),
|
|
237 |
('st_gid', c_gid_t),
|
|
238 |
('st_rdev', c_dev_t),
|
|
239 |
('__pad2', c_ushort),
|
|
240 |
('st_size', c_off_t),
|
|
241 |
('st_blksize', c_long),
|
|
242 |
('st_blocks', c_longlong),
|
|
243 |
('st_atimespec', c_timespec),
|
|
244 |
('st_mtimespec', c_timespec),
|
|
245 |
('st_ctimespec', c_timespec),
|
|
246 |
('st_ino', c_ulonglong)]
|
|
247 |
else:
|
|
248 |
raise NotImplementedError('%s is not supported.' % _system)
|
|
249 |
|
|
250 |
|
|
251 |
class c_statvfs(Structure):
|
|
252 |
_fields_ = [
|
|
253 |
('f_bsize', c_ulong),
|
|
254 |
('f_frsize', c_ulong),
|
|
255 |
('f_blocks', c_fsblkcnt_t),
|
|
256 |
('f_bfree', c_fsblkcnt_t),
|
|
257 |
('f_bavail', c_fsblkcnt_t),
|
|
258 |
('f_files', c_fsfilcnt_t),
|
|
259 |
('f_ffree', c_fsfilcnt_t),
|
|
260 |
('f_favail', c_fsfilcnt_t),
|
|
261 |
('f_fsid', c_ulong),
|
|
262 |
#('unused', c_int),
|
|
263 |
('f_flag', c_ulong),
|
|
264 |
('f_namemax', c_ulong)]
|
|
265 |
|
|
266 |
if _system == 'FreeBSD':
|
|
267 |
c_fsblkcnt_t = c_uint64
|
|
268 |
c_fsfilcnt_t = c_uint64
|
|
269 |
setxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte),
|
|
270 |
c_size_t, c_int)
|
|
271 |
|
|
272 |
getxattr_t = CFUNCTYPE(c_int, c_char_p, c_char_p, POINTER(c_byte),
|
|
273 |
c_size_t)
|
|
274 |
|
|
275 |
class c_statvfs(Structure):
|
|
276 |
_fields_ = [
|
|
277 |
('f_bavail', c_fsblkcnt_t),
|
|
278 |
('f_bfree', c_fsblkcnt_t),
|
|
279 |
('f_blocks', c_fsblkcnt_t),
|
|
280 |
('f_favail', c_fsfilcnt_t),
|
|
281 |
('f_ffree', c_fsfilcnt_t),
|
|
282 |
('f_files', c_fsfilcnt_t),
|
|
283 |
('f_bsize', c_ulong),
|
|
284 |
('f_flag', c_ulong),
|
|
285 |
('f_frsize', c_ulong)]
|
|
286 |
|
|
287 |
class fuse_file_info(Structure):
|
|
288 |
_fields_ = [
|
|
289 |
('flags', c_int),
|
|
290 |
('fh_old', c_ulong),
|
|
291 |
('writepage', c_int),
|
|
292 |
('direct_io', c_uint, 1),
|
|
293 |
('keep_cache', c_uint, 1),
|
|
294 |
('flush', c_uint, 1),
|
|
295 |
('padding', c_uint, 29),
|
|
296 |
('fh', c_uint64),
|
|
297 |
('lock_owner', c_uint64)]
|
|
298 |
|
|
299 |
class fuse_context(Structure):
|
|
300 |
_fields_ = [
|
|
301 |
('fuse', c_voidp),
|
|
302 |
('uid', c_uid_t),
|
|
303 |
('gid', c_gid_t),
|
|
304 |
('pid', c_pid_t),
|
|
305 |
('private_data', c_voidp)]
|
|
306 |
|
|
307 |
_libfuse.fuse_get_context.restype = POINTER(fuse_context)
|
|
308 |
|
|
309 |
|
|
310 |
class fuse_operations(Structure):
|
|
311 |
_fields_ = [
|
|
312 |
('getattr', CFUNCTYPE(c_int, c_char_p, POINTER(c_stat))),
|
|
313 |
('readlink', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t)),
|
|
314 |
('getdir', c_voidp), # Deprecated, use readdir
|
|
315 |
('mknod', CFUNCTYPE(c_int, c_char_p, c_mode_t, c_dev_t)),
|
|
316 |
('mkdir', CFUNCTYPE(c_int, c_char_p, c_mode_t)),
|
|
317 |
('unlink', CFUNCTYPE(c_int, c_char_p)),
|
|
318 |
('rmdir', CFUNCTYPE(c_int, c_char_p)),
|
|
319 |
('symlink', CFUNCTYPE(c_int, c_char_p, c_char_p)),
|
|
320 |
('rename', CFUNCTYPE(c_int, c_char_p, c_char_p)),
|
|
321 |
('link', CFUNCTYPE(c_int, c_char_p, c_char_p)),
|
|
322 |
('chmod', CFUNCTYPE(c_int, c_char_p, c_mode_t)),
|
|
323 |
('chown', CFUNCTYPE(c_int, c_char_p, c_uid_t, c_gid_t)),
|
|
324 |
('truncate', CFUNCTYPE(c_int, c_char_p, c_off_t)),
|
|
325 |
('utime', c_voidp), # Deprecated, use utimens
|
|
326 |
('open', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))),
|
|
327 |
|
|
328 |
('read', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t,
|
|
329 |
c_off_t, POINTER(fuse_file_info))),
|
|
330 |
|
|
331 |
('write', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t,
|
|
332 |
c_off_t, POINTER(fuse_file_info))),
|
|
333 |
|
|
334 |
('statfs', CFUNCTYPE(c_int, c_char_p, POINTER(c_statvfs))),
|
|
335 |
('flush', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))),
|
|
336 |
('release', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))),
|
|
337 |
('fsync', CFUNCTYPE(c_int, c_char_p, c_int, POINTER(fuse_file_info))),
|
|
338 |
('setxattr', setxattr_t),
|
|
339 |
('getxattr', getxattr_t),
|
|
340 |
('listxattr', CFUNCTYPE(c_int, c_char_p, POINTER(c_byte), c_size_t)),
|
|
341 |
('removexattr', CFUNCTYPE(c_int, c_char_p, c_char_p)),
|
|
342 |
('opendir', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))),
|
|
343 |
|
|
344 |
('readdir', CFUNCTYPE(c_int, c_char_p, c_voidp,
|
|
345 |
CFUNCTYPE(c_int, c_voidp, c_char_p,
|
|
346 |
POINTER(c_stat), c_off_t),
|
|
347 |
c_off_t, POINTER(fuse_file_info))),
|
|
348 |
|
|
349 |
('releasedir', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info))),
|
|
350 |
|
|
351 |
('fsyncdir', CFUNCTYPE(c_int, c_char_p, c_int,
|
|
352 |
POINTER(fuse_file_info))),
|
|
353 |
|
|
354 |
('init', CFUNCTYPE(c_voidp, c_voidp)),
|
|
355 |
('destroy', CFUNCTYPE(c_voidp, c_voidp)),
|
|
356 |
('access', CFUNCTYPE(c_int, c_char_p, c_int)),
|
|
357 |
|
|
358 |
('create', CFUNCTYPE(c_int, c_char_p, c_mode_t,
|
|
359 |
POINTER(fuse_file_info))),
|
|
360 |
|
|
361 |
('ftruncate', CFUNCTYPE(c_int, c_char_p, c_off_t,
|
|
362 |
POINTER(fuse_file_info))),
|
|
363 |
|
|
364 |
('fgetattr', CFUNCTYPE(c_int, c_char_p, POINTER(c_stat),
|
|
365 |
POINTER(fuse_file_info))),
|
|
366 |
|
|
367 |
('lock', CFUNCTYPE(c_int, c_char_p, POINTER(fuse_file_info),
|
|
368 |
c_int, c_voidp)),
|
|
369 |
|
|
370 |
('utimens', CFUNCTYPE(c_int, c_char_p, POINTER(c_utimbuf))),
|
|
371 |
('bmap', CFUNCTYPE(c_int, c_char_p, c_size_t, POINTER(c_ulonglong))),
|
|
372 |
('flag_nullpath_ok', c_uint, 1),
|
|
373 |
('flag_nopath', c_uint, 1),
|
|
374 |
('flag_utime_omit_ok', c_uint, 1),
|
|
375 |
('flag_reserved', c_uint, 29),
|
|
376 |
]
|
|
377 |
|
|
378 |
|
|
379 |
def time_of_timespec(ts):
|
|
380 |
return ts.tv_sec + ts.tv_nsec / 10 ** 9
|
|
381 |
|
|
382 |
def set_st_attrs(st, attrs):
|
|
383 |
for key, val in attrs.items():
|
|
384 |
if key in ('st_atime', 'st_mtime', 'st_ctime', 'st_birthtime'):
|
|
385 |
timespec = getattr(st, key + 'spec', None)
|
|
386 |
if timespec is None:
|
|
387 |
continue
|
|
388 |
timespec.tv_sec = int(val)
|
|
389 |
timespec.tv_nsec = int((val - timespec.tv_sec) * 10 ** 9)
|
|
390 |
elif hasattr(st, key):
|
|
391 |
setattr(st, key, val)
|
|
392 |
|
|
393 |
|
|
394 |
def fuse_get_context():
|
|
395 |
'Returns a (uid, gid, pid) tuple'
|
|
396 |
|
|
397 |
ctxp = _libfuse.fuse_get_context()
|
|
398 |
ctx = ctxp.contents
|
|
399 |
return ctx.uid, ctx.gid, ctx.pid
|
|
400 |
|
|
401 |
|
|
402 |
class FuseOSError(OSError):
|
|
403 |
def __init__(self, errno):
|
|
404 |
super(FuseOSError, self).__init__(errno, strerror(errno))
|
|
405 |
|
|
406 |
|
|
407 |
class FUSE(object):
|
|
408 |
'''
|
|
409 |
This class is the lower level interface and should not be subclassed under
|
|
410 |
normal use. Its methods are called by fuse.
|
|
411 |
|
|
412 |
Assumes API version 2.6 or later.
|
|
413 |
'''
|
|
414 |
|
|
415 |
OPTIONS = (
|
|
416 |
('foreground', '-f'),
|
|
417 |
('debug', '-d'),
|
|
418 |
('nothreads', '-s'),
|
|
419 |
)
|
|
420 |
|
|
421 |
def __init__(self, operations, mountpoint, raw_fi=False, encoding='utf-8',
|
|
422 |
**kwargs):
|
|
423 |
|
|
424 |
'''
|
|
425 |
Setting raw_fi to True will cause FUSE to pass the fuse_file_info
|
|
426 |
class as is to Operations, instead of just the fh field.
|
|
427 |
|
|
428 |
This gives you access to direct_io, keep_cache, etc.
|
|
429 |
'''
|
|
430 |
|
|
431 |
self.operations = operations
|
|
432 |
self.raw_fi = raw_fi
|
|
433 |
self.encoding = encoding
|
|
434 |
|
|
435 |
args = ['fuse']
|
|
436 |
|
|
437 |
args.extend(flag for arg, flag in self.OPTIONS
|
|
438 |
if kwargs.pop(arg, False))
|
|
439 |
|
|
440 |
kwargs.setdefault('fsname', operations.__class__.__name__)
|
|
441 |
args.append('-o')
|
|
442 |
args.append(','.join(self._normalize_fuse_options(**kwargs)))
|
|
443 |
args.append(mountpoint)
|
|
444 |
|
|
445 |
args = [arg.encode(encoding) for arg in args]
|
|
446 |
argv = (c_char_p * len(args))(*args)
|
|
447 |
|
|
448 |
fuse_ops = fuse_operations()
|
|
449 |
for ent in fuse_operations._fields_:
|
|
450 |
name, prototype = ent[:2]
|
|
451 |
|
|
452 |
val = getattr(operations, name, None)
|
|
453 |
if val is None:
|
|
454 |
continue
|
|
455 |
|
|
456 |
# Function pointer members are tested for using the
|
|
457 |
# getattr(operations, name) above but are dynamically
|
|
458 |
# invoked using self.operations(name)
|
|
459 |
if hasattr(prototype, 'argtypes'):
|
|
460 |
val = prototype(partial(self._wrapper, getattr(self, name)))
|
|
461 |
|
|
462 |
setattr(fuse_ops, name, val)
|
|
463 |
|
|
464 |
try:
|
|
465 |
old_handler = signal(SIGINT, SIG_DFL)
|
|
466 |
except ValueError:
|
|
467 |
old_handler = SIG_DFL
|
|
468 |
|
|
469 |
err = _libfuse.fuse_main_real(len(args), argv, pointer(fuse_ops),
|
|
470 |
sizeof(fuse_ops), None)
|
|
471 |
|
|
472 |
try:
|
|
473 |
signal(SIGINT, old_handler)
|
|
474 |
except ValueError:
|
|
475 |
pass
|
|
476 |
|
|
477 |
del self.operations # Invoke the destructor
|
|
478 |
if err:
|
|
479 |
raise RuntimeError(err)
|
|
480 |
|
|
481 |
@staticmethod
|
|
482 |
def _normalize_fuse_options(**kargs):
|
|
483 |
for key, value in kargs.items():
|
|
484 |
if isinstance(value, bool):
|
|
485 |
if value is True: yield key
|
|
486 |
else:
|
|
487 |
yield '%s=%s' % (key, value)
|
|
488 |
|
|
489 |
@staticmethod
|
|
490 |
def _wrapper(func, *args, **kwargs):
|
|
491 |
'Decorator for the methods that follow'
|
|
492 |
|
|
493 |
try:
|
|
494 |
return func(*args, **kwargs) or 0
|
|
495 |
except OSError as e:
|
|
496 |
return -(e.errno or EFAULT)
|
|
497 |
except:
|
|
498 |
print_exc()
|
|
499 |
return -EFAULT
|
|
500 |
|
|
501 |
def _decode_optional_path(self, path):
|
|
502 |
# NB: this method is intended for fuse operations that
|
|
503 |
# allow the path argument to be NULL,
|
|
504 |
# *not* as a generic path decoding method
|
|
505 |
if path is None:
|
|
506 |
return None
|
|
507 |
return path.decode(self.encoding)
|
|
508 |
|
|
509 |
def getattr(self, path, buf):
|
|
510 |
return self.fgetattr(path, buf, None)
|
|
511 |
|
|
512 |
def readlink(self, path, buf, bufsize):
|
|
513 |
ret = self.operations('readlink', path.decode(self.encoding)) \
|
|
514 |
.encode(self.encoding)
|
|
515 |
|
|
516 |
# copies a string into the given buffer
|
|
517 |
# (null terminated and truncated if necessary)
|
|
518 |
data = create_string_buffer(ret[:bufsize - 1])
|
|
519 |
memmove(buf, data, len(data))
|
|
520 |
return 0
|
|
521 |
|
|
522 |
def mknod(self, path, mode, dev):
|
|
523 |
return self.operations('mknod', path.decode(self.encoding), mode, dev)
|
|
524 |
|
|
525 |
def mkdir(self, path, mode):
|
|
526 |
return self.operations('mkdir', path.decode(self.encoding), mode)
|
|
527 |
|
|
528 |
def unlink(self, path):
|
|
529 |
return self.operations('unlink', path.decode(self.encoding))
|
|
530 |
|
|
531 |
def rmdir(self, path):
|
|
532 |
return self.operations('rmdir', path.decode(self.encoding))
|
|
533 |
|
|
534 |
def symlink(self, source, target):
|
|
535 |
'creates a symlink `target -> source` (e.g. ln -s source target)'
|
|
536 |
|
|
537 |
return self.operations('symlink', target.decode(self.encoding),
|
|
538 |
source.decode(self.encoding))
|
|
539 |
|
|
540 |
def rename(self, old, new):
|
|
541 |
return self.operations('rename', old.decode(self.encoding),
|
|
542 |
new.decode(self.encoding))
|
|
543 |
|
|
544 |
def link(self, source, target):
|
|
545 |
'creates a hard link `target -> source` (e.g. ln source target)'
|
|
546 |
|
|
547 |
return self.operations('link', target.decode(self.encoding),
|
|
548 |
source.decode(self.encoding))
|
|
549 |
|
|
550 |
def chmod(self, path, mode):
|
|
551 |
return self.operations('chmod', path.decode(self.encoding), mode)
|
|
552 |
|
|
553 |
def chown(self, path, uid, gid):
|
|
554 |
# Check if any of the arguments is a -1 that has overflowed
|
|
555 |
if c_uid_t(uid + 1).value == 0:
|
|
556 |
uid = -1
|
|
557 |
if c_gid_t(gid + 1).value == 0:
|
|
558 |
gid = -1
|
|
559 |
|
|
560 |
return self.operations('chown', path.decode(self.encoding), uid, gid)
|
|
561 |
|
|
562 |
def truncate(self, path, length):
|
|
563 |
return self.operations('truncate', path.decode(self.encoding), length)
|
|
564 |
|
|
565 |
def open(self, path, fip):
|
|
566 |
fi = fip.contents
|
|
567 |
if self.raw_fi:
|
|
568 |
return self.operations('open', path.decode(self.encoding), fi)
|
|
569 |
else:
|
|
570 |
fi.fh = self.operations('open', path.decode(self.encoding),
|
|
571 |
fi.flags)
|
|
572 |
|
|
573 |
return 0
|
|
574 |
|
|
575 |
def read(self, path, buf, size, offset, fip):
|
|
576 |
if self.raw_fi:
|
|
577 |
fh = fip.contents
|
|
578 |
else:
|
|
579 |
fh = fip.contents.fh
|
|
580 |
|
|
581 |
ret = self.operations('read', self._decode_optional_path(path), size,
|
|
582 |
offset, fh)
|
|
583 |
|
|
584 |
if not ret: return 0
|
|
585 |
|
|
586 |
retsize = len(ret)
|
|
587 |
assert retsize <= size, \
|
|
588 |
'actual amount read %d greater than expected %d' % (retsize, size)
|
|
589 |
|
|
590 |
data = create_string_buffer(ret, retsize)
|
|
591 |
memmove(buf, data, retsize)
|
|
592 |
return retsize
|
|
593 |
|
|
594 |
def write(self, path, buf, size, offset, fip):
|
|
595 |
data = string_at(buf, size)
|
|
596 |
|
|
597 |
if self.raw_fi:
|
|
598 |
fh = fip.contents
|
|
599 |
else:
|
|
600 |
fh = fip.contents.fh
|
|
601 |
|
|
602 |
return self.operations('write', self._decode_optional_path(path), data,
|
|
603 |
offset, fh)
|
|
604 |
|
|
605 |
def statfs(self, path, buf):
|
|
606 |
stv = buf.contents
|
|
607 |
attrs = self.operations('statfs', path.decode(self.encoding))
|
|
608 |
for key, val in attrs.items():
|
|
609 |
if hasattr(stv, key):
|
|
610 |
setattr(stv, key, val)
|
|
611 |
|
|
612 |
return 0
|
|
613 |
|
|
614 |
def flush(self, path, fip):
|
|
615 |
if self.raw_fi:
|
|
616 |
fh = fip.contents
|
|
617 |
else:
|
|
618 |
fh = fip.contents.fh
|
|
619 |
|
|
620 |
return self.operations('flush', self._decode_optional_path(path), fh)
|
|
621 |
|
|
622 |
def release(self, path, fip):
|
|
623 |
if self.raw_fi:
|
|
624 |
fh = fip.contents
|
|
625 |
else:
|
|
626 |
fh = fip.contents.fh
|
|
627 |
|
|
628 |
return self.operations('release', self._decode_optional_path(path), fh)
|
|
629 |
|
|
630 |
def fsync(self, path, datasync, fip):
|
|
631 |
if self.raw_fi:
|
|
632 |
fh = fip.contents
|
|
633 |
else:
|
|
634 |
fh = fip.contents.fh
|
|
635 |
|
|
636 |
return self.operations('fsync', self._decode_optional_path(path), datasync,
|
|
637 |
fh)
|
|
638 |
|
|
639 |
def setxattr(self, path, name, value, size, options, *args):
|
|
640 |
return self.operations('setxattr', path.decode(self.encoding),
|
|
641 |
name.decode(self.encoding),
|
|
642 |
string_at(value, size), options, *args)
|
|
643 |
|
|
644 |
def getxattr(self, path, name, value, size, *args):
|
|
645 |
ret = self.operations('getxattr', path.decode(self.encoding),
|
|
646 |
name.decode(self.encoding), *args)
|
|
647 |
|
|
648 |
retsize = len(ret)
|
|
649 |
# allow size queries
|
|
650 |
if not value: return retsize
|
|
651 |
|
|
652 |
# do not truncate
|
|
653 |
if retsize > size: return -ERANGE
|
|
654 |
|
|
655 |
buf = create_string_buffer(ret, retsize) # Does not add trailing 0
|
|
656 |
memmove(value, buf, retsize)
|
|
657 |
|
|
658 |
return retsize
|
|
659 |
|
|
660 |
def listxattr(self, path, namebuf, size):
|
|
661 |
attrs = self.operations('listxattr', path.decode(self.encoding)) or ''
|
|
662 |
ret = '\x00'.join(attrs).encode(self.encoding)
|
|
663 |
if len(ret) > 0:
|
|
664 |
ret += '\x00'.encode(self.encoding)
|
|
665 |
|
|
666 |
retsize = len(ret)
|
|
667 |
# allow size queries
|
|
668 |
if not namebuf: return retsize
|
|
669 |
|
|
670 |
# do not truncate
|
|
671 |
if retsize > size: return -ERANGE
|
|
672 |
|
|
673 |
buf = create_string_buffer(ret, retsize)
|
|
674 |
memmove(namebuf, buf, retsize)
|
|
675 |
|
|
676 |
return retsize
|
|
677 |
|
|
678 |
def removexattr(self, path, name):
|
|
679 |
return self.operations('removexattr', path.decode(self.encoding),
|
|
680 |
name.decode(self.encoding))
|
|
681 |
|
|
682 |
def opendir(self, path, fip):
|
|
683 |
# Ignore raw_fi
|
|
684 |
fip.contents.fh = self.operations('opendir',
|
|
685 |
path.decode(self.encoding))
|
|
686 |
|
|
687 |
return 0
|
|
688 |
|
|
689 |
def readdir(self, path, buf, filler, offset, fip):
|
|
690 |
# Ignore raw_fi
|
|
691 |
for item in self.operations('readdir', self._decode_optional_path(path),
|
|
692 |
fip.contents.fh):
|
|
693 |
|
|
694 |
if isinstance(item, basestring):
|
|
695 |
name, st, offset = item, None, 0
|
|
696 |
else:
|
|
697 |
name, attrs, offset = item
|
|
698 |
if attrs:
|
|
699 |
st = c_stat()
|
|
700 |
set_st_attrs(st, attrs)
|
|
701 |
else:
|
|
702 |
st = None
|
|
703 |
|
|
704 |
if filler(buf, name.encode(self.encoding), st, offset) != 0:
|
|
705 |
break
|
|
706 |
|
|
707 |
return 0
|
|
708 |
|
|
709 |
def releasedir(self, path, fip):
|
|
710 |
# Ignore raw_fi
|
|
711 |
return self.operations('releasedir', self._decode_optional_path(path),
|
|
712 |
fip.contents.fh)
|
|
713 |
|
|
714 |
def fsyncdir(self, path, datasync, fip):
|
|
715 |
# Ignore raw_fi
|
|
716 |
return self.operations('fsyncdir', self._decode_optional_path(path),
|
|
717 |
datasync, fip.contents.fh)
|
|
718 |
|
|
719 |
def init(self, conn):
|
|
720 |
return self.operations('init', '/')
|
|
721 |
|
|
722 |
def destroy(self, private_data):
|
|
723 |
return self.operations('destroy', '/')
|
|
724 |
|
|
725 |
def access(self, path, amode):
|
|
726 |
return self.operations('access', path.decode(self.encoding), amode)
|
|
727 |
|
|
728 |
def create(self, path, mode, fip):
|
|
729 |
fi = fip.contents
|
|
730 |
path = path.decode(self.encoding)
|
|
731 |
|
|
732 |
if self.raw_fi:
|
|
733 |
return self.operations('create', path, mode, fi)
|
|
734 |
else:
|
|
735 |
fi.fh = self.operations('create', path, mode)
|
|
736 |
return 0
|
|
737 |
|
|
738 |
def ftruncate(self, path, length, fip):
|
|
739 |
if self.raw_fi:
|
|
740 |
fh = fip.contents
|
|
741 |
else:
|
|
742 |
fh = fip.contents.fh
|
|
743 |
|
|
744 |
return self.operations('truncate', self._decode_optional_path(path),
|
|
745 |
length, fh)
|
|
746 |
|
|
747 |
def fgetattr(self, path, buf, fip):
|
|
748 |
memset(buf, 0, sizeof(c_stat))
|
|
749 |
|
|
750 |
st = buf.contents
|
|
751 |
if not fip:
|
|
752 |
fh = fip
|
|
753 |
elif self.raw_fi:
|
|
754 |
fh = fip.contents
|
|
755 |
else:
|
|
756 |
fh = fip.contents.fh
|
|
757 |
|
|
758 |
attrs = self.operations('getattr', self._decode_optional_path(path), fh)
|
|
759 |
set_st_attrs(st, attrs)
|
|
760 |
return 0
|
|
761 |
|
|
762 |
def lock(self, path, fip, cmd, lock):
|
|
763 |
if self.raw_fi:
|
|
764 |
fh = fip.contents
|
|
765 |
else:
|
|
766 |
fh = fip.contents.fh
|
|
767 |
|
|
768 |
return self.operations('lock', self._decode_optional_path(path), fh, cmd,
|
|
769 |
lock)
|
|
770 |
|
|
771 |
def utimens(self, path, buf):
|
|
772 |
if buf:
|
|
773 |
atime = time_of_timespec(buf.contents.actime)
|
|
774 |
mtime = time_of_timespec(buf.contents.modtime)
|
|
775 |
times = (atime, mtime)
|
|
776 |
else:
|
|
777 |
times = None
|
|
778 |
|
|
779 |
return self.operations('utimens', path.decode(self.encoding), times)
|
|
780 |
|
|
781 |
def bmap(self, path, blocksize, idx):
|
|
782 |
return self.operations('bmap', path.decode(self.encoding), blocksize,
|
|
783 |
idx)
|
|
784 |
|
|
785 |
|
|
786 |
class Operations(object):
|
|
787 |
'''
|
|
788 |
This class should be subclassed and passed as an argument to FUSE on
|
|
789 |
initialization. All operations should raise a FuseOSError exception on
|
|
790 |
error.
|
|
791 |
|
|
792 |
When in doubt of what an operation should do, check the FUSE header file
|
|
793 |
or the corresponding system call man page.
|
|
794 |
'''
|
|
795 |
|
|
796 |
def __call__(self, op, *args):
|
|
797 |
if not hasattr(self, op):
|
|
798 |
raise FuseOSError(EFAULT)
|
|
799 |
return getattr(self, op)(*args)
|
|
800 |
|
|
801 |
def access(self, path, amode):
|
|
802 |
return 0
|
|
803 |
|
|
804 |
bmap = None
|
|
805 |
|
|
806 |
def chmod(self, path, mode):
|
|
807 |
raise FuseOSError(EROFS)
|
|
808 |
|
|
809 |
def chown(self, path, uid, gid):
|
|
810 |
raise FuseOSError(EROFS)
|
|
811 |
|
|
812 |
def create(self, path, mode, fi=None):
|
|
813 |
'''
|
|
814 |
When raw_fi is False (default case), fi is None and create should
|
|
815 |
return a numerical file handle.
|
|
816 |
|
|
817 |
When raw_fi is True the file handle should be set directly by create
|
|
818 |
and return 0.
|
|
819 |
'''
|
|
820 |
|
|
821 |
raise FuseOSError(EROFS)
|
|
822 |
|
|
823 |
def destroy(self, path):
|
|
824 |
'Called on filesystem destruction. Path is always /'
|
|
825 |
|
|
826 |
pass
|
|
827 |
|
|
828 |
def flush(self, path, fh):
|
|
829 |
return 0
|
|
830 |
|
|
831 |
def fsync(self, path, datasync, fh):
|
|
832 |
return 0
|
|
833 |
|
|
834 |
def fsyncdir(self, path, datasync, fh):
|
|
835 |
return 0
|
|
836 |
|
|
837 |
def getattr(self, path, fh=None):
|
|
838 |
'''
|
|
839 |
Returns a dictionary with keys identical to the stat C structure of
|
|
840 |
stat(2).
|
|
841 |
|
|
842 |
st_atime, st_mtime and st_ctime should be floats.
|
|
843 |
|
|
844 |
NOTE: There is an incombatibility between Linux and Mac OS X
|
|
845 |
concerning st_nlink of directories. Mac OS X counts all files inside
|
|
846 |
the directory, while Linux counts only the subdirectories.
|
|
847 |
'''
|
|
848 |
|
|
849 |
if path != '/':
|
|
850 |
raise FuseOSError(ENOENT)
|
|
851 |
return dict(st_mode=(S_IFDIR | 0o755), st_nlink=2)
|
|
852 |
|
|
853 |
def getxattr(self, path, name, position=0):
|
|
854 |
raise FuseOSError(ENOTSUP)
|
|
855 |
|
|
856 |
def init(self, path):
|
|
857 |
'''
|
|
858 |
Called on filesystem initialization. (Path is always /)
|
|
859 |
|
|
860 |
Use it instead of __init__ if you start threads on initialization.
|
|
861 |
'''
|
|
862 |
|
|
863 |
pass
|
|
864 |
|
|
865 |
def link(self, target, source):
|
|
866 |
'creates a hard link `target -> source` (e.g. ln source target)'
|
|
867 |
|
|
868 |
raise FuseOSError(EROFS)
|
|
869 |
|
|
870 |
def listxattr(self, path):
|
|
871 |
return []
|
|
872 |
|
|
873 |
lock = None
|
|
874 |
|
|
875 |
def mkdir(self, path, mode):
|
|
876 |
raise FuseOSError(EROFS)
|
|
877 |
|
|
878 |
def mknod(self, path, mode, dev):
|
|
879 |
raise FuseOSError(EROFS)
|
|
880 |
|
|
881 |
def open(self, path, flags):
|
|
882 |
'''
|
|
883 |
When raw_fi is False (default case), open should return a numerical
|
|
884 |
file handle.
|
|
885 |
|
|
886 |
When raw_fi is True the signature of open becomes:
|
|
887 |
open(self, path, fi)
|
|
888 |
|
|
889 |
and the file handle should be set directly.
|
|
890 |
'''
|
|
891 |
|
|
892 |
return 0
|
|
893 |
|
|
894 |
def opendir(self, path):
|
|
895 |
'Returns a numerical file handle.'
|
|
896 |
|
|
897 |
return 0
|
|
898 |
|
|
899 |
def read(self, path, size, offset, fh):
|
|
900 |
'Returns a string containing the data requested.'
|
|
901 |
|
|
902 |
raise FuseOSError(EIO)
|
|
903 |
|
|
904 |
def readdir(self, path, fh):
|
|
905 |
'''
|
|
906 |
Can return either a list of names, or a list of (name, attrs, offset)
|
|
907 |
tuples. attrs is a dict as in getattr.
|
|
908 |
'''
|
|
909 |
|
|
910 |
return ['.', '..']
|
|
911 |
|
|
912 |
def readlink(self, path):
|
|
913 |
raise FuseOSError(ENOENT)
|
|
914 |
|
|
915 |
def release(self, path, fh):
|
|
916 |
return 0
|
|
917 |
|
|
918 |
def releasedir(self, path, fh):
|
|
919 |
return 0
|
|
920 |
|
|
921 |
def removexattr(self, path, name):
|
|
922 |
raise FuseOSError(ENOTSUP)
|
|
923 |
|
|
924 |
def rename(self, old, new):
|
|
925 |
raise FuseOSError(EROFS)
|
|
926 |
|
|
927 |
def rmdir(self, path):
|
|
928 |
raise FuseOSError(EROFS)
|
|
929 |
|
|
930 |
def setxattr(self, path, name, value, options, position=0):
|
|
931 |
raise FuseOSError(ENOTSUP)
|
|
932 |
|
|
933 |
def statfs(self, path):
|
|
934 |
'''
|
|
935 |
Returns a dictionary with keys identical to the statvfs C structure of
|
|
936 |
statvfs(3).
|
|
937 |
|
|
938 |
On Mac OS X f_bsize and f_frsize must be a power of 2
|
|
939 |
(minimum 512).
|
|
940 |
'''
|
|
941 |
|
|
942 |
return {}
|
|
943 |
|
|
944 |
def symlink(self, target, source):
|
|
945 |
'creates a symlink `target -> source` (e.g. ln -s source target)'
|
|
946 |
|
|
947 |
raise FuseOSError(EROFS)
|
|
948 |
|
|
949 |
def truncate(self, path, length, fh=None):
|
|
950 |
raise FuseOSError(EROFS)
|
|
951 |
|
|
952 |
def unlink(self, path):
|
|
953 |
raise FuseOSError(EROFS)
|
|
954 |
|
|
955 |
def utimens(self, path, times=None):
|
|
956 |
'Times is a (atime, mtime) tuple. If None use current time.'
|
|
957 |
|
|
958 |
return 0
|
|
959 |
|
|
960 |
def write(self, path, data, offset, fh):
|
|
961 |
raise FuseOSError(EROFS)
|
|
962 |
|
|
963 |
|
|
964 |
class LoggingMixIn:
|
|
965 |
log = logging.getLogger('fuse.log-mixin')
|
|
966 |
|
|
967 |
def __call__(self, op, path, *args):
|
|
968 |
self.log.debug('-> %s %s %s', op, path, repr(args))
|
|
969 |
ret = '[Unhandled Exception]'
|
|
970 |
try:
|
|
971 |
ret = getattr(self, op)(path, *args)
|
|
972 |
return ret
|
|
973 |
except OSError as e:
|
|
974 |
ret = str(e)
|
|
975 |
raise
|
|
976 |
finally:
|
|
977 |
self.log.debug('<- %s %s', op, repr(ret))
|