commit 2159a0d8592704b5bc866098d44ff3b812a8f771
parent 62cef99031565e1f73d071bc65b46c47ed613eaf
Author: Antoine Amarilli <a3nm@a3nm.net>
Date: Sun, 20 Aug 2023 09:14:22 -0700
mergefs
Diffstat:
mergefs.py | | | 179 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
tabs | | | 4 | ++++ |
2 files changed, 183 insertions(+), 0 deletions(-)
diff --git a/mergefs.py b/mergefs.py
@@ -0,0 +1,179 @@
+#!/usr/bin/python
+
+"""MergeFS FUSE filesystem"""
+# Does not really work! just an experiment
+
+import fuse
+import errno
+import os
+import sys
+import threading
+import logging
+
+logging.basicConfig(filename='app.log', filemode='w', format='%(name)s - %(levelname)s - %(message)s', level=logging.DEBUG)
+logging.debug("test debug log")
+
+fuse.fuse_python_api = (0, 2)
+
+class MergeFS(fuse.Fuse):
+ def __init__(self, *args, **kw):
+ fuse.Fuse.__init__(self, *args, **kw)
+
+ self.rwlock = threading.Lock()
+
+ def fsinit(self):
+ self.source = self.cmdline[1][0]
+
+ def baseSourcePath(self, path):
+ return os.path.join(self.source, path[1:])
+
+ def rawSourcePath(self, path):
+ # used for new files
+ return os.path.join(self.source, path[1:])
+
+ def sourcePath(self, path):
+ logging.debug("going into sourcepath")
+ rawSource = self.rawSourcePath(path)
+ if path.endswith("glibc-hwcaps"):
+ return rawSource
+ if '.so.' in path:
+ return rawSource
+ if os.sep in path[1:]:
+ # not at root level
+ return rawSource
+ if len(path) <= 2:
+ # '.' ?
+ return rawSource
+ # ok, try to find the file in a subdirectory
+ if os.path.exists(rawSource):
+ return rawSource
+ logging.debug("%s does not exist" % rawSource)
+ dirs = []
+ for name in os.listdir(self.source):
+ if os.path.isdir(os.path.join(self.source, name)):
+ logging.debug("found dir: %s" % name)
+ dirs.append(name)
+ for dadir in dirs:
+ cand = os.path.join(self.source, os.path.join(dadir, path[1:]))
+ logging.debug("try cand: %s" % cand)
+ if os.path.exists(cand):
+ return cand
+ # does not exist anywhere
+ return rawSource
+
+ def access(self, path, mode):
+ logging.debug("got access call")
+ if not os.access(self.sourcePath(path), mode):
+ return -errno.EACCES
+
+ def chmod(self, path, mode):
+ logging.debug("got chmod call")
+ return os.chmod(self.sourcePath(path), mode)
+
+ def chown(self, path, mode):
+ logging.debug("got chown call")
+ return os.chown(self.sourcePath(path), mode)
+
+ def create(self, path, flags, mode):
+ logging.debug("got create call")
+ return os.open(self.rawSourcePath(path), os.O_WRONLY | os.O_CREAT, mode)
+
+ def getattr(self, path):
+ logging.debug("got getattr call")
+ return os.lstat(self.sourcePath(path))
+
+ def link(self, target, source):
+ logging.debug("got link call")
+ os.link(self.sourcePath(source), self.rawSourcePath(target))
+
+ def mkdir(self, path, mode):
+ logging.debug("got mkdir call")
+ return os.mkdir(self.sourcePath(path), mode)
+
+ def mknod(self, path, mode, rdev):
+ logging.debug("got mknod call")
+ return os.mknod(self.sourcePath(path), mode, rdev)
+
+ def open(self, path, flags):
+ logging.debug("got open call")
+ return 0
+
+ def read(self, path, size, offset):
+ logging.debug("got read call")
+ with self.rwlock:
+ fh = os.open(self.sourcePath(path), os.O_RDONLY)
+ os.lseek(fh, offset, 0)
+ x = os.read(fh, size)
+ os.close(fh)
+ return x
+
+ def readdir(self, path, offset):
+ logging.debug("got readdir call")
+ path = self.sourcePath(path)
+ myIno = os.lstat(path).st_ino
+ print ("will yield dot")
+ yield fuse.Direntry('.', ino=myIno)
+ print ("will yield parent")
+ try:
+ parentIno = os.lstat(os.path.join(path, "..")).st_ino
+ print ("yielded parent")
+ except OSError as e:
+ parentIno = myIno # root
+ print ("faked parent")
+ yield fuse.Direntry('..', ino=parentIno)
+ print ("will yield children")
+ for name in os.listdir(path):
+ print ("yield %s" % os.path.join(path, name))
+ ino = os.lstat(os.path.join(path, name)).st_ino
+ yield fuse.Direntry(name, ino=ino)
+ print ("alldone")
+
+ def readlink(self, path):
+ logging.debug("got readlink call")
+ return os.readlink(self.sourcePath(path))
+
+ def rename(self, old, new):
+ logging.debug("got rename call")
+ return os.rename(self.sourcePath(old), self.rawSourcePath(new))
+
+ def rmdir(self, path):
+ logging.debug("got rmdir call")
+ return os.rmdir(self.sourcePath(path))
+
+ def statfs(self):
+ logging.debug("got statfs call")
+ return os.statvfs(self.sourceRoot)
+
+ def symlink(self, target, source):
+ logging.debug("got symlink call")
+ return os.symlink(self.sourcePath(source), self.rawSourcePath(target))
+
+ def truncate(self, path, length, fh=None):
+ logging.debug("got truncate call")
+ with open(self.sourcePath(path), 'r+') as f:
+ return f.truncate(length)
+
+ def unlink(self, path):
+ logging.debug("got unlink call")
+ return os.unlink(self.sourcePath(path))
+
+ def utimens(self, path, ts_acc, ts_mod):
+ logging.debug("got utimens call")
+ times = (ts_acc.tv_sec, ts_mod.tv_sec)
+ return os.utime(self.sourcePath(path), times)
+
+ def write(self, path, data, offset):
+ logging.debug("got write call")
+ with self.rwlock:
+ fh = os.open(self.sourcePath(path), os.O_WRONLY)
+ os.lseek(fh, offset, 0)
+ x = os.write(fh, data)
+ os.close(fh)
+ return x
+
+
+if __name__ == "__main__":
+ mergefs = MergeFS()
+ fuse_opts = mergefs.parse(['-o', 'fsname=mergefs'] + sys.argv[1:])
+ mergefs.main()
+
diff --git a/tabs b/tabs
@@ -2,3 +2,7 @@ https://github.com/xolox/dedupfs/blob/master/dedupfs.py
http://docs.python.org/2/library/shutil.html
http://docs.python.org/2/library/os.path.html
http://docs.python.org/2/library/os.html#os.lstat
+https://github.com/skorokithakis/python-fuse-sample but does not work for some reason
+https://thepythoncorner.com/posts/2017-02-27-writing-a-fuse-filesystem-in-python/
+https://stackoverflow.com/questions/52925566/which-module-is-the-actual-interface-to-fuse-from-python-3
+apparently python fuse bindings are unmaintained