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")/bup-python" || exit $?
13 exec "$bup_python" "$0"
17 from __future__ import absolute_import, print_function
18 import sys, os, glob, subprocess
19 from shutil import rmtree
20 from subprocess import PIPE, Popen
21 from tempfile import mkdtemp
22 from binascii import hexlify
24 from bup import compat, options, git
25 from bup.compat import argv_bytes
26 from bup.helpers import Sha1, chunkyreader, istty2, log, progress
27 from bup.io import byte_stream
31 nullf = open(os.devnull, 'wb+')
38 # at least in python 2.5, using "stdout=2" or "stdout=sys.stderr" below
39 # doesn't actually work, because subprocess closes fd #2 right before
40 # execing for some reason. So we work around it by duplicating the fd
42 fd = os.dup(2) # copy stderr
44 p = subprocess.Popen(argv, stdout=fd, close_fds=False)
53 p = subprocess.Popen([b'par2', b'--help'],
54 stdout=nullf, stderr=nullf, stdin=nullf)
57 log('fsck: warning: par2 not found; disabling recovery features.\n')
61 def is_par2_parallel():
62 # A true result means it definitely allows -t1; a false result is
63 # technically inconclusive, but likely means no.
64 tmpdir = mkdtemp(prefix=b'bup-fsck')
66 canary = tmpdir + b'/canary'
67 with open(canary, 'wb') as f:
69 p = subprocess.Popen((b'par2', b'create', b'-qq', b'-t1', canary),
70 stderr=PIPE, stdin=nullf)
71 _, err = p.communicate()
72 parallel = p.returncode == 0
74 if len(err) > 0 and err != b'Invalid option specified: -t1\n':
75 log('Unexpected par2 error output\n')
78 log('Assuming par2 supports parallel processing\n')
80 log('Assuming par2 does not support parallel processing\n')
87 def par2(action, args, verb_floor=0):
89 if _par2_parallel is None:
90 _par2_parallel = is_par2_parallel()
91 cmd = [b'par2', action]
92 if opt.verbose >= verb_floor and not istty2:
101 def par2_generate(base):
102 return par2(b'create',
103 [b'-n1', b'-c200', b'--', base, base + b'.pack', base + b'.idx'],
106 def par2_verify(base):
107 return par2(b'verify', [b'--', base], verb_floor=3)
109 def par2_repair(base):
110 return par2(b'repair', [b'--', base], verb_floor=2)
112 def quick_verify(base):
113 f = open(base + b'.pack', 'rb')
116 assert(len(wantsum) == 20)
119 for b in chunkyreader(f, os.fstat(f.fileno()).st_size - 20):
121 if sum.digest() != wantsum:
122 raise ValueError('expected %r, got %r' % (hexlify(wantsum),
126 def git_verify(base):
130 except Exception as e:
131 log('error: %s\n' % e)
135 return run([b'git', b'verify-pack', b'--', base])
138 def do_pack(base, last, par2_exists, out):
140 if par2_ok and par2_exists and (opt.repair or not opt.generate):
141 vresult = par2_verify(base)
144 rresult = par2_repair(base)
146 action_result = b'failed'
147 log('%s par2 repair: failed (%d)\n' % (last, rresult))
150 action_result = b'repaired'
151 log('%s par2 repair: succeeded (0)\n' % last)
154 action_result = b'failed'
155 log('%s par2 verify: failed (%d)\n' % (last, vresult))
158 action_result = b'ok'
159 elif not opt.generate or (par2_ok and not par2_exists):
160 gresult = git_verify(base)
162 action_result = b'failed'
163 log('%s git verify: failed (%d)\n' % (last, gresult))
166 if par2_ok and opt.generate:
167 presult = par2_generate(base)
169 action_result = b'failed'
170 log('%s par2 create: failed (%d)\n' % (last, presult))
173 action_result = b'generated'
175 action_result = b'ok'
177 assert(opt.generate and (not par2_ok or par2_exists))
178 action_result = b'exists' if par2_exists else b'skipped'
180 out.write(last + b' ' + action_result + b'\n')
185 bup fsck [options...] [filenames...]
187 r,repair attempt to repair errors using par2 (dangerous!)
188 g,generate generate auto-repair information using par2
189 v,verbose increase verbosity (can be used more than once)
190 quick just check pack sha1sum, don't use git verify-pack
191 j,jobs= run 'n' jobs in parallel
192 par2-ok immediately return 0 if par2 is ok, 1 if not
193 disable-par2 ignore par2 even if it is available
195 o = options.Options(optspec)
196 opt, flags, extra = o.parse(compat.argv[1:])
197 opt.verbose = opt.verbose or 0
202 sys.exit(0) # 'true' in sh
208 git.check_repo_or_die()
211 extra = [argv_byes(x) for x in extra]
213 debug('fsck: No filenames given: checking all packs.\n')
214 extra = glob.glob(git.repo(b'objects/pack/*.pack'))
217 out = byte_stream(sys.stdout)
222 if name.endswith(b'.pack'):
224 elif name.endswith(b'.idx'):
226 elif name.endswith(b'.par2'):
228 elif os.path.exists(name + b'.pack'):
231 raise Exception('%r is not a pack file!' % name)
232 (dir,last) = os.path.split(base)
233 par2_exists = os.path.exists(base + b'.par2')
234 if par2_exists and os.stat(base + b'.par2').st_size == 0:
236 sys.stdout.flush() # Not sure we still need this, but it'll flush out too
237 debug('fsck: checking %r (%s)\n'
238 % (last, par2_ok and par2_exists and 'par2' or 'git'))
240 progress('fsck (%d/%d)\r' % (count, len(extra)))
243 nc = do_pack(base, last, par2_exists, out)
247 while len(outstanding) >= opt.jobs:
250 if pid in outstanding:
259 sys.exit(do_pack(base, last, par2_exists, out))
260 except Exception as e:
261 log('exception: %r\n' % e)
264 while len(outstanding):
267 if pid in outstanding:
272 progress('fsck (%d/%d)\r' % (count, len(extra)))
275 debug('fsck done. \n')