Add helpers to create child processes
authorBarret Rhoden <brho@cs.berkeley.edu>
Tue, 7 Jun 2016 20:12:00 +0000 (16:12 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Fri, 17 Jun 2016 16:17:53 +0000 (12:17 -0400)
Basically everyone who creates a process will want to try to lookup in
/bin and to be able to handle scripts.  Another common usage is to pass
the parent's FDs to the child.

strace now uses this helper, which means we can now strace shell
scripts.

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
tests/strace.c
user/parlib/include/parlib/parlib.h
user/parlib/parlib.c

index a76251c..1db45ef 100644 (file)
@@ -36,18 +36,11 @@ void main(int argc, char **argv, char **envp)
        static char p[2 * MAX_PATH_LEN];
        static char buf[16384];
        struct syscall sysc;
-       char *prog_name = argv[1];
-
 
        if (argc < 2)
                usage();
-       if ((*argv[1] != '/') && (*argv[1] != '.')) {
-               snprintf(p, sizeof(p), "/bin/%s", argv[1]);
-               prog_name = p;
-       }
 
-       pid = sys_proc_create(prog_name, strlen(prog_name), argv + 1, envp,
-                             PROC_DUP_FGRP);
+       pid = create_child_with_stdfds(argv[1], argc - 1, argv + 1, envp);
        if (pid < 0) {
                perror("proc_create");
                exit(-1);
index 891f683..93a5fb7 100644 (file)
@@ -28,7 +28,6 @@ enum {
 
 int         sys_null(void);
 size_t      sys_getpcoreid(void);
-/* Process Management */
 int         sys_getpid(void);
 int         sys_proc_destroy(int pid, int exitcode);
 void        sys_yield(bool being_nice);
@@ -62,6 +61,12 @@ extern bool parlib_wants_to_be_mcp;  /* instructs the 2LS to be an MCP */
 extern bool parlib_never_yield;                /* instructs the 2LS to not yield vcores */
 extern bool parlib_never_vc_request;/* 2LS: do not request vcores */
 
+/* Process Management */
+pid_t create_child(const char *exe, int argc, char *const argv[],
+                   char *const envp[]);
+pid_t create_child_with_stdfds(const char *exe, int argc, char *const argv[],
+                               char *const envp[]);
+
 __END_DECLS
 
 #endif // !ASSEMBLER
index a6caa00..6fcb027 100644 (file)
@@ -3,8 +3,88 @@
  * See LICENSE for details. */
 
 #include <parlib/parlib.h>
+#include <stdlib.h>
 
 /* Control variables */
 bool parlib_wants_to_be_mcp = TRUE;
 bool parlib_never_yield = FALSE;
 bool parlib_never_vc_request = FALSE;
+
+/* Creates a child process for program @exe, with args and envs.  Will attempt
+ * to look in /bin/ if the initial lookup fails, and will invoke sh to handle
+ * non-elfs.  Returns the child's PID on success, -1 o/w. */
+pid_t create_child(const char *exe, int argc, char *const argv[],
+                   char *const envp[])
+{
+       pid_t kid;
+       char *path_exe;
+       char **sh_argv;
+       const char *sh_path = "/bin/sh";
+
+       kid = sys_proc_create(exe, strlen(exe), argv, envp, 0);
+       if (kid > 0)
+               return kid;
+
+       /* Here's how we avoid infinite recursion.  We can only have ENOENT the
+        * first time through without bailing out, since all errno paths set exe to
+        * begin with '/'.  That includes calls from ENOEXEC, since sh_path begins
+        * with /.  To avoid repeated calls to ENOEXEC, we just look for sh_path as
+        * the exe, so if we have consecutive ENOEXECs, we'll bail out. */
+       switch (errno) {
+       case ENOENT:
+               if (exe[0] == '/')
+                       return -1;
+               path_exe = malloc(MAX_PATH_LEN);
+               if (!path_exe)
+                       return -1;
+               /* Our 'PATH' is only /bin. */
+               snprintf(path_exe, MAX_PATH_LEN, "/bin/%s", exe);
+               path_exe[MAX_PATH_LEN - 1] = 0;
+               kid = create_child(path_exe, argc, argv, envp);
+               free(path_exe);
+               break;
+       case ENOEXEC:
+               /* In case someone replaces /bin/sh with a non-elf. */
+               if (!strcmp(sh_path, exe))
+                       return -1;
+               /* We want enough space for the original argv, plus one entry at the
+                * front for sh_path.  When we grab the original argv, we also need the
+                * trailing NULL, which is at argv[argc].  That means we really want
+                * argc + 1 entries from argv. */
+               sh_argv = malloc(sizeof(char *) * (argc + 2));
+               if (!sh_argv)
+                       return -1;
+               memcpy(&sh_argv[1], argv, sizeof(char *) * (argc + 1));
+               sh_argv[0] = (char*)sh_path;
+               /* Replace the original argv[0] with the path to exe, which might have
+                * been edited to include /bin/ */
+               sh_argv[1] = (char*)exe;
+               kid = create_child(sh_path, argc + 1, sh_argv, envp);
+               free(sh_argv);
+               break;
+       default:
+               return -1;
+       }
+       return kid;
+}
+
+/* Creates a child process for exe, and shares the parent's standard FDs (stdin,
+ * stdout, stderr) with the child.  Returns the child's PID on success, -1 o/w.
+ */
+pid_t create_child_with_stdfds(const char *exe, int argc, char *const argv[],
+                               char *const envp[])
+{
+       struct childfdmap fd_dups[3] = { {0, 0}, {1, 1}, {2, 2} };
+       pid_t kid;
+       int ret;
+
+       kid = create_child(exe, argc, argv, envp);
+       if (kid < 0)
+               return -1;
+       ret = syscall(SYS_dup_fds_to, kid, fd_dups, COUNT_OF(fd_dups));
+       if (ret != COUNT_OF(fd_dups)) {
+               sys_proc_destroy(kid, -1);
+               return -1;
+       }
+       return kid;
+}