]> arthur.barton.de Git - bup.git/blob - lib/bup/cmd/fuse.py
Update base_version to 0.34~ for 0.34 development
[bup.git] / lib / bup / cmd / fuse.py
1
2 from __future__ import absolute_import, print_function
3 import errno, os, sys
4
5 try:
6     import fuse
7 except ImportError:
8     print('error: cannot find the python "fuse" module; please install it',
9           file=sys.stderr)
10     sys.exit(2)
11 if not hasattr(fuse, '__version__'):
12     if hasattr(fuse, 'FUSE'):
13         print('error: python fuse module appears to be fusepy, not python-fuse\n'
14               '       please install https://github.com/libfuse/python-fuse',
15               file=sys.stderr)
16     else:
17         print('error: fuse module may need to be upgraded (no fuse.__version__)',
18               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
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         if self.verbose > 0:
56             log('--getattr(%r)\n' % path)
57         res = vfs.resolve(self.repo, path, want_meta=(not self.fake_metadata),
58                           follow=False)
59         name, item = res[-1]
60         if not item:
61             return -errno.ENOENT
62         if self.fake_metadata:
63             item = vfs.augment_item_meta(self.repo, item, include_size=True)
64         else:
65             item = vfs.ensure_item_has_metadata(self.repo, item,
66                                                 include_size=True)
67         meta = item.meta
68         # FIXME: do we want/need to do anything more with nlink?
69         st = fuse.Stat(st_mode=meta.mode, st_nlink=1, st_size=meta.size)
70         st.st_mode = meta.mode
71         st.st_uid = meta.uid or 0
72         st.st_gid = meta.gid or 0
73         st.st_atime = max(0, xstat.fstime_floor_secs(meta.atime))
74         st.st_mtime = max(0, xstat.fstime_floor_secs(meta.mtime))
75         st.st_ctime = max(0, xstat.fstime_floor_secs(meta.ctime))
76         return st
77
78     def readdir(self, path, offset):
79         path = argv_bytes(path)
80         assert not offset  # We don't return offsets, so offset should be unused
81         res = vfs.resolve(self.repo, path, follow=False)
82         dir_name, dir_item = res[-1]
83         if not dir_item:
84             yield -errno.ENOENT
85         yield fuse.Direntry('..')
86         # FIXME: make sure want_meta=False is being completely respected
87         for ent_name, ent_item in vfs.contents(self.repo, dir_item, want_meta=False):
88             fusename = fsdecode(ent_name.replace(b'/', b'-'))
89             yield fuse.Direntry(fusename)
90
91     def readlink(self, path):
92         path = argv_bytes(path)
93         if self.verbose > 0:
94             log('--readlink(%r)\n' % path)
95         res = vfs.resolve(self.repo, path, follow=False)
96         name, item = res[-1]
97         if not item:
98             return -errno.ENOENT
99         return fsdecode(vfs.readlink(self.repo, item))
100
101     def open(self, path, flags):
102         path = argv_bytes(path)
103         if self.verbose > 0:
104             log('--open(%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         accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
110         if (flags & accmode) != os.O_RDONLY:
111             return -errno.EACCES
112         # Return None since read doesn't need the file atm...
113         # If we *do* return the file, it'll show up as the last argument
114         #return vfs.fopen(repo, item)
115         return None
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(self.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
140 def main(argv):
141     o = options.Options(optspec)
142     opt, flags, extra = o.parse_bytes(argv[1:])
143     if not opt.verbose:
144         opt.verbose = 0
145
146     # Set stderr to be line buffered, even if it's not connected to the console
147     # so that we'll be able to see diagnostics in a timely fashion.
148     errfd = sys.stderr.fileno()
149     sys.stderr.flush()
150     sys.stderr = os.fdopen(errfd, 'w', 1)
151
152     if len(extra) != 1:
153         o.fatal('only one mount point argument expected')
154
155     git.check_repo_or_die()
156     with LocalRepo() as repo:
157         f = BupFs(repo=repo, verbose=opt.verbose, fake_metadata=(not opt.meta))
158
159         # This is likely wrong, but the fuse module doesn't currently accept bytes
160         f.fuse_args.mountpoint = extra[0]
161
162         if opt.debug:
163             f.fuse_args.add('debug')
164         if opt.foreground:
165             f.fuse_args.setmod('foreground')
166         f.multithreaded = False
167         if opt.allow_other:
168             f.fuse_args.add('allow_other')
169         f.main()