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