/core/server/OpenXPKI/Daemonize.pm

https://github.com/mbartosch/openxpki · Perl · 183 lines · 120 code · 42 blank · 21 comment · 15 complexity · 6039b269b8b87b6e75ef08f3cca3126b MD5 · raw file

  1. package OpenXPKI::Daemonize;
  2. use Moose;
  3. =head1 Name
  4. OpenXPKI::Daemonize - Helper functions to cleanly fork background processes
  5. =cut
  6. # Core modules
  7. use English;
  8. # CPAN modules
  9. use POSIX qw(:signal_h setuid setgid);
  10. # Project modules
  11. use OpenXPKI::Debug;
  12. use OpenXPKI::Exception;
  13. has max_fork_redo => (
  14. is => 'rw',
  15. isa => 'Int',
  16. default => 5,
  17. );
  18. has sighup_handler => (
  19. is => 'rw',
  20. isa => 'CodeRef',
  21. );
  22. has sigterm_handler => (
  23. is => 'rw',
  24. isa => 'CodeRef',
  25. );
  26. has uid => (
  27. is => 'rw',
  28. isa => 'Int',
  29. );
  30. has gid => (
  31. is => 'rw',
  32. isa => 'Int',
  33. );
  34. has old_sig_set => (
  35. is => 'rw',
  36. isa => 'POSIX::SigSet',
  37. init_arg => undef,
  38. );
  39. =head1 METHODS
  40. =head2 fork_child
  41. Tries to fork a child process.
  42. Return value depends on who returns: parent will get the child PID and child
  43. will get 0.
  44. An exception will be thrown if the fork fails.
  45. B<Note on STDIN, STDOUT, STDERR>
  46. All IO handles will be connected to I</dev/null> with one exception: if C<STDERR>
  47. was already redirected to a file (and is not a terminal) then it is left untouched.
  48. This is to make sure error messages still go to the desired log files.
  49. B<Note on SIGCHLD>
  50. For the parent process we set C<$SIG{CHLD} = "IGNORE"> to prevent zombie child
  51. processes.
  52. But C<IGNORE> can lead to problems with system calls e.g. via L<Proc::SafeExec>
  53. or L<system>, see
  54. L<the Perl CookBook|https://docstore.mik.ua/orelly/perl/cookbook/ch16_20.htm>
  55. for details.
  56. Thus in the child process we set C<$SIG{CHLD} = "DEFAULT"> to prevent these
  57. problems.
  58. But in the parent process after forking you should manually set
  59. C<$SIG{CHLD} = "DEFAULT"> if you want to do system calls.
  60. =cut
  61. sub fork_child {
  62. my ($self) = @_;
  63. $SIG{CHLD} = 'IGNORE'; # IGNORE means: child zombies are auto-removed from process table
  64. my $pid = $self->_try_fork($self->max_fork_redo);
  65. # parent process: return on successful fork
  66. if ($pid > 0) { return $pid }
  67. #
  68. # child process
  69. #
  70. # Reset handler for SIGCHLD:
  71. # IGNORE could prevent Proc::SafeExec or system() from working correctly
  72. # (see https://docstore.mik.ua/orelly/perl/cookbook/ch16_20.htm)
  73. $SIG{CHLD} = 'DEFAULT';
  74. $SIG{HUP} = $self->sighup_handler if $self->sighup_handler;
  75. $SIG{TERM} = $self->sigterm_handler if $self->sigterm_handler;
  76. if ($self->gid) {
  77. setgid($self->gid);
  78. }
  79. if ($self->uid) {
  80. setuid($self->uid);
  81. $ENV{USER} = getpwuid($self->uid);
  82. $ENV{HOME} = ((getpwuid($self->uid))[7]);
  83. }
  84. umask 0;
  85. chdir '/';
  86. open STDIN, '<', '/dev/null';
  87. open STDOUT, '>', '/dev/null';
  88. open STDERR, '>>', '/dev/null' if (-t STDERR); # only touch STDERR if it's not already redirected to a file
  89. # Re-seed Perl random number generator
  90. srand(time ^ $PROCESS_ID);
  91. return $pid;
  92. }
  93. # "The most paranoid of programmers block signals for a fork to prevent a
  94. # signal handler in the child process being called before Perl can update
  95. # the child's $$ variable, its process id."
  96. # (https://docstore.mik.ua/orelly/perl/cookbook/ch16_21.htm)
  97. sub _block_sigint {
  98. my ($self) = @_;
  99. my $sigint = POSIX::SigSet->new(SIGINT);
  100. sigprocmask(SIG_BLOCK, $sigint, $self->old_sig_set)
  101. or OpenXPKI::Exception->throw(
  102. message => 'Unable to block SIGINT before fork()',
  103. log => { priority => 'fatal', facility => 'system' }
  104. );
  105. }
  106. sub _unblock_sigint {
  107. my ($self) = @_;
  108. sigprocmask(SIG_SETMASK, $self->old_sig_set)
  109. or OpenXPKI::Exception->throw(
  110. message => 'Unable to reset old signals after fork()',
  111. log => { priority => 'fatal', facility => 'system' }
  112. );
  113. }
  114. sub _try_fork {
  115. my ($self, $max_tries) = @_;
  116. for (my $i = 0; $i < $max_tries; $i++) {
  117. $self->_block_sigint;
  118. my $pid = fork;
  119. $self->_unblock_sigint;
  120. # parent or child: successful fork
  121. if (defined $pid) { return $pid }
  122. # parent: unsuccessful fork
  123. # EAGAIN - fork cannot allocate sufficient memory to copy the parent's
  124. # page tables and allocate a task structure for the child.
  125. # ENOMEM - fork failed to allocate the necessary kernel structures
  126. # because memory is tight.
  127. if ($! != POSIX::EAGAIN() and $! != POSIX::ENOMEM()) {
  128. OpenXPKI::Exception->throw(
  129. message => 'fork() failed with an unrecoverable error',
  130. params => { error => $! },
  131. log => { priority => 'fatal', facility => 'system' }
  132. );
  133. }
  134. sleep 2;
  135. }
  136. OpenXPKI::Exception->throw(
  137. message => 'fork() failed due to insufficient memory, tried $max_tries times',
  138. log => { priority => 'fatal', facility => 'system' }
  139. );
  140. }
  141. __PACKAGE__->meta->make_immutable;