started working on fuseFs-implementation for next
This commit is contained in:
		
							parent
							
								
									072e3dd8ff
								
							
						
					
					
						commit
						3261e3e60c
					
				
							
								
								
									
										121
									
								
								next.py
									
									
									
									
									
								
							
							
						
						
									
										121
									
								
								next.py
									
									
									
									
									
								
							@ -1,5 +1,3 @@
 | 
			
		||||
#import pyfuse3
 | 
			
		||||
 | 
			
		||||
# TODO: Implement File COW (except for append) (+ version / token updates caused by this)
 | 
			
		||||
# TODO: ? Stop using tokens for dirs, use hashed name + parent token
 | 
			
		||||
# TODO: ? Stop using tokens for files, use hashed name + short ?version-number + parent token instead
 | 
			
		||||
@ -8,7 +6,7 @@
 | 
			
		||||
# TODO: Decide how / when / from which class to push new blocks
 | 
			
		||||
# TODO: Unload 'overwritten' blobs
 | 
			
		||||
# TODO: Close blobs when they become unknown to the kernel or when we unmount (genesis only on unmount)
 | 
			
		||||
# TODO: When unmounting walk throught tree and seal all blobs
 | 
			
		||||
# TODO: inode_id -> actuall Inode lookup table
 | 
			
		||||
 | 
			
		||||
from iota import Iota, ProposedTransaction, Address, TryteString, Tag
 | 
			
		||||
from iota.crypto.addresses import AddressGenerator
 | 
			
		||||
@ -26,12 +24,25 @@ import random
 | 
			
		||||
import time
 | 
			
		||||
 | 
			
		||||
import msgpack
 | 
			
		||||
import asyncio
 | 
			
		||||
import copy
 | 
			
		||||
 | 
			
		||||
import gzip
 | 
			
		||||
import secrets
 | 
			
		||||
 | 
			
		||||
import stat
 | 
			
		||||
import errno
 | 
			
		||||
import pyfuse3
 | 
			
		||||
import trio
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from pyfuse3 import FUSEError
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    import faulthandler
 | 
			
		||||
except ImportError:
 | 
			
		||||
    pass
 | 
			
		||||
else:
 | 
			
		||||
    faulthandler.enable()
 | 
			
		||||
 | 
			
		||||
CHUNKSIZE = 2187
 | 
			
		||||
SYNCWRITES = True
 | 
			
		||||
 | 
			
		||||
@ -39,8 +50,8 @@ def log(txt):
 | 
			
		||||
    print("[-] "+str(txt))
 | 
			
		||||
 | 
			
		||||
def sendEmOff(bundles, api):
 | 
			
		||||
    print("[->]")
 | 
			
		||||
    for bundle in bundles:
 | 
			
		||||
        print("[->]")
 | 
			
		||||
        api.send_trytes(
 | 
			
		||||
            trytes=bundle
 | 
			
		||||
        )
 | 
			
		||||
@ -496,7 +507,11 @@ class Inode():
 | 
			
		||||
    def hasRef(self):
 | 
			
		||||
        return not self.ref==None
 | 
			
		||||
 | 
			
		||||
class IotaFs():
 | 
			
		||||
class IotaFS(pyfuse3.Operations):
 | 
			
		||||
 | 
			
		||||
    #supports_dot_lookup = True
 | 
			
		||||
    enable_writeback_cache = True
 | 
			
		||||
 | 
			
		||||
    def __init__(self, token) -> None:
 | 
			
		||||
        self.api = Iota('https://nodes.thetangle.org:443', local_pow=True)
 | 
			
		||||
        # TODO Cache last known milestone-Index of genesis locally
 | 
			
		||||
@ -514,13 +529,84 @@ class IotaFs():
 | 
			
		||||
                log("Unable to find reference to root: Creating new root")
 | 
			
		||||
                self.genesis.mkdir("/")
 | 
			
		||||
        log("Successfully Mounted!")
 | 
			
		||||
        self.inodeIds = {}
 | 
			
		||||
        self.fhs = {}
 | 
			
		||||
 | 
			
		||||
    def createNewFile(self, name) -> None:
 | 
			
		||||
    async def access(self, inodeId, mode, ctx):
 | 
			
		||||
        # not called
 | 
			
		||||
        raise Exception("This function should not been called; WTF")
 | 
			
		||||
 | 
			
		||||
    async def create(self, parent_inodeId, name, mode, flags, ctx):
 | 
			
		||||
        #return (fi, attr)
 | 
			
		||||
        # $increase lookupN
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
api = Iota('https://nodes.thetangle.org:443', local_pow=True)
 | 
			
		||||
token = b'testToken'
 | 
			
		||||
genesis = TangleFileTreeElement("*", 0, token, api)
 | 
			
		||||
    async def flush(self, fh):
 | 
			
		||||
        # flush the file at fh
 | 
			
		||||
        # basically means: close, but may be called multiple times,
 | 
			
		||||
        # when open multiple times with same fh
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    async def forget(self, inode_list):
 | 
			
		||||
        # inodelist = [(fh, nlookup),...]
 | 
			
		||||
        # decrement lookupN of file at fh
 | 
			
		||||
        # if lookupN == 0:
 | 
			
		||||
        #   'remove' Inode
 | 
			
		||||
        # should be called at unmount to bring lookupN to 0 for all files
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    async def getattr(self, inodeId, ctx):
 | 
			
		||||
        # return EntryAttributes()
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    #async def link(self, inodeId, new_parent_inode, new_name, ctx):
 | 
			
		||||
 | 
			
		||||
    async def lookup(self, parent_inodeId, name, ctx):
 | 
			
		||||
        #return EntryAttributes()
 | 
			
		||||
        # not exists: raise FUSEError(errno.ENOENT)
 | 
			
		||||
        # must handle .. and .
 | 
			
		||||
        # $increase lookupN
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    async def mkdir(self, parent_inodeId, name, mode, ctx):
 | 
			
		||||
        #return EntryAttributes()
 | 
			
		||||
        # $increase lookupN
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    async def mknod(self, parent_inodeId, name, mode, rdev, ctx):
 | 
			
		||||
        # create file
 | 
			
		||||
        #return EntryAttributes()
 | 
			
		||||
        # $increase lookupN
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    async def open(self, inodeId, flags, ctx):
 | 
			
		||||
        # open file at inodeId; give back fh
 | 
			
		||||
        #return FileInode(..fh)
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    async def opendir(self, inodeId, ctx):
 | 
			
		||||
        #return fh
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    async def read(self, fh, off, size):
 | 
			
		||||
        # Read size bytes from fh at position off
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    async def readdir(self, fh, start_id, token):
 | 
			
		||||
        # fuck this shit
 | 
			
		||||
        # http://www.rath.org/pyfuse3-docs/operations.html#pyfuse3.Operations.readdir
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    async def release(self, fh):
 | 
			
		||||
        # file no longer open -> close? uncache?
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    async def releasedir(self, fh):
 | 
			
		||||
        # dir no longer open -> uncache?
 | 
			
		||||
 | 
			
		||||
#api = Iota('https://nodes.thetangle.org:443', local_pow=True)
 | 
			
		||||
#token = b'testToken'
 | 
			
		||||
#genesis = TangleFileTreeElement("*", 0, token, api)
 | 
			
		||||
 | 
			
		||||
if False:
 | 
			
		||||
    genesis.mkdir("/")
 | 
			
		||||
@ -544,3 +630,18 @@ if False:
 | 
			
		||||
    sub.mkdir("subsub")
 | 
			
		||||
 | 
			
		||||
    print(genesis.tree())
 | 
			
		||||
 | 
			
		||||
def main():
 | 
			
		||||
    iotaFs = IotaFS(b'This is a test token')
 | 
			
		||||
    opts = set(pyfuse3.default_options)
 | 
			
		||||
    opts.add('fsname=IotaFS')
 | 
			
		||||
    #opts.add('debug')
 | 
			
		||||
    pyfuse3.init(iotaFs, "mount", opts)
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        trio.run(pyfuse3.main)
 | 
			
		||||
    except:
 | 
			
		||||
        pyfuse3.close(unmount=True)
 | 
			
		||||
        raise
 | 
			
		||||
 | 
			
		||||
    pyfuse3.close()
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										434
									
								
								testFs.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										434
									
								
								testFs.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,434 @@
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
# If we are running from the pyfuse3 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', 'pyfuse3.pyx'))):
 | 
			
		||||
    sys.path.insert(0, os.path.join(basedir, 'src'))
 | 
			
		||||
 | 
			
		||||
import pyfuse3
 | 
			
		||||
import errno
 | 
			
		||||
import stat
 | 
			
		||||
from time import time
 | 
			
		||||
import sqlite3
 | 
			
		||||
import logging
 | 
			
		||||
from collections import defaultdict
 | 
			
		||||
from pyfuse3 import FUSEError
 | 
			
		||||
from argparse import ArgumentParser
 | 
			
		||||
import trio
 | 
			
		||||
 | 
			
		||||
try:
 | 
			
		||||
    import faulthandler
 | 
			
		||||
except ImportError:
 | 
			
		||||
    pass
 | 
			
		||||
else:
 | 
			
		||||
    faulthandler.enable()
 | 
			
		||||
 | 
			
		||||
log = logging.getLogger()
 | 
			
		||||
 | 
			
		||||
class Operations(pyfuse3.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
 | 
			
		||||
    * lookup counts are not maintained
 | 
			
		||||
    '''
 | 
			
		||||
 | 
			
		||||
    enable_writeback_cache = True
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super(Operations, self).__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 (?,?,?,?,?,?,?)",
 | 
			
		||||
                            (pyfuse3.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'..', pyfuse3.ROOT_INODE, pyfuse3.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
 | 
			
		||||
 | 
			
		||||
    async def lookup(self, inode_p, name, ctx=None):
 | 
			
		||||
        print(str(inode_p)+" -> "+str(name))
 | 
			
		||||
        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(pyfuse3.FUSEError(errno.ENOENT))
 | 
			
		||||
 | 
			
		||||
        return await self.getattr(inode, ctx)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    async def getattr(self, inode, ctx=None):
 | 
			
		||||
        row = self.get_row('SELECT * FROM inodes WHERE id=?', (inode,))
 | 
			
		||||
 | 
			
		||||
        entry = pyfuse3.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
 | 
			
		||||
 | 
			
		||||
    async def readlink(self, inode, ctx):
 | 
			
		||||
        return self.get_row('SELECT * FROM inodes WHERE id=?', (inode,))['target']
 | 
			
		||||
 | 
			
		||||
    async def opendir(self, inode, ctx):
 | 
			
		||||
        return inode
 | 
			
		||||
 | 
			
		||||
    async def readdir(self, inode, off, token):
 | 
			
		||||
        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:
 | 
			
		||||
            pyfuse3.readdir_reply(
 | 
			
		||||
                token, row['name'], await self.getattr(row['inode']), row['rowid'])
 | 
			
		||||
 | 
			
		||||
    async def unlink(self, inode_p, name,ctx):
 | 
			
		||||
        entry = await self.lookup(inode_p, name)
 | 
			
		||||
 | 
			
		||||
        if stat.S_ISDIR(entry.st_mode):
 | 
			
		||||
            raise pyfuse3.FUSEError(errno.EISDIR)
 | 
			
		||||
 | 
			
		||||
        self._remove(inode_p, name, entry)
 | 
			
		||||
 | 
			
		||||
    async def rmdir(self, inode_p, name, ctx):
 | 
			
		||||
        entry = await self.lookup(inode_p, name)
 | 
			
		||||
 | 
			
		||||
        if not stat.S_ISDIR(entry.st_mode):
 | 
			
		||||
            raise pyfuse3.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 pyfuse3.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,))
 | 
			
		||||
 | 
			
		||||
    async 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 await self._create(inode_p, name, mode, ctx, target=target)
 | 
			
		||||
 | 
			
		||||
    async def rename(self, inode_p_old, name_old, inode_p_new, name_new,
 | 
			
		||||
                     flags, ctx):
 | 
			
		||||
        if flags != 0:
 | 
			
		||||
            raise FUSEError(errno.EINVAL)
 | 
			
		||||
 | 
			
		||||
        entry_old = await self.lookup(inode_p_old, name_old)
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            entry_new = await self.lookup(inode_p_new, name_new)
 | 
			
		||||
        except pyfuse3.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 pyfuse3.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,))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    async def link(self, inode, new_inode_p, new_name, ctx):
 | 
			
		||||
        entry_p = await 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 await self.getattr(inode)
 | 
			
		||||
 | 
			
		||||
    async 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))
 | 
			
		||||
 | 
			
		||||
        if fields.update_ctime:
 | 
			
		||||
            self.cursor.execute('UPDATE inodes SET ctime_ns=? WHERE id=?',
 | 
			
		||||
                                (attr.st_ctime_ns, inode))
 | 
			
		||||
        else:
 | 
			
		||||
            self.cursor.execute('UPDATE inodes SET ctime_ns=? WHERE id=?',
 | 
			
		||||
                                (int(time()*1e9), inode))
 | 
			
		||||
 | 
			
		||||
        return await self.getattr(inode)
 | 
			
		||||
 | 
			
		||||
    async def mknod(self, inode_p, name, mode, rdev, ctx):
 | 
			
		||||
        return await self._create(inode_p, name, mode, ctx, rdev=rdev)
 | 
			
		||||
 | 
			
		||||
    async def mkdir(self, inode_p, name, mode, ctx):
 | 
			
		||||
        return await self._create(inode_p, name, mode, ctx)
 | 
			
		||||
 | 
			
		||||
    async def statfs(self, ctx):
 | 
			
		||||
        stat_ = pyfuse3.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_
 | 
			
		||||
 | 
			
		||||
    async 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 pyfuse3.FileInfo(fh=inode)
 | 
			
		||||
 | 
			
		||||
    async def access(self, inode, mode, ctx):
 | 
			
		||||
        # Yeah, could be a function and has unused arguments
 | 
			
		||||
        #pylint: disable=R0201,W0613
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    async def create(self, inode_parent, name, mode, flags, ctx):
 | 
			
		||||
        #pylint: disable=W0612
 | 
			
		||||
        entry = await self._create(inode_parent, name, mode, ctx)
 | 
			
		||||
        self.inode_open_count[entry.st_ino] += 1
 | 
			
		||||
        return (pyfuse3.FileInfo(fh=entry.st_ino), entry)
 | 
			
		||||
 | 
			
		||||
    async def _create(self, inode_p, name, mode, ctx, rdev=0, target=None):
 | 
			
		||||
        if (await 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 await self.getattr(inode)
 | 
			
		||||
 | 
			
		||||
    async 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]
 | 
			
		||||
 | 
			
		||||
    async 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)
 | 
			
		||||
 | 
			
		||||
    async def release(self, fh):
 | 
			
		||||
        self.inode_open_count[fh] -= 1
 | 
			
		||||
 | 
			
		||||
        if self.inode_open_count[fh] == 0:
 | 
			
		||||
            del self.inode_open_count[fh]
 | 
			
		||||
            if (await 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(pyfuse3.default_options)
 | 
			
		||||
    fuse_options.add('fsname=tmpfs')
 | 
			
		||||
    fuse_options.discard('default_permissions')
 | 
			
		||||
    if options.debug_fuse:
 | 
			
		||||
        fuse_options.add('debug')
 | 
			
		||||
    pyfuse3.init(operations, options.mountpoint, fuse_options)
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        trio.run(pyfuse3.main)
 | 
			
		||||
    except:
 | 
			
		||||
        pyfuse3.close(unmount=False)
 | 
			
		||||
        raise
 | 
			
		||||
 | 
			
		||||
    pyfuse3.close()
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user