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: 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 dirs, use hashed name + parent token
|
||||||
# TODO: ? Stop using tokens for files, use hashed name + short ?version-number + parent token instead
|
# 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: Decide how / when / from which class to push new blocks
|
||||||
# TODO: Unload 'overwritten' blobs
|
# TODO: Unload 'overwritten' blobs
|
||||||
# TODO: Close blobs when they become unknown to the kernel or when we unmount (genesis only on unmount)
|
# 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 import Iota, ProposedTransaction, Address, TryteString, Tag
|
||||||
from iota.crypto.addresses import AddressGenerator
|
from iota.crypto.addresses import AddressGenerator
|
||||||
@ -26,12 +24,25 @@ import random
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
import msgpack
|
import msgpack
|
||||||
import asyncio
|
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
import gzip
|
import gzip
|
||||||
import secrets
|
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
|
CHUNKSIZE = 2187
|
||||||
SYNCWRITES = True
|
SYNCWRITES = True
|
||||||
|
|
||||||
@ -39,8 +50,8 @@ def log(txt):
|
|||||||
print("[-] "+str(txt))
|
print("[-] "+str(txt))
|
||||||
|
|
||||||
def sendEmOff(bundles, api):
|
def sendEmOff(bundles, api):
|
||||||
print("[->]")
|
|
||||||
for bundle in bundles:
|
for bundle in bundles:
|
||||||
|
print("[->]")
|
||||||
api.send_trytes(
|
api.send_trytes(
|
||||||
trytes=bundle
|
trytes=bundle
|
||||||
)
|
)
|
||||||
@ -496,7 +507,11 @@ class Inode():
|
|||||||
def hasRef(self):
|
def hasRef(self):
|
||||||
return not self.ref==None
|
return not self.ref==None
|
||||||
|
|
||||||
class IotaFs():
|
class IotaFS(pyfuse3.Operations):
|
||||||
|
|
||||||
|
#supports_dot_lookup = True
|
||||||
|
enable_writeback_cache = True
|
||||||
|
|
||||||
def __init__(self, token) -> None:
|
def __init__(self, token) -> None:
|
||||||
self.api = Iota('https://nodes.thetangle.org:443', local_pow=True)
|
self.api = Iota('https://nodes.thetangle.org:443', local_pow=True)
|
||||||
# TODO Cache last known milestone-Index of genesis locally
|
# 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")
|
log("Unable to find reference to root: Creating new root")
|
||||||
self.genesis.mkdir("/")
|
self.genesis.mkdir("/")
|
||||||
log("Successfully Mounted!")
|
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
|
pass
|
||||||
|
|
||||||
api = Iota('https://nodes.thetangle.org:443', local_pow=True)
|
async def flush(self, fh):
|
||||||
token = b'testToken'
|
# flush the file at fh
|
||||||
genesis = TangleFileTreeElement("*", 0, token, api)
|
# 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:
|
if False:
|
||||||
genesis.mkdir("/")
|
genesis.mkdir("/")
|
||||||
@ -544,3 +630,18 @@ if False:
|
|||||||
sub.mkdir("subsub")
|
sub.mkdir("subsub")
|
||||||
|
|
||||||
print(genesis.tree())
|
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