3 bup_python="$(dirname "$0")/bup-python" || exit $?
4 exec "$bup_python" "$0" ${1+"$@"}
8 from __future__ import absolute_import
9 from binascii import hexlify, unhexlify
10 import os, sys, struct, subprocess
12 from bup import options, git, vfs, vint
13 from bup.compat import environ, hexstr
14 from bup.git import MissingObject
15 from bup.helpers import (Conn, debug1, debug2, linereader, lines_until_sentinel,
17 from bup.io import byte_stream, path_msg
18 from bup.repo import LocalRepo
22 dumb_server_mode = False
25 def do_help(conn, junk):
26 conn.write(b'Commands:\n %s\n' % b'\n '.join(sorted(commands)))
31 global dumb_server_mode
32 dumb_server_mode = os.path.exists(git.repo(b'bup-dumb-server'))
33 debug1('bup server: serving in %s mode\n'
34 % (dumb_server_mode and 'dumb' or 'smart'))
37 def _init_session(reinit_with_new_repopath=None):
39 if reinit_with_new_repopath is None and git.repodir:
43 git.check_repo_or_die(reinit_with_new_repopath)
47 # OK. we now know the path is a proper repository. Record this path in the
48 # environment so that subprocesses inherit it and know where to operate.
49 environ[b'BUP_DIR'] = git.repodir
50 debug1('bup server: bupdir is %s\n' % path_msg(git.repodir))
54 def init_dir(conn, arg):
56 debug1('bup server: bupdir initialized: %s\n' % path_msg(git.repodir))
61 def set_dir(conn, arg):
66 def list_indexes(conn, junk):
71 for f in os.listdir(git.repo(b'objects/pack')):
72 if f.endswith(b'.idx'):
73 conn.write(b'%s%s\n' % (f, suffix))
77 def send_index(conn, name):
79 assert name.find(b'/') < 0
80 assert name.endswith(b'.idx')
81 idx = git.open_idx(git.repo(b'objects/pack/%s' % name))
82 conn.write(struct.pack('!I', len(idx.map)))
87 def receive_objects_v2(conn, junk):
96 w = git.PackWriter(objcache_maker=None)
103 raise Exception('object read: expected length header, got EOF\n')
104 n = struct.unpack('!I', ns)[0]
105 #debug2('expecting %d bytes\n' % n)
107 debug1('bup server: received %d object%s.\n'
108 % (w.count, w.count!=1 and "s" or ''))
109 fullpath = w.close(run_midx=not dumb_server_mode)
111 (dir, name) = os.path.split(fullpath)
112 conn.write(b'%s.idx\n' % name)
115 elif n == 0xffffffff:
116 debug2('bup server: receive-objects suspended.\n')
122 crcr = struct.unpack('!I', conn.read(4))[0]
124 buf = conn.read(n) # object sizes in bup are reasonably small
125 #debug2('read %d bytes\n' % n)
126 _check(w, n, len(buf), 'object read: expected %d bytes, got %d\n')
127 if not dumb_server_mode:
128 oldpack = w.exists(shar, want_source=True)
130 assert(not oldpack == True)
131 assert(oldpack.endswith(b'.idx'))
132 (dir,name) = os.path.split(oldpack)
133 if not (name in suggested):
134 debug1("bup server: suggesting index %s\n"
135 % git.shorten_hash(name).decode('ascii'))
136 debug1("bup server: because of object %s\n"
138 conn.write(b'index %s\n' % name)
141 nw, crc = w._raw_write((buf,), sha=shar)
142 _check(w, crcr, crc, 'object read: expected crc %d, got %d\n')
146 def _check(w, expected, actual, msg):
147 if expected != actual:
149 raise Exception(msg % (expected, actual))
152 def read_ref(conn, refname):
154 r = git.read_ref(refname)
155 conn.write(b'%s\n' % hexlify(r) if r else b'')
159 def update_ref(conn, refname):
161 newval = conn.readline().strip()
162 oldval = conn.readline().strip()
163 git.update_ref(refname, unhexlify(newval), unhexlify(oldval))
169 for blob in git.cp().join(id):
170 conn.write(struct.pack('!I', len(blob)))
172 except KeyError as e:
173 log('server: error: %s\n' % e)
174 conn.write(b'\0\0\0\0')
177 conn.write(b'\0\0\0\0')
180 def cat_batch(conn, dummy):
183 # For now, avoid potential deadlock by just reading them all
184 for ref in tuple(lines_until_sentinel(conn, b'\n', Exception)):
186 it = cat_pipe.get(ref)
189 conn.write(b'missing\n')
191 conn.write(b'%s %s %d\n' % info)
196 def refs(conn, args):
197 limit_to_heads, limit_to_tags = args.split()
198 assert limit_to_heads in (b'0', b'1')
199 assert limit_to_tags in (b'0', b'1')
200 limit_to_heads = int(limit_to_heads)
201 limit_to_tags = int(limit_to_tags)
203 patterns = tuple(x[:-1] for x in lines_until_sentinel(conn, b'\n', Exception))
204 for name, oid in git.list_refs(patterns=patterns,
205 limit_to_heads=limit_to_heads,
206 limit_to_tags=limit_to_tags):
207 assert b'\n' not in name
208 conn.write(b'%s %s\n' % (hexlify(oid), name))
212 def rev_list(conn, _):
214 count = conn.readline()
216 raise Exception('Unexpected EOF while reading rev-list count')
217 count = None if count == b'\n' else int(count)
218 fmt = conn.readline()
220 raise Exception('Unexpected EOF while reading rev-list format')
221 fmt = None if fmt == b'\n' else fmt[:-1]
222 refs = tuple(x[:-1] for x in lines_until_sentinel(conn, b'\n', Exception))
223 args = git.rev_list_invocation(refs, count=count, format=fmt)
224 p = subprocess.Popen(git.rev_list_invocation(refs, count=count, format=fmt),
225 env=git._gitenv(git.repodir),
226 stdout=subprocess.PIPE)
228 out = p.stdout.read(64 * 1024)
233 rv = p.wait() # not fatal
235 msg = 'git rev-list returned error %d' % rv
240 def resolve(conn, args):
242 (flags,) = args.split()
244 want_meta = bool(flags & 1)
245 follow = bool(flags & 2)
246 have_parent = bool(flags & 4)
247 parent = vfs.read_resolution(conn) if have_parent else None
248 path = vint.read_bvec(conn)
250 raise Exception('Empty resolve path')
252 res = list(vfs.resolve(repo, path, parent=parent, want_meta=want_meta,
254 except vfs.IOError as ex:
256 if isinstance(res, vfs.IOError):
257 conn.write(b'\x00') # error
258 vfs.write_ioerror(conn, res)
260 conn.write(b'\x01') # success
261 vfs.write_resolution(conn, res)
267 o = options.Options(optspec)
268 (opt, flags, extra) = o.parse(sys.argv[1:])
271 o.fatal('no arguments expected')
273 debug2('bup server: reading from stdin.\n')
278 b'init-dir': init_dir,
280 b'list-indexes': list_indexes,
281 b'send-index': send_index,
282 b'receive-objects-v2': receive_objects_v2,
283 b'read-ref': read_ref,
284 b'update-ref': update_ref,
286 b'cat': join, # apocryphal alias
287 b'cat-batch' : cat_batch,
289 b'rev-list': rev_list,
293 # FIXME: this protocol is totally lame and not at all future-proof.
294 # (Especially since we abort completely as soon as *anything* bad happens)
296 conn = Conn(byte_stream(sys.stdin), byte_stream(sys.stdout))
297 lr = linereader(conn)
302 debug1('bup server: command: %r\n' % line)
303 words = line.split(b' ', 1)
305 rest = len(words)>1 and words[1] or b''
309 cmd = commands.get(cmd)
313 raise Exception('unknown server command: %r\n' % line)
315 debug1('bup server: done\n')