Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
335 changes: 181 additions & 154 deletions native/jni/native-lib/cpproc.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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);
}
Loading