]> arthur.barton.de Git - bup.git/blob - cmd/fsck-cmd.py
bfd045cb966da4c1da92ef614abe3dc82aceb077
[bup.git] / cmd / fsck-cmd.py
1 #!/usr/bin/env python
2 import sys, os, glob, subprocess, time
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                 par2_generate(base)
108             print '%s ok' % last
109     else:
110         assert(opt.generate and (not par2_ok or par2_exists))
111         debug('    skipped: par2 file already generated.\n')
112     return code
113
114
115 optspec = """
116 bup fsck [options...] [filenames...]
117 --
118 r,repair    attempt to repair errors using par2 (dangerous!)
119 g,generate  generate auto-repair information using par2
120 v,verbose   increase verbosity (can be used more than once)
121 quick       just check pack sha1sum, don't use git verify-pack
122 j,jobs=     run 'n' jobs in parallel
123 par2-ok     immediately return 0 if par2 is ok, 1 if not
124 disable-par2  ignore par2 even if it is available
125 """
126 o = options.Options('bup fsck', optspec)
127 (opt, flags, extra) = o.parse(sys.argv[1:])
128
129 par2_setup()
130 if opt.par2_ok:
131     if par2_ok:
132         sys.exit(0)  # 'true' in sh
133     else:
134         sys.exit(1)
135 if opt.disable_par2:
136     par2_ok = 0
137
138 git.check_repo_or_die()
139
140 if not extra:
141     debug('fsck: No filenames given: checking all packs.\n')
142     extra = glob.glob(git.repo('objects/pack/*.pack'))
143
144 code = 0
145 count = 0
146 outstanding = {}
147 for name in extra:
148     if name.endswith('.pack'):
149         base = name[:-5]
150     elif name.endswith('.idx'):
151         base = name[:-4]
152     elif name.endswith('.par2'):
153         base = name[:-5]
154     elif os.path.exists(name + '.pack'):
155         base = name
156     else:
157         raise Exception('%s is not a pack file!' % name)
158     (dir,last) = os.path.split(base)
159     par2_exists = os.path.exists(base + '.par2')
160     if par2_exists and os.stat(base + '.par2').st_size == 0:
161         par2_exists = 0
162     sys.stdout.flush()
163     debug('fsck: checking %s (%s)\n' 
164           % (last, par2_ok and par2_exists and 'par2' or 'git'))
165     if not opt.verbose:
166         progress('fsck (%d/%d)\r' % (count, len(extra)))
167     
168     if not opt.jobs:
169         nc = do_pack(base, last)
170         code = code or nc
171         count += 1
172     else:
173         while len(outstanding) >= opt.jobs:
174             (pid,nc) = os.wait()
175             nc >>= 8
176             if pid in outstanding:
177                 del outstanding[pid]
178                 code = code or nc
179                 count += 1
180         pid = os.fork()
181         if pid:  # parent
182             outstanding[pid] = 1
183         else: # child
184             try:
185                 sys.exit(do_pack(base, last))
186             except Exception, e:
187                 log('exception: %r\n' % e)
188                 sys.exit(99)
189                 
190 while len(outstanding):
191     (pid,nc) = os.wait()
192     nc >>= 8
193     if pid in outstanding:
194         del outstanding[pid]
195         code = code or nc
196         count += 1
197     if not opt.verbose:
198         progress('fsck (%d/%d)\r' % (count, len(extra)))
199
200 if not opt.verbose and istty:
201     log('fsck done.           \n')
202 sys.exit(code)