]> arthur.barton.de Git - bup.git/blob - main.py
Correctly pass along SIGINT to child processes.
[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     for row in zip(*cols):
32         print prefix + ''.join(('%-*s' % (clen+2, s)) for s in row)
33
34
35 def usage():
36     log('Usage: bup <command> <options...>\n\n')
37     common = dict(
38         ftp = 'Browse backup sets using an ftp-like client',
39         fsck = 'Check backup sets for damage and add redundancy information',
40         fuse = 'Mount your backup sets as a filesystem',
41         help = 'Print detailed help for the given command',
42         index = 'Create or display the index of files to back up',
43         join = 'Retrieve a file backed up using "bup split"',
44         ls = 'Browse the files in your backup sets',
45         midx = 'Index objects to speed up future backups',
46         save = 'Save files into a backup set (note: run "bup index" first)',
47         split = 'Split a single file into its own backup set',
48     )
49
50     log('Common commands:\n')
51     for cmd,synopsis in sorted(common.items()):
52         print '    %-10s %s' % (cmd, synopsis)
53     log('\n')
54     
55     log('Other available commands:\n')
56     cmds = []
57     for c in sorted(os.listdir(cmdpath) + os.listdir(exepath)):
58         if c.startswith('bup-') and c.find('.') < 0:
59             cname = c[4:]
60             if cname not in common:
61                 cmds.append(c[4:])
62     columnate(cmds, '    ')
63     log('\n')
64     
65     log("See 'bup help <command>' for more information on " +
66         "a specific command.\n")
67     sys.exit(99)
68
69
70 if len(argv) < 2 or not argv[1] or argv[1][0] == '-':
71     usage()
72
73 subcmd = argv[1]
74
75 def subpath(s):
76     sp = os.path.join(exepath, 'bup-%s' % s)
77     if not os.path.exists(sp):
78         sp = os.path.join(cmdpath, 'bup-%s' % s)
79     return sp
80
81 if not os.path.exists(subpath(subcmd)):
82     log('error: unknown command "%s"\n' % subcmd)
83     usage()
84
85
86 already_fixed = atoi(os.environ.get('BUP_FORCE_TTY'))
87 if subcmd in ['ftp', 'help']:
88     already_fixed = True
89 fix_stdout = not already_fixed and os.isatty(1)
90 fix_stderr = not already_fixed and os.isatty(2)
91
92 def force_tty():
93     if fix_stdout or fix_stderr:
94         amt = (fix_stdout and 1 or 0) + (fix_stderr and 2 or 0)
95         os.environ['BUP_FORCE_TTY'] = str(amt)
96     os.setsid()  # make sure ctrl-c is sent just to us, not to child too
97
98 if fix_stdout or fix_stderr:
99     realf = fix_stderr and 2 or 1
100     n = subprocess.Popen([subpath('newliner')],
101                          stdin=subprocess.PIPE, stdout=os.dup(realf),
102                          close_fds=True, preexec_fn=force_tty)
103     outf = fix_stdout and n.stdin.fileno() or None
104     errf = fix_stderr and n.stdin.fileno() or None
105 else:
106     n = None
107     outf = None
108     errf = None
109
110
111 class SigException(Exception):
112     def __init__(self, signum):
113         self.signum = signum
114         Exception.__init__(self, 'signal %d received' % signum)
115 def handler(signum, frame):
116     raise SigException(signum)
117
118 signal.signal(signal.SIGTERM, handler)
119 signal.signal(signal.SIGINT, handler)
120
121 ret = 95
122 p = None
123 killsig = signal.SIGTERM
124 try:
125     try:
126         p = subprocess.Popen([subpath(subcmd)] + argv[2:],
127                              stdout=outf, stderr=errf, preexec_fn=force_tty)
128         ret = p.wait()
129     except OSError, e:
130         log('%s: %s\n' % (subpath(subcmd), e))
131         ret = 98
132     except SigException, e:
133         log('\nbup: %s\n' % e)
134         killsig = e.signum
135         ret = 94
136 finally:
137     if p and p.poll() == None:
138         os.kill(p.pid, killsig)
139         p.wait()
140     if n:
141         n.stdin.close()
142         try:
143             n.wait()
144         except:
145             pass
146 sys.exit(ret)