Linux sagir-us1.hostever.us 5.14.0-570.51.1.el9_6.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Oct 8 09:41:34 EDT 2025 x86_64
LiteSpeed
Server IP : 104.247.108.91 & Your IP : 216.73.216.105
Domains : 74 Domain
User : georgeto
Terminal
Auto Root
Create File
Create Folder
Localroot Suggester
Backdoor Destroyer
Readme
/
usr /
share /
doc /
python3-llfuse /
html /
.doctrees /
Delete
Unzip
Name
Size
Permission
Date
Action
about.doctree
10.45
KB
-rw-r--r--
2021-02-01 20:07
changes.doctree
78.18
KB
-rw-r--r--
2021-02-01 20:07
data.doctree
45.45
KB
-rw-r--r--
2021-02-01 20:22
environment.pickle
1.64
MB
-rw-r--r--
2021-02-01 20:22
example.doctree
79.57
KB
-rw-r--r--
2021-02-01 20:07
fuse_api.doctree
34.19
KB
-rw-r--r--
2021-02-01 20:22
general.doctree
20.07
KB
-rw-r--r--
2021-02-01 20:07
gotchas.doctree
5.36
KB
-rw-r--r--
2021-02-01 20:07
index.doctree
4.38
KB
-rw-r--r--
2021-02-01 20:07
install.doctree
19.21
KB
-rw-r--r--
2021-02-01 20:07
lock.doctree
18.36
KB
-rw-r--r--
2021-02-01 20:22
operations.doctree
146.99
KB
-rw-r--r--
2021-02-01 20:22
util.doctree
20.38
KB
-rw-r--r--
2021-02-01 20:22
Save
Rename
���3 �sphinx.addnodes��document���)��}�(� rawsource�� ��children�]�(�docutils.nodes��target���)��}�(h�.. _example file system:�h]�� attributes�}�(�ids�]��classes�]��names�]��dupnames�]��backrefs�]��refid��example-file-system�u�tagname�h �line�K�parent�hhh�source��*/home/user/w/python-llfuse/rst/example.rst�ubh �section���)��}�(hhh]�(h �title���)��}�(h�Example File Systems�h]�h �Text����Example File Systems�����}�(hh,h h*hhh!NhNubah}�(h]�h]�h]�h]�h]�uhh(h h%hhh!h"hKubh � paragraph���)��}�(h��Python-LLFUSE comes with several example file systems in the :file:`examples` directory of the release tarball. For completeness, these examples are also included here.�h]�(h/�=Python-LLFUSE comes with several example file systems in the �����}�(h�=Python-LLFUSE comes with several example file systems in the �h h<hhh!NhNubh �literal���)��}�(h�:file:`examples`�h]�h/�examples�����}�(h�examples�h hGubah}�(h]�h]��file�ah]�h]�h]��role��file�uhhEh h<ubh/�[ directory of the release tarball. For completeness, these examples are also included here.�����}�(h�[ directory of the release tarball. For completeness, these examples are also included here.�h h<hhh!NhNubeh}�(h]�h]�h]�h]�h]�uhh:h!h"hKh h%hhubh$)��}�(hhh]�(h))��}�(h�"Single-file, Read-only File System�h]�h/�"Single-file, Read-only File System�����}�(hhih hghhh!NhNubah}�(h]�h]�h]�h]�h]�uhh(h hdhhh!h"hKubh;)��}�(h�'(shipped as :file:`examples/lltest.py`)�h]�(h/�(shipped as �����}�(h�(shipped as �h huhhh!NhNubhF)��}�(h�:file:`examples/lltest.py`�h]�h/�examples/lltest.py�����}�(h�examples/lltest.py�h h~ubah}�(h]�h]��file�ah]�h]�h]��role��file�uhhEh huubh/�)�����}�(h�)�h huhhh!NhNubeh}�(h]�h]�h]�h]�h]�uhh:h!h"hKh hdhhubh � literal_block���)��}�(hX� #!/usr/bin/env python3 ''' lltest.py - Example file system for Python-LLFUSE. This program presents a static file system containing a single file. It is compatible with both Python 2.x and 3.x. Based on an example from Gerion Entrup. Copyright © 2015 Nikolaus Rath <Nikolaus.org> Copyright © 2015 Gerion Entrup. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ''' import os import sys # If we are running from the Python-LLFUSE source directory, try # to load the module from there first. basedir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '..')) if (os.path.exists(os.path.join(basedir, 'setup.py')) and os.path.exists(os.path.join(basedir, 'src', 'llfuse.pyx'))): sys.path.insert(0, os.path.join(basedir, 'src')) from argparse import ArgumentParser import stat import logging import errno import llfuse try: import faulthandler except ImportError: pass else: faulthandler.enable() log = logging.getLogger(__name__) class TestFs(llfuse.Operations): def __init__(self): super().__init__() self.hello_name = b"message" self.hello_inode = llfuse.ROOT_INODE+1 self.hello_data = b"hello world\n" def getattr(self, inode, ctx=None): entry = llfuse.EntryAttributes() if inode == llfuse.ROOT_INODE: entry.st_mode = (stat.S_IFDIR | 0o755) entry.st_size = 0 elif inode == self.hello_inode: entry.st_mode = (stat.S_IFREG | 0o644) entry.st_size = len(self.hello_data) else: raise llfuse.FUSEError(errno.ENOENT) stamp = int(1438467123.985654 * 1e9) entry.st_atime_ns = stamp entry.st_ctime_ns = stamp entry.st_mtime_ns = stamp entry.st_gid = os.getgid() entry.st_uid = os.getuid() entry.st_ino = inode return entry def lookup(self, parent_inode, name, ctx=None): if parent_inode != llfuse.ROOT_INODE or name != self.hello_name: raise llfuse.FUSEError(errno.ENOENT) return self.getattr(self.hello_inode) def opendir(self, inode, ctx): if inode != llfuse.ROOT_INODE: raise llfuse.FUSEError(errno.ENOENT) return inode def readdir(self, fh, off): assert fh == llfuse.ROOT_INODE # only one entry if off == 0: yield (self.hello_name, self.getattr(self.hello_inode), 1) def open(self, inode, flags, ctx): if inode != self.hello_inode: raise llfuse.FUSEError(errno.ENOENT) if flags & os.O_RDWR or flags & os.O_WRONLY: raise llfuse.FUSEError(errno.EACCES) return inode def read(self, fh, off, size): assert fh == self.hello_inode return self.hello_data[off:off+size] def init_logging(debug=False): formatter = logging.Formatter('%(asctime)s.%(msecs)03d %(threadName)s: ' '[%(name)s] %(message)s', datefmt="%Y-%m-%d %H:%M:%S") handler = logging.StreamHandler() handler.setFormatter(formatter) root_logger = logging.getLogger() if debug: handler.setLevel(logging.DEBUG) root_logger.setLevel(logging.DEBUG) else: handler.setLevel(logging.INFO) root_logger.setLevel(logging.INFO) root_logger.addHandler(handler) def parse_args(): '''Parse command line''' parser = ArgumentParser() parser.add_argument('mountpoint', type=str, help='Where to mount the file system') parser.add_argument('--debug', action='store_true', default=False, help='Enable debugging output') parser.add_argument('--debug-fuse', action='store_true', default=False, help='Enable FUSE debugging output') return parser.parse_args() def main(): options = parse_args() init_logging(options.debug) testfs = TestFs() fuse_options = set(llfuse.default_options) fuse_options.add('fsname=lltest') if options.debug_fuse: fuse_options.add('debug') llfuse.init(testfs, options.mountpoint, fuse_options) try: llfuse.main(workers=1) except: llfuse.close(unmount=False) raise llfuse.close() if __name__ == '__main__': main() �h]�h/X� #!/usr/bin/env python3 ''' lltest.py - Example file system for Python-LLFUSE. This program presents a static file system containing a single file. It is compatible with both Python 2.x and 3.x. Based on an example from Gerion Entrup. Copyright © 2015 Nikolaus Rath <Nikolaus.org> Copyright © 2015 Gerion Entrup. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ''' import os import sys # If we are running from the Python-LLFUSE source directory, try # to load the module from there first. basedir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '..')) if (os.path.exists(os.path.join(basedir, 'setup.py')) and os.path.exists(os.path.join(basedir, 'src', 'llfuse.pyx'))): sys.path.insert(0, os.path.join(basedir, 'src')) from argparse import ArgumentParser import stat import logging import errno import llfuse try: import faulthandler except ImportError: pass else: faulthandler.enable() log = logging.getLogger(__name__) class TestFs(llfuse.Operations): def __init__(self): super().__init__() self.hello_name = b"message" self.hello_inode = llfuse.ROOT_INODE+1 self.hello_data = b"hello world\n" def getattr(self, inode, ctx=None): entry = llfuse.EntryAttributes() if inode == llfuse.ROOT_INODE: entry.st_mode = (stat.S_IFDIR | 0o755) entry.st_size = 0 elif inode == self.hello_inode: entry.st_mode = (stat.S_IFREG | 0o644) entry.st_size = len(self.hello_data) else: raise llfuse.FUSEError(errno.ENOENT) stamp = int(1438467123.985654 * 1e9) entry.st_atime_ns = stamp entry.st_ctime_ns = stamp entry.st_mtime_ns = stamp entry.st_gid = os.getgid() entry.st_uid = os.getuid() entry.st_ino = inode return entry def lookup(self, parent_inode, name, ctx=None): if parent_inode != llfuse.ROOT_INODE or name != self.hello_name: raise llfuse.FUSEError(errno.ENOENT) return self.getattr(self.hello_inode) def opendir(self, inode, ctx): if inode != llfuse.ROOT_INODE: raise llfuse.FUSEError(errno.ENOENT) return inode def readdir(self, fh, off): assert fh == llfuse.ROOT_INODE # only one entry if off == 0: yield (self.hello_name, self.getattr(self.hello_inode), 1) def open(self, inode, flags, ctx): if inode != self.hello_inode: raise llfuse.FUSEError(errno.ENOENT) if flags & os.O_RDWR or flags & os.O_WRONLY: raise llfuse.FUSEError(errno.EACCES) return inode def read(self, fh, off, size): assert fh == self.hello_inode return self.hello_data[off:off+size] def init_logging(debug=False): formatter = logging.Formatter('%(asctime)s.%(msecs)03d %(threadName)s: ' '[%(name)s] %(message)s', datefmt="%Y-%m-%d %H:%M:%S") handler = logging.StreamHandler() handler.setFormatter(formatter) root_logger = logging.getLogger() if debug: handler.setLevel(logging.DEBUG) root_logger.setLevel(logging.DEBUG) else: handler.setLevel(logging.INFO) root_logger.setLevel(logging.INFO) root_logger.addHandler(handler) def parse_args(): '''Parse command line''' parser = ArgumentParser() parser.add_argument('mountpoint', type=str, help='Where to mount the file system') parser.add_argument('--debug', action='store_true', default=False, help='Enable debugging output') parser.add_argument('--debug-fuse', action='store_true', default=False, help='Enable FUSE debugging output') return parser.parse_args() def main(): options = parse_args() init_logging(options.debug) testfs = TestFs() fuse_options = set(llfuse.default_options) fuse_options.add('fsname=lltest') if options.debug_fuse: fuse_options.add('debug') llfuse.init(testfs, options.mountpoint, fuse_options) try: llfuse.main(workers=1) except: llfuse.close(unmount=False) raise llfuse.close() if __name__ == '__main__': main() �����}�(hhh h�ubah}�(h]�h]�h]�h]�h]��source��-/home/user/w/python-llfuse/examples/lltest.py�� xml:space��preserve��force���language��python��linenos���highlight_args�}��linenostart�Ksuhh�h!h"hKh hdhhubeh}�(h]��!single-file-read-only-file-system�ah]�h]��"single-file, read-only file system�ah]�h]�uhh#h h%hhh!h"hKubh$)��}�(hhh]�(h))��}�(h�In-memory File System�h]�h/�In-memory File System�����}�(hh�h h�hhh!NhNubah}�(h]�h]�h]�h]�h]�uhh(h h�hhh!h"hKubh;)��}�(h�&(shipped as :file:`examples/tmpfs.py`)�h]�(h/�(shipped as �����}�(h�(shipped as �h h�hhh!NhNubhF)��}�(h�:file:`examples/tmpfs.py`�h]�h/�examples/tmpfs.py�����}�(h�examples/tmpfs.py�h h�ubah}�(h]�h]��file�ah]�h]�h]��role��file�uhhEh h�ubh/�)�����}�(hh�h h�hhh!NhNubeh}�(h]�h]�h]�h]�h]�uhh:h!h"hKh h�hhubh�)��}�(hX�= #!/usr/bin/env python3 ''' tmpfs.py - Example file system for Python-LLFUSE. This file system stores all data in memory. It is compatible with both Python 2.x and 3.x. Copyright © 2013 Nikolaus Rath <Nikolaus.org> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ''' import os import sys # If we are running from the Python-LLFUSE source directory, try # to load the module from there first. basedir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '..')) if (os.path.exists(os.path.join(basedir, 'setup.py')) and os.path.exists(os.path.join(basedir, 'src', 'llfuse.pyx'))): sys.path.insert(0, os.path.join(basedir, 'src')) import llfuse import errno import stat from time import time import sqlite3 import logging from collections import defaultdict from llfuse import FUSEError from argparse import ArgumentParser try: import faulthandler except ImportError: pass else: faulthandler.enable() log = logging.getLogger() class Operations(llfuse.Operations): '''An example filesystem that stores all data in memory This is a very simple implementation with terrible performance. Don't try to store significant amounts of data. Also, there are some other flaws that have not been fixed to keep the code easier to understand: * atime, mtime and ctime are not updated * generation numbers are not supported ''' def __init__(self): super().__init__() self.db = sqlite3.connect(':memory:') self.db.text_factory = str self.db.row_factory = sqlite3.Row self.cursor = self.db.cursor() self.inode_open_count = defaultdict(int) self.init_tables() def init_tables(self): '''Initialize file system tables''' self.cursor.execute(""" CREATE TABLE inodes ( id INTEGER PRIMARY KEY, uid INT NOT NULL, gid INT NOT NULL, mode INT NOT NULL, mtime_ns INT NOT NULL, atime_ns INT NOT NULL, ctime_ns INT NOT NULL, target BLOB(256) , size INT NOT NULL DEFAULT 0, rdev INT NOT NULL DEFAULT 0, data BLOB ) """) self.cursor.execute(""" CREATE TABLE contents ( rowid INTEGER PRIMARY KEY AUTOINCREMENT, name BLOB(256) NOT NULL, inode INT NOT NULL REFERENCES inodes(id), parent_inode INT NOT NULL REFERENCES inodes(id), UNIQUE (name, parent_inode) )""") # Insert root directory now_ns = int(time() * 1e9) self.cursor.execute("INSERT INTO inodes (id,mode,uid,gid,mtime_ns,atime_ns,ctime_ns) " "VALUES (?,?,?,?,?,?,?)", (llfuse.ROOT_INODE, stat.S_IFDIR | stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH, os.getuid(), os.getgid(), now_ns, now_ns, now_ns)) self.cursor.execute("INSERT INTO contents (name, parent_inode, inode) VALUES (?,?,?)", (b'..', llfuse.ROOT_INODE, llfuse.ROOT_INODE)) def get_row(self, *a, **kw): self.cursor.execute(*a, **kw) try: row = next(self.cursor) except StopIteration: raise NoSuchRowError() try: next(self.cursor) except StopIteration: pass else: raise NoUniqueValueError() return row def lookup(self, inode_p, name, ctx=None): if name == '.': inode = inode_p elif name == '..': inode = self.get_row("SELECT * FROM contents WHERE inode=?", (inode_p,))['parent_inode'] else: try: inode = self.get_row("SELECT * FROM contents WHERE name=? AND parent_inode=?", (name, inode_p))['inode'] except NoSuchRowError: raise(llfuse.FUSEError(errno.ENOENT)) return self.getattr(inode, ctx) def getattr(self, inode, ctx=None): row = self.get_row('SELECT * FROM inodes WHERE id=?', (inode,)) entry = llfuse.EntryAttributes() entry.st_ino = inode entry.generation = 0 entry.entry_timeout = 300 entry.attr_timeout = 300 entry.st_mode = row['mode'] entry.st_nlink = self.get_row("SELECT COUNT(inode) FROM contents WHERE inode=?", (inode,))[0] entry.st_uid = row['uid'] entry.st_gid = row['gid'] entry.st_rdev = row['rdev'] entry.st_size = row['size'] entry.st_blksize = 512 entry.st_blocks = 1 entry.st_atime_ns = row['atime_ns'] entry.st_mtime_ns = row['mtime_ns'] entry.st_ctime_ns = row['ctime_ns'] return entry def readlink(self, inode, ctx): return self.get_row('SELECT * FROM inodes WHERE id=?', (inode,))['target'] def opendir(self, inode, ctx): return inode def readdir(self, inode, off): if off == 0: off = -1 cursor2 = self.db.cursor() cursor2.execute("SELECT * FROM contents WHERE parent_inode=? " 'AND rowid > ? ORDER BY rowid', (inode, off)) for row in cursor2: yield (row['name'], self.getattr(row['inode']), row['rowid']) def unlink(self, inode_p, name,ctx): entry = self.lookup(inode_p, name) if stat.S_ISDIR(entry.st_mode): raise llfuse.FUSEError(errno.EISDIR) self._remove(inode_p, name, entry) def rmdir(self, inode_p, name, ctx): entry = self.lookup(inode_p, name) if not stat.S_ISDIR(entry.st_mode): raise llfuse.FUSEError(errno.ENOTDIR) self._remove(inode_p, name, entry) def _remove(self, inode_p, name, entry): if self.get_row("SELECT COUNT(inode) FROM contents WHERE parent_inode=?", (entry.st_ino,))[0] > 0: raise llfuse.FUSEError(errno.ENOTEMPTY) self.cursor.execute("DELETE FROM contents WHERE name=? AND parent_inode=?", (name, inode_p)) if entry.st_nlink == 1 and entry.st_ino not in self.inode_open_count: self.cursor.execute("DELETE FROM inodes WHERE id=?", (entry.st_ino,)) def symlink(self, inode_p, name, target, ctx): mode = (stat.S_IFLNK | stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH) return self._create(inode_p, name, mode, ctx, target=target) def rename(self, inode_p_old, name_old, inode_p_new, name_new, ctx): entry_old = self.lookup(inode_p_old, name_old) try: entry_new = self.lookup(inode_p_new, name_new) except llfuse.FUSEError as exc: if exc.errno != errno.ENOENT: raise target_exists = False else: target_exists = True if target_exists: self._replace(inode_p_old, name_old, inode_p_new, name_new, entry_old, entry_new) else: self.cursor.execute("UPDATE contents SET name=?, parent_inode=? WHERE name=? " "AND parent_inode=?", (name_new, inode_p_new, name_old, inode_p_old)) def _replace(self, inode_p_old, name_old, inode_p_new, name_new, entry_old, entry_new): if self.get_row("SELECT COUNT(inode) FROM contents WHERE parent_inode=?", (entry_new.st_ino,))[0] > 0: raise llfuse.FUSEError(errno.ENOTEMPTY) self.cursor.execute("UPDATE contents SET inode=? WHERE name=? AND parent_inode=?", (entry_old.st_ino, name_new, inode_p_new)) self.db.execute('DELETE FROM contents WHERE name=? AND parent_inode=?', (name_old, inode_p_old)) if entry_new.st_nlink == 1 and entry_new.st_ino not in self.inode_open_count: self.cursor.execute("DELETE FROM inodes WHERE id=?", (entry_new.st_ino,)) def link(self, inode, new_inode_p, new_name, ctx): entry_p = self.getattr(new_inode_p) if entry_p.st_nlink == 0: log.warn('Attempted to create entry %s with unlinked parent %d', new_name, new_inode_p) raise FUSEError(errno.EINVAL) self.cursor.execute("INSERT INTO contents (name, inode, parent_inode) VALUES(?,?,?)", (new_name, inode, new_inode_p)) return self.getattr(inode) def setattr(self, inode, attr, fields, fh, ctx): if fields.update_size: data = self.get_row('SELECT data FROM inodes WHERE id=?', (inode,))[0] if data is None: data = b'' if len(data) < attr.st_size: data = data + b'\0' * (attr.st_size - len(data)) else: data = data[:attr.st_size] self.cursor.execute('UPDATE inodes SET data=?, size=? WHERE id=?', (memoryview(data), attr.st_size, inode)) if fields.update_mode: self.cursor.execute('UPDATE inodes SET mode=? WHERE id=?', (attr.st_mode, inode)) if fields.update_uid: self.cursor.execute('UPDATE inodes SET uid=? WHERE id=?', (attr.st_uid, inode)) if fields.update_gid: self.cursor.execute('UPDATE inodes SET gid=? WHERE id=?', (attr.st_gid, inode)) if fields.update_atime: self.cursor.execute('UPDATE inodes SET atime_ns=? WHERE id=?', (attr.st_atime_ns, inode)) if fields.update_mtime: self.cursor.execute('UPDATE inodes SET mtime_ns=? WHERE id=?', (attr.st_mtime_ns, inode)) return self.getattr(inode) def mknod(self, inode_p, name, mode, rdev, ctx): return self._create(inode_p, name, mode, ctx, rdev=rdev) def mkdir(self, inode_p, name, mode, ctx): return self._create(inode_p, name, mode, ctx) def statfs(self, ctx): stat_ = llfuse.StatvfsData() stat_.f_bsize = 512 stat_.f_frsize = 512 size = self.get_row('SELECT SUM(size) FROM inodes')[0] stat_.f_blocks = size // stat_.f_frsize stat_.f_bfree = max(size // stat_.f_frsize, 1024) stat_.f_bavail = stat_.f_bfree inodes = self.get_row('SELECT COUNT(id) FROM inodes')[0] stat_.f_files = inodes stat_.f_ffree = max(inodes , 100) stat_.f_favail = stat_.f_ffree return stat_ def open(self, inode, flags, ctx): # Yeah, unused arguments #pylint: disable=W0613 self.inode_open_count[inode] += 1 # Use inodes as a file handles return inode def access(self, inode, mode, ctx): # Yeah, could be a function and has unused arguments #pylint: disable=R0201,W0613 return True def create(self, inode_parent, name, mode, flags, ctx): #pylint: disable=W0612 entry = self._create(inode_parent, name, mode, ctx) self.inode_open_count[entry.st_ino] += 1 return (entry.st_ino, entry) def _create(self, inode_p, name, mode, ctx, rdev=0, target=None): if self.getattr(inode_p).st_nlink == 0: log.warn('Attempted to create entry %s with unlinked parent %d', name, inode_p) raise FUSEError(errno.EINVAL) now_ns = int(time() * 1e9) self.cursor.execute('INSERT INTO inodes (uid, gid, mode, mtime_ns, atime_ns, ' 'ctime_ns, target, rdev) VALUES(?, ?, ?, ?, ?, ?, ?, ?)', (ctx.uid, ctx.gid, mode, now_ns, now_ns, now_ns, target, rdev)) inode = self.cursor.lastrowid self.db.execute("INSERT INTO contents(name, inode, parent_inode) VALUES(?,?,?)", (name, inode, inode_p)) return self.getattr(inode) def read(self, fh, offset, length): data = self.get_row('SELECT data FROM inodes WHERE id=?', (fh,))[0] if data is None: data = b'' return data[offset:offset+length] def write(self, fh, offset, buf): data = self.get_row('SELECT data FROM inodes WHERE id=?', (fh,))[0] if data is None: data = b'' data = data[:offset] + buf + data[offset+len(buf):] self.cursor.execute('UPDATE inodes SET data=?, size=? WHERE id=?', (memoryview(data), len(data), fh)) return len(buf) def release(self, fh): self.inode_open_count[fh] -= 1 if self.inode_open_count[fh] == 0: del self.inode_open_count[fh] if self.getattr(fh).st_nlink == 0: self.cursor.execute("DELETE FROM inodes WHERE id=?", (fh,)) class NoUniqueValueError(Exception): def __str__(self): return 'Query generated more than 1 result row' class NoSuchRowError(Exception): def __str__(self): return 'Query produced 0 result rows' def init_logging(debug=False): formatter = logging.Formatter('%(asctime)s.%(msecs)03d %(threadName)s: ' '[%(name)s] %(message)s', datefmt="%Y-%m-%d %H:%M:%S") handler = logging.StreamHandler() handler.setFormatter(formatter) root_logger = logging.getLogger() if debug: handler.setLevel(logging.DEBUG) root_logger.setLevel(logging.DEBUG) else: handler.setLevel(logging.INFO) root_logger.setLevel(logging.INFO) root_logger.addHandler(handler) def parse_args(): '''Parse command line''' parser = ArgumentParser() parser.add_argument('mountpoint', type=str, help='Where to mount the file system') parser.add_argument('--debug', action='store_true', default=False, help='Enable debugging output') parser.add_argument('--debug-fuse', action='store_true', default=False, help='Enable FUSE debugging output') return parser.parse_args() if __name__ == '__main__': options = parse_args() init_logging(options.debug) operations = Operations() fuse_options = set(llfuse.default_options) fuse_options.add('fsname=tmpfs') fuse_options.discard('default_permissions') if options.debug_fuse: fuse_options.add('debug') llfuse.init(operations, options.mountpoint, fuse_options) # sqlite3 does not support multithreading try: llfuse.main(workers=1) except: llfuse.close(unmount=False) raise llfuse.close() �h]�h/X�= #!/usr/bin/env python3 ''' tmpfs.py - Example file system for Python-LLFUSE. This file system stores all data in memory. It is compatible with both Python 2.x and 3.x. Copyright © 2013 Nikolaus Rath <Nikolaus.org> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ''' import os import sys # If we are running from the Python-LLFUSE source directory, try # to load the module from there first. basedir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '..')) if (os.path.exists(os.path.join(basedir, 'setup.py')) and os.path.exists(os.path.join(basedir, 'src', 'llfuse.pyx'))): sys.path.insert(0, os.path.join(basedir, 'src')) import llfuse import errno import stat from time import time import sqlite3 import logging from collections import defaultdict from llfuse import FUSEError from argparse import ArgumentParser try: import faulthandler except ImportError: pass else: faulthandler.enable() log = logging.getLogger() class Operations(llfuse.Operations): '''An example filesystem that stores all data in memory This is a very simple implementation with terrible performance. Don't try to store significant amounts of data. Also, there are some other flaws that have not been fixed to keep the code easier to understand: * atime, mtime and ctime are not updated * generation numbers are not supported ''' def __init__(self): super().__init__() self.db = sqlite3.connect(':memory:') self.db.text_factory = str self.db.row_factory = sqlite3.Row self.cursor = self.db.cursor() self.inode_open_count = defaultdict(int) self.init_tables() def init_tables(self): '''Initialize file system tables''' self.cursor.execute(""" CREATE TABLE inodes ( id INTEGER PRIMARY KEY, uid INT NOT NULL, gid INT NOT NULL, mode INT NOT NULL, mtime_ns INT NOT NULL, atime_ns INT NOT NULL, ctime_ns INT NOT NULL, target BLOB(256) , size INT NOT NULL DEFAULT 0, rdev INT NOT NULL DEFAULT 0, data BLOB ) """) self.cursor.execute(""" CREATE TABLE contents ( rowid INTEGER PRIMARY KEY AUTOINCREMENT, name BLOB(256) NOT NULL, inode INT NOT NULL REFERENCES inodes(id), parent_inode INT NOT NULL REFERENCES inodes(id), UNIQUE (name, parent_inode) )""") # Insert root directory now_ns = int(time() * 1e9) self.cursor.execute("INSERT INTO inodes (id,mode,uid,gid,mtime_ns,atime_ns,ctime_ns) " "VALUES (?,?,?,?,?,?,?)", (llfuse.ROOT_INODE, stat.S_IFDIR | stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH, os.getuid(), os.getgid(), now_ns, now_ns, now_ns)) self.cursor.execute("INSERT INTO contents (name, parent_inode, inode) VALUES (?,?,?)", (b'..', llfuse.ROOT_INODE, llfuse.ROOT_INODE)) def get_row(self, *a, **kw): self.cursor.execute(*a, **kw) try: row = next(self.cursor) except StopIteration: raise NoSuchRowError() try: next(self.cursor) except StopIteration: pass else: raise NoUniqueValueError() return row def lookup(self, inode_p, name, ctx=None): if name == '.': inode = inode_p elif name == '..': inode = self.get_row("SELECT * FROM contents WHERE inode=?", (inode_p,))['parent_inode'] else: try: inode = self.get_row("SELECT * FROM contents WHERE name=? AND parent_inode=?", (name, inode_p))['inode'] except NoSuchRowError: raise(llfuse.FUSEError(errno.ENOENT)) return self.getattr(inode, ctx) def getattr(self, inode, ctx=None): row = self.get_row('SELECT * FROM inodes WHERE id=?', (inode,)) entry = llfuse.EntryAttributes() entry.st_ino = inode entry.generation = 0 entry.entry_timeout = 300 entry.attr_timeout = 300 entry.st_mode = row['mode'] entry.st_nlink = self.get_row("SELECT COUNT(inode) FROM contents WHERE inode=?", (inode,))[0] entry.st_uid = row['uid'] entry.st_gid = row['gid'] entry.st_rdev = row['rdev'] entry.st_size = row['size'] entry.st_blksize = 512 entry.st_blocks = 1 entry.st_atime_ns = row['atime_ns'] entry.st_mtime_ns = row['mtime_ns'] entry.st_ctime_ns = row['ctime_ns'] return entry def readlink(self, inode, ctx): return self.get_row('SELECT * FROM inodes WHERE id=?', (inode,))['target'] def opendir(self, inode, ctx): return inode def readdir(self, inode, off): if off == 0: off = -1 cursor2 = self.db.cursor() cursor2.execute("SELECT * FROM contents WHERE parent_inode=? " 'AND rowid > ? ORDER BY rowid', (inode, off)) for row in cursor2: yield (row['name'], self.getattr(row['inode']), row['rowid']) def unlink(self, inode_p, name,ctx): entry = self.lookup(inode_p, name) if stat.S_ISDIR(entry.st_mode): raise llfuse.FUSEError(errno.EISDIR) self._remove(inode_p, name, entry) def rmdir(self, inode_p, name, ctx): entry = self.lookup(inode_p, name) if not stat.S_ISDIR(entry.st_mode): raise llfuse.FUSEError(errno.ENOTDIR) self._remove(inode_p, name, entry) def _remove(self, inode_p, name, entry): if self.get_row("SELECT COUNT(inode) FROM contents WHERE parent_inode=?", (entry.st_ino,))[0] > 0: raise llfuse.FUSEError(errno.ENOTEMPTY) self.cursor.execute("DELETE FROM contents WHERE name=? AND parent_inode=?", (name, inode_p)) if entry.st_nlink == 1 and entry.st_ino not in self.inode_open_count: self.cursor.execute("DELETE FROM inodes WHERE id=?", (entry.st_ino,)) def symlink(self, inode_p, name, target, ctx): mode = (stat.S_IFLNK | stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH) return self._create(inode_p, name, mode, ctx, target=target) def rename(self, inode_p_old, name_old, inode_p_new, name_new, ctx): entry_old = self.lookup(inode_p_old, name_old) try: entry_new = self.lookup(inode_p_new, name_new) except llfuse.FUSEError as exc: if exc.errno != errno.ENOENT: raise target_exists = False else: target_exists = True if target_exists: self._replace(inode_p_old, name_old, inode_p_new, name_new, entry_old, entry_new) else: self.cursor.execute("UPDATE contents SET name=?, parent_inode=? WHERE name=? " "AND parent_inode=?", (name_new, inode_p_new, name_old, inode_p_old)) def _replace(self, inode_p_old, name_old, inode_p_new, name_new, entry_old, entry_new): if self.get_row("SELECT COUNT(inode) FROM contents WHERE parent_inode=?", (entry_new.st_ino,))[0] > 0: raise llfuse.FUSEError(errno.ENOTEMPTY) self.cursor.execute("UPDATE contents SET inode=? WHERE name=? AND parent_inode=?", (entry_old.st_ino, name_new, inode_p_new)) self.db.execute('DELETE FROM contents WHERE name=? AND parent_inode=?', (name_old, inode_p_old)) if entry_new.st_nlink == 1 and entry_new.st_ino not in self.inode_open_count: self.cursor.execute("DELETE FROM inodes WHERE id=?", (entry_new.st_ino,)) def link(self, inode, new_inode_p, new_name, ctx): entry_p = self.getattr(new_inode_p) if entry_p.st_nlink == 0: log.warn('Attempted to create entry %s with unlinked parent %d', new_name, new_inode_p) raise FUSEError(errno.EINVAL) self.cursor.execute("INSERT INTO contents (name, inode, parent_inode) VALUES(?,?,?)", (new_name, inode, new_inode_p)) return self.getattr(inode) def setattr(self, inode, attr, fields, fh, ctx): if fields.update_size: data = self.get_row('SELECT data FROM inodes WHERE id=?', (inode,))[0] if data is None: data = b'' if len(data) < attr.st_size: data = data + b'\0' * (attr.st_size - len(data)) else: data = data[:attr.st_size] self.cursor.execute('UPDATE inodes SET data=?, size=? WHERE id=?', (memoryview(data), attr.st_size, inode)) if fields.update_mode: self.cursor.execute('UPDATE inodes SET mode=? WHERE id=?', (attr.st_mode, inode)) if fields.update_uid: self.cursor.execute('UPDATE inodes SET uid=? WHERE id=?', (attr.st_uid, inode)) if fields.update_gid: self.cursor.execute('UPDATE inodes SET gid=? WHERE id=?', (attr.st_gid, inode)) if fields.update_atime: self.cursor.execute('UPDATE inodes SET atime_ns=? WHERE id=?', (attr.st_atime_ns, inode)) if fields.update_mtime: self.cursor.execute('UPDATE inodes SET mtime_ns=? WHERE id=?', (attr.st_mtime_ns, inode)) return self.getattr(inode) def mknod(self, inode_p, name, mode, rdev, ctx): return self._create(inode_p, name, mode, ctx, rdev=rdev) def mkdir(self, inode_p, name, mode, ctx): return self._create(inode_p, name, mode, ctx) def statfs(self, ctx): stat_ = llfuse.StatvfsData() stat_.f_bsize = 512 stat_.f_frsize = 512 size = self.get_row('SELECT SUM(size) FROM inodes')[0] stat_.f_blocks = size // stat_.f_frsize stat_.f_bfree = max(size // stat_.f_frsize, 1024) stat_.f_bavail = stat_.f_bfree inodes = self.get_row('SELECT COUNT(id) FROM inodes')[0] stat_.f_files = inodes stat_.f_ffree = max(inodes , 100) stat_.f_favail = stat_.f_ffree return stat_ def open(self, inode, flags, ctx): # Yeah, unused arguments #pylint: disable=W0613 self.inode_open_count[inode] += 1 # Use inodes as a file handles return inode def access(self, inode, mode, ctx): # Yeah, could be a function and has unused arguments #pylint: disable=R0201,W0613 return True def create(self, inode_parent, name, mode, flags, ctx): #pylint: disable=W0612 entry = self._create(inode_parent, name, mode, ctx) self.inode_open_count[entry.st_ino] += 1 return (entry.st_ino, entry) def _create(self, inode_p, name, mode, ctx, rdev=0, target=None): if self.getattr(inode_p).st_nlink == 0: log.warn('Attempted to create entry %s with unlinked parent %d', name, inode_p) raise FUSEError(errno.EINVAL) now_ns = int(time() * 1e9) self.cursor.execute('INSERT INTO inodes (uid, gid, mode, mtime_ns, atime_ns, ' 'ctime_ns, target, rdev) VALUES(?, ?, ?, ?, ?, ?, ?, ?)', (ctx.uid, ctx.gid, mode, now_ns, now_ns, now_ns, target, rdev)) inode = self.cursor.lastrowid self.db.execute("INSERT INTO contents(name, inode, parent_inode) VALUES(?,?,?)", (name, inode, inode_p)) return self.getattr(inode) def read(self, fh, offset, length): data = self.get_row('SELECT data FROM inodes WHERE id=?', (fh,))[0] if data is None: data = b'' return data[offset:offset+length] def write(self, fh, offset, buf): data = self.get_row('SELECT data FROM inodes WHERE id=?', (fh,))[0] if data is None: data = b'' data = data[:offset] + buf + data[offset+len(buf):] self.cursor.execute('UPDATE inodes SET data=?, size=? WHERE id=?', (memoryview(data), len(data), fh)) return len(buf) def release(self, fh): self.inode_open_count[fh] -= 1 if self.inode_open_count[fh] == 0: del self.inode_open_count[fh] if self.getattr(fh).st_nlink == 0: self.cursor.execute("DELETE FROM inodes WHERE id=?", (fh,)) class NoUniqueValueError(Exception): def __str__(self): return 'Query generated more than 1 result row' class NoSuchRowError(Exception): def __str__(self): return 'Query produced 0 result rows' def init_logging(debug=False): formatter = logging.Formatter('%(asctime)s.%(msecs)03d %(threadName)s: ' '[%(name)s] %(message)s', datefmt="%Y-%m-%d %H:%M:%S") handler = logging.StreamHandler() handler.setFormatter(formatter) root_logger = logging.getLogger() if debug: handler.setLevel(logging.DEBUG) root_logger.setLevel(logging.DEBUG) else: handler.setLevel(logging.INFO) root_logger.setLevel(logging.INFO) root_logger.addHandler(handler) def parse_args(): '''Parse command line''' parser = ArgumentParser() parser.add_argument('mountpoint', type=str, help='Where to mount the file system') parser.add_argument('--debug', action='store_true', default=False, help='Enable debugging output') parser.add_argument('--debug-fuse', action='store_true', default=False, help='Enable FUSE debugging output') return parser.parse_args() if __name__ == '__main__': options = parse_args() init_logging(options.debug) operations = Operations() fuse_options = set(llfuse.default_options) fuse_options.add('fsname=tmpfs') fuse_options.discard('default_permissions') if options.debug_fuse: fuse_options.add('debug') llfuse.init(operations, options.mountpoint, fuse_options) # sqlite3 does not support multithreading try: llfuse.main(workers=1) except: llfuse.close(unmount=False) raise llfuse.close() �����}�(hhh h�ubah}�(h]�h]�h]�h]�h]��source��,/home/user/w/python-llfuse/examples/tmpfs.py�h�h�h��h��python�h��h�}�h�Ksuhh�h!h"hKh h�hhubeh}�(h]��in-memory-file-system�ah]�h]��in-memory file system�ah]�h]�uhh#h h%hhh!h"hKubh$)��}�(hhh]�(h))��}�(h�!Passthrough / Overlay File System�h]�h/�!Passthrough / Overlay File System�����}�(hj h j hhh!NhNubah}�(h]�h]�h]�h]�h]�uhh(h j hhh!h"hKubh;)��}�(h�.(shipped as :file:`examples/passthroughfs.py`)�h]�(h/�(shipped as �����}�(h�(shipped as �h j hhh!NhNubhF)��}�(h�!:file:`examples/passthroughfs.py`�h]�h/�examples/passthroughfs.py�����}�(h�examples/passthroughfs.py�h j( ubah}�(h]�h]��file�ah]�h]�h]��role��file�uhhEh j ubh/�)�����}�(hh�h j hhh!NhNubeh}�(h]�h]�h]�h]�h]�uhh:h!h"hK!h j hhubh�)��}�(hX0A #!/usr/bin/env python3 ''' passthroughfs.py - Example file system for Python-LLFUSE This file system mirrors the contents of a specified directory tree. It requires Python 3.3 (since Python 2.x does not support the follow_symlinks parameters for os.* functions). Caveats: * Inode generation numbers are not passed through but set to zero. * Block size (st_blksize) and number of allocated blocks (st_blocks) are not passed through. * Performance for large directories is not good, because the directory is always read completely. * There may be a way to break-out of the directory tree. * The readdir implementation is not fully POSIX compliant. If a directory contains hardlinks and is modified during a readdir call, readdir() may return some of the hardlinked files twice or omit them completely. * If you delete or rename files in the underlying file system, the passthrough file system will get confused. Copyright © Nikolaus Rath <Nikolaus.org> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ''' import os import sys # If we are running from the Python-LLFUSE source directory, try # to load the module from there first. basedir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '..')) if (os.path.exists(os.path.join(basedir, 'setup.py')) and os.path.exists(os.path.join(basedir, 'src', 'llfuse.pyx'))): sys.path.insert(0, os.path.join(basedir, 'src')) import llfuse from argparse import ArgumentParser import errno import logging import stat as stat_m from llfuse import FUSEError from os import fsencode, fsdecode from collections import defaultdict import faulthandler faulthandler.enable() log = logging.getLogger(__name__) class Operations(llfuse.Operations): def __init__(self, source): super().__init__() self._inode_path_map = { llfuse.ROOT_INODE: source } self._lookup_cnt = defaultdict(lambda : 0) self._fd_inode_map = dict() self._inode_fd_map = dict() self._fd_open_count = dict() def _inode_to_path(self, inode): try: val = self._inode_path_map[inode] except KeyError: raise FUSEError(errno.ENOENT) if isinstance(val, set): # In case of hardlinks, pick any path val = next(iter(val)) return val def _add_path(self, inode, path): log.debug('_add_path for %d, %s', inode, path) self._lookup_cnt[inode] += 1 # With hardlinks, one inode may map to multiple paths. if inode not in self._inode_path_map: self._inode_path_map[inode] = path return val = self._inode_path_map[inode] if isinstance(val, set): val.add(path) elif val != path: self._inode_path_map[inode] = { path, val } def forget(self, inode_list): for (inode, nlookup) in inode_list: if self._lookup_cnt[inode] > nlookup: self._lookup_cnt[inode] -= nlookup continue log.debug('forgetting about inode %d', inode) assert inode not in self._inode_fd_map del self._lookup_cnt[inode] try: del self._inode_path_map[inode] except KeyError: # may have been deleted pass def lookup(self, inode_p, name, ctx=None): name = fsdecode(name) log.debug('lookup for %s in %d', name, inode_p) path = os.path.join(self._inode_to_path(inode_p), name) attr = self._getattr(path=path) if name != '.' and name != '..': self._add_path(attr.st_ino, path) return attr def getattr(self, inode, ctx=None): if inode in self._inode_fd_map: return self._getattr(fd=self._inode_fd_map[inode]) else: return self._getattr(path=self._inode_to_path(inode)) def _getattr(self, path=None, fd=None): assert fd is None or path is None assert not(fd is None and path is None) try: if fd is None: stat = os.lstat(path) else: stat = os.fstat(fd) except OSError as exc: raise FUSEError(exc.errno) entry = llfuse.EntryAttributes() for attr in ('st_ino', 'st_mode', 'st_nlink', 'st_uid', 'st_gid', 'st_rdev', 'st_size', 'st_atime_ns', 'st_mtime_ns', 'st_ctime_ns'): setattr(entry, attr, getattr(stat, attr)) entry.generation = 0 entry.entry_timeout = 5 entry.attr_timeout = 5 entry.st_blksize = 512 entry.st_blocks = ((entry.st_size+entry.st_blksize-1) // entry.st_blksize) return entry def readlink(self, inode, ctx): path = self._inode_to_path(inode) try: target = os.readlink(path) except OSError as exc: raise FUSEError(exc.errno) return fsencode(target) def opendir(self, inode, ctx): return inode def readdir(self, inode, off): path = self._inode_to_path(inode) log.debug('reading %s', path) entries = [] for name in os.listdir(path): attr = self._getattr(path=os.path.join(path, name)) entries.append((attr.st_ino, name, attr)) log.debug('read %d entries, starting at %d', len(entries), off) # This is not fully posix compatible. If there are hardlinks # (two names with the same inode), we don't have a unique # offset to start in between them. Note that we cannot simply # count entries, because then we would skip over entries # (or return them more than once) if the number of directory # entries changes between two calls to readdir(). for (ino, name, attr) in sorted(entries): if ino <= off: continue yield (fsencode(name), attr, ino) def unlink(self, inode_p, name, ctx): name = fsdecode(name) parent = self._inode_to_path(inode_p) path = os.path.join(parent, name) try: inode = os.lstat(path).st_ino os.unlink(path) except OSError as exc: raise FUSEError(exc.errno) if inode in self._lookup_cnt: self._forget_path(inode, path) def rmdir(self, inode_p, name, ctx): name = fsdecode(name) parent = self._inode_to_path(inode_p) path = os.path.join(parent, name) try: inode = os.lstat(path).st_ino os.rmdir(path) except OSError as exc: raise FUSEError(exc.errno) if inode in self._lookup_cnt: self._forget_path(inode, path) def _forget_path(self, inode, path): log.debug('forget %s for %d', path, inode) val = self._inode_path_map[inode] if isinstance(val, set): val.remove(path) if len(val) == 1: self._inode_path_map[inode] = next(iter(val)) else: del self._inode_path_map[inode] def symlink(self, inode_p, name, target, ctx): name = fsdecode(name) target = fsdecode(target) parent = self._inode_to_path(inode_p) path = os.path.join(parent, name) try: os.symlink(target, path) os.chown(path, ctx.uid, ctx.gid, follow_symlinks=False) except OSError as exc: raise FUSEError(exc.errno) stat = os.lstat(path) self._add_path(stat.st_ino, path) return self.getattr(stat.st_ino) def rename(self, inode_p_old, name_old, inode_p_new, name_new, ctx): name_old = fsdecode(name_old) name_new = fsdecode(name_new) parent_old = self._inode_to_path(inode_p_old) parent_new = self._inode_to_path(inode_p_new) path_old = os.path.join(parent_old, name_old) path_new = os.path.join(parent_new, name_new) try: os.rename(path_old, path_new) inode = os.lstat(path_new).st_ino except OSError as exc: raise FUSEError(exc.errno) if inode not in self._lookup_cnt: return val = self._inode_path_map[inode] if isinstance(val, set): assert len(val) > 1 val.add(path_new) val.remove(path_old) else: assert val == path_old self._inode_path_map[inode] = path_new def link(self, inode, new_inode_p, new_name, ctx): new_name = fsdecode(new_name) parent = self._inode_to_path(new_inode_p) path = os.path.join(parent, new_name) try: os.link(self._inode_to_path(inode), path, follow_symlinks=False) except OSError as exc: raise FUSEError(exc.errno) self._add_path(inode, path) return self.getattr(inode) def setattr(self, inode, attr, fields, fh, ctx): # We use the f* functions if possible so that we can handle # a setattr() call for an inode without associated directory # handle. if fh is None: path_or_fh = self._inode_to_path(inode) truncate = os.truncate chmod = os.chmod chown = os.chown stat = os.lstat else: path_or_fh = fh truncate = os.ftruncate chmod = os.fchmod chown = os.fchown stat = os.fstat try: if fields.update_size: truncate(path_or_fh, attr.st_size) if fields.update_mode: # Under Linux, chmod always resolves symlinks so we should # actually never get a setattr() request for a symbolic # link. assert not stat_m.S_ISLNK(attr.st_mode) chmod(path_or_fh, stat_m.S_IMODE(attr.st_mode)) if fields.update_uid: chown(path_or_fh, attr.st_uid, -1, follow_symlinks=False) if fields.update_gid: chown(path_or_fh, -1, attr.st_gid, follow_symlinks=False) if fields.update_atime and fields.update_mtime: # utime accepts both paths and file descriptiors os.utime(path_or_fh, None, follow_symlinks=False, ns=(attr.st_atime_ns, attr.st_mtime_ns)) elif fields.update_atime or fields.update_mtime: # We can only set both values, so we first need to retrieve the # one that we shouldn't be changing. oldstat = stat(path_or_fh) if not fields.update_atime: attr.st_atime_ns = oldstat.st_atime_ns else: attr.st_mtime_ns = oldstat.st_mtime_ns os.utime(path_or_fh, None, follow_symlinks=False, ns=(attr.st_atime_ns, attr.st_mtime_ns)) except OSError as exc: raise FUSEError(exc.errno) return self.getattr(inode) def mknod(self, inode_p, name, mode, rdev, ctx): path = os.path.join(self._inode_to_path(inode_p), fsdecode(name)) try: os.mknod(path, mode=(mode & ~ctx.umask), device=rdev) os.chown(path, ctx.uid, ctx.gid) except OSError as exc: raise FUSEError(exc.errno) attr = self._getattr(path=path) self._add_path(attr.st_ino, path) return attr def mkdir(self, inode_p, name, mode, ctx): path = os.path.join(self._inode_to_path(inode_p), fsdecode(name)) try: os.mkdir(path, mode=(mode & ~ctx.umask)) os.chown(path, ctx.uid, ctx.gid) except OSError as exc: raise FUSEError(exc.errno) attr = self._getattr(path=path) self._add_path(attr.st_ino, path) return attr def statfs(self, ctx): root = self._inode_path_map[llfuse.ROOT_INODE] stat_ = llfuse.StatvfsData() try: statfs = os.statvfs(root) except OSError as exc: raise FUSEError(exc.errno) for attr in ('f_bsize', 'f_frsize', 'f_blocks', 'f_bfree', 'f_bavail', 'f_files', 'f_ffree', 'f_favail'): setattr(stat_, attr, getattr(statfs, attr)) stat_.f_namemax = statfs.f_namemax - (len(root)+1) return stat_ def open(self, inode, flags, ctx): if inode in self._inode_fd_map: fd = self._inode_fd_map[inode] self._fd_open_count[fd] += 1 return fd assert flags & os.O_CREAT == 0 try: fd = os.open(self._inode_to_path(inode), flags) except OSError as exc: raise FUSEError(exc.errno) self._inode_fd_map[inode] = fd self._fd_inode_map[fd] = inode self._fd_open_count[fd] = 1 return fd def create(self, inode_p, name, mode, flags, ctx): path = os.path.join(self._inode_to_path(inode_p), fsdecode(name)) try: fd = os.open(path, flags | os.O_CREAT | os.O_TRUNC) except OSError as exc: raise FUSEError(exc.errno) attr = self._getattr(fd=fd) self._add_path(attr.st_ino, path) self._inode_fd_map[attr.st_ino] = fd self._fd_inode_map[fd] = attr.st_ino self._fd_open_count[fd] = 1 return (fd, attr) def read(self, fd, offset, length): os.lseek(fd, offset, os.SEEK_SET) return os.read(fd, length) def write(self, fd, offset, buf): os.lseek(fd, offset, os.SEEK_SET) return os.write(fd, buf) def release(self, fd): if self._fd_open_count[fd] > 1: self._fd_open_count[fd] -= 1 return del self._fd_open_count[fd] inode = self._fd_inode_map[fd] del self._inode_fd_map[inode] del self._fd_inode_map[fd] try: os.close(fd) except OSError as exc: raise FUSEError(exc.errno) def init_logging(debug=False): formatter = logging.Formatter('%(asctime)s.%(msecs)03d %(threadName)s: ' '[%(name)s] %(message)s', datefmt="%Y-%m-%d %H:%M:%S") handler = logging.StreamHandler() handler.setFormatter(formatter) root_logger = logging.getLogger() if debug: handler.setLevel(logging.DEBUG) root_logger.setLevel(logging.DEBUG) else: handler.setLevel(logging.INFO) root_logger.setLevel(logging.INFO) root_logger.addHandler(handler) def parse_args(args): '''Parse command line''' parser = ArgumentParser() parser.add_argument('source', type=str, help='Directory tree to mirror') parser.add_argument('mountpoint', type=str, help='Where to mount the file system') parser.add_argument('--single', action='store_true', default=False, help='Run single threaded') parser.add_argument('--debug', action='store_true', default=False, help='Enable debugging output') parser.add_argument('--debug-fuse', action='store_true', default=False, help='Enable FUSE debugging output') return parser.parse_args(args) def main(): options = parse_args(sys.argv[1:]) init_logging(options.debug) operations = Operations(options.source) log.debug('Mounting...') fuse_options = set(llfuse.default_options) fuse_options.add('fsname=passthroughfs') if options.debug_fuse: fuse_options.add('debug') llfuse.init(operations, options.mountpoint, fuse_options) try: log.debug('Entering main loop..') if options.single: llfuse.main(workers=1) else: llfuse.main() except: llfuse.close(unmount=False) raise log.debug('Unmounting..') llfuse.close() if __name__ == '__main__': main() �h]�h/X0A #!/usr/bin/env python3 ''' passthroughfs.py - Example file system for Python-LLFUSE This file system mirrors the contents of a specified directory tree. It requires Python 3.3 (since Python 2.x does not support the follow_symlinks parameters for os.* functions). Caveats: * Inode generation numbers are not passed through but set to zero. * Block size (st_blksize) and number of allocated blocks (st_blocks) are not passed through. * Performance for large directories is not good, because the directory is always read completely. * There may be a way to break-out of the directory tree. * The readdir implementation is not fully POSIX compliant. If a directory contains hardlinks and is modified during a readdir call, readdir() may return some of the hardlinked files twice or omit them completely. * If you delete or rename files in the underlying file system, the passthrough file system will get confused. Copyright © Nikolaus Rath <Nikolaus.org> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ''' import os import sys # If we are running from the Python-LLFUSE source directory, try # to load the module from there first. basedir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '..')) if (os.path.exists(os.path.join(basedir, 'setup.py')) and os.path.exists(os.path.join(basedir, 'src', 'llfuse.pyx'))): sys.path.insert(0, os.path.join(basedir, 'src')) import llfuse from argparse import ArgumentParser import errno import logging import stat as stat_m from llfuse import FUSEError from os import fsencode, fsdecode from collections import defaultdict import faulthandler faulthandler.enable() log = logging.getLogger(__name__) class Operations(llfuse.Operations): def __init__(self, source): super().__init__() self._inode_path_map = { llfuse.ROOT_INODE: source } self._lookup_cnt = defaultdict(lambda : 0) self._fd_inode_map = dict() self._inode_fd_map = dict() self._fd_open_count = dict() def _inode_to_path(self, inode): try: val = self._inode_path_map[inode] except KeyError: raise FUSEError(errno.ENOENT) if isinstance(val, set): # In case of hardlinks, pick any path val = next(iter(val)) return val def _add_path(self, inode, path): log.debug('_add_path for %d, %s', inode, path) self._lookup_cnt[inode] += 1 # With hardlinks, one inode may map to multiple paths. if inode not in self._inode_path_map: self._inode_path_map[inode] = path return val = self._inode_path_map[inode] if isinstance(val, set): val.add(path) elif val != path: self._inode_path_map[inode] = { path, val } def forget(self, inode_list): for (inode, nlookup) in inode_list: if self._lookup_cnt[inode] > nlookup: self._lookup_cnt[inode] -= nlookup continue log.debug('forgetting about inode %d', inode) assert inode not in self._inode_fd_map del self._lookup_cnt[inode] try: del self._inode_path_map[inode] except KeyError: # may have been deleted pass def lookup(self, inode_p, name, ctx=None): name = fsdecode(name) log.debug('lookup for %s in %d', name, inode_p) path = os.path.join(self._inode_to_path(inode_p), name) attr = self._getattr(path=path) if name != '.' and name != '..': self._add_path(attr.st_ino, path) return attr def getattr(self, inode, ctx=None): if inode in self._inode_fd_map: return self._getattr(fd=self._inode_fd_map[inode]) else: return self._getattr(path=self._inode_to_path(inode)) def _getattr(self, path=None, fd=None): assert fd is None or path is None assert not(fd is None and path is None) try: if fd is None: stat = os.lstat(path) else: stat = os.fstat(fd) except OSError as exc: raise FUSEError(exc.errno) entry = llfuse.EntryAttributes() for attr in ('st_ino', 'st_mode', 'st_nlink', 'st_uid', 'st_gid', 'st_rdev', 'st_size', 'st_atime_ns', 'st_mtime_ns', 'st_ctime_ns'): setattr(entry, attr, getattr(stat, attr)) entry.generation = 0 entry.entry_timeout = 5 entry.attr_timeout = 5 entry.st_blksize = 512 entry.st_blocks = ((entry.st_size+entry.st_blksize-1) // entry.st_blksize) return entry def readlink(self, inode, ctx): path = self._inode_to_path(inode) try: target = os.readlink(path) except OSError as exc: raise FUSEError(exc.errno) return fsencode(target) def opendir(self, inode, ctx): return inode def readdir(self, inode, off): path = self._inode_to_path(inode) log.debug('reading %s', path) entries = [] for name in os.listdir(path): attr = self._getattr(path=os.path.join(path, name)) entries.append((attr.st_ino, name, attr)) log.debug('read %d entries, starting at %d', len(entries), off) # This is not fully posix compatible. If there are hardlinks # (two names with the same inode), we don't have a unique # offset to start in between them. Note that we cannot simply # count entries, because then we would skip over entries # (or return them more than once) if the number of directory # entries changes between two calls to readdir(). for (ino, name, attr) in sorted(entries): if ino <= off: continue yield (fsencode(name), attr, ino) def unlink(self, inode_p, name, ctx): name = fsdecode(name) parent = self._inode_to_path(inode_p) path = os.path.join(parent, name) try: inode = os.lstat(path).st_ino os.unlink(path) except OSError as exc: raise FUSEError(exc.errno) if inode in self._lookup_cnt: self._forget_path(inode, path) def rmdir(self, inode_p, name, ctx): name = fsdecode(name) parent = self._inode_to_path(inode_p) path = os.path.join(parent, name) try: inode = os.lstat(path).st_ino os.rmdir(path) except OSError as exc: raise FUSEError(exc.errno) if inode in self._lookup_cnt: self._forget_path(inode, path) def _forget_path(self, inode, path): log.debug('forget %s for %d', path, inode) val = self._inode_path_map[inode] if isinstance(val, set): val.remove(path) if len(val) == 1: self._inode_path_map[inode] = next(iter(val)) else: del self._inode_path_map[inode] def symlink(self, inode_p, name, target, ctx): name = fsdecode(name) target = fsdecode(target) parent = self._inode_to_path(inode_p) path = os.path.join(parent, name) try: os.symlink(target, path) os.chown(path, ctx.uid, ctx.gid, follow_symlinks=False) except OSError as exc: raise FUSEError(exc.errno) stat = os.lstat(path) self._add_path(stat.st_ino, path) return self.getattr(stat.st_ino) def rename(self, inode_p_old, name_old, inode_p_new, name_new, ctx): name_old = fsdecode(name_old) name_new = fsdecode(name_new) parent_old = self._inode_to_path(inode_p_old) parent_new = self._inode_to_path(inode_p_new) path_old = os.path.join(parent_old, name_old) path_new = os.path.join(parent_new, name_new) try: os.rename(path_old, path_new) inode = os.lstat(path_new).st_ino except OSError as exc: raise FUSEError(exc.errno) if inode not in self._lookup_cnt: return val = self._inode_path_map[inode] if isinstance(val, set): assert len(val) > 1 val.add(path_new) val.remove(path_old) else: assert val == path_old self._inode_path_map[inode] = path_new def link(self, inode, new_inode_p, new_name, ctx): new_name = fsdecode(new_name) parent = self._inode_to_path(new_inode_p) path = os.path.join(parent, new_name) try: os.link(self._inode_to_path(inode), path, follow_symlinks=False) except OSError as exc: raise FUSEError(exc.errno) self._add_path(inode, path) return self.getattr(inode) def setattr(self, inode, attr, fields, fh, ctx): # We use the f* functions if possible so that we can handle # a setattr() call for an inode without associated directory # handle. if fh is None: path_or_fh = self._inode_to_path(inode) truncate = os.truncate chmod = os.chmod chown = os.chown stat = os.lstat else: path_or_fh = fh truncate = os.ftruncate chmod = os.fchmod chown = os.fchown stat = os.fstat try: if fields.update_size: truncate(path_or_fh, attr.st_size) if fields.update_mode: # Under Linux, chmod always resolves symlinks so we should # actually never get a setattr() request for a symbolic # link. assert not stat_m.S_ISLNK(attr.st_mode) chmod(path_or_fh, stat_m.S_IMODE(attr.st_mode)) if fields.update_uid: chown(path_or_fh, attr.st_uid, -1, follow_symlinks=False) if fields.update_gid: chown(path_or_fh, -1, attr.st_gid, follow_symlinks=False) if fields.update_atime and fields.update_mtime: # utime accepts both paths and file descriptiors os.utime(path_or_fh, None, follow_symlinks=False, ns=(attr.st_atime_ns, attr.st_mtime_ns)) elif fields.update_atime or fields.update_mtime: # We can only set both values, so we first need to retrieve the # one that we shouldn't be changing. oldstat = stat(path_or_fh) if not fields.update_atime: attr.st_atime_ns = oldstat.st_atime_ns else: attr.st_mtime_ns = oldstat.st_mtime_ns os.utime(path_or_fh, None, follow_symlinks=False, ns=(attr.st_atime_ns, attr.st_mtime_ns)) except OSError as exc: raise FUSEError(exc.errno) return self.getattr(inode) def mknod(self, inode_p, name, mode, rdev, ctx): path = os.path.join(self._inode_to_path(inode_p), fsdecode(name)) try: os.mknod(path, mode=(mode & ~ctx.umask), device=rdev) os.chown(path, ctx.uid, ctx.gid) except OSError as exc: raise FUSEError(exc.errno) attr = self._getattr(path=path) self._add_path(attr.st_ino, path) return attr def mkdir(self, inode_p, name, mode, ctx): path = os.path.join(self._inode_to_path(inode_p), fsdecode(name)) try: os.mkdir(path, mode=(mode & ~ctx.umask)) os.chown(path, ctx.uid, ctx.gid) except OSError as exc: raise FUSEError(exc.errno) attr = self._getattr(path=path) self._add_path(attr.st_ino, path) return attr def statfs(self, ctx): root = self._inode_path_map[llfuse.ROOT_INODE] stat_ = llfuse.StatvfsData() try: statfs = os.statvfs(root) except OSError as exc: raise FUSEError(exc.errno) for attr in ('f_bsize', 'f_frsize', 'f_blocks', 'f_bfree', 'f_bavail', 'f_files', 'f_ffree', 'f_favail'): setattr(stat_, attr, getattr(statfs, attr)) stat_.f_namemax = statfs.f_namemax - (len(root)+1) return stat_ def open(self, inode, flags, ctx): if inode in self._inode_fd_map: fd = self._inode_fd_map[inode] self._fd_open_count[fd] += 1 return fd assert flags & os.O_CREAT == 0 try: fd = os.open(self._inode_to_path(inode), flags) except OSError as exc: raise FUSEError(exc.errno) self._inode_fd_map[inode] = fd self._fd_inode_map[fd] = inode self._fd_open_count[fd] = 1 return fd def create(self, inode_p, name, mode, flags, ctx): path = os.path.join(self._inode_to_path(inode_p), fsdecode(name)) try: fd = os.open(path, flags | os.O_CREAT | os.O_TRUNC) except OSError as exc: raise FUSEError(exc.errno) attr = self._getattr(fd=fd) self._add_path(attr.st_ino, path) self._inode_fd_map[attr.st_ino] = fd self._fd_inode_map[fd] = attr.st_ino self._fd_open_count[fd] = 1 return (fd, attr) def read(self, fd, offset, length): os.lseek(fd, offset, os.SEEK_SET) return os.read(fd, length) def write(self, fd, offset, buf): os.lseek(fd, offset, os.SEEK_SET) return os.write(fd, buf) def release(self, fd): if self._fd_open_count[fd] > 1: self._fd_open_count[fd] -= 1 return del self._fd_open_count[fd] inode = self._fd_inode_map[fd] del self._inode_fd_map[inode] del self._fd_inode_map[fd] try: os.close(fd) except OSError as exc: raise FUSEError(exc.errno) def init_logging(debug=False): formatter = logging.Formatter('%(asctime)s.%(msecs)03d %(threadName)s: ' '[%(name)s] %(message)s', datefmt="%Y-%m-%d %H:%M:%S") handler = logging.StreamHandler() handler.setFormatter(formatter) root_logger = logging.getLogger() if debug: handler.setLevel(logging.DEBUG) root_logger.setLevel(logging.DEBUG) else: handler.setLevel(logging.INFO) root_logger.setLevel(logging.INFO) root_logger.addHandler(handler) def parse_args(args): '''Parse command line''' parser = ArgumentParser() parser.add_argument('source', type=str, help='Directory tree to mirror') parser.add_argument('mountpoint', type=str, help='Where to mount the file system') parser.add_argument('--single', action='store_true', default=False, help='Run single threaded') parser.add_argument('--debug', action='store_true', default=False, help='Enable debugging output') parser.add_argument('--debug-fuse', action='store_true', default=False, help='Enable FUSE debugging output') return parser.parse_args(args) def main(): options = parse_args(sys.argv[1:]) init_logging(options.debug) operations = Operations(options.source) log.debug('Mounting...') fuse_options = set(llfuse.default_options) fuse_options.add('fsname=passthroughfs') if options.debug_fuse: fuse_options.add('debug') llfuse.init(operations, options.mountpoint, fuse_options) try: log.debug('Entering main loop..') if options.single: llfuse.main(workers=1) else: llfuse.main() except: llfuse.close(unmount=False) raise log.debug('Unmounting..') llfuse.close() if __name__ == '__main__': main() ������R }�(hhh jD ubah}�(h]�h]�h]�h]�h]��source��4/home/user/w/python-llfuse/examples/passthroughfs.py�h�h�h��h��python�h��h�}�h�Ksuhh�h!h"hK#h j hhubeh}�(h]��passthrough-overlay-file-system�ah]�h]��!passthrough / overlay file system�ah]�h]�uhh#h h%hhh!h"hKubeh}�(h]�(�example-file-systems�heh]�h]�(�example file systems��example file system�eh]�h]�uhh#h hhhh!h"hK�expect_referenced_by_name�}�jd hs�expect_referenced_by_id�}�hhsubeh}�(h]�h]�h]�h]�h]��source�h"uhh�current_source�N�current_line�N�settings��docutils.frontend��Values���)��}�(h(N� generator�N� datestamp�N�source_link�N� source_url�N� toc_backlinks��entry��footnote_backlinks�K� sectnum_xform�K�strip_comments�N�strip_elements_with_classes�N� strip_classes�N�report_level�K� halt_level�K�exit_status_level�K�debug�N�warning_stream�N� traceback���input_encoding��utf-8��input_encoding_error_handler��strict��output_encoding��utf-8��output_encoding_error_handler�j� �error_encoding��UTF-8��error_encoding_error_handler��backslashreplace�� language_code��en��record_dependencies�N�config�N� id_prefix�h�auto_id_prefix��id�� dump_settings�N�dump_internals�N�dump_transforms�N�dump_pseudo_xml�N�expose_internals�N�strict_visitor�N�_disable_config�N�_source�h"�_destination�N� _config_files�]��pep_references�N�pep_base_url�� https://www.python.org/dev/peps/��pep_file_url_template��pep-%04d��rfc_references�N�rfc_base_url��https://tools.ietf.org/html/�� tab_width�K�trim_footnote_reference_space���file_insertion_enabled���raw_enabled�K�syntax_highlight��long��smart_quotes���smartquotes_locales�]��character_level_inline_markup���doctitle_xform��� docinfo_xform�K�sectsubtitle_xform���embed_stylesheet���cloak_email_addresses���env�Nub�reporter�N�indirect_targets�]��substitution_defs�}��substitution_names�}��refnames�}��refids�}�h]�has�nameids�}�(jd hjc j` h�h�j j j[ jX u� nametypes�}�(jd �jc Nh�Nj Nj[ Nuh}�(hh%j` h%h�hdj h�jX j u� footnote_refs�}�� citation_refs�}�� autofootnotes�]��autofootnote_refs�]��symbol_footnotes�]��symbol_footnote_refs�]�� footnotes�]�� citations�]��autofootnote_start�K�symbol_footnote_start�K � id_counter��collections��Counter���}���R��parse_messages�]��transform_messages�]�h �system_message���)��}�(hhh]�h;)��}�(hhh]�h/�9Hyperlink target "example-file-system" is not referenced.�����}�(hhh j� ubah}�(h]�h]�h]�h]�h]�uhh:h j� ubah}�(h]�h]�h]�h]�h]��level�K�type��INFO��source�h"�line�Kuhj� uba�transformer�N� decoration�Nhhub.