mirror of
https://gitlab.com/sortix/sortix.git
synced 2023-02-13 20:55:38 -05:00
176 lines
4.8 KiB
C++
176 lines
4.8 KiB
C++
/*******************************************************************************
|
|
|
|
Copyright(C) Jonas 'Sortie' Termansen 2011, 2012, 2013, 2014.
|
|
|
|
This file is part of the Sortix C Library.
|
|
|
|
The Sortix C Library is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU Lesser General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or (at your
|
|
option) any later version.
|
|
|
|
The Sortix C Library is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
|
License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with the Sortix C Library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
unistd/execvpe.cpp
|
|
Loads a program image.
|
|
|
|
*******************************************************************************/
|
|
|
|
#include <errno.h>
|
|
#include <limits.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.cpp
|
|
// * utils/which.cpp
|
|
|
|
extern "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;
|
|
}
|