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