]> arthur.barton.de Git - bup.git/blob - cmd/fsck-cmd.py
import cleanup
[bup.git] / cmd / fsck-cmd.py
1 #!/usr/bin/env python
2 import sys, os, glob, subprocess
3 from bup import options, git
4 from bup.helpers import *
5
6 par2_ok = 0
7 nullf = open('/dev/null')
8
9 def debug(s):
10     if opt.verbose:
11         log(s)
12
13 def run(argv):
14     # at least in python 2.5, using "stdout=2" or "stdout=sys.stderr" below
15     # doesn't actually work, because subprocess closes fd #2 right before
16     # execing for some reason.  So we work around it by duplicating the fd
17     # first.
18     fd = os.dup(2)  # copy stderr
19     try:
20         p = subprocess.Popen(argv, stdout=fd, close_fds=False)
21         return p.wait()
22     finally:
23         os.close(fd)
24
25 def par2_setup():
26     global par2_ok
27     rv = 1
28     try:
29         p = subprocess.Popen(['par2', '--help'],
30                              stdout=nullf, stderr=nullf, stdin=nullf)
31         rv = p.wait()
32     except OSError:
33         log('fsck: warning: par2 not found; disabling recovery features.\n')
34     else:
35         par2_ok = 1
36
37 def parv(lvl):
38     if opt.verbose >= lvl:
39         if istty:
40             return []
41         else:
42             return ['-q']
43     else:
44         return ['-qq']
45
46 def par2_generate(base):
47     return run(['par2', 'create', '-n1', '-c200'] + parv(2)
48                + ['--', base, base+'.pack', base+'.idx'])
49
50 def par2_verify(base):
51     return run(['par2', 'verify'] + parv(3) + ['--', base])
52
53 def par2_repair(base):
54     return run(['par2', 'repair'] + parv(2) + ['--', base])
55
56 def quick_verify(base):
57     f = open(base + '.pack', 'rb')
58     f.seek(-20, 2)
59     wantsum = f.read(20)
60     assert(len(wantsum) == 20)
61     f.seek(0)
62     sum = Sha1()
63     for b in chunkyreader(f, os.fstat(f.fileno()).st_size - 20):
64         sum.update(b)
65     if sum.digest() != wantsum:
66         raise ValueError('expected %r, got %r' % (wantsum.encode('hex'),
67                                                   sum.hexdigest()))
68         
69
70 def git_verify(base):
71     if opt.quick:
72         try:
73             quick_verify(base)
74         except Exception, e:
75             debug('error: %s\n' % e)
76             return 1
77         return 0
78     else:
79         return run(['git', 'verify-pack', '--', base])
80     
81     
82 def do_pack(base, last):
83     code = 0
84     if par2_ok and par2_exists and (opt.repair or not opt.generate):
85         vresult = par2_verify(base)
86         if vresult != 0:
87             if opt.repair:
88                 rresult = par2_repair(base)
89                 if rresult != 0:
90                     print '%s par2 repair: failed (%d)' % (last, rresult)
91                     code = rresult
92                 else:
93                     print '%s par2 repair: succeeded (0)' % last
94                     code = 100
95             else:
96                 print '%s par2 verify: failed (%d)' % (last, vresult)
97                 code = vresult
98         else:
99             print '%s ok' % last
100     elif not opt.generate or (par2_ok and not par2_exists):
101         gresult = git_verify(base)
102         if gresult != 0:
103             print '%s git verify: failed (%d)' % (last, gresult)
104             code = gresult
105         else:
106             if par2_ok and opt.generate:
107                 presult = par2_generate(base)
108                 if presult != 0:
109                     print '%s par2 create: failed (%d)' % (last, presult)
110                     code = presult
111                 else:
112                     print '%s ok' % last
113             else:
114                 print '%s ok' % last
115     else:
116         assert(opt.generate and (not par2_ok or par2_exists))
117         debug('    skipped: par2 file already generated.\n')
118     return code
119
120
121 optspec = """
122 bup fsck [options...] [filenames...]
123 --
124 r,repair    attempt to repair errors using par2 (dangerous!)
125 g,generate  generate auto-repair information using par2
126 v,verbose   increase verbosity (can be used more than once)
127 quick       just check pack sha1sum, don't use git verify-pack
128 j,jobs=     run 'n' jobs in parallel
129 par2-ok     immediately return 0 if par2 is ok, 1 if not
130 disable-par2  ignore par2 even if it is available
131 """
132 o = options.Options('bup fsck', optspec)
133 (opt, flags, extra) = o.parse(sys.argv[1:])
134
135 par2_setup()
136 if opt.par2_ok:
137     if par2_ok:
138         sys.exit(0)  # 'true' in sh
139     else:
140         sys.exit(1)
141 if opt.disable_par2:
142     par2_ok = 0
143
144 git.check_repo_or_die()
145
146 if not extra:
147     debug('fsck: No filenames given: checking all packs.\n')
148     extra = glob.glob(git.repo('objects/pack/*.pack'))
149
150 code = 0
151 count = 0
152 outstanding = {}
153 for name in extra:
154     if name.endswith('.pack'):
155         base = name[:-5]
156     elif name.endswith('.idx'):
157         base = name[:-4]
158     elif name.endswith('.par2'):
159         base = name[:-5]
160     elif os.path.exists(name + '.pack'):
161         base = name
162     else:
163         raise Exception('%s is not a pack file!' % name)
164     (dir,last) = os.path.split(base)
165     par2_exists = os.path.exists(base + '.par2')
166     if par2_exists and os.stat(base + '.par2').st_size == 0:
167         par2_exists = 0
168     sys.stdout.flush()
169     debug('fsck: checking %s (%s)\n' 
170           % (last, par2_ok and par2_exists and 'par2' or 'git'))
171     if not opt.verbose:
172         progress('fsck (%d/%d)\r' % (count, len(extra)))
173     
174     if not opt.jobs:
175         nc = do_pack(base, last)
176         code = code or nc
177         count += 1
178     else:
179         while len(outstanding) >= opt.jobs:
180             (pid,nc) = os.wait()
181             nc >>= 8
182             if pid in outstanding:
183                 del outstanding[pid]
184                 code = code or nc
185                 count += 1
186         pid = os.fork()
187         if pid:  # parent
188             outstanding[pid] = 1
189         else: # child
190             try:
191                 sys.exit(do_pack(base, last))
192             except Exception, e:
193                 log('exception: %r\n' % e)
194                 sys.exit(99)
195                 
196 while len(outstanding):
197     (pid,nc) = os.wait()
198     nc >>= 8
199     if pid in outstanding:
200         del outstanding[pid]
201         code = code or nc
202         count += 1
203     if not opt.verbose:
204         progress('fsck (%d/%d)\r' % (count, len(extra)))
205
206 if not opt.verbose and istty:
207     log('fsck done.           \n')
208 sys.exit(code)