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