1 import sys, os, pwd, subprocess, errno, socket, select, mmap, stat, re
2 from bup import _version
5 # Write (blockingly) to sockets that may or may not be in blocking mode.
6 # We need this because our stderr is sometimes eaten by subprocesses
7 # (probably ssh) that sometimes make it nonblocking, if only temporarily,
8 # leading to race conditions. Ick. We'll do it the hard way.
9 def _hard_write(fd, buf):
11 (r,w,x) = select.select([], [fd], [], None)
13 raise IOError('select(fd) returned without being writable')
15 sz = os.write(fd, buf)
17 if e.errno != errno.EAGAIN:
24 _hard_write(sys.stderr.fileno(), s)
31 if e.errno == errno.EEXIST:
48 if e.errno == errno.ENOENT:
49 pass # it doesn't exist, that's what you asked for
53 p = subprocess.Popen(argv, stdout=subprocess.PIPE)
59 # FIXME: this function isn't very generic, because it splits the filename
60 # in an odd way and depends on a terminating '/' to indicate directories.
61 # But it's used in a couple of places, so let's put it here.
64 l = [i+'/' for i in l[:-1]] + l[-1:]
66 l.pop() # extra blank caused by terminating '/'
70 # like os.path.realpath, but doesn't follow a symlink for the last element.
71 # (ie. if 'p' itself is itself a symlink, this one won't follow it)
77 if st and stat.S_ISLNK(st.st_mode):
78 (dir, name) = os.path.split(p)
79 dir = os.path.realpath(dir)
80 out = os.path.join(dir, name)
82 out = os.path.realpath(p)
83 #log('realpathing:%r,%r\n' % (p, out))
93 _username = pwd.getpwuid(uid)[0]
95 _username = 'user%d' % uid
102 if not _userfullname:
105 _userfullname = pwd.getpwuid(uid)[4].split(',')[0]
107 _userfullname = 'user%d' % uid
115 _hostname = socket.getfqdn()
119 class NotOk(Exception):
123 def __init__(self, inp, outp):
127 def read(self, size):
129 return self.inp.read(size)
133 return self.inp.readline()
135 def write(self, data):
136 #log('%d writing: %d bytes\n' % (os.getpid(), len(data)))
137 self.outp.write(data)
140 [rl, wl, xl] = select.select([self.inp.fileno()], [], [], 0)
142 assert(rl[0] == self.inp.fileno())
151 s = re.sub(r'\s+', ' ', str(s))
152 self.write('\nerror %s\n' % s)
154 def _check_ok(self, onempty):
157 for rl in linereader(self.inp):
158 #log('%d got line: %r\n' % (os.getpid(), rl))
159 if not rl: # empty line
163 elif rl.startswith('error '):
164 #log('client: error: %s\n' % rl[6:])
168 raise Exception('server exited unexpectedly; see errors above')
170 def drain_and_check_ok(self):
173 return self._check_ok(onempty)
177 raise Exception('expected "ok", got %r' % rl)
178 return self._check_ok(onempty)
189 def chunkyreader(f, count = None):
192 b = f.read(min(count, 65536))
194 raise IOError('EOF with %d bytes remaining' % count)
205 if s and not s.endswith('/'):
211 def _mmap_do(f, sz, flags, prot):
213 st = os.fstat(f.fileno())
215 map = mmap.mmap(f.fileno(), sz, flags, prot)
216 f.close() # map will persist beyond file close
220 def mmap_read(f, sz = 0):
221 return _mmap_do(f, sz, mmap.MAP_PRIVATE, mmap.PROT_READ)
224 def mmap_readwrite(f, sz = 0):
225 return _mmap_do(f, sz, mmap.MAP_SHARED, mmap.PROT_READ|mmap.PROT_WRITE)
229 g = re.match(r'([-+\d.e]+)\s*(\w*)', str(s))
231 raise ValueError("can't parse %r as a number" % s)
232 (val, unit) = g.groups()
235 if unit in ['t', 'tb']:
236 mult = 1024*1024*1024*1024
237 elif unit in ['g', 'gb']:
238 mult = 1024*1024*1024
239 elif unit in ['m', 'mb']:
241 elif unit in ['k', 'kb']:
243 elif unit in ['', 'b']:
246 raise ValueError("invalid unit %r in number %r" % (unit, s))
250 # count the number of elements in an iterator (consumes the iterator)
252 return reduce(lambda x,y: x+1, l)
264 saved_errors.append(e)
267 istty = os.isatty(2) or atoi(os.environ.get('BUP_FORCE_TTY'))
274 oldhook = sys.excepthook
275 def newhook(exctype, value, traceback):
276 if exctype == KeyboardInterrupt:
277 log('Interrupted.\n')
279 return oldhook(exctype, value, traceback)
280 sys.excepthook = newhook
283 def columnate(l, prefix):
285 clen = max(len(s) for s in l)
286 ncols = (78 - len(prefix)) / (clen + 2)
291 while len(l) % ncols:
294 for s in range(0, len(l), rows):
295 cols.append(l[s:s+rows])
297 for row in zip(*cols):
298 out += prefix + ''.join(('%-*s' % (clen+2, s)) for s in row) + '\n'
302 # hashlib is only available in python 2.5 or higher, but the 'sha' module
303 # produces a DeprecationWarning in python 2.6 or higher. We want to support
304 # python 2.4 and above without any stupid warnings, so let's try using hashlib
305 # first, and downgrade if it fails.
316 return _version.DATE.split(' ')[0]
318 def version_commit():
319 return _version.COMMIT
322 names = _version.NAMES.strip()
323 assert(names[0] == '(')
324 assert(names[-1] == ')')
326 l = [n.strip() for n in names.split(',')]
328 if n.startswith('tag: bup-'):
330 return 'unknown-%s' % _version.COMMIT[:7]