1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 """Utility functions for processes.
31
32 """
33
34
35 import os
36 import sys
37 import subprocess
38 import errno
39 import select
40 import logging
41 import signal
42 import resource
43
44 from cStringIO import StringIO
45
46 from ganeti import errors
47 from ganeti import constants
48 from ganeti import compat
49
50 from ganeti.utils import retry as utils_retry
51 from ganeti.utils import wrapper as utils_wrapper
52 from ganeti.utils import text as utils_text
53 from ganeti.utils import io as utils_io
54 from ganeti.utils import algo as utils_algo
55
56
57
58 _no_fork = False
59
60 (_TIMEOUT_NONE,
61 _TIMEOUT_TERM,
62 _TIMEOUT_KILL) = range(3)
63
64
66 """Disables the use of fork(2).
67
68 """
69 global _no_fork
70
71 _no_fork = True
72
73
75 """Holds the result of running external programs.
76
77 @type exit_code: int
78 @ivar exit_code: the exit code of the program, or None (if the program
79 didn't exit())
80 @type signal: int or None
81 @ivar signal: the signal that caused the program to finish, or None
82 (if the program wasn't terminated by a signal)
83 @type stdout: str
84 @ivar stdout: the standard output of the program
85 @type stderr: str
86 @ivar stderr: the standard error of the program
87 @type failed: boolean
88 @ivar failed: True in case the program was
89 terminated by a signal or exited with a non-zero exit code
90 @type failed_by_timeout: boolean
91 @ivar failed_by_timeout: True in case the program was
92 terminated by timeout
93 @ivar fail_reason: a string detailing the termination reason
94
95 """
96 __slots__ = ["exit_code", "signal", "stdout", "stderr",
97 "failed", "failed_by_timeout", "fail_reason", "cmd"]
98
99 - def __init__(self, exit_code, signal_, stdout, stderr, cmd, timeout_action,
100 timeout):
101 self.cmd = cmd
102 self.exit_code = exit_code
103 self.signal = signal_
104 self.stdout = stdout
105 self.stderr = stderr
106 self.failed = (signal_ is not None or exit_code != 0)
107 self.failed_by_timeout = timeout_action != _TIMEOUT_NONE
108
109 fail_msgs = []
110 if self.signal is not None:
111 fail_msgs.append("terminated by signal %s" % self.signal)
112 elif self.exit_code is not None:
113 fail_msgs.append("exited with exit code %s" % self.exit_code)
114 else:
115 fail_msgs.append("unable to determine termination reason")
116
117 if timeout_action == _TIMEOUT_TERM:
118 fail_msgs.append("terminated after timeout of %.2f seconds" % timeout)
119 elif timeout_action == _TIMEOUT_KILL:
120 fail_msgs.append(("force termination after timeout of %.2f seconds"
121 " and linger for another %.2f seconds") %
122 (timeout, constants.CHILD_LINGER_TIMEOUT))
123
124 if fail_msgs and self.failed:
125 self.fail_reason = utils_text.CommaJoin(fail_msgs)
126 else:
127 self.fail_reason = None
128
129 if self.failed:
130 logging.debug("Command '%s' failed (%s); output: %s",
131 self.cmd, self.fail_reason, self.output)
132
134 """Returns the combined stdout and stderr for easier usage.
135
136 """
137 return self.stdout + self.stderr
138
139 output = property(_GetOutput, None, None, "Return full output")
140
141
143 """Builds the environment for an external program.
144
145 """
146 if reset:
147 cmd_env = {}
148 else:
149 cmd_env = os.environ.copy()
150 cmd_env["LC_ALL"] = "C"
151
152 if env is not None:
153 cmd_env.update(env)
154
155 return cmd_env
156
157
158 -def RunCmd(cmd, env=None, output=None, cwd="/", reset_env=False,
159 interactive=False, timeout=None, noclose_fds=None,
160 input_fd=None, postfork_fn=None):
161 """Execute a (shell) command.
162
163 The command should not read from its standard input, as it will be
164 closed.
165
166 @type cmd: string or list
167 @param cmd: Command to run
168 @type env: dict
169 @param env: Additional environment variables
170 @type output: str
171 @param output: if desired, the output of the command can be
172 saved in a file instead of the RunResult instance; this
173 parameter denotes the file name (if not None)
174 @type cwd: string
175 @param cwd: if specified, will be used as the working
176 directory for the command; the default will be /
177 @type reset_env: boolean
178 @param reset_env: whether to reset or keep the default os environment
179 @type interactive: boolean
180 @param interactive: whether we pipe stdin, stdout and stderr
181 (default behaviour) or run the command interactive
182 @type timeout: int
183 @param timeout: If not None, timeout in seconds until child process gets
184 killed
185 @type noclose_fds: list
186 @param noclose_fds: list of additional (fd >=3) file descriptors to leave
187 open for the child process
188 @type input_fd: C{file}-like object containing an actual file descriptor
189 or numeric file descriptor
190 @param input_fd: File descriptor for process' standard input
191 @type postfork_fn: Callable receiving PID as parameter
192 @param postfork_fn: Callback run after fork but before timeout
193 @rtype: L{RunResult}
194 @return: RunResult instance
195 @raise errors.ProgrammerError: if we call this when forks are disabled
196
197 """
198 if _no_fork:
199 raise errors.ProgrammerError("utils.RunCmd() called with fork() disabled")
200
201 if output and interactive:
202 raise errors.ProgrammerError("Parameters 'output' and 'interactive' can"
203 " not be provided at the same time")
204
205 if not (output is None or input_fd is None):
206
207
208 raise errors.ProgrammerError("Parameters 'output' and 'input_fd' can"
209 " not be used at the same time")
210
211 if isinstance(cmd, basestring):
212 strcmd = cmd
213 shell = True
214 else:
215 cmd = [str(val) for val in cmd]
216 strcmd = utils_text.ShellQuoteArgs(cmd)
217 shell = False
218
219 if output:
220 logging.info("RunCmd %s, output file '%s'", strcmd, output)
221 else:
222 logging.info("RunCmd %s", strcmd)
223
224 cmd_env = _BuildCmdEnvironment(env, reset_env)
225
226 try:
227 if output is None:
228 out, err, status, timeout_action = _RunCmdPipe(cmd, cmd_env, shell, cwd,
229 interactive, timeout,
230 noclose_fds, input_fd,
231 postfork_fn=postfork_fn)
232 else:
233 if postfork_fn:
234 raise errors.ProgrammerError("postfork_fn is not supported if output"
235 " should be captured")
236 assert input_fd is None
237 timeout_action = _TIMEOUT_NONE
238 status = _RunCmdFile(cmd, cmd_env, shell, output, cwd, noclose_fds)
239 out = err = ""
240 except OSError, err:
241 if err.errno == errno.ENOENT:
242 raise errors.OpExecError("Can't execute '%s': not found (%s)" %
243 (strcmd, err))
244 else:
245 raise
246
247 if status >= 0:
248 exitcode = status
249 signal_ = None
250 else:
251 exitcode = None
252 signal_ = -status
253
254 return RunResult(exitcode, signal_, out, err, strcmd, timeout_action, timeout)
255
256
258 """Setup a daemon's environment.
259
260 This should be called between the first and second fork, due to
261 setsid usage.
262
263 @param cwd: the directory to which to chdir
264 @param umask: the umask to setup
265
266 """
267 os.chdir(cwd)
268 os.umask(umask)
269 os.setsid()
270
271
273 """Setups up a daemon's file descriptors.
274
275 @param output_file: if not None, the file to which to redirect
276 stdout/stderr
277 @param output_fd: if not None, the file descriptor for stdout/stderr
278
279 """
280
281 assert [output_file, output_fd].count(None) >= 1
282
283
284 devnull_fd = os.open(os.devnull, os.O_RDONLY)
285
286 output_close = True
287
288 if output_fd is not None:
289 output_close = False
290 elif output_file is not None:
291
292 try:
293 output_fd = os.open(output_file,
294 os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0600)
295 except EnvironmentError, err:
296 raise Exception("Opening output file failed: %s" % err)
297 else:
298 output_fd = os.open(os.devnull, os.O_WRONLY)
299
300
301 os.dup2(devnull_fd, 0)
302 os.dup2(output_fd, 1)
303 os.dup2(output_fd, 2)
304
305 if devnull_fd > 2:
306 utils_wrapper.CloseFdNoError(devnull_fd)
307
308 if output_close and output_fd > 2:
309 utils_wrapper.CloseFdNoError(output_fd)
310
311
312 -def StartDaemon(cmd, env=None, cwd="/", output=None, output_fd=None,
313 pidfile=None):
314 """Start a daemon process after forking twice.
315
316 @type cmd: string or list
317 @param cmd: Command to run
318 @type env: dict
319 @param env: Additional environment variables
320 @type cwd: string
321 @param cwd: Working directory for the program
322 @type output: string
323 @param output: Path to file in which to save the output
324 @type output_fd: int
325 @param output_fd: File descriptor for output
326 @type pidfile: string
327 @param pidfile: Process ID file
328 @rtype: int
329 @return: Daemon process ID
330 @raise errors.ProgrammerError: if we call this when forks are disabled
331
332 """
333 if _no_fork:
334 raise errors.ProgrammerError("utils.StartDaemon() called with fork()"
335 " disabled")
336
337 if output and not (bool(output) ^ (output_fd is not None)):
338 raise errors.ProgrammerError("Only one of 'output' and 'output_fd' can be"
339 " specified")
340
341 if isinstance(cmd, basestring):
342 cmd = ["/bin/sh", "-c", cmd]
343
344 strcmd = utils_text.ShellQuoteArgs(cmd)
345
346 if output:
347 logging.debug("StartDaemon %s, output file '%s'", strcmd, output)
348 else:
349 logging.debug("StartDaemon %s", strcmd)
350
351 cmd_env = _BuildCmdEnvironment(env, False)
352
353
354 (pidpipe_read, pidpipe_write) = os.pipe()
355 try:
356 try:
357
358 (errpipe_read, errpipe_write) = os.pipe()
359 try:
360 try:
361
362 pid = os.fork()
363 if pid == 0:
364 try:
365
366 _StartDaemonChild(errpipe_read, errpipe_write,
367 pidpipe_read, pidpipe_write,
368 cmd, cmd_env, cwd,
369 output, output_fd, pidfile)
370 finally:
371
372 os._exit(1)
373 finally:
374 utils_wrapper.CloseFdNoError(errpipe_write)
375
376
377
378 errormsg = utils_wrapper.RetryOnSignal(os.read, errpipe_read,
379 100 * 1024)
380 finally:
381 utils_wrapper.CloseFdNoError(errpipe_read)
382 finally:
383 utils_wrapper.CloseFdNoError(pidpipe_write)
384
385
386 pidtext = utils_wrapper.RetryOnSignal(os.read, pidpipe_read, 128)
387 finally:
388 utils_wrapper.CloseFdNoError(pidpipe_read)
389
390
391 try:
392 os.waitpid(pid, 0)
393 except OSError:
394 pass
395
396 if errormsg:
397 raise errors.OpExecError("Error when starting daemon process: %r" %
398 errormsg)
399
400 try:
401 return int(pidtext)
402 except (ValueError, TypeError), err:
403 raise errors.OpExecError("Error while trying to parse PID %r: %s" %
404 (pidtext, err))
405
406
407 -def _StartDaemonChild(errpipe_read, errpipe_write,
408 pidpipe_read, pidpipe_write,
409 args, env, cwd,
410 output, fd_output, pidfile):
411 """Child process for starting daemon.
412
413 """
414 try:
415
416 utils_wrapper.CloseFdNoError(errpipe_read)
417 utils_wrapper.CloseFdNoError(pidpipe_read)
418
419
420 SetupDaemonEnv()
421
422
423 pid = os.fork()
424 if pid != 0:
425
426 os._exit(0)
427
428
429
430 utils_wrapper.SetCloseOnExecFlag(errpipe_write, True)
431
432
433 noclose_fds = [errpipe_write]
434
435
436 if pidfile:
437 fd_pidfile = utils_io.WritePidFile(pidfile)
438
439
440 noclose_fds.append(fd_pidfile)
441
442 utils_wrapper.SetCloseOnExecFlag(fd_pidfile, False)
443 else:
444 fd_pidfile = None
445
446 SetupDaemonFDs(output, fd_output)
447
448
449 utils_wrapper.RetryOnSignal(os.write, pidpipe_write, str(os.getpid()))
450
451
452 CloseFDs(noclose_fds=noclose_fds)
453
454
455 os.chdir(cwd)
456
457 if env is None:
458 os.execvp(args[0], args)
459 else:
460 os.execvpe(args[0], args, env)
461 except:
462 try:
463
464 WriteErrorToFD(errpipe_write, str(sys.exc_info()[1]))
465 except:
466
467 pass
468
469 os._exit(1)
470
471
473 """Possibly write an error message to a fd.
474
475 @type fd: None or int (file descriptor)
476 @param fd: if not None, the error will be written to this fd
477 @param err: string, the error message
478
479 """
480 if fd is None:
481 return
482
483 if not err:
484 err = "<unknown error>"
485
486 utils_wrapper.RetryOnSignal(os.write, fd, err)
487
488
490 """Raises L{utils_retry.RetryAgain} if child is still alive.
491
492 @raises utils_retry.RetryAgain: If child is still alive
493
494 """
495 if child.poll() is None:
496 raise utils_retry.RetryAgain()
497
498
500 """Waits for the child to terminate or until we reach timeout.
501
502 """
503 try:
504 utils_retry.Retry(_CheckIfAlive, (1.0, 1.2, 5.0), max(0, timeout),
505 args=[child])
506 except utils_retry.RetryTimeout:
507 pass
508
509
513 """Run a command and return its output.
514
515 @type cmd: string or list
516 @param cmd: Command to run
517 @type env: dict
518 @param env: The environment to use
519 @type via_shell: bool
520 @param via_shell: if we should run via the shell
521 @type cwd: string
522 @param cwd: the working directory for the program
523 @type interactive: boolean
524 @param interactive: Run command interactive (without piping)
525 @type timeout: int
526 @param timeout: Timeout after the programm gets terminated
527 @type noclose_fds: list
528 @param noclose_fds: list of additional (fd >=3) file descriptors to leave
529 open for the child process
530 @type input_fd: C{file}-like object containing an actual file descriptor
531 or numeric file descriptor
532 @param input_fd: File descriptor for process' standard input
533 @type postfork_fn: Callable receiving PID as parameter
534 @param postfork_fn: Function run after fork but before timeout
535 @rtype: tuple
536 @return: (out, err, status)
537
538 """
539 poller = select.poll()
540
541 if interactive:
542 stderr = None
543 stdout = None
544 else:
545 stderr = subprocess.PIPE
546 stdout = subprocess.PIPE
547
548 if input_fd:
549 stdin = input_fd
550 elif interactive:
551 stdin = None
552 else:
553 stdin = subprocess.PIPE
554
555 if noclose_fds:
556 preexec_fn = lambda: CloseFDs(noclose_fds)
557 close_fds = False
558 else:
559 preexec_fn = None
560 close_fds = True
561
562 child = subprocess.Popen(cmd, shell=via_shell,
563 stderr=stderr,
564 stdout=stdout,
565 stdin=stdin,
566 close_fds=close_fds, env=env,
567 cwd=cwd,
568 preexec_fn=preexec_fn)
569
570 if postfork_fn:
571 postfork_fn(child.pid)
572
573 out = StringIO()
574 err = StringIO()
575
576 linger_timeout = None
577
578 if timeout is None:
579 poll_timeout = None
580 else:
581 poll_timeout = utils_algo.RunningTimeout(timeout, True).Remaining
582
583 msg_timeout = ("Command %s (%d) run into execution timeout, terminating" %
584 (cmd, child.pid))
585 msg_linger = ("Command %s (%d) run into linger timeout, killing" %
586 (cmd, child.pid))
587
588 timeout_action = _TIMEOUT_NONE
589
590
591
592 assert (stdin == subprocess.PIPE) ^ (child.stdin is None), \
593 "subprocess' stdin did not behave as documented"
594
595 if not interactive:
596 if child.stdin is not None:
597 child.stdin.close()
598 poller.register(child.stdout, select.POLLIN)
599 poller.register(child.stderr, select.POLLIN)
600 fdmap = {
601 child.stdout.fileno(): (out, child.stdout),
602 child.stderr.fileno(): (err, child.stderr),
603 }
604 for fd in fdmap:
605 utils_wrapper.SetNonblockFlag(fd, True)
606
607 while fdmap:
608 if poll_timeout:
609 pt = poll_timeout() * 1000
610 if pt < 0:
611 if linger_timeout is None:
612 logging.warning(msg_timeout)
613 if child.poll() is None:
614 timeout_action = _TIMEOUT_TERM
615 utils_wrapper.IgnoreProcessNotFound(os.kill, child.pid,
616 signal.SIGTERM)
617 linger_timeout = \
618 utils_algo.RunningTimeout(_linger_timeout, True).Remaining
619 pt = linger_timeout() * 1000
620 if pt < 0:
621 break
622 else:
623 pt = None
624
625 pollresult = utils_wrapper.RetryOnSignal(poller.poll, pt)
626
627 for fd, event in pollresult:
628 if event & select.POLLIN or event & select.POLLPRI:
629 data = fdmap[fd][1].read()
630
631 if not data:
632 poller.unregister(fd)
633 del fdmap[fd]
634 continue
635 fdmap[fd][0].write(data)
636 if (event & select.POLLNVAL or event & select.POLLHUP or
637 event & select.POLLERR):
638 poller.unregister(fd)
639 del fdmap[fd]
640
641 if timeout is not None:
642 assert callable(poll_timeout)
643
644
645 if child.poll() is None:
646 _WaitForProcess(child, poll_timeout())
647
648
649 if child.poll() is None:
650 if linger_timeout is None:
651 logging.warning(msg_timeout)
652 timeout_action = _TIMEOUT_TERM
653 utils_wrapper.IgnoreProcessNotFound(os.kill, child.pid, signal.SIGTERM)
654 lt = _linger_timeout
655 else:
656 lt = linger_timeout()
657 _WaitForProcess(child, lt)
658
659
660 if child.poll() is None:
661 timeout_action = _TIMEOUT_KILL
662 logging.warning(msg_linger)
663 utils_wrapper.IgnoreProcessNotFound(os.kill, child.pid, signal.SIGKILL)
664
665 out = out.getvalue()
666 err = err.getvalue()
667
668 status = child.wait()
669 return out, err, status, timeout_action
670
671
672 -def _RunCmdFile(cmd, env, via_shell, output, cwd, noclose_fds):
673 """Run a command and save its output to a file.
674
675 @type cmd: string or list
676 @param cmd: Command to run
677 @type env: dict
678 @param env: The environment to use
679 @type via_shell: bool
680 @param via_shell: if we should run via the shell
681 @type output: str
682 @param output: the filename in which to save the output
683 @type cwd: string
684 @param cwd: the working directory for the program
685 @type noclose_fds: list
686 @param noclose_fds: list of additional (fd >=3) file descriptors to leave
687 open for the child process
688 @rtype: int
689 @return: the exit status
690
691 """
692 fh = open(output, "a")
693
694 if noclose_fds:
695 preexec_fn = lambda: CloseFDs(noclose_fds + [fh.fileno()])
696 close_fds = False
697 else:
698 preexec_fn = None
699 close_fds = True
700
701 try:
702 child = subprocess.Popen(cmd, shell=via_shell,
703 stderr=subprocess.STDOUT,
704 stdout=fh,
705 stdin=subprocess.PIPE,
706 close_fds=close_fds, env=env,
707 cwd=cwd,
708 preexec_fn=preexec_fn)
709
710 child.stdin.close()
711 status = child.wait()
712 finally:
713 fh.close()
714 return status
715
716
717 -def RunParts(dir_name, env=None, reset_env=False):
718 """Run Scripts or programs in a directory
719
720 @type dir_name: string
721 @param dir_name: absolute path to a directory
722 @type env: dict
723 @param env: The environment to use
724 @type reset_env: boolean
725 @param reset_env: whether to reset or keep the default os environment
726 @rtype: list of tuples
727 @return: list of (name, (one of RUNDIR_STATUS), RunResult)
728
729 """
730 rr = []
731
732 try:
733 dir_contents = utils_io.ListVisibleFiles(dir_name)
734 except OSError, err:
735 logging.warning("RunParts: skipping %s (cannot list: %s)", dir_name, err)
736 return rr
737
738 for relname in sorted(dir_contents):
739 fname = utils_io.PathJoin(dir_name, relname)
740 if not (constants.EXT_PLUGIN_MASK.match(relname) is not None and
741 utils_wrapper.IsExecutable(fname)):
742 rr.append((relname, constants.RUNPARTS_SKIP, None))
743 else:
744 try:
745 result = RunCmd([fname], env=env, reset_env=reset_env)
746 except Exception, err:
747 rr.append((relname, constants.RUNPARTS_ERR, str(err)))
748 else:
749 rr.append((relname, constants.RUNPARTS_RUN, result))
750
751 return rr
752
753
755 """Returns the path for a PID's proc status file.
756
757 @type pid: int
758 @param pid: Process ID
759 @rtype: string
760
761 """
762 return "/proc/%d/status" % pid
763
764
766 """Returns the command line of a pid as a list of arguments.
767
768 @type pid: int
769 @param pid: Process ID
770 @rtype: list of string
771
772 @raise EnvironmentError: If the process does not exist
773
774 """
775 proc_path = "/proc/%d/cmdline" % pid
776 with open(proc_path, 'r') as f:
777 nulled_cmdline = f.read()
778
779
780 return nulled_cmdline.split('\x00')
781
782
784 """Check if a given pid exists on the system.
785
786 @note: zombie status is not handled, so zombie processes
787 will be returned as alive
788 @type pid: int
789 @param pid: the process ID to check
790 @rtype: boolean
791 @return: True if the process exists
792
793 """
794 def _TryStat(name):
795 try:
796 os.stat(name)
797 return True
798 except EnvironmentError, err:
799 if err.errno in (errno.ENOENT, errno.ENOTDIR):
800 return False
801 elif err.errno == errno.EINVAL:
802 raise utils_retry.RetryAgain(err)
803 raise
804
805 assert isinstance(pid, int), "pid must be an integer"
806 if pid <= 0:
807 return False
808
809
810
811 try:
812 return utils_retry.Retry(_TryStat, (0.01, 1.5, 0.1), 0.5,
813 args=[_GetProcStatusPath(pid)])
814 except utils_retry.RetryTimeout, err:
815 err.RaiseInner()
816
817
819 """Determines whether a daemon is alive
820
821 @type name: string
822 @param name: daemon name
823
824 @rtype: boolean
825 @return: True if daemon is running, False otherwise
826
827 """
828 return IsProcessAlive(utils_io.ReadPidFile(utils_io.DaemonPidFileName(name)))
829
830
832 """Parse a rendered sigset_t value.
833
834 This is the opposite of the Linux kernel's fs/proc/array.c:render_sigset_t
835 function.
836
837 @type sigset: string
838 @param sigset: Rendered signal set from /proc/$pid/status
839 @rtype: set
840 @return: Set of all enabled signal numbers
841
842 """
843 result = set()
844
845 signum = 0
846 for ch in reversed(sigset):
847 chv = int(ch, 16)
848
849
850
851 if chv & 1:
852 result.add(signum + 1)
853 if chv & 2:
854 result.add(signum + 2)
855 if chv & 4:
856 result.add(signum + 3)
857 if chv & 8:
858 result.add(signum + 4)
859
860 signum += 4
861
862 return result
863
864
866 """Retrieves a field from the contents of a proc status file.
867
868 @type pstatus: string
869 @param pstatus: Contents of /proc/$pid/status
870 @type field: string
871 @param field: Name of field whose value should be returned
872 @rtype: string
873
874 """
875 for line in pstatus.splitlines():
876 parts = line.split(":", 1)
877
878 if len(parts) < 2 or parts[0] != field:
879 continue
880
881 return parts[1].strip()
882
883 return None
884
885
887 """Checks whether a process is handling a signal.
888
889 @type pid: int
890 @param pid: Process ID
891 @type signum: int
892 @param signum: Signal number
893 @rtype: bool
894
895 """
896 if status_path is None:
897 status_path = _GetProcStatusPath(pid)
898
899 try:
900 proc_status = utils_io.ReadFile(status_path)
901 except EnvironmentError, err:
902
903 if err.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL, errno.ESRCH):
904 return False
905 raise
906
907 sigcgt = _GetProcStatusField(proc_status, "SigCgt")
908 if sigcgt is None:
909 raise RuntimeError("%s is missing 'SigCgt' field" % status_path)
910
911
912 return signum in _ParseSigsetT(sigcgt)
913
914
916 """Daemonize the current process.
917
918 This detaches the current process from the controlling terminal and
919 runs it in the background as a daemon.
920
921 @type logfile: str
922 @param logfile: the logfile to which we should redirect stdout/stderr
923 @rtype: tuple; (int, callable)
924 @return: File descriptor of pipe(2) which must be closed to notify parent
925 process and a callable to reopen log files
926
927 """
928
929
930
931
932
933
934
935 (rpipe, wpipe) = os.pipe()
936
937
938 pid = os.fork()
939 if (pid == 0):
940 SetupDaemonEnv()
941
942
943 pid = os.fork()
944 if (pid == 0):
945 utils_wrapper.CloseFdNoError(rpipe)
946 else:
947
948 os._exit(0)
949 else:
950 utils_wrapper.CloseFdNoError(wpipe)
951
952
953 errormsg = utils_wrapper.RetryOnSignal(os.read, rpipe, 100 * 1024)
954 if errormsg:
955 sys.stderr.write("Error when starting daemon process: %r\n" % errormsg)
956 rcode = 1
957 else:
958 rcode = 0
959 os._exit(rcode)
960
961 reopen_fn = compat.partial(SetupDaemonFDs, logfile, None)
962
963
964 reopen_fn()
965
966 return (wpipe, reopen_fn)
967
968
971 """Kill a process given by its pid.
972
973 @type pid: int
974 @param pid: The PID to terminate.
975 @type signal_: int
976 @param signal_: The signal to send, by default SIGTERM
977 @type timeout: int
978 @param timeout: The timeout after which, if the process is still alive,
979 a SIGKILL will be sent. If not positive, no such checking
980 will be done
981 @type waitpid: boolean
982 @param waitpid: If true, we should waitpid on this process after
983 sending signals, since it's our own child and otherwise it
984 would remain as zombie
985
986 """
987 def _helper(pid, signal_, wait):
988 """Simple helper to encapsulate the kill/waitpid sequence"""
989 if utils_wrapper.IgnoreProcessNotFound(os.kill, pid, signal_) and wait:
990 try:
991 os.waitpid(pid, os.WNOHANG)
992 except OSError:
993 pass
994
995 if pid <= 0:
996
997 raise errors.ProgrammerError("Invalid pid given '%s'" % pid)
998
999 if not IsProcessAlive(pid):
1000 return
1001
1002 _helper(pid, signal_, waitpid)
1003
1004 if timeout <= 0:
1005 return
1006
1007 def _CheckProcess():
1008 if not IsProcessAlive(pid):
1009 return
1010
1011 try:
1012 (result_pid, _) = os.waitpid(pid, os.WNOHANG)
1013 except OSError:
1014 raise utils_retry.RetryAgain()
1015
1016 if result_pid > 0:
1017 return
1018
1019 raise utils_retry.RetryAgain()
1020
1021 try:
1022
1023 utils_retry.Retry(_CheckProcess, (0.01, 1.5, 0.1), timeout)
1024 except utils_retry.RetryTimeout:
1025 pass
1026
1027 if IsProcessAlive(pid):
1028
1029 _helper(pid, signal.SIGKILL, waitpid)
1030
1031
1033 """Runs a function in a separate process.
1034
1035 Note: Only boolean return values are supported.
1036
1037 @type fn: callable
1038 @param fn: Function to be called
1039 @rtype: bool
1040 @return: Function's result
1041
1042 """
1043 pid = os.fork()
1044 if pid == 0:
1045
1046 try:
1047
1048 utils_wrapper.ResetTempfileModule()
1049
1050
1051 result = int(bool(fn(*args)))
1052 assert result in (0, 1)
1053 except:
1054 logging.exception("Error while calling function in separate process")
1055
1056 result = 33
1057
1058 os._exit(result)
1059
1060
1061
1062
1063 (_, status) = os.waitpid(pid, 0)
1064
1065 if os.WIFSIGNALED(status):
1066 exitcode = None
1067 signum = os.WTERMSIG(status)
1068 else:
1069 exitcode = os.WEXITSTATUS(status)
1070 signum = None
1071
1072 if not (exitcode in (0, 1) and signum is None):
1073 raise errors.GenericError("Child program failed (code=%s, signal=%s)" %
1074 (exitcode, signum))
1075
1076 return bool(exitcode)
1077
1078
1080 """Close file descriptors.
1081
1082 This closes all file descriptors above 2 (i.e. except
1083 stdin/out/err).
1084
1085 @type noclose_fds: list or None
1086 @param noclose_fds: if given, it denotes a list of file descriptor
1087 that should not be closed
1088
1089 """
1090
1091 if 'SC_OPEN_MAX' in os.sysconf_names:
1092 try:
1093 MAXFD = os.sysconf('SC_OPEN_MAX')
1094 if MAXFD < 0:
1095 MAXFD = 1024
1096 except OSError:
1097 MAXFD = 1024
1098 else:
1099 MAXFD = 1024
1100
1101 maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1]
1102 if (maxfd == resource.RLIM_INFINITY):
1103 maxfd = MAXFD
1104
1105
1106 for fd in range(3, maxfd):
1107 if noclose_fds and fd in noclose_fds:
1108 continue
1109 utils_wrapper.CloseFdNoError(fd)
1110