3 bup_python="$(dirname "$0")/bup-python" || exit $?
4 exec "$bup_python" "$0" ${1+"$@"}
8 from __future__ import absolute_import, print_function
9 import sys, os, glob, subprocess
10 from shutil import rmtree
11 from subprocess import PIPE, Popen
12 from tempfile import mkdtemp
14 from bup import options, git
15 from bup.helpers import Sha1, chunkyreader, istty2, log, progress
18 nullf = open('/dev/null')
25 # at least in python 2.5, using "stdout=2" or "stdout=sys.stderr" below
26 # doesn't actually work, because subprocess closes fd #2 right before
27 # execing for some reason. So we work around it by duplicating the fd
29 fd = os.dup(2) # copy stderr
31 p = subprocess.Popen(argv, stdout=fd, close_fds=False)
40 p = subprocess.Popen(['par2', '--help'],
41 stdout=nullf, stderr=nullf, stdin=nullf)
44 log('fsck: warning: par2 not found; disabling recovery features.\n')
48 def is_par2_parallel():
49 # A true result means it definitely allows -t1; a false result is
50 # technically inconclusive, but likely means no.
51 tmpdir = mkdtemp(prefix="bup-fsck")
53 canary = tmpdir + '/canary'
54 with open(canary, 'w') as f:
55 print('canary', file=f)
56 p = subprocess.Popen(('par2', 'create', '-qq', '-t1', canary),
57 stderr=PIPE, stdin=nullf)
58 _, err = p.communicate()
59 parallel = p.returncode == 0
61 if err != b'Invalid option specified: -t1\n':
62 log('Unexpected par2 error output\n')
65 log('Assuming par2 supports parallel processing\n')
67 log('Assuming par2 does not support parallel processing\n')
74 def par2(action, args, verb_floor=0):
76 if _par2_parallel is None:
77 _par2_parallel = is_par2_parallel()
78 cmd = ['par2', action]
79 if opt.verbose >= verb_floor and not istty2:
88 def par2_generate(base):
90 ['-n1', '-c200', '--', base, base + '.pack', base + '.idx'],
93 def par2_verify(base):
94 return par2('verify', ['--', base], verb_floor=3)
96 def par2_repair(base):
97 return par2('repair', ['--', base], verb_floor=2)
99 def quick_verify(base):
100 f = open(base + '.pack', 'rb')
103 assert(len(wantsum) == 20)
106 for b in chunkyreader(f, os.fstat(f.fileno()).st_size - 20):
108 if sum.digest() != wantsum:
109 raise ValueError('expected %r, got %r' % (wantsum.encode('hex'),
113 def git_verify(base):
117 except Exception as e:
118 log('error: %s\n' % e)
122 return run(['git', 'verify-pack', '--', base])
125 def do_pack(base, last, par2_exists):
127 if par2_ok and par2_exists and (opt.repair or not opt.generate):
128 vresult = par2_verify(base)
131 rresult = par2_repair(base)
133 action_result = 'failed'
134 log('%s par2 repair: failed (%d)\n' % (last, rresult))
137 action_result = 'repaired'
138 log('%s par2 repair: succeeded (0)\n' % last)
141 action_result = 'failed'
142 log('%s par2 verify: failed (%d)\n' % (last, vresult))
146 elif not opt.generate or (par2_ok and not par2_exists):
147 gresult = git_verify(base)
149 action_result = 'failed'
150 log('%s git verify: failed (%d)\n' % (last, gresult))
153 if par2_ok and opt.generate:
154 presult = par2_generate(base)
156 action_result = 'failed'
157 log('%s par2 create: failed (%d)\n' % (last, presult))
160 action_result = 'generated'
164 assert(opt.generate and (not par2_ok or par2_exists))
165 action_result = 'exists' if par2_exists else 'skipped'
167 print(last, action_result)
172 bup fsck [options...] [filenames...]
174 r,repair attempt to repair errors using par2 (dangerous!)
175 g,generate generate auto-repair information using par2
176 v,verbose increase verbosity (can be used more than once)
177 quick just check pack sha1sum, don't use git verify-pack
178 j,jobs= run 'n' jobs in parallel
179 par2-ok immediately return 0 if par2 is ok, 1 if not
180 disable-par2 ignore par2 even if it is available
182 o = options.Options(optspec)
183 (opt, flags, extra) = o.parse(sys.argv[1:])
188 sys.exit(0) # 'true' in sh
194 git.check_repo_or_die()
197 debug('fsck: No filenames given: checking all packs.\n')
198 extra = glob.glob(git.repo('objects/pack/*.pack'))
204 if name.endswith('.pack'):
206 elif name.endswith('.idx'):
208 elif name.endswith('.par2'):
210 elif os.path.exists(name + '.pack'):
213 raise Exception('%s is not a pack file!' % name)
214 (dir,last) = os.path.split(base)
215 par2_exists = os.path.exists(base + '.par2')
216 if par2_exists and os.stat(base + '.par2').st_size == 0:
219 debug('fsck: checking %s (%s)\n'
220 % (last, par2_ok and par2_exists and 'par2' or 'git'))
222 progress('fsck (%d/%d)\r' % (count, len(extra)))
225 nc = do_pack(base, last, par2_exists)
229 while len(outstanding) >= opt.jobs:
232 if pid in outstanding:
241 sys.exit(do_pack(base, last, par2_exists))
242 except Exception as e:
243 log('exception: %r\n' % e)
246 while len(outstanding):
249 if pid in outstanding:
254 progress('fsck (%d/%d)\r' % (count, len(extra)))
257 debug('fsck done. \n')