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