]> arthur.barton.de Git - bup.git/blob - lib/bup/helpers.py
9e428845be0002bb4eb152125175595b75fabd97
[bup.git] / lib / bup / helpers.py
1 """Helper functions and classes for bup."""
2
3 from collections import namedtuple
4 from ctypes import sizeof, c_void_p
5 from os import environ
6 from contextlib import contextmanager
7 import sys, os, pwd, subprocess, errno, socket, select, mmap, stat, re, struct
8 import hashlib, heapq, math, operator, time, grp, tempfile
9
10 from bup import _helpers
11
12 sc_arg_max = os.sysconf('SC_ARG_MAX')
13
14 # This function should really be in helpers, not in bup.options.  But we
15 # want options.py to be standalone so people can include it in other projects.
16 from bup.options import _tty_width
17 tty_width = _tty_width
18
19
20 def atoi(s):
21     """Convert the string 's' to an integer. Return 0 if s is not a number."""
22     try:
23         return int(s or '0')
24     except ValueError:
25         return 0
26
27
28 def atof(s):
29     """Convert the string 's' to a float. Return 0 if s is not a number."""
30     try:
31         return float(s or '0')
32     except ValueError:
33         return 0
34
35
36 buglvl = atoi(os.environ.get('BUP_DEBUG', 0))
37
38
39 # If the platform doesn't have fdatasync (OS X), fall back to fsync.
40 try:
41     fdatasync = os.fdatasync
42 except AttributeError:
43     fdatasync = os.fsync
44
45
46 # Write (blockingly) to sockets that may or may not be in blocking mode.
47 # We need this because our stderr is sometimes eaten by subprocesses
48 # (probably ssh) that sometimes make it nonblocking, if only temporarily,
49 # leading to race conditions.  Ick.  We'll do it the hard way.
50 def _hard_write(fd, buf):
51     while buf:
52         (r,w,x) = select.select([], [fd], [], None)
53         if not w:
54             raise IOError('select(fd) returned without being writable')
55         try:
56             sz = os.write(fd, buf)
57         except OSError, e:
58             if e.errno != errno.EAGAIN:
59                 raise
60         assert(sz >= 0)
61         buf = buf[sz:]
62
63
64 _last_prog = 0
65 def log(s):
66     """Print a log message to stderr."""
67     global _last_prog
68     sys.stdout.flush()
69     _hard_write(sys.stderr.fileno(), s)
70     _last_prog = 0
71
72
73 def debug1(s):
74     if buglvl >= 1:
75         log(s)
76
77
78 def debug2(s):
79     if buglvl >= 2:
80         log(s)
81
82
83 istty1 = os.isatty(1) or (atoi(os.environ.get('BUP_FORCE_TTY')) & 1)
84 istty2 = os.isatty(2) or (atoi(os.environ.get('BUP_FORCE_TTY')) & 2)
85 _last_progress = ''
86 def progress(s):
87     """Calls log() if stderr is a TTY.  Does nothing otherwise."""
88     global _last_progress
89     if istty2:
90         log(s)
91         _last_progress = s
92
93
94 def qprogress(s):
95     """Calls progress() only if we haven't printed progress in a while.
96     
97     This avoids overloading the stderr buffer with excess junk.
98     """
99     global _last_prog
100     now = time.time()
101     if now - _last_prog > 0.1:
102         progress(s)
103         _last_prog = now
104
105
106 def reprogress():
107     """Calls progress() to redisplay the most recent progress message.
108
109     Useful after you've printed some other message that wipes out the
110     progress line.
111     """
112     if _last_progress and _last_progress.endswith('\r'):
113         progress(_last_progress)
114
115
116 def mkdirp(d, mode=None):
117     """Recursively create directories on path 'd'.
118
119     Unlike os.makedirs(), it doesn't raise an exception if the last element of
120     the path already exists.
121     """
122     try:
123         if mode:
124             os.makedirs(d, mode)
125         else:
126             os.makedirs(d)
127     except OSError, e:
128         if e.errno == errno.EEXIST:
129             pass
130         else:
131             raise
132
133
134 _unspecified_next_default = object()
135
136 def _fallback_next(it, default=_unspecified_next_default):
137     """Retrieve the next item from the iterator by calling its
138     next() method. If default is given, it is returned if the
139     iterator is exhausted, otherwise StopIteration is raised."""
140
141     if default is _unspecified_next_default:
142         return it.next()
143     else:
144         try:
145             return it.next()
146         except StopIteration:
147             return default
148
149 if sys.version_info < (2, 6):
150     next =  _fallback_next
151
152
153 def merge_iter(iters, pfreq, pfunc, pfinal, key=None):
154     if key:
155         samekey = lambda e, pe: getattr(e, key) == getattr(pe, key, None)
156     else:
157         samekey = operator.eq
158     count = 0
159     total = sum(len(it) for it in iters)
160     iters = (iter(it) for it in iters)
161     heap = ((next(it, None),it) for it in iters)
162     heap = [(e,it) for e,it in heap if e]
163
164     heapq.heapify(heap)
165     pe = None
166     while heap:
167         if not count % pfreq:
168             pfunc(count, total)
169         e, it = heap[0]
170         if not samekey(e, pe):
171             pe = e
172             yield e
173         count += 1
174         try:
175             e = it.next() # Don't use next() function, it's too expensive
176         except StopIteration:
177             heapq.heappop(heap) # remove current
178         else:
179             heapq.heapreplace(heap, (e, it)) # shift current to new location
180     pfinal(count, total)
181
182
183 def unlink(f):
184     """Delete a file at path 'f' if it currently exists.
185
186     Unlike os.unlink(), does not throw an exception if the file didn't already
187     exist.
188     """
189     try:
190         os.unlink(f)
191     except OSError, e:
192         if e.errno != errno.ENOENT:
193             raise
194
195
196 def readpipe(argv, preexec_fn=None):
197     """Run a subprocess and return its output."""
198     p = subprocess.Popen(argv, stdout=subprocess.PIPE, preexec_fn=preexec_fn)
199     out, err = p.communicate()
200     if p.returncode != 0:
201         raise Exception('subprocess %r failed with status %d'
202                         % (' '.join(argv), p.returncode))
203     return out
204
205
206 def _argmax_base(command):
207     base_size = 2048
208     for c in command:
209         base_size += len(command) + 1
210     for k, v in environ.iteritems():
211         base_size += len(k) + len(v) + 2 + sizeof(c_void_p)
212     return base_size
213
214
215 def _argmax_args_size(args):
216     return sum(len(x) + 1 + sizeof(c_void_p) for x in args)
217
218
219 def batchpipe(command, args, preexec_fn=None, arg_max=sc_arg_max):
220     """If args is not empty, yield the output produced by calling the
221 command list with args as a sequence of strings (It may be necessary
222 to return multiple strings in order to respect ARG_MAX)."""
223     # The optional arg_max arg is a workaround for an issue with the
224     # current wvtest behavior.
225     base_size = _argmax_base(command)
226     while args:
227         room = arg_max - base_size
228         i = 0
229         while i < len(args):
230             next_size = _argmax_args_size(args[i:i+1])
231             if room - next_size < 0:
232                 break
233             room -= next_size
234             i += 1
235         sub_args = args[:i]
236         args = args[i:]
237         assert(len(sub_args))
238         yield readpipe(command + sub_args, preexec_fn=preexec_fn)
239
240
241 def realpath(p):
242     """Get the absolute path of a file.
243
244     Behaves like os.path.realpath, but doesn't follow a symlink for the last
245     element. (ie. if 'p' itself is a symlink, this one won't follow it, but it
246     will follow symlinks in p's directory)
247     """
248     try:
249         st = os.lstat(p)
250     except OSError:
251         st = None
252     if st and stat.S_ISLNK(st.st_mode):
253         (dir, name) = os.path.split(p)
254         dir = os.path.realpath(dir)
255         out = os.path.join(dir, name)
256     else:
257         out = os.path.realpath(p)
258     #log('realpathing:%r,%r\n' % (p, out))
259     return out
260
261
262 def detect_fakeroot():
263     "Return True if we appear to be running under fakeroot."
264     return os.getenv("FAKEROOTKEY") != None
265
266
267 def is_superuser():
268     if sys.platform.startswith('cygwin'):
269         import ctypes
270         return ctypes.cdll.shell32.IsUserAnAdmin()
271     else:
272         return os.geteuid() == 0
273
274
275 def _cache_key_value(get_value, key, cache):
276     """Return (value, was_cached).  If there is a value in the cache
277     for key, use that, otherwise, call get_value(key) which should
278     throw a KeyError if there is no value -- in which case the cached
279     and returned value will be None.
280     """
281     try: # Do we already have it (or know there wasn't one)?
282         value = cache[key]
283         return value, True
284     except KeyError:
285         pass
286     value = None
287     try:
288         cache[key] = value = get_value(key)
289     except KeyError:
290         cache[key] = None
291     return value, False
292
293
294 _uid_to_pwd_cache = {}
295 _name_to_pwd_cache = {}
296
297 def pwd_from_uid(uid):
298     """Return password database entry for uid (may be a cached value).
299     Return None if no entry is found.
300     """
301     global _uid_to_pwd_cache, _name_to_pwd_cache
302     entry, cached = _cache_key_value(pwd.getpwuid, uid, _uid_to_pwd_cache)
303     if entry and not cached:
304         _name_to_pwd_cache[entry.pw_name] = entry
305     return entry
306
307
308 def pwd_from_name(name):
309     """Return password database entry for name (may be a cached value).
310     Return None if no entry is found.
311     """
312     global _uid_to_pwd_cache, _name_to_pwd_cache
313     entry, cached = _cache_key_value(pwd.getpwnam, name, _name_to_pwd_cache)
314     if entry and not cached:
315         _uid_to_pwd_cache[entry.pw_uid] = entry
316     return entry
317
318
319 _gid_to_grp_cache = {}
320 _name_to_grp_cache = {}
321
322 def grp_from_gid(gid):
323     """Return password database entry for gid (may be a cached value).
324     Return None if no entry is found.
325     """
326     global _gid_to_grp_cache, _name_to_grp_cache
327     entry, cached = _cache_key_value(grp.getgrgid, gid, _gid_to_grp_cache)
328     if entry and not cached:
329         _name_to_grp_cache[entry.gr_name] = entry
330     return entry
331
332
333 def grp_from_name(name):
334     """Return password database entry for name (may be a cached value).
335     Return None if no entry is found.
336     """
337     global _gid_to_grp_cache, _name_to_grp_cache
338     entry, cached = _cache_key_value(grp.getgrnam, name, _name_to_grp_cache)
339     if entry and not cached:
340         _gid_to_grp_cache[entry.gr_gid] = entry
341     return entry
342
343
344 _username = None
345 def username():
346     """Get the user's login name."""
347     global _username
348     if not _username:
349         uid = os.getuid()
350         _username = pwd_from_uid(uid)[0] or 'user%d' % uid
351     return _username
352
353
354 _userfullname = None
355 def userfullname():
356     """Get the user's full name."""
357     global _userfullname
358     if not _userfullname:
359         uid = os.getuid()
360         entry = pwd_from_uid(uid)
361         if entry:
362             _userfullname = entry[4].split(',')[0] or entry[0]
363         if not _userfullname:
364             _userfullname = 'user%d' % uid
365     return _userfullname
366
367
368 _hostname = None
369 def hostname():
370     """Get the FQDN of this machine."""
371     global _hostname
372     if not _hostname:
373         _hostname = socket.getfqdn()
374     return _hostname
375
376
377 _resource_path = None
378 def resource_path(subdir=''):
379     global _resource_path
380     if not _resource_path:
381         _resource_path = os.environ.get('BUP_RESOURCE_PATH') or '.'
382     return os.path.join(_resource_path, subdir)
383
384 def format_filesize(size):
385     unit = 1024.0
386     size = float(size)
387     if size < unit:
388         return "%d" % (size)
389     exponent = int(math.log(size) / math.log(unit))
390     size_prefix = "KMGTPE"[exponent - 1]
391     return "%.1f%s" % (size / math.pow(unit, exponent), size_prefix)
392
393
394 class NotOk(Exception):
395     pass
396
397
398 class BaseConn:
399     def __init__(self, outp):
400         self.outp = outp
401
402     def close(self):
403         while self._read(65536): pass
404
405     def read(self, size):
406         """Read 'size' bytes from input stream."""
407         self.outp.flush()
408         return self._read(size)
409
410     def readline(self):
411         """Read from input stream until a newline is found."""
412         self.outp.flush()
413         return self._readline()
414
415     def write(self, data):
416         """Write 'data' to output stream."""
417         #log('%d writing: %d bytes\n' % (os.getpid(), len(data)))
418         self.outp.write(data)
419
420     def has_input(self):
421         """Return true if input stream is readable."""
422         raise NotImplemented("Subclasses must implement has_input")
423
424     def ok(self):
425         """Indicate end of output from last sent command."""
426         self.write('\nok\n')
427
428     def error(self, s):
429         """Indicate server error to the client."""
430         s = re.sub(r'\s+', ' ', str(s))
431         self.write('\nerror %s\n' % s)
432
433     def _check_ok(self, onempty):
434         self.outp.flush()
435         rl = ''
436         for rl in linereader(self):
437             #log('%d got line: %r\n' % (os.getpid(), rl))
438             if not rl:  # empty line
439                 continue
440             elif rl == 'ok':
441                 return None
442             elif rl.startswith('error '):
443                 #log('client: error: %s\n' % rl[6:])
444                 return NotOk(rl[6:])
445             else:
446                 onempty(rl)
447         raise Exception('server exited unexpectedly; see errors above')
448
449     def drain_and_check_ok(self):
450         """Remove all data for the current command from input stream."""
451         def onempty(rl):
452             pass
453         return self._check_ok(onempty)
454
455     def check_ok(self):
456         """Verify that server action completed successfully."""
457         def onempty(rl):
458             raise Exception('expected "ok", got %r' % rl)
459         return self._check_ok(onempty)
460
461
462 class Conn(BaseConn):
463     def __init__(self, inp, outp):
464         BaseConn.__init__(self, outp)
465         self.inp = inp
466
467     def _read(self, size):
468         return self.inp.read(size)
469
470     def _readline(self):
471         return self.inp.readline()
472
473     def has_input(self):
474         [rl, wl, xl] = select.select([self.inp.fileno()], [], [], 0)
475         if rl:
476             assert(rl[0] == self.inp.fileno())
477             return True
478         else:
479             return None
480
481
482 def checked_reader(fd, n):
483     while n > 0:
484         rl, _, _ = select.select([fd], [], [])
485         assert(rl[0] == fd)
486         buf = os.read(fd, n)
487         if not buf: raise Exception("Unexpected EOF reading %d more bytes" % n)
488         yield buf
489         n -= len(buf)
490
491
492 MAX_PACKET = 128 * 1024
493 def mux(p, outfd, outr, errr):
494     try:
495         fds = [outr, errr]
496         while p.poll() is None:
497             rl, _, _ = select.select(fds, [], [])
498             for fd in rl:
499                 if fd == outr:
500                     buf = os.read(outr, MAX_PACKET)
501                     if not buf: break
502                     os.write(outfd, struct.pack('!IB', len(buf), 1) + buf)
503                 elif fd == errr:
504                     buf = os.read(errr, 1024)
505                     if not buf: break
506                     os.write(outfd, struct.pack('!IB', len(buf), 2) + buf)
507     finally:
508         os.write(outfd, struct.pack('!IB', 0, 3))
509
510
511 class DemuxConn(BaseConn):
512     """A helper class for bup's client-server protocol."""
513     def __init__(self, infd, outp):
514         BaseConn.__init__(self, outp)
515         # Anything that comes through before the sync string was not
516         # multiplexed and can be assumed to be debug/log before mux init.
517         tail = ''
518         while tail != 'BUPMUX':
519             b = os.read(infd, (len(tail) < 6) and (6-len(tail)) or 1)
520             if not b:
521                 raise IOError('demux: unexpected EOF during initialization')
522             tail += b
523             sys.stderr.write(tail[:-6])  # pre-mux log messages
524             tail = tail[-6:]
525         self.infd = infd
526         self.reader = None
527         self.buf = None
528         self.closed = False
529
530     def write(self, data):
531         self._load_buf(0)
532         BaseConn.write(self, data)
533
534     def _next_packet(self, timeout):
535         if self.closed: return False
536         rl, wl, xl = select.select([self.infd], [], [], timeout)
537         if not rl: return False
538         assert(rl[0] == self.infd)
539         ns = ''.join(checked_reader(self.infd, 5))
540         n, fdw = struct.unpack('!IB', ns)
541         assert(n <= MAX_PACKET)
542         if fdw == 1:
543             self.reader = checked_reader(self.infd, n)
544         elif fdw == 2:
545             for buf in checked_reader(self.infd, n):
546                 sys.stderr.write(buf)
547         elif fdw == 3:
548             self.closed = True
549             debug2("DemuxConn: marked closed\n")
550         return True
551
552     def _load_buf(self, timeout):
553         if self.buf is not None:
554             return True
555         while not self.closed:
556             while not self.reader:
557                 if not self._next_packet(timeout):
558                     return False
559             try:
560                 self.buf = self.reader.next()
561                 return True
562             except StopIteration:
563                 self.reader = None
564         return False
565
566     def _read_parts(self, ix_fn):
567         while self._load_buf(None):
568             assert(self.buf is not None)
569             i = ix_fn(self.buf)
570             if i is None or i == len(self.buf):
571                 yv = self.buf
572                 self.buf = None
573             else:
574                 yv = self.buf[:i]
575                 self.buf = self.buf[i:]
576             yield yv
577             if i is not None:
578                 break
579
580     def _readline(self):
581         def find_eol(buf):
582             try:
583                 return buf.index('\n')+1
584             except ValueError:
585                 return None
586         return ''.join(self._read_parts(find_eol))
587
588     def _read(self, size):
589         csize = [size]
590         def until_size(buf): # Closes on csize
591             if len(buf) < csize[0]:
592                 csize[0] -= len(buf)
593                 return None
594             else:
595                 return csize[0]
596         return ''.join(self._read_parts(until_size))
597
598     def has_input(self):
599         return self._load_buf(0)
600
601
602 def linereader(f):
603     """Generate a list of input lines from 'f' without terminating newlines."""
604     while 1:
605         line = f.readline()
606         if not line:
607             break
608         yield line[:-1]
609
610
611 def chunkyreader(f, count = None):
612     """Generate a list of chunks of data read from 'f'.
613
614     If count is None, read until EOF is reached.
615
616     If count is a positive integer, read 'count' bytes from 'f'. If EOF is
617     reached while reading, raise IOError.
618     """
619     if count != None:
620         while count > 0:
621             b = f.read(min(count, 65536))
622             if not b:
623                 raise IOError('EOF with %d bytes remaining' % count)
624             yield b
625             count -= len(b)
626     else:
627         while 1:
628             b = f.read(65536)
629             if not b: break
630             yield b
631
632
633 @contextmanager
634 def atomically_replaced_file(name, mode='w', buffering=-1):
635     """Yield a file that will be atomically renamed name when leaving the block.
636
637     This contextmanager yields an open file object that is backed by a
638     temporary file which will be renamed (atomically) to the target
639     name if everything succeeds.
640
641     The mode and buffering arguments are handled exactly as with open,
642     and the yielded file will have very restrictive permissions, as
643     per mkstemp.
644
645     E.g.::
646
647         with atomically_replaced_file('foo.txt', 'w') as f:
648             f.write('hello jack.')
649
650     """
651
652     (ffd, tempname) = tempfile.mkstemp(dir=os.path.dirname(name),
653                                        text=('b' not in mode))
654     try:
655         try:
656             f = os.fdopen(ffd, mode, buffering)
657         except:
658             os.close(ffd)
659             raise
660         try:
661             yield f
662         finally:
663             f.close()
664         os.rename(tempname, name)
665     finally:
666         unlink(tempname)  # nonexistant file is ignored
667
668
669 def slashappend(s):
670     """Append "/" to 's' if it doesn't aleady end in "/"."""
671     if s and not s.endswith('/'):
672         return s + '/'
673     else:
674         return s
675
676
677 def _mmap_do(f, sz, flags, prot, close):
678     if not sz:
679         st = os.fstat(f.fileno())
680         sz = st.st_size
681     if not sz:
682         # trying to open a zero-length map gives an error, but an empty
683         # string has all the same behaviour of a zero-length map, ie. it has
684         # no elements :)
685         return ''
686     map = mmap.mmap(f.fileno(), sz, flags, prot)
687     if close:
688         f.close()  # map will persist beyond file close
689     return map
690
691
692 def mmap_read(f, sz = 0, close=True):
693     """Create a read-only memory mapped region on file 'f'.
694     If sz is 0, the region will cover the entire file.
695     """
696     return _mmap_do(f, sz, mmap.MAP_PRIVATE, mmap.PROT_READ, close)
697
698
699 def mmap_readwrite(f, sz = 0, close=True):
700     """Create a read-write memory mapped region on file 'f'.
701     If sz is 0, the region will cover the entire file.
702     """
703     return _mmap_do(f, sz, mmap.MAP_SHARED, mmap.PROT_READ|mmap.PROT_WRITE,
704                     close)
705
706
707 def mmap_readwrite_private(f, sz = 0, close=True):
708     """Create a read-write memory mapped region on file 'f'.
709     If sz is 0, the region will cover the entire file.
710     The map is private, which means the changes are never flushed back to the
711     file.
712     """
713     return _mmap_do(f, sz, mmap.MAP_PRIVATE, mmap.PROT_READ|mmap.PROT_WRITE,
714                     close)
715
716
717 def parse_timestamp(epoch_str):
718     """Return the number of nanoseconds since the epoch that are described
719 by epoch_str (100ms, 100ns, ...); when epoch_str cannot be parsed,
720 throw a ValueError that may contain additional information."""
721     ns_per = {'s' :  1000000000,
722               'ms' : 1000000,
723               'us' : 1000,
724               'ns' : 1}
725     match = re.match(r'^((?:[-+]?[0-9]+)?)(s|ms|us|ns)$', epoch_str)
726     if not match:
727         if re.match(r'^([-+]?[0-9]+)$', epoch_str):
728             raise ValueError('must include units, i.e. 100ns, 100ms, ...')
729         raise ValueError()
730     (n, units) = match.group(1, 2)
731     if not n:
732         n = 1
733     n = int(n)
734     return n * ns_per[units]
735
736
737 def parse_num(s):
738     """Parse data size information into a float number.
739
740     Here are some examples of conversions:
741         199.2k means 203981 bytes
742         1GB means 1073741824 bytes
743         2.1 tb means 2199023255552 bytes
744     """
745     g = re.match(r'([-+\d.e]+)\s*(\w*)', str(s))
746     if not g:
747         raise ValueError("can't parse %r as a number" % s)
748     (val, unit) = g.groups()
749     num = float(val)
750     unit = unit.lower()
751     if unit in ['t', 'tb']:
752         mult = 1024*1024*1024*1024
753     elif unit in ['g', 'gb']:
754         mult = 1024*1024*1024
755     elif unit in ['m', 'mb']:
756         mult = 1024*1024
757     elif unit in ['k', 'kb']:
758         mult = 1024
759     elif unit in ['', 'b']:
760         mult = 1
761     else:
762         raise ValueError("invalid unit %r in number %r" % (unit, s))
763     return int(num*mult)
764
765
766 def count(l):
767     """Count the number of elements in an iterator. (consumes the iterator)"""
768     return reduce(lambda x,y: x+1, l)
769
770
771 saved_errors = []
772 def add_error(e):
773     """Append an error message to the list of saved errors.
774
775     Once processing is able to stop and output the errors, the saved errors are
776     accessible in the module variable helpers.saved_errors.
777     """
778     saved_errors.append(e)
779     log('%-70s\n' % e)
780
781
782 def clear_errors():
783     global saved_errors
784     saved_errors = []
785
786
787 def handle_ctrl_c():
788     """Replace the default exception handler for KeyboardInterrupt (Ctrl-C).
789
790     The new exception handler will make sure that bup will exit without an ugly
791     stacktrace when Ctrl-C is hit.
792     """
793     oldhook = sys.excepthook
794     def newhook(exctype, value, traceback):
795         if exctype == KeyboardInterrupt:
796             log('\nInterrupted.\n')
797         else:
798             return oldhook(exctype, value, traceback)
799     sys.excepthook = newhook
800
801
802 def columnate(l, prefix):
803     """Format elements of 'l' in columns with 'prefix' leading each line.
804
805     The number of columns is determined automatically based on the string
806     lengths.
807     """
808     if not l:
809         return ""
810     l = l[:]
811     clen = max(len(s) for s in l)
812     ncols = (tty_width() - len(prefix)) / (clen + 2)
813     if ncols <= 1:
814         ncols = 1
815         clen = 0
816     cols = []
817     while len(l) % ncols:
818         l.append('')
819     rows = len(l)/ncols
820     for s in range(0, len(l), rows):
821         cols.append(l[s:s+rows])
822     out = ''
823     for row in zip(*cols):
824         out += prefix + ''.join(('%-*s' % (clen+2, s)) for s in row) + '\n'
825     return out
826
827
828 def parse_date_or_fatal(str, fatal):
829     """Parses the given date or calls Option.fatal().
830     For now we expect a string that contains a float."""
831     try:
832         date = float(str)
833     except ValueError, e:
834         raise fatal('invalid date format (should be a float): %r' % e)
835     else:
836         return date
837
838
839 def parse_excludes(options, fatal):
840     """Traverse the options and extract all excludes, or call Option.fatal()."""
841     excluded_paths = []
842
843     for flag in options:
844         (option, parameter) = flag
845         if option == '--exclude':
846             excluded_paths.append(realpath(parameter))
847         elif option == '--exclude-from':
848             try:
849                 f = open(realpath(parameter))
850             except IOError, e:
851                 raise fatal("couldn't read %s" % parameter)
852             for exclude_path in f.readlines():
853                 # FIXME: perhaps this should be rstrip('\n')
854                 exclude_path = realpath(exclude_path.strip())
855                 if exclude_path:
856                     excluded_paths.append(exclude_path)
857     return sorted(frozenset(excluded_paths))
858
859
860 def parse_rx_excludes(options, fatal):
861     """Traverse the options and extract all rx excludes, or call
862     Option.fatal()."""
863     excluded_patterns = []
864
865     for flag in options:
866         (option, parameter) = flag
867         if option == '--exclude-rx':
868             try:
869                 excluded_patterns.append(re.compile(parameter))
870             except re.error, ex:
871                 fatal('invalid --exclude-rx pattern (%s): %s' % (parameter, ex))
872         elif option == '--exclude-rx-from':
873             try:
874                 f = open(realpath(parameter))
875             except IOError, e:
876                 raise fatal("couldn't read %s" % parameter)
877             for pattern in f.readlines():
878                 spattern = pattern.rstrip('\n')
879                 if not spattern:
880                     continue
881                 try:
882                     excluded_patterns.append(re.compile(spattern))
883                 except re.error, ex:
884                     fatal('invalid --exclude-rx pattern (%s): %s' % (spattern, ex))
885     return excluded_patterns
886
887
888 def should_rx_exclude_path(path, exclude_rxs):
889     """Return True if path matches a regular expression in exclude_rxs."""
890     for rx in exclude_rxs:
891         if rx.search(path):
892             debug1('Skipping %r: excluded by rx pattern %r.\n'
893                    % (path, rx.pattern))
894             return True
895     return False
896
897
898 # FIXME: Carefully consider the use of functions (os.path.*, etc.)
899 # that resolve against the current filesystem in the strip/graft
900 # functions for example, but elsewhere as well.  I suspect bup's not
901 # always being careful about that.  For some cases, the contents of
902 # the current filesystem should be irrelevant, and consulting it might
903 # produce the wrong result, perhaps via unintended symlink resolution,
904 # for example.
905
906 def path_components(path):
907     """Break path into a list of pairs of the form (name,
908     full_path_to_name).  Path must start with '/'.
909     Example:
910       '/home/foo' -> [('', '/'), ('home', '/home'), ('foo', '/home/foo')]"""
911     if not path.startswith('/'):
912         raise Exception, 'path must start with "/": %s' % path
913     # Since we assume path startswith('/'), we can skip the first element.
914     result = [('', '/')]
915     norm_path = os.path.abspath(path)
916     if norm_path == '/':
917         return result
918     full_path = ''
919     for p in norm_path.split('/')[1:]:
920         full_path += '/' + p
921         result.append((p, full_path))
922     return result
923
924
925 def stripped_path_components(path, strip_prefixes):
926     """Strip any prefix in strip_prefixes from path and return a list
927     of path components where each component is (name,
928     none_or_full_fs_path_to_name).  Assume path startswith('/').
929     See thelpers.py for examples."""
930     normalized_path = os.path.abspath(path)
931     sorted_strip_prefixes = sorted(strip_prefixes, key=len, reverse=True)
932     for bp in sorted_strip_prefixes:
933         normalized_bp = os.path.abspath(bp)
934         if normalized_bp == '/':
935             continue
936         if normalized_path.startswith(normalized_bp):
937             prefix = normalized_path[:len(normalized_bp)]
938             result = []
939             for p in normalized_path[len(normalized_bp):].split('/'):
940                 if p: # not root
941                     prefix += '/'
942                 prefix += p
943                 result.append((p, prefix))
944             return result
945     # Nothing to strip.
946     return path_components(path)
947
948
949 def grafted_path_components(graft_points, path):
950     # Create a result that consists of some number of faked graft
951     # directories before the graft point, followed by all of the real
952     # directories from path that are after the graft point.  Arrange
953     # for the directory at the graft point in the result to correspond
954     # to the "orig" directory in --graft orig=new.  See t/thelpers.py
955     # for some examples.
956
957     # Note that given --graft orig=new, orig and new have *nothing* to
958     # do with each other, even if some of their component names
959     # match. i.e. --graft /foo/bar/baz=/foo/bar/bax is semantically
960     # equivalent to --graft /foo/bar/baz=/x/y/z, or even
961     # /foo/bar/baz=/x.
962
963     # FIXME: This can't be the best solution...
964     clean_path = os.path.abspath(path)
965     for graft_point in graft_points:
966         old_prefix, new_prefix = graft_point
967         # Expand prefixes iff not absolute paths.
968         old_prefix = os.path.normpath(old_prefix)
969         new_prefix = os.path.normpath(new_prefix)
970         if clean_path.startswith(old_prefix):
971             escaped_prefix = re.escape(old_prefix)
972             grafted_path = re.sub(r'^' + escaped_prefix, new_prefix, clean_path)
973             # Handle /foo=/ (at least) -- which produces //whatever.
974             grafted_path = '/' + grafted_path.lstrip('/')
975             clean_path_components = path_components(clean_path)
976             # Count the components that were stripped.
977             strip_count = 0 if old_prefix == '/' else old_prefix.count('/')
978             new_prefix_parts = new_prefix.split('/')
979             result_prefix = grafted_path.split('/')[:new_prefix.count('/')]
980             result = [(p, None) for p in result_prefix] \
981                 + clean_path_components[strip_count:]
982             # Now set the graft point name to match the end of new_prefix.
983             graft_point = len(result_prefix)
984             result[graft_point] = \
985                 (new_prefix_parts[-1], clean_path_components[strip_count][1])
986             if new_prefix == '/': # --graft ...=/ is a special case.
987                 return result[1:]
988             return result
989     return path_components(clean_path)
990
991
992 Sha1 = hashlib.sha1
993
994
995 _localtime = getattr(_helpers, 'localtime', None)
996
997 if _localtime:
998     bup_time = namedtuple('bup_time', ['tm_year', 'tm_mon', 'tm_mday',
999                                        'tm_hour', 'tm_min', 'tm_sec',
1000                                        'tm_wday', 'tm_yday',
1001                                        'tm_isdst', 'tm_gmtoff', 'tm_zone'])
1002
1003 # Define a localtime() that returns bup_time when possible.  Note:
1004 # this means that any helpers.localtime() results may need to be
1005 # passed through to_py_time() before being passed to python's time
1006 # module, which doesn't appear willing to ignore the extra items.
1007 if _localtime:
1008     def localtime(time):
1009         return bup_time(*_helpers.localtime(time))
1010     def utc_offset_str(t):
1011         'Return the local offset from UTC as "+hhmm" or "-hhmm" for time t.'
1012         off = localtime(t).tm_gmtoff
1013         hrs = off / 60 / 60
1014         return "%+03d%02d" % (hrs, abs(off - (hrs * 60 * 60)))
1015     def to_py_time(x):
1016         if isinstance(x, time.struct_time):
1017             return x
1018         return time.struct_time(x[:9])
1019 else:
1020     localtime = time.localtime
1021     def utc_offset_str(t):
1022         return time.strftime('%z', localtime(t))
1023     def to_py_time(x):
1024         return x