From 01446a56c76aa5ac3213a86f8992a2371a8301f3 Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sat, 9 Nov 2019 16:04:14 +0000 Subject: [PATCH] Remove the daemon pid file when exit is due to SIGTERM. Bug 340 --- doc/doc-docbook/spec.xfpt | 11 ++++ doc/doc-txt/ChangeLog | 2 + src/src/daemon.c | 110 ++++++++++++++++++++++++++++++----- src/src/exim.c | 8 ++- src/src/functions.h | 1 + test/log/0438 | 1 + test/runtest | 1 + test/scripts/0000-Basic/0438 | 13 +++++ test/stderr/0438 | 22 +++++++ test/stdout/0438 | 3 + 10 files changed, 156 insertions(+), 16 deletions(-) create mode 100644 test/stdout/0438 diff --git a/doc/doc-docbook/spec.xfpt b/doc/doc-docbook/spec.xfpt index 62109e915..19c15c211 100644 --- a/doc/doc-docbook/spec.xfpt +++ b/doc/doc-docbook/spec.xfpt @@ -4367,6 +4367,17 @@ written. When &%-oX%& is used with &%-bd%&, or when &%-q%& with a time is used without &%-bd%&, this is the only way of causing Exim to write a pid file, because in those cases, the normal pid file is not used. +.new +.vitem &%-oPX%& +.oindex "&%-oPX%&" +.cindex "pid (process id)" "of daemon" +.cindex "daemon" "process id (pid)" +This option is not intended for general use. +The daemon uses it when terminating due to a SIGTEM, possibly in +combination with &%-oP%&&~<&'path'&>. +It causes the pid file to be removed. +.wen + .vitem &%-or%&&~<&'time'&> .oindex "&%-or%&" .cindex "timeout" "for non-SMTP input" diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index e9a614c0a..a8cd823b5 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -24,6 +24,8 @@ JH/04 Support CHUNKING from an smtp transport using a transport_filter, when JH/05 Regard command-line receipients as tainted. +JH/06 Bug 340: Remove the daemon pid file on exit, whe due to SIGTERM. + Exim version 4.93 ----------------- diff --git a/src/src/daemon.c b/src/src/daemon.c index 3fc73babe..61371f592 100644 --- a/src/src/daemon.c +++ b/src/src/daemon.c @@ -31,6 +31,7 @@ static smtp_slot empty_smtp_slot = { .pid = 0, .host_address = NULL }; static SIGNAL_BOOL sigchld_seen; static SIGNAL_BOOL sighup_seen; +static SIGNAL_BOOL sigterm_seen; static int accept_retry_count = 0; static int accept_retry_errno; @@ -87,6 +88,16 @@ sigchld_seen = TRUE; } +/* SIGTERM handler. Try to get the damon pif file removed +before exiting. */ + +static void +main_sigterm_handler(int sig) +{ +sigterm_seen = TRUE; +} + + /************************************************* @@ -430,6 +441,7 @@ if (pid == 0) #else signal(SIGCHLD, SIG_IGN); #endif + signal(SIGTERM, SIG_DFL); /* Attempt to get an id from the sending machine via the RFC 1413 protocol. We do this in the sub-process in order not to hold up the @@ -654,6 +666,7 @@ if (pid == 0) signal(SIGHUP, SIG_DFL); signal(SIGCHLD, SIG_DFL); + signal(SIGTERM, SIG_DFL); if (geteuid() != root_uid && !deliver_drop_privilege) { @@ -888,6 +901,77 @@ while ((pid = waitpid(-1, &status, WNOHANG)) > 0) +static void +set_pid_file_path(void) +{ +if (override_pid_file_path) + pid_file_path = override_pid_file_path; + +if (!*pid_file_path) + pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory); +} + + +/* Remove the daemon's pidfile. Note: runs with root privilege, +as a direct child of the daemon. Does not return. */ + +void +delete_pid_file(void) +{ +uschar * daemon_pid = string_sprintf("%d\n", (int)getppid()); +FILE * f; + +set_pid_file_path(); +if ((f = Ufopen(pid_file_path, "rb"))) + { + if ( fgets(CS big_buffer, big_buffer_size, f) + && Ustrcmp(daemon_pid, big_buffer) == 0 + ) + if (Uunlink(pid_file_path) == 0) + { + DEBUG(D_any) + debug_printf("%s unlink: %s\n", pid_file_path, strerror(errno)); + } + else + DEBUG(D_any) + debug_printf("unlinked %s\n", pid_file_path); + fclose(f); + } +else + DEBUG(D_any) + debug_printf("%s\n", string_open_failed(errno, "pid file %s", + pid_file_path)); +exim_exit(EXIT_SUCCESS, US"pid file remover"); +} + + +/* Called by the daemon; exec a child to get the pid file deleted +since we may require privs for the containing directory */ + +static void +daemon_die(void) +{ +int pid; + +if (f.running_in_test_harness || write_pid) + { + if ((pid = fork()) == 0) + { + if (override_pid_file_path) + (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 3, + "-oP", override_pid_file_path, "-oPX"); + else + (void)child_exec_exim(CEE_EXEC_PANIC, FALSE, NULL, FALSE, 1, "-oPX"); + + /* Control never returns here. */ + } + if (pid > 0) + child_close(pid, 1); + } +exim_exit(EXIT_SUCCESS, US"daemon"); +} + + /************************************************* * Exim Daemon Mainline * *************************************************/ @@ -1068,19 +1152,14 @@ if (f.daemon_listen && !f.inetd_wait_mode) gstring * new_smtp_port = NULL; gstring * new_local_interfaces = NULL; - if (override_pid_file_path == NULL) write_pid = FALSE; + if (!override_pid_file_path) write_pid = FALSE; list = override_local_interfaces; sep = 0; while ((s = string_nextinlist(&list, &sep, big_buffer, big_buffer_size))) { uschar joinstr[4]; - gstring ** gp; - - if (Ustrpbrk(s, ".:") == NULL) - gp = &new_smtp_port; - else - gp = &new_local_interfaces; + gstring ** gp = Ustrpbrk(s, ".:") ? &new_local_interfaces : &new_smtp_port; if (!*gp) { @@ -1538,12 +1617,7 @@ if (f.running_in_test_harness || write_pid) { FILE *f; - if (override_pid_file_path) - pid_file_path = override_pid_file_path; - - if (pid_file_path[0] == 0) - pid_file_path = string_sprintf("%s/exim-daemon.pid", spool_directory); - + set_pid_file_path(); if ((f = modefopen(pid_file_path, "wb", 0644))) { (void)fprintf(f, "%d\n", (int)getpid()); @@ -1586,11 +1660,15 @@ if (queue_interval > 0 && local_queue_run_max > 0) for (int i = 0; i < local_queue_run_max; i++) queue_pid_slots[i] = 0; } -/* Set up the handler for termination of child processes. */ +/* Set up the handler for termination of child processes, and the one +telling us to die. */ sigchld_seen = FALSE; os_non_restarting_signal(SIGCHLD, main_sigchld_handler); +sigterm_seen = FALSE; +os_non_restarting_signal(SIGTERM, main_sigterm_handler); + /* If we are to run the queue periodically, pretend the alarm has just gone off. This will cause the first queue-runner to get kicked off straight away. */ @@ -1791,6 +1869,9 @@ for (;;) EXIM_SOCKLEN_T len; pid_t pid; + if (sigterm_seen) + daemon_die(); /* Does not return */ + /* This code is placed first in the loop, so that it gets obeyed at the start, before the first wait, for the queue-runner case, so that the first one can be started immediately. @@ -1868,6 +1949,7 @@ for (;;) signal(SIGHUP, SIG_DFL); signal(SIGCHLD, SIG_DFL); + signal(SIGTERM, SIG_DFL); /* Re-exec if privilege has been given up, unless deliver_drop_ privilege is set. Reset SIGALRM before exec(). */ diff --git a/src/src/exim.c b/src/src/exim.c index a30e35bca..f29435476 100644 --- a/src/src/exim.c +++ b/src/src/exim.c @@ -202,7 +202,7 @@ va_end(ap); static void term_handler(int sig) { - exit(1); +exit(1); } @@ -3067,11 +3067,15 @@ for (i = 1; i < argc; i++) else if (Ustrcmp(argrest, "o") == 0) {} - /* -oP : set pid file path for daemon */ + /* -oP : set pid file path for daemon + -oPX: delete pid file of daemon */ else if (Ustrcmp(argrest, "P") == 0) override_pid_file_path = argv[++i]; + else if (Ustrcmp(argrest, "PX") == 0) + delete_pid_file(); + /* -or : set timeout for non-SMTP acceptance -os : set timeout for SMTP acceptance */ diff --git a/src/src/functions.h b/src/src/functions.h index 488e84c6c..187bdafa6 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -175,6 +175,7 @@ extern void debug_print_tree(tree_node *); extern void debug_vprintf(int, const char *, va_list); extern void decode_bits(unsigned int *, size_t, int *, uschar *, bit_table *, int, uschar *, int); +extern void delete_pid_file(void); extern address_item *deliver_make_addr(uschar *, BOOL); extern void delivery_log(int, address_item *, int, uschar *); extern int deliver_message(uschar *, BOOL, BOOL); diff --git a/test/log/0438 b/test/log/0438 index 574cf9bef..78796810d 100644 --- a/test/log/0438 +++ b/test/log/0438 @@ -2,3 +2,4 @@ ******** SERVER ******** 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D +1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port PORT_D diff --git a/test/runtest b/test/runtest index 9f6569128..97edbc244 100755 --- a/test/runtest +++ b/test/runtest @@ -1029,6 +1029,7 @@ RESET_AFTER_EXTRA_LINE_READ: s/(?<=^>>>>>>>>>>>>>>>> Exim pid=)\d+(?= terminating)/pppp/; s/^(proxy-proc \w{5}-pid) \d+$/$1 pppp/; + s/^(?:\d+)( exec .* -oPX)$/pppp$1/; # IP address lookups use gethostbyname() when IPv6 is not supported, # and gethostbyname2() or getipnodebyname() when it is. diff --git a/test/scripts/0000-Basic/0438 b/test/scripts/0000-Basic/0438 index 099efba0d..a8287cf9f 100644 --- a/test/scripts/0000-Basic/0438 +++ b/test/scripts/0000-Basic/0438 @@ -8,3 +8,16 @@ killdaemon exim -d -DSERVER=server -bd -oX PORT_D -oP DIR/spool/exim-daemon.anotherpid **** killdaemon +# +# Check for a SIGTERM daemon kill removing the pid file +exim -d -DSERVER=server -bd -oX PORT_D -oP DIR/spool/mypidfile +**** +sleep 1 +ls DIR/spool +sudo perl +open(IN, "<", "DIR/spool/mypidfile"); +while () { kill "TERM", $_; } +**** +sleep 1 +ls DIR/spool +# diff --git a/test/stderr/0438 b/test/stderr/0438 index f44d7bb76..bb6ba3f53 100644 --- a/test/stderr/0438 +++ b/test/stderr/0438 @@ -36,3 +36,25 @@ LOG: MAIN set_process_info: pppp daemon(x.yz): no queue runs, listening for SMTP on port 1225 daemon running with uid=EXIM_UID gid=EXIM_GID euid=EXIM_UID egid=EXIM_GID Listening... +Exim version x.yz .... +changed uid/gid: forcing real = effective + uid=uuuu gid=CALLER_GID pid=pppp +configuration file is TESTSUITE/test-config +admin user +dropping to exim gid; retaining priv uid +originator: uid=CALLER_UID gid=CALLER_GID login=CALLER name=CALLER_NAME +daemon_smtp_port overridden by -oX: + <: 1225 +listening on all interfaces (IPv4) port 1225 +pid written to TESTSUITE/spool/mypidfile +changed uid/gid: running as a daemon + uid=EXIM_UID gid=EXIM_GID pid=pppp +LOG: MAIN + exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 +set_process_info: pppp daemon(x.yz): no queue runs, listening for SMTP on port 1225 +daemon running with uid=EXIM_UID gid=EXIM_GID euid=EXIM_UID egid=EXIM_GID +Listening... +OpenSSL: creating STEK +pppp exec TESTSUITE/eximdir/exim -DEXIM_PATH=TESTSUITE/eximdir/exim -DSERVER=server -C TESTSUITE/test-config -d=0xf7795cfd -oP TESTSUITE/spool/mypidfile -oPX +search_tidyup called +>>>>>>>>>>>>>>>> Exim pid=pppp (daemon) terminating with rc=0 >>>>>>>>>>>>>>>> diff --git a/test/stdout/0438 b/test/stdout/0438 new file mode 100644 index 000000000..431c133d8 --- /dev/null +++ b/test/stdout/0438 @@ -0,0 +1,3 @@ +log +mypidfile +log -- 2.30.2