1#!/bin/sh 2"""": # -*-python-*- 3# https://sourceware.org/bugzilla/show_bug.cgi?id=26034 4export "BUP_ARGV_0"="$0" 5arg_i=1 6for arg in "$@"; do 7 export "BUP_ARGV_${arg_i}"="$arg" 8 shift 9 arg_i=$((arg_i + 1)) 10done 11# Here to end of preamble replaced during install 12bup_python="$(dirname "$0")/../../config/bin/python" || exit $? 13exec "$bup_python" "$0" 14""" 15# end of bup preamble 16 17from __future__ import absolute_import, print_function 18import errno, os, sys 19 20try: 21 import fuse 22except ImportError: 23 print('error: cannot find the python "fuse" module; please install it', 24 file=sys.stderr) 25 sys.exit(2) 26if not hasattr(fuse, '__version__'): 27 print('error: fuse module is too old for fuse.__version__', file=sys.stderr) 28 sys.exit(2) 29fuse.fuse_python_api = (0, 2) 30 31if sys.version_info[0] > 2: 32 try: 33 fuse_ver = fuse.__version__.split('.') 34 fuse_ver_maj = int(fuse_ver[0]) 35 except: 36 log('error: cannot determine the fuse major version; please report', 37 file=sys.stderr) 38 sys.exit(2) 39 if len(fuse_ver) < 3 or fuse_ver_maj < 1: 40 print("error: fuse module can't handle binary data; please upgrade to 1.0+\n", 41 file=sys.stderr) 42 sys.exit(2) 43 44sys.path[:0] = [os.path.dirname(os.path.realpath(__file__)) + '/..'] 45 46from bup import compat, options, git, vfs, xstat 47from bup.compat import argv_bytes, fsdecode, py_maj 48from bup.helpers import log 49from bup.repo import LocalRepo 50 51 52# FIXME: self.meta and want_meta? 53 54# The path handling is just wrong, but the current fuse module can't 55# handle bytes paths. 56 57class BupFs(fuse.Fuse): 58 def __init__(self, repo, verbose=0, fake_metadata=False): 59 fuse.Fuse.__init__(self) 60 self.repo = repo 61 self.verbose = verbose 62 self.fake_metadata = fake_metadata 63 64 def getattr(self, path): 65 path = argv_bytes(path) 66 global opt 67 if self.verbose > 0: 68 log('--getattr(%r)\n' % path) 69 res = vfs.resolve(self.repo, path, want_meta=(not self.fake_metadata), 70 follow=False) 71 name, item = res[-1] 72 if not item: 73 return -errno.ENOENT 74 if self.fake_metadata: 75 item = vfs.augment_item_meta(self.repo, item, include_size=True) 76 else: 77 item = vfs.ensure_item_has_metadata(self.repo, item, 78 include_size=True) 79 meta = item.meta 80 # FIXME: do we want/need to do anything more with nlink? 81 st = fuse.Stat(st_mode=meta.mode, st_nlink=1, st_size=meta.size) 82 st.st_mode = meta.mode 83 st.st_uid = meta.uid or 0 84 st.st_gid = meta.gid or 0 85 st.st_atime = max(0, xstat.fstime_floor_secs(meta.atime)) 86 st.st_mtime = max(0, xstat.fstime_floor_secs(meta.mtime)) 87 st.st_ctime = max(0, xstat.fstime_floor_secs(meta.ctime)) 88 return st 89 90 def readdir(self, path, offset): 91 path = argv_bytes(path) 92 assert not offset # We don't return offsets, so offset should be unused 93 res = vfs.resolve(self.repo, path, follow=False) 94 dir_name, dir_item = res[-1] 95 if not dir_item: 96 yield -errno.ENOENT 97 yield fuse.Direntry('..') 98 # FIXME: make sure want_meta=False is being completely respected 99 for ent_name, ent_item in vfs.contents(repo, dir_item, want_meta=False): 100 fusename = fsdecode(ent_name.replace(b'/', b'-')) 101 yield fuse.Direntry(fusename) 102 103 def readlink(self, path): 104 path = argv_bytes(path) 105 if self.verbose > 0: 106 log('--readlink(%r)\n' % path) 107 res = vfs.resolve(self.repo, path, follow=False) 108 name, item = res[-1] 109 if not item: 110 return -errno.ENOENT 111 return fsdecode(vfs.readlink(repo, item)) 112 113 def open(self, path, flags): 114 path = argv_bytes(path) 115 if self.verbose > 0: 116 log('--open(%r)\n' % path) 117 res = vfs.resolve(self.repo, path, follow=False) 118 name, item = res[-1] 119 if not item: 120 return -errno.ENOENT 121 accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR 122 if (flags & accmode) != os.O_RDONLY: 123 return -errno.EACCES 124 # Return None since read doesn't need the file atm... 125 # If we *do* return the file, it'll show up as the last argument 126 #return vfs.fopen(repo, item) 127 128 def read(self, path, size, offset): 129 path = argv_bytes(path) 130 if self.verbose > 0: 131 log('--read(%r)\n' % path) 132 res = vfs.resolve(self.repo, path, follow=False) 133 name, item = res[-1] 134 if not item: 135 return -errno.ENOENT 136 with vfs.fopen(repo, item) as f: 137 f.seek(offset) 138 return f.read(size) 139 140 141optspec = """ 142bup fuse [-d] [-f] <mountpoint> 143-- 144f,foreground run in foreground 145d,debug run in the foreground and display FUSE debug information 146o,allow-other allow other users to access the filesystem 147meta report original metadata for paths when available 148v,verbose increase log output (can be used more than once) 149""" 150o = options.Options(optspec) 151opt, flags, extra = o.parse(compat.argv[1:]) 152if not opt.verbose: 153 opt.verbose = 0 154 155# Set stderr to be line buffered, even if it's not connected to the console 156# so that we'll be able to see diagnostics in a timely fashion. 157errfd = sys.stderr.fileno() 158sys.stderr.flush() 159sys.stderr = os.fdopen(errfd, 'w', 1) 160 161if len(extra) != 1: 162 o.fatal('only one mount point argument expected') 163 164git.check_repo_or_die() 165repo = LocalRepo() 166f = BupFs(repo=repo, verbose=opt.verbose, fake_metadata=(not opt.meta)) 167 168# This is likely wrong, but the fuse module doesn't currently accept bytes 169f.fuse_args.mountpoint = extra[0] 170 171if opt.debug: 172 f.fuse_args.add('debug') 173if opt.foreground: 174 f.fuse_args.setmod('foreground') 175f.multithreaded = False 176if opt.allow_other: 177 f.fuse_args.add('allow_other') 178f.main() 179