X-Git-Url: https://arthur.barton.de/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=cmd%2Ffuse-cmd.py;h=2eb28fbcf8c0293844456c51cf9b4c2bdde09182;hb=093752b42c5548028c6f84c67f7741b2321c512f;hp=450d366eb311ca2e9cf53b2627017892d429524b;hpb=2ff672c48f3dbde761b30775adcdc76a621df527;p=bup.git diff --git a/cmd/fuse-cmd.py b/cmd/fuse-cmd.py index 450d366..2eb28fb 100755 --- a/cmd/fuse-cmd.py +++ b/cmd/fuse-cmd.py @@ -1,138 +1,167 @@ -#!/usr/bin/env python +#!/bin/sh +"""": # -*-python-*- +bup_python="$(dirname "$0")/bup-python" || exit $? +exec "$bup_python" "$0" ${1+"$@"} +""" +# end of bup preamble + +from __future__ import absolute_import, print_function import sys, os, errno -from bup import options, git, vfs -from bup.helpers import * + try: import fuse except ImportError: - log('bup: error: The python "fuse" module is missing.\n' + - 'To use bup fuse, first install the python-fuse package.\n') - sys.exit(1) - - -class Stat(fuse.Stat): - def __init__(self): - self.st_mode = 0 - self.st_ino = 0 - self.st_dev = 0 - self.st_nlink = 0 - self.st_uid = 0 - self.st_gid = 0 - self.st_size = 0 - self.st_atime = 0 - self.st_mtime = 0 - self.st_ctime = 0 - self.st_blocks = 0 - self.st_blksize = 0 - self.st_rdev = 0 - - -cache = {} -def cache_get(top, path): - parts = path.split('/') - cache[('',)] = top - c = None - max = len(parts) - #log('cache: %r\n' % cache.keys()) - for i in range(max): - pre = parts[:max-i] - #log('cache trying: %r\n' % pre) - c = cache.get(tuple(pre)) - if c: - rest = parts[max-i:] - for r in rest: - #log('resolving %r from %r\n' % (r, c.fullname())) - c = c.lresolve(r) - key = tuple(pre + [r]) - #log('saving: %r\n' % (key,)) - cache[key] = c - break - assert(c) - return c - - + print('error: cannot find the python "fuse" module; please install it', + file=sys.stderr) + sys.exit(2) +if not hasattr(fuse, '__version__'): + print('error: fuse module is too old for fuse.__version__', file=sys.stderr) + sys.exit(2) +fuse.fuse_python_api = (0, 2) + +if sys.version_info[0] > 2: + try: + fuse_ver = fuse.__version__.split('.') + fuse_ver_maj = int(fuse_ver[0]) + except: + log('error: cannot determine the fuse major version; please report', + file=sys.stderr) + sys.exit(2) + if len(fuse_ver) < 3 or fuse_ver_maj < 1: + print("error: fuse module can't handle binary data; please upgrade to 1.0+\n", + file=sys.stderr) + sys.exit(2) + +from bup import options, git, vfs, xstat +from bup.compat import argv_bytes, fsdecode, py_maj +from bup.helpers import log +from bup.repo import LocalRepo + + +# FIXME: self.meta and want_meta? + +# The path handling is just wrong, but the current fuse module can't +# handle bytes paths. class BupFs(fuse.Fuse): - def __init__(self, top): + def __init__(self, repo, verbose=0, fake_metadata=False): fuse.Fuse.__init__(self) - self.top = top + self.repo = repo + self.verbose = verbose + self.fake_metadata = fake_metadata def getattr(self, path): - log('--getattr(%r)\n' % path) - try: - node = cache_get(self.top, path) - st = Stat() - st.st_mode = node.mode - st.st_nlink = node.nlinks() - st.st_size = node.size() - st.st_mtime = node.mtime - st.st_ctime = node.ctime - st.st_atime = node.atime - return st - except vfs.NoSuchFile: + path = argv_bytes(path) + global opt + if self.verbose > 0: + log('--getattr(%r)\n' % path) + res = vfs.resolve(self.repo, path, want_meta=(not self.fake_metadata), + follow=False) + name, item = res[-1] + if not item: return -errno.ENOENT + if self.fake_metadata: + item = vfs.augment_item_meta(self.repo, item, include_size=True) + else: + item = vfs.ensure_item_has_metadata(self.repo, item, + include_size=True) + meta = item.meta + # FIXME: do we want/need to do anything more with nlink? + st = fuse.Stat(st_mode=meta.mode, st_nlink=1, st_size=meta.size) + st.st_mode = meta.mode + st.st_uid = meta.uid or 0 + st.st_gid = meta.gid or 0 + st.st_atime = max(0, xstat.fstime_floor_secs(meta.atime)) + st.st_mtime = max(0, xstat.fstime_floor_secs(meta.mtime)) + st.st_ctime = max(0, xstat.fstime_floor_secs(meta.ctime)) + return st def readdir(self, path, offset): - log('--readdir(%r)\n' % path) - node = cache_get(self.top, path) - yield fuse.Direntry('.') + path = argv_bytes(path) + assert not offset # We don't return offsets, so offset should be unused + res = vfs.resolve(self.repo, path, follow=False) + dir_name, dir_item = res[-1] + if not dir_item: + yield -errno.ENOENT yield fuse.Direntry('..') - for sub in node.subs(): - yield fuse.Direntry(sub.name) + # FIXME: make sure want_meta=False is being completely respected + for ent_name, ent_item in vfs.contents(repo, dir_item, want_meta=False): + fusename = fsdecode(ent_name.replace(b'/', b'-')) + yield fuse.Direntry(fusename) def readlink(self, path): - log('--readlink(%r)\n' % path) - node = cache_get(self.top, path) - return node.readlink() + path = argv_bytes(path) + if self.verbose > 0: + log('--readlink(%r)\n' % path) + res = vfs.resolve(self.repo, path, follow=False) + name, item = res[-1] + if not item: + return -errno.ENOENT + return fsdecode(vfs.readlink(repo, item)) def open(self, path, flags): - log('--open(%r)\n' % path) - node = cache_get(self.top, path) + path = argv_bytes(path) + if self.verbose > 0: + log('--open(%r)\n' % path) + res = vfs.resolve(self.repo, path, follow=False) + name, item = res[-1] + if not item: + return -errno.ENOENT accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR if (flags & accmode) != os.O_RDONLY: return -errno.EACCES - node.open() - - def release(self, path, flags): - log('--release(%r)\n' % path) + # Return None since read doesn't need the file atm... + # If we *do* return the file, it'll show up as the last argument + #return vfs.fopen(repo, item) def read(self, path, size, offset): - log('--read(%r)\n' % path) - n = cache_get(self.top, path) - o = n.open() - o.seek(offset) - return o.read(size) - - -if not hasattr(fuse, '__version__'): - raise RuntimeError, "your fuse module is too old for fuse.__version__" -fuse.fuse_python_api = (0, 2) + path = argv_bytes(path) + if self.verbose > 0: + log('--read(%r)\n' % path) + res = vfs.resolve(self.repo, path, follow=False) + name, item = res[-1] + if not item: + return -errno.ENOENT + with vfs.fopen(repo, item) as f: + f.seek(offset) + return f.read(size) optspec = """ bup fuse [-d] [-f] -- -d,debug increase debug level f,foreground run in foreground +d,debug run in the foreground and display FUSE debug information o,allow-other allow other users to access the filesystem +meta report original metadata for paths when available +v,verbose increase log output (can be used more than once) """ -o = options.Options('bup fuse', optspec) -(opt, flags, extra) = o.parse(sys.argv[1:]) +o = options.Options(optspec) +opt, flags, extra = o.parse(sys.argv[1:]) +if not opt.verbose: + opt.verbose = 0 + +# Set stderr to be line buffered, even if it's not connected to the console +# so that we'll be able to see diagnostics in a timely fashion. +errfd = sys.stderr.fileno() +sys.stderr.flush() +sys.stderr = os.fdopen(errfd, 'w', 1) if len(extra) != 1: - o.fatal("exactly one argument expected") + o.fatal('only one mount point argument expected') git.check_repo_or_die() -top = vfs.RefList(None) -f = BupFs(top) +repo = LocalRepo() +f = BupFs(repo=repo, verbose=opt.verbose, fake_metadata=(not opt.meta)) + +# This is likely wrong, but the fuse module doesn't currently accept bytes f.fuse_args.mountpoint = extra[0] + if opt.debug: f.fuse_args.add('debug') if opt.foreground: f.fuse_args.setmod('foreground') -print f.multithreaded f.multithreaded = False if opt.allow_other: f.fuse_args.add('allow_other') - f.main()