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