3 # https://sourceware.org/bugzilla/show_bug.cgi?id=26034
4 export "BUP_ARGV_0"="$0"
7 export "BUP_ARGV_${arg_i}"="$arg"
11 # Here to end of preamble replaced during install
12 bup_python="$(dirname "$0")/../../config/bin/python" || exit $?
13 exec "$bup_python" "$0"
17 from __future__ import absolute_import
19 # Intentionally replace the dirname "$0" that python prepends
21 sys.path[0] = os.path.dirname(os.path.realpath(__file__)) + '/..'
23 from binascii import hexlify, unhexlify
24 import struct, subprocess
26 from bup import compat, options, git, vfs, vint
27 from bup.compat import environ, hexstr
28 from bup.git import MissingObject
29 from bup.helpers import (Conn, debug1, debug2, linereader, lines_until_sentinel,
31 from bup.io import byte_stream, path_msg
32 from bup.repo import LocalRepo
36 dumb_server_mode = False
39 def do_help(conn, junk):
40 conn.write(b'Commands:\n %s\n' % b'\n '.join(sorted(commands)))
45 global dumb_server_mode
46 dumb_server_mode = os.path.exists(git.repo(b'bup-dumb-server'))
47 debug1('bup server: serving in %s mode\n'
48 % (dumb_server_mode and 'dumb' or 'smart'))
51 def _init_session(reinit_with_new_repopath=None):
53 if reinit_with_new_repopath is None and git.repodir:
57 git.check_repo_or_die(reinit_with_new_repopath)
61 # OK. we now know the path is a proper repository. Record this path in the
62 # environment so that subprocesses inherit it and know where to operate.
63 environ[b'BUP_DIR'] = git.repodir
64 debug1('bup server: bupdir is %s\n' % path_msg(git.repodir))
68 def init_dir(conn, arg):
70 debug1('bup server: bupdir initialized: %s\n' % path_msg(git.repodir))
75 def set_dir(conn, arg):
80 def list_indexes(conn, junk):
85 for f in os.listdir(git.repo(b'objects/pack')):
86 if f.endswith(b'.idx'):
87 conn.write(b'%s%s\n' % (f, suffix))
91 def send_index(conn, name):
93 assert name.find(b'/') < 0
94 assert name.endswith(b'.idx')
95 idx = git.open_idx(git.repo(b'objects/pack/%s' % name))
96 conn.write(struct.pack('!I', len(idx.map)))
101 def receive_objects_v2(conn, junk):
110 w = git.PackWriter(objcache_maker=None)
117 raise Exception('object read: expected length header, got EOF\n')
118 n = struct.unpack('!I', ns)[0]
119 #debug2('expecting %d bytes\n' % n)
121 debug1('bup server: received %d object%s.\n'
122 % (w.count, w.count!=1 and "s" or ''))
123 fullpath = w.close(run_midx=not dumb_server_mode)
125 (dir, name) = os.path.split(fullpath)
126 conn.write(b'%s.idx\n' % name)
129 elif n == 0xffffffff:
130 debug2('bup server: receive-objects suspended.\n')
136 crcr = struct.unpack('!I', conn.read(4))[0]
138 buf = conn.read(n) # object sizes in bup are reasonably small
139 #debug2('read %d bytes\n' % n)
140 _check(w, n, len(buf), 'object read: expected %d bytes, got %d\n')
141 if not dumb_server_mode:
142 oldpack = w.exists(shar, want_source=True)
144 assert(not oldpack == True)
145 assert(oldpack.endswith(b'.idx'))
146 (dir,name) = os.path.split(oldpack)
147 if not (name in suggested):
148 debug1("bup server: suggesting index %s\n"
149 % git.shorten_hash(name).decode('ascii'))
150 debug1("bup server: because of object %s\n"
152 conn.write(b'index %s\n' % name)
155 nw, crc = w._raw_write((buf,), sha=shar)
156 _check(w, crcr, crc, 'object read: expected crc %d, got %d\n')
160 def _check(w, expected, actual, msg):
161 if expected != actual:
163 raise Exception(msg % (expected, actual))
166 def read_ref(conn, refname):
168 r = git.read_ref(refname)
169 conn.write(b'%s\n' % hexlify(r) if r else b'')
173 def update_ref(conn, refname):
175 newval = conn.readline().strip()
176 oldval = conn.readline().strip()
177 git.update_ref(refname, unhexlify(newval), unhexlify(oldval))
183 for blob in git.cp().join(id):
184 conn.write(struct.pack('!I', len(blob)))
186 except KeyError as e:
187 log('server: error: %s\n' % e)
188 conn.write(b'\0\0\0\0')
191 conn.write(b'\0\0\0\0')
194 def cat_batch(conn, dummy):
197 # For now, avoid potential deadlock by just reading them all
198 for ref in tuple(lines_until_sentinel(conn, b'\n', Exception)):
200 it = cat_pipe.get(ref)
203 conn.write(b'missing\n')
205 conn.write(b'%s %s %d\n' % info)
210 def refs(conn, args):
211 limit_to_heads, limit_to_tags = args.split()
212 assert limit_to_heads in (b'0', b'1')
213 assert limit_to_tags in (b'0', b'1')
214 limit_to_heads = int(limit_to_heads)
215 limit_to_tags = int(limit_to_tags)
217 patterns = tuple(x[:-1] for x in lines_until_sentinel(conn, b'\n', Exception))
218 for name, oid in git.list_refs(patterns=patterns,
219 limit_to_heads=limit_to_heads,
220 limit_to_tags=limit_to_tags):
221 assert b'\n' not in name
222 conn.write(b'%s %s\n' % (hexlify(oid), name))
226 def rev_list(conn, _):
228 count = conn.readline()
230 raise Exception('Unexpected EOF while reading rev-list count')
231 assert count == b'\n'
233 fmt = conn.readline()
235 raise Exception('Unexpected EOF while reading rev-list format')
236 fmt = None if fmt == b'\n' else fmt[:-1]
237 refs = tuple(x[:-1] for x in lines_until_sentinel(conn, b'\n', Exception))
238 args = git.rev_list_invocation(refs, format=fmt)
239 p = subprocess.Popen(args, env=git._gitenv(git.repodir),
240 stdout=subprocess.PIPE)
242 out = p.stdout.read(64 * 1024)
247 rv = p.wait() # not fatal
249 msg = 'git rev-list returned error %d' % rv
254 def resolve(conn, args):
256 (flags,) = args.split()
258 want_meta = bool(flags & 1)
259 follow = bool(flags & 2)
260 have_parent = bool(flags & 4)
261 parent = vfs.read_resolution(conn) if have_parent else None
262 path = vint.read_bvec(conn)
264 raise Exception('Empty resolve path')
266 res = list(vfs.resolve(repo, path, parent=parent, want_meta=want_meta,
268 except vfs.IOError as ex:
270 if isinstance(res, vfs.IOError):
271 conn.write(b'\x00') # error
272 vfs.write_ioerror(conn, res)
274 conn.write(b'\x01') # success
275 vfs.write_resolution(conn, res)
281 o = options.Options(optspec)
282 (opt, flags, extra) = o.parse(compat.argv[1:])
285 o.fatal('no arguments expected')
287 debug2('bup server: reading from stdin.\n')
292 b'init-dir': init_dir,
294 b'list-indexes': list_indexes,
295 b'send-index': send_index,
296 b'receive-objects-v2': receive_objects_v2,
297 b'read-ref': read_ref,
298 b'update-ref': update_ref,
300 b'cat': join, # apocryphal alias
301 b'cat-batch' : cat_batch,
303 b'rev-list': rev_list,
307 # FIXME: this protocol is totally lame and not at all future-proof.
308 # (Especially since we abort completely as soon as *anything* bad happens)
310 conn = Conn(byte_stream(sys.stdin), byte_stream(sys.stdout))
311 lr = linereader(conn)
316 debug1('bup server: command: %r\n' % line)
317 words = line.split(b' ', 1)
319 rest = len(words)>1 and words[1] or b''
323 cmd = commands.get(cmd)
327 raise Exception('unknown server command: %r\n' % line)
329 debug1('bup server: done\n')