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