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