nix-processmgmt/webapp/daemonize.c

282 lines
8.2 KiB
C

/*
* Copyright 2020 Sander van der Burg
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "daemonize.h"
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#define NULL_DEV_FILE "/dev/null"
#define TRUE 1
#define FALSE 0
#define BUFFER_SIZE 10
static int close_non_standard_file_descriptors(void)
{
unsigned int i;
struct rlimit rlim;
int num_of_fds = getrlimit(RLIMIT_NOFILE, &rlim);
if(num_of_fds == -1)
return FALSE;
for(i = 3; i < num_of_fds; i++)
close(i);
return TRUE;
}
static int reset_signal_handlers_to_default(void)
{
#if defined _NSIG
unsigned int i;
for(i = 1; i < _NSIG; i++)
{
if(i != SIGKILL && i != SIGSTOP)
signal(i, SIG_DFL);
}
#endif
return TRUE;
}
static int clear_signal_mask(void)
{
sigset_t set;
return((sigemptyset(&set) == 0)
&& (sigprocmask(SIG_SETMASK, &set, NULL) == 0));
}
static int create_pid_file(const char *pid_file)
{
pid_t my_pid = getpid();
char my_pid_str[10];
int fd;
sprintf(my_pid_str, "%d", my_pid);
if((fd = open(pid_file, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR)) == -1)
return FALSE;
if(write(fd, my_pid_str, strlen(my_pid_str)) == -1)
return FALSE;
close(fd);
return TRUE;
}
static int attach_standard_file_descriptors_to_null(void)
{
int null_fd_read, null_fd_write;
return(((null_fd_read = open(NULL_DEV_FILE, O_RDONLY)) != -1)
&& (dup2(null_fd_read, STDIN_FILENO) != -1)
&& ((null_fd_write = open(NULL_DEV_FILE, O_WRONLY)) != -1)
&& (dup2(null_fd_write, STDOUT_FILENO) != -1)
&& (dup2(null_fd_write, STDERR_FILENO) != -1));
}
static void notify_parent_process(int writefd, DaemonStatus message)
{
char byte = (char)message;
while(write(writefd, &byte, 1) == 0);
close(writefd);
}
static pid_t fork_daemon_process(int writefd, const char *pid_file, void *data, int (*initialize_daemon) (void *data), int (*run_main_loop) (void *data))
{
pid_t pid = fork();
if(pid == 0)
{
if(!attach_standard_file_descriptors_to_null())
{
notify_parent_process(writefd, STATUS_CANNOT_ATTACH_STD_FDS_TO_NULL);
exit(STATUS_CANNOT_ATTACH_STD_FDS_TO_NULL);
}
umask(0);
if(chdir("/") == -1)
{
notify_parent_process(writefd, STATUS_CANNOT_CHDIR);
exit(STATUS_CANNOT_CHDIR);
}
if(!create_pid_file(pid_file))
{
notify_parent_process(writefd, STATUS_CANNOT_CREATE_PID_FILE);
exit(STATUS_CANNOT_CREATE_PID_FILE);
}
if(!initialize_daemon(data))
{
notify_parent_process(writefd, STATUS_CANNOT_INIT_DAEMON);
unlink(pid_file);
exit(STATUS_CANNOT_INIT_DAEMON);
}
notify_parent_process(writefd, STATUS_INIT_SUCCESS);
int exit_status = run_main_loop(data);
if(unlink(pid_file) == 0)
exit(STATUS_CANNOT_UNLINK_PID_FILE);
exit(exit_status);
}
return pid;
}
static pid_t fork_helper_process(int pipefd[2], const char *pid_file, void *data, int (*initialize_daemon) (void *data), int (*run_main_loop) (void *data))
{
pid_t pid = fork();
if(pid == 0)
{
close(pipefd[0]); /* Close unneeded read-end */
if(setsid() == -1)
{
notify_parent_process(pipefd[1], STATUS_CANNOT_SET_SID);
exit(STATUS_CANNOT_SET_SID);
}
/* Fork again, so that the terminal can not be acquired again */
if(fork_daemon_process(pipefd[1], pid_file, data, initialize_daemon, run_main_loop) == -1)
{
notify_parent_process(pipefd[1], STATUS_CANNOT_FORK_DAEMON_PROCESS);
exit(STATUS_CANNOT_FORK_DAEMON_PROCESS);
}
exit(0); /* Exit the helper process, so that the daemon process gets adopted by PID 1 */
}
return pid;
}
static DaemonStatus wait_for_notification_message(int readfd)
{
char buf[BUFFER_SIZE];
ssize_t bytes_read = read(readfd, buf, 1);
if(bytes_read == -1)
return STATUS_CANNOT_READ_FROM_PIPE;
else if(bytes_read == 0)
return STATUS_UNKNOWN_DAEMON_ERROR;
else
return buf[0];
}
DaemonStatus daemonize(const char *pid_file, void *data, int (*initialize_daemon) (void *data), int (*run_main_loop) (void *data))
{
int pipefd[2];
if(!close_non_standard_file_descriptors())
return STATUS_CANNOT_CLOSE_NON_STD_FDS;
if(!reset_signal_handlers_to_default())
return STATUS_CANNOT_RESET_SIGNAL_HANDLERS;
if(!clear_signal_mask())
return STATUS_CANNOT_CLEAR_SIGNAL_MASK;
if(pipe(pipefd) == -1)
return STATUS_CANNOT_CREATE_PIPE;
else
{
if(fork_helper_process(pipefd, pid_file, data, initialize_daemon, run_main_loop) == -1)
return STATUS_CANNOT_FORK_HELPER_PROCESS;
else
{
DaemonStatus exit_status;
close(pipefd[1]); /* Close unneeded write end */
exit_status = wait_for_notification_message(pipefd[0]);
close(pipefd[0]);
return exit_status;
}
}
}
void print_daemon_status(DaemonStatus status, FILE *file)
{
switch(status)
{
case STATUS_INIT_SUCCESS:
fprintf(file, "Daemon initialized successfully!\n");
break;
case STATUS_CANNOT_ATTACH_STD_FDS_TO_NULL:
fprintf(file, "Cannot attach standard file descriptors to " NULL_DEV_FILE " !\n");
break;
case STATUS_CANNOT_CHDIR:
fprintf(file, "Cannot change current working directory to /\n");
break;
case STATUS_CANNOT_CREATE_PID_FILE:
fprintf(file, "Cannot create PID file!\n");
break;
case STATUS_CANNOT_INIT_DAEMON:
fprintf(file, "Cannot initialize the daemon!\n");
break;
case STATUS_CANNOT_UNLINK_PID_FILE:
fprintf(file, "Cannot unlink PID file!\n");
break;
case STATUS_CANNOT_CLOSE_NON_STD_FDS:
fprintf(file, "Cannot close the non standard file descriptors!\n");
break;
case STATUS_CANNOT_RESET_SIGNAL_HANDLERS:
fprintf(file, "Cannot reset signal handlers!\n");
break;
case STATUS_CANNOT_CLEAR_SIGNAL_MASK:
fprintf(file, "Cannot clear signal mask!\n");
break;
case STATUS_CANNOT_CREATE_PIPE:
fprintf(file, "Cannot create pipe!\n");
break;
case STATUS_CANNOT_FORK_HELPER_PROCESS:
fprintf(file, "Cannot fork helper process!\n");
break;
case STATUS_CANNOT_READ_FROM_PIPE:
fprintf(file, "Cannot read from pipe!\n");
break;
case STATUS_CANNOT_SET_SID:
fprintf(file, "Cannot set session ID!\n");
break;
case STATUS_CANNOT_FORK_DAEMON_PROCESS:
fprintf(file, "Cannot fork daemon process!\n");
break;
case STATUS_UNKNOWN_DAEMON_ERROR:
fprintf(file, "Unknown daemon error!\n");
break;
}
}