fix problem: get unexpected pipename
[re6stnet.git] / re6st / utils.py
1 import argparse, calendar, errno, logging, os, shlex, signal, socket
2 import struct, subprocess, sys, textwrap, threading, time, traceback
3
4 logging_levels = logging.WARNING, logging.INFO, logging.DEBUG, 5
5
6 class FileHandler(logging.FileHandler):
7
8 _reopen = False
9
10 def release(self):
11 try:
12 if self._reopen:
13 self._reopen = False
14 self.close()
15 self._open()
16 finally:
17 self.lock.release()
18 # In the rare case _reopen is set just before the lock was released
19 if self._reopen and self.lock.acquire(0):
20 self.release()
21
22 def async_reopen(self, *_):
23 self._reopen = True
24 if self.lock.acquire(0):
25 self.release()
26
27 def setupLog(log_level, filename=None, **kw):
28 if log_level and filename:
29 makedirs(os.path.dirname(filename))
30 handler = FileHandler(filename)
31 sig = handler.async_reopen
32 else:
33 handler = logging.StreamHandler()
34 sig = signal.SIG_IGN
35 handler.setFormatter(logging.Formatter(
36 '%(asctime)s %(levelname)-9s %(message)s', '%d-%m-%Y %H:%M:%S'))
37 root = logging.getLogger()
38 root.addHandler(handler)
39 signal.signal(signal.SIGUSR1, sig)
40 if log_level:
41 root.setLevel(logging_levels[log_level-1])
42 else:
43 logging.disable(logging.CRITICAL)
44 logging.addLevelName(5, 'TRACE')
45 logging.trace = lambda *args, **kw: logging.log(5, *args, **kw)
46
47 def log_exception():
48 f = traceback.format_exception(*sys.exc_info())
49 logging.error('%s%s', f.pop(), ''.join(f))
50
51
52 class HelpFormatter(argparse.ArgumentDefaultsHelpFormatter):
53
54 def _get_help_string(self, action):
55 return super(HelpFormatter, self)._get_help_string(action) \
56 if action.default else action.help
57
58 def _split_lines(self, text, width):
59 """Preserves new lines in option descriptions"""
60 lines = []
61 for text in text.splitlines():
62 lines += textwrap.wrap(text, width)
63 return lines
64
65 def _fill_text(self, text, width, indent):
66 """Preserves new lines in other descriptions"""
67 kw = dict(width=width, initial_indent=indent, subsequent_indent=indent)
68 return '\n'.join(textwrap.fill(t, **kw) for t in text.splitlines())
69
70 class ArgParser(argparse.ArgumentParser):
71
72 class _HelpFormatter(HelpFormatter):
73
74 def _format_actions_usage(self, actions, groups):
75 r = HelpFormatter._format_actions_usage(self, actions, groups)
76 if actions and actions[0].option_strings:
77 r = '[@OPTIONS_FILE] ' + r
78 return r
79
80 _ca_help = "Certificate authority (CA) file in .pem format." \
81 " Serial number defines the prefix of the network."
82
83 def convert_arg_line_to_args(self, arg_line):
84 if arg_line.split('#', 1)[0].rstrip():
85 if arg_line.startswith('@'):
86 yield arg_line
87 return
88 arg_line = shlex.split(arg_line)
89 arg = '--' + arg_line.pop(0)
90 yield arg[arg not in self._option_string_actions:]
91 for arg in arg_line:
92 yield arg
93
94 def __init__(self, **kw):
95 super(ArgParser, self).__init__(formatter_class=self._HelpFormatter,
96 epilog="""Options can be read from a file. For example:
97 $ cat OPTIONS_FILE
98 ca /etc/re6stnet/ca.crt""", **kw)
99
100
101 class Popen(subprocess.Popen):
102
103 def stop(self):
104 self.terminate()
105 t = threading.Timer(5, self.kill)
106 t.start()
107 r = self.wait()
108 t.cancel()
109 return r
110
111
112 def makedirs(path):
113 try:
114 os.makedirs(path)
115 except OSError, e:
116 if e.errno != errno.EEXIST:
117 raise
118
119 def binFromIp(ip):
120 ip1, ip2 = struct.unpack('>QQ', socket.inet_pton(socket.AF_INET6, ip))
121 return bin(ip1)[2:].rjust(64, '0') + bin(ip2)[2:].rjust(64, '0')
122
123
124 def ipFromBin(ip, suffix=''):
125 suffix_len = 128 - len(ip)
126 if suffix_len > 0:
127 ip += suffix.rjust(suffix_len, '0')
128 elif suffix_len:
129 sys.exit("Prefix exceeds 128 bits")
130 return socket.inet_ntop(socket.AF_INET6,
131 struct.pack('>QQ', int(ip[:64], 2), int(ip[64:], 2)))
132
133 def networkFromCa(ca):
134 return bin(ca.get_serial_number())[3:]
135
136 def subnetFromCert(cert):
137 return cert.get_subject().CN
138
139 def notAfter(cert):
140 return calendar.timegm(time.strptime(cert.get_notAfter(),'%Y%m%d%H%M%SZ'))
141
142 def dump_address(address):
143 return ';'.join(map(','.join, address))
144
145 def parse_address(address_list):
146 for address in address_list.split(';'):
147 try:
148 ip, port, proto = address.split(',')
149 yield ip, str(port), proto
150 except ValueError, e:
151 logging.warning("Failed to parse node address %r (%s)",
152 address, e)
153
154 def binFromSubnet(subnet):
155 p, l = subnet.split('/')
156 return bin(int(p))[2:].rjust(int(l), '0')
157
158 def decrypt(key_path, data):
159 p = subprocess.Popen(
160 ('openssl', 'rsautl', '-decrypt', '-inkey', key_path),
161 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
162 out, err = p.communicate(data)
163 if p.returncode:
164 raise subprocess.CalledProcessError(p.returncode, 'openssl', err)
165 return out
166
167 def encrypt(cert, data):
168 r, w = os.pipe()
169 try:
170 threading.Thread(target=os.write, args=(w, cert)).start()
171 p = subprocess.Popen(('openssl', 'rsautl', '-encrypt', '-certin',
172 '-inkey', '/proc/self/fd/%u' % r),
173 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
174 out, err = p.communicate(data)
175 finally:
176 os.close(r)
177 os.close(w)
178 if p.returncode:
179 raise subprocess.CalledProcessError(p.returncode, 'openssl', err)
180 return out
181
182 def get_pipename(pipe_id):
183 if pipe_id is not None:
184 path = '/proc/%u' % os.getpid()
185 with open(os.path.join(path, 'winpid'), 'r') as f:
186 winpid = f.readline()
187 r = os.path.realpath('%s/fd/%s' % (path, pipe_id)).split('/')
188 r[2] = winpid.strip()
189 return '/'.join(r)