]> git.e2factory.org Git - e2factory.git/commitdiff
Improve robustness of signal handling
authorTobias Ulmer <tu@emlix.com>
Wed, 20 Dec 2017 18:03:13 +0000 (19:03 +0100)
committerTobias Ulmer <tu@emlix.com>
Mon, 10 Dec 2018 17:00:11 +0000 (18:00 +0100)
Signed-off-by: Tobias Ulmer <tu@emlix.com>
generic/e2lib.lua
generic/le2lib.c
global/e2.lua
local/e2tool.lua

index 685eb29215569ffcfa9c5aa6bbe2bbd1ae0c95ed..afa2204ec126938a7c05898af6b81afd7c7ede60 100644 (file)
@@ -321,17 +321,33 @@ function e2lib.setenv(var, val, overwrite)
 end
 
 --- Reset signal handlers back to their default.
--- @return True on success, false on error.
--- @return Error object on failure.
+-- @raise Error on failure.
 function e2lib.signal_reset()
     local rc, errstring
 
     rc, errstring = le2lib.signal_reset()
     if not rc then
-        return false, err.new("resetting signal handlers: %s", errstring)
+        error(err.new("resetting signal handlers: %s", errstring))
     end
+end
 
-    return true
+--- Install signal handlers that will call interrupt_hook()
+-- @raise Error on failure.
+function e2lib.signal_install()
+    local rc, errstring
+
+    rc, errstring = le2lib.signal_install()
+    if not rc then
+        error(err.new("installing signal handlers: %s", errstring))
+    end
+end
+
+--- Get the first signal triggering shutdown.
+-- Returns empty string if no signal has yet occured.
+-- @return Signal name (eg "Interrupt") or "".
+-- @return Signal number or 0.
+function e2lib.signal_received()
+    return le2lib.signal_received()
 end
 
 --- Get current process id.
@@ -341,20 +357,6 @@ function e2lib.getpid()
     return le2lib.getpid()
 end
 
---- Set process group ID.
--- @param pid process id
--- @param pgid process group id
--- @raise error on failure.
-function e2lib.setpgid(pid, pgid)
-    return le2lib.setpgid(pid, pgid)
-end
-
---- Ignore SIGINT
--- @raise error on failure
-function e2lib.ignore_sigint()
-    le2lib.ignore_sigint()
-end
-
 --- Send signal to process.
 -- @param pid Process ID to signal (number).
 -- @param sig Signal number.
@@ -387,11 +389,13 @@ function e2lib.execvp(filenm, argv)
 end
 
 --- Interrupt handling.
---
--- le2lib sets up a SIGINT handler that calls back into this function.
+-- signal_install() sets up signal handlers that call back into this function.
 function e2lib.interrupt_hook()
+    local sigstr, signum = e2lib.signal_received()
     trace.install() -- reinstall the trace hook.
-    e2lib.abort("*interrupted by user*")
+    e2lib.logf(4, "interrupt_hook() pid=%d, signal=%d, sigstr=%s",
+        e2lib.getpid(), signum, sigstr)
+    e2lib.abort(string.format("*** interrupted by user (%s) ***", sigstr))
 end
 
 --- Make sure the environment variables inside the globals table are
@@ -406,10 +410,7 @@ function e2lib.init()
     trace.install()
     trace.default_filter()
 
-    local rc, re = e2lib.signal_reset()
-    if not rc then
-        e2lib.abort(re)
-    end
+    e2lib.signal_reset()
 
     e2lib.closefrom(3)
     -- ignore errors, no /proc should not prevent factory from working
@@ -1460,6 +1461,8 @@ function e2lib.callcmd(argv, fdctv, workdir, envdict, nowait)
         -- potentially mixes with the output of the command
         e2lib.setlog(4, false)
 
+        e2lib.signal_reset()
+
         fd_child_setup(fdctv)
 
         if workdir then
index be4ebc61e9a7788b21452552ea8adac2b7d057e8..2b39536717faef8240995975d3d1f6e1d1c1e4e7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007-2016 emlix GmbH, see file AUTHORS
+ * Copyright (C) 2007-2017 emlix GmbH, see file AUTHORS
  *
  * This file is part of e2factory, the emlix embedded build system.
  * For more information see http://www.e2factory.org
@@ -27,6 +27,7 @@
 #include <errno.h>
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <sys/prctl.h>
 #include <signal.h>
 #include <string.h>
 #include <poll.h>
@@ -512,19 +513,6 @@ do_getpid(lua_State *lua) {
        return 1;
 }
 
-static int
-do_setpgid(lua_State *L)
-{
-       int rc, pid, pgid;
-
-       pid = luaL_checkint(L, 1);
-       pgid = luaL_checkint(L, 2);
-       if (setpgid(pid, pgid) != 0)
-               return luaL_error(L, "setpgid: %s", strerror(errno));
-
-       return 0;
-}
-
 static int
 do_unlink(lua_State *lua)
 {
@@ -541,44 +529,6 @@ do_unlink(lua_State *lua)
        return 1;
 }
 
-/* Reset all (possible) signals back to their default settings */
-static int
-signal_reset(lua_State *L)
-{
-       int s;
-       struct sigaction act;
-
-       for (s = 1; s < NSIG; s++) {
-               if (sigaction(s, NULL, &act) < 0)
-                       break; /* end of signals */
-
-               switch (s) {
-               case SIGINT:
-                       /* used by e2factory */
-                       continue;
-               case SIGFPE:
-                       act.sa_handler = SIG_IGN;
-                       break;
-               case SIGKILL:
-               case SIGSTOP:
-               case SIGCONT:
-                       continue;
-               default:
-                       act.sa_handler = SIG_DFL;
-               }
-
-               if (sigaction(s, &act, NULL) < 0) {
-                       lua_pushboolean(L, 0);
-                       lua_pushstring(L, strerror(errno));
-                       return 2;
-               }
-       }
-
-       lua_pushboolean(L, 1);
-       return 1;
-}
-
-
 /* closes all file descriptors >= fd */
 static int
 closefrom(lua_State *L)
@@ -659,16 +609,6 @@ do_mkdir(lua_State *lua)
        return 1;
 }
 
-static int
-ignore_sigint(lua_State *L)
-{
-       if (signal(SIGINT, SIG_IGN) == SIG_ERR) {
-               return luaL_error(L, "signal: %s", strerror(errno));
-       }
-
-       return 0;
-}
-
 static int
 do_kill(lua_State *lua)
 {
@@ -779,12 +719,53 @@ do_mkstemp(lua_State *L)
        return 4;
 }
 
+/* Reset all (possible) signals to their default settings */
+static int
+signal_reset(lua_State *L)
+{
+       int s;
+       struct sigaction act;
+
+       if (prctl(PR_SET_PDEATHSIG, 0) < 0) {
+               lua_pushboolean(L, 0);
+               lua_pushstring(L, strerror(errno));
+       }
+
+       for (s = 1; s < NSIG; s++) {
+               if (sigaction(s, NULL, &act) < 0)
+                       break; /* end of signals */
+
+               switch (s) {
+               case SIGFPE:
+                       act.sa_handler = SIG_IGN;
+                       break;
+               case SIGKILL:
+               case SIGSTOP:
+               case SIGCONT:
+                       continue;
+               default:
+                       act.sa_handler = SIG_DFL;
+               }
+               /* No SA_RESTART */
+               act.sa_flags = 0;
+               if (sigaction(s, &act, NULL) < 0) {
+                       lua_pushboolean(L, 0);
+                       lua_pushstring(L, strerror(errno));
+                       return 2;
+               }
+       }
+
+       lua_pushboolean(L, 1);
+       return 1;
+}
+
 /*
  * Hook that gets called once an interrupt has been requested.
  * Calls e2lib.interrupt_hook() to deal with any cleanup that might be required.
  */
 static void
-lstop(lua_State *L, lua_Debug *ar) {
+lua_signal_handler(lua_State *L, lua_Debug *ar)
+{
        lua_sethook(L, NULL, 0, 0);
 
        /* require e2lib */
@@ -798,11 +779,16 @@ lstop(lua_State *L, lua_Debug *ar) {
        lua_call(L, 0, 0);
 
        /* not reached under normal circumstances */
-       fprintf(stderr, "e2: interrupt_hook failed, terminating\n");
+       fprintf(stderr, "e2: calling interrupt_hook failed, terminating\n");
        exit(1);
 }
 
-static lua_State *globalL;
+/* Lua context for signal handler */
+static lua_State *globalL = NULL;
+/* Are we in shutdown? */
+static volatile sig_atomic_t signal_shutdown = 0;
+/* First signal that triggered shutdown */
+static volatile sig_atomic_t signal_received_first = 0;
 
 
 /*
@@ -810,13 +796,89 @@ static lua_State *globalL;
  * continuing normal execution at the next possible spot.
  */
 static void
-laction(int i) {
-       /* Ignore further signals because lstop() should
-        * terminate the process in an orderly fashion */
-       signal(i, SIG_IGN);
-       lua_sethook(globalL, lstop, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1);
+signal_handler(int sig)
+{
+       /*
+        * It's normal for subsequent signals to occur (eg. SIGPIPE)
+        * Ignore signals after they occurred once
+        */
+       struct sigaction sa;
+       sigaction(sig, NULL, &sa);
+       sa.sa_handler = SIG_IGN;
+
+       if (sigaction(sig, &sa, NULL) < 0)
+               fprintf(stderr, "e2: signal_handler: sigaction failed!\n");
+
+       /* Make sure we don't install lua_signal_handler more than once */
+       if (signal_shutdown)
+               return;
+
+       signal_shutdown = 1;
+       signal_received_first = sig;
+       if (globalL) {
+               lua_sethook(globalL, lua_signal_handler,
+                   LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1);
+       } else {
+               fprintf(stderr, "e2: signal_handler: missing lua context\n");
+               exit(1);
+       }
+}
+
+/* Install signal handler for all signals of concern */
+static int
+signal_install(lua_State *L)
+{
+       int i;
+       struct sigaction sa;
+       int signals[] = {
+               SIGINT,
+               SIGTERM,
+               SIGPIPE,
+               SIGHUP,
+               0
+       };
+
+       /* Lua context for use in signal handler */
+       globalL = L;
+
+       sa.sa_handler = signal_handler;
+       sa.sa_flags = 0;
+       sigemptyset(&sa.sa_mask);
+
+       for (i = 0; signals[i] != 0; i++) {
+               if (sigaction(signals[i], &sa, NULL) < 0) {
+                       lua_pushboolean(L, 0);
+                       lua_pushstring(L, strerror(errno));
+                       return 2;
+               }
+       }
+
+       /* Notify us if the parent dies for whatever reason */
+       if (prctl(PR_SET_PDEATHSIG, SIGINT) < 0) {
+               lua_pushboolean(L, 0);
+               lua_pushstring(L, strerror(errno));
+       }
+
+       lua_pushboolean(L, 1);
+       return 1;
 }
 
+/* Return the first received signal  triggering shutdown */
+static int
+signal_received(lua_State *L)
+{
+       char *s = NULL;
+
+       if (signal_received_first) {
+               s = strsignal(signal_received_first);
+       } else {
+               s = "";
+       }
+
+       lua_pushstring(L, s);
+       lua_pushinteger(L, signal_received_first);
+       return 2;
+}
 
 static luaL_Reg lib[] = {
        { "chdir", change_directory },
@@ -829,7 +891,6 @@ static luaL_Reg lib[] = {
        { "fork", lua_fork },
        { "getpid", do_getpid },
        { "hardlink", do_hardlink },
-       { "ignore_sigint", ignore_sigint },
        { "kill", do_kill },
        { "mkdir", do_mkdir },
        { "mkdtemp", do_mkdtemp },
@@ -838,8 +899,9 @@ static luaL_Reg lib[] = {
        { "rename", do_rename },
        { "rmdir", do_rmdir },
        { "setenv", do_setenv },
-       { "setpgid", do_setpgid },
        { "signal_reset", signal_reset },
+       { "signal_install", signal_install },
+       { "signal_received", signal_received },
        { "stat", get_file_statistics },
        { "symlink", create_symlink },
        { "umask", set_umask },
@@ -851,7 +913,8 @@ static luaL_Reg lib[] = {
 };
 
 
-int luaopen_le2lib(lua_State *lua)
+int
+luaopen_le2lib(lua_State *lua)
 {
        luaL_Reg *next;
 
@@ -861,9 +924,5 @@ int luaopen_le2lib(lua_State *lua)
                lua_setfield(lua, -2, next->name);
        }
 
-       /* Establish signal handler catching SIGINT for orderly shutdown */
-       globalL = lua;
-       signal(SIGINT, laction);
-
        return 1;
 }
index b6c119df1147ecba2c5e8682a14f51da29e0df38..325205903cf1e35903e61b97158ba0531d80a0d2 100644 (file)
@@ -93,8 +93,7 @@ local function e2(arg)
         end
     end
 
-    e2lib.log(3, "calling " .. e2call.tool)
-    e2lib.ignore_sigint()
+    e2lib.logf(3, "calling %s", e2call.tool)
     rc, re = e2lib.callcmd(cmd, {}, nil, env, true)
     if not rc then
         error(re)
index 0c2da509bb911432c5475c668c27cf69c2a04a94..c669a67281741e4b299f9c2672f849e408645ffc 100644 (file)
@@ -758,6 +758,8 @@ function e2tool.e2project_class:init_project(tool)
     e2tool.root(rc)
     self:rootdir(rc)
 
+    e2lib.signal_install()
+
     -- load local plugins
     local ctx = {  -- plugin context
         info = info,