]> arthur.barton.de Git - bup.git/blob - cmd/server-cmd.py
Add BUP_DIR to the subprocess environment during set-dir on the server.
[bup.git] / cmd / server-cmd.py
1 #!/usr/bin/env python
2 import os, sys, struct
3 from bup import options, git
4 from bup.helpers import *
5
6 suspended_w = None
7 dumb_server_mode = False
8
9
10 def do_help(conn, junk):
11     conn.write('Commands:\n    %s\n' % '\n    '.join(sorted(commands)))
12     conn.ok()
13
14
15 def _set_mode():
16     global dumb_server_mode
17     dumb_server_mode = os.path.exists(git.repo('bup-dumb-server'))
18     debug1('bup server: serving in %s mode\n' 
19            % (dumb_server_mode and 'dumb' or 'smart'))
20
21
22 def init_dir(conn, arg):
23     git.init_repo(arg)
24     debug1('bup server: bupdir initialized: %r\n' % git.repodir)
25     _set_mode()
26     conn.ok()
27
28
29 def set_dir(conn, arg):
30     git.check_repo_or_die(arg)
31     # OK. we now know the path is a proper repository. Record this path in the
32     # environment so that subprocesses inherit it and know where to operate.
33     os.environ['BUP_DIR'] = arg
34     debug1('bup server: bupdir is %r\n' % git.repodir)
35     _set_mode()
36     conn.ok()
37
38     
39 def list_indexes(conn, junk):
40     git.check_repo_or_die()
41     suffix = ''
42     if dumb_server_mode:
43         suffix = ' load'
44     for f in os.listdir(git.repo('objects/pack')):
45         if f.endswith('.idx'):
46             conn.write('%s%s\n' % (f, suffix))
47     conn.ok()
48
49
50 def send_index(conn, name):
51     git.check_repo_or_die()
52     assert(name.find('/') < 0)
53     assert(name.endswith('.idx'))
54     idx = git.open_idx(git.repo('objects/pack/%s' % name))
55     conn.write(struct.pack('!I', len(idx.map)))
56     conn.write(idx.map)
57     conn.ok()
58
59
60 def receive_objects_v2(conn, junk):
61     global suspended_w
62     git.check_repo_or_die()
63     suggested = set()
64     if suspended_w:
65         w = suspended_w
66         suspended_w = None
67     else:
68         if dumb_server_mode:
69             w = git.PackWriter(objcache_maker=None)
70         else:
71             w = git.PackWriter()
72     while 1:
73         ns = conn.read(4)
74         if not ns:
75             w.abort()
76             raise Exception('object read: expected length header, got EOF\n')
77         n = struct.unpack('!I', ns)[0]
78         #debug2('expecting %d bytes\n' % n)
79         if not n:
80             debug1('bup server: received %d object%s.\n' 
81                 % (w.count, w.count!=1 and "s" or ''))
82             fullpath = w.close(run_midx=not dumb_server_mode)
83             if fullpath:
84                 (dir, name) = os.path.split(fullpath)
85                 conn.write('%s.idx\n' % name)
86             conn.ok()
87             return
88         elif n == 0xffffffff:
89             debug2('bup server: receive-objects suspended.\n')
90             suspended_w = w
91             conn.ok()
92             return
93             
94         shar = conn.read(20)
95         crcr = struct.unpack('!I', conn.read(4))[0]
96         n -= 20 + 4
97         buf = conn.read(n)  # object sizes in bup are reasonably small
98         #debug2('read %d bytes\n' % n)
99         _check(w, n, len(buf), 'object read: expected %d bytes, got %d\n')
100         if not dumb_server_mode:
101             oldpack = w.exists(shar, want_source=True)
102             if oldpack:
103                 assert(not oldpack == True)
104                 assert(oldpack.endswith('.idx'))
105                 (dir,name) = os.path.split(oldpack)
106                 if not (name in suggested):
107                     debug1("bup server: suggesting index %s\n"
108                            % git.shorten_hash(name))
109                     debug1("bup server:   because of object %s\n"
110                            % shar.encode('hex'))
111                     conn.write('index %s\n' % name)
112                     suggested.add(name)
113                 continue
114         nw, crc = w._raw_write((buf,), sha=shar)
115         _check(w, crcr, crc, 'object read: expected crc %d, got %d\n')
116     # NOTREACHED
117     
118
119 def _check(w, expected, actual, msg):
120     if expected != actual:
121         w.abort()
122         raise Exception(msg % (expected, actual))
123
124
125 def read_ref(conn, refname):
126     git.check_repo_or_die()
127     r = git.read_ref(refname)
128     conn.write('%s\n' % (r or '').encode('hex'))
129     conn.ok()
130
131
132 def update_ref(conn, refname):
133     git.check_repo_or_die()
134     newval = conn.readline().strip()
135     oldval = conn.readline().strip()
136     git.update_ref(refname, newval.decode('hex'), oldval.decode('hex'))
137     conn.ok()
138
139
140 cat_pipe = None
141 def cat(conn, id):
142     global cat_pipe
143     git.check_repo_or_die()
144     if not cat_pipe:
145         cat_pipe = git.CatPipe()
146     try:
147         for blob in cat_pipe.join(id):
148             conn.write(struct.pack('!I', len(blob)))
149             conn.write(blob)
150     except KeyError, e:
151         log('server: error: %s\n' % e)
152         conn.write('\0\0\0\0')
153         conn.error(e)
154     else:
155         conn.write('\0\0\0\0')
156         conn.ok()
157
158
159 optspec = """
160 bup server
161 """
162 o = options.Options(optspec)
163 (opt, flags, extra) = o.parse(sys.argv[1:])
164
165 if extra:
166     o.fatal('no arguments expected')
167
168 debug2('bup server: reading from stdin.\n')
169
170 commands = {
171     'quit': None,
172     'help': do_help,
173     'init-dir': init_dir,
174     'set-dir': set_dir,
175     'list-indexes': list_indexes,
176     'send-index': send_index,
177     'receive-objects-v2': receive_objects_v2,
178     'read-ref': read_ref,
179     'update-ref': update_ref,
180     'cat': cat,
181 }
182
183 # FIXME: this protocol is totally lame and not at all future-proof.
184 # (Especially since we abort completely as soon as *anything* bad happens)
185 conn = Conn(sys.stdin, sys.stdout)
186 lr = linereader(conn)
187 for _line in lr:
188     line = _line.strip()
189     if not line:
190         continue
191     debug1('bup server: command: %r\n' % line)
192     words = line.split(' ', 1)
193     cmd = words[0]
194     rest = len(words)>1 and words[1] or ''
195     if cmd == 'quit':
196         break
197     else:
198         cmd = commands.get(cmd)
199         if cmd:
200             cmd(conn, rest)
201         else:
202             raise Exception('unknown server command: %r\n' % line)
203
204 debug1('bup server: done\n')