Statistics
| Branch: | Tag: | Revision:

root / lib / utils / __init__.py @ 71ab9dbe

History | View | Annotate | Download (88.2 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2010, 2011 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 signal
46
import OpenSSL
47
import datetime
48
import calendar
49
import hmac
50

    
51
from cStringIO import StringIO
52

    
53
from ganeti import errors
54
from ganeti import constants
55
from ganeti import compat
56

    
57
from ganeti.utils.algo import * # pylint: disable-msg=W0401
58
from ganeti.utils.retry import * # pylint: disable-msg=W0401
59
from ganeti.utils.text import * # pylint: disable-msg=W0401
60
from ganeti.utils.mlock import * # pylint: disable-msg=W0401
61
from ganeti.utils.log import * # pylint: disable-msg=W0401
62

    
63
_locksheld = []
64

    
65
debug_locks = False
66

    
67
#: when set to True, L{RunCmd} is disabled
68
no_fork = False
69

    
70
_RANDOM_UUID_FILE = "/proc/sys/kernel/random/uuid"
71

    
72
HEX_CHAR_RE = r"[a-zA-Z0-9]"
73
VALID_X509_SIGNATURE_SALT = re.compile("^%s+$" % HEX_CHAR_RE, re.S)
74
X509_SIGNATURE = re.compile(r"^%s:\s*(?P<salt>%s+)/(?P<sign>%s+)$" %
75
                            (re.escape(constants.X509_CERT_SIGNATURE_HEADER),
76
                             HEX_CHAR_RE, HEX_CHAR_RE),
77
                            re.S | re.I)
78

    
79
_VALID_SERVICE_NAME_RE = re.compile("^[-_.a-zA-Z0-9]{1,128}$")
80

    
81
UUID_RE = re.compile('^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-'
82
                     '[a-f0-9]{4}-[a-f0-9]{12}$')
83

    
84
# Certificate verification results
85
(CERT_WARNING,
86
 CERT_ERROR) = range(1, 3)
87

    
88
(_TIMEOUT_NONE,
89
 _TIMEOUT_TERM,
90
 _TIMEOUT_KILL) = range(3)
91

    
92
#: Shell param checker regexp
93
_SHELLPARAM_REGEX = re.compile(r"^[-a-zA-Z0-9._+/:%@]+$")
94

    
95
#: ASN1 time regexp
96
_ASN1_TIME_REGEX = re.compile(r"^(\d+)([-+]\d\d)(\d\d)$")
97

    
98

    
99
class RunResult(object):
100
  """Holds the result of running external programs.
101

102
  @type exit_code: int
103
  @ivar exit_code: the exit code of the program, or None (if the program
104
      didn't exit())
105
  @type signal: int or None
106
  @ivar signal: the signal that caused the program to finish, or None
107
      (if the program wasn't terminated by a signal)
108
  @type stdout: str
109
  @ivar stdout: the standard output of the program
110
  @type stderr: str
111
  @ivar stderr: the standard error of the program
112
  @type failed: boolean
113
  @ivar failed: True in case the program was
114
      terminated by a signal or exited with a non-zero exit code
115
  @ivar fail_reason: a string detailing the termination reason
116

117
  """
118
  __slots__ = ["exit_code", "signal", "stdout", "stderr",
119
               "failed", "fail_reason", "cmd"]
120

    
121

    
122
  def __init__(self, exit_code, signal_, stdout, stderr, cmd, timeout_action,
123
               timeout):
124
    self.cmd = cmd
125
    self.exit_code = exit_code
126
    self.signal = signal_
127
    self.stdout = stdout
128
    self.stderr = stderr
129
    self.failed = (signal_ is not None or exit_code != 0)
130

    
131
    fail_msgs = []
132
    if self.signal is not None:
133
      fail_msgs.append("terminated by signal %s" % self.signal)
134
    elif self.exit_code is not None:
135
      fail_msgs.append("exited with exit code %s" % self.exit_code)
136
    else:
137
      fail_msgs.append("unable to determine termination reason")
138

    
139
    if timeout_action == _TIMEOUT_TERM:
140
      fail_msgs.append("terminated after timeout of %.2f seconds" % timeout)
141
    elif timeout_action == _TIMEOUT_KILL:
142
      fail_msgs.append(("force termination after timeout of %.2f seconds"
143
                        " and linger for another %.2f seconds") %
144
                       (timeout, constants.CHILD_LINGER_TIMEOUT))
145

    
146
    if fail_msgs and self.failed:
147
      self.fail_reason = CommaJoin(fail_msgs)
148

    
149
    if self.failed:
150
      logging.debug("Command '%s' failed (%s); output: %s",
151
                    self.cmd, self.fail_reason, self.output)
152

    
153
  def _GetOutput(self):
154
    """Returns the combined stdout and stderr for easier usage.
155

156
    """
157
    return self.stdout + self.stderr
158

    
159
  output = property(_GetOutput, None, None, "Return full output")
160

    
161

    
162
def _BuildCmdEnvironment(env, reset):
163
  """Builds the environment for an external program.
164

165
  """
166
  if reset:
167
    cmd_env = {}
168
  else:
169
    cmd_env = os.environ.copy()
170
    cmd_env["LC_ALL"] = "C"
171

    
172
  if env is not None:
173
    cmd_env.update(env)
174

    
175
  return cmd_env
176

    
177

    
178
def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False,
179
           interactive=False, timeout=None):
180
  """Execute a (shell) command.
181

182
  The command should not read from its standard input, as it will be
183
  closed.
184

185
  @type cmd: string or list
186
  @param cmd: Command to run
187
  @type env: dict
188
  @param env: Additional environment variables
189
  @type output: str
190
  @param output: if desired, the output of the command can be
191
      saved in a file instead of the RunResult instance; this
192
      parameter denotes the file name (if not None)
193
  @type cwd: string
194
  @param cwd: if specified, will be used as the working
195
      directory for the command; the default will be /
196
  @type reset_env: boolean
197
  @param reset_env: whether to reset or keep the default os environment
198
  @type interactive: boolean
199
  @param interactive: weather we pipe stdin, stdout and stderr
200
                      (default behaviour) or run the command interactive
201
  @type timeout: int
202
  @param timeout: If not None, timeout in seconds until child process gets
203
                  killed
204
  @rtype: L{RunResult}
205
  @return: RunResult instance
206
  @raise errors.ProgrammerError: if we call this when forks are disabled
207

208
  """
209
  if no_fork:
210
    raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
211

    
212
  if output and interactive:
213
    raise errors.ProgrammerError("Parameters 'output' and 'interactive' can"
214
                                 " not be provided at the same time")
215

    
216
  if isinstance(cmd, basestring):
217
    strcmd = cmd
218
    shell = True
219
  else:
220
    cmd = [str(val) for val in cmd]
221
    strcmd = ShellQuoteArgs(cmd)
222
    shell = False
223

    
224
  if output:
225
    logging.debug("RunCmd %s, output file '%s'", strcmd, output)
226
  else:
227
    logging.debug("RunCmd %s", strcmd)
228

    
229
  cmd_env = _BuildCmdEnvironment(env, reset_env)
230

    
231
  try:
232
    if output is None:
233
      out, err, status, timeout_action = _RunCmdPipe(cmd, cmd_env, shell, cwd,
234
                                                     interactive, timeout)
235
    else:
236
      timeout_action = _TIMEOUT_NONE
237
      status = _RunCmdFile(cmd, cmd_env, shell, output, cwd)
238
      out = err = ""
239
  except OSError, err:
240
    if err.errno == errno.ENOENT:
241
      raise errors.OpExecError("Can't execute '%s': not found (%s)" %
242
                               (strcmd, err))
243
    else:
244
      raise
245

    
246
  if status >= 0:
247
    exitcode = status
248
    signal_ = None
249
  else:
250
    exitcode = None
251
    signal_ = -status
252

    
253
  return RunResult(exitcode, signal_, out, err, strcmd, timeout_action, timeout)
254

    
255

    
256
def SetupDaemonEnv(cwd="/", umask=077):
257
  """Setup a daemon's environment.
258

259
  This should be called between the first and second fork, due to
260
  setsid usage.
261

262
  @param cwd: the directory to which to chdir
263
  @param umask: the umask to setup
264

265
  """
266
  os.chdir(cwd)
267
  os.umask(umask)
268
  os.setsid()
269

    
270

    
271
def SetupDaemonFDs(output_file, output_fd):
272
  """Setups up a daemon's file descriptors.
273

274
  @param output_file: if not None, the file to which to redirect
275
      stdout/stderr
276
  @param output_fd: if not None, the file descriptor for stdout/stderr
277

278
  """
279
  # check that at most one is defined
280
  assert [output_file, output_fd].count(None) >= 1
281

    
282
  # Open /dev/null (read-only, only for stdin)
283
  devnull_fd = os.open(os.devnull, os.O_RDONLY)
284

    
285
  if output_fd is not None:
286
    pass
287
  elif output_file is not None:
288
    # Open output file
289
    try:
290
      output_fd = os.open(output_file,
291
                          os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0600)
292
    except EnvironmentError, err:
293
      raise Exception("Opening output file failed: %s" % err)
294
  else:
295
    output_fd = os.open(os.devnull, os.O_WRONLY)
296

    
297
  # Redirect standard I/O
298
  os.dup2(devnull_fd, 0)
299
  os.dup2(output_fd, 1)
300
  os.dup2(output_fd, 2)
301

    
302

    
303
def StartDaemon(cmd, env=None, cwd="/", output=None, output_fd=None,
304
                pidfile=None):
305
  """Start a daemon process after forking twice.
306

307
  @type cmd: string or list
308
  @param cmd: Command to run
309
  @type env: dict
310
  @param env: Additional environment variables
311
  @type cwd: string
312
  @param cwd: Working directory for the program
313
  @type output: string
314
  @param output: Path to file in which to save the output
315
  @type output_fd: int
316
  @param output_fd: File descriptor for output
317
  @type pidfile: string
318
  @param pidfile: Process ID file
319
  @rtype: int
320
  @return: Daemon process ID
321
  @raise errors.ProgrammerError: if we call this when forks are disabled
322

323
  """
324
  if no_fork:
325
    raise errors.ProgrammerError("utils.StartDaemon() called with fork()"
326
                                 " disabled")
327

    
328
  if output and not (bool(output) ^ (output_fd is not None)):
329
    raise errors.ProgrammerError("Only one of 'output' and 'output_fd' can be"
330
                                 " specified")
331

    
332
  if isinstance(cmd, basestring):
333
    cmd = ["/bin/sh", "-c", cmd]
334

    
335
  strcmd = ShellQuoteArgs(cmd)
336

    
337
  if output:
338
    logging.debug("StartDaemon %s, output file '%s'", strcmd, output)
339
  else:
340
    logging.debug("StartDaemon %s", strcmd)
341

    
342
  cmd_env = _BuildCmdEnvironment(env, False)
343

    
344
  # Create pipe for sending PID back
345
  (pidpipe_read, pidpipe_write) = os.pipe()
346
  try:
347
    try:
348
      # Create pipe for sending error messages
349
      (errpipe_read, errpipe_write) = os.pipe()
350
      try:
351
        try:
352
          # First fork
353
          pid = os.fork()
354
          if pid == 0:
355
            try:
356
              # Child process, won't return
357
              _StartDaemonChild(errpipe_read, errpipe_write,
358
                                pidpipe_read, pidpipe_write,
359
                                cmd, cmd_env, cwd,
360
                                output, output_fd, pidfile)
361
            finally:
362
              # Well, maybe child process failed
363
              os._exit(1) # pylint: disable-msg=W0212
364
        finally:
365
          CloseFdNoError(errpipe_write)
366

    
367
        # Wait for daemon to be started (or an error message to
368
        # arrive) and read up to 100 KB as an error message
369
        errormsg = RetryOnSignal(os.read, errpipe_read, 100 * 1024)
370
      finally:
371
        CloseFdNoError(errpipe_read)
372
    finally:
373
      CloseFdNoError(pidpipe_write)
374

    
375
    # Read up to 128 bytes for PID
376
    pidtext = RetryOnSignal(os.read, pidpipe_read, 128)
377
  finally:
378
    CloseFdNoError(pidpipe_read)
379

    
380
  # Try to avoid zombies by waiting for child process
381
  try:
382
    os.waitpid(pid, 0)
383
  except OSError:
384
    pass
385

    
386
  if errormsg:
387
    raise errors.OpExecError("Error when starting daemon process: %r" %
388
                             errormsg)
389

    
390
  try:
391
    return int(pidtext)
392
  except (ValueError, TypeError), err:
393
    raise errors.OpExecError("Error while trying to parse PID %r: %s" %
394
                             (pidtext, err))
395

    
396

    
397
def _StartDaemonChild(errpipe_read, errpipe_write,
398
                      pidpipe_read, pidpipe_write,
399
                      args, env, cwd,
400
                      output, fd_output, pidfile):
401
  """Child process for starting daemon.
402

403
  """
404
  try:
405
    # Close parent's side
406
    CloseFdNoError(errpipe_read)
407
    CloseFdNoError(pidpipe_read)
408

    
409
    # First child process
410
    SetupDaemonEnv()
411

    
412
    # And fork for the second time
413
    pid = os.fork()
414
    if pid != 0:
415
      # Exit first child process
416
      os._exit(0) # pylint: disable-msg=W0212
417

    
418
    # Make sure pipe is closed on execv* (and thereby notifies
419
    # original process)
420
    SetCloseOnExecFlag(errpipe_write, True)
421

    
422
    # List of file descriptors to be left open
423
    noclose_fds = [errpipe_write]
424

    
425
    # Open PID file
426
    if pidfile:
427
      fd_pidfile = WritePidFile(pidfile)
428

    
429
      # Keeping the file open to hold the lock
430
      noclose_fds.append(fd_pidfile)
431

    
432
      SetCloseOnExecFlag(fd_pidfile, False)
433
    else:
434
      fd_pidfile = None
435

    
436
    SetupDaemonFDs(output, fd_output)
437

    
438
    # Send daemon PID to parent
439
    RetryOnSignal(os.write, pidpipe_write, str(os.getpid()))
440

    
441
    # Close all file descriptors except stdio and error message pipe
442
    CloseFDs(noclose_fds=noclose_fds)
443

    
444
    # Change working directory
445
    os.chdir(cwd)
446

    
447
    if env is None:
448
      os.execvp(args[0], args)
449
    else:
450
      os.execvpe(args[0], args, env)
451
  except: # pylint: disable-msg=W0702
452
    try:
453
      # Report errors to original process
454
      WriteErrorToFD(errpipe_write, str(sys.exc_info()[1]))
455
    except: # pylint: disable-msg=W0702
456
      # Ignore errors in error handling
457
      pass
458

    
459
  os._exit(1) # pylint: disable-msg=W0212
460

    
461

    
462
def WriteErrorToFD(fd, err):
463
  """Possibly write an error message to a fd.
464

465
  @type fd: None or int (file descriptor)
466
  @param fd: if not None, the error will be written to this fd
467
  @param err: string, the error message
468

469
  """
470
  if fd is None:
471
    return
472

    
473
  if not err:
474
    err = "<unknown error>"
475

    
476
  RetryOnSignal(os.write, fd, err)
477

    
478

    
479
def _CheckIfAlive(child):
480
  """Raises L{RetryAgain} if child is still alive.
481

482
  @raises RetryAgain: If child is still alive
483

484
  """
485
  if child.poll() is None:
486
    raise RetryAgain()
487

    
488

    
489
def _WaitForProcess(child, timeout):
490
  """Waits for the child to terminate or until we reach timeout.
491

492
  """
493
  try:
494
    Retry(_CheckIfAlive, (1.0, 1.2, 5.0), max(0, timeout), args=[child])
495
  except RetryTimeout:
496
    pass
497

    
498

    
499
def _RunCmdPipe(cmd, env, via_shell, cwd, interactive, timeout,
500
                _linger_timeout=constants.CHILD_LINGER_TIMEOUT):
501
  """Run a command and return its output.
502

503
  @type  cmd: string or list
504
  @param cmd: Command to run
505
  @type env: dict
506
  @param env: The environment to use
507
  @type via_shell: bool
508
  @param via_shell: if we should run via the shell
509
  @type cwd: string
510
  @param cwd: the working directory for the program
511
  @type interactive: boolean
512
  @param interactive: Run command interactive (without piping)
513
  @type timeout: int
514
  @param timeout: Timeout after the programm gets terminated
515
  @rtype: tuple
516
  @return: (out, err, status)
517

518
  """
519
  poller = select.poll()
520

    
521
  stderr = subprocess.PIPE
522
  stdout = subprocess.PIPE
523
  stdin = subprocess.PIPE
524

    
525
  if interactive:
526
    stderr = stdout = stdin = None
527

    
528
  child = subprocess.Popen(cmd, shell=via_shell,
529
                           stderr=stderr,
530
                           stdout=stdout,
531
                           stdin=stdin,
532
                           close_fds=True, env=env,
533
                           cwd=cwd)
534

    
535
  out = StringIO()
536
  err = StringIO()
537

    
538
  linger_timeout = None
539

    
540
  if timeout is None:
541
    poll_timeout = None
542
  else:
543
    poll_timeout = RunningTimeout(timeout, True).Remaining
544

    
545
  msg_timeout = ("Command %s (%d) run into execution timeout, terminating" %
546
                 (cmd, child.pid))
547
  msg_linger = ("Command %s (%d) run into linger timeout, killing" %
548
                (cmd, child.pid))
549

    
550
  timeout_action = _TIMEOUT_NONE
551

    
552
  if not interactive:
553
    child.stdin.close()
554
    poller.register(child.stdout, select.POLLIN)
555
    poller.register(child.stderr, select.POLLIN)
556
    fdmap = {
557
      child.stdout.fileno(): (out, child.stdout),
558
      child.stderr.fileno(): (err, child.stderr),
559
      }
560
    for fd in fdmap:
561
      SetNonblockFlag(fd, True)
562

    
563
    while fdmap:
564
      if poll_timeout:
565
        pt = poll_timeout() * 1000
566
        if pt < 0:
567
          if linger_timeout is None:
568
            logging.warning(msg_timeout)
569
            if child.poll() is None:
570
              timeout_action = _TIMEOUT_TERM
571
              IgnoreProcessNotFound(os.kill, child.pid, signal.SIGTERM)
572
            linger_timeout = RunningTimeout(_linger_timeout, True).Remaining
573
          pt = linger_timeout() * 1000
574
          if pt < 0:
575
            break
576
      else:
577
        pt = None
578

    
579
      pollresult = RetryOnSignal(poller.poll, pt)
580

    
581
      for fd, event in pollresult:
582
        if event & select.POLLIN or event & select.POLLPRI:
583
          data = fdmap[fd][1].read()
584
          # no data from read signifies EOF (the same as POLLHUP)
585
          if not data:
586
            poller.unregister(fd)
587
            del fdmap[fd]
588
            continue
589
          fdmap[fd][0].write(data)
590
        if (event & select.POLLNVAL or event & select.POLLHUP or
591
            event & select.POLLERR):
592
          poller.unregister(fd)
593
          del fdmap[fd]
594

    
595
  if timeout is not None:
596
    assert callable(poll_timeout)
597

    
598
    # We have no I/O left but it might still run
599
    if child.poll() is None:
600
      _WaitForProcess(child, poll_timeout())
601

    
602
    # Terminate if still alive after timeout
603
    if child.poll() is None:
604
      if linger_timeout is None:
605
        logging.warning(msg_timeout)
606
        timeout_action = _TIMEOUT_TERM
607
        IgnoreProcessNotFound(os.kill, child.pid, signal.SIGTERM)
608
        lt = _linger_timeout
609
      else:
610
        lt = linger_timeout()
611
      _WaitForProcess(child, lt)
612

    
613
    # Okay, still alive after timeout and linger timeout? Kill it!
614
    if child.poll() is None:
615
      timeout_action = _TIMEOUT_KILL
616
      logging.warning(msg_linger)
617
      IgnoreProcessNotFound(os.kill, child.pid, signal.SIGKILL)
618

    
619
  out = out.getvalue()
620
  err = err.getvalue()
621

    
622
  status = child.wait()
623
  return out, err, status, timeout_action
624

    
625

    
626
def _RunCmdFile(cmd, env, via_shell, output, cwd):
627
  """Run a command and save its output to a file.
628

629
  @type  cmd: string or list
630
  @param cmd: Command to run
631
  @type env: dict
632
  @param env: The environment to use
633
  @type via_shell: bool
634
  @param via_shell: if we should run via the shell
635
  @type output: str
636
  @param output: the filename in which to save the output
637
  @type cwd: string
638
  @param cwd: the working directory for the program
639
  @rtype: int
640
  @return: the exit status
641

642
  """
643
  fh = open(output, "a")
644
  try:
645
    child = subprocess.Popen(cmd, shell=via_shell,
646
                             stderr=subprocess.STDOUT,
647
                             stdout=fh,
648
                             stdin=subprocess.PIPE,
649
                             close_fds=True, env=env,
650
                             cwd=cwd)
651

    
652
    child.stdin.close()
653
    status = child.wait()
654
  finally:
655
    fh.close()
656
  return status
657

    
658

    
659
def SetCloseOnExecFlag(fd, enable):
660
  """Sets or unsets the close-on-exec flag on a file descriptor.
661

662
  @type fd: int
663
  @param fd: File descriptor
664
  @type enable: bool
665
  @param enable: Whether to set or unset it.
666

667
  """
668
  flags = fcntl.fcntl(fd, fcntl.F_GETFD)
669

    
670
  if enable:
671
    flags |= fcntl.FD_CLOEXEC
672
  else:
673
    flags &= ~fcntl.FD_CLOEXEC
674

    
675
  fcntl.fcntl(fd, fcntl.F_SETFD, flags)
676

    
677

    
678
def SetNonblockFlag(fd, enable):
679
  """Sets or unsets the O_NONBLOCK flag on on a file descriptor.
680

681
  @type fd: int
682
  @param fd: File descriptor
683
  @type enable: bool
684
  @param enable: Whether to set or unset it
685

686
  """
687
  flags = fcntl.fcntl(fd, fcntl.F_GETFL)
688

    
689
  if enable:
690
    flags |= os.O_NONBLOCK
691
  else:
692
    flags &= ~os.O_NONBLOCK
693

    
694
  fcntl.fcntl(fd, fcntl.F_SETFL, flags)
695

    
696

    
697
def RetryOnSignal(fn, *args, **kwargs):
698
  """Calls a function again if it failed due to EINTR.
699

700
  """
701
  while True:
702
    try:
703
      return fn(*args, **kwargs)
704
    except EnvironmentError, err:
705
      if err.errno != errno.EINTR:
706
        raise
707
    except (socket.error, select.error), err:
708
      # In python 2.6 and above select.error is an IOError, so it's handled
709
      # above, in 2.5 and below it's not, and it's handled here.
710
      if not (err.args and err.args[0] == errno.EINTR):
711
        raise
712

    
713

    
714
def RunParts(dir_name, env=None, reset_env=False):
715
  """Run Scripts or programs in a directory
716

717
  @type dir_name: string
718
  @param dir_name: absolute path to a directory
719
  @type env: dict
720
  @param env: The environment to use
721
  @type reset_env: boolean
722
  @param reset_env: whether to reset or keep the default os environment
723
  @rtype: list of tuples
724
  @return: list of (name, (one of RUNDIR_STATUS), RunResult)
725

726
  """
727
  rr = []
728

    
729
  try:
730
    dir_contents = ListVisibleFiles(dir_name)
731
  except OSError, err:
732
    logging.warning("RunParts: skipping %s (cannot list: %s)", dir_name, err)
733
    return rr
734

    
735
  for relname in sorted(dir_contents):
736
    fname = PathJoin(dir_name, relname)
737
    if not (os.path.isfile(fname) and os.access(fname, os.X_OK) and
738
            constants.EXT_PLUGIN_MASK.match(relname) is not None):
739
      rr.append((relname, constants.RUNPARTS_SKIP, None))
740
    else:
741
      try:
742
        result = RunCmd([fname], env=env, reset_env=reset_env)
743
      except Exception, err: # pylint: disable-msg=W0703
744
        rr.append((relname, constants.RUNPARTS_ERR, str(err)))
745
      else:
746
        rr.append((relname, constants.RUNPARTS_RUN, result))
747

    
748
  return rr
749

    
750

    
751
def RemoveFile(filename):
752
  """Remove a file ignoring some errors.
753

754
  Remove a file, ignoring non-existing ones or directories. Other
755
  errors are passed.
756

757
  @type filename: str
758
  @param filename: the file to be removed
759

760
  """
761
  try:
762
    os.unlink(filename)
763
  except OSError, err:
764
    if err.errno not in (errno.ENOENT, errno.EISDIR):
765
      raise
766

    
767

    
768
def RemoveDir(dirname):
769
  """Remove an empty directory.
770

771
  Remove a directory, ignoring non-existing ones.
772
  Other errors are passed. This includes the case,
773
  where the directory is not empty, so it can't be removed.
774

775
  @type dirname: str
776
  @param dirname: the empty directory to be removed
777

778
  """
779
  try:
780
    os.rmdir(dirname)
781
  except OSError, err:
782
    if err.errno != errno.ENOENT:
783
      raise
784

    
785

    
786
def RenameFile(old, new, mkdir=False, mkdir_mode=0750):
787
  """Renames a file.
788

789
  @type old: string
790
  @param old: Original path
791
  @type new: string
792
  @param new: New path
793
  @type mkdir: bool
794
  @param mkdir: Whether to create target directory if it doesn't exist
795
  @type mkdir_mode: int
796
  @param mkdir_mode: Mode for newly created directories
797

798
  """
799
  try:
800
    return os.rename(old, new)
801
  except OSError, err:
802
    # In at least one use case of this function, the job queue, directory
803
    # creation is very rare. Checking for the directory before renaming is not
804
    # as efficient.
805
    if mkdir and err.errno == errno.ENOENT:
806
      # Create directory and try again
807
      Makedirs(os.path.dirname(new), mode=mkdir_mode)
808

    
809
      return os.rename(old, new)
810

    
811
    raise
812

    
813

    
814
def Makedirs(path, mode=0750):
815
  """Super-mkdir; create a leaf directory and all intermediate ones.
816

817
  This is a wrapper around C{os.makedirs} adding error handling not implemented
818
  before Python 2.5.
819

820
  """
821
  try:
822
    os.makedirs(path, mode)
823
  except OSError, err:
824
    # Ignore EEXIST. This is only handled in os.makedirs as included in
825
    # Python 2.5 and above.
826
    if err.errno != errno.EEXIST or not os.path.exists(path):
827
      raise
828

    
829

    
830
def ResetTempfileModule():
831
  """Resets the random name generator of the tempfile module.
832

833
  This function should be called after C{os.fork} in the child process to
834
  ensure it creates a newly seeded random generator. Otherwise it would
835
  generate the same random parts as the parent process. If several processes
836
  race for the creation of a temporary file, this could lead to one not getting
837
  a temporary name.
838

839
  """
840
  # pylint: disable-msg=W0212
841
  if hasattr(tempfile, "_once_lock") and hasattr(tempfile, "_name_sequence"):
842
    tempfile._once_lock.acquire()
843
    try:
844
      # Reset random name generator
845
      tempfile._name_sequence = None
846
    finally:
847
      tempfile._once_lock.release()
848
  else:
849
    logging.critical("The tempfile module misses at least one of the"
850
                     " '_once_lock' and '_name_sequence' attributes")
851

    
852

    
853
def _FingerprintFile(filename):
854
  """Compute the fingerprint of a file.
855

856
  If the file does not exist, a None will be returned
857
  instead.
858

859
  @type filename: str
860
  @param filename: the filename to checksum
861
  @rtype: str
862
  @return: the hex digest of the sha checksum of the contents
863
      of the file
864

865
  """
866
  if not (os.path.exists(filename) and os.path.isfile(filename)):
867
    return None
868

    
869
  f = open(filename)
870

    
871
  fp = compat.sha1_hash()
872
  while True:
873
    data = f.read(4096)
874
    if not data:
875
      break
876

    
877
    fp.update(data)
878

    
879
  return fp.hexdigest()
880

    
881

    
882
def FingerprintFiles(files):
883
  """Compute fingerprints for a list of files.
884

885
  @type files: list
886
  @param files: the list of filename to fingerprint
887
  @rtype: dict
888
  @return: a dictionary filename: fingerprint, holding only
889
      existing files
890

891
  """
892
  ret = {}
893

    
894
  for filename in files:
895
    cksum = _FingerprintFile(filename)
896
    if cksum:
897
      ret[filename] = cksum
898

    
899
  return ret
900

    
901

    
902
def ForceDictType(target, key_types, allowed_values=None):
903
  """Force the values of a dict to have certain types.
904

905
  @type target: dict
906
  @param target: the dict to update
907
  @type key_types: dict
908
  @param key_types: dict mapping target dict keys to types
909
                    in constants.ENFORCEABLE_TYPES
910
  @type allowed_values: list
911
  @keyword allowed_values: list of specially allowed values
912

913
  """
914
  if allowed_values is None:
915
    allowed_values = []
916

    
917
  if not isinstance(target, dict):
918
    msg = "Expected dictionary, got '%s'" % target
919
    raise errors.TypeEnforcementError(msg)
920

    
921
  for key in target:
922
    if key not in key_types:
923
      msg = "Unknown key '%s'" % key
924
      raise errors.TypeEnforcementError(msg)
925

    
926
    if target[key] in allowed_values:
927
      continue
928

    
929
    ktype = key_types[key]
930
    if ktype not in constants.ENFORCEABLE_TYPES:
931
      msg = "'%s' has non-enforceable type %s" % (key, ktype)
932
      raise errors.ProgrammerError(msg)
933

    
934
    if ktype in (constants.VTYPE_STRING, constants.VTYPE_MAYBE_STRING):
935
      if target[key] is None and ktype == constants.VTYPE_MAYBE_STRING:
936
        pass
937
      elif not isinstance(target[key], basestring):
938
        if isinstance(target[key], bool) and not target[key]:
939
          target[key] = ''
940
        else:
941
          msg = "'%s' (value %s) is not a valid string" % (key, target[key])
942
          raise errors.TypeEnforcementError(msg)
943
    elif ktype == constants.VTYPE_BOOL:
944
      if isinstance(target[key], basestring) and target[key]:
945
        if target[key].lower() == constants.VALUE_FALSE:
946
          target[key] = False
947
        elif target[key].lower() == constants.VALUE_TRUE:
948
          target[key] = True
949
        else:
950
          msg = "'%s' (value %s) is not a valid boolean" % (key, target[key])
951
          raise errors.TypeEnforcementError(msg)
952
      elif target[key]:
953
        target[key] = True
954
      else:
955
        target[key] = False
956
    elif ktype == constants.VTYPE_SIZE:
957
      try:
958
        target[key] = ParseUnit(target[key])
959
      except errors.UnitParseError, err:
960
        msg = "'%s' (value %s) is not a valid size. error: %s" % \
961
              (key, target[key], err)
962
        raise errors.TypeEnforcementError(msg)
963
    elif ktype == constants.VTYPE_INT:
964
      try:
965
        target[key] = int(target[key])
966
      except (ValueError, TypeError):
967
        msg = "'%s' (value %s) is not a valid integer" % (key, target[key])
968
        raise errors.TypeEnforcementError(msg)
969

    
970

    
971
def _GetProcStatusPath(pid):
972
  """Returns the path for a PID's proc status file.
973

974
  @type pid: int
975
  @param pid: Process ID
976
  @rtype: string
977

978
  """
979
  return "/proc/%d/status" % pid
980

    
981

    
982
def IsProcessAlive(pid):
983
  """Check if a given pid exists on the system.
984

985
  @note: zombie status is not handled, so zombie processes
986
      will be returned as alive
987
  @type pid: int
988
  @param pid: the process ID to check
989
  @rtype: boolean
990
  @return: True if the process exists
991

992
  """
993
  def _TryStat(name):
994
    try:
995
      os.stat(name)
996
      return True
997
    except EnvironmentError, err:
998
      if err.errno in (errno.ENOENT, errno.ENOTDIR):
999
        return False
1000
      elif err.errno == errno.EINVAL:
1001
        raise RetryAgain(err)
1002
      raise
1003

    
1004
  assert isinstance(pid, int), "pid must be an integer"
1005
  if pid <= 0:
1006
    return False
1007

    
1008
  # /proc in a multiprocessor environment can have strange behaviors.
1009
  # Retry the os.stat a few times until we get a good result.
1010
  try:
1011
    return Retry(_TryStat, (0.01, 1.5, 0.1), 0.5,
1012
                 args=[_GetProcStatusPath(pid)])
1013
  except RetryTimeout, err:
1014
    err.RaiseInner()
1015

    
1016

    
1017
def _ParseSigsetT(sigset):
1018
  """Parse a rendered sigset_t value.
1019

1020
  This is the opposite of the Linux kernel's fs/proc/array.c:render_sigset_t
1021
  function.
1022

1023
  @type sigset: string
1024
  @param sigset: Rendered signal set from /proc/$pid/status
1025
  @rtype: set
1026
  @return: Set of all enabled signal numbers
1027

1028
  """
1029
  result = set()
1030

    
1031
  signum = 0
1032
  for ch in reversed(sigset):
1033
    chv = int(ch, 16)
1034

    
1035
    # The following could be done in a loop, but it's easier to read and
1036
    # understand in the unrolled form
1037
    if chv & 1:
1038
      result.add(signum + 1)
1039
    if chv & 2:
1040
      result.add(signum + 2)
1041
    if chv & 4:
1042
      result.add(signum + 3)
1043
    if chv & 8:
1044
      result.add(signum + 4)
1045

    
1046
    signum += 4
1047

    
1048
  return result
1049

    
1050

    
1051
def _GetProcStatusField(pstatus, field):
1052
  """Retrieves a field from the contents of a proc status file.
1053

1054
  @type pstatus: string
1055
  @param pstatus: Contents of /proc/$pid/status
1056
  @type field: string
1057
  @param field: Name of field whose value should be returned
1058
  @rtype: string
1059

1060
  """
1061
  for line in pstatus.splitlines():
1062
    parts = line.split(":", 1)
1063

    
1064
    if len(parts) < 2 or parts[0] != field:
1065
      continue
1066

    
1067
    return parts[1].strip()
1068

    
1069
  return None
1070

    
1071

    
1072
def IsProcessHandlingSignal(pid, signum, status_path=None):
1073
  """Checks whether a process is handling a signal.
1074

1075
  @type pid: int
1076
  @param pid: Process ID
1077
  @type signum: int
1078
  @param signum: Signal number
1079
  @rtype: bool
1080

1081
  """
1082
  if status_path is None:
1083
    status_path = _GetProcStatusPath(pid)
1084

    
1085
  try:
1086
    proc_status = ReadFile(status_path)
1087
  except EnvironmentError, err:
1088
    # In at least one case, reading /proc/$pid/status failed with ESRCH.
1089
    if err.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL, errno.ESRCH):
1090
      return False
1091
    raise
1092

    
1093
  sigcgt = _GetProcStatusField(proc_status, "SigCgt")
1094
  if sigcgt is None:
1095
    raise RuntimeError("%s is missing 'SigCgt' field" % status_path)
1096

    
1097
  # Now check whether signal is handled
1098
  return signum in _ParseSigsetT(sigcgt)
1099

    
1100

    
1101
def ReadPidFile(pidfile):
1102
  """Read a pid from a file.
1103

1104
  @type  pidfile: string
1105
  @param pidfile: path to the file containing the pid
1106
  @rtype: int
1107
  @return: The process id, if the file exists and contains a valid PID,
1108
           otherwise 0
1109

1110
  """
1111
  try:
1112
    raw_data = ReadOneLineFile(pidfile)
1113
  except EnvironmentError, err:
1114
    if err.errno != errno.ENOENT:
1115
      logging.exception("Can't read pid file")
1116
    return 0
1117

    
1118
  try:
1119
    pid = int(raw_data)
1120
  except (TypeError, ValueError), err:
1121
    logging.info("Can't parse pid file contents", exc_info=True)
1122
    return 0
1123

    
1124
  return pid
1125

    
1126

    
1127
def ReadLockedPidFile(path):
1128
  """Reads a locked PID file.
1129

1130
  This can be used together with L{StartDaemon}.
1131

1132
  @type path: string
1133
  @param path: Path to PID file
1134
  @return: PID as integer or, if file was unlocked or couldn't be opened, None
1135

1136
  """
1137
  try:
1138
    fd = os.open(path, os.O_RDONLY)
1139
  except EnvironmentError, err:
1140
    if err.errno == errno.ENOENT:
1141
      # PID file doesn't exist
1142
      return None
1143
    raise
1144

    
1145
  try:
1146
    try:
1147
      # Try to acquire lock
1148
      LockFile(fd)
1149
    except errors.LockError:
1150
      # Couldn't lock, daemon is running
1151
      return int(os.read(fd, 100))
1152
  finally:
1153
    os.close(fd)
1154

    
1155
  return None
1156

    
1157

    
1158
def ValidateServiceName(name):
1159
  """Validate the given service name.
1160

1161
  @type name: number or string
1162
  @param name: Service name or port specification
1163

1164
  """
1165
  try:
1166
    numport = int(name)
1167
  except (ValueError, TypeError):
1168
    # Non-numeric service name
1169
    valid = _VALID_SERVICE_NAME_RE.match(name)
1170
  else:
1171
    # Numeric port (protocols other than TCP or UDP might need adjustments
1172
    # here)
1173
    valid = (numport >= 0 and numport < (1 << 16))
1174

    
1175
  if not valid:
1176
    raise errors.OpPrereqError("Invalid service name '%s'" % name,
1177
                               errors.ECODE_INVAL)
1178

    
1179
  return name
1180

    
1181

    
1182
def ListVolumeGroups():
1183
  """List volume groups and their size
1184

1185
  @rtype: dict
1186
  @return:
1187
       Dictionary with keys volume name and values
1188
       the size of the volume
1189

1190
  """
1191
  command = "vgs --noheadings --units m --nosuffix -o name,size"
1192
  result = RunCmd(command)
1193
  retval = {}
1194
  if result.failed:
1195
    return retval
1196

    
1197
  for line in result.stdout.splitlines():
1198
    try:
1199
      name, size = line.split()
1200
      size = int(float(size))
1201
    except (IndexError, ValueError), err:
1202
      logging.error("Invalid output from vgs (%s): %s", err, line)
1203
      continue
1204

    
1205
    retval[name] = size
1206

    
1207
  return retval
1208

    
1209

    
1210
def BridgeExists(bridge):
1211
  """Check whether the given bridge exists in the system
1212

1213
  @type bridge: str
1214
  @param bridge: the bridge name to check
1215
  @rtype: boolean
1216
  @return: True if it does
1217

1218
  """
1219
  return os.path.isdir("/sys/class/net/%s/bridge" % bridge)
1220

    
1221

    
1222
def TryConvert(fn, val):
1223
  """Try to convert a value ignoring errors.
1224

1225
  This function tries to apply function I{fn} to I{val}. If no
1226
  C{ValueError} or C{TypeError} exceptions are raised, it will return
1227
  the result, else it will return the original value. Any other
1228
  exceptions are propagated to the caller.
1229

1230
  @type fn: callable
1231
  @param fn: function to apply to the value
1232
  @param val: the value to be converted
1233
  @return: The converted value if the conversion was successful,
1234
      otherwise the original value.
1235

1236
  """
1237
  try:
1238
    nv = fn(val)
1239
  except (ValueError, TypeError):
1240
    nv = val
1241
  return nv
1242

    
1243

    
1244
def IsValidShellParam(word):
1245
  """Verifies is the given word is safe from the shell's p.o.v.
1246

1247
  This means that we can pass this to a command via the shell and be
1248
  sure that it doesn't alter the command line and is passed as such to
1249
  the actual command.
1250

1251
  Note that we are overly restrictive here, in order to be on the safe
1252
  side.
1253

1254
  @type word: str
1255
  @param word: the word to check
1256
  @rtype: boolean
1257
  @return: True if the word is 'safe'
1258

1259
  """
1260
  return bool(_SHELLPARAM_REGEX.match(word))
1261

    
1262

    
1263
def BuildShellCmd(template, *args):
1264
  """Build a safe shell command line from the given arguments.
1265

1266
  This function will check all arguments in the args list so that they
1267
  are valid shell parameters (i.e. they don't contain shell
1268
  metacharacters). If everything is ok, it will return the result of
1269
  template % args.
1270

1271
  @type template: str
1272
  @param template: the string holding the template for the
1273
      string formatting
1274
  @rtype: str
1275
  @return: the expanded command line
1276

1277
  """
1278
  for word in args:
1279
    if not IsValidShellParam(word):
1280
      raise errors.ProgrammerError("Shell argument '%s' contains"
1281
                                   " invalid characters" % word)
1282
  return template % args
1283

    
1284

    
1285
def ParseCpuMask(cpu_mask):
1286
  """Parse a CPU mask definition and return the list of CPU IDs.
1287

1288
  CPU mask format: comma-separated list of CPU IDs
1289
  or dash-separated ID ranges
1290
  Example: "0-2,5" -> "0,1,2,5"
1291

1292
  @type cpu_mask: str
1293
  @param cpu_mask: CPU mask definition
1294
  @rtype: list of int
1295
  @return: list of CPU IDs
1296

1297
  """
1298
  if not cpu_mask:
1299
    return []
1300
  cpu_list = []
1301
  for range_def in cpu_mask.split(","):
1302
    boundaries = range_def.split("-")
1303
    n_elements = len(boundaries)
1304
    if n_elements > 2:
1305
      raise errors.ParseError("Invalid CPU ID range definition"
1306
                              " (only one hyphen allowed): %s" % range_def)
1307
    try:
1308
      lower = int(boundaries[0])
1309
    except (ValueError, TypeError), err:
1310
      raise errors.ParseError("Invalid CPU ID value for lower boundary of"
1311
                              " CPU ID range: %s" % str(err))
1312
    try:
1313
      higher = int(boundaries[-1])
1314
    except (ValueError, TypeError), err:
1315
      raise errors.ParseError("Invalid CPU ID value for higher boundary of"
1316
                              " CPU ID range: %s" % str(err))
1317
    if lower > higher:
1318
      raise errors.ParseError("Invalid CPU ID range definition"
1319
                              " (%d > %d): %s" % (lower, higher, range_def))
1320
    cpu_list.extend(range(lower, higher + 1))
1321
  return cpu_list
1322

    
1323

    
1324
def AddAuthorizedKey(file_obj, key):
1325
  """Adds an SSH public key to an authorized_keys file.
1326

1327
  @type file_obj: str or file handle
1328
  @param file_obj: path to authorized_keys file
1329
  @type key: str
1330
  @param key: string containing key
1331

1332
  """
1333
  key_fields = key.split()
1334

    
1335
  if isinstance(file_obj, basestring):
1336
    f = open(file_obj, 'a+')
1337
  else:
1338
    f = file_obj
1339

    
1340
  try:
1341
    nl = True
1342
    for line in f:
1343
      # Ignore whitespace changes
1344
      if line.split() == key_fields:
1345
        break
1346
      nl = line.endswith('\n')
1347
    else:
1348
      if not nl:
1349
        f.write("\n")
1350
      f.write(key.rstrip('\r\n'))
1351
      f.write("\n")
1352
      f.flush()
1353
  finally:
1354
    f.close()
1355

    
1356

    
1357
def RemoveAuthorizedKey(file_name, key):
1358
  """Removes an SSH public key from an authorized_keys file.
1359

1360
  @type file_name: str
1361
  @param file_name: path to authorized_keys file
1362
  @type key: str
1363
  @param key: string containing key
1364

1365
  """
1366
  key_fields = key.split()
1367

    
1368
  fd, tmpname = tempfile.mkstemp(dir=os.path.dirname(file_name))
1369
  try:
1370
    out = os.fdopen(fd, 'w')
1371
    try:
1372
      f = open(file_name, 'r')
1373
      try:
1374
        for line in f:
1375
          # Ignore whitespace changes while comparing lines
1376
          if line.split() != key_fields:
1377
            out.write(line)
1378

    
1379
        out.flush()
1380
        os.rename(tmpname, file_name)
1381
      finally:
1382
        f.close()
1383
    finally:
1384
      out.close()
1385
  except:
1386
    RemoveFile(tmpname)
1387
    raise
1388

    
1389

    
1390
def SetEtcHostsEntry(file_name, ip, hostname, aliases):
1391
  """Sets the name of an IP address and hostname in /etc/hosts.
1392

1393
  @type file_name: str
1394
  @param file_name: path to the file to modify (usually C{/etc/hosts})
1395
  @type ip: str
1396
  @param ip: the IP address
1397
  @type hostname: str
1398
  @param hostname: the hostname to be added
1399
  @type aliases: list
1400
  @param aliases: the list of aliases to add for the hostname
1401

1402
  """
1403
  # Ensure aliases are unique
1404
  aliases = UniqueSequence([hostname] + aliases)[1:]
1405

    
1406
  def _WriteEtcHosts(fd):
1407
    # Duplicating file descriptor because os.fdopen's result will automatically
1408
    # close the descriptor, but we would still like to have its functionality.
1409
    out = os.fdopen(os.dup(fd), "w")
1410
    try:
1411
      for line in ReadFile(file_name).splitlines(True):
1412
        fields = line.split()
1413
        if fields and not fields[0].startswith("#") and ip == fields[0]:
1414
          continue
1415
        out.write(line)
1416

    
1417
      out.write("%s\t%s" % (ip, hostname))
1418
      if aliases:
1419
        out.write(" %s" % " ".join(aliases))
1420
      out.write("\n")
1421
      out.flush()
1422
    finally:
1423
      out.close()
1424

    
1425
  WriteFile(file_name, fn=_WriteEtcHosts, mode=0644)
1426

    
1427

    
1428
def AddHostToEtcHosts(hostname, ip):
1429
  """Wrapper around SetEtcHostsEntry.
1430

1431
  @type hostname: str
1432
  @param hostname: a hostname that will be resolved and added to
1433
      L{constants.ETC_HOSTS}
1434
  @type ip: str
1435
  @param ip: The ip address of the host
1436

1437
  """
1438
  SetEtcHostsEntry(constants.ETC_HOSTS, ip, hostname, [hostname.split(".")[0]])
1439

    
1440

    
1441
def RemoveEtcHostsEntry(file_name, hostname):
1442
  """Removes a hostname from /etc/hosts.
1443

1444
  IP addresses without names are removed from the file.
1445

1446
  @type file_name: str
1447
  @param file_name: path to the file to modify (usually C{/etc/hosts})
1448
  @type hostname: str
1449
  @param hostname: the hostname to be removed
1450

1451
  """
1452
  def _WriteEtcHosts(fd):
1453
    # Duplicating file descriptor because os.fdopen's result will automatically
1454
    # close the descriptor, but we would still like to have its functionality.
1455
    out = os.fdopen(os.dup(fd), "w")
1456
    try:
1457
      for line in ReadFile(file_name).splitlines(True):
1458
        fields = line.split()
1459
        if len(fields) > 1 and not fields[0].startswith("#"):
1460
          names = fields[1:]
1461
          if hostname in names:
1462
            while hostname in names:
1463
              names.remove(hostname)
1464
            if names:
1465
              out.write("%s %s\n" % (fields[0], " ".join(names)))
1466
            continue
1467

    
1468
        out.write(line)
1469

    
1470
      out.flush()
1471
    finally:
1472
      out.close()
1473

    
1474
  WriteFile(file_name, fn=_WriteEtcHosts, mode=0644)
1475

    
1476

    
1477
def RemoveHostFromEtcHosts(hostname):
1478
  """Wrapper around RemoveEtcHostsEntry.
1479

1480
  @type hostname: str
1481
  @param hostname: hostname that will be resolved and its
1482
      full and shot name will be removed from
1483
      L{constants.ETC_HOSTS}
1484

1485
  """
1486
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hostname)
1487
  RemoveEtcHostsEntry(constants.ETC_HOSTS, hostname.split(".")[0])
1488

    
1489

    
1490
def TimestampForFilename():
1491
  """Returns the current time formatted for filenames.
1492

1493
  The format doesn't contain colons as some shells and applications treat them
1494
  as separators. Uses the local timezone.
1495

1496
  """
1497
  return time.strftime("%Y-%m-%d_%H_%M_%S")
1498

    
1499

    
1500
def CreateBackup(file_name):
1501
  """Creates a backup of a file.
1502

1503
  @type file_name: str
1504
  @param file_name: file to be backed up
1505
  @rtype: str
1506
  @return: the path to the newly created backup
1507
  @raise errors.ProgrammerError: for invalid file names
1508

1509
  """
1510
  if not os.path.isfile(file_name):
1511
    raise errors.ProgrammerError("Can't make a backup of a non-file '%s'" %
1512
                                file_name)
1513

    
1514
  prefix = ("%s.backup-%s." %
1515
            (os.path.basename(file_name), TimestampForFilename()))
1516
  dir_name = os.path.dirname(file_name)
1517

    
1518
  fsrc = open(file_name, 'rb')
1519
  try:
1520
    (fd, backup_name) = tempfile.mkstemp(prefix=prefix, dir=dir_name)
1521
    fdst = os.fdopen(fd, 'wb')
1522
    try:
1523
      logging.debug("Backing up %s at %s", file_name, backup_name)
1524
      shutil.copyfileobj(fsrc, fdst)
1525
    finally:
1526
      fdst.close()
1527
  finally:
1528
    fsrc.close()
1529

    
1530
  return backup_name
1531

    
1532

    
1533
def ListVisibleFiles(path):
1534
  """Returns a list of visible files in a directory.
1535

1536
  @type path: str
1537
  @param path: the directory to enumerate
1538
  @rtype: list
1539
  @return: the list of all files not starting with a dot
1540
  @raise ProgrammerError: if L{path} is not an absolue and normalized path
1541

1542
  """
1543
  if not IsNormAbsPath(path):
1544
    raise errors.ProgrammerError("Path passed to ListVisibleFiles is not"
1545
                                 " absolute/normalized: '%s'" % path)
1546
  files = [i for i in os.listdir(path) if not i.startswith(".")]
1547
  return files
1548

    
1549

    
1550
def GetHomeDir(user, default=None):
1551
  """Try to get the homedir of the given user.
1552

1553
  The user can be passed either as a string (denoting the name) or as
1554
  an integer (denoting the user id). If the user is not found, the
1555
  'default' argument is returned, which defaults to None.
1556

1557
  """
1558
  try:
1559
    if isinstance(user, basestring):
1560
      result = pwd.getpwnam(user)
1561
    elif isinstance(user, (int, long)):
1562
      result = pwd.getpwuid(user)
1563
    else:
1564
      raise errors.ProgrammerError("Invalid type passed to GetHomeDir (%s)" %
1565
                                   type(user))
1566
  except KeyError:
1567
    return default
1568
  return result.pw_dir
1569

    
1570

    
1571
def NewUUID():
1572
  """Returns a random UUID.
1573

1574
  @note: This is a Linux-specific method as it uses the /proc
1575
      filesystem.
1576
  @rtype: str
1577

1578
  """
1579
  return ReadFile(_RANDOM_UUID_FILE, size=128).rstrip("\n")
1580

    
1581

    
1582
def EnsureDirs(dirs):
1583
  """Make required directories, if they don't exist.
1584

1585
  @param dirs: list of tuples (dir_name, dir_mode)
1586
  @type dirs: list of (string, integer)
1587

1588
  """
1589
  for dir_name, dir_mode in dirs:
1590
    try:
1591
      os.mkdir(dir_name, dir_mode)
1592
    except EnvironmentError, err:
1593
      if err.errno != errno.EEXIST:
1594
        raise errors.GenericError("Cannot create needed directory"
1595
                                  " '%s': %s" % (dir_name, err))
1596
    try:
1597
      os.chmod(dir_name, dir_mode)
1598
    except EnvironmentError, err:
1599
      raise errors.GenericError("Cannot change directory permissions on"
1600
                                " '%s': %s" % (dir_name, err))
1601
    if not os.path.isdir(dir_name):
1602
      raise errors.GenericError("%s is not a directory" % dir_name)
1603

    
1604

    
1605
def ReadFile(file_name, size=-1):
1606
  """Reads a file.
1607

1608
  @type size: int
1609
  @param size: Read at most size bytes (if negative, entire file)
1610
  @rtype: str
1611
  @return: the (possibly partial) content of the file
1612

1613
  """
1614
  f = open(file_name, "r")
1615
  try:
1616
    return f.read(size)
1617
  finally:
1618
    f.close()
1619

    
1620

    
1621
def WriteFile(file_name, fn=None, data=None,
1622
              mode=None, uid=-1, gid=-1,
1623
              atime=None, mtime=None, close=True,
1624
              dry_run=False, backup=False,
1625
              prewrite=None, postwrite=None):
1626
  """(Over)write a file atomically.
1627

1628
  The file_name and either fn (a function taking one argument, the
1629
  file descriptor, and which should write the data to it) or data (the
1630
  contents of the file) must be passed. The other arguments are
1631
  optional and allow setting the file mode, owner and group, and the
1632
  mtime/atime of the file.
1633

1634
  If the function doesn't raise an exception, it has succeeded and the
1635
  target file has the new contents. If the function has raised an
1636
  exception, an existing target file should be unmodified and the
1637
  temporary file should be removed.
1638

1639
  @type file_name: str
1640
  @param file_name: the target filename
1641
  @type fn: callable
1642
  @param fn: content writing function, called with
1643
      file descriptor as parameter
1644
  @type data: str
1645
  @param data: contents of the file
1646
  @type mode: int
1647
  @param mode: file mode
1648
  @type uid: int
1649
  @param uid: the owner of the file
1650
  @type gid: int
1651
  @param gid: the group of the file
1652
  @type atime: int
1653
  @param atime: a custom access time to be set on the file
1654
  @type mtime: int
1655
  @param mtime: a custom modification time to be set on the file
1656
  @type close: boolean
1657
  @param close: whether to close file after writing it
1658
  @type prewrite: callable
1659
  @param prewrite: function to be called before writing content
1660
  @type postwrite: callable
1661
  @param postwrite: function to be called after writing content
1662

1663
  @rtype: None or int
1664
  @return: None if the 'close' parameter evaluates to True,
1665
      otherwise the file descriptor
1666

1667
  @raise errors.ProgrammerError: if any of the arguments are not valid
1668

1669
  """
1670
  if not os.path.isabs(file_name):
1671
    raise errors.ProgrammerError("Path passed to WriteFile is not"
1672
                                 " absolute: '%s'" % file_name)
1673

    
1674
  if [fn, data].count(None) != 1:
1675
    raise errors.ProgrammerError("fn or data required")
1676

    
1677
  if [atime, mtime].count(None) == 1:
1678
    raise errors.ProgrammerError("Both atime and mtime must be either"
1679
                                 " set or None")
1680

    
1681
  if backup and not dry_run and os.path.isfile(file_name):
1682
    CreateBackup(file_name)
1683

    
1684
  dir_name, base_name = os.path.split(file_name)
1685
  fd, new_name = tempfile.mkstemp('.new', base_name, dir_name)
1686
  do_remove = True
1687
  # here we need to make sure we remove the temp file, if any error
1688
  # leaves it in place
1689
  try:
1690
    if uid != -1 or gid != -1:
1691
      os.chown(new_name, uid, gid)
1692
    if mode:
1693
      os.chmod(new_name, mode)
1694
    if callable(prewrite):
1695
      prewrite(fd)
1696
    if data is not None:
1697
      os.write(fd, data)
1698
    else:
1699
      fn(fd)
1700
    if callable(postwrite):
1701
      postwrite(fd)
1702
    os.fsync(fd)
1703
    if atime is not None and mtime is not None:
1704
      os.utime(new_name, (atime, mtime))
1705
    if not dry_run:
1706
      os.rename(new_name, file_name)
1707
      do_remove = False
1708
  finally:
1709
    if close:
1710
      os.close(fd)
1711
      result = None
1712
    else:
1713
      result = fd
1714
    if do_remove:
1715
      RemoveFile(new_name)
1716

    
1717
  return result
1718

    
1719

    
1720
def GetFileID(path=None, fd=None):
1721
  """Returns the file 'id', i.e. the dev/inode and mtime information.
1722

1723
  Either the path to the file or the fd must be given.
1724

1725
  @param path: the file path
1726
  @param fd: a file descriptor
1727
  @return: a tuple of (device number, inode number, mtime)
1728

1729
  """
1730
  if [path, fd].count(None) != 1:
1731
    raise errors.ProgrammerError("One and only one of fd/path must be given")
1732

    
1733
  if fd is None:
1734
    st = os.stat(path)
1735
  else:
1736
    st = os.fstat(fd)
1737

    
1738
  return (st.st_dev, st.st_ino, st.st_mtime)
1739

    
1740

    
1741
def VerifyFileID(fi_disk, fi_ours):
1742
  """Verifies that two file IDs are matching.
1743

1744
  Differences in the inode/device are not accepted, but and older
1745
  timestamp for fi_disk is accepted.
1746

1747
  @param fi_disk: tuple (dev, inode, mtime) representing the actual
1748
      file data
1749
  @param fi_ours: tuple (dev, inode, mtime) representing the last
1750
      written file data
1751
  @rtype: boolean
1752

1753
  """
1754
  (d1, i1, m1) = fi_disk
1755
  (d2, i2, m2) = fi_ours
1756

    
1757
  return (d1, i1) == (d2, i2) and m1 <= m2
1758

    
1759

    
1760
def SafeWriteFile(file_name, file_id, **kwargs):
1761
  """Wraper over L{WriteFile} that locks the target file.
1762

1763
  By keeping the target file locked during WriteFile, we ensure that
1764
  cooperating writers will safely serialise access to the file.
1765

1766
  @type file_name: str
1767
  @param file_name: the target filename
1768
  @type file_id: tuple
1769
  @param file_id: a result from L{GetFileID}
1770

1771
  """
1772
  fd = os.open(file_name, os.O_RDONLY | os.O_CREAT)
1773
  try:
1774
    LockFile(fd)
1775
    if file_id is not None:
1776
      disk_id = GetFileID(fd=fd)
1777
      if not VerifyFileID(disk_id, file_id):
1778
        raise errors.LockError("Cannot overwrite file %s, it has been modified"
1779
                               " since last written" % file_name)
1780
    return WriteFile(file_name, **kwargs)
1781
  finally:
1782
    os.close(fd)
1783

    
1784

    
1785
def ReadOneLineFile(file_name, strict=False):
1786
  """Return the first non-empty line from a file.
1787

1788
  @type strict: boolean
1789
  @param strict: if True, abort if the file has more than one
1790
      non-empty line
1791

1792
  """
1793
  file_lines = ReadFile(file_name).splitlines()
1794
  full_lines = filter(bool, file_lines)
1795
  if not file_lines or not full_lines:
1796
    raise errors.GenericError("No data in one-liner file %s" % file_name)
1797
  elif strict and len(full_lines) > 1:
1798
    raise errors.GenericError("Too many lines in one-liner file %s" %
1799
                              file_name)
1800
  return full_lines[0]
1801

    
1802

    
1803
def FirstFree(seq, base=0):
1804
  """Returns the first non-existing integer from seq.
1805

1806
  The seq argument should be a sorted list of positive integers. The
1807
  first time the index of an element is smaller than the element
1808
  value, the index will be returned.
1809

1810
  The base argument is used to start at a different offset,
1811
  i.e. C{[3, 4, 6]} with I{offset=3} will return 5.
1812

1813
  Example: C{[0, 1, 3]} will return I{2}.
1814

1815
  @type seq: sequence
1816
  @param seq: the sequence to be analyzed.
1817
  @type base: int
1818
  @param base: use this value as the base index of the sequence
1819
  @rtype: int
1820
  @return: the first non-used index in the sequence
1821

1822
  """
1823
  for idx, elem in enumerate(seq):
1824
    assert elem >= base, "Passed element is higher than base offset"
1825
    if elem > idx + base:
1826
      # idx is not used
1827
      return idx + base
1828
  return None
1829

    
1830

    
1831
def SingleWaitForFdCondition(fdobj, event, timeout):
1832
  """Waits for a condition to occur on the socket.
1833

1834
  Immediately returns at the first interruption.
1835

1836
  @type fdobj: integer or object supporting a fileno() method
1837
  @param fdobj: entity to wait for events on
1838
  @type event: integer
1839
  @param event: ORed condition (see select module)
1840
  @type timeout: float or None
1841
  @param timeout: Timeout in seconds
1842
  @rtype: int or None
1843
  @return: None for timeout, otherwise occured conditions
1844

1845
  """
1846
  check = (event | select.POLLPRI |
1847
           select.POLLNVAL | select.POLLHUP | select.POLLERR)
1848

    
1849
  if timeout is not None:
1850
    # Poller object expects milliseconds
1851
    timeout *= 1000
1852

    
1853
  poller = select.poll()
1854
  poller.register(fdobj, event)
1855
  try:
1856
    # TODO: If the main thread receives a signal and we have no timeout, we
1857
    # could wait forever. This should check a global "quit" flag or something
1858
    # every so often.
1859
    io_events = poller.poll(timeout)
1860
  except select.error, err:
1861
    if err[0] != errno.EINTR:
1862
      raise
1863
    io_events = []
1864
  if io_events and io_events[0][1] & check:
1865
    return io_events[0][1]
1866
  else:
1867
    return None
1868

    
1869

    
1870
class FdConditionWaiterHelper(object):
1871
  """Retry helper for WaitForFdCondition.
1872

1873
  This class contains the retried and wait functions that make sure
1874
  WaitForFdCondition can continue waiting until the timeout is actually
1875
  expired.
1876

1877
  """
1878

    
1879
  def __init__(self, timeout):
1880
    self.timeout = timeout
1881

    
1882
  def Poll(self, fdobj, event):
1883
    result = SingleWaitForFdCondition(fdobj, event, self.timeout)
1884
    if result is None:
1885
      raise RetryAgain()
1886
    else:
1887
      return result
1888

    
1889
  def UpdateTimeout(self, timeout):
1890
    self.timeout = timeout
1891

    
1892

    
1893
def WaitForFdCondition(fdobj, event, timeout):
1894
  """Waits for a condition to occur on the socket.
1895

1896
  Retries until the timeout is expired, even if interrupted.
1897

1898
  @type fdobj: integer or object supporting a fileno() method
1899
  @param fdobj: entity to wait for events on
1900
  @type event: integer
1901
  @param event: ORed condition (see select module)
1902
  @type timeout: float or None
1903
  @param timeout: Timeout in seconds
1904
  @rtype: int or None
1905
  @return: None for timeout, otherwise occured conditions
1906

1907
  """
1908
  if timeout is not None:
1909
    retrywaiter = FdConditionWaiterHelper(timeout)
1910
    try:
1911
      result = Retry(retrywaiter.Poll, RETRY_REMAINING_TIME, timeout,
1912
                     args=(fdobj, event), wait_fn=retrywaiter.UpdateTimeout)
1913
    except RetryTimeout:
1914
      result = None
1915
  else:
1916
    result = None
1917
    while result is None:
1918
      result = SingleWaitForFdCondition(fdobj, event, timeout)
1919
  return result
1920

    
1921

    
1922
def TestDelay(duration):
1923
  """Sleep for a fixed amount of time.
1924

1925
  @type duration: float
1926
  @param duration: the sleep duration
1927
  @rtype: boolean
1928
  @return: False for negative value, True otherwise
1929

1930
  """
1931
  if duration < 0:
1932
    return False, "Invalid sleep duration"
1933
  time.sleep(duration)
1934
  return True, None
1935

    
1936

    
1937
def CloseFdNoError(fd, retries=5):
1938
  """Close a file descriptor ignoring errors.
1939

1940
  @type fd: int
1941
  @param fd: the file descriptor
1942
  @type retries: int
1943
  @param retries: how many retries to make, in case we get any
1944
      other error than EBADF
1945

1946
  """
1947
  try:
1948
    os.close(fd)
1949
  except OSError, err:
1950
    if err.errno != errno.EBADF:
1951
      if retries > 0:
1952
        CloseFdNoError(fd, retries - 1)
1953
    # else either it's closed already or we're out of retries, so we
1954
    # ignore this and go on
1955

    
1956

    
1957
def CloseFDs(noclose_fds=None):
1958
  """Close file descriptors.
1959

1960
  This closes all file descriptors above 2 (i.e. except
1961
  stdin/out/err).
1962

1963
  @type noclose_fds: list or None
1964
  @param noclose_fds: if given, it denotes a list of file descriptor
1965
      that should not be closed
1966

1967
  """
1968
  # Default maximum for the number of available file descriptors.
1969
  if 'SC_OPEN_MAX' in os.sysconf_names:
1970
    try:
1971
      MAXFD = os.sysconf('SC_OPEN_MAX')
1972
      if MAXFD < 0:
1973
        MAXFD = 1024
1974
    except OSError:
1975
      MAXFD = 1024
1976
  else:
1977
    MAXFD = 1024
1978
  maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1979
  if (maxfd == resource.RLIM_INFINITY):
1980
    maxfd = MAXFD
1981

    
1982
  # Iterate through and close all file descriptors (except the standard ones)
1983
  for fd in range(3, maxfd):
1984
    if noclose_fds and fd in noclose_fds:
1985
      continue
1986
    CloseFdNoError(fd)
1987

    
1988

    
1989
def Daemonize(logfile):
1990
  """Daemonize the current process.
1991

1992
  This detaches the current process from the controlling terminal and
1993
  runs it in the background as a daemon.
1994

1995
  @type logfile: str
1996
  @param logfile: the logfile to which we should redirect stdout/stderr
1997
  @rtype: int
1998
  @return: the value zero
1999

2000
  """
2001
  # pylint: disable-msg=W0212
2002
  # yes, we really want os._exit
2003

    
2004
  # TODO: do another attempt to merge Daemonize and StartDaemon, or at
2005
  # least abstract the pipe functionality between them
2006

    
2007
  # Create pipe for sending error messages
2008
  (rpipe, wpipe) = os.pipe()
2009

    
2010
  # this might fail
2011
  pid = os.fork()
2012
  if (pid == 0):  # The first child.
2013
    SetupDaemonEnv()
2014

    
2015
    # this might fail
2016
    pid = os.fork() # Fork a second child.
2017
    if (pid == 0):  # The second child.
2018
      CloseFdNoError(rpipe)
2019
    else:
2020
      # exit() or _exit()?  See below.
2021
      os._exit(0) # Exit parent (the first child) of the second child.
2022
  else:
2023
    CloseFdNoError(wpipe)
2024
    # Wait for daemon to be started (or an error message to
2025
    # arrive) and read up to 100 KB as an error message
2026
    errormsg = RetryOnSignal(os.read, rpipe, 100 * 1024)
2027
    if errormsg:
2028
      sys.stderr.write("Error when starting daemon process: %r\n" % errormsg)
2029
      rcode = 1
2030
    else:
2031
      rcode = 0
2032
    os._exit(rcode) # Exit parent of the first child.
2033

    
2034
  SetupDaemonFDs(logfile, None)
2035
  return wpipe
2036

    
2037

    
2038
def DaemonPidFileName(name):
2039
  """Compute a ganeti pid file absolute path
2040

2041
  @type name: str
2042
  @param name: the daemon name
2043
  @rtype: str
2044
  @return: the full path to the pidfile corresponding to the given
2045
      daemon name
2046

2047
  """
2048
  return PathJoin(constants.RUN_GANETI_DIR, "%s.pid" % name)
2049

    
2050

    
2051
def EnsureDaemon(name):
2052
  """Check for and start daemon if not alive.
2053

2054
  """
2055
  result = RunCmd([constants.DAEMON_UTIL, "check-and-start", name])
2056
  if result.failed:
2057
    logging.error("Can't start daemon '%s', failure %s, output: %s",
2058
                  name, result.fail_reason, result.output)
2059
    return False
2060

    
2061
  return True
2062

    
2063

    
2064
def StopDaemon(name):
2065
  """Stop daemon
2066

2067
  """
2068
  result = RunCmd([constants.DAEMON_UTIL, "stop", name])
2069
  if result.failed:
2070
    logging.error("Can't stop daemon '%s', failure %s, output: %s",
2071
                  name, result.fail_reason, result.output)
2072
    return False
2073

    
2074
  return True
2075

    
2076

    
2077
def WritePidFile(pidfile):
2078
  """Write the current process pidfile.
2079

2080
  @type pidfile: sting
2081
  @param pidfile: the path to the file to be written
2082
  @raise errors.LockError: if the pid file already exists and
2083
      points to a live process
2084
  @rtype: int
2085
  @return: the file descriptor of the lock file; do not close this unless
2086
      you want to unlock the pid file
2087

2088
  """
2089
  # We don't rename nor truncate the file to not drop locks under
2090
  # existing processes
2091
  fd_pidfile = os.open(pidfile, os.O_WRONLY | os.O_CREAT, 0600)
2092

    
2093
  # Lock the PID file (and fail if not possible to do so). Any code
2094
  # wanting to send a signal to the daemon should try to lock the PID
2095
  # file before reading it. If acquiring the lock succeeds, the daemon is
2096
  # no longer running and the signal should not be sent.
2097
  LockFile(fd_pidfile)
2098

    
2099
  os.write(fd_pidfile, "%d\n" % os.getpid())
2100

    
2101
  return fd_pidfile
2102

    
2103

    
2104
def RemovePidFile(name):
2105
  """Remove the current process pidfile.
2106

2107
  Any errors are ignored.
2108

2109
  @type name: str
2110
  @param name: the daemon name used to derive the pidfile name
2111

2112
  """
2113
  pidfilename = DaemonPidFileName(name)
2114
  # TODO: we could check here that the file contains our pid
2115
  try:
2116
    RemoveFile(pidfilename)
2117
  except: # pylint: disable-msg=W0702
2118
    pass
2119

    
2120

    
2121
def KillProcess(pid, signal_=signal.SIGTERM, timeout=30,
2122
                waitpid=False):
2123
  """Kill a process given by its pid.
2124

2125
  @type pid: int
2126
  @param pid: The PID to terminate.
2127
  @type signal_: int
2128
  @param signal_: The signal to send, by default SIGTERM
2129
  @type timeout: int
2130
  @param timeout: The timeout after which, if the process is still alive,
2131
                  a SIGKILL will be sent. If not positive, no such checking
2132
                  will be done
2133
  @type waitpid: boolean
2134
  @param waitpid: If true, we should waitpid on this process after
2135
      sending signals, since it's our own child and otherwise it
2136
      would remain as zombie
2137

2138
  """
2139
  def _helper(pid, signal_, wait):
2140
    """Simple helper to encapsulate the kill/waitpid sequence"""
2141
    if IgnoreProcessNotFound(os.kill, pid, signal_) and wait:
2142
      try:
2143
        os.waitpid(pid, os.WNOHANG)
2144
      except OSError:
2145
        pass
2146

    
2147
  if pid <= 0:
2148
    # kill with pid=0 == suicide
2149
    raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
2150

    
2151
  if not IsProcessAlive(pid):
2152
    return
2153

    
2154
  _helper(pid, signal_, waitpid)
2155

    
2156
  if timeout <= 0:
2157
    return
2158

    
2159
  def _CheckProcess():
2160
    if not IsProcessAlive(pid):
2161
      return
2162

    
2163
    try:
2164
      (result_pid, _) = os.waitpid(pid, os.WNOHANG)
2165
    except OSError:
2166
      raise RetryAgain()
2167

    
2168
    if result_pid > 0:
2169
      return
2170

    
2171
    raise RetryAgain()
2172

    
2173
  try:
2174
    # Wait up to $timeout seconds
2175
    Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
2176
  except RetryTimeout:
2177
    pass
2178

    
2179
  if IsProcessAlive(pid):
2180
    # Kill process if it's still alive
2181
    _helper(pid, signal.SIGKILL, waitpid)
2182

    
2183

    
2184
def FindFile(name, search_path, test=os.path.exists):
2185
  """Look for a filesystem object in a given path.
2186

2187
  This is an abstract method to search for filesystem object (files,
2188
  dirs) under a given search path.
2189

2190
  @type name: str
2191
  @param name: the name to look for
2192
  @type search_path: str
2193
  @param search_path: location to start at
2194
  @type test: callable
2195
  @param test: a function taking one argument that should return True
2196
      if the a given object is valid; the default value is
2197
      os.path.exists, causing only existing files to be returned
2198
  @rtype: str or None
2199
  @return: full path to the object if found, None otherwise
2200

2201
  """
2202
  # validate the filename mask
2203
  if constants.EXT_PLUGIN_MASK.match(name) is None:
2204
    logging.critical("Invalid value passed for external script name: '%s'",
2205
                     name)
2206
    return None
2207

    
2208
  for dir_name in search_path:
2209
    # FIXME: investigate switch to PathJoin
2210
    item_name = os.path.sep.join([dir_name, name])
2211
    # check the user test and that we're indeed resolving to the given
2212
    # basename
2213
    if test(item_name) and os.path.basename(item_name) == name:
2214
      return item_name
2215
  return None
2216

    
2217

    
2218
def CheckVolumeGroupSize(vglist, vgname, minsize):
2219
  """Checks if the volume group list is valid.
2220

2221
  The function will check if a given volume group is in the list of
2222
  volume groups and has a minimum size.
2223

2224
  @type vglist: dict
2225
  @param vglist: dictionary of volume group names and their size
2226
  @type vgname: str
2227
  @param vgname: the volume group we should check
2228
  @type minsize: int
2229
  @param minsize: the minimum size we accept
2230
  @rtype: None or str
2231
  @return: None for success, otherwise the error message
2232

2233
  """
2234
  vgsize = vglist.get(vgname, None)
2235
  if vgsize is None:
2236
    return "volume group '%s' missing" % vgname
2237
  elif vgsize < minsize:
2238
    return ("volume group '%s' too small (%s MiB required, %d MiB found)" %
2239
            (vgname, minsize, vgsize))
2240
  return None
2241

    
2242

    
2243
def SplitTime(value):
2244
  """Splits time as floating point number into a tuple.
2245

2246
  @param value: Time in seconds
2247
  @type value: int or float
2248
  @return: Tuple containing (seconds, microseconds)
2249

2250
  """
2251
  (seconds, microseconds) = divmod(int(value * 1000000), 1000000)
2252

    
2253
  assert 0 <= seconds, \
2254
    "Seconds must be larger than or equal to 0, but are %s" % seconds
2255
  assert 0 <= microseconds <= 999999, \
2256
    "Microseconds must be 0-999999, but are %s" % microseconds
2257

    
2258
  return (int(seconds), int(microseconds))
2259

    
2260

    
2261
def MergeTime(timetuple):
2262
  """Merges a tuple into time as a floating point number.
2263

2264
  @param timetuple: Time as tuple, (seconds, microseconds)
2265
  @type timetuple: tuple
2266
  @return: Time as a floating point number expressed in seconds
2267

2268
  """
2269
  (seconds, microseconds) = timetuple
2270

    
2271
  assert 0 <= seconds, \
2272
    "Seconds must be larger than or equal to 0, but are %s" % seconds
2273
  assert 0 <= microseconds <= 999999, \
2274
    "Microseconds must be 0-999999, but are %s" % microseconds
2275

    
2276
  return float(seconds) + (float(microseconds) * 0.000001)
2277

    
2278

    
2279
def IsNormAbsPath(path):
2280
  """Check whether a path is absolute and also normalized
2281

2282
  This avoids things like /dir/../../other/path to be valid.
2283

2284
  """
2285
  return os.path.normpath(path) == path and os.path.isabs(path)
2286

    
2287

    
2288
def PathJoin(*args):
2289
  """Safe-join a list of path components.
2290

2291
  Requirements:
2292
      - the first argument must be an absolute path
2293
      - no component in the path must have backtracking (e.g. /../),
2294
        since we check for normalization at the end
2295

2296
  @param args: the path components to be joined
2297
  @raise ValueError: for invalid paths
2298

2299
  """
2300
  # ensure we're having at least one path passed in
2301
  assert args
2302
  # ensure the first component is an absolute and normalized path name
2303
  root = args[0]
2304
  if not IsNormAbsPath(root):
2305
    raise ValueError("Invalid parameter to PathJoin: '%s'" % str(args[0]))
2306
  result = os.path.join(*args)
2307
  # ensure that the whole path is normalized
2308
  if not IsNormAbsPath(result):
2309
    raise ValueError("Invalid parameters to PathJoin: '%s'" % str(args))
2310
  # check that we're still under the original prefix
2311
  prefix = os.path.commonprefix([root, result])
2312
  if prefix != root:
2313
    raise ValueError("Error: path joining resulted in different prefix"
2314
                     " (%s != %s)" % (prefix, root))
2315
  return result
2316

    
2317

    
2318
def TailFile(fname, lines=20):
2319
  """Return the last lines from a file.
2320

2321
  @note: this function will only read and parse the last 4KB of
2322
      the file; if the lines are very long, it could be that less
2323
      than the requested number of lines are returned
2324

2325
  @param fname: the file name
2326
  @type lines: int
2327
  @param lines: the (maximum) number of lines to return
2328

2329
  """
2330
  fd = open(fname, "r")
2331
  try:
2332
    fd.seek(0, 2)
2333
    pos = fd.tell()
2334
    pos = max(0, pos-4096)
2335
    fd.seek(pos, 0)
2336
    raw_data = fd.read()
2337
  finally:
2338
    fd.close()
2339

    
2340
  rows = raw_data.splitlines()
2341
  return rows[-lines:]
2342

    
2343

    
2344
def _ParseAsn1Generalizedtime(value):
2345
  """Parses an ASN1 GENERALIZEDTIME timestamp as used by pyOpenSSL.
2346

2347
  @type value: string
2348
  @param value: ASN1 GENERALIZEDTIME timestamp
2349
  @return: Seconds since the Epoch (1970-01-01 00:00:00 UTC)
2350

2351
  """
2352
  m = _ASN1_TIME_REGEX.match(value)
2353
  if m:
2354
    # We have an offset
2355
    asn1time = m.group(1)
2356
    hours = int(m.group(2))
2357
    minutes = int(m.group(3))
2358
    utcoffset = (60 * hours) + minutes
2359
  else:
2360
    if not value.endswith("Z"):
2361
      raise ValueError("Missing timezone")
2362
    asn1time = value[:-1]
2363
    utcoffset = 0
2364

    
2365
  parsed = time.strptime(asn1time, "%Y%m%d%H%M%S")
2366

    
2367
  tt = datetime.datetime(*(parsed[:7])) - datetime.timedelta(minutes=utcoffset)
2368

    
2369
  return calendar.timegm(tt.utctimetuple())
2370

    
2371

    
2372
def GetX509CertValidity(cert):
2373
  """Returns the validity period of the certificate.
2374

2375
  @type cert: OpenSSL.crypto.X509
2376
  @param cert: X509 certificate object
2377

2378
  """
2379
  # The get_notBefore and get_notAfter functions are only supported in
2380
  # pyOpenSSL 0.7 and above.
2381
  try:
2382
    get_notbefore_fn = cert.get_notBefore
2383
  except AttributeError:
2384
    not_before = None
2385
  else:
2386
    not_before_asn1 = get_notbefore_fn()
2387

    
2388
    if not_before_asn1 is None:
2389
      not_before = None
2390
    else:
2391
      not_before = _ParseAsn1Generalizedtime(not_before_asn1)
2392

    
2393
  try:
2394
    get_notafter_fn = cert.get_notAfter
2395
  except AttributeError:
2396
    not_after = None
2397
  else:
2398
    not_after_asn1 = get_notafter_fn()
2399

    
2400
    if not_after_asn1 is None:
2401
      not_after = None
2402
    else:
2403
      not_after = _ParseAsn1Generalizedtime(not_after_asn1)
2404

    
2405
  return (not_before, not_after)
2406

    
2407

    
2408
def _VerifyCertificateInner(expired, not_before, not_after, now,
2409
                            warn_days, error_days):
2410
  """Verifies certificate validity.
2411

2412
  @type expired: bool
2413
  @param expired: Whether pyOpenSSL considers the certificate as expired
2414
  @type not_before: number or None
2415
  @param not_before: Unix timestamp before which certificate is not valid
2416
  @type not_after: number or None
2417
  @param not_after: Unix timestamp after which certificate is invalid
2418
  @type now: number
2419
  @param now: Current time as Unix timestamp
2420
  @type warn_days: number or None
2421
  @param warn_days: How many days before expiration a warning should be reported
2422
  @type error_days: number or None
2423
  @param error_days: How many days before expiration an error should be reported
2424

2425
  """
2426
  if expired:
2427
    msg = "Certificate is expired"
2428

    
2429
    if not_before is not None and not_after is not None:
2430
      msg += (" (valid from %s to %s)" %
2431
              (FormatTime(not_before), FormatTime(not_after)))
2432
    elif not_before is not None:
2433
      msg += " (valid from %s)" % FormatTime(not_before)
2434
    elif not_after is not None:
2435
      msg += " (valid until %s)" % FormatTime(not_after)
2436

    
2437
    return (CERT_ERROR, msg)
2438

    
2439
  elif not_before is not None and not_before > now:
2440
    return (CERT_WARNING,
2441
            "Certificate not yet valid (valid from %s)" %
2442
            FormatTime(not_before))
2443

    
2444
  elif not_after is not None:
2445
    remaining_days = int((not_after - now) / (24 * 3600))
2446

    
2447
    msg = "Certificate expires in about %d days" % remaining_days
2448

    
2449
    if error_days is not None and remaining_days <= error_days:
2450
      return (CERT_ERROR, msg)
2451

    
2452
    if warn_days is not None and remaining_days <= warn_days:
2453
      return (CERT_WARNING, msg)
2454

    
2455
  return (None, None)
2456

    
2457

    
2458
def VerifyX509Certificate(cert, warn_days, error_days):
2459
  """Verifies a certificate for LUVerifyCluster.
2460

2461
  @type cert: OpenSSL.crypto.X509
2462
  @param cert: X509 certificate object
2463
  @type warn_days: number or None
2464
  @param warn_days: How many days before expiration a warning should be reported
2465
  @type error_days: number or None
2466
  @param error_days: How many days before expiration an error should be reported
2467

2468
  """
2469
  # Depending on the pyOpenSSL version, this can just return (None, None)
2470
  (not_before, not_after) = GetX509CertValidity(cert)
2471

    
2472
  return _VerifyCertificateInner(cert.has_expired(), not_before, not_after,
2473
                                 time.time(), warn_days, error_days)
2474

    
2475

    
2476
def SignX509Certificate(cert, key, salt):
2477
  """Sign a X509 certificate.
2478

2479
  An RFC822-like signature header is added in front of the certificate.
2480

2481
  @type cert: OpenSSL.crypto.X509
2482
  @param cert: X509 certificate object
2483
  @type key: string
2484
  @param key: Key for HMAC
2485
  @type salt: string
2486
  @param salt: Salt for HMAC
2487
  @rtype: string
2488
  @return: Serialized and signed certificate in PEM format
2489

2490
  """
2491
  if not VALID_X509_SIGNATURE_SALT.match(salt):
2492
    raise errors.GenericError("Invalid salt: %r" % salt)
2493

    
2494
  # Dumping as PEM here ensures the certificate is in a sane format
2495
  cert_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
2496

    
2497
  return ("%s: %s/%s\n\n%s" %
2498
          (constants.X509_CERT_SIGNATURE_HEADER, salt,
2499
           Sha1Hmac(key, cert_pem, salt=salt),
2500
           cert_pem))
2501

    
2502

    
2503
def _ExtractX509CertificateSignature(cert_pem):
2504
  """Helper function to extract signature from X509 certificate.
2505

2506
  """
2507
  # Extract signature from original PEM data
2508
  for line in cert_pem.splitlines():
2509
    if line.startswith("---"):
2510
      break
2511

    
2512
    m = X509_SIGNATURE.match(line.strip())
2513
    if m:
2514
      return (m.group("salt"), m.group("sign"))
2515

    
2516
  raise errors.GenericError("X509 certificate signature is missing")
2517

    
2518

    
2519
def LoadSignedX509Certificate(cert_pem, key):
2520
  """Verifies a signed X509 certificate.
2521

2522
  @type cert_pem: string
2523
  @param cert_pem: Certificate in PEM format and with signature header
2524
  @type key: string
2525
  @param key: Key for HMAC
2526
  @rtype: tuple; (OpenSSL.crypto.X509, string)
2527
  @return: X509 certificate object and salt
2528

2529
  """
2530
  (salt, signature) = _ExtractX509CertificateSignature(cert_pem)
2531

    
2532
  # Load certificate
2533
  cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_pem)
2534

    
2535
  # Dump again to ensure it's in a sane format
2536
  sane_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
2537

    
2538
  if not VerifySha1Hmac(key, sane_pem, signature, salt=salt):
2539
    raise errors.GenericError("X509 certificate signature is invalid")
2540

    
2541
  return (cert, salt)
2542

    
2543

    
2544
def Sha1Hmac(key, text, salt=None):
2545
  """Calculates the HMAC-SHA1 digest of a text.
2546

2547
  HMAC is defined in RFC2104.
2548

2549
  @type key: string
2550
  @param key: Secret key
2551
  @type text: string
2552

2553
  """
2554
  if salt:
2555
    salted_text = salt + text
2556
  else:
2557
    salted_text = text
2558

    
2559
  return hmac.new(key, salted_text, compat.sha1).hexdigest()
2560

    
2561

    
2562
def VerifySha1Hmac(key, text, digest, salt=None):
2563
  """Verifies the HMAC-SHA1 digest of a text.
2564

2565
  HMAC is defined in RFC2104.
2566

2567
  @type key: string
2568
  @param key: Secret key
2569
  @type text: string
2570
  @type digest: string
2571
  @param digest: Expected digest
2572
  @rtype: bool
2573
  @return: Whether HMAC-SHA1 digest matches
2574

2575
  """
2576
  return digest.lower() == Sha1Hmac(key, text, salt=salt).lower()
2577

    
2578

    
2579
def FindMatch(data, name):
2580
  """Tries to find an item in a dictionary matching a name.
2581

2582
  Callers have to ensure the data names aren't contradictory (e.g. a regexp
2583
  that matches a string). If the name isn't a direct key, all regular
2584
  expression objects in the dictionary are matched against it.
2585

2586
  @type data: dict
2587
  @param data: Dictionary containing data
2588
  @type name: string
2589
  @param name: Name to look for
2590
  @rtype: tuple; (value in dictionary, matched groups as list)
2591

2592
  """
2593
  if name in data:
2594
    return (data[name], [])
2595

    
2596
  for key, value in data.items():
2597
    # Regex objects
2598
    if hasattr(key, "match"):
2599
      m = key.match(name)
2600
      if m:
2601
        return (value, list(m.groups()))
2602

    
2603
  return None
2604

    
2605

    
2606
def BytesToMebibyte(value):
2607
  """Converts bytes to mebibytes.
2608

2609
  @type value: int
2610
  @param value: Value in bytes
2611
  @rtype: int
2612
  @return: Value in mebibytes
2613

2614
  """
2615
  return int(round(value / (1024.0 * 1024.0), 0))
2616

    
2617

    
2618
def CalculateDirectorySize(path):
2619
  """Calculates the size of a directory recursively.
2620

2621
  @type path: string
2622
  @param path: Path to directory
2623
  @rtype: int
2624
  @return: Size in mebibytes
2625

2626
  """
2627
  size = 0
2628

    
2629
  for (curpath, _, files) in os.walk(path):
2630
    for filename in files:
2631
      st = os.lstat(PathJoin(curpath, filename))
2632
      size += st.st_size
2633

    
2634
  return BytesToMebibyte(size)
2635

    
2636

    
2637
def GetMounts(filename=constants.PROC_MOUNTS):
2638
  """Returns the list of mounted filesystems.
2639

2640
  This function is Linux-specific.
2641

2642
  @param filename: path of mounts file (/proc/mounts by default)
2643
  @rtype: list of tuples
2644
  @return: list of mount entries (device, mountpoint, fstype, options)
2645

2646
  """
2647
  # TODO(iustin): investigate non-Linux options (e.g. via mount output)
2648
  data = []
2649
  mountlines = ReadFile(filename).splitlines()
2650
  for line in mountlines:
2651
    device, mountpoint, fstype, options, _ = line.split(None, 4)
2652
    data.append((device, mountpoint, fstype, options))
2653

    
2654
  return data
2655

    
2656

    
2657
def GetFilesystemStats(path):
2658
  """Returns the total and free space on a filesystem.
2659

2660
  @type path: string
2661
  @param path: Path on filesystem to be examined
2662
  @rtype: int
2663
  @return: tuple of (Total space, Free space) in mebibytes
2664

2665
  """
2666
  st = os.statvfs(path)
2667

    
2668
  fsize = BytesToMebibyte(st.f_bavail * st.f_frsize)
2669
  tsize = BytesToMebibyte(st.f_blocks * st.f_frsize)
2670
  return (tsize, fsize)
2671

    
2672

    
2673
def RunInSeparateProcess(fn, *args):
2674
  """Runs a function in a separate process.
2675

2676
  Note: Only boolean return values are supported.
2677

2678
  @type fn: callable
2679
  @param fn: Function to be called
2680
  @rtype: bool
2681
  @return: Function's result
2682

2683
  """
2684
  pid = os.fork()
2685
  if pid == 0:
2686
    # Child process
2687
    try:
2688
      # In case the function uses temporary files
2689
      ResetTempfileModule()
2690

    
2691
      # Call function
2692
      result = int(bool(fn(*args)))
2693
      assert result in (0, 1)
2694
    except: # pylint: disable-msg=W0702
2695
      logging.exception("Error while calling function in separate process")
2696
      # 0 and 1 are reserved for the return value
2697
      result = 33
2698

    
2699
    os._exit(result) # pylint: disable-msg=W0212
2700

    
2701
  # Parent process
2702

    
2703
  # Avoid zombies and check exit code
2704
  (_, status) = os.waitpid(pid, 0)
2705

    
2706
  if os.WIFSIGNALED(status):
2707
    exitcode = None
2708
    signum = os.WTERMSIG(status)
2709
  else:
2710
    exitcode = os.WEXITSTATUS(status)
2711
    signum = None
2712

    
2713
  if not (exitcode in (0, 1) and signum is None):
2714
    raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
2715
                              (exitcode, signum))
2716

    
2717
  return bool(exitcode)
2718

    
2719

    
2720
def IgnoreProcessNotFound(fn, *args, **kwargs):
2721
  """Ignores ESRCH when calling a process-related function.
2722

2723
  ESRCH is raised when a process is not found.
2724

2725
  @rtype: bool
2726
  @return: Whether process was found
2727

2728
  """
2729
  try:
2730
    fn(*args, **kwargs)
2731
  except EnvironmentError, err:
2732
    # Ignore ESRCH
2733
    if err.errno == errno.ESRCH:
2734
      return False
2735
    raise
2736

    
2737
  return True
2738

    
2739

    
2740
def IgnoreSignals(fn, *args, **kwargs):
2741
  """Tries to call a function ignoring failures due to EINTR.
2742

2743
  """
2744
  try:
2745
    return fn(*args, **kwargs)
2746
  except EnvironmentError, err:
2747
    if err.errno == errno.EINTR:
2748
      return None
2749
    else:
2750
      raise
2751
  except (select.error, socket.error), err:
2752
    # In python 2.6 and above select.error is an IOError, so it's handled
2753
    # above, in 2.5 and below it's not, and it's handled here.
2754
    if err.args and err.args[0] == errno.EINTR:
2755
      return None
2756
    else:
2757
      raise
2758

    
2759

    
2760
def LockFile(fd):
2761
  """Locks a file using POSIX locks.
2762

2763
  @type fd: int
2764
  @param fd: the file descriptor we need to lock
2765

2766
  """
2767
  try:
2768
    fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
2769
  except IOError, err:
2770
    if err.errno == errno.EAGAIN:
2771
      raise errors.LockError("File already locked")
2772
    raise
2773

    
2774

    
2775
def ReadWatcherPauseFile(filename, now=None, remove_after=3600):
2776
  """Reads the watcher pause file.
2777

2778
  @type filename: string
2779
  @param filename: Path to watcher pause file
2780
  @type now: None, float or int
2781
  @param now: Current time as Unix timestamp
2782
  @type remove_after: int
2783
  @param remove_after: Remove watcher pause file after specified amount of
2784
    seconds past the pause end time
2785

2786
  """
2787
  if now is None:
2788
    now = time.time()
2789

    
2790
  try:
2791
    value = ReadFile(filename)
2792
  except IOError, err:
2793
    if err.errno != errno.ENOENT:
2794
      raise
2795
    value = None
2796

    
2797
  if value is not None:
2798
    try:
2799
      value = int(value)
2800
    except ValueError:
2801
      logging.warning(("Watcher pause file (%s) contains invalid value,"
2802
                       " removing it"), filename)
2803
      RemoveFile(filename)
2804
      value = None
2805

    
2806
    if value is not None:
2807
      # Remove file if it's outdated
2808
      if now > (value + remove_after):
2809
        RemoveFile(filename)
2810
        value = None
2811

    
2812
      elif now > value:
2813
        value = None
2814

    
2815
  return value
2816

    
2817

    
2818
def GetClosedTempfile(*args, **kwargs):
2819
  """Creates a temporary file and returns its path.
2820

2821
  """
2822
  (fd, path) = tempfile.mkstemp(*args, **kwargs)
2823
  CloseFdNoError(fd)
2824
  return path
2825

    
2826

    
2827
def GenerateSelfSignedX509Cert(common_name, validity):
2828
  """Generates a self-signed X509 certificate.
2829

2830
  @type common_name: string
2831
  @param common_name: commonName value
2832
  @type validity: int
2833
  @param validity: Validity for certificate in seconds
2834

2835
  """
2836
  # Create private and public key
2837
  key = OpenSSL.crypto.PKey()
2838
  key.generate_key(OpenSSL.crypto.TYPE_RSA, constants.RSA_KEY_BITS)
2839

    
2840
  # Create self-signed certificate
2841
  cert = OpenSSL.crypto.X509()
2842
  if common_name:
2843
    cert.get_subject().CN = common_name
2844
  cert.set_serial_number(1)
2845
  cert.gmtime_adj_notBefore(0)
2846
  cert.gmtime_adj_notAfter(validity)
2847
  cert.set_issuer(cert.get_subject())
2848
  cert.set_pubkey(key)
2849
  cert.sign(key, constants.X509_CERT_SIGN_DIGEST)
2850

    
2851
  key_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key)
2852
  cert_pem = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert)
2853

    
2854
  return (key_pem, cert_pem)
2855

    
2856

    
2857
def GenerateSelfSignedSslCert(filename, common_name=constants.X509_CERT_CN,
2858
                              validity=constants.X509_CERT_DEFAULT_VALIDITY):
2859
  """Legacy function to generate self-signed X509 certificate.
2860

2861
  @type filename: str
2862
  @param filename: path to write certificate to
2863
  @type common_name: string
2864
  @param common_name: commonName value
2865
  @type validity: int
2866
  @param validity: validity of certificate in number of days
2867

2868
  """
2869
  # TODO: Investigate using the cluster name instead of X505_CERT_CN for
2870
  # common_name, as cluster-renames are very seldom, and it'd be nice if RAPI
2871
  # and node daemon certificates have the proper Subject/Issuer.
2872
  (key_pem, cert_pem) = GenerateSelfSignedX509Cert(common_name,
2873
                                                   validity * 24 * 60 * 60)
2874

    
2875
  WriteFile(filename, mode=0400, data=key_pem + cert_pem)
2876

    
2877

    
2878
class FileLock(object):
2879
  """Utility class for file locks.
2880

2881
  """
2882
  def __init__(self, fd, filename):
2883
    """Constructor for FileLock.
2884

2885
    @type fd: file
2886
    @param fd: File object
2887
    @type filename: str
2888
    @param filename: Path of the file opened at I{fd}
2889

2890
    """
2891
    self.fd = fd
2892
    self.filename = filename
2893

    
2894
  @classmethod
2895
  def Open(cls, filename):
2896
    """Creates and opens a file to be used as a file-based lock.
2897

2898
    @type filename: string
2899
    @param filename: path to the file to be locked
2900

2901
    """
2902
    # Using "os.open" is necessary to allow both opening existing file
2903
    # read/write and creating if not existing. Vanilla "open" will truncate an
2904
    # existing file -or- allow creating if not existing.
2905
    return cls(os.fdopen(os.open(filename, os.O_RDWR | os.O_CREAT), "w+"),
2906
               filename)
2907

    
2908
  def __del__(self):
2909
    self.Close()
2910

    
2911
  def Close(self):
2912
    """Close the file and release the lock.
2913

2914
    """
2915
    if hasattr(self, "fd") and self.fd:
2916
      self.fd.close()
2917
      self.fd = None
2918

    
2919
  def _flock(self, flag, blocking, timeout, errmsg):
2920
    """Wrapper for fcntl.flock.
2921

2922
    @type flag: int
2923
    @param flag: operation flag
2924
    @type blocking: bool
2925
    @param blocking: whether the operation should be done in blocking mode.
2926
    @type timeout: None or float
2927
    @param timeout: for how long the operation should be retried (implies
2928
                    non-blocking mode).
2929
    @type errmsg: string
2930
    @param errmsg: error message in case operation fails.
2931

2932
    """
2933
    assert self.fd, "Lock was closed"
2934
    assert timeout is None or timeout >= 0, \
2935
      "If specified, timeout must be positive"
2936
    assert not (flag & fcntl.LOCK_NB), "LOCK_NB must not be set"
2937

    
2938
    # When a timeout is used, LOCK_NB must always be set
2939
    if not (timeout is None and blocking):
2940
      flag |= fcntl.LOCK_NB
2941

    
2942
    if timeout is None:
2943
      self._Lock(self.fd, flag, timeout)
2944
    else:
2945
      try:
2946
        Retry(self._Lock, (0.1, 1.2, 1.0), timeout,
2947
              args=(self.fd, flag, timeout))
2948
      except RetryTimeout:
2949
        raise errors.LockError(errmsg)
2950

    
2951
  @staticmethod
2952
  def _Lock(fd, flag, timeout):
2953
    try:
2954
      fcntl.flock(fd, flag)
2955
    except IOError, err:
2956
      if timeout is not None and err.errno == errno.EAGAIN:
2957
        raise RetryAgain()
2958

    
2959
      logging.exception("fcntl.flock failed")
2960
      raise
2961

    
2962
  def Exclusive(self, blocking=False, timeout=None):
2963
    """Locks the file in exclusive mode.
2964

2965
    @type blocking: boolean
2966
    @param blocking: whether to block and wait until we
2967
        can lock the file or return immediately
2968
    @type timeout: int or None
2969
    @param timeout: if not None, the duration to wait for the lock
2970
        (in blocking mode)
2971

2972
    """
2973
    self._flock(fcntl.LOCK_EX, blocking, timeout,
2974
                "Failed to lock %s in exclusive mode" % self.filename)
2975

    
2976
  def Shared(self, blocking=False, timeout=None):
2977
    """Locks the file in shared mode.
2978

2979
    @type blocking: boolean
2980
    @param blocking: whether to block and wait until we
2981
        can lock the file or return immediately
2982
    @type timeout: int or None
2983
    @param timeout: if not None, the duration to wait for the lock
2984
        (in blocking mode)
2985

2986
    """
2987
    self._flock(fcntl.LOCK_SH, blocking, timeout,
2988
                "Failed to lock %s in shared mode" % self.filename)
2989

    
2990
  def Unlock(self, blocking=True, timeout=None):
2991
    """Unlocks the file.
2992

2993
    According to C{flock(2)}, unlocking can also be a nonblocking
2994
    operation::
2995

2996
      To make a non-blocking request, include LOCK_NB with any of the above
2997
      operations.
2998

2999
    @type blocking: boolean
3000
    @param blocking: whether to block and wait until we
3001
        can lock the file or return immediately
3002
    @type timeout: int or None
3003
    @param timeout: if not None, the duration to wait for the lock
3004
        (in blocking mode)
3005

3006
    """
3007
    self._flock(fcntl.LOCK_UN, blocking, timeout,
3008
                "Failed to unlock %s" % self.filename)
3009

    
3010

    
3011
def SignalHandled(signums):
3012
  """Signal Handled decoration.
3013

3014
  This special decorator installs a signal handler and then calls the target
3015
  function. The function must accept a 'signal_handlers' keyword argument,
3016
  which will contain a dict indexed by signal number, with SignalHandler
3017
  objects as values.
3018

3019
  The decorator can be safely stacked with iself, to handle multiple signals
3020
  with different handlers.
3021

3022
  @type signums: list
3023
  @param signums: signals to intercept
3024

3025
  """
3026
  def wrap(fn):
3027
    def sig_function(*args, **kwargs):
3028
      assert 'signal_handlers' not in kwargs or \
3029
             kwargs['signal_handlers'] is None or \
3030
             isinstance(kwargs['signal_handlers'], dict), \
3031
             "Wrong signal_handlers parameter in original function call"
3032
      if 'signal_handlers' in kwargs and kwargs['signal_handlers'] is not None:
3033
        signal_handlers = kwargs['signal_handlers']
3034
      else:
3035
        signal_handlers = {}
3036
        kwargs['signal_handlers'] = signal_handlers
3037
      sighandler = SignalHandler(signums)
3038
      try:
3039
        for sig in signums:
3040
          signal_handlers[sig] = sighandler
3041
        return fn(*args, **kwargs)
3042
      finally:
3043
        sighandler.Reset()
3044
    return sig_function
3045
  return wrap
3046

    
3047

    
3048
class SignalWakeupFd(object):
3049
  try:
3050
    # This is only supported in Python 2.5 and above (some distributions
3051
    # backported it to Python 2.4)
3052
    _set_wakeup_fd_fn = signal.set_wakeup_fd
3053
  except AttributeError:
3054
    # Not supported
3055
    def _SetWakeupFd(self, _): # pylint: disable-msg=R0201
3056
      return -1
3057
  else:
3058
    def _SetWakeupFd(self, fd):
3059
      return self._set_wakeup_fd_fn(fd)
3060

    
3061
  def __init__(self):
3062
    """Initializes this class.
3063

3064
    """
3065
    (read_fd, write_fd) = os.pipe()
3066

    
3067
    # Once these succeeded, the file descriptors will be closed automatically.
3068
    # Buffer size 0 is important, otherwise .read() with a specified length
3069
    # might buffer data and the file descriptors won't be marked readable.
3070
    self._read_fh = os.fdopen(read_fd, "r", 0)
3071
    self._write_fh = os.fdopen(write_fd, "w", 0)
3072

    
3073
    self._previous = self._SetWakeupFd(self._write_fh.fileno())
3074

    
3075
    # Utility functions
3076
    self.fileno = self._read_fh.fileno
3077
    self.read = self._read_fh.read
3078

    
3079
  def Reset(self):
3080
    """Restores the previous wakeup file descriptor.
3081

3082
    """
3083
    if hasattr(self, "_previous") and self._previous is not None:
3084
      self._SetWakeupFd(self._previous)
3085
      self._previous = None
3086

    
3087
  def Notify(self):
3088
    """Notifies the wakeup file descriptor.
3089

3090
    """
3091
    self._write_fh.write("\0")
3092

    
3093
  def __del__(self):
3094
    """Called before object deletion.
3095

3096
    """
3097
    self.Reset()
3098

    
3099

    
3100
class SignalHandler(object):
3101
  """Generic signal handler class.
3102

3103
  It automatically restores the original handler when deconstructed or
3104
  when L{Reset} is called. You can either pass your own handler
3105
  function in or query the L{called} attribute to detect whether the
3106
  signal was sent.
3107

3108
  @type signum: list
3109
  @ivar signum: the signals we handle
3110
  @type called: boolean
3111
  @ivar called: tracks whether any of the signals have been raised
3112

3113
  """
3114
  def __init__(self, signum, handler_fn=None, wakeup=None):
3115
    """Constructs a new SignalHandler instance.
3116

3117
    @type signum: int or list of ints
3118
    @param signum: Single signal number or set of signal numbers
3119
    @type handler_fn: callable
3120
    @param handler_fn: Signal handling function
3121

3122
    """
3123
    assert handler_fn is None or callable(handler_fn)
3124

    
3125
    self.signum = set(signum)
3126
    self.called = False
3127

    
3128
    self._handler_fn = handler_fn
3129
    self._wakeup = wakeup
3130

    
3131
    self._previous = {}
3132
    try:
3133
      for signum in self.signum:
3134
        # Setup handler
3135
        prev_handler = signal.signal(signum, self._HandleSignal)
3136
        try:
3137
          self._previous[signum] = prev_handler
3138
        except:
3139
          # Restore previous handler
3140
          signal.signal(signum, prev_handler)
3141
          raise
3142
    except:
3143
      # Reset all handlers
3144
      self.Reset()
3145
      # Here we have a race condition: a handler may have already been called,
3146
      # but there's not much we can do about it at this point.
3147
      raise
3148

    
3149
  def __del__(self):
3150
    self.Reset()
3151

    
3152
  def Reset(self):
3153
    """Restore previous handler.
3154

3155
    This will reset all the signals to their previous handlers.
3156

3157
    """
3158
    for signum, prev_handler in self._previous.items():
3159
      signal.signal(signum, prev_handler)
3160
      # If successful, remove from dict
3161
      del self._previous[signum]
3162

    
3163
  def Clear(self):
3164
    """Unsets the L{called} flag.
3165

3166
    This function can be used in case a signal may arrive several times.
3167

3168
    """
3169
    self.called = False
3170

    
3171
  def _HandleSignal(self, signum, frame):
3172
    """Actual signal handling function.
3173

3174
    """
3175
    # This is not nice and not absolutely atomic, but it appears to be the only
3176
    # solution in Python -- there are no atomic types.
3177
    self.called = True
3178

    
3179
    if self._wakeup:
3180
      # Notify whoever is interested in signals
3181
      self._wakeup.Notify()
3182

    
3183
    if self._handler_fn:
3184
      self._handler_fn(signum, frame)
3185

    
3186

    
3187
class FieldSet(object):
3188
  """A simple field set.
3189

3190
  Among the features are:
3191
    - checking if a string is among a list of static string or regex objects
3192
    - checking if a whole list of string matches
3193
    - returning the matching groups from a regex match
3194

3195
  Internally, all fields are held as regular expression objects.
3196

3197
  """
3198
  def __init__(self, *items):
3199
    self.items = [re.compile("^%s$" % value) for value in items]
3200

    
3201
  def Extend(self, other_set):
3202
    """Extend the field set with the items from another one"""
3203
    self.items.extend(other_set.items)
3204

    
3205
  def Matches(self, field):
3206
    """Checks if a field matches the current set
3207

3208
    @type field: str
3209
    @param field: the string to match
3210
    @return: either None or a regular expression match object
3211

3212
    """
3213
    for m in itertools.ifilter(None, (val.match(field) for val in self.items)):
3214
      return m
3215
    return None
3216

    
3217
  def NonMatching(self, items):
3218
    """Returns the list of fields not matching the current set
3219

3220
    @type items: list
3221
    @param items: the list of fields to check
3222
    @rtype: list
3223
    @return: list of non-matching fields
3224

3225
    """
3226
    return [val for val in items if not self.Matches(val)]
3227

    
3228

    
3229
class RunningTimeout(object):
3230
  """Class to calculate remaining timeout when doing several operations.
3231

3232
  """
3233
  __slots__ = [
3234
    "_allow_negative",
3235
    "_start_time",
3236
    "_time_fn",
3237
    "_timeout",
3238
    ]
3239

    
3240
  def __init__(self, timeout, allow_negative, _time_fn=time.time):
3241
    """Initializes this class.
3242

3243
    @type timeout: float
3244
    @param timeout: Timeout duration
3245
    @type allow_negative: bool
3246
    @param allow_negative: Whether to return values below zero
3247
    @param _time_fn: Time function for unittests
3248

3249
    """
3250
    object.__init__(self)
3251

    
3252
    if timeout is not None and timeout < 0.0:
3253
      raise ValueError("Timeout must not be negative")
3254

    
3255
    self._timeout = timeout
3256
    self._allow_negative = allow_negative
3257
    self._time_fn = _time_fn
3258

    
3259
    self._start_time = None
3260

    
3261
  def Remaining(self):
3262
    """Returns the remaining timeout.
3263

3264
    """
3265
    if self._timeout is None:
3266
      return None
3267

    
3268
    # Get start time on first calculation
3269
    if self._start_time is None:
3270
      self._start_time = self._time_fn()
3271

    
3272
    # Calculate remaining time
3273
    remaining_timeout = self._start_time + self._timeout - self._time_fn()
3274

    
3275
    if not self._allow_negative:
3276
      # Ensure timeout is always >= 0
3277
      return max(0.0, remaining_timeout)
3278

    
3279
    return remaining_timeout