summaryrefslogtreecommitdiff
path: root/libc/stdlib/system.c
diff options
context:
space:
mode:
Diffstat (limited to 'libc/stdlib/system.c')
-rw-r--r--libc/stdlib/system.c204
1 files changed, 204 insertions, 0 deletions
diff --git a/libc/stdlib/system.c b/libc/stdlib/system.c
index 99f7970c8..4f54d1df9 100644
--- a/libc/stdlib/system.c
+++ b/libc/stdlib/system.c
@@ -10,8 +10,18 @@
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
+#ifdef __UCLIBC_HAS_THREADS_NATIVE__
+#include <sched.h>
+#include <errno.h>
+#include <bits/libc-lock.h>
+#include <sysdep-cancel.h>
+#endif
+/* TODO: the cancellable version breaks on sparc currently,
+ * need to figure out why still
+ */
+#if !defined __UCLIBC_HAS_THREADS_NATIVE__ || defined __sparc__
/* uClinux-2.0 has vfork, but Linux 2.0 doesn't */
#include <sys/syscall.h>
#ifndef __NR_vfork
@@ -61,4 +71,198 @@ int __libc_system(const char *command)
signal(SIGCHLD, save_chld);
return wait_val;
}
+#else
+/* We have to and actually can handle cancelable system(). The big
+ problem: we have to kill the child process if necessary. To do
+ this a cleanup handler has to be registered and is has to be able
+ to find the PID of the child. The main problem is to reliable have
+ the PID when needed. It is not necessary for the parent thread to
+ return. It might still be in the kernel when the cancellation
+ request comes. Therefore we have to use the clone() calls ability
+ to have the kernel write the PID into the user-level variable. */
+
+libc_hidden_proto(sigaction)
+libc_hidden_proto(waitpid)
+
+#if defined __ia64__
+# define FORK() \
+ INLINE_SYSCALL (clone2, 6, CLONE_PARENT_SETTID | SIGCHLD, NULL, 0, \
+ &pid, NULL, NULL)
+#elif defined __sparc__
+# define FORK() \
+ INLINE_CLONE_SYSCALL (CLONE_PARENT_SETTID | SIGCHLD, 0, &pid, NULL, NULL)
+#elif defined __s390__
+# define FORK() \
+ INLINE_SYSCALL (clone, 3, 0, CLONE_PARENT_SETTID | SIGCHLD, &pid)
+#else
+# define FORK() \
+ INLINE_SYSCALL (clone, 3, CLONE_PARENT_SETTID | SIGCHLD, 0, &pid)
+#endif
+
+static void cancel_handler (void *arg);
+
+# define CLEANUP_HANDLER \
+ __libc_cleanup_region_start (1, cancel_handler, &pid)
+
+# define CLEANUP_RESET \
+ __libc_cleanup_region_end (0)
+
+static struct sigaction intr, quit;
+static int sa_refcntr;
+__libc_lock_define_initialized (static, lock);
+
+# define DO_LOCK() __libc_lock_lock (lock)
+# define DO_UNLOCK() __libc_lock_unlock (lock)
+# define INIT_LOCK() ({ __libc_lock_init (lock); sa_refcntr = 0; })
+# define ADD_REF() sa_refcntr++
+# define SUB_REF() --sa_refcntr
+
+/* Execute LINE as a shell command, returning its status. */
+static int
+do_system (const char *line)
+{
+ int status, save;
+ pid_t pid;
+ struct sigaction sa;
+ sigset_t omask;
+
+ sa.sa_handler = SIG_IGN;
+ sa.sa_flags = 0;
+ __sigemptyset (&sa.sa_mask);
+
+ DO_LOCK ();
+ if (ADD_REF () == 0)
+ {
+ if (sigaction (SIGINT, &sa, &intr) < 0)
+ {
+ SUB_REF ();
+ goto out;
+ }
+ if (sigaction (SIGQUIT, &sa, &quit) < 0)
+ {
+ save = errno;
+ SUB_REF ();
+ goto out_restore_sigint;
+ }
+ }
+ DO_UNLOCK ();
+
+ /* We reuse the bitmap in the 'sa' structure. */
+ __sigaddset (&sa.sa_mask, SIGCHLD);
+ save = errno;
+ if (sigprocmask (SIG_BLOCK, &sa.sa_mask, &omask) < 0)
+ {
+ {
+ DO_LOCK ();
+ if (SUB_REF () == 0)
+ {
+ save = errno;
+ (void) sigaction (SIGQUIT, &quit, (struct sigaction *) NULL);
+ out_restore_sigint:
+ (void) sigaction (SIGINT, &intr, (struct sigaction *) NULL);
+ __set_errno (save);
+ }
+ out:
+ DO_UNLOCK ();
+ return -1;
+ }
+ }
+
+ CLEANUP_HANDLER;
+
+ pid = FORK ();
+ if (pid == (pid_t) 0)
+ {
+ /* Child side. */
+ const char *new_argv[4];
+ new_argv[0] = "/bin/sh";
+ new_argv[1] = "-c";
+ new_argv[2] = line;
+ new_argv[3] = NULL;
+
+ /* Restore the signals. */
+ (void) sigaction (SIGINT, &intr, (struct sigaction *) NULL);
+ (void) sigaction (SIGQUIT, &quit, (struct sigaction *) NULL);
+ (void) sigprocmask (SIG_SETMASK, &omask, (sigset_t *) NULL);
+ INIT_LOCK ();
+
+ /* Exec the shell. */
+ (void) execve ("/bin/sh", (char *const *) new_argv, __environ);
+ _exit (127);
+ }
+ else if (pid < (pid_t) 0)
+ /* The fork failed. */
+ status = -1;
+ else
+ /* Parent side. */
+ {
+ /* Note the system() is a cancellation point. But since we call
+ waitpid() which itself is a cancellation point we do not
+ have to do anything here. */
+ if (TEMP_FAILURE_RETRY (waitpid (pid, &status, 0)) != pid)
+ status = -1;
+ }
+
+ CLEANUP_RESET;
+
+ save = errno;
+ DO_LOCK ();
+ if ((SUB_REF () == 0
+ && (sigaction (SIGINT, &intr, (struct sigaction *) NULL)
+ | sigaction (SIGQUIT, &quit, (struct sigaction *) NULL)) != 0)
+ || sigprocmask (SIG_SETMASK, &omask, (sigset_t *) NULL) != 0)
+ {
+ status = -1;
+ }
+ DO_UNLOCK ();
+
+ return status;
+}
+
+
+int
+__libc_system (const char *line)
+{
+ if (line == NULL)
+ /* Check that we have a command processor available. It might
+ not be available after a chroot(), for example. */
+ return do_system ("exit 0") == 0;
+
+ if (SINGLE_THREAD_P)
+ return do_system (line);
+
+ int oldtype = LIBC_CANCEL_ASYNC ();
+
+ int result = do_system (line);
+
+ LIBC_CANCEL_RESET (oldtype);
+
+ return result;
+}
+
+
+/* The cancellation handler. */
+static void
+cancel_handler (void *arg)
+{
+ pid_t child = *(pid_t *) arg;
+
+ INTERNAL_SYSCALL_DECL (err);
+ INTERNAL_SYSCALL (kill, err, 2, child, SIGKILL);
+
+ TEMP_FAILURE_RETRY (waitpid (child, NULL, 0));
+
+ DO_LOCK ();
+
+ if (SUB_REF () == 0)
+ {
+ (void) sigaction (SIGQUIT, &quit, (struct sigaction *) NULL);
+ (void) sigaction (SIGINT, &intr, (struct sigaction *) NULL);
+ }
+
+ DO_UNLOCK ();
+}
+#endif
+#ifdef IS_IN_libc
weak_alias(__libc_system,system)
+#endif