]> arthur.barton.de Git - bup.git/blob - main.py
main: when printing help, don't mix stdout/stderr.
[bup.git] / main.py
1 #!/usr/bin/env python
2 import sys, os, subprocess, signal
3
4 argv = sys.argv
5 exe = argv[0]
6 exepath = os.path.split(exe)[0] or '.'
7
8 # fix the PYTHONPATH to include our lib dir
9 libpath = os.path.join(exepath, 'lib')
10 cmdpath = os.path.join(exepath, 'cmd')
11 sys.path[:0] = [libpath]
12 os.environ['PYTHONPATH'] = libpath + ':' + os.environ.get('PYTHONPATH', '')
13 os.environ['BUP_MAIN_EXE'] = os.path.abspath(exe)
14
15 from bup.helpers import *
16
17
18 def columnate(l, prefix):
19     l = l[:]
20     clen = max(len(s) for s in l)
21     ncols = (78 - len(prefix)) / (clen + 2)
22     if ncols <= 1:
23         ncols = 1
24         clen = 0
25     cols = []
26     while len(l) % ncols:
27         l.append('')
28     rows = len(l)/ncols
29     for s in range(0, len(l), rows):
30         cols.append(l[s:s+rows])
31     out = ''
32     for row in zip(*cols):
33         out += prefix + ''.join(('%-*s' % (clen+2, s)) for s in row) + '\n'
34     return out
35
36
37 def usage():
38     log('Usage: bup <command> <options...>\n\n')
39     common = dict(
40         ftp = 'Browse backup sets using an ftp-like client',
41         fsck = 'Check backup sets for damage and add redundancy information',
42         fuse = 'Mount your backup sets as a filesystem',
43         help = 'Print detailed help for the given command',
44         index = 'Create or display the index of files to back up',
45         join = 'Retrieve a file backed up using "bup split"',
46         ls = 'Browse the files in your backup sets',
47         midx = 'Index objects to speed up future backups',
48         save = 'Save files into a backup set (note: run "bup index" first)',
49         split = 'Split a single file into its own backup set',
50     )
51
52     log('Common commands:\n')
53     for cmd,synopsis in sorted(common.items()):
54         log('    %-10s %s\n' % (cmd, synopsis))
55     log('\n')
56     
57     log('Other available commands:\n')
58     cmds = []
59     for c in sorted(os.listdir(cmdpath) + os.listdir(exepath)):
60         if c.startswith('bup-') and c.find('.') < 0:
61             cname = c[4:]
62             if cname not in common:
63                 cmds.append(c[4:])
64     log(columnate(cmds, '    '))
65     log('\n')
66     
67     log("See 'bup help <command>' for more information on " +
68         "a specific command.\n")
69     sys.exit(99)
70
71
72 if len(argv) < 2 or not argv[1] or argv[1][0] == '-':
73     usage()
74
75 subcmd = argv[1]
76
77 def subpath(s):
78     sp = os.path.join(exepath, 'bup-%s' % s)
79     if not os.path.exists(sp):
80         sp = os.path.join(cmdpath, 'bup-%s' % s)
81     return sp
82
83 if not os.path.exists(subpath(subcmd)):
84     log('error: unknown command "%s"\n' % subcmd)
85     usage()
86
87
88 already_fixed = atoi(os.environ.get('BUP_FORCE_TTY'))
89 if subcmd in ['ftp', 'help']:
90     already_fixed = True
91 fix_stdout = not already_fixed and os.isatty(1)
92 fix_stderr = not already_fixed and os.isatty(2)
93
94 def force_tty():
95     if fix_stdout or fix_stderr:
96         amt = (fix_stdout and 1 or 0) + (fix_stderr and 2 or 0)
97         os.environ['BUP_FORCE_TTY'] = str(amt)
98     os.setsid()  # make sure ctrl-c is sent just to us, not to child too
99
100 if fix_stdout or fix_stderr:
101     realf = fix_stderr and 2 or 1
102     drealf = os.dup(realf)  # Popen goes crazy with stdout=2
103     n = subprocess.Popen([subpath('newliner')],
104                          stdin=subprocess.PIPE, stdout=drealf,
105                          close_fds=True, preexec_fn=force_tty)
106     os.close(drealf)
107     outf = fix_stdout and n.stdin.fileno() or None
108     errf = fix_stderr and n.stdin.fileno() or None
109 else:
110     n = None
111     outf = None
112     errf = None
113
114
115 class SigException(Exception):
116     def __init__(self, signum):
117         self.signum = signum
118         Exception.__init__(self, 'signal %d received' % signum)
119 def handler(signum, frame):
120     raise SigException(signum)
121
122 signal.signal(signal.SIGTERM, handler)
123 signal.signal(signal.SIGINT, handler)
124
125 ret = 95
126 p = None
127 try:
128     try:
129         p = subprocess.Popen([subpath(subcmd)] + argv[2:],
130                              stdout=outf, stderr=errf, preexec_fn=force_tty)
131         while 1:
132             # if we get a signal while waiting, we have to keep waiting, just
133             # in case our child doesn't die.
134             try:
135                 ret = p.wait()
136                 break
137             except SigException, e:
138                 log('\nbup: %s\n' % e)
139                 os.kill(p.pid, e.signum)
140                 ret = 94
141     except OSError, e:
142         log('%s: %s\n' % (subpath(subcmd), e))
143         ret = 98
144 finally:
145     if p and p.poll() == None:
146         os.kill(p.pid, signal.SIGTERM)
147         p.wait()
148     if n:
149         n.stdin.close()
150         try:
151             n.wait()
152         except:
153             pass
154 sys.exit(ret)