1 |
|
#
|
2 |
|
#
|
3 |
|
|
4 |
|
# Copyright (C) 2006, 2007, 2010 Google Inc.
|
5 |
|
#
|
6 |
|
# This program is free software; you can redistribute it and/or modify
|
7 |
|
# it under the terms of the GNU General Public License as published by
|
8 |
|
# the Free Software Foundation; either version 2 of the License, or
|
9 |
|
# (at your option) any later version.
|
10 |
|
#
|
11 |
|
# This program is distributed in the hope that it will be useful, but
|
12 |
|
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
13 |
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14 |
|
# General Public License for more details.
|
15 |
|
#
|
16 |
|
# You should have received a copy of the GNU General Public License
|
17 |
|
# along with this program; if not, write to the Free Software
|
18 |
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
19 |
|
# 02110-1301, USA.
|
20 |
|
|
21 |
|
|
22 |
|
"""Ganeti utility module.
|
23 |
|
|
24 |
|
This module holds functions that can be used in both daemons (all) and
|
25 |
|
the command line scripts.
|
26 |
|
|
27 |
|
"""
|
28 |
|
|
29 |
|
|
30 |
|
import os
|
31 |
|
import sys
|
32 |
|
import time
|
33 |
|
import subprocess
|
34 |
|
import re
|
35 |
|
import socket
|
36 |
|
import tempfile
|
37 |
|
import shutil
|
38 |
|
import errno
|
39 |
|
import pwd
|
40 |
|
import itertools
|
41 |
|
import select
|
42 |
|
import fcntl
|
43 |
|
import resource
|
44 |
|
import logging
|
45 |
|
import logging.handlers
|
46 |
|
import signal
|
47 |
|
import OpenSSL
|
48 |
|
import datetime
|
49 |
|
import calendar
|
50 |
|
import hmac
|
51 |
|
import collections
|
52 |
|
|
53 |
|
from cStringIO import StringIO
|
54 |
|
|
55 |
|
try:
|
56 |
|
# pylint: disable-msg=F0401
|
57 |
|
import ctypes
|
58 |
|
except ImportError:
|
59 |
|
ctypes = None
|
60 |
|
|
61 |
|
from ganeti import errors
|
62 |
|
from ganeti import constants
|
63 |
|
from ganeti import compat
|
64 |
|
|
65 |
|
|
66 |
|
_locksheld = []
|
67 |
|
_re_shell_unquoted = re.compile('^[-.,=:/_+@A-Za-z0-9]+$')
|
68 |
|
|
69 |
|
debug_locks = False
|
70 |
|
|
71 |
|
#: when set to True, L{RunCmd} is disabled
|
72 |
|
no_fork = False
|
73 |
|
|
74 |
|
_RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
|
75 |
|
|
76 |
|
HEX_CHAR_RE = r"[a-zA-Z0-9]"
|
77 |
|
VALID_X509_SIGNATURE_SALT = re.compile("^%s+$" % HEX_CHAR_RE, re.S)
|
78 |
|
X509_SIGNATURE = re.compile(r"^%s:\s*(?P<salt>%s+)/(?P<sign>%s+)$" %
|
79 |
|
(re.escape(constants.X509_CERT_SIGNATURE_HEADER),
|
80 |
|
HEX_CHAR_RE, HEX_CHAR_RE),
|
81 |
|
re.S | re.I)
|
82 |
|
|
83 |
|
_VALID_SERVICE_NAME_RE = re.compile("^[-_.a-zA-Z0-9]{1,128}$")
|
84 |
|
|
85 |
|
UUID_RE = re.compile('^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-'
|
86 |
|
'[a-f0-9]{4}-[a-f0-9]{12}$')
|
87 |
|
|
88 |
|
# Certificate verification results
|
89 |
|
(CERT_WARNING,
|
90 |
|
CERT_ERROR) = range(1, 3)
|
91 |
|
|
92 |
|
# Flags for mlockall() (from bits/mman.h)
|
93 |
|
_MCL_CURRENT = 1
|
94 |
|
_MCL_FUTURE = 2
|
95 |
|
|
96 |
|
#: MAC checker regexp
|
97 |
|
_MAC_CHECK = re.compile("^([0-9a-f]{2}:){5}[0-9a-f]{2}$", re.I)
|
98 |
|
|
99 |
|
(_TIMEOUT_NONE,
|
100 |
|
_TIMEOUT_TERM,
|
101 |
|
_TIMEOUT_KILL) = range(3)
|
102 |
|
|
103 |
|
#: Shell param checker regexp
|
104 |
|
_SHELLPARAM_REGEX = re.compile(r"^[-a-zA-Z0-9._+/:%@]+$")
|
105 |
|
|
106 |
|
#: Unit checker regexp
|
107 |
|
_PARSEUNIT_REGEX = re.compile(r"^([.\d]+)\s*([a-zA-Z]+)?$")
|
108 |
|
|
109 |
|
#: ASN1 time regexp
|
110 |
|
_ASN1_TIME_REGEX = re.compile(r"^(\d+)([-+]\d\d)(\d\d)$")
|
111 |
|
|
112 |
|
_SORTER_RE = re.compile("^%s(.*)$" % (8 * "(\D+|\d+)?"))
|
113 |
|
_SORTER_DIGIT = re.compile("^\d+$")
|
114 |
|
|
115 |
|
|
116 |
|
class RunResult(object):
|
117 |
|
"""Holds the result of running external programs.
|
118 |
|
|
119 |
|
@type exit_code: int
|
120 |
|
@ivar exit_code: the exit code of the program, or None (if the program
|
121 |
|
didn't exit())
|
122 |
|
@type signal: int or None
|
123 |
|
@ivar signal: the signal that caused the program to finish, or None
|
124 |
|
(if the program wasn't terminated by a signal)
|
125 |
|
@type stdout: str
|
126 |
|
@ivar stdout: the standard output of the program
|
127 |
|
@type stderr: str
|
128 |
|
@ivar stderr: the standard error of the program
|
129 |
|
@type failed: boolean
|
130 |
|
@ivar failed: True in case the program was
|
131 |
|
terminated by a signal or exited with a non-zero exit code
|
132 |
|
@ivar fail_reason: a string detailing the termination reason
|
133 |
|
|
134 |
|
"""
|
135 |
|
__slots__ = ["exit_code", "signal", "stdout", "stderr",
|
136 |
|
"failed", "fail_reason", "cmd"]
|
137 |
|
|
138 |
|
|
139 |
|
def __init__(self, exit_code, signal_, stdout, stderr, cmd, timeout_action,
|
140 |
|
timeout):
|
141 |
|
self.cmd = cmd
|
142 |
|
self.exit_code = exit_code
|
143 |
|
self.signal = signal_
|
144 |
|
self.stdout = stdout
|
145 |
|
self.stderr = stderr
|
146 |
|
self.failed = (signal_ is not None or exit_code != 0)
|
147 |
|
|
148 |
|
fail_msgs = []
|
149 |
|
if self.signal is not None:
|
150 |
|
fail_msgs.append("terminated by signal %s" % self.signal)
|
151 |
|
elif self.exit_code is not None:
|
152 |
|
fail_msgs.append("exited with exit code %s" % self.exit_code)
|
153 |
|
else:
|
154 |
|
fail_msgs.append("unable to determine termination reason")
|
155 |
|
|
156 |
|
if timeout_action == _TIMEOUT_TERM:
|
157 |
|
fail_msgs.append("terminated after timeout of %.2f seconds" % timeout)
|
158 |
|
elif timeout_action == _TIMEOUT_KILL:
|
159 |
|
fail_msgs.append(("force termination after timeout of %.2f seconds"
|
160 |
|
" and linger for another %.2f seconds") %
|
161 |
|
(timeout, constants.CHILD_LINGER_TIMEOUT))
|
162 |
|
|
163 |
|
if fail_msgs and self.failed:
|
164 |
|
self.fail_reason = CommaJoin(fail_msgs)
|
165 |
|
|
166 |
|
if self.failed:
|
167 |
|
logging.debug("Command '%s' failed (%s); output: %s",
|
168 |
|
self.cmd, self.fail_reason, self.output)
|
169 |
|
|
170 |
|
def _GetOutput(self):
|
171 |
|
"""Returns the combined stdout and stderr for easier usage.
|
172 |
|
|
173 |
|
"""
|
174 |
|
return self.stdout + self.stderr
|
175 |
|
|
176 |
|
output = property(_GetOutput, None, None, "Return full output")
|
177 |
|
|
178 |
|
|
179 |
|
def _BuildCmdEnvironment(env, reset):
|
180 |
|
"""Builds the environment for an external program.
|
181 |
|
|
182 |
|
"""
|
183 |
|
if reset:
|
184 |
|
cmd_env = {}
|
185 |
|
else:
|
186 |
|
cmd_env = os.environ.copy()
|
187 |
|
cmd_env["LC_ALL"] = "C"
|
188 |
|
|
189 |
|
if env is not None:
|
190 |
|
cmd_env.update(env)
|
191 |
|
|
192 |
|
return cmd_env
|
193 |
|
|
194 |
|
|
195 |
|
def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False,
|
196 |
|
interactive=False, timeout=None):
|
197 |
|
"""Execute a (shell) command.
|
198 |
|
|
199 |
|
The command should not read from its standard input, as it will be
|
200 |
|
closed.
|
201 |
|
|
202 |
|
@type cmd: string or list
|
203 |
|
@param cmd: Command to run
|
204 |
|
@type env: dict
|
205 |
|
@param env: Additional environment variables
|
206 |
|
@type output: str
|
207 |
|
@param output: if desired, the output of the command can be
|
208 |
|
saved in a file instead of the RunResult instance; this
|
209 |
|
parameter denotes the file name (if not None)
|
210 |
|
@type cwd: string
|
211 |
|
@param cwd: if specified, will be used as the working
|
212 |
|
directory for the command; the default will be /
|
213 |
|
@type reset_env: boolean
|
214 |
|
@param reset_env: whether to reset or keep the default os environment
|
215 |
|
@type interactive: boolean
|
216 |
|
@param interactive: weather we pipe stdin, stdout and stderr
|
217 |
|
(default behaviour) or run the command interactive
|
218 |
|
@type timeout: int
|
219 |
|
@param timeout: If not None, timeout in seconds until child process gets
|
220 |
|
killed
|
221 |
|
@rtype: L{RunResult}
|
222 |
|
@return: RunResult instance
|
223 |
|
@raise errors.ProgrammerError: if we call this when forks are disabled
|
224 |
|
|
225 |
|
"""
|
226 |
|
if no_fork:
|
227 |
|
raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
|
228 |
|
|
229 |
|
if output and interactive:
|
230 |
|
raise errors.ProgrammerError("Parameters 'output' and 'interactive' can"
|
231 |
|
" not be provided at the same time")
|
232 |
|
|
233 |
|
if isinstance(cmd, basestring):
|
234 |
|
strcmd = cmd
|
235 |
|
shell = True
|
236 |
|
else:
|
237 |
|
cmd = [str(val) for val in cmd]
|
238 |
|
strcmd = ShellQuoteArgs(cmd)
|
239 |
|
shell = False
|
240 |
|
|
241 |
|
if output:
|
242 |
|
logging.debug("RunCmd %s, output file '%s'", strcmd, output)
|
243 |
|
else:
|
244 |
|
logging.debug("RunCmd %s", strcmd)
|
245 |
|
|
246 |
|
cmd_env = _BuildCmdEnvironment(env, reset_env)
|
247 |
|
|
248 |
|
try:
|
249 |
|
if output is None:
|
250 |
|
out, err, status, timeout_action = _RunCmdPipe(cmd, cmd_env, shell, cwd,
|
251 |
|
interactive, timeout)
|
252 |
|
else:
|
253 |
|
timeout_action = _TIMEOUT_NONE
|
254 |
|
status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
|
255 |
|
out = err = ""
|
256 |
|
except OSError, err:
|
257 |
|
if err.errno == errno.ENOENT:
|
258 |
|
raise errors.OpExecError("Can't execute '%s': not found (%s)" %
|
259 |
|
(strcmd, err))
|
260 |
|
else:
|
261 |
|
raise
|
262 |
|
|
263 |
|
if status >= 0:
|
264 |
|
exitcode = status
|
265 |
|
signal_ = None
|
266 |
|
else:
|
267 |
|
exitcode = None
|
268 |
|
signal_ = -status
|
269 |
|
|
270 |
|
return RunResult(exitcode, signal_, out, err, strcmd, timeout_action, timeout)
|
271 |
|
|
272 |
|
|
273 |
|
def SetupDaemonEnv(cwd="/", umask=077):
|
274 |
|
"""Setup a daemon's environment.
|
275 |
|
|
276 |
|
This should be called between the first and second fork, due to
|
277 |
|
setsid usage.
|
278 |
|
|
279 |
|
@param cwd: the directory to which to chdir
|
280 |
|
@param umask: the umask to setup
|
281 |
|
|
282 |
|
"""
|
283 |
|
os.chdir(cwd)
|
284 |
|
os.umask(umask)
|
285 |
|
os.setsid()
|
286 |
|
|
287 |
|
|
288 |
|
def SetupDaemonFDs(output_file, output_fd):
|
289 |
|
"""Setups up a daemon's file descriptors.
|
290 |
|
|
291 |
|
@param output_file: if not None, the file to which to redirect
|
292 |
|
stdout/stderr
|
293 |
|
@param output_fd: if not None, the file descriptor for stdout/stderr
|
294 |
|
|
295 |
|
"""
|
296 |
|
# check that at most one is defined
|
297 |
|
assert [output_file, output_fd].count(None) >= 1
|
298 |
|
|
299 |
|
# Open /dev/null (read-only, only for stdin)
|
300 |
|
devnull_fd = os.open(os.devnull, os.O_RDONLY)
|
301 |
|
|
302 |
|
if output_fd is not None:
|
303 |
|
pass
|
304 |
|
elif output_file is not None:
|
305 |
|
# Open output file
|
306 |
|
try:
|
307 |
|
output_fd = os.open(output_file,
|
308 |
|
os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0600)
|
309 |
|
except EnvironmentError, err:
|
310 |
|
raise Exception("Opening output file failed: %s" % err)
|
311 |
|
else:
|
312 |
|
output_fd = os.open(os.devnull, os.O_WRONLY)
|
313 |
|
|
314 |
|
# Redirect standard I/O
|
315 |
|
os.dup2(devnull_fd, 0)
|
316 |
|
os.dup2(output_fd, 1)
|
317 |
|
os.dup2(output_fd, 2)
|
318 |
|
|
319 |
|
|
320 |
|
def StartDaemon(cmd, env=None, cwd="/", output=None, output_fd=None,
|
321 |
|
pidfile=None):
|
322 |
|
"""Start a daemon process after forking twice.
|
323 |
|
|
324 |
|
@type cmd: string or list
|
325 |
|
@param cmd: Command to run
|
326 |
|
@type env: dict
|
327 |
|
@param env: Additional environment variables
|
328 |
|
@type cwd: string
|
329 |
|
@param cwd: Working directory for the program
|
330 |
|
@type output: string
|
331 |
|
@param output: Path to file in which to save the output
|
332 |
|
@type output_fd: int
|
333 |
|
@param output_fd: File descriptor for output
|
334 |
|
@type pidfile: string
|
335 |
|
@param pidfile: Process ID file
|
336 |
|
@rtype: int
|
337 |
|
@return: Daemon process ID
|
338 |
|
@raise errors.ProgrammerError: if we call this when forks are disabled
|
339 |
|
|
340 |
|
"""
|
341 |
|
if no_fork:
|
342 |
|
raise errors.ProgrammerError("utils.StartDaemon() called with fork()"
|
343 |
|
" disabled")
|
344 |
|
|
345 |
|
if output and not (bool(output) ^ (output_fd is not None)):
|
346 |
|
raise errors.ProgrammerError("Only one of 'output' and 'output_fd' can be"
|
347 |
|
" specified")
|
348 |
|
|
349 |
|
if isinstance(cmd, basestring):
|
350 |
|
cmd = ["/bin/sh", "-c", cmd]
|
351 |
|
|
352 |
|
strcmd = ShellQuoteArgs(cmd)
|
353 |
|
|
354 |
|
if output:
|
355 |
|
logging.debug("StartDaemon %s, output file '%s'", strcmd, output)
|
356 |
|
else:
|
357 |
|
logging.debug("StartDaemon %s", strcmd)
|
358 |
|
|
359 |
|
cmd_env = _BuildCmdEnvironment(env, False)
|
360 |
|
|
361 |
|
# Create pipe for sending PID back
|
362 |
|
(pidpipe_read, pidpipe_write) = os.pipe()
|
363 |
|
try:
|
364 |
|
try:
|
365 |
|
# Create pipe for sending error messages
|
366 |
|
(errpipe_read, errpipe_write) = os.pipe()
|
367 |
|
try:
|
368 |
|
try:
|
369 |
|
# First fork
|
370 |
|
pid = os.fork()
|
371 |
|
if pid == 0:
|
372 |
|
try:
|
373 |
|
# Child process, won't return
|
374 |
|
_StartDaemonChild(errpipe_read, errpipe_write,
|
375 |
|
pidpipe_read, pidpipe_write,
|
376 |
|
cmd, cmd_env, cwd,
|
377 |
|
output, output_fd, pidfile)
|
378 |
|
finally:
|
379 |
|
# Well, maybe child process failed
|
380 |
|
os._exit(1) # pylint: disable-msg=W0212
|
381 |
|
finally:
|
382 |
|
_CloseFDNoErr(errpipe_write)
|
383 |
|
|
384 |
|
# Wait for daemon to be started (or an error message to
|
385 |
|
# arrive) and read up to 100 KB as an error message
|
386 |
|
errormsg = RetryOnSignal(os.read, errpipe_read, 100 * 1024)
|
387 |
|
finally:
|
388 |
|
_CloseFDNoErr(errpipe_read)
|
389 |
|
finally:
|
390 |
|
_CloseFDNoErr(pidpipe_write)
|
391 |
|
|
392 |
|
# Read up to 128 bytes for PID
|
393 |
|
pidtext = RetryOnSignal(os.read, pidpipe_read, 128)
|
394 |
|
finally:
|
395 |
|
_CloseFDNoErr(pidpipe_read)
|
396 |
|
|
397 |
|
# Try to avoid zombies by waiting for child process
|
398 |
|
try:
|
399 |
|
os.waitpid(pid, 0)
|
400 |
|
except OSError:
|
401 |
|
pass
|
402 |
|
|
403 |
|
if errormsg:
|
404 |
|
raise errors.OpExecError("Error when starting daemon process: %r" %
|
405 |
|
errormsg)
|
406 |
|
|
407 |
|
try:
|
408 |
|
return int(pidtext)
|
409 |
|
except (ValueError, TypeError), err:
|
410 |
|
raise errors.OpExecError("Error while trying to parse PID %r: %s" %
|
411 |
|
(pidtext, err))
|
412 |
|
|
413 |
|
|
414 |
|
def _StartDaemonChild(errpipe_read, errpipe_write,
|
415 |
|
pidpipe_read, pidpipe_write,
|
416 |
|
args, env, cwd,
|
417 |
|
output, fd_output, pidfile):
|
418 |
|
"""Child process for starting daemon.
|
419 |
|
|
420 |
|
"""
|
421 |
|
try:
|
422 |
|
# Close parent's side
|
423 |
|
_CloseFDNoErr(errpipe_read)
|
424 |
|
_CloseFDNoErr(pidpipe_read)
|
425 |
|
|
426 |
|
# First child process
|
427 |
|
SetupDaemonEnv()
|
428 |
|
|
429 |
|
# And fork for the second time
|
430 |
|
pid = os.fork()
|
431 |
|
if pid != 0:
|
432 |
|
# Exit first child process
|
433 |
|
os._exit(0) # pylint: disable-msg=W0212
|
434 |
|
|
435 |
|
# Make sure pipe is closed on execv* (and thereby notifies
|
436 |
|
# original process)
|
437 |
|
SetCloseOnExecFlag(errpipe_write, True)
|
438 |
|
|
439 |
|
# List of file descriptors to be left open
|
440 |
|
noclose_fds = [errpipe_write]
|
441 |
|
|
442 |
|
# Open PID file
|
443 |
|
if pidfile:
|
444 |
|
fd_pidfile = WritePidFile(pidfile)
|
445 |
|
|
446 |
|
# Keeping the file open to hold the lock
|
447 |
|
noclose_fds.append(fd_pidfile)
|
448 |
|
|
449 |
|
SetCloseOnExecFlag(fd_pidfile, False)
|
450 |
|
else:
|
451 |
|
fd_pidfile = None
|
452 |
|
|
453 |
|
SetupDaemonFDs(output, fd_output)
|
454 |
|
|
455 |
|
# Send daemon PID to parent
|
456 |
|
RetryOnSignal(os.write, pidpipe_write, str(os.getpid()))
|
457 |
|
|
458 |
|
# Close all file descriptors except stdio and error message pipe
|
459 |
|
CloseFDs(noclose_fds=noclose_fds)
|
460 |
|
|
461 |
|
# Change working directory
|
462 |
|
os.chdir(cwd)
|
463 |
|
|
464 |
|
if env is None:
|
465 |
|
os.execvp(args[0], args)
|
466 |
|
else:
|
467 |
|
os.execvpe(args[0], args, env)
|
468 |
|
except: # pylint: disable-msg=W0702
|
469 |
|
try:
|
470 |
|
# Report errors to original process
|
471 |
|
WriteErrorToFD(errpipe_write, str(sys.exc_info()[1]))
|
472 |
|
except: # pylint: disable-msg=W0702
|
473 |
|
# Ignore errors in error handling
|
474 |
|
pass
|
475 |
|
|
476 |
|
os._exit(1) # pylint: disable-msg=W0212
|
477 |
|
|
478 |
|
|
479 |
|
def WriteErrorToFD(fd, err):
|
480 |
|
"""Possibly write an error message to a fd.
|
481 |
|
|
482 |
|
@type fd: None or int (file descriptor)
|
483 |
|
@param fd: if not None, the error will be written to this fd
|
484 |
|
@param err: string, the error message
|
485 |
|
|
486 |
|
"""
|
487 |
|
if fd is None:
|
488 |
|
return
|
489 |
|
|
490 |
|
if not err:
|
491 |
|
err = "<unknown error>"
|
492 |
|
|
493 |
|
RetryOnSignal(os.write, fd, err)
|
494 |
|
|
495 |
|
|
496 |
|
def _CheckIfAlive(child):
|
497 |
|
"""Raises L{RetryAgain} if child is still alive.
|
498 |
|
|
499 |
|
@raises RetryAgain: If child is still alive
|
500 |
|
|
501 |
|
"""
|
502 |
|
if child.poll() is None:
|
503 |
|
raise RetryAgain()
|
504 |
|
|
505 |
|
|
506 |
|
def _WaitForProcess(child, timeout):
|
507 |
|
"""Waits for the child to terminate or until we reach timeout.
|
508 |
|
|
509 |
|
"""
|
510 |
|
try:
|
511 |
|
Retry(_CheckIfAlive, (1.0, 1.2, 5.0), max(0, timeout), args=[child])
|
512 |
|
except RetryTimeout:
|
513 |
|
pass
|
514 |
|
|
515 |
|
|
516 |
|
def _RunCmdPipe(cmd, env, via_shell, cwd, interactive, timeout,
|
517 |
|
_linger_timeout=constants.CHILD_LINGER_TIMEOUT):
|
518 |
|
"""Run a command and return its output.
|
519 |
|
|
520 |
|
@type cmd: string or list
|
521 |
|
@param cmd: Command to run
|
522 |
|
@type env: dict
|
523 |
|
@param env: The environment to use
|
524 |
|
@type via_shell: bool
|
525 |
|
@param via_shell: if we should run via the shell
|
526 |
|
@type cwd: string
|
527 |
|
@param cwd: the working directory for the program
|
528 |
|
@type interactive: boolean
|
529 |
|
@param interactive: Run command interactive (without piping)
|
530 |
|
@type timeout: int
|
531 |
|
@param timeout: Timeout after the programm gets terminated
|
532 |
|
@rtype: tuple
|
533 |
|
@return: (out, err, status)
|
534 |
|
|
535 |
|
"""
|
536 |
|
poller = select.poll()
|
537 |
|
|
538 |
|
stderr = subprocess.PIPE
|
539 |
|
stdout = subprocess.PIPE
|
540 |
|
stdin = subprocess.PIPE
|
541 |
|
|
542 |
|
if interactive:
|
543 |
|
stderr = stdout = stdin = None
|
544 |
|
|
545 |
|
child = subprocess.Popen(cmd, shell=via_shell,
|
546 |
|
stderr=stderr,
|
547 |
|
stdout=stdout,
|
548 |
|
stdin=stdin,
|
549 |
|
close_fds=True, env=env,
|
550 |
|
cwd=cwd)
|
551 |
|
|
552 |
|
out = StringIO()
|
553 |
|
err = StringIO()
|
554 |
|
|
555 |
|
linger_timeout = None
|
556 |
|
|
557 |
|
if timeout is None:
|
558 |
|
poll_timeout = None
|
559 |
|
else:
|
560 |
|
poll_timeout = RunningTimeout(timeout, True).Remaining
|
561 |
|
|
562 |
|
msg_timeout = ("Command %s (%d) run into execution timeout, terminating" %
|
563 |
|
(cmd, child.pid))
|
564 |
|
msg_linger = ("Command %s (%d) run into linger timeout, killing" %
|
565 |
|
(cmd, child.pid))
|
566 |
|
|
567 |
|
timeout_action = _TIMEOUT_NONE
|
568 |
|
|
569 |
|
if not interactive:
|
570 |
|
child.stdin.close()
|
571 |
|
poller.register(child.stdout, select.POLLIN)
|
572 |
|
poller.register(child.stderr, select.POLLIN)
|
573 |
|
fdmap = {
|
574 |
|
child.stdout.fileno(): (out, child.stdout),
|
575 |
|
child.stderr.fileno(): (err, child.stderr),
|
576 |
|
}
|
577 |
|
for fd in fdmap:
|
578 |
|
SetNonblockFlag(fd, True)
|
579 |
|
|
580 |
|
while fdmap:
|
581 |
|
if poll_timeout:
|
582 |
|
pt = poll_timeout() * 1000
|
583 |
|
if pt < 0:
|
584 |
|
if linger_timeout is None:
|
585 |
|
logging.warning(msg_timeout)
|
586 |
|
if child.poll() is None:
|
587 |
|
timeout_action = _TIMEOUT_TERM
|
588 |
|
IgnoreProcessNotFound(os.kill, child.pid, signal.SIGTERM)
|
589 |
|
linger_timeout = RunningTimeout(_linger_timeout, True).Remaining
|
590 |
|
pt = linger_timeout() * 1000
|
591 |
|
if pt < 0:
|
592 |
|
break
|
593 |
|
else:
|
594 |
|
pt = None
|
595 |
|
|
596 |
|
pollresult = RetryOnSignal(poller.poll, pt)
|
597 |
|
|
598 |
|
for fd, event in pollresult:
|
599 |
|
if event & select.POLLIN or event & select.POLLPRI:
|
600 |
|
data = fdmap[fd][1].read()
|
601 |
|
# no data from read signifies EOF (the same as POLLHUP)
|
602 |
|
if not data:
|
603 |
|
poller.unregister(fd)
|
604 |
|
del fdmap[fd]
|
605 |
|
continue
|
606 |
|
fdmap[fd][0].write(data)
|
607 |
|
if (event & select.POLLNVAL or event & select.POLLHUP or
|
608 |
|
event & select.POLLERR):
|
609 |
|
poller.unregister(fd)
|
610 |
|
del fdmap[fd]
|
611 |
|
|
612 |
|
if timeout is not None:
|
613 |
|
assert callable(poll_timeout)
|
614 |
|
|
615 |
|
# We have no I/O left but it might still run
|
616 |
|
if child.poll() is None:
|
617 |
|
_WaitForProcess(child, poll_timeout())
|
618 |
|
|
619 |
|
# Terminate if still alive after timeout
|
620 |
|
if child.poll() is None:
|
621 |
|
if linger_timeout is None:
|
622 |
|
logging.warning(msg_timeout)
|
623 |
|
timeout_action = _TIMEOUT_TERM
|
624 |
|
IgnoreProcessNotFound(os.kill, child.pid, signal.SIGTERM)
|
625 |
|
lt = _linger_timeout
|
626 |
|
else:
|
627 |
|
lt = linger_timeout()
|
628 |
|
_WaitForProcess(child, lt)
|
629 |
|
|
630 |
|
# Okay, still alive after timeout and linger timeout? Kill it!
|
631 |
|
if child.poll() is None:
|
632 |
|
timeout_action = _TIMEOUT_KILL
|
633 |
|
logging.warning(msg_linger)
|
634 |
|
IgnoreProcessNotFound(os.kill, child.pid, signal.SIGKILL)
|
635 |
|
|
636 |
|
out = out.getvalue()
|
637 |
|
err = err.getvalue()
|
638 |
|
|
639 |
|
status = child.wait()
|
640 |
|
return out, err, status, timeout_action
|
641 |
|
|
642 |
|
|
643 |
|
def _RunCmdFile(cmd, env, via_shell, output, cwd):
|
644 |
|
"""Run a command and save its output to a file.
|
645 |
|
|
646 |
|
@type cmd: string or list
|
647 |
|
@param cmd: Command to run
|
648 |
|
@type env: dict
|
649 |
|
@param env: The environment to use
|
650 |
|
@type via_shell: bool
|
651 |
|
@param via_shell: if we should run via the shell
|
652 |
|
@type output: str
|
653 |
|
@param output: the filename in which to save the output
|
654 |
|
@type cwd: string
|
655 |
|
@param cwd: the working directory for the program
|
656 |
|
@rtype: int
|
657 |
|
@return: the exit status
|
658 |
|
|
659 |
|
"""
|
660 |
|
fh = open(output, "a")
|
661 |
|
try:
|
662 |
|
child = subprocess.Popen(cmd, shell=via_shell,
|
663 |
|
stderr=subprocess.STDOUT,
|
664 |
|
stdout=fh,
|
665 |
|
stdin=subprocess.PIPE,
|
666 |
|
close_fds=True, env=env,
|
667 |
|
cwd=cwd)
|
668 |
|
|
669 |
|
child.stdin.close()
|
670 |
|
status = child.wait()
|
671 |
|
finally:
|
672 |
|
fh.close()
|
673 |
|
return status
|
674 |
|
|
675 |
|
|
676 |
|
def SetCloseOnExecFlag(fd, enable):
|
677 |
|
"""Sets or unsets the close-on-exec flag on a file descriptor.
|
678 |
|
|
679 |
|
@type fd: int
|
680 |
|
@param fd: File descriptor
|
681 |
|
@type enable: bool
|
682 |
|
@param enable: Whether to set or unset it.
|
683 |
|
|
684 |
|
"""
|
685 |
|
flags = fcntl.fcntl(fd, fcntl.F_GETFD)
|
686 |
|
|
687 |
|
if enable:
|
688 |
|
flags |= fcntl.FD_CLOEXEC
|
689 |
|
else:
|
690 |
|
flags &= ~fcntl.FD_CLOEXEC
|
691 |
|
|
692 |
|
fcntl.fcntl(fd, fcntl.F_SETFD, flags)
|
693 |
|
|
694 |
|
|
695 |
|
def SetNonblockFlag(fd, enable):
|
696 |
|
"""Sets or unsets the O_NONBLOCK flag on on a file descriptor.
|
697 |
|
|
698 |
|
@type fd: int
|
699 |
|
@param fd: File descriptor
|
700 |
|
@type enable: bool
|
701 |
|
@param enable: Whether to set or unset it
|
702 |
|
|
703 |
|
"""
|
704 |
|
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
|
705 |
|
|
706 |
|
if enable:
|
707 |
|
flags |= os.O_NONBLOCK
|
708 |
|
else:
|
709 |
|
flags &= ~os.O_NONBLOCK
|
710 |
|
|
711 |
|
fcntl.fcntl(fd, fcntl.F_SETFL, flags)
|
712 |
|
|
713 |
|
|
714 |
|
def RetryOnSignal(fn, *args, **kwargs):
|
715 |
|
"""Calls a function again if it failed due to EINTR.
|
716 |
|
|
717 |
|
"""
|
718 |
|
while True:
|
719 |
|
try:
|
720 |
|
return fn(*args, **kwargs)
|
721 |
|
except EnvironmentError, err:
|
722 |
|
if err.errno != errno.EINTR:
|
723 |
|
raise
|
724 |
|
except (socket.error, select.error), err:
|
725 |
|
# In python 2.6 and above select.error is an IOError, so it's handled
|
726 |
|
# above, in 2.5 and below it's not, and it's handled here.
|
727 |
|
if not (err.args and err.args[0] == errno.EINTR):
|
728 |
|
raise
|
729 |
|
|
730 |
|
|
731 |
|
def RunParts(dir_name, env=None, reset_env=False):
|
732 |
|
"""Run Scripts or programs in a directory
|
733 |
|
|
734 |
|
@type dir_name: string
|
735 |
|
@param dir_name: absolute path to a directory
|
736 |
|
@type env: dict
|
737 |
|
@param env: The environment to use
|
738 |
|
@type reset_env: boolean
|
739 |
|
@param reset_env: whether to reset or keep the default os environment
|
740 |
|
@rtype: list of tuples
|
741 |
|
@return: list of (name, (one of RUNDIR_STATUS), RunResult)
|
742 |
|
|
743 |
|
"""
|
744 |
|
rr = []
|
745 |
|
|
746 |
|
try:
|
747 |
|
dir_contents = ListVisibleFiles(dir_name)
|
748 |
|
except OSError, err:
|
749 |
|
logging.warning("RunParts: skipping %s (cannot list: %s)", dir_name, err)
|
750 |
|
return rr
|
751 |
|
|
752 |
|
for relname in sorted(dir_contents):
|
753 |
|
fname = PathJoin(dir_name, relname)
|
754 |
|
if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
|
755 |
|
constants.EXT_PLUGIN_MASK.match(relname) is not None):
|
756 |
|
rr.append((relname, constants.RUNPARTS_SKIP, None))
|
757 |
|
else:
|
758 |
|
try:
|
759 |
|
result = RunCmd([fname], env=env, reset_env=reset_env)
|
760 |
|
except Exception, err: # pylint: disable-msg=W0703
|
761 |
|
rr.append((relname, constants.RUNPARTS_ERR, str(err)))
|
762 |
|
else:
|
763 |
|
rr.append((relname, constants.RUNPARTS_RUN, result))
|
764 |
|
|
765 |
|
return rr
|
766 |
|
|
767 |
|
|
768 |
|
def RemoveFile(filename):
|
769 |
|
"""Remove a file ignoring some errors.
|
770 |
|
|
771 |
|
Remove a file, ignoring non-existing ones or directories. Other
|
772 |
|
errors are passed.
|
773 |
|
|
774 |
|
@type filename: str
|
775 |
|
@param filename: the file to be removed
|
776 |
|
|
777 |
|
"""
|
778 |
|
try:
|
779 |
|
os.unlink(filename)
|
780 |
|
except OSError, err:
|
781 |
|
if err.errno not in (errno.ENOENT, errno.EISDIR):
|
782 |
|
raise
|
783 |
|
|
784 |
|
|
785 |
|
def RemoveDir(dirname):
|
786 |
|
"""Remove an empty directory.
|
787 |
|
|
788 |
|
Remove a directory, ignoring non-existing ones.
|
789 |
|
Other errors are passed. This includes the case,
|
790 |
|
where the directory is not empty, so it can't be removed.
|
791 |
|
|
792 |
|
@type dirname: str
|
793 |
|
@param dirname: the empty directory to be removed
|
794 |
|
|
795 |
|
"""
|
796 |
|
try:
|
797 |
|
os.rmdir(dirname)
|
798 |
|
except OSError, err:
|
799 |
|
if err.errno != errno.ENOENT:
|
800 |
|
raise
|
801 |
|
|
802 |
|
|
803 |
|
def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
|
804 |
|
"""Renames a file.
|
805 |
|
|
806 |
|
@type old: string
|
807 |
|
@param old: Original path
|
808 |
|
@type new: string
|
809 |
|
@param new: New path
|
810 |
|
@type mkdir: bool
|
811 |
|
@param mkdir: Whether to create target directory if it doesn't exist
|
812 |
|
@type mkdir_mode: int
|
813 |
|
@param mkdir_mode: Mode for newly created directories
|
814 |
|
|
815 |
|
"""
|
816 |
|
try:
|
817 |
|
return os.rename(old, new)
|
818 |
|
except OSError, err:
|
819 |
|
# In at least one use case of this function, the job queue, directory
|
820 |
|
# creation is very rare. Checking for the directory before renaming is not
|
821 |
|
# as efficient.
|
822 |
|
if mkdir and err.errno == errno.ENOENT:
|
823 |
|
# Create directory and try again
|
824 |
|
Makedirs(os.path.dirname(new), mode=mkdir_mode)
|
825 |
|
|
826 |
|
return os.rename(old, new)
|
827 |
|
|
828 |
|
raise
|
829 |
|
|
830 |
|
|
831 |
|
def Makedirs(path, mode=0750):
|
832 |
|
"""Super-mkdir; create a leaf directory and all intermediate ones.
|
833 |
|
|
834 |
|
This is a wrapper around C{os.makedirs} adding error handling not implemented
|
835 |
|
before Python 2.5.
|
836 |
|
|
837 |
|
"""
|
838 |
|
try:
|
839 |
|
os.makedirs(path, mode)
|
840 |
|
except OSError, err:
|
841 |
|
# Ignore EEXIST. This is only handled in os.makedirs as included in
|
842 |
|
# Python 2.5 and above.
|
843 |
|
if err.errno != errno.EEXIST or not os.path.exists(path):
|
844 |
|
raise
|
845 |
|
|
846 |
|
|
847 |
|
def ResetTempfileModule():
|
848 |
|
"""Resets the random name generator of the tempfile module.
|
849 |
|
|
850 |
|
This function should be called after C{os.fork} in the child process to
|
851 |
|
ensure it creates a newly seeded random generator. Otherwise it would
|
852 |
|
generate the same random parts as the parent process. If several processes
|
853 |
|
race for the creation of a temporary file, this could lead to one not getting
|
854 |
|
a temporary name.
|
855 |
|
|
856 |
|
"""
|
857 |
|
# pylint: disable-msg=W0212
|
858 |
|
if hasattr(tempfile, "_once_lock") and hasattr(tempfile, "_name_sequence"):
|
859 |
|
tempfile._once_lock.acquire()
|
860 |
|
try:
|
861 |
|
# Reset random name generator
|
862 |
|
tempfile._name_sequence = None
|
863 |
|
finally:
|
864 |
|
tempfile._once_lock.release()
|
865 |
|
else:
|
866 |
|
logging.critical("The tempfile module misses at least one of the"
|
867 |
|
" '_once_lock' and '_name_sequence' attributes")
|
868 |
|
|
869 |
|
|
870 |
|
def _FingerprintFile(filename):
|
871 |
|
"""Compute the fingerprint of a file.
|
872 |
|
|
873 |
|
If the file does not exist, a None will be returned
|
874 |
|
instead.
|
875 |
|
|
876 |
|
@type filename: str
|
877 |
|
@param filename: the filename to checksum
|
878 |
|
@rtype: str
|
879 |
|
@return: the hex digest of the sha checksum of the contents
|
880 |
|
of the file
|
881 |
|
|
882 |
|
"""
|
883 |
|
if not (os.path.exists(filename) and os.path.isfile(filename)):
|
884 |
|
return None
|
885 |
|
|
886 |
|
f = open(filename)
|
887 |
|
|
888 |
|
fp = compat.sha1_hash()
|
889 |
|
while True:
|
890 |
|
data = f.read(4096)
|
891 |
|
if not data:
|
892 |
|
break
|
893 |
|
|
894 |
|
fp.update(data)
|
895 |
|
|
896 |
|
return fp.hexdigest()
|
897 |
|
|
898 |
|
|
899 |
|
def FingerprintFiles(files):
|
900 |
|
"""Compute fingerprints for a list of files.
|
901 |
|
|
902 |
|
@type files: list
|
903 |
|
@param files: the list of filename to fingerprint
|
904 |
|
@rtype: dict
|
905 |
|
@return: a dictionary filename: fingerprint, holding only
|
906 |
|
existing files
|
907 |
|
|
908 |
|
"""
|
909 |
|
ret = {}
|
910 |
|
|
911 |
|
for filename in files:
|
912 |
|
cksum = _FingerprintFile(filename)
|
913 |
|
if cksum:
|
914 |
|
ret[filename] = cksum
|
915 |
|
|
916 |
|
return ret
|
917 |
|
|
918 |
|
|
919 |
|
def ForceDictType(target, key_types, allowed_values=None):
|
920 |
|
"""Force the values of a dict to have certain types.
|
921 |
|
|
922 |
|
@type target: dict
|
923 |
|
@param target: the dict to update
|
924 |
|
@type key_types: dict
|
925 |
|
@param key_types: dict mapping target dict keys to types
|
926 |
|
in constants.ENFORCEABLE_TYPES
|
927 |
|
@type allowed_values: list
|
928 |
|
@keyword allowed_values: list of specially allowed values
|
929 |
|
|
930 |
|
"""
|
931 |
|
if allowed_values is None:
|
932 |
|
allowed_values = []
|
933 |
|
|
934 |
|
if not isinstance(target, dict):
|
935 |
|
msg = "Expected dictionary, got '%s'" % target
|
936 |
|
raise errors.TypeEnforcementError(msg)
|
937 |
|
|
938 |
|
for key in target:
|
939 |
|
if key not in key_types:
|
940 |
|
msg = "Unknown key '%s'" % key
|
941 |
|
raise errors.TypeEnforcementError(msg)
|
942 |
|
|
943 |
|
if target[key] in allowed_values:
|
944 |
|
continue
|
945 |
|
|
946 |
|
ktype = key_types[key]
|
947 |
|
if ktype not in constants.ENFORCEABLE_TYPES:
|
948 |
|
msg = "'%s' has non-enforceable type %s" % (key, ktype)
|
949 |
|
raise errors.ProgrammerError(msg)
|
950 |
|
|
951 |
|
if ktype in (constants.VTYPE_STRING, constants.VTYPE_MAYBE_STRING):
|
952 |
|
if target[key] is None and ktype == constants.VTYPE_MAYBE_STRING:
|
953 |
|
pass
|
954 |
|
elif not isinstance(target[key], basestring):
|
955 |
|
if isinstance(target[key], bool) and not target[key]:
|
956 |
|
target[key] = ''
|
957 |
|
else:
|
958 |
|
msg = "'%s' (value %s) is not a valid string" % (key, target[key])
|
959 |
|
raise errors.TypeEnforcementError(msg)
|
960 |
|
elif ktype == constants.VTYPE_BOOL:
|
961 |
|
if isinstance(target[key], basestring) and target[key]:
|
962 |
|
if target[key].lower() == constants.VALUE_FALSE:
|
963 |
|
target[key] = False
|
964 |
|
elif target[key].lower() == constants.VALUE_TRUE:
|
965 |
|
target[key] = True
|
966 |
|
else:
|
967 |
|
msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
|
968 |
|
raise errors.TypeEnforcementError(msg)
|
969 |
|
elif target[key]:
|
970 |
|
target[key] = True
|
971 |
|
else:
|
972 |
|
target[key] = False
|
973 |
|
elif ktype == constants.VTYPE_SIZE:
|
974 |
|
try:
|
975 |
|
target[key] = ParseUnit(target[key])
|
976 |
|
except errors.UnitParseError, err:
|
977 |
|
msg = "'%s' (value %s) is not a valid size. error: %s" % \
|
978 |
|
(key, target[key], err)
|
979 |
|
raise errors.TypeEnforcementError(msg)
|
980 |
|
elif ktype == constants.VTYPE_INT:
|
981 |
|
try:
|
982 |
|
target[key] = int(target[key])
|
983 |
|
except (ValueError, TypeError):
|
984 |
|
msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
|
985 |
|
raise errors.TypeEnforcementError(msg)
|
986 |
|
|
987 |
|
|
988 |
|
def _GetProcStatusPath(pid):
|
989 |
|
"""Returns the path for a PID's proc status file.
|
990 |
|
|
991 |
|
@type pid: int
|
992 |
|
@param pid: Process ID
|
993 |
|
@rtype: string
|
994 |
|
|
995 |
|
"""
|
996 |
|
return "/proc/%d/status" % pid
|
997 |
|
|
998 |
|
|
999 |
|
def IsProcessAlive(pid):
|
1000 |
|
"""Check if a given pid exists on the system.
|
1001 |
|
|
1002 |
|
@note: zombie status is not handled, so zombie processes
|
1003 |
|
will be returned as alive
|
1004 |
|
@type pid: int
|
1005 |
|
@param pid: the process ID to check
|
1006 |
|
@rtype: boolean
|
1007 |
|
@return: True if the process exists
|
1008 |
|
|
1009 |
|
"""
|
1010 |
|
def _TryStat(name):
|
1011 |
|
try:
|
1012 |
|
os.stat(name)
|
1013 |
|
return True
|
1014 |
|
except EnvironmentError, err:
|
1015 |
|
if err.errno in (errno.ENOENT, errno.ENOTDIR):
|
1016 |
|
return False
|
1017 |
|
elif err.errno == errno.EINVAL:
|
1018 |
|
raise RetryAgain(err)
|
1019 |
|
raise
|
1020 |
|
|
1021 |
|
assert isinstance(pid, int), "pid must be an integer"
|
1022 |
|
if pid <= 0:
|
1023 |
|
return False
|
1024 |
|
|
1025 |
|
# /proc in a multiprocessor environment can have strange behaviors.
|
1026 |
|
# Retry the os.stat a few times until we get a good result.
|
1027 |
|
try:
|
1028 |
|
return Retry(_TryStat, (0.01, 1.5, 0.1), 0.5,
|
1029 |
|
args=[_GetProcStatusPath(pid)])
|
1030 |
|
except RetryTimeout, err:
|
1031 |
|
err.RaiseInner()
|
1032 |
|
|
1033 |
|
|
1034 |
|
def _ParseSigsetT(sigset):
|
1035 |
|
"""Parse a rendered sigset_t value.
|
1036 |
|
|
1037 |
|
This is the opposite of the Linux kernel's fs/proc/array.c:render_sigset_t
|
1038 |
|
function.
|
1039 |
|
|
1040 |
|
@type sigset: string
|
1041 |
|
@param sigset: Rendered signal set from /proc/$pid/status
|
1042 |
|
@rtype: set
|
1043 |
|
@return: Set of all enabled signal numbers
|
1044 |
|
|
1045 |
|
"""
|
1046 |
|
result = set()
|
1047 |
|
|
1048 |
|
signum = 0
|
1049 |
|
for ch in reversed(sigset):
|
1050 |
|
chv = int(ch, 16)
|
1051 |
|
|
1052 |
|
# The following could be done in a loop, but it's easier to read and
|
1053 |
|
# understand in the unrolled form
|
1054 |
|
if chv & 1:
|
1055 |
|
result.add(signum + 1)
|
1056 |
|
if chv & 2:
|
1057 |
|
result.add(signum + 2)
|
1058 |
|
if chv & 4:
|
1059 |
|
result.add(signum + 3)
|
1060 |
|
if chv & 8:
|
1061 |
|
result.add(signum + 4)
|
1062 |
|
|
1063 |
|
signum += 4
|
1064 |
|
|
1065 |
|
return result
|
1066 |
|
|
1067 |
|
|
1068 |
|
def _GetProcStatusField(pstatus, field):
|
1069 |
|
"""Retrieves a field from the contents of a proc status file.
|
1070 |
|
|
1071 |
|
@type pstatus: string
|
1072 |
|
@param pstatus: Contents of /proc/$pid/status
|
1073 |
|
@type field: string
|
1074 |
|
@param field: Name of field whose value should be returned
|
1075 |
|
@rtype: string
|
1076 |
|
|
1077 |
|
"""
|
1078 |
|
for line in pstatus.splitlines():
|
1079 |
|
parts = line.split(":", 1)
|
1080 |
|
|
1081 |
|
if len(parts) < 2 or parts[0] != field:
|
1082 |
|
continue
|
1083 |
|
|
1084 |
|
return parts[1].strip()
|
1085 |
|
|
1086 |
|
return None
|
1087 |
|
|
1088 |
|
|
1089 |
|
def IsProcessHandlingSignal(pid, signum, status_path=None):
|
1090 |
|
"""Checks whether a process is handling a signal.
|
1091 |
|
|
1092 |
|
@type pid: int
|
1093 |
|
@param pid: Process ID
|
1094 |
|
@type signum: int
|
1095 |
|
@param signum: Signal number
|
1096 |
|
@rtype: bool
|
1097 |
|
|
1098 |
|
"""
|
1099 |
|
if status_path is None:
|
1100 |
|
status_path = _GetProcStatusPath(pid)
|
1101 |
|
|
1102 |
|
try:
|
1103 |
|
proc_status = ReadFile(status_path)
|
1104 |
|
except EnvironmentError, err:
|
1105 |
|
# In at least one case, reading /proc/$pid/status failed with ESRCH.
|
1106 |
|
if err.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL, errno.ESRCH):
|
1107 |
|
return False
|
1108 |
|
raise
|
1109 |
|
|
1110 |
|
sigcgt = _GetProcStatusField(proc_status, "SigCgt")
|
1111 |
|
if sigcgt is None:
|
1112 |
|
raise RuntimeError("%s is missing 'SigCgt' field" % status_path)
|
1113 |
|
|
1114 |
|
# Now check whether signal is handled
|
1115 |
|
return signum in _ParseSigsetT(sigcgt)
|
1116 |
|
|
1117 |
|
|
1118 |
|
def ReadPidFile(pidfile):
|
1119 |
|
"""Read a pid from a file.
|
1120 |
|
|
1121 |
|
@type pidfile: string
|
1122 |
|
@param pidfile: path to the file containing the pid
|
1123 |
|
@rtype: int
|
1124 |
|
@return: The process id, if the file exists and contains a valid PID,
|
1125 |
|
otherwise 0
|
1126 |
|
|
1127 |
|
"""
|
1128 |
|
try:
|
1129 |
|
raw_data = ReadOneLineFile(pidfile)
|
1130 |
|
except EnvironmentError, err:
|
1131 |
|
if err.errno != errno.ENOENT:
|
1132 |
|
logging.exception("Can't read pid file")
|
1133 |
|
return 0
|
1134 |
|
|
1135 |
|
try:
|
1136 |
|
pid = int(raw_data)
|
1137 |
|
except (TypeError, ValueError), err:
|
1138 |
|
logging.info("Can't parse pid file contents", exc_info=True)
|
1139 |
|
return 0
|
1140 |
|
|
1141 |
|
return pid
|
1142 |
|
|
1143 |
|
|
1144 |
|
def ReadLockedPidFile(path):
|
1145 |
|
"""Reads a locked PID file.
|
1146 |
|
|
1147 |
|
This can be used together with L{StartDaemon}.
|
1148 |
|
|
1149 |
|
@type path: string
|
1150 |
|
@param path: Path to PID file
|
1151 |
|
@return: PID as integer or, if file was unlocked or couldn't be opened, None
|
1152 |
|
|
1153 |
|
"""
|
1154 |
|
try:
|
1155 |
|
fd = os.open(path, os.O_RDONLY)
|
1156 |
|
except EnvironmentError, err:
|
1157 |
|
if err.errno == errno.ENOENT:
|
1158 |
|
# PID file doesn't exist
|
1159 |
|
return None
|
1160 |
|
raise
|
1161 |
|
|
1162 |
|
try:
|
1163 |
|
try:
|
1164 |
|
# Try to acquire lock
|
1165 |
|
LockFile(fd)
|
1166 |
|
except errors.LockError:
|
1167 |
|
# Couldn't lock, daemon is running
|
1168 |
|
return int(os.read(fd, 100))
|
1169 |
|
finally:
|
1170 |
|
os.close(fd)
|
1171 |
|
|
1172 |
|
return None
|
1173 |
|
|
1174 |
|
|
1175 |
|
def MatchNameComponent(key, name_list, case_sensitive=True):
|
1176 |
|
"""Try to match a name against a list.
|
1177 |
|
|
1178 |
|
This function will try to match a name like test1 against a list
|
1179 |
|
like C{['test1.example.com', 'test2.example.com', ...]}. Against
|
1180 |
|
this list, I{'test1'} as well as I{'test1.example'} will match, but
|
1181 |
|
not I{'test1.ex'}. A multiple match will be considered as no match
|
1182 |
|
at all (e.g. I{'test1'} against C{['test1.example.com',
|
1183 |
|
'test1.example.org']}), except when the key fully matches an entry
|
1184 |
|
(e.g. I{'test1'} against C{['test1', 'test1.example.com']}).
|
1185 |
|
|
1186 |
|
@type key: str
|
1187 |
|
@param key: the name to be searched
|
1188 |
|
@type name_list: list
|
1189 |
|
@param name_list: the list of strings against which to search the key
|
1190 |
|
@type case_sensitive: boolean
|
1191 |
|
@param case_sensitive: whether to provide a case-sensitive match
|
1192 |
|
|
1193 |
|
@rtype: None or str
|
1194 |
|
@return: None if there is no match I{or} if there are multiple matches,
|
1195 |
|
otherwise the element from the list which matches
|
1196 |
|
|
1197 |
|
"""
|
1198 |
|
if key in name_list:
|
1199 |
|
return key
|
1200 |
|
|
1201 |
|
re_flags = 0
|
1202 |
|
if not case_sensitive:
|
1203 |
|
re_flags |= re.IGNORECASE
|
1204 |
|
key = key.upper()
|
1205 |
|
mo = re.compile("^%s(\..*)?$" % re.escape(key), re_flags)
|
1206 |
|
names_filtered = []
|
1207 |
|
string_matches = []
|
1208 |
|
for name in name_list:
|
1209 |
|
if mo.match(name) is not None:
|
1210 |
|
names_filtered.append(name)
|
1211 |
|
if not case_sensitive and key == name.upper():
|
1212 |
|
string_matches.append(name)
|
1213 |
|
|
1214 |
|
if len(string_matches) == 1:
|
1215 |
|
return string_matches[0]
|
1216 |
|
if len(names_filtered) == 1:
|
1217 |
|
return names_filtered[0]
|
1218 |
|
return None
|
1219 |
|
|
1220 |
|
|
1221 |
|
def ValidateServiceName(name):
|
1222 |
|
"""Validate the given service name.
|
1223 |
|
|
1224 |
|
@type name: number or string
|
1225 |
|
@param name: Service name or port specification
|
1226 |
|
|
1227 |
|
"""
|
1228 |
|
try:
|
1229 |
|
numport = int(name)
|
1230 |
|
except (ValueError, TypeError):
|
1231 |
|
# Non-numeric service name
|
1232 |
|
valid = _VALID_SERVICE_NAME_RE.match(name)
|
1233 |
|
else:
|
1234 |
|
# Numeric port (protocols other than TCP or UDP might need adjustments
|
1235 |
|
# here)
|
1236 |
|
valid = (numport >= 0 and numport < (1 << 16))
|
1237 |
|
|
1238 |
|
if not valid:
|
1239 |
|
raise errors.OpPrereqError("Invalid service name '%s'" % name,
|
1240 |
|
errors.ECODE_INVAL)
|
1241 |
|
|
1242 |
|
return name
|
1243 |
|
|
1244 |
|
|
1245 |
|
def ListVolumeGroups():
|
1246 |
|
"""List volume groups and their size
|
1247 |
|
|
1248 |
|
@rtype: dict
|
1249 |
|
@return:
|
1250 |
|
Dictionary with keys volume name and values
|
1251 |
|
the size of the volume
|
1252 |
|
|
1253 |
|
"""
|
1254 |
|
command = "vgs --noheadings --units m --nosuffix -o name,size"
|
1255 |
|
result = RunCmd(command)
|
1256 |
|
retval = {}
|
1257 |
|
if result.failed:
|
1258 |
|
return retval
|
1259 |
|
|
1260 |
|
for line in result.stdout.splitlines():
|
1261 |
|
try:
|
1262 |
|
name, size = line.split()
|
1263 |
|
size = int(float(size))
|
1264 |
|
except (IndexError, ValueError), err:
|
1265 |
|
logging.error("Invalid output from vgs (%s): %s", err, line)
|
1266 |
|
continue
|
1267 |
|
|
1268 |
|
retval[name] = size
|
1269 |
|
|
1270 |
|
return retval
|
1271 |
|
|
1272 |
|
|
1273 |
|
def BridgeExists(bridge):
|
1274 |
|
"""Check whether the given bridge exists in the system
|
1275 |
|
|
1276 |
|
@type bridge: str
|
1277 |
|
@param bridge: the bridge name to check
|
1278 |
|
@rtype: boolean
|
1279 |
|
@return: True if it does
|
1280 |
|
|
1281 |
|
"""
|
1282 |
|
return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
|
1283 |
|
|
1284 |
|
|
1285 |
|
def _NiceSortTryInt(val):
|
1286 |
|
"""Attempts to convert a string to an integer.
|
1287 |
|
|
1288 |
|
"""
|
1289 |
|
if val and _SORTER_DIGIT.match(val):
|
1290 |
|
return int(val)
|
1291 |
|
else:
|
1292 |
|
return val
|
1293 |
|
|
1294 |
|
|
1295 |
|
def _NiceSortKey(value):
|
1296 |
|
"""Extract key for sorting.
|
1297 |
|
|
1298 |
|
"""
|
1299 |
|
return [_NiceSortTryInt(grp)
|
1300 |
|
for grp in _SORTER_RE.match(value).groups()]
|
1301 |
|
|
1302 |
|
|
1303 |
|
def NiceSort(values, key=None):
|
1304 |
|
"""Sort a list of strings based on digit and non-digit groupings.
|
1305 |
|
|
1306 |
|
Given a list of names C{['a1', 'a10', 'a11', 'a2']} this function
|
1307 |
|
will sort the list in the logical order C{['a1', 'a2', 'a10',
|
1308 |
|
'a11']}.
|
1309 |
|
|
1310 |
|
The sort algorithm breaks each name in groups of either only-digits
|
1311 |
|
or no-digits. Only the first eight such groups are considered, and
|
1312 |
|
after that we just use what's left of the string.
|
1313 |
|
|
1314 |
|
@type values: list
|
1315 |
|
@param values: the names to be sorted
|
1316 |
|
@type key: callable or None
|
1317 |
|
@param key: function of one argument to extract a comparison key from each
|
1318 |
|
list element, must return string
|
1319 |
|
@rtype: list
|
1320 |
|
@return: a copy of the name list sorted with our algorithm
|
1321 |
|
|
1322 |
|
"""
|
1323 |
|
if key is None:
|
1324 |
|
keyfunc = _NiceSortKey
|
1325 |
|
else:
|
1326 |
|
keyfunc = lambda value: _NiceSortKey(key(value))
|
1327 |
|
|
1328 |
|
return sorted(values, key=keyfunc)
|
1329 |
|
|
1330 |
|
|
1331 |
|
def TryConvert(fn, val):
|
1332 |
|
"""Try to convert a value ignoring errors.
|
1333 |
|
|
1334 |
|
This function tries to apply function I{fn} to I{val}. If no
|
1335 |
|
C{ValueError} or C{TypeError} exceptions are raised, it will return
|
1336 |
|
the result, else it will return the original value. Any other
|
1337 |
|
exceptions are propagated to the caller.
|
1338 |
|
|
1339 |
|
@type fn: callable
|
1340 |
|
@param fn: function to apply to the value
|
1341 |
|
@param val: the value to be converted
|
1342 |
|
@return: The converted value if the conversion was successful,
|
1343 |
|
otherwise the original value.
|
1344 |
|
|
1345 |
|
"""
|
1346 |
|
try:
|
1347 |
|
nv = fn(val)
|
1348 |
|
except (ValueError, TypeError):
|
1349 |
|
nv = val
|
1350 |
|
return nv
|
1351 |
|
|
1352 |
|
|
1353 |
|
def IsValidShellParam(word):
|
1354 |
|
"""Verifies is the given word is safe from the shell's p.o.v.
|
1355 |
|
|
1356 |
|
This means that we can pass this to a command via the shell and be
|
1357 |
|
sure that it doesn't alter the command line and is passed as such to
|
1358 |
|
the actual command.
|
1359 |
|
|
1360 |
|
Note that we are overly restrictive here, in order to be on the safe
|
1361 |
|
side.
|
1362 |
|
|
1363 |
|
@type word: str
|
1364 |
|
@param word: the word to check
|
1365 |
|
@rtype: boolean
|
1366 |
|
@return: True if the word is 'safe'
|
1367 |
|
|
1368 |
|
"""
|
1369 |
|
return bool(_SHELLPARAM_REGEX.match(word))
|
1370 |
|
|
1371 |
|
|
1372 |
|
def BuildShellCmd(template, *args):
|
1373 |
|
"""Build a safe shell command line from the given arguments.
|
1374 |
|
|
1375 |
|
This function will check all arguments in the args list so that they
|
1376 |
|
are valid shell parameters (i.e. they don't contain shell
|
1377 |
|
metacharacters). If everything is ok, it will return the result of
|
1378 |
|
template % args.
|
1379 |
|
|
1380 |
|
@type template: str
|
1381 |
|
@param template: the string holding the template for the
|
1382 |
|
string formatting
|
1383 |
|
@rtype: str
|
1384 |
|
@return: the expanded command line
|
1385 |
|
|
1386 |
|
"""
|
1387 |
|
for word in args:
|
1388 |
|
if not IsValidShellParam(word):
|
1389 |
|
raise errors.ProgrammerError("Shell argument '%s' contains"
|
1390 |
|
" invalid characters" % word)
|
1391 |
|
return template % args
|
1392 |
|
|
1393 |
|
|
1394 |
|
def FormatUnit(value, units):
|
1395 |
|
"""Formats an incoming number of MiB with the appropriate unit.
|
1396 |
|
|
1397 |
|
@type value: int
|
1398 |
|
@param value: integer representing the value in MiB (1048576)
|
1399 |
|
@type units: char
|
1400 |
|
@param units: the type of formatting we should do:
|
1401 |
|
- 'h' for automatic scaling
|
1402 |
|
- 'm' for MiBs
|
1403 |
|
- 'g' for GiBs
|
1404 |
|
- 't' for TiBs
|
1405 |
|
@rtype: str
|
1406 |
|
@return: the formatted value (with suffix)
|
1407 |
|
|
1408 |
|
"""
|
1409 |
|
if units not in ('m', 'g', 't', 'h'):
|
1410 |
|
raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
|
1411 |
|
|
1412 |
|
suffix = ''
|
1413 |
|
|
1414 |
|
if units == 'm' or (units == 'h' and value < 1024):
|
1415 |
|
if units == 'h':
|
1416 |
|
suffix = 'M'
|
1417 |
|
return "%d%s" % (round(value, 0), suffix)
|
1418 |
|
|
1419 |
|
elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
|
1420 |
|
if units == 'h':
|
1421 |
|
suffix = 'G'
|
1422 |
|
return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
|
1423 |
|
|
1424 |
|
else:
|
1425 |
|
if units == 'h':
|
1426 |
|
suffix = 'T'
|
1427 |
|
return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
|
1428 |
|
|
1429 |
|
|
1430 |
|
def ParseUnit(input_string):
|
1431 |
|
"""Tries to extract number and scale from the given string.
|
1432 |
|
|
1433 |
|
Input must be in the format C{NUMBER+ [DOT NUMBER+] SPACE*
|
1434 |
|
[UNIT]}. If no unit is specified, it defaults to MiB. Return value
|
1435 |
|
is always an int in MiB.
|
1436 |
|
|
1437 |
|
"""
|
1438 |
|
m = _PARSEUNIT_REGEX.match(str(input_string))
|