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