1 import re, struct, errno
4 from subprocess import Popen, PIPE
6 class ClientError(Exception):
10 def __init__(self, remote, create=False):
12 self._indexes_synced = 0
15 rs = remote.split(':', 1)
16 nicedir = os.path.split(os.path.abspath(sys.argv[0]))[0]
17 nicedir = re.sub(r':', "_", nicedir)
19 (host, dir) = ('NONE', remote)
21 os.environ['PATH'] = ':'.join([nicedir,
22 os.environ.get('PATH', '')])
23 argv = ['bup', 'server']
27 # WARNING: shell quoting security holes are possible here, so we
28 # have to be super careful. We have to use 'sh -c' because
29 # csh-derived shells can't handle PATH= notation. We can't
30 # set PATH in advance, because ssh probably replaces it. We
31 # can't exec *safely* using argv, because *both* ssh and 'sh -c'
32 # allow shellquoting. So we end up having to double-shellquote
34 escapedir = re.sub(r'([^\w/])', r'\\\\\\\1', nicedir)
36 sh -c PATH=%s:'$PATH bup server'
38 argv = ['ssh', host, '--', cmd.strip()]
39 #log('argv is: %r\n' % argv)
40 (self.host, self.dir) = (host, dir)
41 self.cachedir = git.repo('index-cache/%s'
42 % re.sub(r'[^@:\w]', '_',
43 "%s:%s" % (host, dir)))
45 self.p = p = Popen(argv, stdin=PIPE, stdout=PIPE, preexec_fn=fixenv)
47 raise ClientError, 'exec %r: %s' % (argv[0], e), sys.exc_info()[2]
48 self.conn = conn = Conn(p.stdout, p.stdin)
50 dir = re.sub(r'[\r\n]', ' ', dir)
52 conn.write('init-dir %s\n' % dir)
54 conn.write('set-dir %s\n' % dir)
61 if e.errno == errno.EPIPE:
67 if self.conn and not self._busy:
68 self.conn.write('quit\n')
71 while self.p.stdout.read(65536):
77 raise ClientError('server tunnel returned exit code %d' % rv)
84 raise ClientError('server exited unexpectedly with code %r' % rv)
88 raise ClientError, e, sys.exc_info()[2]
92 raise ClientError('already busy with command %r' % self._busy)
97 def sync_indexes(self):
100 conn.write('list-indexes\n')
101 packdir = git.repo('objects/pack')
102 mkdirp(self.cachedir)
105 for line in linereader(conn):
109 assert(line.find('/') < 0)
110 if not os.path.exists(os.path.join(self.cachedir, line)):
114 for f in os.listdir(self.cachedir):
115 if f.endswith('.idx') and not f in all:
116 log('pruning old index: %r\n' % f)
117 os.unlink(os.path.join(self.cachedir, f))
119 # FIXME this should be pipelined: request multiple indexes at a time, or
120 # we waste lots of network turnarounds.
121 for name in needed.keys():
122 log('requesting %r\n' % name)
123 conn.write('send-index %s\n' % name)
124 n = struct.unpack('!I', conn.read(4))[0]
126 log(' expect %d bytes\n' % n)
127 fn = os.path.join(self.cachedir, name)
128 f = open(fn + '.tmp', 'w')
129 for b in chunkyreader(conn, n):
133 os.rename(fn + '.tmp', fn)
135 self._indexes_synced = 1
137 def _make_objcache(self):
142 return git.MultiPackIndex(self.cachedir)
144 def new_packwriter(self):
146 self._busy = 'receive-objects'
147 return PackWriter_Remote(self.conn,
148 objcache_maker = self._make_objcache,
149 onclose = self._not_busy)
151 def read_ref(self, refname):
153 self.conn.write('read-ref %s\n' % refname)
154 r = self.conn.readline().strip()
157 assert(len(r) == 40) # hexified sha
158 return r.decode('hex')
160 return None # nonexistent ref
162 def update_ref(self, refname, newval, oldval):
164 self.conn.write('update-ref %s\n%s\n%s\n'
165 % (refname, newval.encode('hex'),
166 (oldval or '').encode('hex')))
172 self.conn.write('cat %s\n' % re.sub(r'[\n\r]', '_', id))
174 sz = struct.unpack('!I', self.conn.read(4))[0]
176 yield self.conn.read(sz)
181 class PackWriter_Remote(git.PackWriter):
182 def __init__(self, conn, objcache_maker=None, onclose=None):
183 git.PackWriter.__init__(self, objcache_maker)
185 self.filename = 'remote socket'
186 self.onclose = onclose
187 self._packopen = False
190 if not self._packopen:
191 self._make_objcache()
192 self.file.write('receive-objects\n')
193 self._packopen = True
196 if self._packopen and self.file:
197 self.file.write('\0\0\0\0')
198 self._packopen = False
199 id = self.file.readline().strip()
212 raise GitError("don't know how to abort remote pack writing")
214 def _raw_write(self, datalist):
216 if not self._packopen:
218 data = ''.join(datalist)
220 self.file.write(struct.pack('!I', len(data)) + data)
221 self.outbytes += len(data)