toaruos/kernel/binfmt.c

150 lines
4.8 KiB
C

/**
* @file kernel/binfmt.c
* @brief Top-level executable parsing.
*/
#include <errno.h>
#include <kernel/vfs.h>
#include <kernel/printf.h>
#include <kernel/process.h>
#include <kernel/string.h>
#include <kernel/mmu.h>
#include <kernel/elf.h>
#include <sys/time.h>
extern int elf_exec(const char * path, fs_node_t * file, int argc, char *const argv[], char *const env[], int interp);
int exec(const char * path, int argc, char *const argv[], char *const env[], int interp_depth);
/**
* @brief hash-exclamation parser
*
* Tries to safely read the first line of a script file to find an appropriate loader.
*/
int exec_shebang(const char * path, fs_node_t * file, int argc, char *const argv[], char *const env[], int interp) {
if (interp > 4) {
/* If an interpreter calls an interpreter too many times, bail. */
return -ELOOP;
}
/* Read MAX_LINE... */
char tmp[100];
read_fs(file, 0, 100, (unsigned char *)tmp); close_fs(file);
char * cmd = (char *)&tmp[2];
if (*cmd == ' ') cmd++; /* Handle a leading space */
char * space_or_linefeed = strpbrk(cmd, " \n");
char * arg = NULL;
/* We read too much stuff before finding EOL or another signal
* that the interpreter was found, so bail. */
if (!space_or_linefeed) {
return -ENOEXEC;
}
/* If we found a space, accept one argument before the path... */
if (*space_or_linefeed == ' ') {
*space_or_linefeed = '\0';
space_or_linefeed++;
arg = space_or_linefeed;
/* ... and look for another EOL. */
space_or_linefeed = strpbrk(space_or_linefeed, "\n");
if (!space_or_linefeed) {
/* If we didn't find one, bail. */
return -ENOEXEC;
}
}
/* Make sure interpreter or argument is nil-terminated */
*space_or_linefeed = '\0';
char script[strlen(path)+1];
memcpy(script, path, strlen(path)+1);
unsigned int nargc = argc + (arg ? 2 : 1);
char * args[nargc + 2];
args[0] = cmd;
args[1] = arg ? arg : script;
args[2] = arg ? script : NULL;
args[3] = NULL;
int j = arg ? 3 : 2;
for (int i = 1; i < argc; ++i, ++j) {
args[j] = argv[i];
}
args[j] = NULL;
/* Try to execut the interpreter with the new arguments */
return exec(cmd, nargc, args, env, interp+1);
}
/* Consider exposing this and making it a list so it can be extended ... */
typedef int (*exec_func)(const char * path, fs_node_t * file, int argc, char *const argv[], char *const env[], int interp);
typedef struct {
exec_func func;
unsigned char bytes[4];
unsigned int match;
const char * name;
} exec_def_t;
exec_def_t fmts[] = {
{elf_exec, {ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3}, 4, "ELF"},
{exec_shebang, {'#', '!', 0, 0}, 2, "#!"},
};
static int matches(unsigned char * a, unsigned char * b, unsigned int len) {
for (unsigned int i = 0; i < len; ++i) {
if (a[i] != b[i]) return 0;
}
return 1;
}
/**
* @brief Replace the current process with a new one.
*
* @param path Filename of the new executable.
* @param argc Number of arguments passed in @p argv
* @param argv Arguments to supply to the new executable's entry point.
* @param env Environment strings to pass to the new executable.
* @param interp_depth Should be 0 for all external callers.
* @returns Either never or -ENOEXEC on failure.
*/
int exec(const char * path, int argc, char *const argv[], char *const env[], int interp_depth) {
fs_node_t * file = kopen(path, 0);
if (!file) return -ENOENT;
if (!has_permission(file, 01)) return -EACCES;
unsigned char head[4];
read_fs(file, 0, 4, head);
this_core->current_process->name = strdup(path);
gettimeofday((struct timeval*)&this_core->current_process->start, NULL);
for (unsigned int i = 0; i < sizeof(fmts) / sizeof(exec_def_t); ++i) {
if (matches(fmts[i].bytes, head, fmts[i].match)) {
return fmts[i].func(path, file, argc, argv, env, interp_depth);
}
}
return -ENOEXEC;
}
/**
* This is generally only called by system startup code to launch /bin/init.
* Copies arguments from kernel constants into the heap, sets up a new MMU context
* from the kernel boot context, and then calls @ref exec.
*/
int system(const char * path, int argc, char *const argv[], char *const envin[]) {
char ** argv_ = malloc(sizeof(char*) * (argc + 1));
for (int j = 0; j < argc; ++j) {
argv_[j] = malloc((strlen(argv[j]) + 1));
memcpy((void*)argv_[j], argv[j], strlen(argv[j]) + 1);
}
argv_[argc] = NULL;
char * env[] = {NULL};
this_core->current_process->thread.page_directory = malloc(sizeof(page_directory_t));
this_core->current_process->thread.page_directory->directory = mmu_clone(NULL); /* base PML? for exec? */
this_core->current_process->thread.page_directory->refcount = 1;
spin_init(this_core->current_process->thread.page_directory->lock);
mmu_set_directory(this_core->current_process->thread.page_directory->directory);
this_core->current_process->cmdline = (char**)argv_;
exec(path,argc,argv_,envin ? envin : env,0);
return -EINVAL;
}