diff --git a/native/jni/native-lib/cpproc.c b/native/jni/native-lib/cpproc.c index 6c114ee64..3bf69963e 100644 --- a/native/jni/native-lib/cpproc.c +++ b/native/jni/native-lib/cpproc.c @@ -53,138 +53,11 @@ exception statement from your version. */ #define PATH_MAX 4096 #endif -static void close_all_fds(int *fds, int numFds) -{ - int i; - - for (i = 0; i < numFds; i++) - close(fds[i]); -} - -/* Like execve, but also implementing execvp's "shell fallback" - behaviour: if execve fails with ENOEXEC, try to execute as a - script via /bin/sh. The shell receives the script path (file) - followed by the original arguments minus argv[0], which is - dropped. If envp is NULL the environment is inherited (execv is - used instead of execve). */ -static void cp_execve_sh(const char *file, char * const *argv, - char * const *envp, char **sh_argv) -{ - if (envp != NULL) - execve(file, argv, envp); - else - execv(file, argv); - - if (errno == ENOEXEC) - { - int i; - - sh_argv[0] = (char *) "/bin/sh"; - sh_argv[1] = (char *) file; - for (i = 1; argv[i] != NULL; i++) - sh_argv[i + 1] = argv[i]; - sh_argv[i + 1] = NULL; - - if (envp != NULL) - execve("/bin/sh", sh_argv, envp); - else - execv("/bin/sh", sh_argv); - } -} - -/* Replacement for execvpe, which is a GNU extension and not available - everywhere. If envp is NULL the environment is inherited. The - supplied preallocated sh_argv array must have room for one entry - more than argv, including its terminating NULL. */ -static void cp_execvpe(const char *file, char * const *argv, - char * const *envp, const char *path, - char **sh_argv) -{ - /* - This runs in the child of a fork of a multi-threaded process, - so it may only execute async-signal-safe operations. - - If execve fails with ENOEXEC, we assume it is a script with +x - permission (otherwise we would have seen EACCES) but without a - shebang line, and execute it via /bin/sh, as execvp would do. - The fallback is implemented explicitly because execve does not - provide it, and execvp (which does) is not async-signal-safe. - - OpenJDK implements a similar execvpe replacement, except that - they do use execvp in fork mode (see childproc.c). */ - char buffer[PATH_MAX]; - const char *p, *next; - size_t filelen = strlen(file); - int got_eacces = 0; - - /* An empty command name fails with ENOENT */ - if (*file == '\0') - { - errno = ENOENT; - return; - } - - /* Command names containing a slash are not looked up in the PATH */ - if (strchr(file, '/') != NULL) - { - cp_execve_sh(file, argv, envp, sh_argv); - return; - } - - for (p = path; p != NULL; p = next) - { - const char *candidate; - const char *sep; - size_t len; - - sep = strchr(p, ':'); - next = (sep != NULL) ? sep + 1 : NULL; - len = (sep != NULL) ? (size_t) (sep - p) : strlen(p); - if (len == 0) - { - /* An empty PATH element means the current directory */ - candidate = file; - } - else if (len + filelen + 2 <= sizeof(buffer)) - { - memcpy(buffer, p, len); - buffer[len] = '/'; - strcpy(buffer + len + 1, file); - candidate = buffer; - } - else - { - errno = ENAMETOOLONG; - continue; - } - - cp_execve_sh(candidate, argv, envp, sh_argv); - switch (errno) - { - case EACCES: - /* Keep searching, but report EACCES if nothing is found */ - got_eacces = 1; - break; - case ENOENT: - case ENOTDIR: -#ifdef ELOOP - case ELOOP: -#endif -#ifdef ESTALE - case ESTALE: -#endif -#ifdef ENODEV - case ENODEV: -#endif -#ifdef ETIMEDOUT - case ETIMEDOUT: -#endif - break; - default: - return; - } - } - - if (got_eacces) - errno = EACCES; -} +static void close_all_fds(int *fds, int numFds); +static void child_process(char * const *commandLine, + char * const *newEnviron, + int *local_fds, int pipe_count, int *fail_fds, + const char *path, char **sh_argv, const char *wd); int cpproc_forkAndExec (char * const *commandLine, char * const * newEnviron, int *fds, int pipe_count, pid_t *out_pid, const char *wd) @@ -248,28 +121,9 @@ int cpproc_forkAndExec (char * const *commandLine, char * const * newEnviron, switch (pid) { case 0: - close(fail_fds[0]); - if (fcntl(fail_fds[1], F_SETFD, FD_CLOEXEC) < 0) - goto child_error; - - dup2(local_fds[0], 0); - dup2(local_fds[3], 1); - if (pipe_count == 3) - dup2(local_fds[5], 2); - else - dup2(1, 2); - - close_all_fds(local_fds, pipe_count * 2); - - if (wd == NULL || chdir(wd) == 0) - cp_execvpe(commandLine[0], commandLine, newEnviron, path, sh_argv); - - child_error: - /* The fcntl, chdir or exec failed; send our errno to the parent */ - errnum = errno; - while (write(fail_fds[1], &errnum, sizeof(errnum)) < 0 - && errno == EINTR) - ; + child_process(commandLine, newEnviron, local_fds, pipe_count, + fail_fds, path, sh_argv, wd); + /* child_process() does not return. */ _exit(127); case -1: @@ -346,3 +200,176 @@ int cpproc_kill (pid_t pid, int signal) return 0; } + + +/* Child-side implementation. + + Everything below this point may run in the child of a fork() of a + multi-threaded process, so it should only use async-signal-safe + operations. Avoid malloc and functions that may take locks. Any + buffers that require allocation must be preallocated by the parent + and passed in. */ + +/* Also used by the parent. */ +static void close_all_fds(int *fds, int numFds) +{ + int i; + + for (i = 0; i < numFds; i++) + close(fds[i]); +} + +/* Like execve, but also implementing execvp's "shell fallback" + behaviour: if execve fails with ENOEXEC, try to execute as a + script via /bin/sh. The shell receives the script path (file) + followed by the original arguments minus argv[0], which is + dropped. If envp is NULL the environment is inherited (execv is + used instead of execve). */ +static void cp_execve_sh(const char *file, char * const *argv, + char * const *envp, char **sh_argv) +{ + if (envp != NULL) + execve(file, argv, envp); + else + execv(file, argv); + + if (errno == ENOEXEC) + { + int i; + + sh_argv[0] = (char *) "/bin/sh"; + sh_argv[1] = (char *) file; + for (i = 1; argv[i] != NULL; i++) + sh_argv[i + 1] = argv[i]; + sh_argv[i + 1] = NULL; + + if (envp != NULL) + execve("/bin/sh", sh_argv, envp); + else + execv("/bin/sh", sh_argv); + } +} + +/* Replacement for execvpe, which is a GNU extension and not available + everywhere. If envp is NULL the environment is inherited. The + supplied preallocated sh_argv array must have room for one entry + more than argv, including its terminating NULL. */ +static void cp_execvpe(const char *file, char * const *argv, + char * const *envp, const char *path, + char **sh_argv) +{ + /* - If execve fails with ENOEXEC, we assume it is a script with +x + permission (otherwise we would have seen EACCES) but without a + shebang line, and execute it via /bin/sh, as execvp would do. + The fallback is implemented explicitly because execve does not + provide it, and execvp (which does) is not async-signal-safe. + - OpenJDK implements a similar execvpe replacement, except that + they do use execvp in fork mode (see childproc.c). */ + char buffer[PATH_MAX]; + const char *p, *next; + size_t filelen = strlen(file); + int got_eacces = 0; + + /* An empty command name fails with ENOENT */ + if (*file == '\0') + { + errno = ENOENT; + return; + } + + /* Command names containing a slash are not looked up in the PATH */ + if (strchr(file, '/') != NULL) + { + cp_execve_sh(file, argv, envp, sh_argv); + return; + } + + for (p = path; p != NULL; p = next) + { + const char *candidate; + const char *sep; + size_t len; + + sep = strchr(p, ':'); + next = (sep != NULL) ? sep + 1 : NULL; + len = (sep != NULL) ? (size_t) (sep - p) : strlen(p); + if (len == 0) + { + /* An empty PATH element means the current directory */ + candidate = file; + } + else if (len + filelen + 2 <= sizeof(buffer)) + { + memcpy(buffer, p, len); + buffer[len] = '/'; + strcpy(buffer + len + 1, file); + candidate = buffer; + } + else + { + errno = ENAMETOOLONG; + continue; + } + + cp_execve_sh(candidate, argv, envp, sh_argv); + switch (errno) + { + case EACCES: + /* Keep searching, but report EACCES if nothing is found */ + got_eacces = 1; + break; + case ENOENT: + case ENOTDIR: +#ifdef ELOOP + case ELOOP: +#endif +#ifdef ESTALE + case ESTALE: +#endif +#ifdef ENODEV + case ENODEV: +#endif +#ifdef ETIMEDOUT + case ETIMEDOUT: +#endif + break; + default: + return; + } + } + + if (got_eacces) + errno = EACCES; +} + +static void child_process(char * const *commandLine, + char * const *newEnviron, + int *local_fds, int pipe_count, int *fail_fds, + const char *path, char **sh_argv, const char *wd) +{ + int errnum; + + close(fail_fds[0]); + if (fcntl(fail_fds[1], F_SETFD, FD_CLOEXEC) < 0) + goto child_error; + + dup2(local_fds[0], 0); + dup2(local_fds[3], 1); + if (pipe_count == 3) + dup2(local_fds[5], 2); + else + dup2(1, 2); + + close_all_fds(local_fds, pipe_count * 2); + + if (wd == NULL || chdir(wd) == 0) + cp_execvpe(commandLine[0], commandLine, newEnviron, path, sh_argv); + + child_error: + /* The fcntl, chdir or exec failed; send our errno to the parent */ + errnum = errno; + while (write(fail_fds[1], &errnum, sizeof(errnum)) < 0 + && errno == EINTR) + ; + _exit(127); +}