3 2

[PATCH v3 1/2] Add shell/entry.c

Details
Message ID
<20190118020629.27199-1-sir@cmpwn.com>
Download raw message
Patch +153 -95
---
 include/mrsh/entry.h |  35 ++++++++++++++
 main.c               | 104 ++++-------------------------------------
 meson.build          |   1 +
 shell/entry.c        | 108 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 153 insertions(+), 95 deletions(-)
 create mode 100644 include/mrsh/entry.h
 create mode 100644 shell/entry.c

diff --git a/include/mrsh/entry.h b/include/mrsh/entry.h
new file mode 100644
index 0000000..53389d9
--- /dev/null
+++ b/include/mrsh/entry.h
@@ -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
diff --git a/main.c b/main.c
index 2b6a313..b0355e9 100644
--- a/main.c
+++ b/main.c
@@ -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);
diff --git a/meson.build b/meson.build
index e3dc33e..eaa9a8e 100644
--- a/meson.build
+++ b/meson.build
@@ -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',
diff --git a/shell/entry.c b/shell/entry.c
new file mode 100644
index 0000000..99df78e
--- /dev/null
+++ b/shell/entry.c
@@ -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);
+}
-- 
2.20.1
Details
Message ID
<_XLS0dZaYEY9bBgMvI9aIJR_qSvd9S7ipZdWmQNdLItT9V-OjeIZVBAjTMGn7cVjpzALBAqfBE-UPjJ5MLbcysIZxmfAL4qeTTik8jLPMw8=@emersion.fr>
In-Reply-To
<20190118020629.27199-1-sir@cmpwn.com> (view parent)
Download raw message
Turns out this change makes the FreeBSD build fail:
https://builds.sr.ht/~emersion/job/22512

On FreeBSD, environ is undefined when linking a shared library. It's
only defined when linking an executable.

I see two solutions:

1. On FreeBSD, make the linker warn about undefined symbols when
   linking libmrsh, instead of erroring out.
2. Make the function take environ as an argument.

What do you think?

(2) is a little annoying if we use getenv as well -- the user could
provide an environ which is not the real environ.

[PATCH v3 2/2] Add mrsh_get_alias

Details
Message ID
<20190118020629.27199-2-sir@cmpwn.com>
In-Reply-To
<20190118020629.27199-1-sir@cmpwn.com> (view parent)
Download raw message
Patch +18 -8
---
 include/mrsh/parser.h |  2 +-
 include/mrsh/shell.h  |  4 ++++
 main.c                |  7 +------
 parser/parser.c       |  2 +-
 shell/shell.c         | 11 +++++++++++
 5 files changed, 18 insertions(+), 8 deletions(-)

diff --git a/include/mrsh/parser.h b/include/mrsh/parser.h
index c67f59d..29f1992 100644
--- a/include/mrsh/parser.h
+++ b/include/mrsh/parser.h
@@ -52,7 +52,7 @@ bool mrsh_parser_eof(struct mrsh_parser *state);
 /**
  * Set the alias callback.
  */
-void mrsh_parser_set_alias(struct mrsh_parser *state,
+void mrsh_parser_set_alias_fn(struct mrsh_parser *state,
 	mrsh_parser_alias_func_t alias, void *user_data);
 /**
  * Check if the parser ended with a syntax error. The error message is returned.
diff --git a/include/mrsh/shell.h b/include/mrsh/shell.h
index e0fae04..14e6094 100644
--- a/include/mrsh/shell.h
+++ b/include/mrsh/shell.h
@@ -92,7 +92,11 @@ struct mrsh_state {
 
 void mrsh_function_destroy(struct mrsh_function *fn);
 
+struct mrsh_parser;
+
 void mrsh_state_init(struct mrsh_state *state);
+void mrsh_state_set_parser_alias_fn(
+		struct mrsh_state *state, struct mrsh_parser *parser);
 void mrsh_state_finish(struct mrsh_state *state);
 void mrsh_env_set(struct mrsh_state *state,
 	const char *key, const char *value, uint32_t attribs);
diff --git a/main.c b/main.c
index b0355e9..a5c3fc5 100644
--- a/main.c
+++ b/main.c
@@ -14,11 +14,6 @@
 #include <unistd.h>
 #include "frontend.h"
 
-static const char *get_alias(const char *name, void *data) {
-	struct mrsh_state *state = data;
-	return mrsh_hashtable_get(&state->aliases, name);
-}
-
 int main(int argc, char *argv[]) {
 	struct mrsh_state state = {0};
 	mrsh_state_init(&state);
@@ -68,7 +63,7 @@ int main(int argc, char *argv[]) {
 			parser = mrsh_parser_with_fd(fd);
 		}
 	}
-	mrsh_parser_set_alias(parser, get_alias, &state);
+	mrsh_state_set_parser_alias_fn(&state, parser);
 
 	struct mrsh_buffer read_buffer = {0};
 	while (state.exit == -1) {
diff --git a/parser/parser.c b/parser/parser.c
index d320313..184349b 100644
--- a/parser/parser.c
+++ b/parser/parser.c
@@ -352,7 +352,7 @@ bool mrsh_parser_eof(struct mrsh_parser *state) {
 	return state->has_sym && state->sym == EOF_TOKEN;
 }
 
-void mrsh_parser_set_alias(struct mrsh_parser *state,
+void mrsh_parser_set_alias_fn(struct mrsh_parser *state,
 		mrsh_parser_alias_func_t alias, void *user_data) {
 	state->alias = alias;
 	state->alias_user_data = user_data;
diff --git a/shell/shell.c b/shell/shell.c
index fb010ba..fa84fe6 100644
--- a/shell/shell.c
+++ b/shell/shell.c
@@ -1,6 +1,7 @@
 #define _POSIX_C_SOURCE 200809L
 #include <assert.h>
 #include <mrsh/hashtable.h>
+#include <mrsh/parser.h>
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
@@ -22,6 +23,16 @@ void mrsh_state_init(struct mrsh_state *state) {
 	state->args = calloc(1, sizeof(struct mrsh_call_frame));
 }
 
+static const char *get_alias(const char *name, void *data) {
+	struct mrsh_state *state = data;
+	return mrsh_hashtable_get(&state->aliases, name);
+}
+
+void mrsh_state_set_parser_alias_fn(
+		struct mrsh_state *state, struct mrsh_parser *parser) {
+	mrsh_parser_set_alias_fn(parser, get_alias, state);
+}
+
 static void state_string_finish_iterator(const char *key, void *value,
 		void *user_data) {
 	free(value);
-- 
2.20.1

Re: [PATCH v3 2/2] Add mrsh_get_alias

Details
Message ID
<SvmFuHRprYcAkDJIZEKEgjhoZdF-lVnY4FBBcktMnQJzq5-BfQZ2Rq6nAgA0GSzzBf3bhAG15a0DEcF7kXdSOBtrjgVCVJvBzGS0hm3W-dE=@emersion.fr>
In-Reply-To
<20190118020629.27199-2-sir@cmpwn.com> (view parent)
Download raw message
Both pushed:

To git.sr.ht:~emersion/mrsh
   b201f01..c4de9e0  master -> master

Thanks!