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