aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xfusearchive.py301
1 files changed, 301 insertions, 0 deletions
diff --git a/fusearchive.py b/fusearchive.py
new file mode 100755
index 0000000..24beeeb
--- /dev/null
+++ b/fusearchive.py
@@ -0,0 +1,301 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2001 Jeff Epler <jepler@unpythonic.dhs.org>
+# Copyright (C) 2006 Csaba Henk <csaba.henk@creo.hu>
+#
+# This program can be distributed under the terms of the GNU LGPL.
+# See the file COPYING.
+#
+
+import os, sys
+from errno import *
+from stat import *
+import shutil
+import fcntl
+import fuse
+import re
+import tempfile
+from fuse import Fuse
+
+
+if not hasattr(fuse, '__version__'):
+ raise RuntimeError, \
+ "your fuse-py doesn't know of fuse.__version__, probably it's too old."
+
+fuse.fuse_python_api = (0, 2)
+
+fuse.feature_assert('stateful_files', 'has_init')
+
+
+def flag2mode(flags):
+ md = {os.O_RDONLY: 'r', os.O_WRONLY: 'w', os.O_RDWR: 'w+'}
+ m = md[flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR)]
+
+ if flags | os.O_APPEND:
+ m = m.replace('w', 'a', 1)
+
+ return m
+
+
+class FuseArchive(Fuse):
+
+ def __init__(self, *args, **kw):
+
+ Fuse.__init__(self, *args, **kw)
+ self.root = None
+
+ def getattr(self, path):
+ return os.lstat("./tree" + path)
+
+ def readlink(self, path):
+ return os.readlink("./tree" + path)
+
+ def readdir(self, path, offset):
+ for e in os.listdir("./tree" + path):
+ yield fuse.Direntry(e)
+
+ def unlink(self, path):
+ os.unlink("./tree" + path)
+
+ def rmdir(self, path):
+ os.rmdir("./tree" + path)
+
+ def symlink(self, path, path1):
+ os.symlink(path, "./tree" + path1)
+
+ def rename(self, path, path1):
+ os.rename("./tree" + path, "./tree" + path1)
+
+ def link(self, path, path1):
+ os.link("./tree" + path, "./tree" + path1)
+
+ def chmod(self, path, mode):
+ os.chmod("./tree" + path, mode)
+
+ def chown(self, path, user, group):
+ os.chown("./tree" + path, user, group)
+
+ def truncate(self, path, len):
+ f = open("./tree" + path, "a")
+ f.truncate(len)
+ f.close()
+
+ def mknod(self, path, mode, dev):
+ os.mknod("./tree" + path, mode, dev)
+
+ def mkdir(self, path, mode):
+ os.mkdir("./tree" + path, mode)
+
+ def utime(self, path, times):
+ os.utime("./tree" + path, times)
+
+# The following utimens method would do the same as the above utime method.
+# We can't make it better though as the Python stdlib doesn't know of
+# subsecond preciseness in acces/modify times.
+#
+# def utimens(self, path, ts_acc, ts_mod):
+# os.utime("." + path, (ts_acc.tv_sec, ts_mod.tv_sec))
+
+ def access(self, path, mode):
+ if not os.access("./tree" + path, mode):
+ return -EACCES
+
+# This is how we could add stub extended attribute handlers...
+# (We can't have ones which aptly delegate requests to the underlying fs
+# because Python lacks a standard xattr interface.)
+#
+# def getxattr(self, path, name, size):
+# val = name.swapcase() + '@' + path
+# if size == 0:
+# # We are asked for size of the value.
+# return len(val)
+# return val
+#
+# def listxattr(self, path, size):
+# # We use the "user" namespace to please XFS utils
+# aa = ["user." + a for a in ("foo", "bar")]
+# if size == 0:
+# # We are asked for size of the attr list, ie. joint size of attrs
+# # plus null separators.
+# return len("".join(aa)) + len(aa)
+# return aa
+
+ def statfs(self):
+ """
+ Should return an object with statvfs attributes (f_bsize, f_frsize...).
+ Eg., the return value of os.statvfs() is such a thing (since py 2.2).
+ If you are not reusing an existing statvfs object, start with
+ fuse.StatVFS(), and define the attributes.
+
+ To provide usable information (ie., you want sensible df(1)
+ output, you are suggested to specify the following attributes:
+
+ - f_bsize - preferred size of file blocks, in bytes
+ - f_frsize - fundamental size of file blcoks, in bytes
+ [if you have no idea, use the same as blocksize]
+ - f_blocks - total number of blocks in the filesystem
+ - f_bfree - number of free blocks
+ - f_files - total number of file inodes
+ - f_ffree - nunber of free file inodes
+ """
+
+ return os.statvfs(".")
+
+ def fsinit(self):
+ os.chdir(self.root)
+
+ class FuseArchiveFile(object):
+
+ def __init__(self, path, flags, *mode):
+ # Inflate the file
+ print "Init file: " + path
+ self.orig_path = path;
+ ( fdnum, self.tmp_name ) = tempfile.mkstemp();
+ os.close( fdnum );
+
+ if os.path.exists( ".tree/" + self.orig_path ):
+ shutil.copy( ".tree/" + path, self.tmp_name )
+
+ print "Shadow file: " + self.tmp_name + " for " + self.orig_path
+ self.file = os.fdopen( os.open( self.tmp_name, flags, *mode),
+ flag2mode(flags))
+ self.fd = self.file.fileno()
+
+ self.direct_io = False
+ self.keep_cache = False
+
+ def read(self, length, offset):
+ print "Reading from " + self.orig_path
+ self.file.seek(offset)
+ return self.file.read(length)
+
+ def write(self, buf, offset):
+ print "Writing to " + self.orig_path
+ self.file.seek(offset)
+ self.file.write(buf)
+ return len(buf)
+
+ def release(self, flags):
+ # Deflate the file
+ print "Release: " + self.orig_path
+ self.file.close()
+
+ print "Copying working file back to storage: " + \
+ self.tmp_name + " -> " + self.orig_path
+
+ shutil.copy( self.tmp_name, ".tree/" + self.orig_path );
+
+ print "Deleting old file: " + self.tmp_name
+ os.unlink( self.tmp_name );
+
+ def _fflush(self):
+ if 'w' in self.file.mode or 'a' in self.file.mode:
+ self.file.flush()
+
+ def fsync(self, isfsyncfile):
+ self._fflush()
+ if isfsyncfile and hasattr(os, 'fdatasync'):
+ os.fdatasync(self.fd)
+ else:
+ os.fsync(self.fd)
+
+ def flush(self):
+ self._fflush()
+ # cf. xmp_flush() in fusexmp_fh.c
+ os.close(os.dup(self.fd))
+
+ def fgetattr(self):
+ return os.fstat(self.fd)
+
+ def ftruncate(self, len):
+ self.file.truncate(len)
+
+ def lock(self, cmd, owner, **kw):
+ # The code here is much rather just a demonstration of the locking
+ # API than something which actually was seen to be useful.
+
+ # Advisory file locking is pretty messy in Unix, and the Python
+ # interface to this doesn't make it better.
+ # We can't do fcntl(2)/F_GETLK from Python in a platfrom independent
+ # way. The following implementation *might* work under Linux.
+ #
+ # if cmd == fcntl.F_GETLK:
+ # import struct
+ #
+ # lockdata = struct.pack('hhQQi', kw['l_type'], os.SEEK_SET,
+ # kw['l_start'], kw['l_len'], kw['l_pid'])
+ # ld2 = fcntl.fcntl(self.fd, fcntl.F_GETLK, lockdata)
+ # flockfields = ('l_type', 'l_whence', 'l_start', 'l_len', 'l_pid')
+ # uld2 = struct.unpack('hhQQi', ld2)
+ # res = {}
+ # for i in xrange(len(uld2)):
+ # res[flockfields[i]] = uld2[i]
+ #
+ # return fuse.Flock(**res)
+
+ # Convert fcntl-ish lock parameters to Python's weird
+ # lockf(3)/flock(2) medley locking API...
+ op = { fcntl.F_UNLCK : fcntl.LOCK_UN,
+ fcntl.F_RDLCK : fcntl.LOCK_SH,
+ fcntl.F_WRLCK : fcntl.LOCK_EX }[kw['l_type']]
+ if cmd == fcntl.F_GETLK:
+ return -EOPNOTSUPP
+ elif cmd == fcntl.F_SETLK:
+ if op != fcntl.LOCK_UN:
+ op |= fcntl.LOCK_NB
+ elif cmd == fcntl.F_SETLKW:
+ pass
+ else:
+ return -EINVAL
+
+ fcntl.lockf(self.fd, op, kw['l_start'], kw['l_len'])
+
+
+ def main(self, *a, **kw):
+
+ self.file_class = self.FuseArchiveFile
+
+ # This is where fragments go
+ if not os.path.exists( 'storage' ):
+ os.mkdir( 'storage' )
+
+ # This is where the real files exist
+ if not os.path.exists( 'tree' ):
+ os.mkdir( 'tree' )
+
+ return Fuse.main(self, *a, **kw)
+
+
+def main():
+
+ usage = """
+Userspace nullfs-alike: mirror the filesystem tree from some point on.
+
+""" + Fuse.fusage
+
+ server = FuseArchive(version="%prog " + fuse.__version__,
+ usage=usage,
+ dash_s_do='setsingle')
+
+ server.multithreaded = False
+
+ server.parse(values=server, errex=1)
+
+ if len(server.parser.largs) != 2:
+ print "Usage: " + sys.argv[0] + " storageDirectory mountDirectory"
+ sys.exit(1)
+
+ server.root = server.parser.largs[0]
+
+ try:
+ if server.fuse_args.mount_expected():
+ os.chdir(server.root)
+ except OSError:
+ print >> sys.stderr, "can't enter root of underlying filesystem"
+ sys.exit(1)
+
+ server.main()
+
+
+if __name__ == '__main__':
+ main()