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 rc = call(('par2', 'create', '-qq', '-t1', canary))
63 def par2(action, args, verb_floor=0):
65 if _par2_parallel is None:
66 _par2_parallel = is_par2_parallel()
67 cmd = ['par2', action]
68 if opt.verbose >= verb_floor and not istty2:
77 def par2_generate(base):
79 ['-n1', '-c200', '--', base, base + '.pack', base + '.idx'],
82 def par2_verify(base):
83 return par2('verify', ['--', base], verb_floor=3)
85 def par2_repair(base):
86 return par2('repair', ['--', base], verb_floor=2)
88 def quick_verify(base):
89 f = open(base + '.pack', 'rb')
92 assert(len(wantsum) == 20)
95 for b in chunkyreader(f, os.fstat(f.fileno()).st_size - 20):
97 if sum.digest() != wantsum:
98 raise ValueError('expected %r, got %r' % (wantsum.encode('hex'),
102 def git_verify(base):
106 except Exception as e:
107 log('error: %s\n' % e)
111 return run(['git', 'verify-pack', '--', base])
114 def do_pack(base, last, par2_exists):
116 if par2_ok and par2_exists and (opt.repair or not opt.generate):
117 vresult = par2_verify(base)
120 rresult = par2_repair(base)
122 action_result = 'failed'
123 log('%s par2 repair: failed (%d)\n' % (last, rresult))
126 action_result = 'repaired'
127 log('%s par2 repair: succeeded (0)\n' % last)
130 action_result = 'failed'
131 log('%s par2 verify: failed (%d)\n' % (last, vresult))
135 elif not opt.generate or (par2_ok and not par2_exists):
136 gresult = git_verify(base)
138 action_result = 'failed'
139 log('%s git verify: failed (%d)\n' % (last, gresult))
142 if par2_ok and opt.generate:
143 presult = par2_generate(base)
145 action_result = 'failed'
146 log('%s par2 create: failed (%d)\n' % (last, presult))
149 action_result = 'generated'
153 assert(opt.generate and (not par2_ok or par2_exists))
154 action_result = 'exists' if par2_exists else 'skipped'
156 print(last, action_result)
161 bup fsck [options...] [filenames...]
163 r,repair attempt to repair errors using par2 (dangerous!)
164 g,generate generate auto-repair information using par2
165 v,verbose increase verbosity (can be used more than once)
166 quick just check pack sha1sum, don't use git verify-pack
167 j,jobs= run 'n' jobs in parallel
168 par2-ok immediately return 0 if par2 is ok, 1 if not
169 disable-par2 ignore par2 even if it is available
171 o = options.Options(optspec)
172 (opt, flags, extra) = o.parse(sys.argv[1:])
177 sys.exit(0) # 'true' in sh
183 git.check_repo_or_die()
186 debug('fsck: No filenames given: checking all packs.\n')
187 extra = glob.glob(git.repo('objects/pack/*.pack'))
193 if name.endswith('.pack'):
195 elif name.endswith('.idx'):
197 elif name.endswith('.par2'):
199 elif os.path.exists(name + '.pack'):
202 raise Exception('%s is not a pack file!' % name)
203 (dir,last) = os.path.split(base)
204 par2_exists = os.path.exists(base + '.par2')
205 if par2_exists and os.stat(base + '.par2').st_size == 0:
208 debug('fsck: checking %s (%s)\n'
209 % (last, par2_ok and par2_exists and 'par2' or 'git'))
211 progress('fsck (%d/%d)\r' % (count, len(extra)))
214 nc = do_pack(base, last, par2_exists)
218 while len(outstanding) >= opt.jobs:
221 if pid in outstanding:
230 sys.exit(do_pack(base, last, par2_exists))
231 except Exception as e:
232 log('exception: %r\n' % e)
235 while len(outstanding):
238 if pid in outstanding:
243 progress('fsck (%d/%d)\r' % (count, len(extra)))
246 debug('fsck done. \n')