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;