fusetoys

various hacky fuse filesystem utilities
git clone https://a3nm.net/git/fusetoys/
Log | Files | Refs | README

commit 470c7a0926e72f5eaf1d0d98970341afc2524a31
Author: Antoine Amarilli <a3nm@a3nm.net>
Date:   Fri, 28 Dec 2012 17:04:12 +0100

initial commit

Diffstat:
cachefs.py | 271+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 271 insertions(+), 0 deletions(-)

diff --git a/cachefs.py b/cachefs.py @@ -0,0 +1,271 @@ +#!/usr/bin/python + +# inspired by +# https://github.com/terencehonles/fusepy/blob/master/examples/loopback.py + +from fuse import Fuse +import fuse +import errno +import logging +import os +import shutil +import sys +from threading import Lock + +class __Struct: + def __init__(self, **kw): + for k, v in kw.iteritems(): + setattr(self, k, v) + +class Stat(__Struct): pass + +class FileTooBigException: + pass + +fuse.fuse_python_api = (0, 2) + +class CacheFS(fuse.Fuse): + #def __init__(self, source, cache, db): + # self.source = source + # self.cache = cache + # self.db = db + # self.rwlock = Lock() + + def __init__(self, *args, **kw): + fuse.Fuse.__init__(self, *args, **kw) + + self.rwlock = Lock() + + # Initialize a Logger() object to handle logging. + self.logger = logging.getLogger('cachefs') + self.logger.setLevel(logging.INFO) + self.logger.addHandler(logging.StreamHandler(sys.stderr)) + self.parser.add_option('--source', dest='source', metavar='SOURCE', + help="source") + self.parser.add_option('--cache', dest='cache', metavar='CACHE', + help="cache") + self.parser.add_option('--db', dest='db', metavar='DB', + help="db") + self.parser.add_option('--size', dest='size', metavar='SIZE', + type='int', help="size") + + def fsinit(self): + options = self.cmdline[0] + self.source = options.source + self.cache = options.cache + self.db = options.db + self.size = options.size + + def sourcePath(self, path): + return os.path.join(self.source, "./"+path) + #TODO + #return self.source+path + + def cachePath(self, path): + #TODO + return os.path.join(self.cache, "./"+path) + #return self.cache+path + + def statvfsCache(self): + return os.statvfs(self.cache) + + def availableCache(self): + # TODO account the files, there is no better solution... + stv = self.statvfsCache() + return stv.f_bsize * stv.f_bavail + + def totalCache(self): + stv = self.statvfsCache() + return stv.f_frsize * stv.f_blocks + + def nextTarget(self): + """return the next cached file to remove""" + return None # TODO + + def makeRoom(self, bytes): + # TODO maybe don't flush all the cache for a big file + if bytes > self.totalCache(): + raise FileTooBigException() + while bytes > self.availableCache(): + os.unlink(self.nextTarget()) + + def registerHit(self, path): + """register a hit for path in the cache""" + + def isCached(self, path): + """is a path cached?""" + if os.path.exists(self.cachePath(path)): + statOriginal = os.stat(self.cachePath(path)) + statCache = os.stat(self.cachePath(path)) + if statOriginal.st_size == statCache.st_size: + # the cache file is good + # TODO better checks + return True + return False + + def prepare(self, path): + #if not flags & os.O_RDONLY: + # return self.sourcePath(path) + self.registerHit(path) + print "PREPARATION" + if not os.path.exists(self.sourcePath(path)): + # no such original file, let the source handle it + print "*** original missing" + return self.sourcePath(path) + if self.isCached(path): + print "*** already cached" + return self.cachePath(path) + statOriginal = os.stat(self.sourcePath(path)) + # cache the file and then open it + print "PREPARATIONca" + with self.rwlock: + try: + print "*** make room" + self.makeRoom(statOriginal.st_size) + except FileTooBigException: + # no room to cache, open the original file + return self.sourcePath(path) + print ("*** docopy from %s to %s" % (self.sourcePath(path), + self.cachePath(path))) + shutil.copy2(self.sourcePath(path), self.cachePath(path)) + return self.cachePath(path) + + def access(self, path, mode): + if not os.access(self.sourcePath(path), mode): + return -errno.EACCES + + def chmod(self, path, mode): + wasCached = self.isCached(path) + retval = os.chmod(self.sourcePath(path), mode) + if wasCached: + os.chmod(self.cachePath(path), mode) # ignore errors + return retval + + def chown(self, path, mode): + wasCached = self.isCached(path) + retval = os.chown(self.sourcePath(path), mode) + if wasCached: + os.chown(self.cachePath(path), mode) # ignore errors + return retval + + def create(self, path, flags, mode): + return os.open(self.sourcePath(path), os.O_WRONLY | os.O_CREAT, mode) + + def getattr(self, path, fh=None): + st = os.lstat(self.sourcePath(path)) + return st + + def link(self, target, source): + os.link(self.sourcePath(source), self.sourcePath(target)) + + def mkdir(self, path, mode): + return os.mkdir(self.sourcePath(path), mode) + + def mknod(self, filename): + return os.mknod(self.sourcePath(filename)) + + def open(self, path, flags): + #print("will call open %s %s" % (self.prepare(path), flags)) + #return os.open(self.prepare(path), flags) + return 0 + + def read(self, path, size, offset): + f = self.prepare(path) # TODO hmm + with self.rwlock: + fh = os.open(f, os.O_RDONLY) + os.lseek(fh, offset, 0) + x = os.read(fh, size) + os.close(fh) + return x + + def readdir(self, path, fh): + path = self.sourcePath(path) + myIno = os.stat(path).st_ino + yield fuse.Direntry('.', ino=myIno) + parentIno = os.stat(os.path.join(path, "..")).st_ino + yield fuse.Direntry('..', ino=parentIno) + for name in os.listdir(path): + ino = os.stat(os.path.join(path, name)).st_ino + yield fuse.Direntry(name, ino=ino) + + def readlink(self, path): + return os.readlink(self.sourcePath(path)) + + def release(self, path, fh): + return os.close(fh) + + def rename(self, old, new): + wasCached = self.isCached(old) + retval = os.rename(self.sourcePath(old), self.sourcePath(new)) + if wasCached: + os.rename(self.cachePath(old), self.cachePath(new)) + return retval + + def rmdir(self, path): + return os.rmdir(self.sourcePath(path)) + + def statfs(self, path): + stv = os.statvfs(self.sourcePath(path)) + return stv + + def symlink(self, target, source): + return os.symlink(self.sourcePath(source), self.sourcePath(target)) + + def doTruncate(self, path, length): + with open(path, 'r+') as f: + f.truncate(length) + + def truncate(self, path, length, fh=None): + wasCached = self.isCached(path) + self.doTruncate(self.sourcePath(path), length) + if wasCached: + self.doTruncate(self.cachePath(path), length) + + def unlink(self, path): + wasCached = self.isCached(path) + retval = os.unlink(self.sourcePath(path)) + if wasCached: + os.unlink(self.cachePath(path)) + return retval + + def utimens(self, path, ts_acc, ts_mod): + wasCached = self.isCached(path) + times = (ts_acc.tv_sec, ts_mod.tv_sec) + retval = os.utime(self.sourcePath(path), times) + if wasCached: + os.utime(self.cachePath(path), times) + return retval + + def doWrite(self, path, data, offset): + with self.rwlock: + fh = os.open(path, os.O_WRONLY) + os.lseek(fh, offset, 0) + x = os.write(fh, data) + os.close(fh) + return x + + def write(self, path, data, offset): + wasCached = self.isCached(path) + print "writing to a %s file" % ("cached" if wasCached else "notcached") + retval = self.doWrite(self.sourcePath(path), data, offset) + if retval > 0 and wasCached: + self.doWrite(self.cachePath(path), data, offset) + return retval + + + + + + + +if __name__ == "__main__": + #if len(sys.argv) != 6: + # print("Usage: %s SOURCE CACHE SIZE DB MOUNTPOINT" % sys.argv[0]) + # sys.exit(1) + #logging.getLogger().setLevel(logging.DEBUG) + #cachefs = CacheFS(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]) + + cachefs = CacheFS() + fuse_opts = cachefs.parse(['-o', 'fsname=cachefs'] + sys.argv[1:]) + cachefs.main() +