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