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