3 # https://sourceware.org/bugzilla/show_bug.cgi?id=26034
4 export "BUP_ARGV_0"="$0"
7 export "BUP_ARGV_${arg_i}"="$arg"
11 # Here to end of preamble replaced during install
12 bup_python="$(dirname "$0")/../../config/bin/python" || exit $?
13 exec "$bup_python" "$0"
17 from __future__ import absolute_import, print_function
18 import os, glob, subprocess, sys
19 from shutil import rmtree
20 from subprocess import PIPE, Popen
21 from tempfile import mkdtemp
22 from binascii import hexlify
24 sys.path[:0] = [os.path.dirname(os.path.realpath(__file__)) + '/..']
26 from bup import compat, options, git
27 from bup.compat import argv_bytes
28 from bup.helpers import Sha1, chunkyreader, istty2, log, progress
29 from bup.io import byte_stream
33 nullf = open(os.devnull, 'wb+')
40 # at least in python 2.5, using "stdout=2" or "stdout=sys.stderr" below
41 # doesn't actually work, because subprocess closes fd #2 right before
42 # execing for some reason. So we work around it by duplicating the fd
44 fd = os.dup(2) # copy stderr
46 p = subprocess.Popen(argv, stdout=fd, close_fds=False)
55 p = subprocess.Popen([b'par2', b'--help'],
56 stdout=nullf, stderr=nullf, stdin=nullf)
59 log('fsck: warning: par2 not found; disabling recovery features.\n')
63 def is_par2_parallel():
64 # A true result means it definitely allows -t1; a false result is
65 # technically inconclusive, but likely means no.
66 tmpdir = mkdtemp(prefix=b'bup-fsck')
68 canary = tmpdir + b'/canary'
69 with open(canary, 'wb') as f:
71 p = subprocess.Popen((b'par2', b'create', b'-qq', b'-t1', canary),
72 stderr=PIPE, stdin=nullf)
73 _, err = p.communicate()
74 parallel = p.returncode == 0
76 if len(err) > 0 and err != b'Invalid option specified: -t1\n':
77 log('Unexpected par2 error output\n')
80 log('Assuming par2 supports parallel processing\n')
82 log('Assuming par2 does not support parallel processing\n')
89 def par2(action, args, verb_floor=0):
91 if _par2_parallel is None:
92 _par2_parallel = is_par2_parallel()
93 cmd = [b'par2', action]
94 if opt.verbose >= verb_floor and not istty2:
103 def par2_generate(base):
104 return par2(b'create',
105 [b'-n1', b'-c200', b'--', base, base + b'.pack', base + b'.idx'],
108 def par2_verify(base):
109 return par2(b'verify', [b'--', base], verb_floor=3)
111 def par2_repair(base):
112 return par2(b'repair', [b'--', base], verb_floor=2)
114 def quick_verify(base):
115 f = open(base + b'.pack', 'rb')
118 assert(len(wantsum) == 20)
121 for b in chunkyreader(f, os.fstat(f.fileno()).st_size - 20):
123 if sum.digest() != wantsum:
124 raise ValueError('expected %r, got %r' % (hexlify(wantsum),
128 def git_verify(base):
132 except Exception as e:
133 log('error: %s\n' % e)
137 return run([b'git', b'verify-pack', b'--', base])
140 def do_pack(base, last, par2_exists, out):
142 if par2_ok and par2_exists and (opt.repair or not opt.generate):
143 vresult = par2_verify(base)
146 rresult = par2_repair(base)
148 action_result = b'failed'
149 log('%s par2 repair: failed (%d)\n' % (last, rresult))
152 action_result = b'repaired'
153 log('%s par2 repair: succeeded (0)\n' % last)
156 action_result = b'failed'
157 log('%s par2 verify: failed (%d)\n' % (last, vresult))
160 action_result = b'ok'
161 elif not opt.generate or (par2_ok and not par2_exists):
162 gresult = git_verify(base)
164 action_result = b'failed'
165 log('%s git verify: failed (%d)\n' % (last, gresult))
168 if par2_ok and opt.generate:
169 presult = par2_generate(base)
171 action_result = b'failed'
172 log('%s par2 create: failed (%d)\n' % (last, presult))
175 action_result = b'generated'
177 action_result = b'ok'
179 assert(opt.generate and (not par2_ok or par2_exists))
180 action_result = b'exists' if par2_exists else b'skipped'
182 out.write(last + b' ' + action_result + b'\n')
187 bup fsck [options...] [filenames...]
189 r,repair attempt to repair errors using par2 (dangerous!)
190 g,generate generate auto-repair information using par2
191 v,verbose increase verbosity (can be used more than once)
192 quick just check pack sha1sum, don't use git verify-pack
193 j,jobs= run 'n' jobs in parallel
194 par2-ok immediately return 0 if par2 is ok, 1 if not
195 disable-par2 ignore par2 even if it is available
197 o = options.Options(optspec)
198 opt, flags, extra = o.parse(compat.argv[1:])
199 opt.verbose = opt.verbose or 0
204 sys.exit(0) # 'true' in sh
210 git.check_repo_or_die()
213 extra = [argv_bytes(x) for x in extra]
215 debug('fsck: No filenames given: checking all packs.\n')
216 extra = glob.glob(git.repo(b'objects/pack/*.pack'))
219 out = byte_stream(sys.stdout)
224 if name.endswith(b'.pack'):
226 elif name.endswith(b'.idx'):
228 elif name.endswith(b'.par2'):
230 elif os.path.exists(name + b'.pack'):
233 raise Exception('%r is not a pack file!' % name)
234 (dir,last) = os.path.split(base)
235 par2_exists = os.path.exists(base + b'.par2')
236 if par2_exists and os.stat(base + b'.par2').st_size == 0:
238 sys.stdout.flush() # Not sure we still need this, but it'll flush out too
239 debug('fsck: checking %r (%s)\n'
240 % (last, par2_ok and par2_exists and 'par2' or 'git'))
242 progress('fsck (%d/%d)\r' % (count, len(extra)))
245 nc = do_pack(base, last, par2_exists, out)
249 while len(outstanding) >= opt.jobs:
252 if pid in outstanding:
261 sys.exit(do_pack(base, last, par2_exists, out))
262 except Exception as e:
263 log('exception: %r\n' % e)
266 while len(outstanding):
269 if pid in outstanding:
274 progress('fsck (%d/%d)\r' % (count, len(extra)))
277 debug('fsck done. \n')