~emersion/mrsh

acb409b62bad53539470992d053775fed44124a0 — Drew DeVault 2 years ago b201f01
Add shell/entry.c
4 files changed, 153 insertions(+), 95 deletions(-)

A include/mrsh/entry.h
M main.c
M meson.build
A shell/entry.c
A include/mrsh/entry.h => include/mrsh/entry.h +35 -0
@@ 0,0 1,35 @@
#ifndef MRSH_ENTRY_H
#define MRSH_ENTRY_H

#include <mrsh/shell.h>
#include <stdbool.h>

/**
 * Expands $PS1 or returns the POSIX-specified default of "$" or "#". The caller
 * must free the return value.
 */
char *mrsh_get_ps1(struct mrsh_state *state, int next_history_id);

/**
 * Expands $PS2 or returns the POSIX-specified default of ">". The caller must
 * free the return value.
 */
char *mrsh_get_ps2(struct mrsh_state *state);

/**
 * Copies variables from the environment and sets up internal variables like
 * IFS, PPID, PWD, etc.
 */
bool mrsh_populate_env(struct mrsh_state *state);

/**
 * Sources /etc/profile and $HOME/.profile. Note that this behavior is not
 * specified by POSIX. It is recommended to call this file in login shells
 * (for which argv[0][0] == '-' by convention).
 */
void mrsh_source_profile(struct mrsh_state *state);

/** Sources $ENV. It is recommended to source this in interactive shells. */
void mrsh_source_env(struct mrsh_state *state);

#endif

M main.c => main.c +9 -95
@@ 5,82 5,20 @@
#include <mrsh/ast.h>
#include <mrsh/buffer.h>
#include <mrsh/builtin.h>
#include <mrsh/entry.h>
#include <mrsh/parser.h>
#include <mrsh/shell.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "builtin.h"
#include "frontend.h"

static char *expand_ps(struct mrsh_state *state, const char *ps1) {
	struct mrsh_parser *parser = mrsh_parser_with_data(ps1, strlen(ps1));
	struct mrsh_word *word = mrsh_parse_word(parser);
	mrsh_parser_destroy(parser);
	if (word == NULL) {
		return NULL;
	}

	mrsh_run_word(state, &word);

	return mrsh_word_str(word);
}

static char *get_ps1(struct mrsh_state *state) {
	const char *ps1 = mrsh_env_get(state, "PS1", NULL);
	if (ps1 != NULL) {
		// TODO: Replace ! with next history ID
		return expand_ps(state, ps1);
	}
	char *p = malloc(3);
	sprintf(p, "%s", getuid() ? "$ " : "# ");
	return p;
}

static char *get_ps2(struct mrsh_state *state) {
	const char *ps2 = mrsh_env_get(state, "PS2", NULL);
	if (ps2 != NULL) {
		return expand_ps(state, ps2);
	}
	return strdup("> ");
}

static void source_file(struct mrsh_state *state, char *path) {
	if (access(path, F_OK) == -1) {
		return;
	}
	char *env_argv[] = { ".", path };
	mrsh_run_builtin(state, sizeof(env_argv) / sizeof(env_argv[0]), env_argv);
}

static void source_profile(struct mrsh_state *state) {
	source_file(state, "/etc/profile");

	char path[PATH_MAX];
	int n = snprintf(path, sizeof(path), "%s/.profile", getenv("HOME"));
	if (n == sizeof(path)) {
		fprintf(stderr, "Warning: $HOME/.profile is longer than PATH_MAX\n");
		return;
	}
	source_file(state, path);
}

static void source_env(struct mrsh_state *state) {
	char *path = getenv("ENV");
	if (path == NULL) {
		return;
	}
	// TODO: parameter expansion
	source_file(state, path);
}

static const char *get_alias(const char *name, void *data) {
	struct mrsh_state *state = data;
	return mrsh_hashtable_get(&state->aliases, name);
}

extern char **environ;

int main(int argc, char *argv[]) {
	struct mrsh_state state = {0};
	mrsh_state_init(&state);


@@ 91,42 29,17 @@ int main(int argc, char *argv[]) {
		return EXIT_FAILURE;
	}

	for (size_t i = 0; environ[i] != NULL; ++i) {
		char *eql = strchr(environ[i], '=');
		size_t klen = eql - environ[i];
		char *key = strndup(environ[i], klen);
		char *val = &eql[1];
		mrsh_env_set(&state, key, val, MRSH_VAR_ATTRIB_EXPORT);
		free(key);
	}

	mrsh_env_set(&state, "IFS", " \t\n", MRSH_VAR_ATTRIB_NONE);

	pid_t ppid = getppid();
	char ppid_str[24];
	snprintf(ppid_str, sizeof(ppid_str), "%d", ppid);
	mrsh_env_set(&state, "PPID", ppid_str, MRSH_VAR_ATTRIB_NONE);

	// TODO check if path is well-formed, has . or .., and handle symbolic links
	const char *pwd = mrsh_env_get(&state, "PWD", NULL);
	if (pwd == NULL || strlen(pwd) >= PATH_MAX) {
		char cwd[PATH_MAX];
		if (getcwd(cwd, PATH_MAX) == NULL) {
			fprintf(stderr, "getcwd failed: %s\n", strerror(errno));
			return EXIT_FAILURE;
		}
		mrsh_env_set(&state, "PWD", cwd, MRSH_VAR_ATTRIB_EXPORT);
	if (!mrsh_populate_env(&state)) {
		return EXIT_FAILURE;
	}

	mrsh_env_set(&state, "OPTIND", "1", MRSH_VAR_ATTRIB_NONE);

	if (!(state.options & MRSH_OPT_NOEXEC)) {
		// If argv[0] begins with `-`, it's a login shell
		if (state.args->argv[0][0] == '-') {
			source_profile(&state);
			mrsh_source_profile(&state);
		}
		if (state.interactive) {
			source_env(&state);
			mrsh_source_env(&state);
		}
	}



@@ 162,9 75,10 @@ int main(int argc, char *argv[]) {
		if (state.interactive) {
			char *prompt;
			if (read_buffer.len > 0) {
				prompt = get_ps2(&state);
				prompt = mrsh_get_ps2(&state);
			} else {
				prompt = get_ps1(&state);
				// TODO: next_history_id
				prompt = mrsh_get_ps1(&state, 0);
			}
			char *line = NULL;
			size_t n = interactive_next(&state, &line, prompt);

M meson.build => meson.build +1 -0
@@ 91,6 91,7 @@ lib_mrsh = library(
		'parser/program.c',
		'parser/word.c',
		'shell/arithm.c',
		'shell/entry.c',
		'shell/path.c',
		'shell/process.c',
		'shell/redir.c',

A shell/entry.c => shell/entry.c +108 -0
@@ 0,0 1,108 @@
#define _POSIX_C_SOURCE 200809L
#include <mrsh/builtin.h>
#include <mrsh/entry.h>
#include <mrsh/shell.h>
#include <mrsh/parser.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include "builtin.h"

extern char **environ;

static char *expand_ps(struct mrsh_state *state, const char *ps1) {
	struct mrsh_parser *parser = mrsh_parser_with_data(ps1, strlen(ps1));
	struct mrsh_word *word = mrsh_parse_word(parser);
	mrsh_parser_destroy(parser);
	if (word == NULL) {
		return NULL;
	}
	mrsh_run_word(state, &word);
	return mrsh_word_str(word);
}

char *mrsh_get_ps1(struct mrsh_state *state, int next_history_id) {
	// TODO: Replace ! with next history ID
	const char *ps1 = mrsh_env_get(state, "PS1", NULL);
	if (ps1 != NULL) {
		return expand_ps(state, ps1);
	}
	char *p = malloc(3);
	sprintf(p, "%s", getuid() ? "$ " : "# ");
	return p;
}

char *mrsh_get_ps2(struct mrsh_state *state) {
	// TODO: Replace ! with next history ID
	const char *ps2 = mrsh_env_get(state, "PS2", NULL);
	if (ps2 != NULL) {
		return expand_ps(state, ps2);
	}
	return strdup("> ");
}

bool mrsh_populate_env(struct mrsh_state *state) {
	for (size_t i = 0; environ[i] != NULL; ++i) {
		char *eql = strchr(environ[i], '=');
		size_t klen = eql - environ[i];
		char *key = strndup(environ[i], klen);
		char *val = &eql[1];
		mrsh_env_set(state, key, val, MRSH_VAR_ATTRIB_EXPORT);
		free(key);
	}

	mrsh_env_set(state, "IFS", " \t\n", MRSH_VAR_ATTRIB_NONE);

	pid_t ppid = getppid();
	char ppid_str[24];
	snprintf(ppid_str, sizeof(ppid_str), "%d", ppid);
	mrsh_env_set(state, "PPID", ppid_str, MRSH_VAR_ATTRIB_NONE);

	// TODO check if path is well-formed, has . or .., and handle symbolic links
	const char *pwd = mrsh_env_get(state, "PWD", NULL);
	if (pwd == NULL || strlen(pwd) >= PATH_MAX) {
		char cwd[PATH_MAX];
		if (getcwd(cwd, PATH_MAX) == NULL) {
			fprintf(stderr, "getcwd failed: %s\n", strerror(errno));
			return false;
		}
		mrsh_env_set(state, "PWD", cwd, MRSH_VAR_ATTRIB_EXPORT);
	}

	mrsh_env_set(state, "OPTIND", "1", MRSH_VAR_ATTRIB_NONE);
	return true;
}

static void source_file(struct mrsh_state *state, char *path) {
	if (access(path, F_OK) == -1) {
		return;
	}
	char *env_argv[] = { ".", path };
	mrsh_run_builtin(state, sizeof(env_argv) / sizeof(env_argv[0]), env_argv);
}

void mrsh_source_profile(struct mrsh_state *state) {
	source_file(state, "/etc/profile");

	char path[PATH_MAX];
	int n = snprintf(path, sizeof(path), "%s/.profile", getenv("HOME"));
	if (n == sizeof(path)) {
		fprintf(stderr, "Warning: $HOME/.profile is longer than PATH_MAX\n");
		return;
	}
	source_file(state, path);
}

void mrsh_source_env(struct mrsh_state *state) {
	char *path = getenv("ENV");
	if (path == NULL) {
		return;
	}
	if (getuid() != geteuid() || getgid() != getegid()) {
		return;
	}
	// TODO: parameter expansion
	source_file(state, path);
}