Statistics
| Branch: | Tag: | Revision:

root / daemons / import-export @ 7e3c1da6

History | View | Annotate | Download (21.1 kB)

1 2d76b580 Michael Hanselmann
#!/usr/bin/python
2 2d76b580 Michael Hanselmann
#
3 2d76b580 Michael Hanselmann
4 2d76b580 Michael Hanselmann
# Copyright (C) 2010 Google Inc.
5 2d76b580 Michael Hanselmann
#
6 2d76b580 Michael Hanselmann
# This program is free software; you can redistribute it and/or modify
7 2d76b580 Michael Hanselmann
# it under the terms of the GNU General Public License as published by
8 2d76b580 Michael Hanselmann
# the Free Software Foundation; either version 2 of the License, or
9 2d76b580 Michael Hanselmann
# (at your option) any later version.
10 2d76b580 Michael Hanselmann
#
11 2d76b580 Michael Hanselmann
# This program is distributed in the hope that it will be useful, but
12 2d76b580 Michael Hanselmann
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 2d76b580 Michael Hanselmann
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 2d76b580 Michael Hanselmann
# General Public License for more details.
15 2d76b580 Michael Hanselmann
#
16 2d76b580 Michael Hanselmann
# You should have received a copy of the GNU General Public License
17 2d76b580 Michael Hanselmann
# along with this program; if not, write to the Free Software
18 2d76b580 Michael Hanselmann
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 2d76b580 Michael Hanselmann
# 02110-1301, USA.
20 2d76b580 Michael Hanselmann
21 2d76b580 Michael Hanselmann
22 2d76b580 Michael Hanselmann
"""Import/export daemon.
23 2d76b580 Michael Hanselmann
24 2d76b580 Michael Hanselmann
"""
25 2d76b580 Michael Hanselmann
26 2d76b580 Michael Hanselmann
# pylint: disable-msg=C0103
27 2d76b580 Michael Hanselmann
# C0103: Invalid name import-export
28 2d76b580 Michael Hanselmann
29 2d76b580 Michael Hanselmann
import errno
30 2d76b580 Michael Hanselmann
import logging
31 2d76b580 Michael Hanselmann
import optparse
32 2d76b580 Michael Hanselmann
import os
33 2d76b580 Michael Hanselmann
import re
34 2d76b580 Michael Hanselmann
import select
35 2d76b580 Michael Hanselmann
import signal
36 2d76b580 Michael Hanselmann
import socket
37 2d76b580 Michael Hanselmann
import subprocess
38 2d76b580 Michael Hanselmann
import sys
39 2d76b580 Michael Hanselmann
import time
40 2d76b580 Michael Hanselmann
from cStringIO import StringIO
41 2d76b580 Michael Hanselmann
42 2d76b580 Michael Hanselmann
from ganeti import constants
43 2d76b580 Michael Hanselmann
from ganeti import cli
44 2d76b580 Michael Hanselmann
from ganeti import utils
45 2d76b580 Michael Hanselmann
from ganeti import serializer
46 2d76b580 Michael Hanselmann
from ganeti import objects
47 2d76b580 Michael Hanselmann
from ganeti import locking
48 2d76b580 Michael Hanselmann
49 2d76b580 Michael Hanselmann
50 2d76b580 Michael Hanselmann
#: Used to recognize point at which socat(1) starts to listen on its socket.
51 2d76b580 Michael Hanselmann
#: The local address is required for the remote peer to connect (in particular
52 2d76b580 Michael Hanselmann
#: the port number).
53 2d76b580 Michael Hanselmann
LISTENING_RE = re.compile(r"^listening on\s+"
54 2d76b580 Michael Hanselmann
                          r"AF=(?P<family>\d+)\s+"
55 2d76b580 Michael Hanselmann
                          r"(?P<address>.+):(?P<port>\d+)$", re.I)
56 2d76b580 Michael Hanselmann
57 2d76b580 Michael Hanselmann
#: Used to recognize point at which socat(1) is sending data over the wire
58 2d76b580 Michael Hanselmann
TRANSFER_LOOP_RE = re.compile(r"^starting data transfer loop with FDs\s+.*$",
59 2d76b580 Michael Hanselmann
                              re.I)
60 2d76b580 Michael Hanselmann
61 2d76b580 Michael Hanselmann
SOCAT_LOG_DEBUG = "D"
62 2d76b580 Michael Hanselmann
SOCAT_LOG_INFO = "I"
63 2d76b580 Michael Hanselmann
SOCAT_LOG_NOTICE = "N"
64 2d76b580 Michael Hanselmann
SOCAT_LOG_WARNING = "W"
65 2d76b580 Michael Hanselmann
SOCAT_LOG_ERROR = "E"
66 2d76b580 Michael Hanselmann
SOCAT_LOG_FATAL = "F"
67 2d76b580 Michael Hanselmann
68 2d76b580 Michael Hanselmann
SOCAT_LOG_IGNORE = frozenset([
69 2d76b580 Michael Hanselmann
  SOCAT_LOG_DEBUG,
70 2d76b580 Michael Hanselmann
  SOCAT_LOG_INFO,
71 2d76b580 Michael Hanselmann
  SOCAT_LOG_NOTICE,
72 2d76b580 Michael Hanselmann
  ])
73 2d76b580 Michael Hanselmann
74 2d76b580 Michael Hanselmann
#: Socat buffer size: at most this many bytes are transferred per step
75 2d76b580 Michael Hanselmann
SOCAT_BUFSIZE = 1024 * 1024
76 2d76b580 Michael Hanselmann
77 2d76b580 Michael Hanselmann
#: How many lines to keep in the status file
78 2d76b580 Michael Hanselmann
MAX_RECENT_OUTPUT_LINES = 20
79 2d76b580 Michael Hanselmann
80 2d76b580 Michael Hanselmann
#: Don't update status file more than once every 5 seconds (unless forced)
81 2d76b580 Michael Hanselmann
MIN_UPDATE_INTERVAL = 5.0
82 2d76b580 Michael Hanselmann
83 2d76b580 Michael Hanselmann
#: Give child process up to 5 seconds to exit after sending a signal
84 2d76b580 Michael Hanselmann
CHILD_LINGER_TIMEOUT = 5.0
85 2d76b580 Michael Hanselmann
86 043f2292 Michael Hanselmann
#: How long to wait for a connection to be established
87 043f2292 Michael Hanselmann
DEFAULT_CONNECT_TIMEOUT = 60
88 043f2292 Michael Hanselmann
89 2d76b580 Michael Hanselmann
# Common options for socat
90 2d76b580 Michael Hanselmann
SOCAT_TCP_OPTS = ["keepalive", "keepidle=60", "keepintvl=10", "keepcnt=5"]
91 2d76b580 Michael Hanselmann
SOCAT_OPENSSL_OPTS = ["verify=1", "cipher=HIGH", "method=TLSv1"]
92 2d76b580 Michael Hanselmann
93 2d76b580 Michael Hanselmann
94 2d76b580 Michael Hanselmann
# Global variable for options
95 2d76b580 Michael Hanselmann
options = None
96 2d76b580 Michael Hanselmann
97 2d76b580 Michael Hanselmann
98 2d76b580 Michael Hanselmann
class Error(Exception):
99 2d76b580 Michael Hanselmann
  """Generic exception"""
100 2d76b580 Michael Hanselmann
101 2d76b580 Michael Hanselmann
102 2d76b580 Michael Hanselmann
def SetupLogging():
103 2d76b580 Michael Hanselmann
  """Configures the logging module.
104 2d76b580 Michael Hanselmann
105 2d76b580 Michael Hanselmann
  """
106 2d76b580 Michael Hanselmann
  formatter = logging.Formatter("%(asctime)s: %(message)s")
107 2d76b580 Michael Hanselmann
108 2d76b580 Michael Hanselmann
  stderr_handler = logging.StreamHandler()
109 2d76b580 Michael Hanselmann
  stderr_handler.setFormatter(formatter)
110 2d76b580 Michael Hanselmann
  stderr_handler.setLevel(logging.NOTSET)
111 2d76b580 Michael Hanselmann
112 2d76b580 Michael Hanselmann
  root_logger = logging.getLogger("")
113 2d76b580 Michael Hanselmann
  root_logger.addHandler(stderr_handler)
114 2d76b580 Michael Hanselmann
115 2d76b580 Michael Hanselmann
  if options.debug:
116 2d76b580 Michael Hanselmann
    root_logger.setLevel(logging.NOTSET)
117 2d76b580 Michael Hanselmann
  elif options.verbose:
118 2d76b580 Michael Hanselmann
    root_logger.setLevel(logging.INFO)
119 2d76b580 Michael Hanselmann
  else:
120 2d76b580 Michael Hanselmann
    root_logger.setLevel(logging.ERROR)
121 2d76b580 Michael Hanselmann
122 2d76b580 Michael Hanselmann
  # Create special logger for child process output
123 2d76b580 Michael Hanselmann
  child_logger = logging.Logger("child output")
124 2d76b580 Michael Hanselmann
  child_logger.addHandler(stderr_handler)
125 2d76b580 Michael Hanselmann
  child_logger.setLevel(logging.NOTSET)
126 2d76b580 Michael Hanselmann
127 2d76b580 Michael Hanselmann
  return child_logger
128 2d76b580 Michael Hanselmann
129 2d76b580 Michael Hanselmann
130 2d76b580 Michael Hanselmann
def _VerifyListening(family, address, port):
131 2d76b580 Michael Hanselmann
  """Verify address given as listening address by socat.
132 2d76b580 Michael Hanselmann
133 2d76b580 Michael Hanselmann
  """
134 2d76b580 Michael Hanselmann
  # TODO: Implement IPv6 support
135 2d76b580 Michael Hanselmann
  if family != socket.AF_INET:
136 2d76b580 Michael Hanselmann
    raise Error("Address family %r not supported" % family)
137 2d76b580 Michael Hanselmann
138 2d76b580 Michael Hanselmann
  try:
139 2d76b580 Michael Hanselmann
    packed_address = socket.inet_pton(family, address)
140 2d76b580 Michael Hanselmann
  except socket.error:
141 2d76b580 Michael Hanselmann
    raise Error("Invalid address %r for family %s" % (address, family))
142 2d76b580 Michael Hanselmann
143 2d76b580 Michael Hanselmann
  return (socket.inet_ntop(family, packed_address), port)
144 2d76b580 Michael Hanselmann
145 2d76b580 Michael Hanselmann
146 2d76b580 Michael Hanselmann
class StatusFile:
147 2d76b580 Michael Hanselmann
  """Status file manager.
148 2d76b580 Michael Hanselmann
149 2d76b580 Michael Hanselmann
  """
150 2d76b580 Michael Hanselmann
  def __init__(self, path):
151 2d76b580 Michael Hanselmann
    """Initializes class.
152 2d76b580 Michael Hanselmann
153 2d76b580 Michael Hanselmann
    """
154 2d76b580 Michael Hanselmann
    self._path = path
155 2d76b580 Michael Hanselmann
    self._data = objects.ImportExportStatus(ctime=time.time(),
156 2d76b580 Michael Hanselmann
                                            mtime=None,
157 2d76b580 Michael Hanselmann
                                            recent_output=[])
158 2d76b580 Michael Hanselmann
159 2d76b580 Michael Hanselmann
  def AddRecentOutput(self, line):
160 2d76b580 Michael Hanselmann
    """Adds a new line of recent output.
161 2d76b580 Michael Hanselmann
162 2d76b580 Michael Hanselmann
    """
163 2d76b580 Michael Hanselmann
    self._data.recent_output.append(line)
164 2d76b580 Michael Hanselmann
165 2d76b580 Michael Hanselmann
    # Remove old lines
166 2d76b580 Michael Hanselmann
    del self._data.recent_output[:-MAX_RECENT_OUTPUT_LINES]
167 2d76b580 Michael Hanselmann
168 2d76b580 Michael Hanselmann
  def SetListenPort(self, port):
169 2d76b580 Michael Hanselmann
    """Sets the port the daemon is listening on.
170 2d76b580 Michael Hanselmann
171 2d76b580 Michael Hanselmann
    @type port: int
172 2d76b580 Michael Hanselmann
    @param port: TCP/UDP port
173 2d76b580 Michael Hanselmann
174 2d76b580 Michael Hanselmann
    """
175 2d76b580 Michael Hanselmann
    assert isinstance(port, (int, long)) and 0 < port < 2**16
176 2d76b580 Michael Hanselmann
    self._data.listen_port = port
177 2d76b580 Michael Hanselmann
178 2d76b580 Michael Hanselmann
  def GetListenPort(self):
179 2d76b580 Michael Hanselmann
    """Returns the port the daemon is listening on.
180 2d76b580 Michael Hanselmann
181 2d76b580 Michael Hanselmann
    """
182 2d76b580 Michael Hanselmann
    return self._data.listen_port
183 2d76b580 Michael Hanselmann
184 2d76b580 Michael Hanselmann
  def SetConnected(self):
185 2d76b580 Michael Hanselmann
    """Sets the connected flag.
186 2d76b580 Michael Hanselmann
187 2d76b580 Michael Hanselmann
    """
188 2d76b580 Michael Hanselmann
    self._data.connected = True
189 2d76b580 Michael Hanselmann
190 043f2292 Michael Hanselmann
  def GetConnected(self):
191 043f2292 Michael Hanselmann
    """Determines whether the daemon is connected.
192 043f2292 Michael Hanselmann
193 043f2292 Michael Hanselmann
    """
194 043f2292 Michael Hanselmann
    return self._data.connected
195 043f2292 Michael Hanselmann
196 2d76b580 Michael Hanselmann
  def SetExitStatus(self, exit_status, error_message):
197 2d76b580 Michael Hanselmann
    """Sets the exit status and an error message.
198 2d76b580 Michael Hanselmann
199 2d76b580 Michael Hanselmann
    """
200 2d76b580 Michael Hanselmann
    # Require error message when status isn't 0
201 2d76b580 Michael Hanselmann
    assert exit_status == 0 or error_message
202 2d76b580 Michael Hanselmann
203 2d76b580 Michael Hanselmann
    self._data.exit_status = exit_status
204 2d76b580 Michael Hanselmann
    self._data.error_message = error_message
205 2d76b580 Michael Hanselmann
206 2d76b580 Michael Hanselmann
  def ExitStatusIsSuccess(self):
207 2d76b580 Michael Hanselmann
    """Returns whether the exit status means "success".
208 2d76b580 Michael Hanselmann
209 2d76b580 Michael Hanselmann
    """
210 2d76b580 Michael Hanselmann
    return not bool(self._data.error_message)
211 2d76b580 Michael Hanselmann
212 2d76b580 Michael Hanselmann
  def Update(self, force):
213 2d76b580 Michael Hanselmann
    """Updates the status file.
214 2d76b580 Michael Hanselmann
215 2d76b580 Michael Hanselmann
    @type force: bool
216 2d76b580 Michael Hanselmann
    @param force: Write status file in any case, not only when minimum interval
217 2d76b580 Michael Hanselmann
                  is expired
218 2d76b580 Michael Hanselmann
219 2d76b580 Michael Hanselmann
    """
220 2d76b580 Michael Hanselmann
    if not (force or
221 2d76b580 Michael Hanselmann
            self._data.mtime is None or
222 2d76b580 Michael Hanselmann
            time.time() > (self._data.mtime + MIN_UPDATE_INTERVAL)):
223 2d76b580 Michael Hanselmann
      return
224 2d76b580 Michael Hanselmann
225 2d76b580 Michael Hanselmann
    logging.debug("Updating status file %s", self._path)
226 2d76b580 Michael Hanselmann
227 2d76b580 Michael Hanselmann
    self._data.mtime = time.time()
228 2d76b580 Michael Hanselmann
    utils.WriteFile(self._path,
229 2d76b580 Michael Hanselmann
                    data=serializer.DumpJson(self._data.ToDict(), indent=True),
230 2d76b580 Michael Hanselmann
                    mode=0400)
231 2d76b580 Michael Hanselmann
232 2d76b580 Michael Hanselmann
233 2d76b580 Michael Hanselmann
def _ProcessSocatOutput(status_file, level, msg):
234 2d76b580 Michael Hanselmann
  """Interprets socat log output.
235 2d76b580 Michael Hanselmann
236 2d76b580 Michael Hanselmann
  """
237 2d76b580 Michael Hanselmann
  if level == SOCAT_LOG_NOTICE:
238 2d76b580 Michael Hanselmann
    if status_file.GetListenPort() is None:
239 2d76b580 Michael Hanselmann
      # TODO: Maybe implement timeout to not listen forever
240 2d76b580 Michael Hanselmann
      m = LISTENING_RE.match(msg)
241 2d76b580 Michael Hanselmann
      if m:
242 2d76b580 Michael Hanselmann
        (_, port) = _VerifyListening(int(m.group("family")), m.group("address"),
243 2d76b580 Michael Hanselmann
                                     int(m.group("port")))
244 2d76b580 Michael Hanselmann
245 2d76b580 Michael Hanselmann
        status_file.SetListenPort(port)
246 2d76b580 Michael Hanselmann
        return True
247 2d76b580 Michael Hanselmann
248 043f2292 Michael Hanselmann
    if not status_file.GetConnected():
249 043f2292 Michael Hanselmann
      m = TRANSFER_LOOP_RE.match(msg)
250 043f2292 Michael Hanselmann
      if m:
251 043f2292 Michael Hanselmann
        status_file.SetConnected()
252 043f2292 Michael Hanselmann
        return True
253 2d76b580 Michael Hanselmann
254 2d76b580 Michael Hanselmann
  return False
255 2d76b580 Michael Hanselmann
256 2d76b580 Michael Hanselmann
257 2d76b580 Michael Hanselmann
def ProcessOutput(line, status_file, logger, socat):
258 2d76b580 Michael Hanselmann
  """Takes care of child process output.
259 2d76b580 Michael Hanselmann
260 2d76b580 Michael Hanselmann
  @param status_file: Status file manager
261 2d76b580 Michael Hanselmann
  @param logger: Child output logger
262 2d76b580 Michael Hanselmann
  @type socat: bool
263 2d76b580 Michael Hanselmann
  @param socat: Whether it's a socat output line
264 2d76b580 Michael Hanselmann
  @type line: string
265 2d76b580 Michael Hanselmann
  @param line: Child output line
266 2d76b580 Michael Hanselmann
267 2d76b580 Michael Hanselmann
  """
268 2d76b580 Michael Hanselmann
  force_update = False
269 2d76b580 Michael Hanselmann
  forward_line = line
270 2d76b580 Michael Hanselmann
271 2d76b580 Michael Hanselmann
  if socat:
272 2d76b580 Michael Hanselmann
    level = None
273 2d76b580 Michael Hanselmann
    parts = line.split(None, 4)
274 2d76b580 Michael Hanselmann
275 2d76b580 Michael Hanselmann
    if len(parts) == 5:
276 2d76b580 Michael Hanselmann
      (_, _, _, level, msg) = parts
277 2d76b580 Michael Hanselmann
278 2d76b580 Michael Hanselmann
      force_update = _ProcessSocatOutput(status_file, level, msg)
279 2d76b580 Michael Hanselmann
280 2d76b580 Michael Hanselmann
      if options.debug or (level and level not in SOCAT_LOG_IGNORE):
281 2d76b580 Michael Hanselmann
        forward_line = "socat: %s %s" % (level, msg)
282 2d76b580 Michael Hanselmann
      else:
283 2d76b580 Michael Hanselmann
        forward_line = None
284 2d76b580 Michael Hanselmann
    else:
285 2d76b580 Michael Hanselmann
      forward_line = "socat: %s" % line
286 2d76b580 Michael Hanselmann
287 2d76b580 Michael Hanselmann
  if forward_line:
288 2d76b580 Michael Hanselmann
    logger.info(forward_line)
289 2d76b580 Michael Hanselmann
    status_file.AddRecentOutput(forward_line)
290 2d76b580 Michael Hanselmann
291 2d76b580 Michael Hanselmann
  status_file.Update(force_update)
292 2d76b580 Michael Hanselmann
293 2d76b580 Michael Hanselmann
294 2d76b580 Michael Hanselmann
def GetBashCommand(cmd):
295 2d76b580 Michael Hanselmann
  """Prepares a command to be run in Bash.
296 2d76b580 Michael Hanselmann
297 2d76b580 Michael Hanselmann
  """
298 2d76b580 Michael Hanselmann
  return ["bash", "-o", "errexit", "-o", "pipefail", "-c", cmd]
299 2d76b580 Michael Hanselmann
300 2d76b580 Michael Hanselmann
301 2d76b580 Michael Hanselmann
def GetSocatCommand(mode):
302 2d76b580 Michael Hanselmann
  """Returns the socat command.
303 2d76b580 Michael Hanselmann
304 2d76b580 Michael Hanselmann
  """
305 2d76b580 Michael Hanselmann
  common_addr_opts = SOCAT_TCP_OPTS + SOCAT_OPENSSL_OPTS + [
306 2d76b580 Michael Hanselmann
    "key=%s" % options.key,
307 2d76b580 Michael Hanselmann
    "cert=%s" % options.cert,
308 2d76b580 Michael Hanselmann
    "cafile=%s" % options.ca,
309 2d76b580 Michael Hanselmann
    ]
310 2d76b580 Michael Hanselmann
311 2d76b580 Michael Hanselmann
  if options.bind is not None:
312 2d76b580 Michael Hanselmann
    common_addr_opts.append("bind=%s" % options.bind)
313 2d76b580 Michael Hanselmann
314 2d76b580 Michael Hanselmann
  if mode == constants.IEM_IMPORT:
315 2d76b580 Michael Hanselmann
    if options.port is None:
316 2d76b580 Michael Hanselmann
      port = 0
317 2d76b580 Michael Hanselmann
    else:
318 2d76b580 Michael Hanselmann
      port = options.port
319 2d76b580 Michael Hanselmann
320 2d76b580 Michael Hanselmann
    addr1 = [
321 2d76b580 Michael Hanselmann
      "OPENSSL-LISTEN:%s" % port,
322 2d76b580 Michael Hanselmann
      "reuseaddr",
323 043f2292 Michael Hanselmann
324 043f2292 Michael Hanselmann
      # Retry to listen if connection wasn't established successfully, up to
325 043f2292 Michael Hanselmann
      # 100 times a second. Note that this still leaves room for DoS attacks.
326 043f2292 Michael Hanselmann
      "forever",
327 043f2292 Michael Hanselmann
      "intervall=0.01",
328 2d76b580 Michael Hanselmann
      ] + common_addr_opts
329 2d76b580 Michael Hanselmann
    addr2 = ["stdout"]
330 2d76b580 Michael Hanselmann
331 2d76b580 Michael Hanselmann
  elif mode == constants.IEM_EXPORT:
332 2d76b580 Michael Hanselmann
    addr1 = ["stdin"]
333 2d76b580 Michael Hanselmann
    addr2 = [
334 2d76b580 Michael Hanselmann
      "OPENSSL:%s:%s" % (options.host, options.port),
335 043f2292 Michael Hanselmann
336 043f2292 Michael Hanselmann
      # How long to wait per connection attempt
337 043f2292 Michael Hanselmann
      "connect-timeout=%s" % options.connect_timeout,
338 043f2292 Michael Hanselmann
339 043f2292 Michael Hanselmann
      # Retry a few times before giving up to connect (once per second)
340 043f2292 Michael Hanselmann
      "retry=%s" % options.connect_retries,
341 043f2292 Michael Hanselmann
      "intervall=1",
342 2d76b580 Michael Hanselmann
      ] + common_addr_opts
343 2d76b580 Michael Hanselmann
344 2d76b580 Michael Hanselmann
  else:
345 2d76b580 Michael Hanselmann
    raise Error("Invalid mode")
346 2d76b580 Michael Hanselmann
347 2d76b580 Michael Hanselmann
  for i in [addr1, addr2]:
348 2d76b580 Michael Hanselmann
    for value in i:
349 2d76b580 Michael Hanselmann
      if "," in value:
350 2d76b580 Michael Hanselmann
        raise Error("Comma not allowed in socat option value: %r" % value)
351 2d76b580 Michael Hanselmann
352 2d76b580 Michael Hanselmann
  return [
353 2d76b580 Michael Hanselmann
    constants.SOCAT_PATH,
354 2d76b580 Michael Hanselmann
355 2d76b580 Michael Hanselmann
    # Log to stderr
356 2d76b580 Michael Hanselmann
    "-ls",
357 2d76b580 Michael Hanselmann
358 2d76b580 Michael Hanselmann
    # Log level
359 2d76b580 Michael Hanselmann
    "-d", "-d",
360 2d76b580 Michael Hanselmann
361 2d76b580 Michael Hanselmann
    # Buffer size
362 2d76b580 Michael Hanselmann
    "-b%s" % SOCAT_BUFSIZE,
363 2d76b580 Michael Hanselmann
364 2d76b580 Michael Hanselmann
    # Unidirectional mode, the first address is only used for reading, and the
365 2d76b580 Michael Hanselmann
    # second address is only used for writing
366 2d76b580 Michael Hanselmann
    "-u",
367 2d76b580 Michael Hanselmann
368 2d76b580 Michael Hanselmann
    ",".join(addr1), ",".join(addr2)
369 2d76b580 Michael Hanselmann
    ]
370 2d76b580 Michael Hanselmann
371 2d76b580 Michael Hanselmann
372 2d76b580 Michael Hanselmann
def GetTransportCommand(mode, socat_stderr_fd):
373 2d76b580 Michael Hanselmann
  """Returns the command for the transport part of the daemon.
374 2d76b580 Michael Hanselmann
375 2d76b580 Michael Hanselmann
  @param mode: Daemon mode (import or export)
376 2d76b580 Michael Hanselmann
  @type socat_stderr_fd: int
377 2d76b580 Michael Hanselmann
  @param socat_stderr_fd: File descriptor socat should write its stderr to
378 2d76b580 Michael Hanselmann
379 2d76b580 Michael Hanselmann
  """
380 2d76b580 Michael Hanselmann
  socat_cmd = ("%s 2>&%d" %
381 2d76b580 Michael Hanselmann
               (utils.ShellQuoteArgs(GetSocatCommand(mode)),
382 2d76b580 Michael Hanselmann
                socat_stderr_fd))
383 2d76b580 Michael Hanselmann
384 7e3c1da6 Michael Hanselmann
  compr = options.compress
385 7e3c1da6 Michael Hanselmann
386 7e3c1da6 Michael Hanselmann
  assert compr in constants.IEC_ALL
387 2d76b580 Michael Hanselmann
388 2d76b580 Michael Hanselmann
  if mode == constants.IEM_IMPORT:
389 7e3c1da6 Michael Hanselmann
    if compr == constants.IEC_GZIP:
390 7e3c1da6 Michael Hanselmann
      transport_cmd = "%s | gunzip -c" % socat_cmd
391 7e3c1da6 Michael Hanselmann
    else:
392 7e3c1da6 Michael Hanselmann
      transport_cmd = socat_cmd
393 2d76b580 Michael Hanselmann
  elif mode == constants.IEM_EXPORT:
394 7e3c1da6 Michael Hanselmann
    if compr == constants.IEC_GZIP:
395 7e3c1da6 Michael Hanselmann
      transport_cmd = "gzip -c | %s" % socat_cmd
396 7e3c1da6 Michael Hanselmann
    else:
397 7e3c1da6 Michael Hanselmann
      transport_cmd = socat_cmd
398 2d76b580 Michael Hanselmann
  else:
399 2d76b580 Michael Hanselmann
    raise Error("Invalid mode")
400 2d76b580 Michael Hanselmann
401 2d76b580 Michael Hanselmann
  # TODO: Use "dd" to measure processed data (allows to give an ETA)
402 2d76b580 Michael Hanselmann
403 2d76b580 Michael Hanselmann
  # TODO: Run transport as separate user
404 2d76b580 Michael Hanselmann
  # The transport uses its own shell to simplify running it as a separate user
405 2d76b580 Michael Hanselmann
  # in the future.
406 2d76b580 Michael Hanselmann
  return GetBashCommand(transport_cmd)
407 2d76b580 Michael Hanselmann
408 2d76b580 Michael Hanselmann
409 2d76b580 Michael Hanselmann
def GetCommand(mode, socat_stderr_fd):
410 2d76b580 Michael Hanselmann
  """Returns the complete child process command.
411 2d76b580 Michael Hanselmann
412 2d76b580 Michael Hanselmann
  """
413 2d76b580 Michael Hanselmann
  buf = StringIO()
414 2d76b580 Michael Hanselmann
415 2d76b580 Michael Hanselmann
  if options.cmd_prefix:
416 2d76b580 Michael Hanselmann
    buf.write(options.cmd_prefix)
417 2d76b580 Michael Hanselmann
    buf.write(" ")
418 2d76b580 Michael Hanselmann
419 2d76b580 Michael Hanselmann
  buf.write(utils.ShellQuoteArgs(GetTransportCommand(mode, socat_stderr_fd)))
420 2d76b580 Michael Hanselmann
421 2d76b580 Michael Hanselmann
  if options.cmd_suffix:
422 2d76b580 Michael Hanselmann
    buf.write(" ")
423 2d76b580 Michael Hanselmann
    buf.write(options.cmd_suffix)
424 2d76b580 Michael Hanselmann
425 2d76b580 Michael Hanselmann
  return GetBashCommand(buf.getvalue())
426 2d76b580 Michael Hanselmann
427 2d76b580 Michael Hanselmann
428 29da446a Michael Hanselmann
def ProcessChildIO(child, socat_stderr_read_fd, status_file, child_logger,
429 043f2292 Michael Hanselmann
                   signal_notify, signal_handler, mode):
430 2d76b580 Michael Hanselmann
  """Handles the child processes' output.
431 2d76b580 Michael Hanselmann
432 2d76b580 Michael Hanselmann
  """
433 29da446a Michael Hanselmann
  assert not (signal_handler.signum - set([signal.SIGTERM, signal.SIGINT])), \
434 29da446a Michael Hanselmann
         "Other signals are not handled in this function"
435 29da446a Michael Hanselmann
436 29da446a Michael Hanselmann
  # Buffer size 0 is important, otherwise .read() with a specified length
437 29da446a Michael Hanselmann
  # might buffer data while poll(2) won't mark its file descriptor as
438 29da446a Michael Hanselmann
  # readable again.
439 29da446a Michael Hanselmann
  socat_stderr_read = os.fdopen(socat_stderr_read_fd, "r", 0)
440 2d76b580 Michael Hanselmann
441 2d76b580 Michael Hanselmann
  script_stderr_lines = utils.LineSplitter(ProcessOutput, status_file,
442 2d76b580 Michael Hanselmann
                                           child_logger, False)
443 2d76b580 Michael Hanselmann
  try:
444 2d76b580 Michael Hanselmann
    socat_stderr_lines = utils.LineSplitter(ProcessOutput, status_file,
445 2d76b580 Michael Hanselmann
                                            child_logger, True)
446 2d76b580 Michael Hanselmann
    try:
447 2d76b580 Michael Hanselmann
      fdmap = {
448 2d76b580 Michael Hanselmann
        child.stderr.fileno(): (child.stderr, script_stderr_lines),
449 2d76b580 Michael Hanselmann
        socat_stderr_read.fileno(): (socat_stderr_read, socat_stderr_lines),
450 2d76b580 Michael Hanselmann
        signal_notify.fileno(): (signal_notify, None),
451 2d76b580 Michael Hanselmann
        }
452 2d76b580 Michael Hanselmann
453 29da446a Michael Hanselmann
      poller = select.poll()
454 2d76b580 Michael Hanselmann
      for fd in fdmap:
455 2d76b580 Michael Hanselmann
        utils.SetNonblockFlag(fd, True)
456 2d76b580 Michael Hanselmann
        poller.register(fd, select.POLLIN)
457 2d76b580 Michael Hanselmann
458 043f2292 Michael Hanselmann
      if options.connect_timeout and mode == constants.IEM_IMPORT:
459 043f2292 Michael Hanselmann
        listen_timeout = locking.RunningTimeout(options.connect_timeout, True)
460 043f2292 Michael Hanselmann
      else:
461 043f2292 Michael Hanselmann
        listen_timeout = None
462 043f2292 Michael Hanselmann
463 043f2292 Michael Hanselmann
      exit_timeout = None
464 043f2292 Michael Hanselmann
465 2d76b580 Michael Hanselmann
      while True:
466 2d76b580 Michael Hanselmann
        # Break out of loop if only signal notify FD is left
467 2d76b580 Michael Hanselmann
        if len(fdmap) == 1 and signal_notify.fileno() in fdmap:
468 2d76b580 Michael Hanselmann
          break
469 2d76b580 Michael Hanselmann
470 043f2292 Michael Hanselmann
        timeout = None
471 043f2292 Michael Hanselmann
472 043f2292 Michael Hanselmann
        if listen_timeout and not exit_timeout:
473 043f2292 Michael Hanselmann
          if status_file.GetConnected():
474 043f2292 Michael Hanselmann
            listen_timeout = None
475 043f2292 Michael Hanselmann
          elif listen_timeout.Remaining() < 0:
476 043f2292 Michael Hanselmann
            logging.info("Child process didn't establish connection in time")
477 043f2292 Michael Hanselmann
            child.Kill(signal.SIGTERM)
478 043f2292 Michael Hanselmann
            exit_timeout = \
479 043f2292 Michael Hanselmann
              locking.RunningTimeout(CHILD_LINGER_TIMEOUT, True)
480 043f2292 Michael Hanselmann
            # Next block will calculate timeout
481 043f2292 Michael Hanselmann
          else:
482 043f2292 Michael Hanselmann
            # Not yet connected, check again in a second
483 043f2292 Michael Hanselmann
            timeout = 1000
484 043f2292 Michael Hanselmann
485 043f2292 Michael Hanselmann
        if exit_timeout:
486 043f2292 Michael Hanselmann
          timeout = exit_timeout.Remaining() * 1000
487 2d76b580 Michael Hanselmann
          if timeout < 0:
488 2d76b580 Michael Hanselmann
            logging.info("Child process didn't exit in time")
489 2d76b580 Michael Hanselmann
            break
490 2d76b580 Michael Hanselmann
491 2d76b580 Michael Hanselmann
        for fd, event in utils.RetryOnSignal(poller.poll, timeout):
492 2d76b580 Michael Hanselmann
          if event & (select.POLLIN | event & select.POLLPRI):
493 2d76b580 Michael Hanselmann
            (from_, to) = fdmap[fd]
494 2d76b580 Michael Hanselmann
495 2d76b580 Michael Hanselmann
            # Read up to 1 KB of data
496 2d76b580 Michael Hanselmann
            data = from_.read(1024)
497 2d76b580 Michael Hanselmann
            if data:
498 2d76b580 Michael Hanselmann
              if to:
499 2d76b580 Michael Hanselmann
                to.write(data)
500 2d76b580 Michael Hanselmann
              elif fd == signal_notify.fileno():
501 2d76b580 Michael Hanselmann
                # Signal handling
502 2d76b580 Michael Hanselmann
                if signal_handler.called:
503 2d76b580 Michael Hanselmann
                  signal_handler.Clear()
504 043f2292 Michael Hanselmann
                  if exit_timeout:
505 2d76b580 Michael Hanselmann
                    logging.info("Child process still has about %0.2f seconds"
506 043f2292 Michael Hanselmann
                                 " to exit", exit_timeout.Remaining())
507 2d76b580 Michael Hanselmann
                  else:
508 2d76b580 Michael Hanselmann
                    logging.info("Giving child process %0.2f seconds to exit",
509 2d76b580 Michael Hanselmann
                                 CHILD_LINGER_TIMEOUT)
510 043f2292 Michael Hanselmann
                    exit_timeout = \
511 2d76b580 Michael Hanselmann
                      locking.RunningTimeout(CHILD_LINGER_TIMEOUT, True)
512 2d76b580 Michael Hanselmann
            else:
513 2d76b580 Michael Hanselmann
              poller.unregister(fd)
514 2d76b580 Michael Hanselmann
              del fdmap[fd]
515 2d76b580 Michael Hanselmann
516 2d76b580 Michael Hanselmann
          elif event & (select.POLLNVAL | select.POLLHUP |
517 2d76b580 Michael Hanselmann
                        select.POLLERR):
518 2d76b580 Michael Hanselmann
            poller.unregister(fd)
519 2d76b580 Michael Hanselmann
            del fdmap[fd]
520 2d76b580 Michael Hanselmann
521 2d76b580 Michael Hanselmann
        script_stderr_lines.flush()
522 2d76b580 Michael Hanselmann
        socat_stderr_lines.flush()
523 2d76b580 Michael Hanselmann
524 2d76b580 Michael Hanselmann
      # If there was a timeout calculator, we were waiting for the child to
525 2d76b580 Michael Hanselmann
      # finish, e.g. due to a signal
526 043f2292 Michael Hanselmann
      return not bool(exit_timeout)
527 2d76b580 Michael Hanselmann
    finally:
528 2d76b580 Michael Hanselmann
      socat_stderr_lines.close()
529 2d76b580 Michael Hanselmann
  finally:
530 2d76b580 Michael Hanselmann
    script_stderr_lines.close()
531 2d76b580 Michael Hanselmann
532 2d76b580 Michael Hanselmann
533 2d76b580 Michael Hanselmann
def ParseOptions():
534 2d76b580 Michael Hanselmann
  """Parses the options passed to the program.
535 2d76b580 Michael Hanselmann
536 2d76b580 Michael Hanselmann
  @return: Arguments to program
537 2d76b580 Michael Hanselmann
538 2d76b580 Michael Hanselmann
  """
539 2d76b580 Michael Hanselmann
  global options # pylint: disable-msg=W0603
540 2d76b580 Michael Hanselmann
541 2d76b580 Michael Hanselmann
  parser = optparse.OptionParser(usage=("%%prog <status-file> {%s|%s}" %
542 2d76b580 Michael Hanselmann
                                        (constants.IEM_IMPORT,
543 2d76b580 Michael Hanselmann
                                         constants.IEM_EXPORT)))
544 2d76b580 Michael Hanselmann
  parser.add_option(cli.DEBUG_OPT)
545 2d76b580 Michael Hanselmann
  parser.add_option(cli.VERBOSE_OPT)
546 2d76b580 Michael Hanselmann
  parser.add_option("--key", dest="key", action="store", type="string",
547 2d76b580 Michael Hanselmann
                    help="RSA key file")
548 2d76b580 Michael Hanselmann
  parser.add_option("--cert", dest="cert", action="store", type="string",
549 2d76b580 Michael Hanselmann
                    help="X509 certificate file")
550 2d76b580 Michael Hanselmann
  parser.add_option("--ca", dest="ca", action="store", type="string",
551 2d76b580 Michael Hanselmann
                    help="X509 CA file")
552 2d76b580 Michael Hanselmann
  parser.add_option("--bind", dest="bind", action="store", type="string",
553 2d76b580 Michael Hanselmann
                    help="Bind address")
554 2d76b580 Michael Hanselmann
  parser.add_option("--host", dest="host", action="store", type="string",
555 2d76b580 Michael Hanselmann
                    help="Remote hostname")
556 2d76b580 Michael Hanselmann
  parser.add_option("--port", dest="port", action="store", type="int",
557 2d76b580 Michael Hanselmann
                    help="Remote port")
558 043f2292 Michael Hanselmann
  parser.add_option("--connect-retries", dest="connect_retries", action="store",
559 043f2292 Michael Hanselmann
                    type="int", default=0,
560 043f2292 Michael Hanselmann
                    help=("How many times the connection should be retried"
561 043f2292 Michael Hanselmann
                          " (export only)"))
562 043f2292 Michael Hanselmann
  parser.add_option("--connect-timeout", dest="connect_timeout", action="store",
563 043f2292 Michael Hanselmann
                    type="int", default=DEFAULT_CONNECT_TIMEOUT,
564 043f2292 Michael Hanselmann
                    help="Timeout for connection to be established (seconds)")
565 7e3c1da6 Michael Hanselmann
  parser.add_option("--compress", dest="compress", action="store",
566 7e3c1da6 Michael Hanselmann
                    type="choice", help="Compression method",
567 7e3c1da6 Michael Hanselmann
                    metavar="[%s]" % "|".join(constants.IEC_ALL),
568 7e3c1da6 Michael Hanselmann
                    choices=list(constants.IEC_ALL), default=constants.IEC_GZIP)
569 2d76b580 Michael Hanselmann
  parser.add_option("--cmd-prefix", dest="cmd_prefix", action="store",
570 2d76b580 Michael Hanselmann
                    type="string", help="Command prefix")
571 2d76b580 Michael Hanselmann
  parser.add_option("--cmd-suffix", dest="cmd_suffix", action="store",
572 2d76b580 Michael Hanselmann
                    type="string", help="Command suffix")
573 2d76b580 Michael Hanselmann
574 2d76b580 Michael Hanselmann
  (options, args) = parser.parse_args()
575 2d76b580 Michael Hanselmann
576 2d76b580 Michael Hanselmann
  if len(args) != 2:
577 2d76b580 Michael Hanselmann
    # Won't return
578 2d76b580 Michael Hanselmann
    parser.error("Expected exactly two arguments")
579 2d76b580 Michael Hanselmann
580 2d76b580 Michael Hanselmann
  (status_file_path, mode) = args
581 2d76b580 Michael Hanselmann
582 2d76b580 Michael Hanselmann
  if mode not in (constants.IEM_IMPORT,
583 2d76b580 Michael Hanselmann
                  constants.IEM_EXPORT):
584 2d76b580 Michael Hanselmann
    # Won't return
585 2d76b580 Michael Hanselmann
    parser.error("Invalid mode: %s" % mode)
586 2d76b580 Michael Hanselmann
587 2d76b580 Michael Hanselmann
  return (status_file_path, mode)
588 2d76b580 Michael Hanselmann
589 2d76b580 Michael Hanselmann
590 4ca693ca Michael Hanselmann
class ChildProcess(subprocess.Popen):
591 4ca693ca Michael Hanselmann
  def __init__(self, cmd, noclose_fds):
592 4ca693ca Michael Hanselmann
    """Initializes this class.
593 4ca693ca Michael Hanselmann
594 4ca693ca Michael Hanselmann
    """
595 4ca693ca Michael Hanselmann
    self._noclose_fds = noclose_fds
596 4ca693ca Michael Hanselmann
597 4ca693ca Michael Hanselmann
    # Not using close_fds because doing so would also close the socat stderr
598 4ca693ca Michael Hanselmann
    # pipe, which we still need.
599 4ca693ca Michael Hanselmann
    subprocess.Popen.__init__(self, cmd, shell=False, close_fds=False,
600 4ca693ca Michael Hanselmann
                              stderr=subprocess.PIPE, stdout=None, stdin=None,
601 4ca693ca Michael Hanselmann
                              preexec_fn=self._ChildPreexec)
602 4ca693ca Michael Hanselmann
    self._SetProcessGroup()
603 4ca693ca Michael Hanselmann
604 4ca693ca Michael Hanselmann
  def _ChildPreexec(self):
605 4ca693ca Michael Hanselmann
    """Called before child executable is execve'd.
606 4ca693ca Michael Hanselmann
607 4ca693ca Michael Hanselmann
    """
608 4ca693ca Michael Hanselmann
    # Move to separate process group. By sending a signal to its process group
609 4ca693ca Michael Hanselmann
    # we can kill the child process and all grandchildren.
610 4ca693ca Michael Hanselmann
    os.setpgid(0, 0)
611 4ca693ca Michael Hanselmann
612 4ca693ca Michael Hanselmann
    # Close almost all file descriptors
613 4ca693ca Michael Hanselmann
    utils.CloseFDs(noclose_fds=self._noclose_fds)
614 4ca693ca Michael Hanselmann
615 4ca693ca Michael Hanselmann
  def _SetProcessGroup(self):
616 4ca693ca Michael Hanselmann
    """Sets the child's process group.
617 4ca693ca Michael Hanselmann
618 4ca693ca Michael Hanselmann
    """
619 4ca693ca Michael Hanselmann
    assert self.pid, "Can't be called in child process"
620 4ca693ca Michael Hanselmann
621 4ca693ca Michael Hanselmann
    # Avoid race condition by setting child's process group (as good as
622 4ca693ca Michael Hanselmann
    # possible in Python) before sending signals to child. For an
623 4ca693ca Michael Hanselmann
    # explanation, see preexec function for child.
624 4ca693ca Michael Hanselmann
    try:
625 4ca693ca Michael Hanselmann
      os.setpgid(self.pid, self.pid)
626 4ca693ca Michael Hanselmann
    except EnvironmentError, err:
627 4ca693ca Michael Hanselmann
      # If the child process was faster we receive EPERM or EACCES
628 4ca693ca Michael Hanselmann
      if err.errno not in (errno.EPERM, errno.EACCES):
629 4ca693ca Michael Hanselmann
        raise
630 4ca693ca Michael Hanselmann
631 4ca693ca Michael Hanselmann
  def Kill(self, signum):
632 4ca693ca Michael Hanselmann
    """Sends signal to child process.
633 4ca693ca Michael Hanselmann
634 4ca693ca Michael Hanselmann
    """
635 4ca693ca Michael Hanselmann
    logging.info("Sending signal %s to child process", signum)
636 4ca693ca Michael Hanselmann
    os.killpg(self.pid, signum)
637 4ca693ca Michael Hanselmann
638 4ca693ca Michael Hanselmann
  def ForceQuit(self):
639 4ca693ca Michael Hanselmann
    """Ensure child process is no longer running.
640 4ca693ca Michael Hanselmann
641 4ca693ca Michael Hanselmann
    """
642 4ca693ca Michael Hanselmann
    # Final check if child process is still alive
643 4ca693ca Michael Hanselmann
    if utils.RetryOnSignal(self.poll) is None:
644 4ca693ca Michael Hanselmann
      logging.error("Child process still alive, sending SIGKILL")
645 4ca693ca Michael Hanselmann
      self.Kill(signal.SIGKILL)
646 4ca693ca Michael Hanselmann
      utils.RetryOnSignal(self.wait)
647 4ca693ca Michael Hanselmann
648 4ca693ca Michael Hanselmann
649 2d76b580 Michael Hanselmann
def main():
650 2d76b580 Michael Hanselmann
  """Main function.
651 2d76b580 Michael Hanselmann
652 2d76b580 Michael Hanselmann
  """
653 2d76b580 Michael Hanselmann
  # Option parsing
654 2d76b580 Michael Hanselmann
  (status_file_path, mode) = ParseOptions()
655 2d76b580 Michael Hanselmann
656 2d76b580 Michael Hanselmann
  # Configure logging
657 2d76b580 Michael Hanselmann
  child_logger = SetupLogging()
658 2d76b580 Michael Hanselmann
659 2d76b580 Michael Hanselmann
  status_file = StatusFile(status_file_path)
660 2d76b580 Michael Hanselmann
  try:
661 2d76b580 Michael Hanselmann
    try:
662 2d76b580 Michael Hanselmann
      # Pipe to receive socat's stderr output
663 2d76b580 Michael Hanselmann
      (socat_stderr_read_fd, socat_stderr_write_fd) = os.pipe()
664 2d76b580 Michael Hanselmann
665 2d76b580 Michael Hanselmann
      # Get child process command
666 2d76b580 Michael Hanselmann
      cmd = GetCommand(mode, socat_stderr_write_fd)
667 2d76b580 Michael Hanselmann
668 2d76b580 Michael Hanselmann
      logging.debug("Starting command %r", cmd)
669 2d76b580 Michael Hanselmann
670 4ca693ca Michael Hanselmann
      # Start child process
671 4ca693ca Michael Hanselmann
      child = ChildProcess(cmd, [socat_stderr_write_fd])
672 2d76b580 Michael Hanselmann
      try:
673 2d76b580 Michael Hanselmann
        def _ForwardSignal(signum, _):
674 29da446a Michael Hanselmann
          """Forwards signals to child process.
675 2d76b580 Michael Hanselmann
676 29da446a Michael Hanselmann
          """
677 4ca693ca Michael Hanselmann
          child.Kill(signum)
678 2d76b580 Michael Hanselmann
679 29da446a Michael Hanselmann
        signal_wakeup = utils.SignalWakeupFd()
680 2d76b580 Michael Hanselmann
        try:
681 29da446a Michael Hanselmann
          # TODO: There is a race condition between starting the child and
682 29da446a Michael Hanselmann
          # handling the signals here. While there might be a way to work around
683 29da446a Michael Hanselmann
          # it by registering the handlers before starting the child and
684 29da446a Michael Hanselmann
          # deferring sent signals until the child is available, doing so can be
685 29da446a Michael Hanselmann
          # complicated.
686 29da446a Michael Hanselmann
          signal_handler = utils.SignalHandler([signal.SIGTERM, signal.SIGINT],
687 29da446a Michael Hanselmann
                                               handler_fn=_ForwardSignal,
688 29da446a Michael Hanselmann
                                               wakeup=signal_wakeup)
689 29da446a Michael Hanselmann
          try:
690 29da446a Michael Hanselmann
            # Close child's side
691 29da446a Michael Hanselmann
            utils.RetryOnSignal(os.close, socat_stderr_write_fd)
692 29da446a Michael Hanselmann
693 29da446a Michael Hanselmann
            if ProcessChildIO(child, socat_stderr_read_fd, status_file,
694 043f2292 Michael Hanselmann
                              child_logger, signal_wakeup, signal_handler,
695 043f2292 Michael Hanselmann
                              mode):
696 29da446a Michael Hanselmann
              # The child closed all its file descriptors and there was no
697 29da446a Michael Hanselmann
              # signal
698 29da446a Michael Hanselmann
              # TODO: Implement timeout instead of waiting indefinitely
699 29da446a Michael Hanselmann
              utils.RetryOnSignal(child.wait)
700 29da446a Michael Hanselmann
          finally:
701 29da446a Michael Hanselmann
            signal_handler.Reset()
702 2d76b580 Michael Hanselmann
        finally:
703 29da446a Michael Hanselmann
          signal_wakeup.Reset()
704 2d76b580 Michael Hanselmann
      finally:
705 4ca693ca Michael Hanselmann
        child.ForceQuit()
706 2d76b580 Michael Hanselmann
707 2d76b580 Michael Hanselmann
      if child.returncode == 0:
708 2d76b580 Michael Hanselmann
        errmsg = None
709 2d76b580 Michael Hanselmann
      elif child.returncode < 0:
710 2d76b580 Michael Hanselmann
        errmsg = "Exited due to signal %s" % (-child.returncode, )
711 2d76b580 Michael Hanselmann
      else:
712 2d76b580 Michael Hanselmann
        errmsg = "Exited with status %s" % (child.returncode, )
713 2d76b580 Michael Hanselmann
714 2d76b580 Michael Hanselmann
      status_file.SetExitStatus(child.returncode, errmsg)
715 2d76b580 Michael Hanselmann
    except Exception, err: # pylint: disable-msg=W0703
716 2d76b580 Michael Hanselmann
      logging.exception("Unhandled error occurred")
717 2d76b580 Michael Hanselmann
      status_file.SetExitStatus(constants.EXIT_FAILURE,
718 2d76b580 Michael Hanselmann
                                "Unhandled error occurred: %s" % (err, ))
719 2d76b580 Michael Hanselmann
720 2d76b580 Michael Hanselmann
    if status_file.ExitStatusIsSuccess():
721 2d76b580 Michael Hanselmann
      sys.exit(constants.EXIT_SUCCESS)
722 2d76b580 Michael Hanselmann
723 2d76b580 Michael Hanselmann
    sys.exit(constants.EXIT_FAILURE)
724 2d76b580 Michael Hanselmann
  finally:
725 2d76b580 Michael Hanselmann
    status_file.Update(True)
726 2d76b580 Michael Hanselmann
727 2d76b580 Michael Hanselmann
728 2d76b580 Michael Hanselmann
if __name__ == "__main__":
729 2d76b580 Michael Hanselmann
  main()