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