sortix-mirror/libc/unistd/execvpe.c

172 lines
4.6 KiB
C

/*
* Copyright (c) 2011, 2012, 2013, 2014 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* unistd/execvpe.c
* Loads a program image.
*/
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
// TODO: Move this to some generic environment interface.
static const char* LookupEnvironment(const char* name, char* const* envp)
{
size_t equalpos = strcspn(name, "=");
if ( name[equalpos] == '=' )
return NULL;
size_t namelen = equalpos;
for ( size_t i = 0; envp[i]; i++ )
{
if ( strncmp(name, envp[i], namelen) )
continue;
if ( envp[i][namelen] != '=' )
continue;
return envp[i] + namelen + 1;
}
return NULL;
}
static
int execvpe_attempt(const char* filename,
const char* original,
char* const* argv,
char* const* envp)
{
execve(filename, argv, envp);
if ( errno != ENOEXEC )
return -1;
// Prevent attempting to run the shell using itself in an endless loop if it
// happens to be an unknown format or a shell script itself.
if ( !strcmp(original, "sh") )
return errno = ENOEXEC, -1;
int argc = 0;
while ( argv[argc] )
argc++;
if ( INT_MAX - argc < 1 + 1 )
return errno = EOVERFLOW, -1;
int new_argc = 1 + argc;
char** new_argv = (char**) malloc(sizeof(char*) * (new_argc + 1));
if ( !new_argv )
return -1;
new_argv[0] = (char*) "sh";
new_argv[1] = (char*) filename;
for ( int i = 1; i < argc; i++ )
new_argv[1 + i] = argv[i];
new_argv[new_argc] = (char*) NULL;
execvpe(new_argv[0], new_argv, envp);
free(new_argv);
return errno = ENOEXEC, -1;
}
// NOTE: The PATH-searching logic is repeated multiple places. Until this logic
// can be shared somehow, you need to keep this comment in sync as well
// as the logic in these files:
// * kernel/process.cpp
// * libc/unistd/execvpe.c
// * utils/which.c
int execvpe(const char* filename, char* const* argv, char* const* envp)
{
if ( !filename || !filename[0] )
return errno = ENOENT;
const char* path = LookupEnvironment("PATH", envp);
bool search_path = !strchr(filename, '/') && path;
bool any_tries = false;
bool any_eacces = false;
// Search each directory in the PATH variable for a suitable file.
while ( search_path && *path )
{
size_t len = strcspn(path, ":");
if ( !len )
{
// Sortix doesn't support that the empty string means current
// directory. While it does inductively make sense, the common
// kernel interfaces such as openat doesn't accept it and software
// often just prefix their directories and a colon to PATH without
// regard to whether it's already empty.
path++;
continue;
}
any_tries = true;
// Determine the directory prefix.
char* dirpath = strndup(path, len);
if ( !dirpath )
return -1;
if ( (path += len)[0] == ':' )
path++;
while ( len && dirpath[len - 1] == '/' )
dirpath[--len] = '\0';
// Determine the full path to the file inside the directory.
char* fullpath;
if ( asprintf(&fullpath, "%s/%s", dirpath, filename) < 0 )
return free(dirpath), -1;
execvpe_attempt(fullpath, filename, argv, envp);
free(fullpath);
free(dirpath);
// Proceed to the next PATH entry if the file didn't exist.
if ( errno == ENOENT )
continue;
// Ignore errors related to path resolution where the cause is a bad
// entry in the PATH as opposed to security issues.
if ( errno == ELOOP ||
errno == EISDIR ||
errno == ENAMETOOLONG ||
errno == ENOTDIR )
continue;
// Remember permission denied errors and report that errno value if the
// entire PATH search fails rather than the error of the last attempt.
if ( errno == EACCES )
{
any_eacces = true;
continue;
}
// Any other errors are treated as fatal and we stop the search.
break;
}
if ( !any_tries )
execvpe_attempt(filename, filename, argv, envp);
if ( any_eacces )
errno = EACCES;
return -1;
}