github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/lib/python/fusepy/low-level/llfuse/interface.py (about) 1 ''' 2 $Id: interface.py 54 2010-02-22 02:33:10Z nikratio $ 3 4 Copyright (c) 2010, Nikolaus Rath <Nikolaus@rath.org> 5 All rights reserved. 6 7 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 8 9 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 10 * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 11 * Neither the name of the main author nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. 12 13 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 15 16 This module defines the interface between the FUSE C and Python API. The actual file system 17 is implemented as an `Operations` instance whose methods will 18 be called by the fuse library. 19 20 Note that all "string-like" quantities (e.g. file names, extended attribute names & values) are 21 represented as bytes, since POSIX doesn't require any of them to be valid unicode strings. 22 23 24 Exception Handling 25 ------------------ 26 27 Since Python exceptions cannot be forwarded to the FUSE kernel module, 28 the FUSE Python API catches all exceptions that are generated during 29 request processing. 30 31 If the exception is of type `FUSEError`, the appropriate errno is returned 32 to the kernel module and the exception is discarded. 33 34 For any other exceptions, a warning is logged and a generic error signaled 35 to the kernel module. Then the `handle_exc` method of the `Operations` 36 instance is called, so that the file system itself has a chance to react 37 to the problem (e.g. by marking the file system as needing a check). 38 39 The return value and any raised exceptions of `handle_exc` are ignored. 40 41 ''' 42 43 # Since we are using ctype Structures, we often have to 44 # access attributes that are not defined in __init__ 45 # (since they are defined in _fields_ instead) 46 #pylint: disable-msg=W0212 47 48 # We need globals 49 #pylint: disable-msg=W0603 50 51 from __future__ import division, print_function, absolute_import 52 53 # Using .. as libfuse makes PyDev really unhappy. 54 from . import ctypes_api 55 libfuse = ctypes_api 56 57 from ctypes import c_char_p, sizeof, create_string_buffer, addressof, string_at, POINTER, c_char, cast 58 from functools import partial 59 import errno 60 import logging 61 import sys 62 63 64 __all__ = [ 'FUSEError', 'ENOATTR', 'ENOTSUP', 'init', 'main', 'close', 65 'fuse_version' ] 66 67 68 # These should really be defined in the errno module, but 69 # unfortunately they are missing 70 ENOATTR = libfuse.ENOATTR 71 ENOTSUP = libfuse.ENOTSUP 72 73 log = logging.getLogger("fuse") 74 75 # Init globals 76 operations = None 77 fuse_ops = None 78 mountpoint = None 79 session = None 80 channel = None 81 82 class DiscardedRequest(Exception): 83 '''Request was interrupted and reply discarded. 84 85 ''' 86 87 pass 88 89 class ReplyError(Exception): 90 '''Unable to send reply to fuse kernel module. 91 92 ''' 93 94 pass 95 96 class FUSEError(Exception): 97 '''Wrapped errno value to be returned to the fuse kernel module 98 99 This exception can store only an errno. Request handlers should raise 100 to return a specific errno to the fuse kernel module. 101 ''' 102 103 __slots__ = [ 'errno' ] 104 105 def __init__(self, errno_): 106 super(FUSEError, self).__init__() 107 self.errno = errno_ 108 109 def __str__(self): 110 # errno may not have strings for all error codes 111 return errno.errorcode.get(self.errno, str(self.errno)) 112 113 114 115 def check_reply_result(result, func, *args): 116 '''Check result of a call to a fuse_reply_* foreign function 117 118 If `result` is 0, it is assumed that the call succeeded and the function does nothing. 119 120 If result is `-errno.ENOENT`, this means that the request has been discarded and `DiscardedRequest` 121 is raised. 122 123 In all other cases, `ReplyError` is raised. 124 125 (We do not try to call `fuse_reply_err` or any other reply method as well, because the first reply 126 function may have already invalidated the `req` object and it seems better to (possibly) let the 127 request pend than to crash the server application.) 128 ''' 129 130 if result == 0: 131 return None 132 133 elif result == -errno.ENOENT: 134 raise DiscardedRequest() 135 136 elif result > 0: 137 raise ReplyError('Foreign function %s returned unexpected value %d' 138 % (func.name, result)) 139 elif result < 0: 140 raise ReplyError('Foreign function %s returned error %s' 141 % (func.name, errno.errorcode.get(-result, str(-result)))) 142 143 144 # 145 # Set return checker for common ctypes calls 146 # 147 reply_functions = [ 'fuse_reply_err', 'fuse_reply_entry', 148 'fuse_reply_create', 'fuse_reply_readlink', 'fuse_reply_open', 149 'fuse_reply_write', 'fuse_reply_attr', 'fuse_reply_buf', 150 'fuse_reply_iov', 'fuse_reply_statfs', 'fuse_reply_xattr', 151 'fuse_reply_lock' ] 152 for fname in reply_functions: 153 getattr(libfuse, fname).errcheck = check_reply_result 154 155 # Name isn't stored by ctypes 156 getattr(libfuse, fname).name = fname 157 158 159 def dict_to_entry(attr): 160 '''Convert dict to fuse_entry_param''' 161 162 entry = libfuse.fuse_entry_param() 163 164 entry.ino = attr['st_ino'] 165 entry.generation = attr.pop('generation') 166 entry.entry_timeout = attr.pop('entry_timeout') 167 entry.attr_timeout = attr.pop('attr_timeout') 168 169 entry.attr = dict_to_stat(attr) 170 171 return entry 172 173 def dict_to_stat(attr): 174 '''Convert dict to struct stat''' 175 176 stat = libfuse.stat() 177 178 # Determine correct way to store times 179 if hasattr(stat, 'st_atim'): # Linux 180 get_timespec_key = lambda key: key[:-1] 181 elif hasattr(stat, 'st_atimespec'): # FreeBSD 182 get_timespec_key = lambda key: key + 'spec' 183 else: 184 get_timespec_key = False 185 186 # Raises exception if there are any unknown keys 187 for (key, val) in attr.iteritems(): 188 if val is None: # do not set undefined items 189 continue 190 if get_timespec_key and key in ('st_atime', 'st_mtime', 'st_ctime'): 191 key = get_timespec_key(key) 192 spec = libfuse.timespec() 193 spec.tv_sec = int(val) 194 spec.tv_nsec = int((val - int(val)) * 10 ** 9) 195 val = spec 196 setattr(stat, key, val) 197 198 return stat 199 200 201 def stat_to_dict(stat): 202 '''Convert ``struct stat`` to dict''' 203 204 attr = dict() 205 for (name, dummy) in libfuse.stat._fields_: 206 if name.startswith('__'): 207 continue 208 209 if name in ('st_atim', 'st_mtim', 'st_ctim'): 210 key = name + 'e' 211 attr[key] = getattr(stat, name).tv_sec + getattr(stat, name).tv_nsec / 10 ** 9 212 elif name in ('st_atimespec', 'st_mtimespec', 'st_ctimespec'): 213 key = name[:-4] 214 attr[key] = getattr(stat, name).tv_sec + getattr(stat, name).tv_nsec / 10 ** 9 215 else: 216 attr[name] = getattr(stat, name) 217 218 return attr 219 220 221 def op_wrapper(func, req, *args): 222 '''Catch all exceptions and call fuse_reply_err instead''' 223 224 try: 225 func(req, *args) 226 except FUSEError as e: 227 log.debug('op_wrapper caught FUSEError, calling fuse_reply_err(%s)', 228 errno.errorcode.get(e.errno, str(e.errno))) 229 try: 230 libfuse.fuse_reply_err(req, e.errno) 231 except DiscardedRequest: 232 pass 233 except Exception as exc: 234 log.exception('FUSE handler raised exception.') 235 236 # Report error to filesystem 237 if hasattr(operations, 'handle_exc'): 238 try: 239 operations.handle_exc(exc) 240 except: 241 pass 242 243 # Send error reply, unless the error occured when replying 244 if not isinstance(exc, ReplyError): 245 log.debug('Calling fuse_reply_err(EIO)') 246 libfuse.fuse_reply_err(req, errno.EIO) 247 248 def fuse_version(): 249 '''Return version of loaded fuse library''' 250 251 return libfuse.fuse_version() 252 253 254 def init(operations_, mountpoint_, args): 255 '''Initialize and mount FUSE file system 256 257 `operations_` has to be an instance of the `Operations` class (or another 258 class defining the same methods). 259 260 `args` has to be a list of strings. Valid options are listed in struct fuse_opt fuse_mount_opts[] 261 (mount.c:68) and struct fuse_opt fuse_ll_opts[] (fuse_lowlevel_c:1526). 262 ''' 263 264 log.debug('Initializing llfuse') 265 266 global operations 267 global fuse_ops 268 global mountpoint 269 global session 270 global channel 271 272 # Give operations instance a chance to check and change 273 # the FUSE options 274 operations_.check_args(args) 275 276 mountpoint = mountpoint_ 277 operations = operations_ 278 fuse_ops = libfuse.fuse_lowlevel_ops() 279 fuse_args = make_fuse_args(args) 280 281 # Init fuse_ops 282 module = globals() 283 for (name, prototype) in libfuse.fuse_lowlevel_ops._fields_: 284 if hasattr(operations, name): 285 method = partial(op_wrapper, module['fuse_' + name]) 286 setattr(fuse_ops, name, prototype(method)) 287 288 log.debug('Calling fuse_mount') 289 channel = libfuse.fuse_mount(mountpoint, fuse_args) 290 if not channel: 291 raise RuntimeError('fuse_mount failed') 292 try: 293 log.debug('Calling fuse_lowlevel_new') 294 session = libfuse.fuse_lowlevel_new(fuse_args, fuse_ops, sizeof(fuse_ops), None) 295 if not session: 296 raise RuntimeError("fuse_lowlevel_new() failed") 297 try: 298 log.debug('Calling fuse_set_signal_handlers') 299 if libfuse.fuse_set_signal_handlers(session) == -1: 300 raise RuntimeError("fuse_set_signal_handlers() failed") 301 try: 302 log.debug('Calling fuse_session_add_chan') 303 libfuse.fuse_session_add_chan(session, channel) 304 session = session 305 channel = channel 306 return 307 308 except: 309 log.debug('Calling fuse_remove_signal_handlers') 310 libfuse.fuse_remove_signal_handlers(session) 311 raise 312 313 except: 314 log.debug('Calling fuse_session_destroy') 315 libfuse.fuse_session_destroy(session) 316 raise 317 except: 318 log.debug('Calling fuse_unmount') 319 libfuse.fuse_unmount(mountpoint, channel) 320 raise 321 322 def make_fuse_args(args): 323 '''Create fuse_args Structure for given mount options''' 324 325 args1 = [ sys.argv[0] ] 326 for opt in args: 327 args1.append(b'-o') 328 args1.append(opt) 329 330 # Init fuse_args struct 331 fuse_args = libfuse.fuse_args() 332 fuse_args.allocated = 0 333 fuse_args.argc = len(args1) 334 fuse_args.argv = (POINTER(c_char) * len(args1))(*[cast(c_char_p(x), POINTER(c_char)) 335 for x in args1]) 336 return fuse_args 337 338 def main(single=False): 339 '''Run FUSE main loop''' 340 341 if not session: 342 raise RuntimeError('Need to call init() before main()') 343 344 if single: 345 log.debug('Calling fuse_session_loop') 346 if libfuse.fuse_session_loop(session) != 0: 347 raise RuntimeError("fuse_session_loop() failed") 348 else: 349 log.debug('Calling fuse_session_loop_mt') 350 if libfuse.fuse_session_loop_mt(session) != 0: 351 raise RuntimeError("fuse_session_loop_mt() failed") 352 353 def close(): 354 '''Unmount file system and clean up''' 355 356 global operations 357 global fuse_ops 358 global mountpoint 359 global session 360 global channel 361 362 log.debug('Calling fuse_session_remove_chan') 363 libfuse.fuse_session_remove_chan(channel) 364 log.debug('Calling fuse_remove_signal_handlers') 365 libfuse.fuse_remove_signal_handlers(session) 366 log.debug('Calling fuse_session_destroy') 367 libfuse.fuse_session_destroy(session) 368 log.debug('Calling fuse_unmount') 369 libfuse.fuse_unmount(mountpoint, channel) 370 371 operations = None 372 fuse_ops = None 373 mountpoint = None 374 session = None 375 channel = None 376 377 378 def fuse_lookup(req, parent_inode, name): 379 '''Look up a directory entry by name and get its attributes''' 380 381 log.debug('Handling lookup(%d, %s)', parent_inode, string_at(name)) 382 383 attr = operations.lookup(parent_inode, string_at(name)) 384 entry = dict_to_entry(attr) 385 386 log.debug('Calling fuse_reply_entry') 387 try: 388 libfuse.fuse_reply_entry(req, entry) 389 except DiscardedRequest: 390 pass 391 392 def fuse_init(userdata_p, conn_info_p): 393 '''Initialize Operations''' 394 operations.init() 395 396 def fuse_destroy(userdata_p): 397 '''Cleanup Operations''' 398 operations.destroy() 399 400 def fuse_getattr(req, ino, _unused): 401 '''Get attributes for `ino`''' 402 403 log.debug('Handling getattr(%d)', ino) 404 405 attr = operations.getattr(ino) 406 407 attr_timeout = attr.pop('attr_timeout') 408 stat = dict_to_stat(attr) 409 410 log.debug('Calling fuse_reply_attr') 411 try: 412 libfuse.fuse_reply_attr(req, stat, attr_timeout) 413 except DiscardedRequest: 414 pass 415 416 def fuse_access(req, ino, mask): 417 '''Check if calling user has `mask` rights for `ino`''' 418 419 log.debug('Handling access(%d, %o)', ino, mask) 420 421 # Get UID 422 ctx = libfuse.fuse_req_ctx(req).contents 423 424 # Define a function that returns a list of the GIDs 425 def get_gids(): 426 # Get GID list if FUSE supports it 427 # Weird syntax to prevent PyDev from complaining 428 getgroups = getattr(libfuse, "fuse_req_getgroups") 429 gid_t = getattr(libfuse, 'gid_t') 430 no = 10 431 buf = (gid_t * no)(range(no)) 432 ret = getgroups(req, no, buf) 433 if ret > no: 434 no = ret 435 buf = (gid_t * no)(range(no)) 436 ret = getgroups(req, no, buf) 437 438 return [ buf[i].value for i in range(ret) ] 439 440 ret = operations.access(ino, mask, ctx, get_gids) 441 442 log.debug('Calling fuse_reply_err') 443 try: 444 if ret: 445 libfuse.fuse_reply_err(req, 0) 446 else: 447 libfuse.fuse_reply_err(req, errno.EPERM) 448 except DiscardedRequest: 449 pass 450 451 452 def fuse_create(req, ino_parent, name, mode, fi): 453 '''Create and open a file''' 454 455 log.debug('Handling create(%d, %s, %o)', ino_parent, string_at(name), mode) 456 (fh, attr) = operations.create(ino_parent, string_at(name), mode, 457 libfuse.fuse_req_ctx(req).contents) 458 fi.contents.fh = fh 459 fi.contents.keep_cache = 1 460 entry = dict_to_entry(attr) 461 462 log.debug('Calling fuse_reply_create') 463 try: 464 libfuse.fuse_reply_create(req, entry, fi) 465 except DiscardedRequest: 466 operations.release(fh) 467 468 469 def fuse_flush(req, ino, fi): 470 '''Handle close() system call 471 472 May be called multiple times for the same open file. 473 ''' 474 475 log.debug('Handling flush(%d)', fi.contents.fh) 476 operations.flush(fi.contents.fh) 477 log.debug('Calling fuse_reply_err(0)') 478 try: 479 libfuse.fuse_reply_err(req, 0) 480 except DiscardedRequest: 481 pass 482 483 484 def fuse_fsync(req, ino, datasync, fi): 485 '''Flush buffers for `ino` 486 487 If the datasync parameter is non-zero, then only the user data 488 is flushed (and not the meta data). 489 ''' 490 491 log.debug('Handling fsync(%d, %s)', fi.contents.fh, datasync != 0) 492 operations.fsync(fi.contents.fh, datasync != 0) 493 log.debug('Calling fuse_reply_err(0)') 494 try: 495 libfuse.fuse_reply_err(req, 0) 496 except DiscardedRequest: 497 pass 498 499 500 def fuse_fsyncdir(req, ino, datasync, fi): 501 '''Synchronize directory contents 502 503 If the datasync parameter is non-zero, then only the directory contents 504 are flushed (and not the meta data about the directory itself). 505 ''' 506 507 log.debug('Handling fsyncdir(%d, %s)', fi.contents.fh, datasync != 0) 508 operations.fsyncdir(fi.contents.fh, datasync != 0) 509 log.debug('Calling fuse_reply_err(0)') 510 try: 511 libfuse.fuse_reply_err(req, 0) 512 except DiscardedRequest: 513 pass 514 515 516 def fuse_getxattr(req, ino, name, size): 517 '''Get an extended attribute. 518 ''' 519 520 log.debug('Handling getxattr(%d, %r, %d)', ino, string_at(name), size) 521 val = operations.getxattr(ino, string_at(name)) 522 if not isinstance(val, bytes): 523 raise TypeError("getxattr return value must be of type bytes") 524 525 try: 526 if size == 0: 527 log.debug('Calling fuse_reply_xattr') 528 libfuse.fuse_reply_xattr(req, len(val)) 529 elif size >= len(val): 530 log.debug('Calling fuse_reply_buf') 531 libfuse.fuse_reply_buf(req, val, len(val)) 532 else: 533 raise FUSEError(errno.ERANGE) 534 except DiscardedRequest: 535 pass 536 537 538 def fuse_link(req, ino, new_parent_ino, new_name): 539 '''Create a hard link''' 540 541 log.debug('Handling fuse_link(%d, %d, %s)', ino, new_parent_ino, string_at(new_name)) 542 attr = operations.link(ino, new_parent_ino, string_at(new_name)) 543 entry = dict_to_entry(attr) 544 545 log.debug('Calling fuse_reply_entry') 546 try: 547 libfuse.fuse_reply_entry(req, entry) 548 except DiscardedRequest: 549 pass 550 551 def fuse_listxattr(req, inode, size): 552 '''List extended attributes for `inode`''' 553 554 log.debug('Handling listxattr(%d)', inode) 555 names = operations.listxattr(inode) 556 557 if not all([ isinstance(name, bytes) for name in names]): 558 raise TypeError("listxattr return value must be list of bytes") 559 560 # Size of the \0 separated buffer 561 act_size = (len(names) - 1) + sum([ len(name) for name in names ]) 562 563 if size == 0: 564 try: 565 log.debug('Calling fuse_reply_xattr') 566 libfuse.fuse_reply_xattr(req, len(names)) 567 except DiscardedRequest: 568 pass 569 570 elif act_size > size: 571 raise FUSEError(errno.ERANGE) 572 573 else: 574 try: 575 log.debug('Calling fuse_reply_buf') 576 libfuse.fuse_reply_buf(req, b'\0'.join(names), act_size) 577 except DiscardedRequest: 578 pass 579 580 581 def fuse_mkdir(req, inode_parent, name, mode): 582 '''Create directory''' 583 584 log.debug('Handling mkdir(%d, %s, %o)', inode_parent, string_at(name), mode) 585 attr = operations.mkdir(inode_parent, string_at(name), mode, 586 libfuse.fuse_req_ctx(req).contents) 587 entry = dict_to_entry(attr) 588 589 log.debug('Calling fuse_reply_entry') 590 try: 591 libfuse.fuse_reply_entry(req, entry) 592 except DiscardedRequest: 593 pass 594 595 def fuse_mknod(req, inode_parent, name, mode, rdev): 596 '''Create (possibly special) file''' 597 598 log.debug('Handling mknod(%d, %s, %o, %d)', inode_parent, string_at(name), 599 mode, rdev) 600 attr = operations.mknod(inode_parent, string_at(name), mode, rdev, 601 libfuse.fuse_req_ctx(req).contents) 602 entry = dict_to_entry(attr) 603 604 log.debug('Calling fuse_reply_entry') 605 try: 606 libfuse.fuse_reply_entry(req, entry) 607 except DiscardedRequest: 608 pass 609 610 def fuse_open(req, inode, fi): 611 '''Open a file''' 612 log.debug('Handling open(%d, %d)', inode, fi.contents.flags) 613 fi.contents.fh = operations.open(inode, fi.contents.flags) 614 fi.contents.keep_cache = 1 615 616 log.debug('Calling fuse_reply_open') 617 try: 618 libfuse.fuse_reply_open(req, fi) 619 except DiscardedRequest: 620 operations.release(inode, fi.contents.fh) 621 622 def fuse_opendir(req, inode, fi): 623 '''Open a directory''' 624 625 log.debug('Handling opendir(%d)', inode) 626 fi.contents.fh = operations.opendir(inode) 627 628 log.debug('Calling fuse_reply_open') 629 try: 630 libfuse.fuse_reply_open(req, fi) 631 except DiscardedRequest: 632 operations.releasedir(fi.contents.fh) 633 634 635 def fuse_read(req, ino, size, off, fi): 636 '''Read data from file''' 637 638 log.debug('Handling read(ino=%d, off=%d, size=%d)', fi.contents.fh, off, size) 639 data = operations.read(fi.contents.fh, off, size) 640 641 if not isinstance(data, bytes): 642 raise TypeError("read() must return bytes") 643 644 if len(data) > size: 645 raise ValueError('read() must not return more than `size` bytes') 646 647 log.debug('Calling fuse_reply_buf') 648 try: 649 libfuse.fuse_reply_buf(req, data, len(data)) 650 except DiscardedRequest: 651 pass 652 653 654 def fuse_readlink(req, inode): 655 '''Read target of symbolic link''' 656 657 log.debug('Handling readlink(%d)', inode) 658 target = operations.readlink(inode) 659 log.debug('Calling fuse_reply_readlink') 660 try: 661 libfuse.fuse_reply_readlink(req, target) 662 except DiscardedRequest: 663 pass 664 665 666 def fuse_readdir(req, ino, bufsize, off, fi): 667 '''Read directory entries''' 668 669 log.debug('Handling readdir(%d, %d, %d, %d)', ino, bufsize, off, fi.contents.fh) 670 671 # Collect as much entries as we can return 672 entries = list() 673 size = 0 674 for (name, attr) in operations.readdir(fi.contents.fh, off): 675 if not isinstance(name, bytes): 676 raise TypeError("readdir() must return entry names as bytes") 677 678 stat = dict_to_stat(attr) 679 680 entry_size = libfuse.fuse_add_direntry(req, None, 0, name, stat, 0) 681 if size + entry_size > bufsize: 682 break 683 684 entries.append((name, stat)) 685 size += entry_size 686 687 log.debug('Gathered %d entries, total size %d', len(entries), size) 688 689 # If there are no entries left, return empty buffer 690 if not entries: 691 try: 692 log.debug('Calling fuse_reply_buf') 693 libfuse.fuse_reply_buf(req, None, 0) 694 except DiscardedRequest: 695 pass 696 return 697 698 # Create and fill buffer 699 log.debug('Adding entries to buffer') 700 buf = create_string_buffer(size) 701 next_ = off 702 addr_off = 0 703 for (name, stat) in entries: 704 next_ += 1 705 addr_off += libfuse.fuse_add_direntry(req, cast(addressof(buf) + addr_off, POINTER(c_char)), 706 bufsize, name, stat, next_) 707 708 # Return buffer 709 log.debug('Calling fuse_reply_buf') 710 try: 711 libfuse.fuse_reply_buf(req, buf, size) 712 except DiscardedRequest: 713 pass 714 715 716 def fuse_release(req, inode, fi): 717 '''Release open file''' 718 719 log.debug('Handling release(%d)', fi.contents.fh) 720 operations.release(fi.contents.fh) 721 log.debug('Calling fuse_reply_err(0)') 722 try: 723 libfuse.fuse_reply_err(req, 0) 724 except DiscardedRequest: 725 pass 726 727 def fuse_releasedir(req, inode, fi): 728 '''Release open directory''' 729 730 log.debug('Handling releasedir(%d)', fi.contents.fh) 731 operations.releasedir(fi.contents.fh) 732 log.debug('Calling fuse_reply_err(0)') 733 try: 734 libfuse.fuse_reply_err(req, 0) 735 except DiscardedRequest: 736 pass 737 738 def fuse_removexattr(req, inode, name): 739 '''Remove extended attribute''' 740 741 log.debug('Handling removexattr(%d, %s)', inode, string_at(name)) 742 operations.removexattr(inode, string_at(name)) 743 log.debug('Calling fuse_reply_err(0)') 744 try: 745 libfuse.fuse_reply_err(req, 0) 746 except DiscardedRequest: 747 pass 748 749 def fuse_rename(req, parent_inode_old, name_old, parent_inode_new, name_new): 750 '''Rename a directory entry''' 751 752 log.debug('Handling rename(%d, %r, %d, %r)', parent_inode_old, string_at(name_old), 753 parent_inode_new, string_at(name_new)) 754 operations.rename(parent_inode_old, string_at(name_old), parent_inode_new, 755 string_at(name_new)) 756 log.debug('Calling fuse_reply_err(0)') 757 try: 758 libfuse.fuse_reply_err(req, 0) 759 except DiscardedRequest: 760 pass 761 762 def fuse_rmdir(req, inode_parent, name): 763 '''Remove a directory''' 764 765 log.debug('Handling rmdir(%d, %r)', inode_parent, string_at(name)) 766 operations.rmdir(inode_parent, string_at(name)) 767 log.debug('Calling fuse_reply_err(0)') 768 try: 769 libfuse.fuse_reply_err(req, 0) 770 except DiscardedRequest: 771 pass 772 773 def fuse_setattr(req, inode, stat, to_set, fi): 774 '''Change directory entry attributes''' 775 776 log.debug('Handling fuse_setattr(%d)', inode) 777 778 # Note: We can't check if we know all possible flags, 779 # because the part of to_set that is not "covered" 780 # by flags seems to be undefined rather than zero. 781 782 attr_all = stat_to_dict(stat.contents) 783 attr = dict() 784 785 if (to_set & libfuse.FUSE_SET_ATTR_MTIME) != 0: 786 attr['st_mtime'] = attr_all['st_mtime'] 787 788 if (to_set & libfuse.FUSE_SET_ATTR_ATIME) != 0: 789 attr['st_atime'] = attr_all['st_atime'] 790 791 if (to_set & libfuse.FUSE_SET_ATTR_MODE) != 0: 792 attr['st_mode'] = attr_all['st_mode'] 793 794 if (to_set & libfuse.FUSE_SET_ATTR_UID) != 0: 795 attr['st_uid'] = attr_all['st_uid'] 796 797 if (to_set & libfuse.FUSE_SET_ATTR_GID) != 0: 798 attr['st_gid'] = attr_all['st_gid'] 799 800 if (to_set & libfuse.FUSE_SET_ATTR_SIZE) != 0: 801 attr['st_size'] = attr_all['st_size'] 802 803 attr = operations.setattr(inode, attr) 804 805 attr_timeout = attr.pop('attr_timeout') 806 stat = dict_to_stat(attr) 807 808 log.debug('Calling fuse_reply_attr') 809 try: 810 libfuse.fuse_reply_attr(req, stat, attr_timeout) 811 except DiscardedRequest: 812 pass 813 814 def fuse_setxattr(req, inode, name, val, size, flags): 815 '''Set an extended attribute''' 816 817 log.debug('Handling setxattr(%d, %r, %r, %d)', inode, string_at(name), 818 string_at(val, size), flags) 819 820 # Make sure we know all the flags 821 if (flags & ~(libfuse.XATTR_CREATE | libfuse.XATTR_REPLACE)) != 0: 822 raise ValueError('unknown flag') 823 824 if (flags & libfuse.XATTR_CREATE) != 0: 825 try: 826 operations.getxattr(inode, string_at(name)) 827 except FUSEError as e: 828 if e.errno == ENOATTR: 829 pass 830 raise 831 else: 832 raise FUSEError(errno.EEXIST) 833 elif (flags & libfuse.XATTR_REPLACE) != 0: 834 # Exception can be passed on if the attribute does not exist 835 operations.getxattr(inode, string_at(name)) 836 837 operations.setxattr(inode, string_at(name), string_at(val, size)) 838 839 log.debug('Calling fuse_reply_err(0)') 840 try: 841 libfuse.fuse_reply_err(req, 0) 842 except DiscardedRequest: 843 pass 844 845 def fuse_statfs(req, inode): 846 '''Return filesystem statistics''' 847 848 log.debug('Handling statfs(%d)', inode) 849 attr = operations.statfs() 850 statfs = libfuse.statvfs() 851 852 for (key, val) in attr.iteritems(): 853 setattr(statfs, key, val) 854 855 log.debug('Calling fuse_reply_statfs') 856 try: 857 libfuse.fuse_reply_statfs(req, statfs) 858 except DiscardedRequest: 859 pass 860 861 def fuse_symlink(req, target, parent_inode, name): 862 '''Create a symbolic link''' 863 864 log.debug('Handling symlink(%d, %r, %r)', parent_inode, string_at(name), string_at(target)) 865 attr = operations.symlink(parent_inode, string_at(name), string_at(target), 866 libfuse.fuse_req_ctx(req).contents) 867 entry = dict_to_entry(attr) 868 869 log.debug('Calling fuse_reply_entry') 870 try: 871 libfuse.fuse_reply_entry(req, entry) 872 except DiscardedRequest: 873 pass 874 875 876 def fuse_unlink(req, parent_inode, name): 877 '''Delete a file''' 878 879 log.debug('Handling unlink(%d, %r)', parent_inode, string_at(name)) 880 operations.unlink(parent_inode, string_at(name)) 881 log.debug('Calling fuse_reply_err(0)') 882 try: 883 libfuse.fuse_reply_err(req, 0) 884 except DiscardedRequest: 885 pass 886 887 def fuse_write(req, inode, buf, size, off, fi): 888 '''Write into an open file handle''' 889 890 log.debug('Handling write(fh=%d, off=%d, size=%d)', fi.contents.fh, off, size) 891 written = operations.write(fi.contents.fh, off, string_at(buf, size)) 892 893 log.debug('Calling fuse_reply_write') 894 try: 895 libfuse.fuse_reply_write(req, written) 896 except DiscardedRequest: 897 pass