Guest User

disable-uid

a guest
Nov 9th, 2025
15
0
336 days
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 17.06 KB | None | 0 0
  1. #!/usr/bin/env python3
  2. '''
  3. passthroughfs.py - Example file system for Python-LLFUSE
  4.  
  5. This file system mirrors the contents of a specified directory tree. It requires
  6. Python 3.3 (since Python 2.x does not support the follow_symlinks parameters for
  7. os.* functions).
  8.  
  9. Caveats:
  10.  
  11. * Inode generation numbers are not passed through but set to zero.
  12.  
  13. * Block size (st_blksize) and number of allocated blocks (st_blocks) are not
  14.   passed through.
  15.  
  16. * Performance for large directories is not good, because the directory
  17.   is always read completely.
  18.  
  19. * There may be a way to break-out of the directory tree.
  20.  
  21. * The readdir implementation is not fully POSIX compliant. If a directory
  22.   contains hardlinks and is modified during a readdir call, readdir()
  23.   may return some of the hardlinked files twice or omit them completely.
  24.  
  25. * If you delete or rename files in the underlying file system, the
  26.   passthrough file system will get confused.
  27.  
  28. Copyright ©  Nikolaus Rath <Nikolaus.org>
  29.  
  30. Permission is hereby granted, free of charge, to any person obtaining a copy of
  31. this software and associated documentation files (the "Software"), to deal in
  32. the Software without restriction, including without limitation the rights to
  33. use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  34. the Software, and to permit persons to whom the Software is furnished to do so.
  35.  
  36. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  37. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  38. FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  39. COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  40. IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  41. CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  42. '''
  43.  
  44. import os
  45. import sys
  46.  
  47. # If we are running from the Python-LLFUSE source directory, try
  48. # to load the module from there first.
  49. basedir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '..'))
  50. if (os.path.exists(os.path.join(basedir, 'setup.py')) and
  51.     os.path.exists(os.path.join(basedir, 'src', 'llfuse.pyx'))):
  52.     sys.path.insert(0, os.path.join(basedir, 'src'))
  53.  
  54. import llfuse
  55. from argparse import ArgumentParser
  56. import errno
  57. import logging
  58. import stat as stat_m
  59. from llfuse import FUSEError
  60. from os import fsencode, fsdecode
  61. from collections import defaultdict
  62.  
  63. import faulthandler
  64. faulthandler.enable()
  65.  
  66. log = logging.getLogger(__name__)
  67.  
  68. class Operations(llfuse.Operations):
  69.  
  70.     def __init__(self, source):
  71.         super().__init__()
  72.         self._inode_path_map = { llfuse.ROOT_INODE: source }
  73.         self._lookup_cnt = defaultdict(lambda : 0)
  74.         self._fd_inode_map = dict()
  75.         self._inode_fd_map = dict()
  76.         self._fd_open_count = dict()
  77.  
  78.     def _inode_to_path(self, inode):
  79.         try:
  80.             val = self._inode_path_map[inode]
  81.         except KeyError:
  82.             raise FUSEError(errno.ENOENT)
  83.  
  84.         if isinstance(val, set):
  85.             # In case of hardlinks, pick any path
  86.             val = next(iter(val))
  87.         return val
  88.  
  89.     def _add_path(self, inode, path):
  90.         log.debug('_add_path for %d, %s', inode, path)
  91.         self._lookup_cnt[inode] += 1
  92.  
  93.         # With hardlinks, one inode may map to multiple paths.
  94.         if inode not in self._inode_path_map:
  95.             self._inode_path_map[inode] = path
  96.             return
  97.  
  98.         val = self._inode_path_map[inode]
  99.         if isinstance(val, set):
  100.             val.add(path)
  101.         elif val != path:
  102.             self._inode_path_map[inode] = { path, val }
  103.  
  104.     def forget(self, inode_list):
  105.         for (inode, nlookup) in inode_list:
  106.             if self._lookup_cnt[inode] > nlookup:
  107.                 self._lookup_cnt[inode] -= nlookup
  108.                 continue
  109.             log.debug('forgetting about inode %d', inode)
  110.             assert inode not in self._inode_fd_map
  111.             del self._lookup_cnt[inode]
  112.             try:
  113.                 del self._inode_path_map[inode]
  114.             except KeyError: # may have been deleted
  115.                 pass
  116.  
  117.     def lookup(self, inode_p, name, ctx=None):
  118.         name = fsdecode(name)
  119.         log.debug('lookup for %s in %d', name, inode_p)
  120.         path = os.path.join(self._inode_to_path(inode_p), name)
  121.         attr = self._getattr(path=path)
  122.         if name != '.' and name != '..':
  123.             self._add_path(attr.st_ino, path)
  124.         return attr
  125.  
  126.     def getattr(self, inode, ctx=None):
  127.         if inode in self._inode_fd_map:
  128.             return self._getattr(fd=self._inode_fd_map[inode])
  129.         else:
  130.             return self._getattr(path=self._inode_to_path(inode))
  131.  
  132.     def _getattr(self, path=None, fd=None):
  133.         assert fd is None or path is None
  134.         assert not(fd is None and path is None)
  135.         try:
  136.             if fd is None:
  137.                 stat = os.lstat(path)
  138.             else:
  139.                 stat = os.fstat(fd)
  140.         except OSError as exc:
  141.             raise FUSEError(exc.errno)
  142.  
  143.         entry = llfuse.EntryAttributes()
  144.         for attr in ('st_ino', 'st_mode', 'st_nlink', 'st_uid', 'st_gid',
  145.                      'st_rdev', 'st_size', 'st_atime_ns', 'st_mtime_ns',
  146.                      'st_ctime_ns'):
  147.             setattr(entry, attr, getattr(stat, attr))
  148.         entry.generation = 0
  149.         entry.entry_timeout = 5
  150.         entry.attr_timeout = 5
  151.         entry.st_blksize = 512
  152.         entry.st_blocks = ((entry.st_size+entry.st_blksize-1) // entry.st_blksize)
  153.  
  154.         return entry
  155.  
  156.     def readlink(self, inode, ctx):
  157.         path = self._inode_to_path(inode)
  158.         try:
  159.             target = os.readlink(path)
  160.         except OSError as exc:
  161.             raise FUSEError(exc.errno)
  162.         return fsencode(target)
  163.  
  164.     def opendir(self, inode, ctx):
  165.         return inode
  166.  
  167.     def readdir(self, inode, off):
  168.         path = self._inode_to_path(inode)
  169.         log.debug('reading %s', path)
  170.         entries = []
  171.         for name in os.listdir(path):
  172.             attr = self._getattr(path=os.path.join(path, name))
  173.             entries.append((attr.st_ino, name, attr))
  174.  
  175.         log.debug('read %d entries, starting at %d', len(entries), off)
  176.  
  177.         # This is not fully posix compatible. If there are hardlinks
  178.         # (two names with the same inode), we don't have a unique
  179.         # offset to start in between them. Note that we cannot simply
  180.         # count entries, because then we would skip over entries
  181.         # (or return them more than once) if the number of directory
  182.         # entries changes between two calls to readdir().
  183.         for (ino, name, attr) in sorted(entries):
  184.             if ino <= off:
  185.                 continue
  186.             yield (fsencode(name), attr, ino)
  187.  
  188.     def unlink(self, inode_p, name, ctx):
  189.         name = fsdecode(name)
  190.         parent = self._inode_to_path(inode_p)
  191.         path = os.path.join(parent, name)
  192.         try:
  193.             inode = os.lstat(path).st_ino
  194.             os.unlink(path)
  195.         except OSError as exc:
  196.             raise FUSEError(exc.errno)
  197.         if inode in self._lookup_cnt:
  198.             self._forget_path(inode, path)
  199.  
  200.     def rmdir(self, inode_p, name, ctx):
  201.         name = fsdecode(name)
  202.         parent = self._inode_to_path(inode_p)
  203.         path = os.path.join(parent, name)
  204.         try:
  205.             inode = os.lstat(path).st_ino
  206.             os.rmdir(path)
  207.         except OSError as exc:
  208.             raise FUSEError(exc.errno)
  209.         if inode in self._lookup_cnt:
  210.             self._forget_path(inode, path)
  211.  
  212.     def _forget_path(self, inode, path):
  213.         log.debug('forget %s for %d', path, inode)
  214.         val = self._inode_path_map[inode]
  215.         if isinstance(val, set):
  216.             val.remove(path)
  217.             if len(val) == 1:
  218.                 self._inode_path_map[inode] = next(iter(val))
  219.         else:
  220.             del self._inode_path_map[inode]
  221.  
  222.     def symlink(self, inode_p, name, target, ctx):
  223.         name = fsdecode(name)
  224.         target = fsdecode(target)
  225.         parent = self._inode_to_path(inode_p)
  226.         path = os.path.join(parent, name)
  227.         if path.endswith(".uid"):
  228.             raise FUSEError(errno.EPERM)
  229.         try:
  230.             os.symlink(target, path)
  231.             os.chown(path, ctx.uid, ctx.gid, follow_symlinks=False)
  232.         except OSError as exc:
  233.             raise FUSEError(exc.errno)
  234.         stat = os.lstat(path)
  235.         self._add_path(stat.st_ino, path)
  236.         return self.getattr(stat.st_ino)
  237.  
  238.     def rename(self, inode_p_old, name_old, inode_p_new, name_new, ctx):
  239.         name_old = fsdecode(name_old)
  240.         name_new = fsdecode(name_new)
  241.         parent_old = self._inode_to_path(inode_p_old)
  242.         parent_new = self._inode_to_path(inode_p_new)
  243.         path_old = os.path.join(parent_old, name_old)
  244.         path_new = os.path.join(parent_new, name_new)
  245.         if path_new.endswith(".uid"):
  246.             raise FUSEError(errno.EPERM)
  247.         try:
  248.             os.rename(path_old, path_new)
  249.             inode = os.lstat(path_new).st_ino
  250.         except OSError as exc:
  251.             raise FUSEError(exc.errno)
  252.         if inode not in self._lookup_cnt:
  253.             return
  254.  
  255.         val = self._inode_path_map[inode]
  256.         if isinstance(val, set):
  257.             assert len(val) > 1
  258.             val.add(path_new)
  259.             val.remove(path_old)
  260.         else:
  261.             assert val == path_old
  262.             self._inode_path_map[inode] = path_new
  263.  
  264.     def link(self, inode, new_inode_p, new_name, ctx):
  265.         new_name = fsdecode(new_name)
  266.         parent = self._inode_to_path(new_inode_p)
  267.         path = os.path.join(parent, new_name)
  268.         try:
  269.             os.link(self._inode_to_path(inode), path, follow_symlinks=False)
  270.         except OSError as exc:
  271.             raise FUSEError(exc.errno)
  272.         self._add_path(inode, path)
  273.         return self.getattr(inode)
  274.  
  275.     def setattr(self, inode, attr, fields, fh, ctx):
  276.         # We use the f* functions if possible so that we can handle
  277.         # a setattr() call for an inode without associated directory
  278.         # handle.
  279.         if fh is None:
  280.             path_or_fh = self._inode_to_path(inode)
  281.             truncate = os.truncate
  282.             chmod = os.chmod
  283.             chown = os.chown
  284.             stat = os.lstat
  285.         else:
  286.             path_or_fh = fh
  287.             truncate = os.ftruncate
  288.             chmod = os.fchmod
  289.             chown = os.fchown
  290.             stat = os.fstat
  291.  
  292.         try:
  293.             if fields.update_size:
  294.                 truncate(path_or_fh, attr.st_size)
  295.  
  296.             if fields.update_mode:
  297.                 # Under Linux, chmod always resolves symlinks so we should
  298.                 # actually never get a setattr() request for a symbolic
  299.                 # link.
  300.                 assert not stat_m.S_ISLNK(attr.st_mode)
  301.                 chmod(path_or_fh, stat_m.S_IMODE(attr.st_mode))
  302.  
  303.             if fields.update_uid:
  304.                 chown(path_or_fh, attr.st_uid, -1, follow_symlinks=False)
  305.  
  306.             if fields.update_gid:
  307.                 chown(path_or_fh, -1, attr.st_gid, follow_symlinks=False)
  308.  
  309.             if fields.update_atime and fields.update_mtime:
  310.                 # utime accepts both paths and file descriptiors
  311.                 os.utime(path_or_fh, None, follow_symlinks=False,
  312.                          ns=(attr.st_atime_ns, attr.st_mtime_ns))
  313.             elif fields.update_atime or fields.update_mtime:
  314.                 # We can only set both values, so we first need to retrieve the
  315.                 # one that we shouldn't be changing.
  316.                 oldstat = stat(path_or_fh)
  317.                 if not fields.update_atime:
  318.                     attr.st_atime_ns = oldstat.st_atime_ns
  319.                 else:
  320.                     attr.st_mtime_ns = oldstat.st_mtime_ns
  321.                 os.utime(path_or_fh, None, follow_symlinks=False,
  322.                          ns=(attr.st_atime_ns, attr.st_mtime_ns))
  323.  
  324.         except OSError as exc:
  325.             raise FUSEError(exc.errno)
  326.  
  327.         return self.getattr(inode)
  328.  
  329.     def mknod(self, inode_p, name, mode, rdev, ctx):
  330.         path = os.path.join(self._inode_to_path(inode_p), fsdecode(name))
  331.         if path.endswith(".uid"):
  332.             raise FUSEError(errno.EPERM)
  333.         try:
  334.             os.mknod(path, mode=(mode & ~ctx.umask), device=rdev)
  335.             os.chown(path, ctx.uid, ctx.gid)
  336.         except OSError as exc:
  337.             raise FUSEError(exc.errno)
  338.         attr = self._getattr(path=path)
  339.         self._add_path(attr.st_ino, path)
  340.         return attr
  341.  
  342.     def mkdir(self, inode_p, name, mode, ctx):
  343.         path = os.path.join(self._inode_to_path(inode_p), fsdecode(name))
  344.         try:
  345.             os.mkdir(path, mode=(mode & ~ctx.umask))
  346.             os.chown(path, ctx.uid, ctx.gid)
  347.         except OSError as exc:
  348.             raise FUSEError(exc.errno)
  349.         attr = self._getattr(path=path)
  350.         self._add_path(attr.st_ino, path)
  351.         return attr
  352.  
  353.     def statfs(self, ctx):
  354.         root = self._inode_path_map[llfuse.ROOT_INODE]
  355.         stat_ = llfuse.StatvfsData()
  356.         try:
  357.             statfs = os.statvfs(root)
  358.         except OSError as exc:
  359.             raise FUSEError(exc.errno)
  360.         for attr in ('f_bsize', 'f_frsize', 'f_blocks', 'f_bfree', 'f_bavail',
  361.                      'f_files', 'f_ffree', 'f_favail'):
  362.             setattr(stat_, attr, getattr(statfs, attr))
  363.         stat_.f_namemax = statfs.f_namemax - (len(root)+1)
  364.         return stat_
  365.  
  366.     def open(self, inode, flags, ctx):
  367.         if inode in self._inode_fd_map:
  368.             fd = self._inode_fd_map[inode]
  369.             self._fd_open_count[fd] += 1
  370.             return fd
  371.         assert flags & os.O_CREAT == 0
  372.         try:
  373.             fd = os.open(self._inode_to_path(inode), flags)
  374.         except OSError as exc:
  375.             raise FUSEError(exc.errno)
  376.         self._inode_fd_map[inode] = fd
  377.         self._fd_inode_map[fd] = inode
  378.         self._fd_open_count[fd] = 1
  379.         return fd
  380.  
  381.     def create(self, inode_p, name, mode, flags, ctx):
  382.         path = os.path.join(self._inode_to_path(inode_p), fsdecode(name))
  383.         if path.endswith(".uid"):
  384.             raise FUSEError(errno.EPERM)
  385.         try:
  386.             fd = os.open(path, flags | os.O_CREAT | os.O_TRUNC)
  387.         except OSError as exc:
  388.             raise FUSEError(exc.errno)
  389.         attr = self._getattr(fd=fd)
  390.         self._add_path(attr.st_ino, path)
  391.         self._inode_fd_map[attr.st_ino] = fd
  392.         self._fd_inode_map[fd] = attr.st_ino
  393.         self._fd_open_count[fd] = 1
  394.         return (fd, attr)
  395.  
  396.     def read(self, fd, offset, length):
  397.         os.lseek(fd, offset, os.SEEK_SET)
  398.         return os.read(fd, length)
  399.  
  400.     def write(self, fd, offset, buf):
  401.         os.lseek(fd, offset, os.SEEK_SET)
  402.         return os.write(fd, buf)
  403.  
  404.     def release(self, fd):
  405.         if self._fd_open_count[fd] > 1:
  406.             self._fd_open_count[fd] -= 1
  407.             return
  408.  
  409.         del self._fd_open_count[fd]
  410.         inode = self._fd_inode_map[fd]
  411.         del self._inode_fd_map[inode]
  412.         del self._fd_inode_map[fd]
  413.         try:
  414.             os.close(fd)
  415.         except OSError as exc:
  416.             raise FUSEError(exc.errno)
  417.  
  418. def init_logging(debug=False):
  419.     formatter = logging.Formatter('%(asctime)s.%(msecs)03d %(threadName)s: '
  420.                                   '[%(name)s] %(message)s', datefmt="%Y-%m-%d %H:%M:%S")
  421.     handler = logging.StreamHandler()
  422.     handler.setFormatter(formatter)
  423.     root_logger = logging.getLogger()
  424.     if debug:
  425.         handler.setLevel(logging.DEBUG)
  426.         root_logger.setLevel(logging.DEBUG)
  427.     else:
  428.         handler.setLevel(logging.INFO)
  429.         root_logger.setLevel(logging.INFO)
  430.     root_logger.addHandler(handler)
  431.  
  432.  
  433. def parse_args(args):
  434.     '''Parse command line'''
  435.  
  436.     parser = ArgumentParser()
  437.  
  438.     parser.add_argument('source', type=str,
  439.                         help='Directory tree to mirror')
  440.     parser.add_argument('mountpoint', type=str,
  441.                         help='Where to mount the file system')
  442.     parser.add_argument('--single', action='store_true', default=False,
  443.                         help='Run single threaded')
  444.     parser.add_argument('--debug', action='store_true', default=False,
  445.                         help='Enable debugging output')
  446.     parser.add_argument('--debug-fuse', action='store_true', default=False,
  447.                         help='Enable FUSE debugging output')
  448.  
  449.     return parser.parse_args(args)
  450.  
  451.  
  452. def main():
  453.     options = parse_args(sys.argv[1:])
  454.     init_logging(options.debug)
  455.     operations = Operations(options.source)
  456.  
  457.     log.debug('Mounting...')
  458.     fuse_options = set(llfuse.default_options)
  459.     fuse_options.add('fsname=passthroughfs')
  460.     if options.debug_fuse:
  461.         fuse_options.add('debug')
  462.     llfuse.init(operations, options.mountpoint, fuse_options)
  463.  
  464.     try:
  465.         log.debug('Entering main loop..')
  466.         if options.single:
  467.             llfuse.main(workers=1)
  468.         else:
  469.             llfuse.main()
  470.     except:
  471.         llfuse.close(unmount=False)
  472.         raise
  473.  
  474.     log.debug('Unmounting..')
  475.     llfuse.close()
  476.  
  477. if __name__ == '__main__':
  478.     main()
  479.  
Advertisement
Add Comment
Please, Sign In to add comment