Improve make process management.

- Add support for waiting on a shell function
- Improve error messages
- Consider PATH environment variable when finding executables

Change-Id: Icabb69126a824a11ff95d6e2cded80f275e65240
diff --git a/function.c b/function.c
index ebbc88e..a5bc89a 100644
--- a/function.c
+++ b/function.c
@@ -1792,7 +1792,10 @@
 
   if (pid < 0)
     {
+/* For fuchsia, we produce an error in child_execute_job.  */
+#ifndef __Fuchsia__
       perror_with_name (error_prefix, "fork");
+#endif
       return o;
     }
 #endif
diff --git a/job.c b/job.c
index d9dec2c..9d8b4a7 100644
--- a/job.c
+++ b/job.c
@@ -555,6 +555,45 @@
 #endif
 }
 
+#ifdef __Fuchsia__
+/* This oxymoronic function checks if the specified handle has a terminated
+   signal to report to us. Returns a result indicating whether the process
+   has terminated.  */
+static bool
+fuchsia_nonblocking_wait (mx_handle_t handle, int *status)
+{
+  int mx_status = ERR_TIMED_OUT;
+  mx_signals_t pending_signals = 0;
+
+  mx_status = mx_handle_wait_one (handle, MX_TASK_TERMINATED, 0,
+                                  &pending_signals);
+
+  /* If we get any other status, something has gone wildly off the rails.
+     Unfortunately, the API for reap_children doesn't allow us to, for
+     example, report that the child handle is invalid, so it's best just
+     to drop everything.  */
+  assert (mx_status == NO_ERROR || mx_status == ERR_TIMED_OUT);
+
+  /* The status doesn't indicate decisively if a child has exited. For that,
+     we have to look at pending_signals.  */
+  if (pending_signals)
+    {
+      mx_info_process_t proc_info;
+
+      mx_status = mx_object_get_info (handle, MX_INFO_PROCESS, &proc_info,
+                                      sizeof (proc_info), NULL, NULL);
+      if (mx_status == NO_ERROR)
+        *status = (proc_info.return_code & 0xff) << 8;
+      else
+        *status = -1;
+
+      return true;
+    }
+
+  return false;
+}
+#endif
+
 extern pid_t shell_function_pid;
 
 /* Reap all dead children, storing the returned status and the new command
@@ -594,10 +633,6 @@
       pid_t pid;
       int exit_code, exit_sig, coredump;
       struct child *lastc, *c;
-#ifdef __Fuchsia__
-      int mx_status = ERR_TIMED_OUT;
-      mx_signals_t pending_signals = 0;
-#endif
       int child_failed;
       int any_remote, any_local;
       int dontcare;
@@ -685,40 +720,20 @@
               status = 0;
               pid = 0;
 
-              do
+              if (shell_function_pid != 0
+                  && fuchsia_nonblocking_wait (shell_function_pid, &status))
+                pid = shell_function_pid;
+              else
                 {
                   for (c = children; c != 0; c = c->next)
                     {
-                      mx_status = mx_handle_wait_one
-                                    (c->pid, MX_TASK_TERMINATED, 0,
-                                     &pending_signals);
-
-                      /* If we get any other status, something has gone
-                         wildly off the rails. Unfortunately, the API for
-                         this function doesn't allow us to, for example,
-                         report that the child handle is invalid.  */
-                      assert (mx_status == NO_ERROR
-                              || mx_status == ERR_TIMED_OUT);
-
-                      /* mx_status will always be ERR_TIMED_OUT when
-                         we do a non-blocking wait, so we have to look
-                         at pending_signals to discover an exited
-                         child.  */
-                      if (pending_signals)
+                      if (fuchsia_nonblocking_wait (c->pid, &status))
                         {
-                          mx_info_process_t proc_info;
-
-                          mx_status = mx_object_get_info
-                                        (c->pid, MX_INFO_PROCESS, &proc_info,
-                                         sizeof (proc_info), NULL, NULL);
-                          if (mx_status == NO_ERROR)
-                            status = proc_info.return_code;
-
                           pid = c->pid;
                           break;
                         }
                     }
-                } while (block && !pending_signals);
+                }
 #else
 #ifdef WAIT_NOHANG
               if (!block)
@@ -1444,9 +1459,10 @@
       if (child->pid < 0)
         {
           /* Fork failed!  */
-          /* Fuchsia prints an error on failure in child_execute_job.  */
-#ifndef __Fuchsia__
           unblock_sigs ();
+/* For fuchsia we print the error message in child_execute_job because it
+   allows us to be more specific about the error.  */
+#ifndef __Fuchsia__
           perror_with_name ("fork", "");
 #endif
           goto error;
@@ -2166,16 +2182,127 @@
 }
 
 #elif defined (__Fuchsia__)
-/* Fuchsia:
-   Create a child process executing the command in ARGV.
+/* Look through the environment variables to see if the value for 'varname'
+   is set.  If so, return a pointer to the value.  The value is not
+   reallocated - the return value points into the envp values.  If the
+   value is not found, NULL is returned.  */
+static char *
+find_env_value (const char *varname, char **envp)
+{
+  size_t varname_len = strlen (varname);
+  char **next_var = &envp[0];
+
+  while (*next_var)
+    {
+      if (! strncmp (*next_var, varname, varname_len)
+          && (*next_var)[varname_len] == '=')
+        {
+          return &(*next_var)[varname_len + 1];
+        }
+      next_var++;
+    }
+
+  return NULL;
+}
+
+#define PATH_DELIMITER ':'
+#define DIR_DELIMITER '/'
+
+/* Grab the first directory name from (*path_str_ptr) and return a pointer
+   to it.  Also advances (*path_str_ptr) to point to the next directory name
+   in the path.  Returns NULL if there are no more directories in the path
+   string.  */
+static char *
+get_next_path_location (char **path_str_ptr)
+{
+  char *path_str = *path_str_ptr;
+  char *delimiter_loc;
+  size_t elem_len;
+
+  /* No more directories left in the path.  */
+  if (! path_str)
+    return NULL;
+
+  delimiter_loc = strchr (path_str, PATH_DELIMITER);
+
+  if (! delimiter_loc)
+    {
+      /* At the last directory in the path.  */
+      *path_str_ptr = NULL;
+      return xstrdup (path_str);
+    }
+
+  /* Set (*path_str_ptr) to point immediately after the delimiter
+     at the end of the directory name.  */
+  elem_len = delimiter_loc - path_str;
+  *path_str_ptr = delimiter_loc + 1;
+  return xstrndup (path_str, elem_len);
+}
+
+/* Find an executable file, looking in directories in the user's PATH.
+   If a file is found, returns a path to that file in a newly-allocated
+   string (that the caller must free).  If not found, returns NULL.  */
+static char *
+find_exe_file_location (const char *exe_filename, char **envp)
+{
+  char *path_value;
+  char *next_location;
+
+  /* If the filename already has path information, pass it back unmodified.  */
+  if (strchr (exe_filename, DIR_DELIMITER))
+    return xstrdup (exe_filename);
+
+  path_value = find_env_value ("PATH", envp);
+  if (! path_value)
+    return NULL;
+
+  while (next_location = get_next_path_location (&path_value))
+    {
+      size_t path_len = strlen (next_location);
+      size_t exe_len = strlen (exe_filename);
+      char *full_filename;
+
+      if (path_len == 0)
+        {
+          full_filename = xstrdup (exe_filename);
+        }
+      else if (next_location[path_len - 1] == DIR_DELIMITER)
+        {
+          /* Path already has a trailing directory delimiter.  */
+          full_filename = xmalloc (path_len + exe_len + 1);
+          sprintf (full_filename, "%s%s", next_location, exe_filename);
+        }
+      else
+        {
+          /* Insert a directory delimiter.  */
+          full_filename = xmalloc (path_len + 1 + exe_len + 1);
+          sprintf (full_filename, "%s/%s", next_location, exe_filename);
+        }
+
+      free (next_location);
+
+      /* Look for an executable file in that location.  */
+      if (access (full_filename, X_OK) != -1)
+        return full_filename;
+
+      free (full_filename);
+    }
+
+  return NULL;
+}
+
+/* Create a child process executing the command in ARGV.
    ENVP is the environment of the new program.  Returns the PID or -1.  */
 int
 child_execute_job (struct output *out, int good_stdin, char **argv, char **envp)
 {
+  const int fdin = good_stdin ? FD_STDIN : get_bad_stdin ();
   launchpad_t *lp = NULL;
   mx_handle_t child_job = MX_HANDLE_INVALID;
   mx_handle_t this_job = launchpad_get_mxio_job ();
   mx_status_t status;
+  char *full_filename;
+  char *orig_exe_name = argv[0];
   int argc;
   mx_handle_t result = -1;
 
@@ -2183,11 +2310,23 @@
   if (this_job != MX_HANDLE_INVALID)
     mx_handle_duplicate (this_job, MX_RIGHT_SAME_RIGHTS, &child_job);
 
+  full_filename = find_exe_file_location (argv[0], envp);
+  if (! full_filename)
+    {
+      OS (error, NILF, _("%s: Command not found"), argv[0]);
+      return -1;
+    }
+
+  argv[0] = full_filename;
+
   status = launchpad_create (child_job, argv[0], &lp);
   if (status != NO_ERROR)
-    goto done_noalloc;
+    {
+      free (full_filename);
+      goto done_noalloc;
+    }
 
-  status = launchpad_elf_load (lp, launchpad_vmo_from_file (argv[0]));
+  status = launchpad_file_load (lp, launchpad_vmo_from_file (argv[0]));
   if (status != NO_ERROR)
     goto done;
 
@@ -2220,6 +2359,14 @@
     goto done;
 
   /* Copy descriptors, but don't make stdin available to the child.  */
+  if (fdin >= 0 && fdin != FD_STDIN)
+    status = launchpad_clone_fd (lp, fdin, FD_STDIN);
+  else
+    status = launchpad_clone_fd (lp, FD_STDIN, FD_STDIN);
+
+  if (status != NO_ERROR)
+    goto done;
+
   if (out && out->syncout)
     {
       if (out->out >= 0)
@@ -2249,10 +2396,14 @@
 
 done:
   launchpad_destroy (lp);
+  free (full_filename);
 
 done_noalloc:
+  argv[0] = orig_exe_name;
+
   if (status != NO_ERROR)
-    O (error, NILF, _("launchpad: failure to launch"));
+    OSN (error, NILF, _("launchpad: failure to launch command '%s' (%d)"),
+         argv[0], status);
 
   if (result > 0)
     return (int) result;