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
19 # Intentionally replace the dirname "$0" that python prepends
21 sys.path[0] = os.path.dirname(os.path.realpath(__file__)) + '/..'
23 import glob, subprocess
24 from shutil import rmtree
25 from subprocess import PIPE, Popen
26 from tempfile import mkdtemp
27 from binascii import hexlify
29 from bup import compat, options, git
30 from bup.compat import argv_bytes
31 from bup.helpers import Sha1, chunkyreader, istty2, log, progress
32 from bup.io import byte_stream
36 nullf = open(os.devnull, 'wb+')
43 # at least in python 2.5, using "stdout=2" or "stdout=sys.stderr" below
44 # doesn't actually work, because subprocess closes fd #2 right before
45 # execing for some reason. So we work around it by duplicating the fd
47 fd = os.dup(2) # copy stderr
49 p = subprocess.Popen(argv, stdout=fd, close_fds=False)
58 p = subprocess.Popen([b'par2', b'--help'],
59 stdout=nullf, stderr=nullf, stdin=nullf)
62 log('fsck: warning: par2 not found; disabling recovery features.\n')
66 def is_par2_parallel():
67 # A true result means it definitely allows -t1; a false result is
68 # technically inconclusive, but likely means no.
69 tmpdir = mkdtemp(prefix=b'bup-fsck')
71 canary = tmpdir + b'/canary'
72 with open(canary, 'wb') as f:
74 p = subprocess.Popen((b'par2', b'create', b'-qq', b'-t1', canary),
75 stderr=PIPE, stdin=nullf)
76 _, err = p.communicate()
77 parallel = p.returncode == 0
79 if len(err) > 0 and err != b'Invalid option specified: -t1\n':
80 log('Unexpected par2 error output\n')
83 log('Assuming par2 supports parallel processing\n')
85 log('Assuming par2 does not support parallel processing\n')
92 def par2(action, args, verb_floor=0):
94 if _par2_parallel is None:
95 _par2_parallel = is_par2_parallel()
96 cmd = [b'par2', action]
97 if opt.verbose >= verb_floor and not istty2:
106 def par2_generate(base):
107 return par2(b'create',
108 [b'-n1', b'-c200', b'--', base, base + b'.pack', base + b'.idx'],
111 def par2_verify(base):
112 return par2(b'verify', [b'--', base], verb_floor=3)
114 def par2_repair(base):
115 return par2(b'repair', [b'--', base], verb_floor=2)
117 def quick_verify(base):
118 f = open(base + b'.pack', 'rb')
121 assert(len(wantsum) == 20)
124 for b in chunkyreader(f, os.fstat(f.fileno()).st_size - 20):
126 if sum.digest() != wantsum:
127 raise ValueError('expected %r, got %r' % (hexlify(wantsum),
131 def git_verify(base):
135 except Exception as e:
136 log('error: %s\n' % e)
140 return run([b'git', b'verify-pack', b'--', base])
143 def do_pack(base, last, par2_exists, out):
145 if par2_ok and par2_exists and (opt.repair or not opt.generate):
146 vresult = par2_verify(base)
149 rresult = par2_repair(base)
151 action_result = b'failed'
152 log('%s par2 repair: failed (%d)\n' % (last, rresult))
155 action_result = b'repaired'
156 log('%s par2 repair: succeeded (0)\n' % last)
159 action_result = b'failed'
160 log('%s par2 verify: failed (%d)\n' % (last, vresult))
163 action_result = b'ok'
164 elif not opt.generate or (par2_ok and not par2_exists):
165 gresult = git_verify(base)
167 action_result = b'failed'
168 log('%s git verify: failed (%d)\n' % (last, gresult))
171 if par2_ok and opt.generate:
172 presult = par2_generate(base)
174 action_result = b'failed'
175 log('%s par2 create: failed (%d)\n' % (last, presult))
178 action_result = b'generated'
180 action_result = b'ok'
182 assert(opt.generate and (not par2_ok or par2_exists))
183 action_result = b'exists' if par2_exists else b'skipped'
185 out.write(last + b' ' + action_result + b'\n')
190 bup fsck [options...] [filenames...]
192 r,repair attempt to repair errors using par2 (dangerous!)
193 g,generate generate auto-repair information using par2
194 v,verbose increase verbosity (can be used more than once)
195 quick just check pack sha1sum, don't use git verify-pack
196 j,jobs= run 'n' jobs in parallel
197 par2-ok immediately return 0 if par2 is ok, 1 if not
198 disable-par2 ignore par2 even if it is available
200 o = options.Options(optspec)
201 opt, flags, extra = o.parse(compat.argv[1:])
202 opt.verbose = opt.verbose or 0
207 sys.exit(0) # 'true' in sh
213 git.check_repo_or_die()
216 extra = [argv_bytes(x) for x in extra]
218 debug('fsck: No filenames given: checking all packs.\n')
219 extra = glob.glob(git.repo(b'objects/pack/*.pack'))
222 out = byte_stream(sys.stdout)
227 if name.endswith(b'.pack'):
229 elif name.endswith(b'.idx'):
231 elif name.endswith(b'.par2'):
233 elif os.path.exists(name + b'.pack'):
236 raise Exception('%r is not a pack file!' % name)
237 (dir,last) = os.path.split(base)
238 par2_exists = os.path.exists(base + b'.par2')
239 if par2_exists and os.stat(base + b'.par2').st_size == 0:
241 sys.stdout.flush() # Not sure we still need this, but it'll flush out too
242 debug('fsck: checking %r (%s)\n'
243 % (last, par2_ok and par2_exists and 'par2' or 'git'))
245 progress('fsck (%d/%d)\r' % (count, len(extra)))
248 nc = do_pack(base, last, par2_exists, out)
252 while len(outstanding) >= opt.jobs:
255 if pid in outstanding:
264 sys.exit(do_pack(base, last, par2_exists, out))
265 except Exception as e:
266 log('exception: %r\n' % e)
269 while len(outstanding):
272 if pid in outstanding:
277 progress('fsck (%d/%d)\r' % (count, len(extra)))
280 debug('fsck done. \n')