]> arthur.barton.de Git - bup.git/blob - t/testfile2
replace test files with ones made up of bup code
[bup.git] / t / testfile2
1 #!/usr/bin/env python
2 from bup import options, drecurse
3 from bup.helpers import *
4
5 optspec = """
6 bup drecurse <path>
7 --
8 x,xdev,one-file-system   don't cross filesystem boundaries
9 q,quiet  don't actually print filenames
10 profile  run under the python profiler
11 """
12 o = options.Options('bup drecurse', optspec)
13 (opt, flags, extra) = o.parse(sys.argv[1:])
14
15 if len(extra) != 1:
16     o.fatal("exactly one filename expected")
17
18 it = drecurse.recursive_dirlist(extra, opt.xdev)
19 if opt.profile:
20     import cProfile
21     def do_it():
22         for i in it:
23             pass
24     cProfile.run('do_it()')
25 else:
26     if opt.quiet:
27         for i in it:
28             pass
29     else:
30         for (name,st) in it:
31             print name
32
33 if saved_errors:
34     log('WARNING: %d errors encountered.\n' % len(saved_errors))
35     sys.exit(1)
36 #!/usr/bin/env python
37 import sys, time, struct
38 from bup import hashsplit, git, options, client
39 from bup.helpers import *
40 from subprocess import PIPE
41
42
43 optspec = """
44 bup split [-tcb] [-n name] [--bench] [filenames...]
45 --
46 r,remote=  remote repository path
47 b,blobs    output a series of blob ids
48 t,tree     output a tree id
49 c,commit   output a commit id
50 n,name=    name of backup set to update (if any)
51 N,noop     don't actually save the data anywhere
52 q,quiet    don't print progress messages
53 v,verbose  increase log output (can be used more than once)
54 copy       just copy input to output, hashsplitting along the way
55 bench      print benchmark timings to stderr
56 max-pack-size=  maximum bytes in a single pack
57 max-pack-objects=  maximum number of objects in a single pack
58 fanout=  maximum number of blobs in a single tree
59 """
60 o = options.Options('bup split', optspec)
61 (opt, flags, extra) = o.parse(sys.argv[1:])
62
63 git.check_repo_or_die()
64 if not (opt.blobs or opt.tree or opt.commit or opt.name or
65         opt.noop or opt.copy):
66     o.fatal("use one or more of -b, -t, -c, -n, -N, --copy")
67 if (opt.noop or opt.copy) and (opt.blobs or opt.tree or 
68                                opt.commit or opt.name):
69     o.fatal('-N is incompatible with -b, -t, -c, -n')
70
71 if opt.verbose >= 2:
72     git.verbose = opt.verbose - 1
73     opt.bench = 1
74 if opt.max_pack_size:
75     hashsplit.max_pack_size = parse_num(opt.max_pack_size)
76 if opt.max_pack_objects:
77     hashsplit.max_pack_objects = parse_num(opt.max_pack_objects)
78 if opt.fanout:
79     hashsplit.fanout = parse_num(opt.fanout)
80 if opt.blobs:
81     hashsplit.fanout = 0
82
83 is_reverse = os.environ.get('BUP_SERVER_REVERSE')
84 if is_reverse and opt.remote:
85     o.fatal("don't use -r in reverse mode; it's automatic")
86 start_time = time.time()
87
88 refname = opt.name and 'refs/heads/%s' % opt.name or None
89 if opt.noop or opt.copy:
90     cli = w = oldref = None
91 elif opt.remote or is_reverse:
92     cli = client.Client(opt.remote)
93     oldref = refname and cli.read_ref(refname) or None
94     w = cli.new_packwriter()
95 else:
96     cli = None
97     oldref = refname and git.read_ref(refname) or None
98     w = git.PackWriter()
99
100 files = extra and (open(fn) for fn in extra) or [sys.stdin]
101 if w:
102     shalist = hashsplit.split_to_shalist(w, files)
103     tree = w.new_tree(shalist)
104 else:
105     last = 0
106     for (blob, bits) in hashsplit.hashsplit_iter(files):
107         hashsplit.total_split += len(blob)
108         if opt.copy:
109             sys.stdout.write(str(blob))
110         megs = hashsplit.total_split/1024/1024
111         if not opt.quiet and last != megs:
112             progress('%d Mbytes read\r' % megs)
113             last = megs
114     progress('%d Mbytes read, done.\n' % megs)
115
116 if opt.verbose:
117     log('\n')
118 if opt.blobs:
119     for (mode,name,bin) in shalist:
120         print bin.encode('hex')
121 if opt.tree:
122     print tree.encode('hex')
123 if opt.commit or opt.name:
124     msg = 'bup split\n\nGenerated by command:\n%r' % sys.argv
125     ref = opt.name and ('refs/heads/%s' % opt.name) or None
126     commit = w.new_commit(oldref, tree, msg)
127     if opt.commit:
128         print commit.encode('hex')
129
130 if w:
131     w.cjon is changing some random bytes here and theref
132         
133 if opt.name:
134     if cli:
135         cli.update_ref(refname, commit, oldref)
136     else:
137         git.update_ref(refname, commit, oldref)
138
139 if cli:
140     cli.close()
141
142 secs = time.time() - start_time
143 size = hashsplit.total_split
144 if opt.bench:
145     log('\nbup: %.2fkbytes in %.2f secs = %.2f kbytes/sec\n'
146         % (size/1024., secs, size/1024./secs))
147 #!/usr/bin/env python
148 import sys, re, struct, mmap
149 from bup import git, options
150 from bup.helpers import *
151
152
153 def s_from_bytes(bytes):
154     clist = [chr(b) for b in bytes]
155     return ''.join(clist)
156
157
158 def report(count):
159     fields = ['VmSize', 'VmRSS', 'VmData', 'VmStk']
160     d = {}
161     for line in open('/proc/self/status').readlines():
162         l = re.split(r':\s*', line.strip(), 1)
163         d[l[0]] = l[1]
164     if count >= 0:
165         e1 = count
166         fields = [d[k] for k in fields]
167     else:
168         e1 = ''
169     print ('%9s  ' + ('%10s ' * len(fields))) % tuple([e1] + fields)
170     sys.stdout.flush()
171
172
173 optspec = """
174 bup memtest [-n elements] [-c cycles]
175 --
176 n,number=  number of objects per cycle
177 c,cycles=  number of cycles to run
178 ignore-midx  ignore .midx files, use only .idx files
179 """
180 o = options.Options('bup memtest', optspec)
181 (opt, flags, extra) = o.parse(sys.argv[1:])
182
183 if extra:
184     o.fatal('no arguments expected')
185
186 git.ignore_midx = opt.ignore_midx
187
188 git.check_repo_or_die()
189 m = git.PackIdxList(git.repo('objects/pack'))
190
191 cycles = opt.cycles or 100
192 number = opt.number or 10000
193
194 report(-1)
195 f = open('/dev/urandom')
196 a = mmap.mmap(-1, 20)
197 report(0)
198 for c in xrange(cycles):
199     for n in xrange(number):
200         b = f.read(3)
201         if 0:
202             bytes = list(struct.unpack('!BBB', b)) + [0]*17
203             bytes[2] &= 0xf0
204             bin = struct.pack('!20s', s_from_bytes(bytes))
205         else:
206             a[0:2] = b[0:2]
207             a[2] = chr(ord(b[2]) & 0xf0)
208             bin = str(a[0:20])
209         #print bin.encode('hex')
210         m.exists(bin)
211     report((c+1)*number)
212 #!/usr/bin/env python
213 import sys, os, stat
214 from bup import options, git, vfs
215 from bup.helpers import *
216
217 def print_node(text, n):
218     prefix = ''
219     if opt.hash:
220         prefix += "%s " % n.hash.encode('hex')
221     if stat.S_ISDIR(n.mode):
222         print '%s%s/' % (prefix, text)
223     elif stat.S_ISLNK(n.mode):
224         print '%s%s@' % (prefix, text)
225     else:
226         print '%s%s' % (prefix, text)
227
228
229 optspec = """
230 bup ls <dirs...>
231 --
232 s,hash   show hash for each file
233 """
234 o = options.Options('bup ls', optspec)
235 (opt, flags, extra) = o.parse(sys.argv[1:])
236
237 git.check_repo_or_die()
238 top = vfs.RefList(None)
239
240 if not extra:
241     extra = ['/']
242
243 ret = 0
244 for d in extra:
245     try:
246         n = top.lresolve(d)
247         if stat.S_ISDIR(n.mode):
248             for sub in n:
249                 print_node(sub.name, sub)
250         else:
251             print_node(d, n)
252     except vfs.NodeError, e:
253         log('error: %s\n' % e)
254         ret = 1
255
256 sys.exit(ret)
257 #!/usr/bin/env python
258 import sys, os, re, stat, readline, fnmatch
259 from bup import options, git, shquote, vfs
260 from bup.helpers import *
261
262 def node_name(text, n):
263     if stat.S_ISDIR(n.mode):
264         return '%s/' % text
265     elif stat.S_ISLNK(n.mode):
266         return '%s@' % text
267     else:
268         return '%s' % text
269
270
271 def do_ls(path, n):
272     l = []
273     if stat.S_ISDIR(n.mode):
274         for sub in n:
275             l.append(node_name(sub.name, sub))
276     else:
277         l.append(node_name(path, n))
278     print columnate(l, '')
279     
280
281 def write_to_file(inf, outf):
282     for blob in chunkyreader(inf):
283         outf.write(blob)
284     
285
286 def inputiter():
287     if os.isatty(sys.stdin.fileno()):
288         while 1:
289             try:
290                 yield raw_input('bup> ')
291             except EOFError:
292                 break
293     else:
294         for line in sys.stdin:
295             yield line
296
297
298 def _completer_get_subs(line):
299     (qtype, lastword) = shquote.unfinished_word(line)
300     (dir,name) = os.path.split(lastword)
301     #log('\ncompleter: %r %r %r\n' % (qtype, lastword, text))
302     n = pwd.resolve(dir)
303     subs = list(filter(lambda x: x.name.startswith(name),
304                        n.subs()))
305     return (dir, name, qtype, lastword, subs)
306
307
308 _last_line = None
309 _last_res = None
310 def completer(text, state):
311     global _last_line
312     global _last_res
313     try:
314         line = readline.get_line_buffer()[:readline.get_endidx()]
315         if _last_line != line:
316             _last_res = _completer_get_subs(line)
317             _last_line = line
318         (dir, name, qtype, lastword, subs) = _last_res
319         if state < len(subs):
320             sn = subs[state]
321             sn1 = sn.resolve('')  # deref symlinks
322             fullname = os.path.join(dir, sn.name)
323             if stat.S_ISDIR(sn1.mode):
324                 ret = shquote.what_to_add(qtype, lastword, fullname+'/',
325                                           terminate=False)
326             else:
327                 ret = shquote.what_to_add(qtype, lastword, fullname,
328                                           terminate=True) + ' '
329             return text + ret
330     except Exception, e:
331         log('\nerror in completion: %s\n' % e)
332
333             
334 optspec = """
335 bup ftp
336 """
337 o = options.Options('bup ftp', optspec)
338 (opt, flags, extra) = o.parse(sys.argv[1:])
339
340 git.check_repo_or_die()
341
342 top = vfs.RefList(None)
343 pwd = top
344
345 if extra:
346     lines = extra
347 else:
348     readline.set_completer_delims(' \t\n\r/')
349     readline.set_completer(completer)
350     readline.parse_and_bind("tab: complete")
351     lines = inputiter()
352
353 for line in lines:
354     if not line.strip():
355         continue
356     words = [word for (wordstart,word) in shquote.quotesplit(line)]
357     cmd = words[0].lower()
358     #log('execute: %r %r\n' % (cmd, parm))
359     try:
360         if cmd == 'ls':
361             for parm in (words[1:] or ['.']):
362                 do_ls(parm, pwd.resolve(parm))
363         elif cmd == 'cd':
364             for parm in words[1:]:
365                 pwd = pwd.resolve(parm)
366         elif cmd == 'pwd':
367             print pwd.fullname()
368         elif cmd == 'cat':
369             for parm in words[1:]:
370                 write_to_file(pwd.resolve(parm).open(), sys.stdout)
371         elif cmd == 'get':
372             if len(words) not in [2,3]:
373                 raise Exception('Usage: get <filename> [localname]')
374             rname = words[1]
375             (dir,base) = os.path.split(rname)
376             lname = len(words)>2 and words[2] or base
377             inf = pwd.resolve(rname).open()
378             log('Saving %r\n' % lname)
379             write_to_file(inf, open(lname, 'wb'))
380         elif cmd == 'mget':
381             for parm in words[1:]:
382                 (dir,base) = os.path.split(parm)
383                 for n in pwd.resolve(dir).subs():
384                     if fnmatch.fnmatch(n.name, base):
385                         try:
386                             log('Saving %r\n' % n.name)
387                             inf = n.open()
388                             outf = open(n.name, 'wb')
389                             write_to_file(inf, outf)
390                             outf.close()
391                         except Exception, e:
392                             log('  error: %s\n' % e)
393         elif cmd == 'help' or cmd == '?':
394             log('Commands: ls cd pwd cat get mget help quit\n')
395         elif cmd == 'quit' or cmd == 'exit' or cmd == 'bye':
396             break
397         else:
398             raise Exception('no such command %r' % cmd)
399     except Exception, e:
400         log('error: %s\n' % e)
401         #raise
402 #!/usr/bin/env python
403 import sys, mmap
404 from bup import options, _hashsplit
405 from bup.helpers import *
406
407 optspec = """
408 bup random [-S seed] <numbytes>
409 --
410 S,seed=   optional random number seed (default 1)
411 f,force   print random data to stdout even if it's a tty
412 """
413 o = options.Options('bup random', optspec)
414 (opt, flags, extra) = o.parse(sys.argv[1:])
415
416 if len(extra) != 1:
417     o.fatal("exactly one argument expected")
418
419 total = parse_num(extra[0])
420
421 if opt.force or (not os.isatty(1) and
422                  not atoi(os.environ.get('BUP_FORCE_TTY')) & 1):
423     _hashsplit.write_random(sys.stdout.fileno(), total, opt.seed or 0)
424 else:
425     log('error: not writing binary data to a terminal. Use -f to force.\n')
426     sys.exit(1)
427 #!/usr/bin/env python
428 import sys, os, glob
429 from bup import options
430
431 optspec = """
432 bup help <command>
433 """
434 o = options.Options('bup help', optspec)
435 (opt, flags, extra) = o.parse(sys.argv[1:])
436
437 if len(extra) == 0:
438     # the wrapper program provides the default usage string
439     os.execvp(os.environ['BUP_MAIN_EXE'], ['bup'])
440 elif len(extra) == 1:
441     docname = (extra[0]=='bup' and 'bup' or ('bup-%s' % extra[0]))
442     exe = sys.argv[0]
443     (exepath, exefile) = os.path.split(exe)
444     manpath = os.path.join(exepath, '../Documentation/' + docname + '.[1-9]')
445     g = glob.glob(manpath)
446     if g:
447         os.execvp('man', ['man', '-l', g[0]])
448     else:
449         os.execvp('man', ['man', docname])
450 else:
451     o.fatal("exactly one command name expected")
452 #!/usr/bin/env python
453 import sys, os, stat, errno, fuse, re, time, tempfile
454 from bup import options, git, vfs
455 from bup.helpers import *
456
457
458 class Stat(fuse.Stat):
459     def __init__(self):
460         self.st_mode = 0
461         self.st_ino = 0
462         self.st_dev = 0
463         self.st_nlink = 0
464         self.st_uid = 0
465         self.st_gid = 0
466         self.st_size = 0
467         self.st_atime = 0
468         self.st_mtime = 0
469         self.st_ctime = 0
470         self.st_blocks = 0
471         self.st_blksize = 0
472         self.st_rdev = 0
473
474
475 cache = {}
476 def cache_get(top, path):
477     parts = path.split('/')
478     cache[('',)] = top
479     c = None
480     max = len(parts)
481     #log('cache: %r\n' % cache.keys())
482     for i in range(max):
483         pre = parts[:max-i]
484         #log('cache trying: %r\n' % pre)
485         c = cache.get(tuple(pre))
486         if c:
487             rest = parts[max-i:]
488             for r in rest:
489                 #log('resolving %r from %r\n' % (r, c.fullname()))
490                 c = c.lresolve(r)
491                 key = tuple(pre + [r])
492                 #log('saving: %r\n' % (key,))
493                 cache[key] = c
494             break
495     assert(c)
496     return c
497         
498     
499
500 class BupFs(fuse.Fuse):
501     def __init__(self, top):
502         fuse.Fuse.__init__(self)
503         self.top = top
504     
505     def getattr(self, path):
506         log('--getattr(%r)\n' % path)
507         try:
508             node = cache_get(self.top, path)
509             st = Stat()
510             st.st_mode = node.mode
511             st.st_nlink = node.nlinks()
512             st.st_size = node.size()
513             st.st_mtime = node.mtime
514             st.st_ctime = node.ctime
515             st.st_atime = node.atime
516             return st
517         except vfs.NoSuchFile:
518             return -errno.ENOENT
519
520     def readdir(self, path, offset):
521         log('--readdir(%r)\n' % path)
522         node = cache_get(self.top, path)
523         yield fuse.Direntry('.')
524         yield fuse.Direntry('..')
525         for sub in node.subs():
526             yield fuse.Direntry(sub.name)
527
528     def readlink(self, path):
529         log('--readlink(%r)\n' % path)
530         node = cache_get(self.top, path)
531         return node.readlink()
532
533     def open(self, path, flags):
534         log('--open(%r)\n' % path)
535         node = cache_get(self.top, path)
536         accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
537         if (flags & accmode) != os.O_RDONLY:
538             return -errno.EACCES
539         node.open()
540
541     def release(self, path, flags):
542         log('--release(%r)\n' % path)
543
544     def read(self, path, size, offset):
545         log('--read(%r)\n' % path)
546         n = cache_get(self.top, path)
547         o = n.open()
548         o.seek(offset)
549         return o.read(size)
550
551
552 if not hasattr(fuse, '__version__'):
553     raise RuntimeError, "your fuse module is too old for fuse.__version__"
554 fuse.fuse_python_api = (0, 2)
555
556
557 optspec = """
558 bup fuse [-d] [-f] <mountpoint>
559 --
560 d,debug   increase debug level
561 f,foreground  run in foreground
562 """
563 o = options.Options('bup fuse', optspec)
564 (opt, flags, extra) = o.parse(sys.argv[1:])
565
566 if len(extra) != 1:
567     o.fatal("exactly one argument expected")
568
569 git.check_repo_or_die()
570 top = vfs.RefList(None)
571 f = BupFs(top)
572 f.fuse_args.mountpoint = extra[0]
573 if opt.debug:
574     f.fuse_args.add('debug')
575 if opt.foreground:
576     f.fuse_args.setmod('foreground')
577 print f.multithreaded
578 f.multithreaded = False
579
580 f.main()
581 #!/usr/bin/env python
582 from bup import git, options, client
583 from bup.helpers import *
584
585 optspec = """
586 [BUP_DIR=...] bup init [-r host:path]
587 --
588 r,remote=  remote repository path
589 """
590 o = options.Options('bup init', optspec)
591 (opt, flags, extra) = o.parse(sys.argv[1:])
592
593 if extra:
594     o.fatal("no arguments expected")
595
596
597 if opt.remote:
598     git.init_repo()  # local repo
599     git.check_repo_or_die()
600     cli = client.Client(opt.remote, create=True)
601     cli.close()
602 else:
603     git.init_repo()
604 #!/usr/bin/env python
605 import sys, math, struct, glob
606 from bup import options, git
607 from bup.helpers import *
608
609 PAGE_SIZE=4096
610 SHA_PER_PAGE=PAGE_SIZE/200.
611
612
613 def merge(idxlist, bits, table):
614     count = 0
615     for e in git.idxmerge(idxlist):
616         count += 1
617         prefix = git.extract_bits(e, bits)
618         table[prefix] = count
619         yield e
620
621
622 def do_midx(outdir, outfilename, infilenames):
623     if not outfilename:
624         assert(outdir)
625         sum = Sha1('\0'.join(infilenames)).hexdigest()
626         outfilename = '%s/midx-%s.midx' % (outdir, sum)
627     
628     inp = []
629     total = 0
630     for name in infilenames:
631         ix = git.PackIdx(name)
632         inp.append(ix)
633         total += len(ix)
634
635     log('Merging %d indexes (%d objects).\n' % (len(infilenames), total))
636     if (not opt.force and (total < 1024 and len(infilenames) < 3)) \
637        or (opt.force and not total):
638         log('midx: nothing to do.\n')
639         return
640
641     pages = int(total/SHA_PER_PAGE) or 1
642     bits = int(math.ceil(math.log(pages, 2)))
643     entries = 2**bits
644     log('Table size: %d (%d bits)\n' % (entries*4, bits))
645     
646     table = [0]*entries
647
648     try:
649         os.unlink(outfilename)
650     except OSError:
651         pass
652     f = open(outfilename + '.tmp', 'w+')
653     f.write('MIDX\0\0\0\2')
654     f.write(struct.pack('!I', bits))
655     assert(f.tell() == 12)
656     f.write('\0'*4*entries)
657     
658     for e in merge(inp, bits, table):
659         f.write(e)
660         
661     f.write('\0'.join(os.path.basename(p) for p in infilenames))
662
663     f.seek(12)
664     f.write(struct.pack('!%dI' % entries, *table))
665     f.close()
666     os.rename(outfilename + '.tmp', outfilename)
667
668     # this is just for testing
669     if 0:
670         p = git.PackMidx(outfilename)
671         assert(len(p.idxnames) == len(infilenames))
672         print p.idxnames
673         assert(len(p) == total)
674         pi = iter(p)
675         for i in merge(inp, total, bits, table):
676             assert(i == pi.next())
677             assert(p.exists(i))
678
679     print outfilename
680
681 optspec = """
682 bup midx [options...] <idxnames...>
683 --
684 o,output=  output midx filename (default: auto-generated)
685 a,auto     automatically create .midx from any unindexed .idx files
686 f,force    automatically create .midx from *all* .idx files
687 """
688 o = options.Options('bup midx', optspec)
689 (opt, flags, extra) = o.parse(sys.argv[1:])
690
691 if extra and (opt.auto or opt.force):
692     o.fatal("you can't use -f/-a and also provide filenames")
693
694 git.check_repo_or_die()
695
696 if extra:
697     do_midx(git.repo('objects/pack'), opt.output, extra)
698 elif opt.auto or opt.force:
699     paths = [git.repo('objects/pack')]
700     paths += glob.glob(git.repo('index-cache/*/.'))
701     for path in paths:
702         log('midx: scanning %s\n' % path)
703         if opt.force:
704             do_midx(path, opt.output, glob.glob('%s/*.idx' % path))
705         elif opt.auto:
706             m = git.PackIdxList(path)
707             needed = {}
708             for pack in m.packs:  # only .idx files without a .midx are open
709                 if pack.name.endswith('.idx'):
710                     needed[pack.name] = 1
711             del m
712             do_midx(path, opt.output, needed.keys())
713         log('\n')
714 else:
715     o.fatal("you must use -f or -a or provide input filenames")
716 #!/usr/bin/env python
717 import sys, os, random
718 from bup import options
719 from bup.helpers import *
720
721
722 def randblock(n):
723     l = []
724     for i in xrange(n):
725         l.append(chr(random.randrange(0,256)))
726     return ''.join(l)
727
728
729 optspec = """
730 bup damage [-n count] [-s maxsize] [-S seed] <filenames...>
731 --
732    WARNING: THIS COMMAND IS EXTREMELY DANGEROUS
733 n,num=   number of blocks to damage
734 s,size=  maximum size of each damaged block
735 percent= maximum size of each damaged block (as a percent of entire file)
736 equal    spread damage evenly throughout the file
737 S,seed=  random number seed (for repeatable tests)
738 """
739 o = options.Options('bup damage', optspec)
740 (opt, flags, extra) = o.parse(sys.argv[1:])
741
742 if not extra:
743     o.fatal('filenames expected')
744
745 if opt.seed != None:
746     random.seed(opt.seed)
747
748 for name in extra:
749     log('Damaging "%s"...\n' % name)
750     f = open(name, 'r+b')
751     st = os.fstat(f.fileno())
752     size = st.st_size
753     if opt.percent or opt.size:
754         ms1 = int(float(opt.percent or 0)/100.0*size) or size
755         ms2 = opt.size or size
756         maxsize = min(ms1, ms2)
757     else:
758         maxsize = 1
759     chunks = opt.num or 10
760     chunksize = size/chunks
761     for r in range(chunks):
762         sz = random.randrange(1, maxsize+1)
763         if sz > size:
764             sz = size
765         if opt.equal:
766             ofs = r*chunksize
767         else:
768             ofs = random.randrange(0, size - sz + 1)
769         log('  %6d bytes at %d\n' % (sz, ofs))
770         f.seek(ofs)
771         f.write(randblock(sz))
772     f.close()
773 #!/usr/bin/env python
774 import sys, struct, mmap
775 from bup import options, git
776 from bup.helpers import *
777
778 suspended_w = None
779
780
781 def init_dir(conn, arg):
782     git.init_repo(arg)
783     log('bup server: bupdir initialized: %r\n' % git.repodir)
784     conn.ok()
785
786
787 def set_dir(conn, arg):
788     git.check_repo_or_die(arg)
789     log('bup server: bupdir is %r\n' % git.repodir)
790     conn.ok()
791
792     
793 def list_indexes(conn, junk):
794     git.check_repo_or_die()
795     for f in os.listdir(git.repo('objects/pack')):
796         if f.endswith('.idx'):
797             conn.write('%s\n' % f)
798     conn.ok()
799
800
801 def send_index(conn, name):
802     git.check_repo_or_die()
803     assert(name.find('/') < 0)
804     assert(name.endswith('.idx'))
805     idx = git.PackIdx(git.repo('objects/pack/%s' % name))
806     conn.write(struct.pack('!I', len(idx.map)))
807     conn.write(idx.map)
808     conn.ok()
809
810
811 def receive_objects(conn, junk):
812     global suspended_w
813     git.check_repo_or_die()
814     suggested = {}
815     if suspended_w:
816         w = suspended_w
817         suspended_w = None
818     else:
819         w = git.PackWriter()
820     while 1:
821         ns = conn.read(4)
822         if not ns:
823             w.abort()
824             raise Exception('object read: expected length header, got EOF\n')
825         n = struct.unpack('!I', ns)[0]
826         #log('expecting %d bytes\n' % n)
827         if not n:
828             log('bup server: received %d object%s.\n' 
829                 % (w.count, w.count!=1 and "s" or ''))
830             fullpath = w.close()
831             if fullpath:
832                 (dir, name) = os.path.split(fullpath)
833                 conn.write('%s.idx\n' % name)
834             conn.ok()
835             return
836         elif n == 0xffffffff:
837             log('bup server: receive-objects suspended.\n')
838             suspended_w = w
839             conn.ok()
840             return
841             
842         buf = conn.read(n)  # object sizes in bup are reasonably small
843         #log('read %d bytes\n' % n)
844         if len(buf) < n:
845             w.abort()
846             raise Exception('object read: expected %d bytes, got %d\n'
847                             % (n, len(buf)))
848         (type, content) = git._decode_packobj(buf)
849         sha = git.calc_hash(type, content)
850         oldpack = w.exists(sha)
851         # FIXME: we only suggest a single index per cycle, because the client
852         # is currently dumb to download more than one per cycle anyway.
853         # Actually we should fix the client, but this is a minor optimization
854         # on the server side.
855         if not suggested and \
856           oldpack and (oldpack == True or oldpack.endswith('.midx')):
857             # FIXME: we shouldn't really have to know about midx files
858             # at this layer.  But exists() on a midx doesn't return the
859             # packname (since it doesn't know)... probably we should just
860             # fix that deficiency of midx files eventually, although it'll
861             # make the files bigger.  This method is certainly not very
862             # efficient.
863             w.objcache.refresh(skip_midx = True)
864             oldpack = w.objcache.exists(sha)
865             log('new suggestion: %r\n' % oldpack)
866             assert(oldpack)
867             assert(oldpack != True)
868             assert(not oldpack.endswith('.midx'))
869             w.objcache.refresh(skip_midx = False)
870         if not suggested and oldpack:
871             assert(oldpack.endswith('.idx'))
872             (dir,name) = os.path.split(oldpack)
873             if not (name in suggested):
874                 log("bup server: suggesting index %s\n" % name)
875                 conn.write('index %s\n' % name)
876                 suggested[name] = 1
877         else:
878             w._raw_write([buf])
879     # NOTREACHED
880
881
882 def read_ref(conn, refname):
883     git.check_repo_or_die()
884     r = git.read_ref(refname)
885     conn.write('%s\n' % (r or '').encode('hex'))
886     conn.ok()
887
888
889 def update_ref(conn, refname):
890     git.check_repo_or_die()
891     newval = conn.readline().strip()
892     oldval = conn.readline().strip()
893     git.update_ref(refname, newval.decode('hex'), oldval.decode('hex'))
894     conn.ok()
895
896
897 def cat(conn, id):
898     git.check_repo_or_die()
899     try:
900         for blob in git.cat(id):
901             conn.write(struct.pack('!I', len(blob)))
902             conn.write(blob)
903     except KeyError, e:
904         log('server: error: %s\n' % e)
905         conn.write('\0\0\0\0')
906         conn.error(e)
907     else:
908         conn.write('\0\0\0\0')
909         conn.ok()
910
911
912 optspec = """
913 bup server
914 """
915 o = options.Options('bup server', optspec)
916 (opt, flags, extra) = o.parse(sys.argv[1:])
917
918 if extra:
919     o.fatal('no arguments expected')
920
921 log('bup server: reading from stdin.\n')
922
923 commands = {
924     'init-dir': init_dir,
925     'set-dir': set_dir,
926     'list-indexes': list_indexes,
927     'send-index': send_index,
928     'receive-objects': receive_objects,
929     'read-ref': read_ref,
930     'update-ref': update_ref,
931     'cat': cat,
932 }
933
934 # FIXME: this protocol is totally lame and not at all future-proof.
935 # (Especially since we abort completely as soon as *anything* bad happens)
936 conn = Conn(sys.stdin, sys.stdout)
937 lr = linereader(conn)
938 for _line in lr:
939     line = _line.strip()
940     if not line:
941         continue
942     log('bup server: command: %r\n' % line)
943     words = line.split(' ', 1)
944     cmd = words[0]
945     rest = len(words)>1 and words[1] or ''
946     if cmd == 'quit':
947         break
948     else:
949         cmd = commands.get(cmd)
950         if cmd:
951             cmd(conn, rest)
952         else:
953             raise Exception('unknown server command: %r\n' % line)
954
955 log('bup server: done\n')
956 #!/usr/bin/env python
957 import sys, time, struct
958 from bup import hashsplit, git, options, client
959 from bup.helpers import *
960 from subprocess import PIPE
961
962
963 optspec = """
964 bup join [-r host:path] [refs or hashes...]
965 --
966 r,remote=  remote repository path
967 """
968 o = options.Options('bup join', optspec)
969 (opt, flags, extra) = o.parse(sys.argv[1:])
970
971 git.check_repo_or_die()
972
973 if not extra:
974     extra = linereader(sys.stdin)
975
976 ret = 0
977
978 if opt.remote:
979     cli = client.Client(opt.remote)
980     cat = cli.cat
981 else:
982     cp = git.CatPipe()
983     cat = cp.join
984
985 for id in extra:
986     try:
987         for blob in cat(id):
988             sys.stdout.write(blob)
989     except KeyError, e:
990         sys.stdout.flush()
991         log('error: %s\n' % e)
992         ret = 1
993
994 sys.exit(ret)
995 #!/usr/bin/env python
996 import sys, re, errno, stat, time, math
997 from bup import hashsplit, git, options, index, client
998 from bup.helpers import *
999
1000
1001 optspec = """
1002 bup save [-tc] [-n name] <filenames...>
1003 --
1004 r,remote=  remote repository path
1005 t,tree     output a tree id
1006 c,commit   output a commit id
1007 n,name=    name of backup set to update (if any)
1008 v,verbose  increase log output (can be used more than once)
1009 q,quiet    don't show progress meter
1010 smaller=   only back up files smaller than n bytes
1011 """
1012 o = options.Options('bup save', optspec)
1013 (opt, flags, extra) = o.parse(sys.argv[1:])
1014
1015 git.check_repo_or_die()
1016 if not (opt.tree or opt.commit or opt.name):
1017     o.fatal("use one or more of -t, -c, -n")
1018 if not extra:
1019     o.fatal("no filenames given")
1020
1021 opt.progress = (istty and not opt.quiet)
1022 opt.smaller = parse_num(opt.smaller or 0)
1023
1024 is_reverse = os.environ.get('BUP_SERVER_REVERSE')
1025 if is_reverse and opt.remote:
1026     o.fatal("don't use -r in reverse mode; it's automatic")
1027
1028 refname = opt.name and 'refs/heads/%s' % opt.name or None
1029 if opt.remote or is_reverse:
1030     cli = client.Client(opt.remote)
1031     oldref = refname and cli.read_ref(refname) or None
1032     w = cli.new_packwriter()
1033 else:
1034     cli = None
1035     oldref = refname and git.read_ref(refname) or None
1036     w = git.PackWriter()
1037
1038 handle_ctrl_c()
1039
1040
1041 def eatslash(dir):
1042     if dir.endswith('/'):
1043         return dir[:-1]
1044     else:
1045         return dir
1046
1047
1048 parts = ['']
1049 shalists = [[]]
1050
1051 def _push(part):
1052     assert(part)
1053     parts.append(part)
1054     shalists.append([])
1055
1056 def _pop(force_tree):
1057     assert(len(parts) >= 1)
1058     part = parts.pop()
1059     shalist = shalists.pop()
1060     tree = force_tree or w.new_tree(shalist)
1061     if shalists:
1062         shalists[-1].append(('40000', part, tree))
1063     else:  # this was the toplevel, so put it back for sanity
1064         shalists.append(shalist)
1065     return tree
1066
1067 lastremain = None
1068 def progress_report(n):
1069     global count, subcount, lastremain
1070     subcount += n
1071     cc = count + subcount
1072     pct = total and (cc*100.0/total) or 0
1073     now = time.time()
1074     elapsed = now - tstart
1075     kps = elapsed and int(cc/1024./elapsed)
1076     kps_frac = 10 ** int(math.log(kps+1, 10) - 1)
1077     kps = int(kps/kps_frac)*kps_frac
1078     if cc:
1079         remain = elapsed*1.0/cc * (total-cc)
1080     else:
1081         remain = 0.0
1082     if (lastremain and (remain > lastremain)
1083           and ((remain - lastremain)/lastremain < 0.05)):
1084         remain = lastremain
1085     else:
1086         lastremain = remain
1087     hours = int(remain/60/60)
1088     mins = int(remain/60 - hours*60)
1089     secs = int(remain - hours*60*60 - mins*60)
1090     if elapsed < 30:
1091         remainstr = ''
1092         kpsstr = ''
1093     else:
1094         kpsstr = '%dk/s' % kps
1095         if hours:
1096             remainstr = '%dh%dm' % (hours, mins)
1097         elif mins:
1098             remainstr = '%dm%d' % (mins, secs)
1099         else:
1100             remainstr = '%ds' % secs
1101     progress('Saving: %.2f%% (%d/%dk, %d/%d files) %s %s\r'
1102              % (pct, cc/1024, total/1024, fcount, ftotal,
1103                 remainstr, kpsstr))
1104
1105
1106 r = index.Reader(git.repo('bupindex'))
1107
1108 def already_saved(ent):
1109     return ent.is_valid() and w.exists(ent.sha) and ent.sha
1110
1111 def wantrecurse_pre(ent):
1112     return not already_saved(ent)
1113
1114 def wantrecurse_during(ent):
1115     return not already_saved(ent) or ent.sha_missing()
1116
1117 total = ftotal = 0
1118 if opt.progress:
1119     for (transname,ent) in r.filter(extra, wantrecurse=wantrecurse_pre):
1120         if not (ftotal % 10024):
1121             progress('Reading index: %d\r' % ftotal)
1122         exists = ent.exists()
1123         hashvalid = already_saved(ent)
1124         ent.set_sha_missing(not hashvalid)
1125         if not opt.smaller or ent.size < opt.smaller:
1126             if exists and not hashvalid:
1127                 total += ent.size
1128         ftotal += 1
1129     progress('Reading index: %d, done.\n' % ftotal)
1130     hashsplit.progress_callback = progress_report
1131
1132 tstart = time.time()
1133 count = subcount = fcount = 0
1134 lastskip_name = None
1135 lastdir = ''
1136 for (transname,ent) in r.filter(extra, wantrecurse=wantrecurse_during):
1137     (dir, file) = os.path.split(ent.name)
1138     exists = (ent.flags & index.IX_EXISTS)
1139     hashvalid = already_saved(ent)
1140     wasmissing = ent.sha_missing()
1141     oldsize = ent.size
1142     if opt.verbose:
1143         if not exists:
1144             status = 'D'
1145         elif not hashvalid:
1146             if ent.sha == index.EMPTY_SHA:
1147                 status = 'A'
1148             else:
1149                 status = 'M'
1150         else:
1151             status = ' '
1152         if opt.verbose >= 2:
1153             log('%s %-70s\n' % (status, ent.name))
1154         elif not stat.S_ISDIR(ent.mode) and lastdir != dir:
1155             if not lastdir.startswith(dir):
1156                 log('%s %-70s\n' % (status, os.path.join(dir, '')))
1157             lastdir = dir
1158
1159     if opt.progress:
1160         progress_report(0)
1161     fcount += 1
1162     
1163     if not exists:
1164         continue
1165     if opt.smaller and ent.size >= opt.smaller:
1166         if exists and not hashvalid:
1167             add_error('skipping large file "%s"' % ent.name)
1168             lastskip_name = ent.name
1169         continue
1170
1171     assert(dir.startswith('/'))
1172     dirp = dir.split('/')
1173     while parts > dirp:
1174         _pop(force_tree = None)
1175     if dir != '/':
1176         for part in dirp[len(parts):]:
1177             _push(part)
1178
1179     if not file:
1180         # no filename portion means this is a subdir.  But
1181         # sub/parentdirectories already handled in the pop/push() part above.
1182         oldtree = already_saved(ent) # may be None
1183         newtree = _pop(force_tree = oldtree)
1184         if not oldtree:
1185             if lastskip_name and lastskip_name.startswith(ent.name):
1186                 ent.invalidate()
1187             else:
1188                 ent.validate(040000, newtree)
1189             ent.repack()
1190         if exists and wasmissing:
1191             count += oldsize
1192         continue
1193
1194     # it's not a directory
1195     id = None
1196     if hashvalid:
1197         mode = '%o' % ent.gitmode
1198         id = ent.sha
1199         shalists[-1].append((mode, 
1200                              git.mangle_name(file, ent.mode, ent.gitmode),
1201                              id))
1202     else:
1203         if stat.S_ISREG(ent.mode):
1204             try:
1205                 f = hashsplit.open_noatime(ent.name)
1206             except IOError, e:
1207                 add_error(e)
1208                 lastskip_name = ent.name
1209             except OSError, e:
1210                 add_error(e)
1211                 lastskip_name = ent.name
1212             else:
1213                 (mode, id) = hashsplit.split_to_blob_or_tree(w, [f])
1214         else:
1215             if stat.S_ISDIR(ent.mode):
1216                 assert(0)  # handled above
1217             elif stat.S_ISLNK(ent.mode):
1218                 try:
1219                     rl = os.readlink(ent.name)
1220                 except OSError, e:
1221                     add_error(e)
1222                     lastskip_name = ent.name
1223                 except IOError, e:
1224                     add_error(e)
1225                     lastskip_name = ent.name
1226                 else:
1227                     (mode, id) = ('120000', w.new_blob(rl))
1228             else:
1229                 add_error(Exception('skipping special file "%s"' % ent.name))
1230                 lastskip_name = ent.name
1231         if id:
1232             ent.validate(int(mode, 8), id)
1233             ent.repack()
1234             shalists[-1].append((mode,
1235                                  git.mangle_name(file, ent.mode, ent.gitmode),
1236                                  id))
1237     if exists and wasmissing:
1238         count += oldsize
1239         subcount = 0
1240
1241
1242 if opt.progress:
1243     pct = total and count*100.0/total or 100
1244     progress('Saving: %.2f%% (%d/%dk, %d/%d files), done.    \n'
1245              % (pct, count/1024, total/1024, fcount, ftotal))
1246
1247 while len(parts) > 1:
1248     _pop(force_tree = None)
1249 assert(len(shalists) == 1)
1250 tree = w.new_tree(shalists[-1])
1251 if opt.tree:
1252     print tree.encode('hex')
1253 if opt.commit or opt.name:
1254     msg = 'bup save\n\nGenerated by command:\n%r' % sys.argv
1255     ref = opt.name and ('refs/heads/%s' % opt.name) or None
1256     commit = w.new_commit(oldref, tree, msg)
1257     if opt.commit:
1258         print commit.encode('hex')
1259
1260 w.close()  # must close before we can update the ref
1261         
1262 if opt.name:
1263     if cli:
1264         cli.update_ref(refname, commit, oldref)
1265     else:
1266         git.update_ref(refname, commit, oldref)
1267
1268 if cli:
1269     cli.close()
1270
1271 if saved_errors:
1272     log('WARNING: %d errors encountered while saving.\n' % len(saved_errors))
1273     sys.exit(1)
1274 #!/usr/bin/env python
1275 import sys, time
1276 from bup import options
1277
1278 optspec = """
1279 bup tick
1280 """
1281 o = options.Options('bup tick', optspec)
1282 (opt, flags, extra) = o.parse(sys.argv[1:])
1283
1284 if extra:
1285     o.fatal("no arguments expected")
1286
1287 t = time.time()
1288 tleft = 1 - (t - int(t))
1289 time.sleep(tleft)
1290 #!/usr/bin/env python
1291 import os, sys, stat, time
1292 from bup import options, git, index, drecurse
1293 from bup.helpers import *
1294
1295
1296 def merge_indexes(out, r1, r2):
1297     for e in index.MergeIter([r1, r2]):
1298         # FIXME: shouldn't we remove deleted entries eventually?  When?
1299         out.add_ixentry(e)
1300
1301
1302 class IterHelper:
1303     def __init__(self, l):
1304         self.i = iter(l)
1305         self.cur = None
1306         self.next()
1307
1308     def next(self):
1309         try:
1310             self.cur = self.i.next()
1311         except StopIteration:
1312             self.cur = None
1313         return self.cur
1314
1315
1316 def check_index(reader):
1317     try:
1318         log('check: checking forward iteration...\n')
1319         e = None
1320         d = {}
1321         for e in reader.forward_iter():
1322             if e.children_n:
1323                 if opt.verbose:
1324                     log('%08x+%-4d %r\n' % (e.children_ofs, e.children_n,
1325                                             e.name))
1326                 assert(e.children_ofs)
1327                 assert(e.name.endswith('/'))
1328                 assert(not d.get(e.children_ofs))
1329                 d[e.children_ofs] = 1
1330             if e.flags & index.IX_HASHVALID:
1331                 assert(e.sha != index.EMPTY_SHA)
1332                 assert(e.gitmode)
1333         assert(not e or e.name == '/')  # last entry is *always* /
1334         log('check: checking normal iteration...\n')
1335         last = None
1336         for e in reader:
1337             if last:
1338                 assert(last > e.name)
1339             last = e.name
1340     except:
1341         log('index error! at %r\n' % e)
1342         raise
1343     log('check: passed.\n')
1344
1345
1346 def update_index(top):
1347     ri = index.Reader(indexfile)
1348     wi = index.Writer(indexfile)
1349     rig = IterHelper(ri.iter(name=top))
1350     tstart = int(time.time())
1351
1352     hashgen = None
1353     if opt.fake_valid:
1354         def hashgen(name):
1355             return (0100644, index.FAKE_SHA)
1356
1357     total = 0
1358     for (path,pst) in drecurse.recursive_dirlist([top], xdev=opt.xdev):
1359         if opt.verbose>=2 or (opt.verbose==1 and stat.S_ISDIR(pst.st_mode)):
1360             sys.stdout.write('%s\n' % path)
1361             sys.stdout.flush()
1362             progress('Indexing: %d\r' % total)
1363         elif not (total % 128):
1364             progress('Indexing: %d\r' % total)
1365         total += 1
1366         while rig.cur and rig.cur.name > path:  # deleted paths
1367             if rig.cur.exists():
1368                 rig.cur.set_deleted()
1369                 rig.cur.repack()
1370             rig.next()
1371         if rig.cur and rig.cur.name == path:    # paths that already existed
1372             if pst:
1373                 rig.cur.from_stat(pst, tstart)
1374             if not (rig.cur.flags & index.IX_HASHVALID):
1375                 if hashgen:
1376                     (rig.cur.gitmode, rig.cur.sha) = hashgen(path)
1377                     rig.cur.flags |= index.IX_HASHVALID
1378             if opt.fake_invalid:
1379                 rig.cur.invalidate()
1380             rig.cur.repack()
1381             rig.next()
1382         else:  # new paths
1383             wi.add(path, pst, hashgen = hashgen)
1384     progress('Indexing: %d, done.\n' % total)
1385     
1386     if ri.exists():
1387         ri.save()
1388         wi.flush()
1389         if wi.count:
1390             wr = wi.new_reader()
1391             if opt.check:
1392                 log('check: before merging: oldfile\n')
1393                 check_index(ri)
1394                 log('check: before merging: newfile\n')
1395                 check_index(wr)
1396             mi = index.Writer(indexfile)
1397             merge_indexes(mi, ri, wr)
1398             ri.close()
1399             mi.close()
1400             wr.close()
1401         wi.abort()
1402     else:
1403         wi.close()
1404
1405
1406 optspec = """
1407 bup index <-p|m|u> [options...] <filenames...>
1408 --
1409 p,print    print the index entries for the given names (also works with -u)
1410 m,modified print only added/deleted/modified files (implies -p)
1411 s,status   print each filename with a status char (A/M/D) (implies -p)
1412 H,hash     print the hash for each object next to its name (implies -p)
1413 l,long     print more information about each file
1414 u,update   (recursively) update the index entries for the given filenames
1415 x,xdev,one-file-system  don't cross filesystem boundaries
1416 fake-valid mark all index entries as up-to-date even if they aren't
1417 fake-invalid mark all index entries as invalid
1418 check      carefully check index file integrity
1419 f,indexfile=  the name of the index file (default 'index')
1420 v,verbose  increase log output (can be used more than once)
1421 """
1422 o = options.Options('bup index', optspec)
1423 (opt, flags, extra) = o.parse(sys.argv[1:])
1424
1425 if not (opt.modified or opt['print'] or opt.status or opt.update or opt.check):
1426     o.fatal('supply one or more of -p, -s, -m, -u, or --check')
1427 if (opt.fake_valid or opt.fake_invalid) and not opt.update:
1428     o.fatal('--fake-{in,}valid are meaningless without -u')
1429 if opt.fake_valid and opt.fake_invalid:
1430     o.fatal('--fake-valid is incompatible with --fake-invalid')
1431
1432 git.check_repo_or_die()
1433 indexfile = opt.indexfile or git.repo('bupindex')
1434
1435 handle_ctrl_c()
1436
1437 if opt.check:
1438     log('check: starting initial check.\n')
1439     check_index(index.Reader(indexfile))
1440
1441 paths = index.reduce_paths(extra)
1442
1443 if opt.update:
1444     if not paths:
1445         o.fatal('update (-u) requested but no paths given')
1446     for (rp,path) in paths:
1447         update_index(rp)
1448
1449 if opt['print'] or opt.status or opt.modified:
1450     for (name, ent) in index.Reader(indexfile).filter(extra or ['']):
1451         if (opt.modified 
1452             and (ent.is_valid() or ent.is_deleted() or not ent.mode)):
1453             continue
1454         line = ''
1455         if opt.status:
1456             if ent.is_deleted():
1457                 line += 'D '
1458             elif not ent.is_valid():
1459                 if ent.sha == index.EMPTY_SHA:
1460                     line += 'A '
1461                 else:
1462                     line += 'M '
1463             else:
1464                 line += '  '
1465         if opt.hash:
1466             line += ent.sha.encode('hex') + ' '
1467         if opt.long:
1468             line += "%7s %7s " % (oct(ent.mode), oct(ent.gitmode))
1469         print line + (name or './')
1470
1471 if opt.check and (opt['print'] or opt.status or opt.modified or opt.update):
1472     log('check: starting final check.\n')
1473     check_index(index.Reader(indexfile))
1474
1475 if saved_errors:
1476     log('WARNING: %d errors encountered.\n' % len(saved_errors))
1477     sys.exit(1)
1478 #!/usr/bin/env python
1479 import sys, os, struct
1480 from bup import options, helpers
1481
1482 optspec = """
1483 bup rbackup-server
1484 --
1485     This command is not intended to be run manually.
1486 """
1487 o = options.Options('bup rbackup-server', optspec)
1488 (opt, flags, extra) = o.parse(sys.argv[1:])
1489 if extra:
1490     o.fatal('no arguments expected')
1491
1492 # get the subcommand's argv.
1493 # Normally we could just pass this on the command line, but since we'll often
1494 # be getting called on the other end of an ssh pipe, which tends to mangle
1495 # argv (by sending it via the shell), this way is much safer.
1496 buf = sys.stdin.read(4)
1497 sz = struct.unpack('!I', buf)[0]
1498 assert(sz > 0)
1499 assert(sz < 1000000)
1500 buf = sys.stdin.read(sz)
1501 assert(len(buf) == sz)
1502 argv = buf.split('\0')
1503
1504 # stdin/stdout are supposedly connected to 'bup server' that the caller
1505 # started for us (often on the other end of an ssh tunnel), so we don't want
1506 # to misuse them.  Move them out of the way, then replace stdout with
1507 # a pointer to stderr in case our subcommand wants to do something with it.
1508 #
1509 # It might be nice to do the same with stdin, but my experiments showed that
1510 # ssh seems to make its child's stderr a readable-but-never-reads-anything
1511 # socket.  They really should have used shutdown(SHUT_WR) on the other end
1512 # of it, but probably didn't.  Anyway, it's too messy, so let's just make sure
1513 # anyone reading from stdin is disappointed.
1514 #
1515 # (You can't just leave stdin/stdout "not open" by closing the file
1516 # descriptors.  Then the next file that opens is automatically assigned 0 or 1,
1517 # and people *trying* to read/write stdin/stdout get screwed.)
1518 os.dup2(0, 3)
1519 os.dup2(1, 4)
1520 os.dup2(2, 1)
1521 in approximately the same placeRDONLY)
1522 and distrib-0)
1523 ution as(fd)
1524
1525 in the original test filesERSE'] = helpers.hostname()
1526 os.execvp(argv[0], argv)
1527 sys.exit(99)
1528 #!/usr/bin/env python
1529 import sys, os, glob, subprocess, time
1530 from bup import options, git
1531 from bup.helpers import *
1532
1533 par2_ok = 0
1534 nullf = open('/dev/null')
1535
1536 def debug(s):
1537     if opt.verbose:
1538         log(s)
1539
1540 def run(argv):
1541     # at least in python 2.5, using "stdout=2" or "stdout=sys.stderr" below
1542     # doesn't actually work, because subprocess closes fd #2 right before
1543     # execing for some reason.  So we work around it by duplicating the fd
1544     # first.
1545     fd = os.dup(2)  # copy stderr
1546     try:
1547         p = subprocess.Popen(argv, stdout=fd, close_fds=False)
1548         return p.wait()
1549     finally:
1550         os.close(fd)
1551
1552 def par2_setup():
1553     global par2_ok
1554     rv = 1
1555     try:
1556         p = subprocess.Popen(['par2', '--help'],
1557                              stdout=nullf, stderr=nullf, stdin=nullf)
1558         rv = p.wait()
1559     except OSError:
1560         log('fsck: warning: par2 not found; disabling recovery features.\n')
1561     else:
1562         par2_ok = 1
1563
1564 def parv(lvl):
1565     if opt.verbose >= lvl:
1566         if istty:
1567             return []
1568         else:
1569             return ['-q']
1570     else:
1571         return ['-qq']
1572
1573 def par2_generate(base):
1574     return run(['par2', 'create', '-n1', '-c200'] + parv(2)
1575                + ['--', base, base+'.pack', base+'.idx'])
1576
1577 def par2_verify(base):
1578     return run(['par2', 'verify'] + parv(3) + ['--', base])
1579
1580 def par2_repair(base):
1581     return run(['par2', 'repair'] + parv(2) + ['--', base])
1582
1583 def quick_verify(base):
1584     f = open(base + '.pack', 'rb')
1585     f.seek(-20, 2)
1586     wantsum = f.read(20)
1587     assert(len(wantsum) == 20)
1588     f.seek(0)
1589     sum = Sha1()
1590     for b in chunkyreader(f, os.fstat(f.fileno()).st_size - 20):
1591         sum.update(b)
1592     if sum.digest() != wantsum:
1593         raise ValueError('expected %r, got %r' % (wantsum.encode('hex'),
1594                                                   sum.hexdigest()))
1595         
1596
1597 def git_verify(base):
1598     if opt.quick:
1599         try:
1600             quick_verify(base)
1601         except Exception, e:
1602             debug('error: %s\n' % e)
1603             return 1
1604         return 0
1605     else:
1606         return run(['git', 'verify-pack', '--', base])
1607     
1608     
1609 def do_pack(base, last):
1610     code = 0
1611     if par2_ok and par2_exists and (opt.repair or not opt.generate):
1612         vresult = par2_verify(base)
1613         if vresult != 0:
1614             if opt.repair:
1615                 rresult = par2_repair(base)
1616                 if rresult != 0:
1617                     print '%s par2 repair: failed (%d)' % (last, rresult)
1618                     code = rresult
1619                 else:
1620                     print '%s par2 repair: succeeded (0)' % last
1621                     code = 100
1622             else:
1623                 print '%s par2 verify: failed (%d)' % (last, vresult)
1624                 code = vresult
1625         else:
1626             print '%s ok' % last
1627     elif not opt.generate or (par2_ok and not par2_exists):
1628         gresult = git_verify(base)
1629         if gresult != 0:
1630             print '%s git verify: failed (%d)' % (last, gresult)
1631             code = gresult
1632         else:
1633             if par2_ok and opt.generate:
1634                 presult = par2_generate(base)
1635                 if presult != 0:
1636                     print '%s par2 create: failed (%d)' % (last, presult)
1637                     code = presult
1638                 else:
1639                     print '%s ok' % last
1640             else:
1641                 print '%s ok' % last
1642     else:
1643         assert(opt.generate and (not par2_ok or par2_exists))
1644         debug('    skipped: par2 file already generated.\n')
1645     return code
1646
1647
1648 optspec = """
1649 bup fsck [options...] [filenames...]
1650 --
1651 r,repair    attempt to repair errors using par2 (dangerous!)
1652 g,generate  generate auto-repair information using par2
1653 v,verbose   increase verbosity (can be used more than once)
1654 quick       just check pack sha1sum, don't use git verify-pack
1655 j,jobs=     run 'n' jobs in parallel
1656 par2-ok     immediately return 0 if par2 is ok, 1 if not
1657 disable-par2  ignore par2 even if it is available
1658 """
1659 o = options.Options('bup fsck', optspec)
1660 (opt, flags, extra) = o.parse(sys.argv[1:])
1661
1662 par2_setup()
1663 if opt.par2_ok:
1664     if par2_ok:
1665         sys.exit(0)  # 'true' in sh
1666     else:
1667         sys.exit(1)
1668 if opt.disable_par2:
1669     par2_ok = 0
1670
1671 git.check_repo_or_die()
1672
1673 if not extra:
1674     debug('fsck: No filenames given: checking all packs.\n')
1675     extra = glob.glob(git.repo('objects/pack/*.pack'))
1676
1677 code = 0
1678 count = 0
1679 outstanding = {}
1680 for name in extra:
1681     if name.endswith('.pack'):
1682         base = name[:-5]
1683     elif name.endswith('.idx'):
1684         base = name[:-4]
1685     elif name.endswith('.par2'):
1686         base = name[:-5]
1687     elif os.path.exists(name + '.pack'):
1688         base = name
1689     else:
1690         raise Exception('%s is not a pack file!' % name)
1691     (dir,last) = os.path.split(base)
1692     par2_exists = os.path.exists(base + '.par2')
1693     if par2_exists and os.stat(base + '.par2').st_size == 0:
1694         par2_exists = 0
1695     sys.stdout.flush()
1696     debug('fsck: checking %s (%s)\n' 
1697           % (last, par2_ok and par2_exists and 'par2' or 'git'))
1698     if not opt.verbose:
1699         progress('fsck (%d/%d)\r' % (count, len(extra)))
1700     
1701     if not opt.jobs:
1702         nc = do_pack(base, last)
1703         code = code or nc
1704         count += 1
1705     else:
1706         while len(outstanding) >= opt.jobs:
1707             (pid,nc) = os.wait()
1708             nc >>= 8
1709             if pid in outstanding:
1710                 del outstanding[pid]
1711                 code = code or nc
1712                 count += 1
1713         pid = os.fork()
1714         if pid:  # parent
1715             outstanding[pid] = 1
1716         else: # child
1717             try:
1718                 sys.exit(do_pack(base, last))
1719             except Exception, e:
1720                 log('exception: %r\n' % e)
1721                 sys.exit(99)
1722                 
1723 while len(outstanding):
1724     (pid,nc) = os.wait()
1725     nc >>= 8
1726     if pid in outstanding:
1727         del outstanding[pid]
1728         code = code or nc
1729         count += 1
1730     if not opt.verbose:
1731         progress('fsck (%d/%d)\r' % (count, len(extra)))
1732
1733 if not opt.verbose and istty:
1734     log('fsck done.           \n')
1735 sys.exit(code)
1736 #!/usr/bin/env python
1737 import sys, os, struct, getopt, subprocess, signal
1738 from bup import options, ssh
1739 from bup.helpers import *
1740
1741 optspec = """
1742 bup rbackup <hostname> index ...
1743 bup rbackup <hostname> save ...
1744 bup rbackup <hostname> split ...
1745 """
1746 o = options.Options('bup rbackup', optspec, optfunc=getopt.getopt)
1747 (opt, flags, extra) = o.parse(sys.argv[1:])
1748 if len(extra) < 2:
1749     o.fatal('arguments expected')
1750
1751 class SigException(Exception):
1752     def __init__(self, signum):
1753         self.signum = signum
1754         Exception.__init__(self, 'signal %d received' % signum)
1755 def handler(signum, frame):
1756     raise SigException(signum)
1757
1758 signal.signal(signal.SIGTERM, handler)
1759 signal.signal(signal.SIGINT, handler)
1760
1761 sp = None
1762 p = None
1763 ret = 99
1764
1765 try:
1766     hostname = extra[0]
1767     argv = extra[1:]
1768     p = ssh.connect(hostname, 'rbackup-server')
1769
1770     argvs = '\0'.join(['bup'] + argv)
1771     p.stdin.write(struct.pack('!I', len(argvs)) + argvs)
1772     p.stdin.flush()
1773
1774     main_exe = os.environ.get('BUP_MAIN_EXE') or sys.argv[0]
1775     sp = subprocess.Popen([main_exe, 'server'], stdin=p.stdout, stdout=p.stdin)
1776
1777     p.stdin.close()
1778     p.stdout.close()
1779
1780 finally:
1781     while 1:
1782         # if we get a signal while waiting, we have to keep waiting, just
1783         # in case our child doesn't die.
1784         try:
1785             ret = p.wait()
1786             sp.wait()
1787             break
1788         except SigException, e:
1789             log('\nbup rbackup: %s\n' % e)
1790             os.kill(p.pid, e.signum)
1791             ret = 84
1792 sys.exit(ret)
1793 #!/usr/bin/env python
1794 import sys, os, re
1795 from bup import options
1796
1797 optspec = """
1798 bup newliner
1799 """
1800 o = options.Options('bup newliner', optspec)
1801 (opt, flags, extra) = o.parse(sys.argv[1:])
1802
1803 if extra:
1804     o.fatal("no arguments expected")
1805
1806 r = re.compile(r'([\r\n])')
1807 lastlen = 0
1808 all = ''
1809 while 1:
1810     l = r.split(all, 1)
1811     if len(l) <= 1:
1812         try:
1813             b = os.read(sys.stdin.fileno(), 4096)
1814         except KeyboardInterrupt:
1815             break
1816         if not b:
1817             break
1818         all += b
1819     else:
1820         assert(len(l) == 3)
1821         (line, splitchar, all) = l
1822         #splitchar = '\n'
1823         sys.stdout.write('%-*s%s' % (lastlen, line, splitchar))
1824         if splitchar == '\r':
1825             lastlen = len(line)
1826         else:
1827             lastlen = 0
1828         sys.stdout.flush()
1829
1830 if lastlen or all:
1831     sys.stdout.write('%-*s\n' % (lastlen, all))
1832 #!/usr/bin/env python
1833 import sys
1834 from bup import options, git, _hashsplit
1835 from bup.helpers import *
1836
1837
1838 optspec = """
1839 bup margin
1840 """
1841 o = options.Options('bup margin', optspec)
1842 (opt, flags, extra) = o.parse(sys.argv[1:])
1843
1844 if extra:
1845     o.fatal("no arguments expected")
1846
1847 git.check_repo_or_die()
1848 #git.ignore_midx = 1
1849
1850 mi = git.PackIdxList(git.repo('objects/pack'))
1851 last = '\0'*20
1852 longmatch = 0
1853 for i in mi:
1854     if i == last:
1855         continue
1856     #assert(str(i) >= last)
1857     pm = _hashsplit.bitmatch(last, i)
1858     longmatch = max(longmatch, pm)
1859     last = i
1860 print longmatch
1861 #!/usr/bin/env python
1862 from bup import options, drecurse
1863 from bup.helpers import *
1864
1865 optspec = """
1866 bup drecurse <path>
1867 --
1868 x,xdev,one-file-system   don't cross filesystem boundaries
1869 q,quiet  don't actually print filenames
1870 profile  run under the python profiler
1871 """
1872 o = options.Options('bup drecurse', optspec)
1873 (opt, flags, extra) = o.parse(sys.argv[1:])
1874
1875 if len(extra) != 1:
1876     o.fatal("exactly one filename expected")
1877
1878 it = drecurse.recursive_dirlist(extra, opt.xdev)
1879 if opt.profile:
1880     import cProfile
1881     def do_it():
1882         for i in it:
1883             pass
1884     cProfile.run('do_it()')
1885 else:
1886     if opt.quiet:
1887         for i in it:
1888             pass
1889     else:
1890         for (name,st) in it:
1891             print name
1892
1893 if saved_errors:
1894     log('WARNING: %d errors encountered.\n' % len(saved_errors))
1895     sys.exit(1)
1896 #!/usr/bin/env python
1897 import sys, time, struct
1898 from bup import hashsplit, git, options, client
1899 from bup.helpers import *
1900 from subprocess import PIPE
1901
1902
1903 optspec = """
1904 bup split [-tcb] [-n name] [--bench] [filenames...]
1905 --
1906 r,remote=  remote repository path
1907 b,blobs    output a series of blob ids
1908 t,tree     output a tree id
1909 c,commit   output a commit id
1910 n,name=    name of backup set to update (if any)
1911 N,noop     don't actually save the data anywhere
1912 q,quiet    don't print progress messages
1913 v,verbose  increase log output (can be used more than once)
1914 copy       just copy input to output, hashsplitting along the way
1915 bench      print benchmark timings to stderr
1916 max-pack-size=  maximum bytes in a single pack
1917 max-pack-objects=  maximum number of objects in a single pack
1918 fanout=  maximum number of blobs in a single tree
1919 """
1920 o = options.Options('bup split', optspec)
1921 (opt, flags, extra) = o.parse(sys.argv[1:])
1922
1923 git.check_repo_or_die()
1924 if not (opt.blobs or opt.tree or opt.commit or opt.name or
1925         opt.noop or opt.copy):
1926     o.fatal("use one or more of -b, -t, -c, -n, -N, --copy")
1927 if (opt.noop or opt.copy) and (opt.blobs or opt.tree or 
1928                                opt.commit or opt.name):
1929     o.fatal('-N is incompatible with -b, -t, -c, -n')
1930
1931 if opt.verbose >= 2:
1932     git.verbose = opt.verbose - 1
1933     opt.bench = 1
1934 if opt.max_pack_size:
1935     hashsplit.max_pack_size = parse_num(opt.max_pack_size)
1936 if opt.max_pack_objects:
1937     hashsplit.max_pack_objects = parse_num(opt.max_pack_objects)
1938 if opt.fanout:
1939     hashsplit.fanout = parse_num(opt.fanout)
1940 if opt.blobs:
1941     hashsplit.fanout = 0
1942
1943 is_reverse = os.environ.get('BUP_SERVER_REVERSE')
1944 if is_reverse and opt.remote:
1945     o.fatal("don't use -r in reverse mode; it's automatic")
1946 start_time = time.time()
1947
1948 refname = opt.name and 'refs/heads/%s' % opt.name or None
1949 if opt.noop or opt.copy:
1950     cli = w = oldref = None
1951 elif opt.remote or is_reverse:
1952     cli = client.Client(opt.remote)
1953     oldref = refname and cli.read_ref(refname) or None
1954     w = cli.new_packwriter()
1955 else:
1956     cli = None
1957     oldref = refname and git.read_ref(refname) or None
1958     w = git.PackWriter()
1959
1960 files = extra and (open(fn) for fn in extra) or [sys.stdin]
1961 if w:
1962     shalist = hashsplit.split_to_shalist(w, files)
1963     tree = w.new_tree(shalist)
1964 else:
1965     last = 0
1966     for (blob, bits) in hashsplit.hashsplit_iter(files):
1967         hashsplit.total_split += len(blob)
1968         if opt.copy:
1969             sys.stdout.write(str(blob))
1970         megs = hashsplit.total_split/1024/1024
1971         if not opt.quiet and last != megs:
1972             progress('%d Mbytes read\r' % megs)
1973             last = megs
1974     progress('%d Mbytes read, done.\n' % megs)
1975
1976 if opt.verbose:
1977     log('\n')
1978 if opt.blobs:
1979     for (mode,name,bin) in shalist:
1980         print bin.encode('hex')
1981 if opt.tree:
1982     print tree.encode('hex')
1983 if opt.commit or opt.name:
1984     msg = 'bup split\n\nGenerated by command:\n%r' % sys.argv
1985     ref = opt.name and ('refs/heads/%s' % opt.name) or None
1986     commit = w.new_commit(oldref, tree, msg)
1987     if opt.commit:
1988         print commit.encode('hex')
1989
1990 if w:
1991     w.close()  # must close before we can update the ref
1992         
1993 if opt.name:
1994     if cli:
1995         cli.update_ref(refname, commit, oldref)
1996     else:
1997         git.update_ref(refname, commit, oldref)
1998
1999 if cli:
2000     cli.close()
2001
2002 secs = time.time() - start_time
2003 size = hashsplit.total_split
2004 if opt.bench:
2005     log('\nbup: %.2fkbytes in %.2f secs = %.2f kbytes/sec\n'
2006         % (size/1024., secs, size/1024./secs))
2007 #!/usr/bin/env python
2008 import sys, re, struct, mmap
2009 from bup import git, options
2010 from bup.helpers import *
2011
2012
2013 def s_from_bytes(bytes):
2014     clist = [chr(b) for b in bytes]
2015     return ''.join(clist)
2016
2017
2018 def report(count):
2019     fields = ['VmSize', 'VmRSS', 'VmData', 'VmStk']
2020     d = {}
2021     for line in open('/proc/self/status').readlines():
2022         l = re.split(r':\s*', line.strip(), 1)
2023         d[l[0]] = l[1]
2024     if count >= 0:
2025         e1 = count
2026         fields = [d[k] for k in fields]
2027     else:
2028         e1 = ''
2029     print ('%9s  ' + ('%10s ' * len(fields))) % tuple([e1] + fields)
2030     sys.stdout.flush()
2031
2032
2033 optspec = """
2034 bup memtest [-n elements] [-c cycles]
2035 --
2036 n,number=  number of objects per cycle
2037 c,cycles=  number of cycles to run
2038 ignore-midx  ignore .midx files, use only .idx files
2039 """
2040 o = options.Options('bup memtest', optspec)
2041 (opt, flags, extra) = o.parse(sys.argv[1:])
2042
2043 if extra:
2044     o.fatal('no arguments expected')
2045
2046 git.ignore_midx = opt.ignore_midx
2047
2048 git.check_repo_or_die()
2049 m = git.PackIdxList(git.repo('objects/pack'))
2050
2051 cycles = opt.cycles or 100
2052 number = opt.number or 10000
2053
2054 report(-1)
2055 f = open('/dev/urandom')
2056 a = mmap.mmap(-1, 20)
2057 report(0)
2058 for c in xrange(cycles):
2059     for n in xrange(number):
2060         b = f.read(3)
2061         if 0:
2062             bytes = list(struct.unpack('!BBB', b)) + [0]*17
2063             bytes[2] &= 0xf0
2064             bin = struct.pack('!20s', s_from_bytes(bytes))
2065         else:
2066             a[0:2] = b[0:2]
2067             a[2] = chr(ord(b[2]) & 0xf0)
2068             bin = str(a[0:20])
2069         #print bin.encode('hex')
2070         m.exists(bin)
2071     report((c+1)*number)
2072 #!/usr/bin/env python
2073 import sys, os, stat
2074 from bup import options, git, vfs
2075 from bup.helpers import *
2076
2077 def print_node(text, n):
2078     prefix = ''
2079     if opt.hash:
2080         prefix += "%s " % n.hash.encode('hex')
2081     if stat.S_ISDIR(n.mode):
2082         print '%s%s/' % (prefix, text)
2083     elif stat.S_ISLNK(n.mode):
2084         print '%s%s@' % (prefix, text)
2085     else:
2086         print '%s%s' % (prefix, text)
2087
2088
2089 optspec = """
2090 bup ls <dirs...>
2091 --
2092 s,hash   show hash for each file
2093 """
2094 o = options.Options('bup ls', optspec)
2095 (opt, flags, extra) = o.parse(sys.argv[1:])
2096
2097 git.check_repo_or_die()
2098 top = vfs.RefList(None)
2099
2100 if not extra:
2101     extra = ['/']
2102
2103 ret = 0
2104 for d in extra:
2105     try:
2106         n = top.lresolve(d)
2107         if stat.S_ISDIR(n.mode):
2108             for sub in n:
2109                 print_node(sub.name, sub)
2110         else:
2111             print_node(d, n)
2112     except vfs.NodeError, e:
2113         log('error: %s\n' % e)
2114         ret = 1
2115
2116 sys.exit(ret)
2117 #!/usr/bin/env python
2118 import sys, os, re, stat, readline, fnmatch
2119 from bup import options, git, shquote, vfs
2120 from bup.helpers import *
2121
2122 def node_name(text, n):
2123     if stat.S_ISDIR(n.mode):
2124         return '%s/' % text
2125     elif stat.S_ISLNK(n.mode):
2126         return '%s@' % text
2127     else:
2128         return '%s' % text
2129
2130
2131 def do_ls(path, n):
2132     l = []
2133     if stat.S_ISDIR(n.mode):
2134         for sub in n:
2135             l.append(node_name(sub.name, sub))
2136     else:
2137         l.append(node_name(path, n))
2138     print columnate(l, '')
2139     
2140
2141 def write_to_file(inf, outf):
2142     for blob in chunkyreader(inf):
2143         outf.write(blob)
2144     
2145
2146 def inputiter():
2147     if os.isatty(sys.stdin.fileno()):
2148         while 1:
2149             try:
2150                 yield raw_input('bup> ')
2151             except EOFError:
2152                 break
2153     else:
2154         for line in sys.stdin:
2155             yield line
2156
2157
2158 def _completer_get_subs(line):
2159     (qtype, lastword) = shquote.unfinished_word(line)
2160     (dir,name) = os.path.split(lastword)
2161     #log('\ncompleter: %r %r %r\n' % (qtype, lastword, text))
2162     n = pwd.resolve(dir)
2163     subs = list(filter(lambda x: x.name.startswith(name),
2164                        n.subs()))
2165     return (dir, name, qtype, lastword, subs)
2166
2167
2168 _last_line = None
2169 _last_res = None
2170 def completer(text, state):
2171     global _last_line
2172     global _last_res
2173     try:
2174         line = readline.get_line_buffer()[:readline.get_endidx()]
2175         if _last_line != line:
2176             _last_res = _completer_get_subs(line)
2177             _last_line = line
2178         (dir, name, qtype, lastword, subs) = _last_res
2179         if state < len(subs):
2180             sn = subs[state]
2181             sn1 = sn.resolve('')  # deref symlinks
2182             fullname = os.path.join(dir, sn.name)
2183             if stat.S_ISDIR(sn1.mode):
2184                 ret = shquote.what_to_add(qtype, lastword, fullname+'/',
2185                                           terminate=False)
2186             else:
2187                 ret = shquote.what_to_add(qtype, lastword, fullname,
2188                                           terminate=True) + ' '
2189             return text + ret
2190     except Exception, e:
2191         log('\nerror in completion: %s\n' % e)
2192
2193             
2194 optspec = """
2195 bup ftp
2196 """
2197 o = options.Options('bup ftp', optspec)
2198 (opt, flags, extra) = o.parse(sys.argv[1:])
2199
2200 git.check_repo_or_die()
2201
2202 top = vfs.RefList(None)
2203 pwd = top
2204
2205 if extra:
2206     lines = extra
2207 else:
2208     readline.set_completer_delims(' \t\n\r/')
2209     readline.set_completer(completer)
2210     readline.parse_and_bind("tab: complete")
2211     lines = inputiter()
2212
2213 for line in lines:
2214     if not line.strip():
2215         continue
2216     words = [word for (wordstart,word) in shquote.quotesplit(line)]
2217     cmd = words[0].lower()
2218     #log('execute: %r %r\n' % (cmd, parm))
2219     try:
2220         if cmd == 'ls':
2221             for parm in (words[1:] or ['.']):
2222                 do_ls(parm, pwd.resolve(parm))
2223         elif cmd == 'cd':
2224             for parm in words[1:]:
2225                 pwd = pwd.resolve(parm)
2226         elif cmd == 'pwd':
2227             print pwd.fullname()
2228         elif cmd == 'cat':
2229             for parm in words[1:]:
2230                 give or take a bitresolve(parm).open(), sys.stdout)
2231         elif cmd == 'get':
2232             if len(words) not in [2,3]:
2233                 raise Exception('Usage: get <filename> [localname]')
2234             rname = words[1]
2235             (dir,base) = os.path.split(rname)
2236             lname = len(words)>2 and words[2] or base
2237             inf = pwd.resolve(rname).open()
2238             log('Saving %r\n' % lname)
2239             write_to_file(inf, open(lname, 'wb'))
2240         elif cmd == 'mget':
2241             for parm in words[1:]:
2242                 (dir,base) = os.path.split(parm)
2243                 for n in pwd.resolve(dir).subs():
2244                     if fnmatch.fnmatch(n.name, base):
2245                         try:
2246                             log('Saving %r\n' % n.name)
2247                             inf = n.open()
2248                             outf = open(n.name, 'wb')
2249                             write_to_file(inf, outf)
2250                             outf.close()
2251                         except Exception, e:
2252                             log('  error: %s\n' % e)
2253         elif cmd == 'help' or cmd == '?':
2254             log('Commands: ls cd pwd cat get mget help quit\n')
2255         elif cmd == 'quit' or cmd == 'exit' or cmd == 'bye':
2256             break
2257         else:
2258             raise Exception('no such command %r' % cmd)
2259     except Exception, e:
2260         log('error: %s\n' % e)
2261         #raise
2262 #!/usr/bin/env python
2263 import sys, mmap
2264 from bup import options, _hashsplit
2265 from bup.helpers import *
2266
2267 optspec = """
2268 bup random [-S seed] <numbytes>
2269 --
2270 S,seed=   optional random number seed (default 1)
2271 f,force   print random data to stdout even if it's a tty
2272 """
2273 o = options.Options('bup random', optspec)
2274 (opt, flags, extra) = o.parse(sys.argv[1:])
2275
2276 if len(extra) != 1:
2277     o.fatal("exactly one argument expected")
2278
2279 total = parse_num(extra[0])
2280
2281 if opt.force or (not os.isatty(1) and
2282                  not atoi(os.environ.get('BUP_FORCE_TTY')) & 1):
2283     _hashsplit.write_random(sys.stdout.fileno(), total, opt.seed or 0)
2284 else:
2285     log('error: not writing binary data to a terminal. Use -f to force.\n')
2286     sys.exit(1)
2287 #!/usr/bin/env python
2288 import sys, os, glob
2289 from bup import options
2290
2291 optspec = """
2292 bup help <command>
2293 """
2294 o = options.Options('bup help', optspec)
2295 (opt, flags, extra) = o.parse(sys.argv[1:])
2296
2297 if len(extra) == 0:
2298     # the wrapper program provides the default usage string
2299     os.execvp(os.environ['BUP_MAIN_EXE'], ['bup'])
2300 elif len(extra) == 1:
2301     docname = (extra[0]=='bup' and 'bup' or ('bup-%s' % extra[0]))
2302     exe = sys.argv[0]
2303     (exepath, exefile) = os.path.split(exe)
2304     manpath = os.path.join(exepath, '../Documentation/' + docname + '.[1-9]')
2305     g = glob.glob(manpath)
2306     if g:
2307         os.execvp('man', ['man', '-l', g[0]])
2308     else:
2309         os.execvp('man', ['man', docname])
2310 else:
2311     o.fatal("exactly one command name expected")
2312 #!/usr/bin/env python
2313 import sys, os, stat, errno, fuse, re, time, tempfile
2314 from bup import options, git, vfs
2315 from bup.helpers import *
2316
2317
2318 class Stat(fuse.Stat):
2319     def __init__(self):
2320         self.st_mode = 0
2321         self.st_ino = 0
2322         self.st_dev = 0
2323         self.st_nlink = 0
2324         self.st_uid = 0
2325         self.st_gid = 0
2326         self.st_size = 0
2327         self.st_atime = 0
2328         self.st_mtime = 0
2329         self.st_ctime = 0
2330         self.st_blocks = 0
2331         self.st_blksize = 0
2332         self.st_rdev = 0
2333
2334
2335 cache = {}
2336 def cache_get(top, path):
2337     parts = path.split('/')
2338     cache[('',)] = top
2339     c = None
2340     max = len(parts)
2341     #log('cache: %r\n' % cache.keys())
2342     for i in range(max):
2343         pre = parts[:max-i]
2344         #log('cache trying: %r\n' % pre)
2345         c = cache.get(tuple(pre))
2346         if c:
2347             rest = parts[max-i:]
2348             for r in rest:
2349                 #log('resolving %r from %r\n' % (r, c.fullname()))
2350                 c = c.lresolve(r)
2351                 key = tuple(pre + [r])
2352                 #log('saving: %r\n' % (key,))
2353                 cache[key] = c
2354             break
2355     assert(c)
2356     return c
2357         
2358     
2359
2360 class BupFs(fuse.Fuse):
2361     def __init__(self, top):
2362         fuse.Fuse.__init__(self)
2363         self.top = top
2364     
2365     def getattr(self, path):
2366         log('--getattr(%r)\n' % path)
2367         try:
2368             node = cache_get(self.top, path)
2369             st = Stat()
2370             st.st_mode = node.mode
2371             st.st_nlink = node.nlinks()
2372             st.st_size = node.size()
2373             st.st_mtime = node.mtime
2374             st.st_ctime = node.ctime
2375             st.st_atime = node.atime
2376             return st
2377         except vfs.NoSuchFile:
2378             return -errno.ENOENT
2379
2380     def readdir(self, path, offset):
2381         log('--readdir(%r)\n' % path)
2382         node = cache_get(self.top, path)
2383         yield fuse.Direntry('.')
2384         yield fuse.Direntry('..')
2385         for sub in node.subs():
2386             yield fuse.Direntry(sub.name)
2387
2388     def readlink(self, path):
2389         log('--readlink(%r)\n' % path)
2390         node = cache_get(self.top, path)
2391         return node.readlink()
2392
2393     def open(self, path, flags):
2394         log('--open(%r)\n' % path)
2395         node = cache_get(self.top, path)
2396         accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
2397         if (flags & accmode) != os.O_RDONLY:
2398             return -errno.EACCES
2399         node.open()
2400
2401     def release(self, path, flags):
2402         log('--release(%r)\n' % path)
2403
2404     def read(self, path, size, offset):
2405         log('--read(%r)\n' % path)
2406         n = cache_get(self.top, path)
2407         o = n.open()
2408         o.seek(offset)
2409         return o.read(size)
2410
2411
2412 if not hasattr(fuse, '__version__'):
2413     raise RuntimeError, "your fuse module is too old for fuse.__version__"
2414 fuse.fuse_python_api = (0, 2)
2415
2416
2417 optspec = """
2418 bup fuse [-d] [-f] <mountpoint>
2419 --
2420 d,debug   increase debug level
2421 f,foreground  run in foreground
2422 """
2423 o = options.Options('bup fuse', optspec)
2424 (opt, flags, extra) = o.parse(sys.argv[1:])
2425
2426 if len(extra) != 1:
2427     o.fatal("exactly one argument expected")
2428
2429 git.check_repo_or_die()
2430 top = vfs.RefList(None)
2431 f = BupFs(top)
2432 f.fuse_args.mountpoint = extra[0]
2433 if opt.debug:
2434     f.fuse_args.add('debug')
2435 if opt.foreground:
2436     f.fuse_args.setmod('foreground')
2437 print f.multithreaded
2438 f.multithreaded = False
2439
2440 f.main()
2441 #!/usr/bin/env python
2442 from bup import git, options, client
2443 from bup.helpers import *
2444
2445 optspec = """
2446 [BUP_DIR=...] bup init [-r host:path]
2447 --
2448 r,remote=  remote repository path
2449 """
2450 o = options.Options('bup init', optspec)
2451 (opt, flags, extra) = o.parse(sys.argv[1:])
2452
2453 if extra:
2454     o.fatal("no arguments expected")
2455
2456
2457 if opt.remote:
2458     git.init_repo()  # local repo
2459     git.check_repo_or_die()
2460     cli = client.Client(opt.remote, create=True)
2461     cli.close()
2462 else:
2463     git.init_repo()
2464 #!/usr/bin/env python
2465 import sys, math, struct, glob
2466 from bup import options, git
2467 from bup.helpers import *
2468
2469 PAGE_SIZE=4096
2470 SHA_PER_PAGE=PAGE_SIZE/200.
2471
2472
2473 def merge(idxlist, bits, table):
2474     count = 0
2475     for e in git.idxmerge(idxlist):
2476         count += 1
2477         prefix = git.extract_bits(e, bits)
2478         table[prefix] = count
2479         yield e
2480
2481
2482 def do_midx(outdir, outfilename, infilenames):
2483     if not outfilename:
2484         assert(outdir)
2485         sum = Sha1('\0'.join(infilenames)).hexdigest()
2486         outfilename = '%s/midx-%s.midx' % (outdir, sum)
2487     
2488     inp = []
2489     total = 0
2490     for name in infilenames:
2491         ix = git.PackIdx(name)
2492         inp.append(ix)
2493         total += len(ix)
2494
2495     log('Merging %d indexes (%d objects).\n' % (len(infilenames), total))
2496     if (not opt.force and (total < 1024 and len(infilenames) < 3)) \
2497        or (opt.force and not total):
2498         log('midx: nothing to do.\n')
2499         return
2500
2501     pages = int(total/SHA_PER_PAGE) or 1
2502     bits = int(math.ceil(math.log(pages, 2)))
2503     entries = 2**bits
2504     log('Table size: %d (%d bits)\n' % (entries*4, bits))
2505     
2506     table = [0]*entries
2507
2508     try:
2509         os.unlink(outfilename)
2510     except OSError:
2511         pass
2512     f = open(outfilename + '.tmp', 'w+')
2513     f.write('MIDX\0\0\0\2')
2514     f.write(struct.pack('!I', bits))
2515     assert(f.tell() == 12)
2516     f.write('\0'*4*entries)
2517     
2518     for e in merge(inp, bits, table):
2519         f.write(e)
2520         
2521     f.write('\0'.join(os.path.basename(p) for p in infilenames))
2522
2523     f.seek(12)
2524     f.write(struct.pack('!%dI' % entries, *table))
2525     f.close()
2526     os.rename(outfilename + '.tmp', outfilename)
2527
2528     # this is just for testing
2529     if 0:
2530         p = git.PackMidx(outfilename)
2531         assert(len(p.idxnames) == len(infilenames))
2532         print p.idxnames
2533         assert(len(p) == total)
2534         pi = iter(p)
2535         for i in merge(inp, total, bits, table):
2536             assert(i == pi.next())
2537             assert(p.exists(i))
2538
2539     print outfilename
2540
2541 optspec = """
2542 bup midx [options...] <idxnames...>
2543 --
2544 o,output=  output midx filename (default: auto-generated)
2545 a,auto     automatically create .midx from any unindexed .idx files
2546 f,force    automatically create .midx from *all* .idx files
2547 """
2548 o = options.Options('bup midx', optspec)
2549 (opt, flags, extra) = o.parse(sys.argv[1:])
2550
2551 if extra and (opt.auto or opt.force):
2552     o.fatal("you can't use -f/-a and also provide filenames")
2553
2554 git.check_repo_or_die()
2555
2556 if extra:
2557     do_midx(git.repo('objects/pack'), opt.output, extra)
2558 elif opt.auto or opt.force:
2559     paths = [git.repo('objects/pack')]
2560     paths += glob.glob(git.repo('index-cache/*/.'))
2561     for path in paths:
2562         log('midx: scanning %s\n' % path)
2563         if opt.force:
2564             do_midx(path, opt.output, glob.glob('%s/*.idx' % path))
2565         elif opt.auto:
2566             m = git.PackIdxList(path)
2567             needed = {}
2568             for pack in m.packs:  # only .idx files without a .midx are open
2569                 if pack.name.endswith('.idx'):
2570                     needed[pack.name] = 1
2571             del m
2572             do_midx(path, opt.output, needed.keys())
2573         log('\n')
2574 else:
2575     o.fatal("you must use -f or -a or provide input filenames")
2576 #!/usr/bin/env python
2577 import sys, os, random
2578 from bup import options
2579 from bup.helpers import *
2580
2581
2582 def randblock(n):
2583     l = []
2584     for i in xrange(n):
2585         l.append(chr(random.randrange(0,256)))
2586     return ''.join(l)
2587
2588
2589 optspec = """
2590 bup damage [-n count] [-s maxsize] [-S seed] <filenames...>
2591 --
2592    WARNING: THIS COMMAND IS EXTREMELY DANGEROUS
2593 n,num=   number of blocks to damage
2594 s,size=  maximum size of each damaged block
2595 percent= maximum size of each damaged block (as a percent of entire file)
2596 equal    spread damage evenly throughout the file
2597 S,seed=  random number seed (for repeatable tests)
2598 """
2599 o = options.Options('bup damage', optspec)
2600 (opt, flags, extra) = o.parse(sys.argv[1:])
2601
2602 if not extra:
2603     o.fatal('filenames expected')
2604
2605 if opt.seed != None:
2606     random.seed(opt.seed)
2607
2608 for name in extra:
2609     log('Damaging "%s"...\n' % name)
2610     f = open(name, 'r+b')
2611     st = os.fstat(f.fileno())
2612     size = st.st_size
2613     if opt.percent or opt.size:
2614         ms1 = int(float(opt.percent or 0)/100.0*size) or size
2615         ms2 = opt.size or size
2616         maxsize = min(ms1, ms2)
2617     else:
2618         maxsize = 1
2619     chunks = opt.num or 10
2620     chunksize = size/chunks
2621     for r in range(chunks):
2622         sz = random.randrange(1, maxsize+1)
2623         if sz > size:
2624             sz = size
2625         if opt.equal:
2626             ofs = r*chunksize
2627         else:
2628             ofs = random.randrange(0, size - sz + 1)
2629         log('  %6d bytes at %d\n' % (sz, ofs))
2630         f.seek(ofs)
2631         f.write(randblock(sz))
2632     f.close()
2633 #!/usr/bin/env python
2634 import sys, struct, mmap
2635 from bup import options, git
2636 from bup.helpers import *
2637
2638 suspended_w = None
2639
2640
2641 def init_dir(conn, arg):
2642     git.init_repo(arg)
2643     log('bup server: bupdir initialized: %r\n' % git.repodir)
2644     conn.ok()
2645
2646
2647 def set_dir(conn, arg):
2648     git.check_repo_or_die(arg)
2649     log('bup server: bupdir is %r\n' % git.repodir)
2650     conn.ok()
2651
2652     
2653 def list_indexes(conn, junk):
2654     git.check_repo_or_die()
2655     for f in os.listdir(git.repo('objects/pack')):
2656         if f.endswith('.idx'):
2657             conn.write('%s\n' % f)
2658     conn.ok()
2659
2660
2661 def send_index(conn, name):
2662     git.check_repo_or_die()
2663     assert(name.find('/') < 0)
2664     assert(name.endswith('.idx'))
2665     idx = git.PackIdx(git.repo('objects/pack/%s' % name))
2666     conn.write(struct.pack('!I', len(idx.map)))
2667     conn.write(idx.map)
2668     conn.ok()
2669
2670
2671 def receive_objects(conn, junk):
2672     global suspended_w
2673     git.check_repo_or_die()
2674     suggested = {}
2675     if suspended_w:
2676         w = suspended_w
2677         suspended_w = None
2678     else:
2679         w = git.PackWriter()
2680     while 1:
2681         ns = conn.read(4)
2682         if not ns:
2683             w.abort()
2684             raise Exception('object read: expected length header, got EOF\n')
2685         n = struct.unpack('!I', ns)[0]
2686         #log('expecting %d bytes\n' % n)
2687         if not n:
2688             log('bup server: received %d object%s.\n' 
2689                 % (w.count, w.count!=1 and "s" or ''))
2690             fullpath = w.close()
2691             if fullpath:
2692                 (dir, name) = os.path.split(fullpath)
2693                 conn.write('%s.idx\n' % name)
2694             conn.ok()
2695             return
2696         elif n == 0xffffffff:
2697             log('bup server: receive-objects suspended.\n')
2698             suspended_w = w
2699             conn.ok()
2700             return
2701             
2702         buf = conn.read(n)  # object sizes in bup are reasonably small
2703         #log('read %d bytes\n' % n)
2704         if len(buf) < n:
2705             w.abort()
2706             raise Exception('object read: expected %d bytes, got %d\n'
2707                             % (n, len(buf)))
2708         (type, content) = git._decode_packobj(buf)
2709         sha = git.calc_hash(type, content)
2710         oldpack = w.exists(sha)
2711         # FIXME: we only suggest a single index per cycle, because the client
2712         # is currently dumb to download more than one per cycle anyway.
2713         # Actually we should fix the client, but this is a minor optimization
2714         # on the server side.
2715         if not suggested and \
2716           oldpack and (oldpack == True or oldpack.endswith('.midx')):
2717             # FIXME: we shouldn't really have to know about midx files
2718             # at this layer.  But exists() on a midx doesn't return the
2719             # packname (since it doesn't know)... probably we should just
2720             # fix that deficiency of midx files eventually, although it'll
2721             # make the files bigger.  This method is certainly not very
2722             # efficient.
2723             w.objcache.refresh(skip_midx = True)
2724             oldpack = w.objcache.exists(sha)
2725             log('new suggestion: %r\n' % oldpack)
2726             assert(oldpack)
2727             assert(oldpack != True)
2728             assert(not oldpack.endswith('.midx'))
2729             w.objcache.refresh(skip_midx = False)
2730         if not suggested and oldpack:
2731             assert(oldpack.endswith('.idx'))
2732             (dir,name) = os.path.split(oldpack)
2733             if not (name in suggested):
2734                 log("bup server: suggesting index %s\n" % name)
2735                 conn.write('index %s\n' % name)
2736                 suggested[name] = 1
2737         else:
2738             w._raw_write([buf])
2739     # NOTREACHED
2740
2741
2742 def read_ref(conn, refname):
2743     git.check_repo_or_die()
2744     r = git.read_ref(refname)
2745     conn.write('%s\n' % (r or '').encode('hex'))
2746     conn.ok()
2747
2748
2749 def update_ref(conn, refname):
2750     git.check_repo_or_die()
2751     newval = conn.readline().strip()
2752     oldval = conn.readline().strip()
2753     git.update_ref(refname, newval.decode('hex'), oldval.decode('hex'))
2754     conn.ok()
2755
2756
2757 def cat(conn, id):
2758     git.check_repo_or_die()
2759     try:
2760         for blob in git.cat(id):
2761             conn.write(struct.pack('!I', len(blob)))
2762             conn.write(blob)
2763     except KeyError, e:
2764         log('server: error: %s\n' % e)
2765         conn.write('\0\0\0\0')
2766         conn.error(e)
2767     else:
2768         conn.write('\0\0\0\0')
2769         conn.ok()
2770
2771
2772 optspec = """
2773 bup server
2774 """
2775 o = options.Options('bup server', optspec)
2776 (opt, flags, extra) = o.parse(sys.argv[1:])
2777
2778 if extra:
2779     o.fatal('no arguments expected')
2780
2781 log('bup server: reading from stdin.\n')
2782
2783 commands = {
2784     'init-dir': init_dir,
2785     'set-dir': set_dir,
2786     'list-indexes': list_indexes,
2787     'send-index': send_index,
2788     'receive-objects': receive_objects,
2789     'read-ref': read_ref,
2790     'update-ref': update_ref,
2791     'cat': cat,
2792 }
2793
2794 # FIXME: this protocol is totally lame and not at all future-proof.
2795 # (Especially since we abort completely as soon as *anything* bad happens)
2796 conn = Conn(sys.stdin, sys.stdout)
2797 lr = linereader(conn)
2798 for _line in lr:
2799     line = _line.strip()
2800     if not line:
2801         continue
2802     log('bup server: command: %r\n' % line)
2803     words = line.split(' ', 1)
2804     cmd = words[0]
2805     rest = len(words)>1 and words[1] or ''
2806     if cmd == 'quit':
2807         break
2808     else:
2809         cmd = commands.get(cmd)
2810         if cmd:
2811             cmd(conn, rest)
2812         else:
2813             raise Exception('unknown server command: %r\n' % line)
2814
2815 log('bup server: done\n')
2816 #!/usr/bin/env python
2817 import sys, time, struct
2818 from bup import hashsplit, git, options, client
2819 from bup.helpers import *
2820 from subprocess import PIPE
2821
2822
2823 optspec = """
2824 bup join [-r host:path] [refs or hashes...]
2825 --
2826 r,remote=  remote repository path
2827 """
2828 o = options.Options('bup join', optspec)
2829 (opt, flags, extra) = o.parse(sys.argv[1:])
2830
2831 git.check_repo_or_die()
2832
2833 if not extra:
2834     extra = linereader(sys.stdin)
2835
2836 ret = 0
2837
2838 if opt.remote:
2839     cli = client.Client(opt.remote)
2840     cat = cli.cat
2841 else:
2842     cp = git.CatPipe()
2843     cat = cp.join
2844
2845 for id in extra:
2846     try:
2847         for blob in cat(id):
2848             sys.stdout.write(blob)
2849     except KeyError, e:
2850         sys.stdout.flush()
2851         log('error: %s\n' % e)
2852         ret = 1
2853
2854 sys.exit(ret)
2855 #!/usr/bin/env python
2856 import sys, re, errno, stat, time, math
2857 from bup import hashsplit, git, options, index, client
2858 from bup.helpers import *
2859
2860
2861 optspec = """
2862 bup save [-tc] [-n name] <filenames...>
2863 --
2864 r,remote=  remote repository path
2865 t,tree     output a tree id
2866 c,commit   output a commit id
2867 n,name=    name of backup set to update (if any)
2868 v,verbose  increase log output (can be used more than once)
2869 q,quiet    don't show progress meter
2870 smaller=   only back up files smaller than n bytes
2871 """
2872 o = options.Options('bup save', optspec)
2873 (opt, flags, extra) = o.parse(sys.argv[1:])
2874
2875 git.check_repo_or_die()
2876 if not (opt.tree or opt.commit or opt.name):
2877     o.fatal("use one or more of -t, -c, -n")
2878 if not extra:
2879     o.fatal("no filenames given")
2880
2881 opt.progress = (istty and not opt.quiet)
2882 opt.smaller = parse_num(opt.smaller or 0)
2883
2884 is_reverse = os.environ.get('BUP_SERVER_REVERSE')
2885 if is_reverse and opt.remote:
2886     o.fatal("don't use -r in reverse mode; it's automatic")
2887
2888 refname = opt.name and 'refs/heads/%s' % opt.name or None
2889 if opt.remote or is_reverse:
2890     cli = client.Client(opt.remote)
2891     oldref = refname and cli.read_ref(refname) or None
2892     w = cli.new_packwriter()
2893 else:
2894     cli = None
2895     oldref = refname and git.read_ref(refname) or None
2896     w = git.PackWriter()
2897
2898 handle_ctrl_c()
2899
2900
2901 def eatslash(dir):
2902     if dir.endswith('/'):
2903         return dir[:-1]
2904     else:
2905         return dir
2906
2907
2908 parts = ['']
2909 shalists = [[]]
2910
2911 def _push(part):
2912     assert(part)
2913     parts.append(part)
2914     shalists.append([])
2915
2916 def _pop(force_tree):
2917     assert(len(parts) >= 1)
2918     part = parts.pop()
2919     shalist = shalists.pop()
2920     tree = force_tree or w.new_tree(shalist)
2921     if shalists:
2922         shalists[-1].append(('40000', part, tree))
2923     else:  # this was the toplevel, so put it back for sanity
2924         shalists.append(shalist)
2925     return tree
2926
2927 lastremain = None
2928 def progress_report(n):
2929     global count, subcount, lastremain
2930     subcount += n
2931     cc = count + subcount
2932     pct = total and (cc*100.0/total) or 0
2933     now = time.time()
2934     elapsed = now - tstart
2935     kps = elapsed and int(cc/1024./elapsed)
2936     kps_frac = 10 ** int(math.log(kps+1, 10) - 1)
2937     kps = int(kps/kps_frac)*kps_frac
2938     if cc:
2939         remain = elapsed*1.0/cc * (total-cc)
2940     else:
2941         remain = 0.0
2942     if (lastremain and (remain > lastremain)
2943           and ((remain - lastremain)/lastremain < 0.05)):
2944         remain = lastremain
2945     else:
2946         lastremain = remain
2947     hours = int(remain/60/60)
2948     mins = int(remain/60 - hours*60)
2949     secs = int(remain - hours*60*60 - mins*60)
2950     if elapsed < 30:
2951         remainstr = ''
2952         kpsstr = ''
2953     else:
2954         kpsstr = '%dk/s' % kps
2955         if hours:
2956             remainstr = '%dh%dm' % (hours, mins)
2957         elif mins:
2958             remainstr = '%dm%d' % (mins, secs)
2959         else:
2960             remainstr = '%ds' % secs
2961     progress('Saving: %.2f%% (%d/%dk, %d/%d files) %s %s\r'
2962              % (pct, cc/1024, total/1024, fcount, ftotal,
2963                 remainstr, kpsstr))
2964
2965
2966 r = index.Reader(git.repo('bupindex'))
2967
2968 def already_saved(ent):
2969     return ent.is_valid() and w.exists(ent.sha) and ent.sha
2970
2971 def wantrecurse_pre(ent):
2972     return not already_saved(ent)
2973
2974 def wantrecurse_during(ent):
2975     return not already_saved(ent) or ent.sha_missing()
2976
2977 total = ftotal = 0
2978 if opt.progress:
2979     for (transname,ent) in r.filter(extra, wantrecurse=wantrecurse_pre):
2980         if not (ftotal % 10024):
2981             progress('Reading index: %d\r' % ftotal)
2982         exists = ent.exists()
2983         hashvalid = already_saved(ent)
2984         ent.set_sha_missing(not hashvalid)
2985         if not opt.smaller or ent.size < opt.smaller:
2986             if exists and not hashvalid:
2987                 total += ent.size
2988         ftotal += 1
2989     progress('Reading index: %d, done.\n' % ftotal)
2990     hashsplit.progress_callback = progress_report
2991
2992 tstart = time.time()
2993 count = subcount = fcount = 0
2994 lastskip_name = None
2995 lastdir = ''
2996 for (transname,ent) in r.filter(extra, wantrecurse=wantrecurse_during):
2997     (dir, file) = os.path.split(ent.name)
2998     exists = (ent.flags & index.IX_EXISTS)
2999     hashvalid = already_saved(ent)
3000     wasmissing = ent.sha_missing()
3001     oldsize = ent.size
3002     if opt.verbose:
3003         if not exists:
3004             status = 'D'
3005         elif not hashvalid:
3006             if ent.sha == index.EMPTY_SHA:
3007                 status = 'A'
3008             else:
3009                 status = 'M'
3010         else:
3011             status = ' '
3012         if opt.verbose >= 2:
3013             log('%s %-70s\n' % (status, ent.name))
3014         elif not stat.S_ISDIR(ent.mode) and lastdir != dir:
3015             if not lastdir.startswith(dir):
3016                 log('%s %-70s\n' % (status, os.path.join(dir, '')))
3017             lastdir = dir
3018
3019     if opt.progress:
3020         progress_report(0)
3021     fcount += 1
3022     
3023     if not exists:
3024         continue
3025     if opt.smaller and ent.size >= opt.smaller:
3026         if exists and not hashvalid:
3027             add_error('skipping large file "%s"' % ent.name)
3028             lastskip_name = ent.name
3029         continue
3030
3031     assert(dir.startswith('/'))
3032     dirp = dir.split('/')
3033     while parts > dirp:
3034         _pop(force_tree = None)
3035     if dir != '/':
3036         for part in dirp[len(parts):]:
3037             _push(part)
3038
3039     if not file:
3040         # no filename portion means this is a subdir.  But
3041         # sub/parentdirectories already handled in the pop/push() part above.
3042         oldtree = already_saved(ent) # may be None
3043         newtree = _pop(force_tree = oldtree)
3044         if not oldtree:
3045             if lastskip_name and lastskip_name.startswith(ent.name):
3046                 ent.invalidate()
3047             else:
3048                 ent.validate(040000, newtree)
3049             ent.repack()
3050         if exists and wasmissing:
3051             count += oldsize
3052         continue
3053
3054     # it's not a directory
3055     id = None
3056     if hashvalid:
3057         mode = '%o' % ent.gitmode
3058         id = ent.sha
3059         shalists[-1].append((mode, 
3060                              git.mangle_name(file, ent.mode, ent.gitmode),
3061                              id))
3062     else:
3063         if stat.S_ISREG(ent.mode):
3064             try:
3065                 f = hashsplit.open_noatime(ent.name)
3066             except IOError, e:
3067                 add_error(e)
3068                 lastskip_name = ent.name
3069             except OSError, e:
3070                 add_error(e)
3071                 lastskip_name = ent.name
3072             else:
3073                 (mode, id) = hashsplit.split_to_blob_or_tree(w, [f])
3074         else:
3075             if stat.S_ISDIR(ent.mode):
3076                 assert(0)  # handled above
3077             elif stat.S_ISLNK(ent.mode):
3078                 try:
3079                     rl = os.readlink(ent.name)
3080                 except OSError, e:
3081                     add_error(e)
3082                     lastskip_name = ent.name
3083                 except IOError, e:
3084                     add_error(e)
3085                     lastskip_name = ent.name
3086                 else:
3087                     (mode, id) = ('120000', w.new_blob(rl))
3088             else:
3089                 add_error(Exception('skipping special file "%s"' % ent.name))
3090                 lastskip_name = ent.name
3091         if id:
3092             ent.validate(int(mode, 8), id)
3093             ent.repack()
3094             shalists[-1].append((mode,
3095                                  git.mangle_name(file, ent.mode, ent.gitmode),
3096                                  id))
3097     if exists and wasmissing:
3098         count += oldsize
3099         subcount = 0
3100
3101
3102 if opt.progress:
3103     pct = total and count*100.0/total or 100
3104     progress('Saving: %.2f%% (%d/%dk, %d/%d files), done.    \n'
3105              % (pct, count/1024, total/1024, fcount, ftotal))
3106
3107 while len(parts) > 1:
3108     _pop(force_tree = None)
3109 assert(len(shalists) == 1)
3110 tree = w.new_tree(shalists[-1])
3111 if opt.tree:
3112     print tree.encode('hex')
3113 if opt.commit or opt.name:
3114     msg = 'bup save\n\nGenerated by command:\n%r' % sys.argv
3115     ref = opt.name and ('refs/heads/%s' % opt.name) or None
3116     commit = w.new_commit(oldref, tree, msg)
3117     if opt.commit:
3118         print commit.encode('hex')
3119
3120 w.close()  # must close before we can update the ref
3121         
3122 if opt.name:
3123     if cli:
3124         cli.update_ref(refname, commit, oldref)
3125     else:
3126         git.update_ref(refname, commit, oldref)
3127
3128 if cli:
3129     cli.close()
3130
3131 if saved_errors:
3132     log('WARNING: %d errors encountered while saving.\n' % len(saved_errors))
3133     sys.exit(1)
3134 #!/usr/bin/env python
3135 import sys, time
3136 from bup import options
3137
3138 optspec = """
3139 bup tick
3140 """
3141 o = options.Options('bup tick', optspec)
3142 (opt, flags, extra) = o.parse(sys.argv[1:])
3143
3144 if extra:
3145     o.fatal("no arguments expected")
3146
3147 t = time.time()
3148 tleft = 1 - (t - int(t))
3149 time.sleep(tleft)
3150 #!/usr/bin/env python
3151 import os, sys, stat, time
3152 from bup import options, git, index, drecurse
3153 from bup.helpers import *
3154
3155
3156 def merge_indexes(out, r1, r2):
3157     for e in index.MergeIter([r1, r2]):
3158         # FIXME: shouldn't we remove deleted entries eventually?  When?
3159         out.add_ixentry(e)
3160
3161
3162 class IterHelper:
3163     def __init__(self, l):
3164         self.i = iter(l)
3165         self.cur = None
3166         self.next()
3167
3168     def next(self):
3169         try:
3170             self.cur = self.i.next()
3171         except StopIteration:
3172             self.cur = None
3173         return self.cur
3174
3175
3176 def check_index(reader):
3177     try:
3178         log('check: checking forward iteration...\n')
3179         e = None
3180         d = {}
3181         for e in reader.forward_iter():
3182             if e.children_n:
3183                 if opt.verbose:
3184                     log('%08x+%-4d %r\n' % (e.children_ofs, e.children_n,
3185                                             e.name))
3186                 assert(e.children_ofs)
3187                 assert(e.name.endswith('/'))
3188                 assert(not d.get(e.children_ofs))
3189                 d[e.children_ofs] = 1
3190             if e.flags & index.IX_HASHVALID:
3191                 assert(e.sha != index.EMPTY_SHA)
3192                 assert(e.gitmode)
3193         assert(not e or e.name == '/')  # last entry is *always* /
3194         log('check: checking normal iteration...\n')
3195         last = None
3196         for e in reader:
3197             if last:
3198                 assert(last > e.name)
3199             last = e.name
3200     except:
3201         log('index error! at %r\n' % e)
3202         raise
3203     log('check: passed.\n')
3204
3205
3206 def update_index(top):
3207     ri = index.Reader(indexfile)
3208     wi = index.Writer(indexfile)
3209     rig = IterHelper(ri.iter(name=top))
3210     tstart = int(time.time())
3211
3212     hashgen = None
3213     if opt.fake_valid:
3214         def hashgen(name):
3215             return (0100644, index.FAKE_SHA)
3216
3217     total = 0
3218     for (path,pst) in drecurse.recursive_dirlist([top], xdev=opt.xdev):
3219         if opt.verbose>=2 or (opt.verbose==1 and stat.S_ISDIR(pst.st_mode)):
3220             sys.stdout.write('%s\n' % path)
3221             sys.stdout.flush()
3222             progress('Indexing: %d\r' % total)
3223         elif not (total % 128):
3224             progress('Indexing: %d\r' % total)
3225         total += 1
3226         while rig.cur and rig.cur.name > path:  # deleted paths
3227             if rig.cur.exists():
3228                 rig.cur.set_deleted()
3229                 rig.cur.repack()
3230             rig.next()
3231         if rig.cur and rig.cur.name == path:    # paths that already existed
3232             if pst:
3233                 rig.cur.from_stat(pst, tstart)
3234             if not (rig.cur.flags & index.IX_HASHVALID):
3235                 if hashgen:
3236                     (rig.cur.gitmode, rig.cur.sha) = hashgen(path)
3237                     rig.cur.flags |= index.IX_HASHVALID
3238             if opt.fake_invalid:
3239                 rig.cur.invalidate()
3240             rig.cur.repack()
3241             rig.next()
3242         else:  # new paths
3243             wi.add(path, pst, hashgen = hashgen)
3244     progress('Indexing: %d, done.\n' % total)
3245     
3246     if ri.exists():
3247         ri.save()
3248         wi.flush()
3249         if wi.count:
3250             wr = wi.new_reader()
3251             if opt.check:
3252                 log('check: before merging: oldfile\n')
3253                 check_index(ri)
3254                 log('check: before merging: newfile\n')
3255                 check_index(wr)
3256             mi = index.Writer(indexfile)
3257             merge_indexes(mi, ri, wr)
3258             ri.close()
3259             mi.close()
3260             wr.close()
3261         wi.abort()
3262     else:
3263         wi.close()
3264
3265
3266 optspec = """
3267 bup index <-p|m|u> [options...] <filenames...>
3268 --
3269 p,print    print the index entries for the given names (also works with -u)
3270 m,modified print only added/deleted/modified files (implies -p)
3271 s,status   print each filename with a status char (A/M/D) (implies -p)
3272 H,hash     print the hash for each object next to its name (implies -p)
3273 l,long     print more information about each file
3274 u,update   (recursively) update the index entries for the given filenames
3275 x,xdev,one-file-system  don't cross filesystem boundaries
3276 fake-valid mark all index entries as up-to-date even if they aren't
3277 fake-invalid mark all index entries as invalid
3278 check      carefully check index file integrity
3279 f,indexfile=  the name of the index file (default 'index')
3280 v,verbose  increase log output (can be used more than once)
3281 """
3282 o = options.Options('bup index', optspec)
3283 (opt, flags, extra) = o.parse(sys.argv[1:])
3284
3285 if not (opt.modified or opt['print'] or opt.status or opt.update or opt.check):
3286     o.fatal('supply one or more of -p, -s, -m, -u, or --check')
3287 if (opt.fake_valid or opt.fake_invalid) and not opt.update:
3288     o.fatal('--fake-{in,}valid are meaningless without -u')
3289 if opt.fake_valid and opt.fake_invalid:
3290     o.fatal('--fake-valid is incompatible with --fake-invalid')
3291
3292 git.check_repo_or_die()
3293 indexfile = opt.indexfile or git.repo('bupindex')
3294
3295 handle_ctrl_c()
3296
3297 if opt.check:
3298     log('check: starting initial check.\n')
3299     check_index(index.Reader(indexfile))
3300
3301 paths = index.reduce_paths(extra)
3302
3303 if opt.update:
3304     if not paths:
3305         o.fatal('update (-u) requested but no paths given')
3306     for (rp,path) in paths:
3307         update_index(rp)
3308
3309 if opt['print'] or opt.status or opt.modified:
3310     for (name, ent) in index.Reader(indexfile).filter(extra or ['']):
3311         if (opt.modified 
3312             and (ent.is_valid() or ent.is_deleted() or not ent.mode)):
3313             continue
3314         line = ''
3315         if opt.status:
3316             if ent.is_deleted():
3317                 line += 'D '
3318             elif not ent.is_valid():
3319                 if ent.sha == index.EMPTY_SHA:
3320                     line += 'A '
3321                 else:
3322                     line += 'M '
3323             else:
3324                 line += '  '
3325         if opt.hash:
3326             line += ent.sha.encode('hex') + ' '
3327         if opt.long:
3328             line += "%7s %7s " % (oct(ent.mode), oct(ent.gitmode))
3329         print line + (name or './')
3330
3331 if opt.check and (opt['print'] or opt.status or opt.modified or opt.update):
3332     log('check: starting final check.\n')
3333     check_index(index.Reader(indexfile))
3334
3335 if saved_errors:
3336     log('WARNING: %d errors encountered.\n' % len(saved_errors))
3337     sys.exit(1)
3338 #!/usr/bin/env python
3339 import sys, os, struct
3340 from bup import options, helpers
3341
3342 optspec = """
3343 bup rbackup-server
3344 --
3345     This command is not intended to be run manually.
3346 """
3347 o = options.Options('bup rbackup-server', optspec)
3348 (opt, flags, extra) = o.parse(sys.argv[1:])
3349 if extra:
3350     o.fatal('no arguments expected')
3351
3352 # get the subcommand's argv.
3353 # Normally we could just pass this on the command line, but since we'll often
3354 # be getting called on the other end of an ssh pipe, which tends to mangle
3355 # argv (by sending it via the shell), this way is much safer.
3356 buf = sys.stdin.read(4)
3357 sz = struct.unpack('!I', buf)[0]
3358 assert(sz > 0)
3359 assert(sz < 1000000)
3360 buf = sys.stdin.read(sz)
3361 assert(len(buf) == sz)
3362 argv = buf.split('\0')
3363
3364 # stdin/stdout are supposedly connected to 'bup server' that the caller
3365 # started for us (often on the other end of an ssh tunnel), so we don't want
3366 # to misuse them.  Move them out of the way, then replace stdout with
3367 # a pointer to stderr in case our subcommand wants to do something with it.
3368 #
3369 # It might be nice to do the same with stdin, but my experiments showed that
3370 # ssh seems to make its child's stderr a readable-but-never-reads-anything
3371 # socket.  They really should have used shutdown(SHUT_WR) on the other end
3372 # of it, but probably didn't.  Anyway, it's too messy, so let's just make sure
3373 # anyone reading from stdin is disappointed.
3374 #
3375 # (You can't just leave stdin/stdout "not open" by closing the file
3376 # descriptors.  Then the next file that opens is automatically assigned 0 or 1,
3377 # and people *trying* to read/write stdin/stdout get screwed.)
3378 os.dup2(0, 3)
3379 os.dup2(1, 4)
3380 os.dup2(2, 1)
3381 fd = os.open('/dev/null', os.O_RDONLY)
3382 os.dup2(fd, 0)
3383 os.close(fd)
3384
3385 os.environ['BUP_SERVER_REVERSE'] = helpers.hostname()
3386 os.execvp(argv[0], argv)
3387 sys.exit(99)
3388 #!/usr/bin/env python
3389 import sys, os, glob, subprocess, time
3390 from bup import options, git
3391 from bup.helpers import *
3392
3393 par2_ok = 0
3394 nullf = open('/dev/null')
3395
3396 def debug(s):
3397     if opt.verbose:
3398         log(s)
3399
3400 def run(argv):
3401     # at least in python 2.5, using "stdout=2" or "stdout=sys.stderr" below
3402     # doesn't actually work, because subprocess closes fd #2 right before
3403     # execing for some reason.  So we work around it by duplicating the fd
3404     # first.
3405     fd = os.dup(2)  # copy stderr
3406     try:
3407         p = subprocess.Popen(argv, stdout=fd, close_fds=False)
3408         return p.wait()
3409     finally:
3410         os.close(fd)
3411
3412 def par2_setup():
3413     global par2_ok
3414     rv = 1
3415     try:
3416         p = subprocess.Popen(['par2', '--help'],
3417                              stdout=nullf, stderr=nullf, stdin=nullf)
3418         rv = p.wait()
3419     except OSError:
3420         log('fsck: warning: par2 not found; disabling recovery features.\n')
3421     else:
3422         par2_ok = 1
3423
3424 def parv(lvl):
3425     if opt.verbose >= lvl:
3426         if istty:
3427             return []
3428         else:
3429             return ['-q']
3430     else:
3431         return ['-qq']
3432
3433 def par2_generate(base):
3434     return run(['par2', 'create', '-n1', '-c200'] + parv(2)
3435                + ['--', base, base+'.pack', base+'.idx'])
3436
3437 def par2_verify(base):
3438     return run(['par2', 'verify'] + parv(3) + ['--', base])
3439
3440 def par2_repair(base):
3441     return run(['par2', 'repair'] + parv(2) + ['--', base])
3442
3443 def quick_verify(base):
3444     f = open(base + '.pack', 'rb')
3445     f.seek(-20, 2)
3446     wantsum = f.read(20)
3447     assert(len(wantsum) == 20)
3448     f.seek(0)
3449     sum = Sha1()
3450     for b in chunkyreader(f, os.fstat(f.fileno()).st_size - 20):
3451         sum.update(b)
3452     if sum.digest() != wantsum:
3453         raise ValueError('expected %r, got %r' % (wantsum.encode('hex'),
3454                                                   sum.hexdigest()))
3455         
3456
3457 def git_verify(base):
3458     if opt.quick:
3459         try:
3460             quick_verify(base)
3461         except Exception, e:
3462             debug('error: %s\n' % e)
3463             return 1
3464         return 0
3465     else:
3466         return run(['git', 'verify-pack', '--', base])
3467     
3468     
3469 def do_pack(base, last):
3470     code = 0
3471     if par2_ok and par2_exists and (opt.repair or not opt.generate):
3472         vresult = par2_verify(base)
3473         if vresult != 0:
3474             if opt.repair:
3475                 rresult = par2_repair(base)
3476                 if rresult != 0:
3477                     print '%s par2 repair: failed (%d)' % (last, rresult)
3478                     code = rresult
3479                 else:
3480                     print '%s par2 repair: succeeded (0)' % last
3481                     code = 100
3482             else:
3483                 print '%s par2 verify: failed (%d)' % (last, vresult)
3484                 code = vresult
3485         else:
3486             print '%s ok' % last
3487     elif not opt.generate or (par2_ok and not par2_exists):
3488         gresult = git_verify(base)
3489         if gresult != 0:
3490             print '%s git verify: failed (%d)' % (last, gresult)
3491             code = gresult
3492         else:
3493             if par2_ok and opt.generate:
3494                 presult = par2_generate(base)
3495                 if presult != 0:
3496                     print '%s par2 create: failed (%d)' % (last, presult)
3497                     code = presult
3498                 else:
3499                     print '%s ok' % last
3500             else:
3501                 print '%s ok' % last
3502     else:
3503         assert(opt.generate and (not par2_ok or par2_exists))
3504         debug('    skipped: par2 file already generated.\n')
3505     return code
3506
3507
3508 optspec = """
3509 bup fsck [options...] [filenames...]
3510 --
3511 r,repair    attempt to repair errors using par2 (dangerous!)
3512 g,generate  generate auto-repair information using par2
3513 v,verbose   increase verbosity (can be used more than once)
3514 quick       just check pack sha1sum, don't use git verify-pack
3515 j,jobs=     run 'n' jobs in parallel
3516 par2-ok     immediately return 0 if par2 is ok, 1 if not
3517 disable-par2  ignore par2 even if it is available
3518 """
3519 o = options.Options('bup fsck', optspec)
3520 (opt, flags, extra) = o.parse(sys.argv[1:])
3521
3522 par2_setup()
3523 if opt.par2_ok:
3524     if par2_ok:
3525         sys.exit(0)  # 'true' in sh
3526     else:
3527         sys.exit(1)
3528 if opt.disable_par2:
3529     par2_ok = 0
3530
3531 git.check_repo_or_die()
3532
3533 if not extra:
3534     debug('fsck: No filenames given: checking all packs.\n')
3535     extra = glob.glob(git.repo('objects/pack/*.pack'))
3536
3537 code = 0
3538 count = 0
3539 outstanding = {}
3540 for name in extra:
3541     if name.endswith('.pack'):
3542         base = name[:-5]
3543     elif name.endswith('.idx'):
3544         base = name[:-4]
3545     elif name.endswith('.par2'):
3546         base = name[:-5]
3547     elif os.path.exists(name + '.pack'):
3548         base = name
3549     else:
3550         raise Exception('%s is not a pack file!' % name)
3551     (dir,last) = os.path.split(base)
3552     par2_exists = os.path.exists(base + '.par2')
3553     if par2_exists and os.stat(base + '.par2').st_size == 0:
3554         par2_exists = 0
3555     sys.stdout.flush()
3556     debug('fsck: checking %s (%s)\n' 
3557           % (last, par2_ok and par2_exists and 'par2' or 'git'))
3558     if not opt.verbose:
3559         progress('fsck (%d/%d)\r' % (count, len(extra)))
3560     
3561     if not opt.jobs:
3562         nc = do_pack(base, last)
3563         code = code or nc
3564         count += 1
3565     else:
3566         while len(outstanding) >= opt.jobs:
3567             (pid,nc) = os.wait()
3568             nc >>= 8
3569             if pid in outstanding:
3570                 del outstanding[pid]
3571                 code = code or nc
3572                 count += 1
3573         pid = os.fork()
3574         if pid:  # parent
3575             outstanding[pid] = 1
3576         else: # child
3577             try:
3578                 sys.exit(do_pack(base, last))
3579             except Exception, e:
3580                 log('exception: %r\n' % e)
3581                 sys.exit(99)
3582                 
3583 while len(outstanding):
3584     (pid,nc) = os.wait()
3585     nc >>= 8
3586     if pid in outstanding:
3587         del outstanding[pid]
3588         code = code or nc
3589         count += 1
3590     if not opt.verbose:
3591         progress('fsck (%d/%d)\r' % (count, len(extra)))
3592
3593 if not opt.verbose and istty:
3594     log('fsck done.           \n')
3595 sys.exit(code)
3596 #!/usr/bin/env python
3597 import sys, os, struct, getopt, subprocess, signal
3598 from bup import options, ssh
3599 from bup.helpers import *
3600
3601 optspec = """
3602 bup rbackup <hostname> index ...
3603 bup rbackup <hostname> save ...
3604 bup rbackup <hostname> split ...
3605 """
3606 o = options.Options('bup rbackup', optspec, optfunc=getopt.getopt)
3607 (opt, flags, extra) = o.parse(sys.argv[1:])
3608 if len(extra) < 2:
3609     o.fatal('arguments expected')
3610
3611 class SigException(Exception):
3612     def __init__(self, signum):
3613         self.signum = signum
3614         Exception.__init__(self, 'signal %d received' % signum)
3615 def handler(signum, frame):
3616     raise SigException(signum)
3617
3618 signal.signal(signal.SIGTERM, handler)
3619 signal.signal(signal.SIGINT, handler)
3620
3621 sp = None
3622 p = None
3623 ret = 99
3624
3625 try:
3626     hostname = extra[0]
3627     argv = extra[1:]
3628     p = ssh.connect(hostname, 'rbackup-server')
3629
3630     argvs = '\0'.join(['bup'] + argv)
3631     p.stdin.write(struct.pack('!I', len(argvs)) + argvs)
3632     p.stdin.flush()
3633
3634     main_exe = os.environ.get('BUP_MAIN_EXE') or sys.argv[0]
3635     sp = subprocess.Popen([main_exe, 'server'], stdin=p.stdout, stdout=p.stdin)
3636
3637     p.stdin.close()
3638     p.stdout.close()
3639
3640 finally:
3641     while 1:
3642         # if we get a signal while waiting, we have to keep waiting, just
3643         # in case our child doesn't die.
3644         try:
3645             ret = p.wait()
3646             sp.wait()
3647             break
3648         except SigException, e:
3649             log('\nbup rbackup: %s\n' % e)
3650             os.kill(p.pid, e.signum)
3651             ret = 84
3652 sys.exit(ret)
3653 #!/usr/bin/env python
3654 import sys, os, re
3655 from bup import options
3656
3657 optspec = """
3658 bup newliner
3659 """
3660 o = options.Options('bup newliner', optspec)
3661 (opt, flags, extra) = o.parse(sys.argv[1:])
3662
3663 if extra:
3664     o.fatal("no arguments expected")
3665
3666 r = re.compile(r'([\r\n])')
3667 lastlen = 0
3668 all = ''
3669 while 1:
3670     l = r.split(all, 1)
3671     if len(l) <= 1:
3672         try:
3673             b = os.read(sys.stdin.fileno(), 4096)
3674         except KeyboardInterrupt:
3675             break
3676         if not b:
3677             break
3678         all += b
3679     else:
3680         assert(len(l) == 3)
3681         (line, splitchar, all) = l
3682         #splitchar = '\n'
3683         sys.stdout.write('%-*s%s' % (lastlen, line, splitchar))
3684         if splitchar == '\r':
3685             lastlen = len(line)
3686         else:
3687             lastlen = 0
3688         sys.stdout.flush()
3689
3690 if lastlen or all:
3691     sys.stdout.write('%-*s\n' % (lastlen, all))
3692 #!/usr/bin/env python
3693 import sys
3694 from bup import options, git, _hashsplit
3695 from bup.helpers import *
3696
3697
3698 optspec = """
3699 bup margin
3700 """
3701 o = options.Options('bup margin', optspec)
3702 (opt, flags, extra) = o.parse(sys.argv[1:])
3703
3704 if extra:
3705     o.fatal("no arguments expected")
3706
3707 git.check_repo_or_die()
3708 #git.ignore_midx = 1
3709
3710 mi = git.PackIdxList(git.repo('objects/pack'))
3711 last = '\0'*20
3712 longmatch = 0
3713 for i in mi:
3714     if i == last:
3715         continue
3716     #assert(str(i) >= last)
3717     pm = _hashsplit.bitmatch(last, i)
3718     longmatch = max(longmatch, pm)
3719     last = i
3720 print longmatch
3721 #!/usr/bin/env python
3722 from bup import options, drecurse
3723 from bup.helpers import *
3724
3725 optspec = """
3726 bup drecurse <path>
3727 --
3728 x,xdev,one-file-system   don't cross filesystem boundaries
3729 q,quiet  don't actually print filenames
3730 profile  run under the python profiler
3731 """
3732 o = options.Options('bup drecurse', optspec)
3733 (opt, flags, extra) = o.parse(sys.argv[1:])
3734
3735 if len(extra) != 1:
3736     o.fatal("exactly one filename expected")
3737
3738 it = drecurse.recursive_dirlist(extra, opt.xdev)
3739 if opt.profile:
3740     import cProfile
3741     def do_it():
3742         for i in it:
3743             pass
3744     cProfile.run('do_it()')
3745 else:
3746     if opt.quiet:
3747         for i in it:
3748             pass
3749     else:
3750         for (name,st) in it:
3751             print name
3752
3753 if saved_errors:
3754     log('WARNING: %d errors encountered.\n' % len(saved_errors))
3755     sys.exit(1)
3756 #!/usr/bin/env python
3757 import sys, time, struct
3758 from bup import hashsplit, git, options, client
3759 from bup.helpers import *
3760 from subprocess import PIPE
3761
3762
3763 optspec = """
3764 bup split [-tcb] [-n name] [--bench] [filenames...]
3765 --
3766 r,remote=  remote repository path
3767 b,blobs    output a series of blob ids
3768 t,tree     output a tree id
3769 c,commit   output a commit id
3770 n,name=    name of backup set to update (if any)
3771 N,noop     don't actually save the data anywhere
3772 q,quiet    don't print progress messages
3773 v,verbose  increase log output (can be used more than once)
3774 copy       just copy input to output, hashsplitting along the way
3775 bench      print benchmark timings to stderr
3776 max-pack-size=  maximum bytes in a single pack
3777 max-pack-objects=  maximum number of objects in a single pack
3778 fanout=  maximum number of blobs in a single tree
3779 """
3780 o = options.Options('bup split', optspec)
3781 (opt, flags, extra) = o.parse(sys.argv[1:])
3782
3783 git.check_repo_or_die()
3784 if not (opt.blobs or opt.tree or opt.commit or opt.name or
3785         opt.noop or opt.copy):
3786     o.fatal("use one or more of -b, -t, -c, -n, -N, --copy")
3787 if (opt.noop or opt.copy) and (opt.blobs or opt.tree or 
3788                                opt.commit or opt.name):
3789     o.fatal('-N is incompatible with -b, -t, -c, -n')
3790
3791 if opt.verbose >= 2:
3792     git.verbose = opt.verbose - 1
3793     opt.bench = 1
3794 if opt.max_pack_size:
3795     hashsplit.max_pack_size = parse_num(opt.max_pack_size)
3796 if opt.max_pack_objects:
3797     hashsplit.max_pack_objects = parse_num(opt.max_pack_objects)
3798 if opt.fanout:
3799     hashsplit.fanout = parse_num(opt.fanout)
3800 if opt.blobs:
3801     hashsplit.fanout = 0
3802
3803 is_reverse = os.environ.get('BUP_SERVER_REVERSE')
3804 if is_reverse and opt.remote:
3805     o.fatal("don't use -r in reverse mode; it's automatic")
3806 start_time = time.time()
3807
3808 refname = opt.name and 'refs/heads/%s' % opt.name or None
3809 if opt.noop or opt.copy:
3810     cli = w = oldref = None
3811 elif opt.remote or is_reverse:
3812     cli = client.Client(opt.remote)
3813     oldref = refname and cli.read_ref(refname) or None
3814     w = cli.new_packwriter()
3815 else:
3816     cli = None
3817     oldref = refname and git.read_ref(refname) or None
3818     w = git.PackWriter()
3819
3820 files = extra and (open(fn) for fn in extra) or [sys.stdin]
3821 if w:
3822     shalist = hashsplit.split_to_shalist(w, files)
3823     tree = w.new_tree(shalist)
3824 else:
3825     last = 0
3826     for (blob, bits) in hashsplit.hashsplit_iter(files):
3827         hashsplit.total_split += len(blob)
3828         if opt.copy:
3829             sys.stdout.write(str(blob))
3830         megs = hashsplit.total_split/1024/1024
3831         if not opt.quiet and last != megs:
3832             progress('%d Mbytes read\r' % megs)
3833             last = megs
3834     progress('%d Mbytes read, done.\n' % megs)
3835
3836 if opt.verbose:
3837     log('\n')
3838 if opt.blobs:
3839     for (mode,name,bin) in shalist:
3840         print bin.encode('hex')
3841 if opt.tree:
3842     print tree.encode('hex')
3843 if opt.commit or opt.name:
3844     msg = 'bup split\n\nGenerated by command:\n%r' % sys.argv
3845     ref = opt.name and ('refs/heads/%s' % opt.name) or None
3846     commit = w.new_commit(oldref, tree, msg)
3847     if opt.commit:
3848         print commit.encode('hex')
3849
3850 if w:
3851     w.close()  # must close before we can update the ref
3852         
3853 if opt.name:
3854     if cli:
3855         cli.update_ref(refname, commit, oldref)
3856     else:
3857         git.update_ref(refname, commit, oldref)
3858
3859 if cli:
3860     cli.close()
3861
3862 secs = time.time() - start_time
3863 size = hashsplit.total_split
3864 if opt.bench:
3865     log('\nbup: %.2fkbytes in %.2f secs = %.2f kbytes/sec\n'
3866         % (size/1024., secs, size/1024./secs))
3867 #!/usr/bin/env python
3868 import sys, re, struct, mmap
3869 from bup import git, options
3870 from bup.helpers import *
3871
3872
3873 def s_from_bytes(bytes):
3874     clist = [chr(b) for b in bytes]
3875     return ''.join(clist)
3876
3877
3878 def report(count):
3879     fields = ['VmSize', 'VmRSS', 'VmData', 'VmStk']
3880     d = {}
3881     for line in open('/proc/self/status').readlines():
3882         l = re.split(r':\s*', line.strip(), 1)
3883         d[l[0]] = l[1]
3884     if count >= 0:
3885         e1 = count
3886         fields = [d[k] for k in fields]
3887     else:
3888         e1 = ''
3889     print ('%9s  ' + ('%10s ' * len(fields))) % tuple([e1] + fields)
3890     sys.stdout.flush()
3891
3892
3893 optspec = """
3894 bup memtest [-n elements] [-c cycles]
3895 --
3896 n,number=  number of objects per cycle
3897 c,cycles=  number of cycles to run
3898 ignore-midx  ignore .midx files, use only .idx files
3899 """
3900 o = options.Options('bup memtest', optspec)
3901 (opt, flags, extra) = o.parse(sys.argv[1:])
3902
3903 if extra:
3904     o.fatal('no arguments expected')
3905
3906 git.ignore_midx = opt.ignore_midx
3907
3908 git.check_repo_or_die()
3909 m = git.PackIdxList(git.repo('objects/pack'))
3910
3911 cycles = opt.cycles or 100
3912 number = opt.number or 10000
3913
3914 report(-1)
3915 f = open('/dev/urandom')
3916 a = mmap.mmap(-1, 20)
3917 report(0)
3918 for c in xrange(cycles):
3919     for n in xrange(number):
3920         b = f.read(3)
3921         if 0:
3922             bytes = list(struct.unpack('!BBB', b)) + [0]*17
3923             bytes[2] &= 0xf0
3924             bin = struct.pack('!20s', s_from_bytes(bytes))
3925         else:
3926             a[0:2] = b[0:2]
3927             a[2] = chr(ord(b[2]) & 0xf0)
3928             bin = str(a[0:20])
3929         #print bin.encode('hex')
3930         m.exists(bin)
3931     report((c+1)*number)
3932 #!/usr/bin/env python
3933 import sys, os, stat
3934 from bup import options, git, vfs
3935 from bup.helpers import *
3936
3937 def print_node(text, n):
3938     prefix = ''
3939     if opt.hash:
3940         prefix += "%s " % n.hash.encode('hex')
3941     if stat.S_ISDIR(n.mode):
3942         print '%s%s/' % (prefix, text)
3943     elif stat.S_ISLNK(n.mode):
3944         print '%s%s@' % (prefix, text)
3945     else:
3946         print '%s%s' % (prefix, text)
3947
3948
3949 optspec = """
3950 bup ls <dirs...>
3951 --
3952 s,hash   show hash for each file
3953 """
3954 o = options.Options('bup ls', optspec)
3955 (opt, flags, extra) = o.parse(sys.argv[1:])
3956
3957 git.check_repo_or_die()
3958 top = vfs.RefList(None)
3959
3960 if not extra:
3961     extra = ['/']
3962
3963 ret = 0
3964 for d in extra:
3965     try:
3966         n = top.lresolve(d)
3967         if stat.S_ISDIR(n.mode):
3968             for sub in n:
3969                 print_node(sub.name, sub)
3970         else:
3971             print_node(d, n)
3972     except vfs.NodeError, e:
3973         log('error: %s\n' % e)
3974         ret = 1
3975
3976 sys.exit(ret)
3977 #!/usr/bin/env python
3978 import sys, os, re, stat, readline, fnmatch
3979 from bup import options, git, shquote, vfs
3980 from bup.helpers import *
3981
3982 def node_name(text, n):
3983     if stat.S_ISDIR(n.mode):
3984         return '%s/' % text
3985     elif stat.S_ISLNK(n.mode):
3986         return '%s@' % text
3987     else:
3988         return '%s' % text
3989
3990
3991 def do_ls(path, n):
3992     l = []
3993     if stat.S_ISDIR(n.mode):
3994         for sub in n:
3995             l.append(node_name(sub.name, sub))
3996     else:
3997         l.append(node_name(path, n))
3998     print columnate(l, '')
3999     
4000
4001 def write_to_file(inf, outf):
4002     for blob in chunkyreader(inf):
4003         outf.write(blob)
4004     
4005
4006 def inputiter():
4007     if os.isatty(sys.stdin.fileno()):
4008         while 1:
4009             try:
4010                 yield raw_input('bup> ')
4011             except EOFError:
4012                 break
4013     else:
4014         for line in sys.stdin:
4015             yield line
4016
4017
4018 def _completer_get_subs(line):
4019     (qtype, lastword) = shquote.unfinished_word(line)
4020     (dir,name) = os.path.split(lastword)
4021     #log('\ncompleter: %r %r %r\n' % (qtype, lastword, text))
4022     n = pwd.resolve(dir)
4023     subs = list(filter(lambda x: x.name.startswith(name),
4024                        n.subs()))
4025     return (dir, name, qtype, lastword, subs)
4026
4027
4028 _last_line = None
4029 _last_res = None
4030 def completer(text, state):
4031     global _last_line
4032     global _last_res
4033     try:
4034         line = readline.get_line_buffer()[:readline.get_endidx()]
4035         if _last_line != line:
4036             _last_res = _completer_get_subs(line)
4037             _last_line = line
4038         (dir, name, qtype, lastword, subs) = _last_res
4039         if state < len(subs):
4040             sn = subs[state]
4041             sn1 = sn.resolve('')  # deref symlinks
4042             fullname = os.path.join(dir, sn.name)
4043             if stat.S_ISDIR(sn1.mode):
4044                 ret = shquote.what_to_add(qtype, lastword, fullname+'/',
4045                                           terminate=False)
4046             else:
4047                 ret = shquote.what_to_add(qtype, lastword, fullname,
4048                                           terminate=True) + ' '
4049             return text + ret
4050     except Exception, e:
4051         log('\nerror in completion: %s\n' % e)
4052
4053             
4054 optspec = """
4055 bup ftp
4056 """
4057 o = options.Options('bup ftp', optspec)
4058 (opt, flags, extra) = o.parse(sys.argv[1:])
4059
4060 git.check_repo_or_die()
4061
4062 top = vfs.RefList(None)
4063 pwd = top
4064
4065 if extra:
4066     lines = extra
4067 else:
4068     readline.set_completer_delims(' \t\n\r/')
4069     readline.set_completer(completer)
4070     readline.parse_and_bind("tab: complete")
4071     lines = inputiter()
4072
4073 for line in lines:
4074     if not line.strip():
4075         continue
4076     words = [word for (wordstart,word) in shquote.quotesplit(line)]
4077     cmd = words[0].lower()
4078     #log('execute: %r %r\n' % (cmd, parm))
4079     try:
4080         if cmd == 'ls':
4081             for parm in (words[1:] or ['.']):
4082                 do_ls(parm, pwd.resolve(parm))
4083         elif cmd == 'cd':
4084             for parm in words[1:]:
4085                 pwd = pwd.resolve(parm)
4086         elif cmd == 'pwd':
4087             print pwd.fullname()
4088         elif cmd == 'cat':
4089             for parm in words[1:]:
4090                 write_to_file(pwd.resolve(parm).open(), sys.stdout)
4091         elif cmd == 'get':
4092             if len(words) not in [2,3]:
4093                 raise Exception('Usage: get <filename> [localname]')
4094             rname = words[1]
4095             (dir,base) = os.path.split(rname)
4096             lname = len(words)>2 and words[2] or base
4097             inf = pwd.resolve(rname).open()
4098             log('Saving %r\n' % lname)
4099             write_to_file(inf, open(lname, 'wb'))
4100         elif cmd == 'mget':
4101             for parm in words[1:]:
4102                 (dir,base) = os.path.split(parm)
4103                 for n in pwd.resolve(dir).subs():
4104                     if fnmatch.fnmatch(n.name, base):
4105                         try:
4106                             log('Saving %r\n' % n.name)
4107                             inf = n.open()
4108                             outf = open(n.name, 'wb')
4109                             write_to_file(inf, outf)
4110                             outf.close()
4111                         except Exception, e:
4112                             log('  error: %s\n' % e)
4113         elif cmd == 'help' or cmd == '?':
4114             log('Commands: ls cd pwd cat get mget help quit\n')
4115         elif cmd == 'quit' or cmd == 'exit' or cmd == 'bye':
4116             break
4117         else:
4118             raise Exception('no such command %r' % cmd)
4119     except Exception, e:
4120         log('error: %s\n' % e)
4121         #raise
4122 #!/usr/bin/env python
4123 import sys, mmap
4124 from bup import options, _hashsplit
4125 from bup.helpers import *
4126
4127 optspec = """
4128 bup random [-S seed] <numbytes>
4129 --
4130 S,seed=   optional random number seed (default 1)
4131 f,force   print random data to stdout even if it's a tty
4132 """
4133 o = options.Options('bup random', optspec)
4134 (opt, flags, extra) = o.parse(sys.argv[1:])
4135
4136 if len(extra) != 1:
4137     o.fatal("exactly one argument expected")
4138
4139 total = parse_num(extra[0])
4140
4141 if opt.force or (not os.isatty(1) and
4142                  not atoi(os.environ.get('BUP_FORCE_TTY')) & 1):
4143     _hashsplit.write_random(sys.stdout.fileno(), total, opt.seed or 0)
4144 else:
4145     log('error: not writing binary data to a terminal. Use -f to force.\n')
4146     sys.exit(1)
4147 #!/usr/bin/env python
4148 import sys, os, glob
4149 from bup import options
4150
4151 optspec = """
4152 bup help <command>
4153 """
4154 o = options.Options('bup help', optspec)
4155 (opt, flags, extra) = o.parse(sys.argv[1:])
4156
4157 if len(extra) == 0:
4158     # the wrapper program provides the default usage string
4159     os.execvp(os.environ['BUP_MAIN_EXE'], ['bup'])
4160 elif len(extra) == 1:
4161     docname = (extra[0]=='bup' and 'bup' or ('bup-%s' % extra[0]))
4162     exe = sys.argv[0]
4163     (exepath, exefile) = os.path.split(exe)
4164     manpath = os.path.join(exepath, '../Documentation/' + docname + '.[1-9]')
4165     g = glob.glob(manpath)
4166     if g:
4167         os.execvp('man', ['man', '-l', g[0]])
4168     else:
4169         os.execvp('man', ['man', docname])
4170 else:
4171     o.fatal("exactly one command name expected")
4172 #!/usr/bin/env python
4173 import sys, os, stat, errno, fuse, re, time, tempfile
4174 from bup import options, git, vfs
4175 from bup.helpers import *
4176
4177
4178 class Stat(fuse.Stat):
4179     def __init__(self):
4180         self.st_mode = 0
4181         self.st_ino = 0
4182         self.st_dev = 0
4183         self.st_nlink = 0
4184         self.st_uid = 0
4185         self.st_gid = 0
4186         self.st_size = 0
4187         self.st_atime = 0
4188         self.st_mtime = 0
4189         self.st_ctime = 0
4190         self.st_blocks = 0
4191         self.st_blksize = 0
4192         self.st_rdev = 0
4193
4194
4195 cache = {}
4196 def cache_get(top, path):
4197     parts = path.split('/')
4198     cache[('',)] = top
4199     c = None
4200     max = len(parts)
4201     #log('cache: %r\n' % cache.keys())
4202     for i in range(max):
4203         pre = parts[:max-i]
4204         #log('cache trying: %r\n' % pre)
4205         c = cache.get(tuple(pre))
4206         if c:
4207             rest = parts[max-i:]
4208             for r in rest:
4209                 #log('resolving %r from %r\n' % (r, c.fullname()))
4210                 c = c.lresolve(r)
4211                 key = tuple(pre + [r])
4212                 #log('saving: %r\n' % (key,))
4213                 cache[key] = c
4214             break
4215     assert(c)
4216     return c
4217         
4218     
4219
4220 class BupFs(fuse.Fuse):
4221     def __init__(self, top):
4222         fuse.Fuse.__init__(self)
4223         self.top = top
4224     
4225     def getattr(self, path):
4226         log('--getattr(%r)\n' % path)
4227         try:
4228             node = cache_get(self.top, path)
4229             st = Stat()
4230             st.st_mode = node.mode
4231             st.st_nlink = node.nlinks()
4232             st.st_size = node.size()
4233             st.st_mtime = node.mtime
4234             st.st_ctime = node.ctime
4235             st.st_atime = node.atime
4236             return st
4237         except vfs.NoSuchFile:
4238             return -errno.ENOENT
4239
4240     def readdir(self, path, offset):
4241         log('--readdir(%r)\n' % path)
4242         node = cache_get(self.top, path)
4243         yield fuse.Direntry('.')
4244         yield fuse.Direntry('..')
4245         for sub in node.subs():
4246             yield fuse.Direntry(sub.name)
4247
4248     def readlink(self, path):
4249         log('--readlink(%r)\n' % path)
4250         node = cache_get(self.top, path)
4251         return node.readlink()
4252
4253     def open(self, path, flags):
4254         log('--open(%r)\n' % path)
4255         node = cache_get(self.top, path)
4256         accmode = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
4257         if (flags & accmode) != os.O_RDONLY:
4258             return -errno.EACCES
4259         node.open()
4260
4261     def release(self, path, flags):
4262         log('--release(%r)\n' % path)
4263
4264     def read(self, path, size, offset):
4265         log('--read(%r)\n' % path)
4266         n = cache_get(self.top, path)
4267         o = n.open()
4268         o.seek(offset)
4269         return o.read(size)
4270
4271
4272 if not hasattr(fuse, '__version__'):
4273     raise RuntimeError, "your fuse module is too old for fuse.__version__"
4274 fuse.fuse_python_api = (0, 2)
4275
4276
4277 optspec = """
4278 bup fuse [-d] [-f] <mountpoint>
4279 --
4280 d,debug   increase debug level
4281 f,foreground  run in foreground
4282 """
4283 o = options.Options('bup fuse', optspec)
4284 (opt, flags, extra) = o.parse(sys.argv[1:])
4285
4286 if len(extra) != 1:
4287     o.fatal("exactly one argument expected")
4288
4289 git.check_repo_or_die()
4290 top = vfs.RefList(None)
4291 f = BupFs(top)
4292 f.fuse_args.mountpoint = extra[0]
4293 if opt.debug:
4294     f.fuse_args.add('debug')
4295 if opt.foreground:
4296     f.fuse_args.setmod('foreground')
4297 print f.multithreaded
4298 f.multithreaded = False
4299
4300 f.main()
4301 #!/usr/bin/env python
4302 from bup import git, options, client
4303 from bup.helpers import *
4304
4305 optspec = """
4306 [BUP_DIR=...] bup init [-r host:path]
4307 --
4308 r,remote=  remote repository path
4309 """
4310 o = options.Options('bup init', optspec)
4311 (opt, flags, extra) = o.parse(sys.argv[1:])
4312
4313 if extra:
4314     o.fatal("no arguments expected")
4315
4316
4317 if opt.remote:
4318     git.init_repo()  # local repo
4319     git.check_repo_or_die()
4320     cli = client.Client(opt.remote, create=True)
4321     cli.close()
4322 else:
4323     git.init_repo()
4324 #!/usr/bin/env python
4325 import sys, math, struct, glob
4326 from bup import options, git
4327 from bup.helpers import *
4328
4329 PAGE_SIZE=4096
4330 SHA_PER_PAGE=PAGE_SIZE/200.
4331
4332
4333 def merge(idxlist, bits, table):
4334     count = 0
4335     for e in git.idxmerge(idxlist):
4336         count += 1
4337         prefix = git.extract_bits(e, bits)
4338         table[prefix] = count
4339         yield e
4340
4341
4342 def do_midx(outdir, outfilename, infilenames):
4343     if not outfilename:
4344         assert(outdir)
4345         sum = Sha1('\0'.join(infilenames)).hexdigest()
4346         outfilename = '%s/midx-%s.midx' % (outdir, sum)
4347     
4348     inp = []
4349     total = 0
4350     for name in infilenames:
4351         ix = git.PackIdx(name)
4352         inp.append(ix)
4353         total += len(ix)
4354
4355     log('Merging %d indexes (%d objects).\n' % (len(infilenames), total))
4356     if (not opt.force and (total < 1024 and len(infilenames) < 3)) \
4357        or (opt.force and not total):
4358         log('midx: nothing to do.\n')
4359         return
4360
4361     pages = int(total/SHA_PER_PAGE) or 1
4362     bits = int(math.ceil(math.log(pages, 2)))
4363     entries = 2**bits
4364     log('Table size: %d (%d bits)\n' % (entries*4, bits))
4365     
4366     table = [0]*entries
4367
4368     try:
4369         os.unlink(outfilename)
4370     except OSError:
4371         pass
4372     f = open(outfilename + '.tmp', 'w+')
4373     f.write('MIDX\0\0\0\2')
4374     f.write(struct.pack('!I', bits))
4375     assert(f.tell() == 12)
4376     f.write('\0'*4*entries)
4377     
4378     for e in merge(inp, bits, table):
4379         f.write(e)
4380         
4381     f.write('\0'.join(os.path.basename(p) for p in infilenames))
4382
4383     f.seek(12)
4384     f.write(struct.pack('!%dI' % entries, *table))
4385     f.close()
4386     os.rename(outfilename + '.tmp', outfilename)
4387
4388     # this is just for testing
4389     if 0:
4390         p = git.PackMidx(outfilename)
4391         assert(len(p.idxnames) == len(infilenames))
4392         print p.idxnames
4393         assert(len(p) == total)
4394         pi = iter(p)
4395         for i in merge(inp, total, bits, table):
4396             assert(i == pi.next())
4397             assert(p.exists(i))
4398
4399     print outfilename
4400
4401 optspec = """
4402 bup midx [options...] <idxnames...>
4403 --
4404 o,output=  output midx filename (default: auto-generated)
4405 a,auto     automatically create .midx from any unindexed .idx files
4406 f,force    automatically create .midx from *all* .idx files
4407 """
4408 o = options.Options('bup midx', optspec)
4409 (opt, flags, extra) = o.parse(sys.argv[1:])
4410
4411 if extra and (opt.auto or opt.force):
4412     o.fatal("you can't use -f/-a and also provide filenames")
4413
4414 git.check_repo_or_die()
4415
4416 if extra:
4417     do_midx(git.repo('objects/pack'), opt.output, extra)
4418 elif opt.auto or opt.force:
4419     paths = [git.repo('objects/pack')]
4420     paths += glob.glob(git.repo('index-cache/*/.'))
4421     for path in paths:
4422         log('midx: scanning %s\n' % path)
4423         if opt.force:
4424             do_midx(path, opt.output, glob.glob('%s/*.idx' % path))
4425         elif opt.auto:
4426             m = git.PackIdxList(path)
4427             needed = {}
4428             for pack in m.packs:  # only .idx files without a .midx are open
4429                 if pack.name.endswith('.idx'):
4430                     needed[pack.name] = 1
4431             del m
4432             do_midx(path, opt.output, needed.keys())
4433         log('\n')
4434 else:
4435     o.fatal("you must use -f or -a or provide input filenames")
4436 #!/usr/bin/env python
4437 import sys, os, random
4438 from bup import options
4439 from bup.helpers import *
4440
4441
4442 def randblock(n):
4443     l = []
4444     for i in xrange(n):
4445         l.append(chr(random.randrange(0,256)))
4446     return ''.join(l)
4447
4448
4449 optspec = """
4450 bup damage [-n count] [-s maxsize] [-S seed] <filenames...>
4451 --
4452    WARNING: THIS COMMAND IS EXTREMELY DANGEROUS
4453 n,num=   number of blocks to damage
4454 s,size=  maximum size of each damaged block
4455 percent= maximum size of each damaged block (as a percent of entire file)
4456 equal    spread damage evenly throughout the file
4457 S,seed=  random number seed (for repeatable tests)
4458 """
4459 o = options.Options('bup damage', optspec)
4460 (opt, flags, extra) = o.parse(sys.argv[1:])
4461
4462 if not extra:
4463     o.fatal('filenames expected')
4464
4465 if opt.seed != None:
4466     random.seed(opt.seed)
4467
4468 for name in extra:
4469     log('Damaging "%s"...\n' % name)
4470     f = open(name, 'r+b')
4471     st = os.fstat(f.fileno())
4472     size = st.st_size
4473     if opt.percent or opt.size:
4474         ms1 = int(float(opt.percent or 0)/100.0*size) or size
4475         ms2 = opt.size or size
4476         maxsize = min(ms1, ms2)
4477     else:
4478         maxsize = 1
4479     chunks = opt.num or 10
4480     chunksize = size/chunks
4481     for r in range(chunks):
4482         sz = random.randrange(1, maxsize+1)
4483         if sz > size:
4484             sz = size
4485         if opt.equal:
4486             ofs = r*chunksize
4487         else:
4488             ofs = random.randrange(0, size - sz + 1)
4489         log('  %6d bytes at %d\n' % (sz, ofs))
4490         f.seek(ofs)
4491         f.write(randblock(sz))
4492     f.close()
4493 #!/usr/bin/env python
4494 import sys, struct, mmap
4495 from bup import options, git
4496 from bup.helpers import *
4497
4498 suspended_w = None
4499
4500
4501 def init_dir(conn, arg):
4502     git.init_repo(arg)
4503     log('bup server: bupdir initialized: %r\n' % git.repodir)
4504     conn.ok()
4505
4506
4507 def set_dir(conn, arg):
4508     git.check_repo_or_die(arg)
4509     log('bup server: bupdir is %r\n' % git.repodir)
4510     conn.ok()
4511
4512     
4513 def list_indexes(conn, junk):
4514     git.check_repo_or_die()
4515     for f in os.listdir(git.repo('objects/pack')):
4516         if f.endswith('.idx'):
4517             conn.write('%s\n' % f)
4518     conn.ok()
4519
4520
4521 def send_index(conn, name):
4522     git.check_repo_or_die()
4523     assert(name.find('/') < 0)
4524     assert(name.endswith('.idx'))
4525     idx = git.PackIdx(git.repo('objects/pack/%s' % name))
4526     conn.write(struct.pack('!I', len(idx.map)))
4527     conn.write(idx.map)
4528     conn.ok()
4529
4530
4531 def receive_objects(conn, junk):
4532     global suspended_w
4533     git.check_repo_or_die()
4534     suggested = {}
4535     if suspended_w:
4536         w = suspended_w
4537         suspended_w = None
4538     else:
4539         w = git.PackWriter()
4540     while 1:
4541         ns = conn.read(4)
4542         if not ns:
4543             w.abort()
4544             raise Exception('object read: expected length header, got EOF\n')
4545         n = struct.unpack('!I', ns)[0]
4546         #log('expecting %d bytes\n' % n)
4547         if not n:
4548             log('bup server: received %d object%s.\n' 
4549                 % (w.count, w.count!=1 and "s" or ''))
4550             fullpath = w.close()
4551             if fullpath:
4552                 (dir, name) = os.path.split(fullpath)
4553                 conn.write('%s.idx\n' % name)
4554             conn.ok()
4555             return
4556         elif n == 0xffffffff:
4557             log('bup server: receive-objects suspended.\n')
4558             suspended_w = w
4559             conn.ok()
4560             return
4561             
4562         buf = conn.read(n)  # object sizes in bup are reasonably small
4563         #log('read %d bytes\n' % n)
4564         if len(buf) < n:
4565             w.abort()
4566             raise Exception('object read: expected %d bytes, got %d\n'
4567                             % (n, len(buf)))
4568         (type, content) = git._decode_packobj(buf)
4569         sha = git.calc_hash(type, content)
4570         oldpack = w.exists(sha)
4571         # FIXME: we only suggest a single index per cycle, because the client
4572         # is currently dumb to download more than one per cycle anyway.
4573         # Actually we should fix the client, but this is a minor optimization
4574         # on the server side.
4575         if not suggested and \
4576           oldpack and (oldpack == True or oldpack.endswith('.midx')):
4577             # FIXME: we shouldn't really have to know about midx files
4578             # at this layer.  But exists() on a midx doesn't return the
4579             # packname (since it doesn't know)... probably we should just
4580             # fix that deficiency of midx files eventually, although it'll
4581             # make the files bigger.  This method is certainly not very
4582             # efficient.
4583             w.objcache.refresh(skip_midx = True)
4584             oldpack = w.objcache.exists(sha)
4585             log('new suggestion: %r\n' % oldpack)
4586             assert(oldpack)
4587             assert(oldpack != True)
4588             assert(not oldpack.endswith('.midx'))
4589             w.objcache.refresh(skip_midx = False)
4590         if not suggested and oldpack:
4591             assert(oldpack.endswith('.idx'))
4592             (dir,name) = os.path.split(oldpack)
4593             if not (name in suggested):
4594                 log("bup server: suggesting index %s\n" % name)
4595                 conn.write('index %s\n' % name)
4596                 suggested[name] = 1
4597         else:
4598             w._raw_write([buf])
4599     # NOTREACHED
4600
4601
4602 def read_ref(conn, refname):
4603     git.check_repo_or_die()
4604     r = git.read_ref(refname)
4605     conn.write('%s\n' % (r or '').encode('hex'))
4606     conn.ok()
4607
4608
4609 def update_ref(conn, refname):
4610     git.check_repo_or_die()
4611     newval = conn.readline().strip()
4612     oldval = conn.readline().strip()
4613     git.update_ref(refname, newval.decode('hex'), oldval.decode('hex'))
4614     conn.ok()
4615
4616
4617 def cat(conn, id):
4618     git.check_repo_or_die()
4619     try:
4620         for blob in git.cat(id):
4621             conn.write(struct.pack('!I', len(blob)))
4622             conn.write(blob)
4623     except KeyError, e:
4624         log('server: error: %s\n' % e)
4625         conn.write('\0\0\0\0')
4626         conn.error(e)
4627     else:
4628         conn.write('\0\0\0\0')
4629         conn.ok()
4630
4631
4632 optspec = """
4633 bup server
4634 """
4635 o = options.Options('bup server', optspec)
4636 (opt, flags, extra) = o.parse(sys.argv[1:])
4637
4638 if extra:
4639     o.fatal('no arguments expected')
4640
4641 log('bup server: reading from stdin.\n')
4642
4643 commands = {
4644     'init-dir': init_dir,
4645     'set-dir': set_dir,
4646     'list-indexes': list_indexes,
4647     'send-index': send_index,
4648     'receive-objects': receive_objects,
4649     'read-ref': read_ref,
4650     'update-ref': update_ref,
4651     'cat': cat,
4652 }
4653
4654 # FIXME: this protocol is totally lame and not at all future-proof.
4655 # (Especially since we abort completely as soon as *anything* bad happens)
4656 conn = Conn(sys.stdin, sys.stdout)
4657 lr = linereader(conn)
4658 for _line in lr:
4659     line = _line.strip()
4660     if not line:
4661         continue
4662     log('bup server: command: %r\n' % line)
4663     words = line.split(' ', 1)
4664     cmd = words[0]
4665     rest = len(words)>1 and words[1] or ''
4666     if cmd == 'quit':
4667         break
4668     else:
4669         cmd = commands.get(cmd)
4670         if cmd:
4671             cmd(conn, rest)
4672         else:
4673             raise Exception('unknown server command: %r\n' % line)
4674
4675 log('bup server: done\n')
4676 #!/usr/bin/env python
4677 import sys, time, struct
4678 from bup import hashsplit, git, options, client
4679 from bup.helpers import *
4680 from subprocess import PIPE
4681
4682
4683 optspec = """
4684 bup join [-r host:path] [refs or hashes...]
4685 --
4686 r,remote=  remote repository path
4687 """
4688 o = options.Options('bup join', optspec)
4689 (opt, flags, extra) = o.parse(sys.argv[1:])
4690
4691 git.check_repo_or_die()
4692
4693 if not extra:
4694     extra = linereader(sys.stdin)
4695
4696 ret = 0
4697
4698 if opt.remote:
4699     cli = client.Client(opt.remote)
4700     cat = cli.cat
4701 else:
4702     cp = git.CatPipe()
4703     cat = cp.join
4704
4705 for id in extra:
4706     try:
4707         for blob in cat(id):
4708             sys.stdout.write(blob)
4709     except KeyError, e:
4710         sys.stdout.flush()
4711         log('error: %s\n' % e)
4712         ret = 1
4713
4714 sys.exit(ret)
4715 #!/usr/bin/env python
4716 import sys, re, errno, stat, time, math
4717 from bup import hashsplit, git, options, index, client
4718 from bup.helpers import *
4719
4720
4721 optspec = """
4722 bup save [-tc] [-n name] <filenames...>
4723 --
4724 r,remote=  remote repository path
4725 t,tree     output a tree id
4726 c,commit   output a commit id
4727 n,name=    name of backup set to update (if any)
4728 v,verbose  increase log output (can be used more than once)
4729 q,quiet    don't show progress meter
4730 smaller=   only back up files smaller than n bytes
4731 """
4732 o = options.Options('bup save', optspec)
4733 (opt, flags, extra) = o.parse(sys.argv[1:])
4734
4735 git.check_repo_or_die()
4736 if not (opt.tree or opt.commit or opt.name):
4737     o.fatal("use one or more of -t, -c, -n")
4738 if not extra:
4739     o.fatal("no filenames given")
4740
4741 opt.progress = (istty and not opt.quiet)
4742 opt.smaller = parse_num(opt.smaller or 0)
4743
4744 is_reverse = os.environ.get('BUP_SERVER_REVERSE')
4745 if is_reverse and opt.remote:
4746     o.fatal("don't use -r in reverse mode; it's automatic")
4747
4748 refname = opt.name and 'refs/heads/%s' % opt.name or None
4749 if opt.remote or is_reverse:
4750     cli = client.Client(opt.remote)
4751     oldref = refname and cli.read_ref(refname) or None
4752     w = cli.new_packwriter()
4753 else:
4754     cli = None
4755     oldref = refname and git.read_ref(refname) or None
4756     w = git.PackWriter()
4757
4758 handle_ctrl_c()
4759
4760
4761 def eatslash(dir):
4762     if dir.endswith('/'):
4763         return dir[:-1]
4764     else:
4765         return dir
4766
4767
4768 parts = ['']
4769 shalists = [[]]
4770
4771 def _push(part):
4772     assert(part)
4773     parts.append(part)
4774     shalists.append([])
4775
4776 def _pop(force_tree):
4777     assert(len(parts) >= 1)
4778     part = parts.pop()
4779     shalist = shalists.pop()
4780     tree = force_tree or w.new_tree(shalist)
4781     if shalists:
4782         shalists[-1].append(('40000', part, tree))
4783     else:  # this was the toplevel, so put it back for sanity
4784         shalists.append(shalist)
4785     return tree
4786
4787 lastremain = None
4788 def progress_report(n):
4789     global count, subcount, lastremain
4790     subcount += n
4791     cc = count + subcount
4792     pct = total and (cc*100.0/total) or 0
4793     now = time.time()
4794     elapsed = now - tstart
4795     kps = elapsed and int(cc/1024./elapsed)
4796     kps_frac = 10 ** int(math.log(kps+1, 10) - 1)
4797     kps = int(kps/kps_frac)*kps_frac
4798     if cc:
4799         remain = elapsed*1.0/cc * (total-cc)
4800     else:
4801         remain = 0.0
4802     if (lastremain and (remain > lastremain)
4803           and ((remain - lastremain)/lastremain < 0.05)):
4804         remain = lastremain
4805     else:
4806         lastremain = remain
4807     hours = int(remain/60/60)
4808     mins = int(remain/60 - hours*60)
4809     secs = int(remain - hours*60*60 - mins*60)
4810     if elapsed < 30:
4811         remainstr = ''
4812         kpsstr = ''
4813     else:
4814         kpsstr = '%dk/s' % kps
4815         if hours:
4816             remainstr = '%dh%dm' % (hours, mins)
4817         elif mins:
4818             remainstr = '%dm%d' % (mins, secs)
4819         else:
4820             remainstr = '%ds' % secs
4821     progress('Saving: %.2f%% (%d/%dk, %d/%d files) %s %s\r'
4822              % (pct, cc/1024, total/1024, fcount, ftotal,
4823                 remainstr, kpsstr))
4824
4825
4826 r = index.Reader(git.repo('bupindex'))
4827
4828 def already_saved(ent):
4829     return ent.is_valid() and w.exists(ent.sha) and ent.sha
4830
4831 def wantrecurse_pre(ent):
4832     return not already_saved(ent)
4833
4834 def wantrecurse_during(ent):
4835     return not already_saved(ent) or ent.sha_missing()
4836
4837 total = ftotal = 0
4838 if opt.progress:
4839     for (transname,ent) in r.filter(extra, wantrecurse=wantrecurse_pre):
4840         if not (ftotal % 10024):
4841             progress('Reading index: %d\r' % ftotal)
4842         exists = ent.exists()
4843         hashvalid = already_saved(ent)
4844         ent.set_sha_missing(not hashvalid)
4845         if not opt.smaller or ent.size < opt.smaller:
4846             if exists and not hashvalid:
4847                 total += ent.size
4848         ftotal += 1
4849     progress('Reading index: %d, done.\n' % ftotal)
4850     hashsplit.progress_callback = progress_report
4851
4852 tstart = time.time()
4853 count = subcount = fcount = 0
4854 lastskip_name = None
4855 lastdir = ''
4856 for (transname,ent) in r.filter(extra, wantrecurse=wantrecurse_during):
4857     (dir, file) = os.path.split(ent.name)
4858     exists = (ent.flags & index.IX_EXISTS)
4859     hashvalid = already_saved(ent)
4860     wasmissing = ent.sha_missing()
4861     oldsize = ent.size
4862     if opt.verbose:
4863         if not exists:
4864             status = 'D'
4865         elif not hashvalid:
4866             if ent.sha == index.EMPTY_SHA:
4867                 status = 'A'
4868             else:
4869                 status = 'M'
4870         else:
4871             status = ' '
4872         if opt.verbose >= 2:
4873             log('%s %-70s\n' % (status, ent.name))
4874         elif not stat.S_ISDIR(ent.mode) and lastdir != dir:
4875             if not lastdir.startswith(dir):
4876                 log('%s %-70s\n' % (status, os.path.join(dir, '')))
4877             lastdir = dir
4878
4879     if opt.progress:
4880         progress_report(0)
4881     fcount += 1
4882     
4883     if not exists:
4884         continue
4885     if opt.smaller and ent.size >= opt.smaller:
4886         if exists and not hashvalid:
4887             add_error('skipping large file "%s"' % ent.name)
4888             lastskip_name = ent.name
4889         continue
4890
4891     assert(dir.startswith('/'))
4892     dirp = dir.split('/')
4893     while parts > dirp:
4894         _pop(force_tree = None)
4895     if dir != '/':
4896         for part in dirp[len(parts):]:
4897             _push(part)
4898
4899     if not file:
4900         # no filename portion means this is a subdir.  But
4901         # sub/parentdirectories already handled in the pop/push() part above.
4902         oldtree = already_saved(ent) # may be None
4903         newtree = _pop(force_tree = oldtree)
4904         if not oldtree:
4905             if lastskip_name and lastskip_name.startswith(ent.name):
4906                 ent.invalidate()
4907             else:
4908                 ent.validate(040000, newtree)
4909             ent.repack()
4910         if exists and wasmissing:
4911             count += oldsize
4912         continue
4913
4914     # it's not a directory
4915     id = None
4916     if hashvalid:
4917         mode = '%o' % ent.gitmode
4918         id = ent.sha
4919         shalists[-1].append((mode, 
4920                              git.mangle_name(file, ent.mode, ent.gitmode),
4921                              id))
4922     else:
4923         if stat.S_ISREG(ent.mode):
4924             try:
4925                 f = hashsplit.open_noatime(ent.name)
4926             except IOError, e:
4927                 add_error(e)
4928                 lastskip_name = ent.name
4929             except OSError, e:
4930                 add_error(e)
4931                 lastskip_name = ent.name
4932             else:
4933                 (mode, id) = hashsplit.split_to_blob_or_tree(w, [f])
4934         else:
4935             if stat.S_ISDIR(ent.mode):
4936                 assert(0)  # handled above
4937             elif stat.S_ISLNK(ent.mode):
4938                 try:
4939                     rl = os.readlink(ent.name)
4940                 except OSError, e:
4941                     add_error(e)
4942                     lastskip_name = ent.name
4943                 except IOError, e:
4944                     add_error(e)
4945                     lastskip_name = ent.name
4946                 else:
4947                     (mode, id) = ('120000', w.new_blob(rl))
4948             else:
4949                 add_error(Exception('skipping special file "%s"' % ent.name))
4950                 lastskip_name = ent.name
4951         if id:
4952             ent.validate(int(mode, 8), id)
4953             ent.repack()
4954             shalists[-1].append((mode,
4955                                  git.mangle_name(file, ent.mode, ent.gitmode),
4956                                  id))
4957     if exists and wasmissing:
4958         count += oldsize
4959         subcount = 0
4960
4961
4962 if opt.progress:
4963     pct = total and count*100.0/total or 100
4964     progress('Saving: %.2f%% (%d/%dk, %d/%d files), done.    \n'
4965              % (pct, count/1024, total/1024, fcount, ftotal))
4966
4967 while len(parts) > 1:
4968     _pop(force_tree = None)
4969 assert(len(shalists) == 1)
4970 tree = w.new_tree(shalists[-1])
4971 if opt.tree:
4972     print tree.encode('hex')
4973 if opt.commit or opt.name:
4974     msg = 'bup save\n\nGenerated by command:\n%r' % sys.argv
4975     ref = opt.name and ('refs/heads/%s' % opt.name) or None
4976     commit = w.new_commit(oldref, tree, msg)
4977     if opt.commit:
4978         print commit.encode('hex')
4979
4980 w.close()  # must close before we can update the ref
4981         
4982 if opt.name:
4983     if cli:
4984         cli.update_ref(refname, commit, oldref)
4985     else:
4986         git.update_ref(refname, commit, oldref)
4987
4988 if cli:
4989     cli.close()
4990
4991 if saved_errors:
4992     log('WARNING: %d errors encountered while saving.\n' % len(saved_errors))
4993     sys.exit(1)
4994 #!/usr/bin/env python
4995 import sys, time
4996 from bup import options
4997
4998 optspec = """
4999 bup tick
5000 """
5001 o = options.Options('bup tick', optspec)
5002 (opt, flags, extra) = o.parse(sys.argv[1:])
5003
5004 if extra:
5005     o.fatal("no arguments expected")
5006
5007 t = time.time()
5008 tleft = 1 - (t - int(t))
5009 time.sleep(tleft)
5010 #!/usr/bin/env python
5011 import os, sys, stat, time
5012 from bup import options, git, index, drecurse
5013 from bup.helpers import *
5014
5015
5016 def merge_indexes(out, r1, r2):
5017     for e in index.MergeIter([r1, r2]):
5018         # FIXME: shouldn't we remove deleted entries eventually?  When?
5019         out.add_ixentry(e)
5020
5021
5022 class IterHelper:
5023     def __init__(self, l):
5024         self.i = iter(l)
5025         self.cur = None
5026         self.next()
5027
5028     def next(self):
5029         try:
5030             self.cur = self.i.next()
5031         except StopIteration:
5032             self.cur = None
5033         return self.cur
5034
5035
5036 def check_index(reader):
5037     try:
5038         log('check: checking forward iteration...\n')
5039         e = None
5040         d = {}
5041         for e in reader.forward_iter():
5042             if e.children_n:
5043                 if opt.verbose:
5044                     log('%08x+%-4d %r\n' % (e.children_ofs, e.children_n,
5045                                             e.name))
5046                 assert(e.children_ofs)
5047                 assert(e.name.endswith('/'))
5048                 assert(not d.get(e.children_ofs))
5049                 d[e.children_ofs] = 1
5050             if e.flags & index.IX_HASHVALID:
5051                 assert(e.sha != index.EMPTY_SHA)
5052                 assert(e.gitmode)
5053         assert(not e or e.name == '/')  # last entry is *always* /
5054         log('check: checking normal iteration...\n')
5055         last = None
5056         for e in reader:
5057             if last:
5058                 assert(last > e.name)
5059             last = e.name
5060     except:
5061         log('index error! at %r\n' % e)
5062         raise
5063     log('check: passed.\n')
5064
5065
5066 def update_index(top):
5067     ri = index.Reader(indexfile)
5068     wi = index.Writer(indexfile)
5069     rig = IterHelper(ri.iter(name=top))
5070     tstart = int(time.time())
5071
5072     hashgen = None
5073     if opt.fake_valid:
5074         def hashgen(name):
5075             return (0100644, index.FAKE_SHA)
5076
5077     total = 0
5078     for (path,pst) in drecurse.recursive_dirlist([top], xdev=opt.xdev):
5079         if opt.verbose>=2 or (opt.verbose==1 and stat.S_ISDIR(pst.st_mode)):
5080             sys.stdout.write('%s\n' % path)
5081             sys.stdout.flush()
5082             progress('Indexing: %d\r' % total)
5083         elif not (total % 128):
5084             progress('Indexing: %d\r' % total)
5085         total += 1
5086         while rig.cur and rig.cur.name > path:  # deleted paths
5087             if rig.cur.exists():
5088                 rig.cur.set_deleted()
5089                 rig.cur.repack()
5090             rig.next()
5091         if rig.cur and rig.cur.name == path:    # paths that already existed
5092             if pst:
5093                 rig.cur.from_stat(pst, tstart)
5094             if not (rig.cur.flags & index.IX_HASHVALID):
5095                 if hashgen:
5096                     (rig.cur.gitmode, rig.cur.sha) = hashgen(path)
5097                     rig.cur.flags |= index.IX_HASHVALID
5098             if opt.fake_invalid:
5099                 rig.cur.invalidate()
5100             rig.cur.repack()
5101             rig.next()
5102         else:  # new paths
5103             wi.add(path, pst, hashgen = hashgen)
5104     progress('Indexing: %d, done.\n' % total)
5105     
5106     if ri.exists():
5107         ri.save()
5108         wi.flush()
5109         if wi.count:
5110             wr = wi.new_reader()
5111             if opt.check:
5112                 log('check: before merging: oldfile\n')
5113                 check_index(ri)
5114                 log('check: before merging: newfile\n')
5115                 check_index(wr)
5116             mi = index.Writer(indexfile)
5117             merge_indexes(mi, ri, wr)
5118             ri.close()
5119             mi.close()
5120             wr.close()
5121         wi.abort()
5122     else:
5123         wi.close()
5124
5125
5126 optspec = """
5127 bup index <-p|m|u> [options...] <filenames...>
5128 --
5129 p,print    print the index entries for the given names (also works with -u)
5130 m,modified print only added/deleted/modified files (implies -p)
5131 s,status   print each filename with a status char (A/M/D) (implies -p)
5132 H,hash     print the hash for each object next to its name (implies -p)
5133 l,long     print more information about each file
5134 u,update   (recursively) update the index entries for the given filenames
5135 x,xdev,one-file-system  don't cross filesystem boundaries
5136 fake-valid mark all index entries as up-to-date even if they aren't
5137 fake-invalid mark all index entries as invalid
5138 check      carefully check index file integrity
5139 f,indexfile=  the name of the index file (default 'index')
5140 v,verbose  increase log output (can be used more than once)
5141 """
5142 o = options.Options('bup index', optspec)
5143 (opt, flags, extra) = o.parse(sys.argv[1:])
5144
5145 if not (opt.modified or opt['print'] or opt.status or opt.update or opt.check):
5146     o.fatal('supply one or more of -p, -s, -m, -u, or --check')
5147 if (opt.fake_valid or opt.fake_invalid) and not opt.update:
5148     o.fatal('--fake-{in,}valid are meaningless without -u')
5149 if opt.fake_valid and opt.fake_invalid:
5150     o.fatal('--fake-valid is incompatible with --fake-invalid')
5151
5152 git.check_repo_or_die()
5153 indexfile = opt.indexfile or git.repo('bupindex')
5154
5155 handle_ctrl_c()
5156
5157 if opt.check:
5158     log('check: starting initial check.\n')
5159     check_index(index.Reader(indexfile))
5160
5161 paths = index.reduce_paths(extra)
5162
5163 if opt.update:
5164     if not paths:
5165         o.fatal('update (-u) requested but no paths given')
5166     for (rp,path) in paths:
5167         update_index(rp)
5168
5169 if opt['print'] or opt.status or opt.modified:
5170     for (name, ent) in index.Reader(indexfile).filter(extra or ['']):
5171         if (opt.modified 
5172             and (ent.is_valid() or ent.is_deleted() or not ent.mode)):
5173             continue
5174         line = ''
5175         if opt.status:
5176             if ent.is_deleted():
5177                 line += 'D '
5178             elif not ent.is_valid():
5179                 if ent.sha == index.EMPTY_SHA:
5180                     line += 'A '
5181                 else:
5182                     line += 'M '
5183             else:
5184                 line += '  '
5185         if opt.hash:
5186             line += ent.sha.encode('hex') + ' '
5187         if opt.long:
5188             line += "%7s %7s " % (oct(ent.mode), oct(ent.gitmode))
5189         print line + (name or './')
5190
5191 if opt.check and (opt['print'] or opt.status or opt.modified or opt.update):
5192     log('check: starting final check.\n')
5193     check_index(index.Reader(indexfile))
5194
5195 if saved_errors:
5196     log('WARNING: %d errors encountered.\n' % len(saved_errors))
5197     sys.exit(1)
5198 #!/usr/bin/env python
5199 import sys, os, struct
5200 from bup import options, helpers
5201
5202 optspec = """
5203 bup rbackup-server
5204 --
5205     This command is not intended to be run manually.
5206 """
5207 o = options.Options('bup rbackup-server', optspec)
5208 (opt, flags, extra) = o.parse(sys.argv[1:])
5209 if extra:
5210     o.fatal('no arguments expected')
5211
5212 # get the subcommand's argv.
5213 # Normally we could just pass this on the command line, but since we'll often
5214 # be getting called on the other end of an ssh pipe, which tends to mangle
5215 # argv (by sending it via the shell), this way is much safer.
5216 buf = sys.stdin.read(4)
5217 sz = struct.unpack('!I', buf)[0]
5218 assert(sz > 0)
5219 assert(sz < 1000000)
5220 buf = sys.stdin.read(sz)
5221 assert(len(buf) == sz)
5222 argv = buf.split('\0')
5223
5224 # stdin/stdout are supposedly connected to 'bup server' that the caller
5225 # started for us (often on the other end of an ssh tunnel), so we don't want
5226 # to misuse them.  Move them out of the way, then replace stdout with
5227 # a pointer to stderr in case our subcommand wants to do something with it.
5228 #
5229 # It might be nice to do the same with stdin, but my experiments showed that
5230 # ssh seems to make its child's stderr a readable-but-never-reads-anything
5231 # socket.  They really should have used shutdown(SHUT_WR) on the other end
5232 # of it, but probably didn't.  Anyway, it's too messy, so let's just make sure
5233 # anyone reading from stdin is disappointed.
5234 #
5235 # (You can't just leave stdin/stdout "not open" by closing the file
5236 # descriptors.  Then the next file that opens is automatically assigned 0 or 1,
5237 # and people *trying* to read/write stdin/stdout get screwed.)
5238 os.dup2(0, 3)
5239 os.dup2(1, 4)
5240 os.dup2(2, 1)
5241 fd = os.open('/dev/null', os.O_RDONLY)
5242 os.dup2(fd, 0)
5243 os.close(fd)
5244
5245 os.environ['BUP_SERVER_REVERSE'] = helpers.hostname()
5246 os.execvp(argv[0], argv)
5247 sys.exit(99)
5248 #!/usr/bin/env python
5249 import sys, os, glob, subprocess, time
5250 from bup import options, git
5251 from bup.helpers import *
5252
5253 par2_ok = 0
5254 nullf = open('/dev/null')
5255
5256 def debug(s):
5257     if opt.verbose:
5258         log(s)
5259
5260 def run(argv):
5261     # at least in python 2.5, using "stdout=2" or "stdout=sys.stderr" below
5262     # doesn't actually work, because subprocess closes fd #2 right before
5263     # execing for some reason.  So we work around it by duplicating the fd
5264     # first.
5265     fd = os.dup(2)  # copy stderr
5266     try:
5267         p = subprocess.Popen(argv, stdout=fd, close_fds=False)
5268         return p.wait()
5269     finally:
5270         os.close(fd)
5271
5272 def par2_setup():
5273     global par2_ok
5274     rv = 1
5275     try:
5276         p = subprocess.Popen(['par2', '--help'],
5277                              stdout=nullf, stderr=nullf, stdin=nullf)
5278         rv = p.wait()
5279     except OSError:
5280         log('fsck: warning: par2 not found; disabling recovery features.\n')
5281     else:
5282         par2_ok = 1
5283
5284 def parv(lvl):
5285     if opt.verbose >= lvl:
5286         if istty:
5287             return []
5288         else:
5289             return ['-q']
5290     else:
5291         return ['-qq']
5292
5293 def par2_generate(base):
5294     return run(['par2', 'create', '-n1', '-c200'] + parv(2)
5295                + ['--', base, base+'.pack', base+'.idx'])
5296
5297 def par2_verify(base):
5298     return run(['par2', 'verify'] + parv(3) + ['--', base])
5299
5300 def par2_repair(base):
5301     return run(['par2', 'repair'] + parv(2) + ['--', base])
5302
5303 def quick_verify(base):
5304     f = open(base + '.pack', 'rb')
5305     f.seek(-20, 2)
5306     wantsum = f.read(20)
5307     assert(len(wantsum) == 20)
5308     f.seek(0)
5309     sum = Sha1()
5310     for b in chunkyreader(f, os.fstat(f.fileno()).st_size - 20):
5311         sum.update(b)
5312     if sum.digest() != wantsum:
5313         raise ValueError('expected %r, got %r' % (wantsum.encode('hex'),
5314                                                   sum.hexdigest()))
5315         
5316
5317 def git_verify(base):
5318     if opt.quick:
5319         try:
5320             quick_verify(base)
5321         except Exception, e:
5322             debug('error: %s\n' % e)
5323             return 1
5324         return 0
5325     else:
5326         return run(['git', 'verify-pack', '--', base])
5327     
5328     
5329 def do_pack(base, last):
5330     code = 0
5331     if par2_ok and par2_exists and (opt.repair or not opt.generate):
5332         vresult = par2_verify(base)
5333         if vresult != 0:
5334             if opt.repair:
5335                 rresult = par2_repair(base)
5336                 if rresult != 0:
5337                     print '%s par2 repair: failed (%d)' % (last, rresult)
5338                     code = rresult
5339                 else:
5340                     print '%s par2 repair: succeeded (0)' % last
5341                     code = 100
5342             else:
5343                 print '%s par2 verify: failed (%d)' % (last, vresult)
5344                 code = vresult
5345         else:
5346             print '%s ok' % last
5347     elif not opt.generate or (par2_ok and not par2_exists):
5348         gresult = git_verify(base)
5349         if gresult != 0:
5350             print '%s git verify: failed (%d)' % (last, gresult)
5351             code = gresult
5352         else:
5353             if par2_ok and opt.generate:
5354                 presult = par2_generate(base)
5355                 if presult != 0:
5356                     print '%s par2 create: failed (%d)' % (last, presult)
5357                     code = presult
5358                 else:
5359                     print '%s ok' % last
5360             else:
5361                 print '%s ok' % last
5362     else:
5363         assert(opt.generate and (not par2_ok or par2_exists))
5364         debug('    skipped: par2 file already generated.\n')
5365     return code
5366
5367
5368 optspec = """
5369 bup fsck [options...] [filenames...]
5370 --
5371 r,repair    attempt to repair errors using par2 (dangerous!)
5372 g,generate  generate auto-repair information using par2
5373 v,verbose   increase verbosity (can be used more than once)
5374 quick       just check pack sha1sum, don't use git verify-pack
5375 j,jobs=     run 'n' jobs in parallel
5376 par2-ok     immediately return 0 if par2 is ok, 1 if not
5377 disable-par2  ignore par2 even if it is available
5378 """
5379 o = options.Options('bup fsck', optspec)
5380 (opt, flags, extra) = o.parse(sys.argv[1:])
5381
5382 par2_setup()
5383 if opt.par2_ok:
5384     if par2_ok:
5385         sys.exit(0)  # 'true' in sh
5386     else:
5387         sys.exit(1)
5388 if opt.disable_par2:
5389     par2_ok = 0
5390
5391 git.check_repo_or_die()
5392
5393 if not extra:
5394     debug('fsck: No filenames given: checking all packs.\n')
5395     extra = glob.glob(git.repo('objects/pack/*.pack'))
5396
5397 code = 0
5398 count = 0
5399 outstanding = {}
5400 for name in extra:
5401     if name.endswith('.pack'):
5402         base = name[:-5]
5403     elif name.endswith('.idx'):
5404         base = name[:-4]
5405     elif name.endswith('.par2'):
5406         base = name[:-5]
5407     elif os.path.exists(name + '.pack'):
5408         base = name
5409     else:
5410         raise Exception('%s is not a pack file!' % name)
5411     (dir,last) = os.path.split(base)
5412     par2_exists = os.path.exists(base + '.par2')
5413     if par2_exists and os.stat(base + '.par2').st_size == 0:
5414         par2_exists = 0
5415     sys.stdout.flush()
5416     debug('fsck: checking %s (%s)\n' 
5417           % (last, par2_ok and par2_exists and 'par2' or 'git'))
5418     if not opt.verbose:
5419         progress('fsck (%d/%d)\r' % (count, len(extra)))
5420     
5421     if not opt.jobs:
5422         nc = do_pack(base, last)
5423         code = code or nc
5424         count += 1
5425     else:
5426         while len(outstanding) >= opt.jobs:
5427             (pid,nc) = os.wait()
5428             nc >>= 8
5429             if pid in outstanding:
5430                 del outstanding[pid]
5431                 code = code or nc
5432                 count += 1
5433         pid = os.fork()
5434         if pid:  # parent
5435             outstanding[pid] = 1
5436         else: # child
5437             try:
5438                 sys.exit(do_pack(base, last))
5439             except Exception, e:
5440                 log('exception: %r\n' % e)
5441                 sys.exit(99)
5442                 
5443 while len(outstanding):
5444     (pid,nc) = os.wait()
5445     nc >>= 8
5446     if pid in outstanding:
5447         del outstanding[pid]
5448         code = code or nc
5449         count += 1
5450     if not opt.verbose:
5451         progress('fsck (%d/%d)\r' % (count, len(extra)))
5452
5453 if not opt.verbose and istty:
5454     log('fsck done.           \n')
5455 sys.exit(code)
5456 #!/usr/bin/env python
5457 import sys, os, struct, getopt, subprocess, signal
5458 from bup import options, ssh
5459 from bup.helpers import *
5460
5461 optspec = """
5462 bup rbackup <hostname> index ...
5463 bup rbackup <hostname> save ...
5464 bup rbackup <hostname> split ...
5465 """
5466 o = options.Options('bup rbackup', optspec, optfunc=getopt.getopt)
5467 (opt, flags, extra) = o.parse(sys.argv[1:])
5468 if len(extra) < 2:
5469     o.fatal('arguments expected')
5470
5471 class SigException(Exception):
5472     def __init__(self, signum):
5473         self.signum = signum
5474         Exception.__init__(self, 'signal %d received' % signum)
5475 def handler(signum, frame):
5476     raise SigException(signum)
5477
5478 signal.signal(signal.SIGTERM, handler)
5479 signal.signal(signal.SIGINT, handler)
5480
5481 sp = None
5482 p = None
5483 ret = 99
5484
5485 try:
5486     hostname = extra[0]
5487     argv = extra[1:]
5488     p = ssh.connect(hostname, 'rbackup-server')
5489
5490     argvs = '\0'.join(['bup'] + argv)
5491     p.stdin.write(struct.pack('!I', len(argvs)) + argvs)
5492     p.stdin.flush()
5493
5494     main_exe = os.environ.get('BUP_MAIN_EXE') or sys.argv[0]
5495     sp = subprocess.Popen([main_exe, 'server'], stdin=p.stdout, stdout=p.stdin)
5496
5497     p.stdin.close()
5498     p.stdout.close()
5499
5500 finally:
5501     while 1:
5502         # if we get a signal while waiting, we have to keep waiting, just
5503         # in case our child doesn't die.
5504         try:
5505             ret = p.wait()
5506             sp.wait()
5507             break
5508         except SigException, e:
5509             log('\nbup rbackup: %s\n' % e)
5510             os.kill(p.pid, e.signum)
5511             ret = 84
5512 sys.exit(ret)
5513 #!/usr/bin/env python
5514 import sys, os, re
5515 from bup import options
5516
5517 optspec = """
5518 bup newliner
5519 """
5520 o = options.Options('bup newliner', optspec)
5521 (opt, flags, extra) = o.parse(sys.argv[1:])
5522
5523 if extra:
5524     o.fatal("no arguments expected")
5525
5526 r = re.compile(r'([\r\n])')
5527 lastlen = 0
5528 all = ''
5529 while 1:
5530     l = r.split(all, 1)
5531     if len(l) <= 1:
5532         try:
5533             b = os.read(sys.stdin.fileno(), 4096)
5534         except KeyboardInterrupt:
5535             break
5536         if not b:
5537             break
5538         all += b
5539     else:
5540         assert(len(l) == 3)
5541         (line, splitchar, all) = l
5542         #splitchar = '\n'
5543         sys.stdout.write('%-*s%s' % (lastlen, line, splitchar))
5544         if splitchar == '\r':
5545             lastlen = len(line)
5546         else:
5547             lastlen = 0
5548         sys.stdout.flush()
5549
5550 if lastlen or all:
5551     sys.stdout.write('%-*s\n' % (lastlen, all))
5552 #!/usr/bin/env python
5553 import sys
5554 from bup import options, git, _hashsplit
5555 from bup.helpers import *
5556
5557
5558 optspec = """
5559 bup margin
5560 """
5561 o = options.Options('bup margin', optspec)
5562 (opt, flags, extra) = o.parse(sys.argv[1:])
5563
5564 if extra:
5565     o.fatal("no arguments expected")
5566
5567 git.check_repo_or_die()
5568 #git.ignore_midx = 1
5569
5570 mi = git.PackIdxList(git.repo('objects/pack'))
5571 last = '\0'*20
5572 longmatch = 0
5573 for i in mi:
5574     if i == last:
5575         continue
5576     #assert(str(i) >= last)
5577     pm = _hashsplit.bitmatch(last, i)
5578     longmatch = max(longmatch, pm)
5579     last = i
5580 print longmatch