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