15

Right now I have the following:

# this function is meant for future script expansions
# its purpose is clear, i.e. to clean up some temp files
# now, it is doing nothing, just a special null command
cleanup_on_signal() { :; }

# define functions to handle signals
# treat them as errors with appropriate messages
# example calls:
#    kill -15   this_script_name    # POSIX, all shells compatible
#    kill -TERM this_script_name    # Bash and alike - newer shells
signal_handler_HUP()   {  cleanup_on_signal; print_error_and_exit "\\ntrap()" "Caught SIGHUP (1).\\n\\tClean-up finished.\\n\\tTerminating. Bye!";    }
signal_handler_INT()   {  cleanup_on_signal; print_error_and_exit "\\ntrap()" "Caught SIGINT (2).\\n\\tClean-up finished.\\n\\tTerminating. Bye!";    }
signal_handler_QUIT()  {  cleanup_on_signal; print_error_and_exit "\\ntrap()" "Caught SIGQUIT (3).\\n\\tClean-up finished.\\n\\tTerminating. Bye!";   }
signal_handler_ABRT()  {  cleanup_on_signal; print_error_and_exit "\\ntrap()" "Caught SIGABRT (6).\\n\\tClean-up finished.\\n\\tTerminating. Bye!";   }
signal_handler_TERM()  {  cleanup_on_signal; print_error_and_exit "\\ntrap()" "Caught SIGTERM (15).\\n\\tClean-up finished.\\n\\tTerminating. Bye!";  }

# use the above functions as signal handlers;
# note that the SIG* constants are undefined in POSIX,
# and numbers are to be used for the signals instead
trap 'signal_handler_HUP' 1; trap 'signal_handler_INT' 2; trap 'signal_handler_QUIT' 3; trap 'signal_handler_ABRT' 6; trap 'signal_handler_TERM' 15

I want the script to terminate tidily on shutdown, which right now it does.

But I opened one suggestion of a colleague to issue a question on CTRL+C instead of quitting to shell.

I don't want to turn off the machine, I don't do that often, anyway:

What signal is sent to running programs / scripts on shutdown?

  • 3
    It is SIGTERM. see https://unix.stackexchange.com/questions/10231/when-does-the-system-send-a-sigterm-to-a-process – Rui F Ribeiro Feb 10 '19 at 11:43
  • I think, when the init manager (systemd/whatever) does not do their job on some process, then the remaining processes get a SIGTERM. The ones that do not obey SIGTERM, are then sent a SIGKILL. The system makes an effort to shutdown as cleanly as possible. – Rui F Ribeiro Feb 10 '19 at 11:49
  • Added some shutdown() snippet source. – Rui F Ribeiro Feb 10 '19 at 12:25

1 Answers1

16

While on shutdown the running processes are first told to stop by init(from sendsigs on old implementations, according to @JdeBP)/systemd.

The remaining processes, if any, are sent a SIGTERM. The ones that ignore SIGTERM or do not finish on time, are shortly thereafter sent a SIGKILL by init/systemd.

Those actions are meant to guarantee a stable/clean shutdown (as possible).

Out of curiosity, see the report of a related (old) systemd bug:

Bug 1352264 - systemd immediately sends SIGKILL after SIGTERM during shutdown

systemd immediately sends SIGKILL after SIGTERM during shutdown, there's no window of opportunity for processes to terminate

Also from shutdown.c/main():

    disable_coredumps();
log_info("Sending SIGTERM to remaining processes...");
broadcast_signal(SIGTERM, true, true, arg_timeout);

log_info("Sending SIGKILL to remaining processes...");
broadcast_signal(SIGKILL, true, false, arg_timeout);

Also from sysvinit 2.94 sources/init.c, here is the code around a SIGTERM round. If any process(es) were sent a SIGTERM, test each second during 5 seconds to see if there are any processes remaining. Leave the wait if in one of those tests none process is found, or send SIGKILL to the remaining process(es) after the 5 seconds are up.

        switch(round) { 
                case 0: /* Send TERM signal */
                        if (talk)
                                initlog(L_CO,
                                        "Sending processes configured via /etc/inittab the TERM signal");
                        kill(-(ch->pid), SIGTERM);
                        foundOne = 1;
                        break;
                case 1: /* Send KILL signal and collect status */
                        if (talk)
                                initlog(L_CO,
                                        "Sending processes configured via /etc/inittab the KILL signal");
                        kill(-(ch->pid), SIGKILL);
                        break;
        }
        talk = 0;

    }
    /*
     *  See if we have to wait 5 seconds
     */
    if (foundOne && round == 0) {
        /*
         *      Yup, but check every second if we still have children.
         */
        for(f = 0; f < sleep_time; f++) {
                for(ch = family; ch; ch = ch->next) {
                        if (!(ch->flags & KILLME)) continue;
                        if ((ch->flags & RUNNING) && !(ch->flags & ZOMBIE))
                                break;
                }
                if (ch == NULL) {
                        /*
                         *      No running children, skip SIGKILL
                         */
                        round = 1;
                        foundOne = 0; /* Skip the sleep below. */
                        break;
                }
                do_sleep(1);
        }
    }
  }

  /*
   *    Now give all processes the chance to die and collect exit statuses.
   */
  if (foundOne) do_sleep(1)
  for(ch = family; ch; ch = ch->next)
        if (ch->flags & KILLME) {
                if (!(ch->flags & ZOMBIE))
                    initlog(L_CO, "Pid %d [id %s] seems to hang", ch->pid,
                                ch->id);
                else {
                    INITDBG(L_VB, "Updating utmp for pid %d [id %s]",
                                ch->pid, ch->id);
                    ch->flags &= ~RUNNING;
                    if (ch->process[0] != '+')
                        write_utmp_wtmp("", ch->id, ch->pid, DEAD_PROCESS, NULL);
                }
        }

  /*
   *    Both rounds done; clean up the list.
   */
Rui F Ribeiro
  • 56,709
  • 26
  • 150
  • 232