first pass at `trap` support in hush
authorMike Frysinger <vapier@gentoo.org>
Sun, 29 Mar 2009 23:49:37 +0000 (23:49 -0000)
committerMike Frysinger <vapier@gentoo.org>
Sun, 29 Mar 2009 23:49:37 +0000 (23:49 -0000)
shell/hush.c

index 5203f2e..17673c0 100644 (file)
@@ -489,6 +489,10 @@ struct globals {
 #endif
        unsigned char charmap[256];
        char user_input_buf[ENABLE_FEATURE_EDITING ? BUFSIZ : 2];
+       struct {
+               char *cmd;
+               struct sigaction oact;
+       } *traps;
 };
 
 #define G (*ptr_to_globals)
@@ -517,6 +521,8 @@ static int builtin_help(char **argv);
 static int builtin_pwd(char **argv);
 static int builtin_read(char **argv);
 static int builtin_test(char **argv);
+static void handle_trap(int sig);
+static int builtin_trap(char **argv);
 static int builtin_true(char **argv);
 static int builtin_set(char **argv);
 static int builtin_set_mode(const char, const char);
@@ -578,8 +584,8 @@ static const struct built_in_command bltins[] = {
 //     BLTIN("return", builtin_not_written, "Return from a function"),
        BLTIN("set"   , builtin_set, "Set/unset shell local variables"),
        BLTIN("shift" , builtin_shift, "Shift positional parameters"),
-//     BLTIN("trap"  , builtin_not_written, "Trap signals"),
        BLTIN("test"  , builtin_test, "Test condition"),
+       BLTIN("trap"  , builtin_trap, "Trap signals"),
 //     BLTIN("ulimit", builtin_not_written, "Control resource limits"),
        BLTIN("umask" , builtin_umask, "Set file creation mask"),
        BLTIN("unset" , builtin_unset, "Unset environment variable"),
@@ -857,22 +863,27 @@ static void sigexit(int sig)
        kill_myself_with_sig(sig); /* does not return */
 }
 
-/* Restores tty foreground process group, and exits. */
-static void hush_exit(int exitcode) NORETURN;
-static void hush_exit(int exitcode)
-{
-       fflush(NULL); /* flush all streams */
-       sigexit(- (exitcode & 0xff));
-}
-
 #else /* !JOB */
 
 #define set_fatal_sighandler(handler)   ((void)0)
 #define set_jobctrl_sighandler(handler) ((void)0)
-#define hush_exit(e)                    exit(e)
 
 #endif /* JOB */
 
+/* Restores tty foreground process group, and exits. */
+static void hush_exit(int exitcode) NORETURN;
+static void hush_exit(int exitcode)
+{
+       if (G.traps && G.traps[0].cmd)
+               handle_trap(0);
+
+       if (ENABLE_HUSH_JOB) {
+               fflush(NULL); /* flush all streams */
+               sigexit(- (exitcode & 0xff));
+       } else
+               exit(exitcode);
+}
+
 
 static const char *set_cwd(void)
 {
@@ -4516,6 +4527,114 @@ int lash_main(int argc, char **argv)
 /*
  * Built-ins
  */
+/* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_28
+ *
+ * Traps are also evaluated immediately instead of being delayed properly:
+ * http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_11
+ * Example: hush -c 'trap "echo hi" 31; sleep 10; echo moo' & sleep 1; kill -31 $!
+ *          "hi" should not be displayed until the sleep finishes
+ * This will have to get fixed ...
+ */
+static void handle_trap(int sig)
+{
+       int save_errno, save_rcode;
+       char *argv[] = { NULL, G.traps[sig].cmd, NULL };
+       /* Race!  We transitioned from handled to ignore/default, but
+        * the signal came in after updating .cmd but before we could
+        * register the new signal handler.
+        */
+       if (!argv[1] || argv[1][0] == '\0')
+               return;
+       /* need to save/restore errno/$? across traps */
+       save_errno = errno;
+       save_rcode = G.last_return_code;
+       builtin_eval(argv);
+       errno = save_errno;
+       G.last_return_code = save_rcode;
+}
+static int builtin_trap(char **argv)
+{
+       size_t i;
+       int sig;
+       bool ign = false;
+       char *new_cmd = NULL;
+
+       if (!G.traps)
+               G.traps = xzalloc(sizeof(*G.traps) * NSIG);
+
+       if (!argv[1]) {
+               /* No args: print all trapped.  This isn't 100% correct as we should
+                * be escaping the cmd so that it can be pasted back in ...
+                */
+               for (i = 0; i < NSIG; ++i)
+                       if (G.traps[i].cmd)
+                               printf("trap -- '%s' %s\n", G.traps[i].cmd, get_signame(i));
+               return EXIT_SUCCESS;
+       }
+
+       /* first arg is decimal: reset all specified */
+       sig = bb_strtou(argv[1], NULL, 10);
+       if (errno == 0) {
+               int ret;
+               i = 0;
+ set_all:
+               ret = EXIT_SUCCESS;
+               while (argv[++i]) {
+                       char *old_cmd;
+
+                       sig = get_signum(argv[i]);
+                       if (sig < 0 || sig >= NSIG) {
+                               ret = EXIT_FAILURE;
+                               bb_perror_msg("trap: %s: invalid signal specification", argv[i]);
+                               continue;
+                       }
+
+                       /* Make sure .cmd is always a valid command list since
+                        * signals can occur at any time ...
+                        */
+                       old_cmd = G.traps[sig].cmd;
+                       G.traps[sig].cmd = xstrdup(new_cmd);
+                       free(old_cmd);
+
+                       debug_printf("trap: setting SIG%s (%i) to: %s",
+                               get_signame(sig), sig, G.traps[sig].cmd);
+
+                       /* There is no signal for 0 (EXIT) */
+                       if (sig == 0)
+                               continue;
+
+                       if (new_cmd) {
+                               /* add/update a handler */
+                               struct sigaction act = {
+                                       .sa_handler = ign ? SIG_IGN : handle_trap,
+                                       .sa_flags = SA_RESTART,
+                               };
+                               sigemptyset(&act.sa_mask);
+                               sigaction(sig, &act, old_cmd ? NULL : &G.traps[sig].oact);
+                       } else if (old_cmd && !new_cmd)
+                               /* there was a handler, and we are removing it */
+                               sigaction_set(sig, &G.traps[sig].oact);
+               }
+               return ret;
+       }
+
+       /* first arg is "-": reset all specified to default */
+       /* first arg is "": ignore all specified */
+       /* everything else: execute first arg upon signal */
+       if (!argv[2]) {
+               bb_error_msg("trap: invalid arguments");
+               return EXIT_FAILURE;
+       }
+       if (LONE_DASH(argv[1]))
+               /* nothing! */;
+       else
+               new_cmd = argv[1];
+       if (argv[1][0] == '\0')
+               ign = true;
+       i = 1;
+       goto set_all;
+}
+
 static int builtin_true(char **argv UNUSED_PARAM)
 {
        return 0;