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 call
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 stdout=nullf, stderr=nullf, stdin=nullf)
65 def par2(action, args, verb_floor=0):
67 if _par2_parallel is None:
68 _par2_parallel = is_par2_parallel()
69 cmd = ['par2', action]
70 if opt.verbose >= verb_floor and not istty2:
79 def par2_generate(base):
81 ['-n1', '-c200', '--', base, base + '.pack', base + '.idx'],
84 def par2_verify(base):
85 return par2('verify', ['--', base], verb_floor=3)
87 def par2_repair(base):
88 return par2('repair', ['--', base], verb_floor=2)
90 def quick_verify(base):
91 f = open(base + '.pack', 'rb')
94 assert(len(wantsum) == 20)
97 for b in chunkyreader(f, os.fstat(f.fileno()).st_size - 20):
99 if sum.digest() != wantsum:
100 raise ValueError('expected %r, got %r' % (wantsum.encode('hex'),
104 def git_verify(base):
108 except Exception as e:
109 log('error: %s\n' % e)
113 return run(['git', 'verify-pack', '--', base])
116 def do_pack(base, last, par2_exists):
118 if par2_ok and par2_exists and (opt.repair or not opt.generate):
119 vresult = par2_verify(base)
122 rresult = par2_repair(base)
124 action_result = 'failed'
125 log('%s par2 repair: failed (%d)\n' % (last, rresult))
128 action_result = 'repaired'
129 log('%s par2 repair: succeeded (0)\n' % last)
132 action_result = 'failed'
133 log('%s par2 verify: failed (%d)\n' % (last, vresult))
137 elif not opt.generate or (par2_ok and not par2_exists):
138 gresult = git_verify(base)
140 action_result = 'failed'
141 log('%s git verify: failed (%d)\n' % (last, gresult))
144 if par2_ok and opt.generate:
145 presult = par2_generate(base)
147 action_result = 'failed'
148 log('%s par2 create: failed (%d)\n' % (last, presult))
151 action_result = 'generated'
155 assert(opt.generate and (not par2_ok or par2_exists))
156 action_result = 'exists' if par2_exists else 'skipped'
158 print(last, action_result)
163 bup fsck [options...] [filenames...]
165 r,repair attempt to repair errors using par2 (dangerous!)
166 g,generate generate auto-repair information using par2
167 v,verbose increase verbosity (can be used more than once)
168 quick just check pack sha1sum, don't use git verify-pack
169 j,jobs= run 'n' jobs in parallel
170 par2-ok immediately return 0 if par2 is ok, 1 if not
171 disable-par2 ignore par2 even if it is available
173 o = options.Options(optspec)
174 (opt, flags, extra) = o.parse(sys.argv[1:])
179 sys.exit(0) # 'true' in sh
185 git.check_repo_or_die()
188 debug('fsck: No filenames given: checking all packs.\n')
189 extra = glob.glob(git.repo('objects/pack/*.pack'))
195 if name.endswith('.pack'):
197 elif name.endswith('.idx'):
199 elif name.endswith('.par2'):
201 elif os.path.exists(name + '.pack'):
204 raise Exception('%s is not a pack file!' % name)
205 (dir,last) = os.path.split(base)
206 par2_exists = os.path.exists(base + '.par2')
207 if par2_exists and os.stat(base + '.par2').st_size == 0:
210 debug('fsck: checking %s (%s)\n'
211 % (last, par2_ok and par2_exists and 'par2' or 'git'))
213 progress('fsck (%d/%d)\r' % (count, len(extra)))
216 nc = do_pack(base, last, par2_exists)
220 while len(outstanding) >= opt.jobs:
223 if pid in outstanding:
232 sys.exit(do_pack(base, last, par2_exists))
233 except Exception as e:
234 log('exception: %r\n' % e)
237 while len(outstanding):
240 if pid in outstanding:
245 progress('fsck (%d/%d)\r' % (count, len(extra)))
248 debug('fsck done. \n')