1af38099508a577e3e312693048bf1148c9ad768 — Ben Brown 11 months ago 22232fa
Adding getopt implementation

It's pretty simple but I think it covers all of items described in the
standard.
M builtin/alias.c => builtin/alias.c +6 -6
@@ 1,5 1,5 @@
 #define _POSIX_C_SOURCE 200809L
-#include <getopt.h>
+#include <mrsh/getopt.h>
 #include <mrsh/builtin.h>
 #include <stdio.h>
 #include <string.h>


@@ 17,19 17,19 @@ static void print_alias_iterator(const char *key, void *_value,
 }
 
 int builtin_alias(struct mrsh_state *state, int argc, char *argv[]) {
-	optind = 0;
-	if (getopt(argc, argv, ":") != -1) {
-		fprintf(stderr, "alias: unknown option -- %c\n", optopt);
+	mrsh_optind = 1;
+	if (mrsh_getopt(argc, argv, ":") != -1) {
+		fprintf(stderr, "alias: unknown option -- %c\n", mrsh_optopt);
 		fprintf(stderr, alias_usage);
 		return EXIT_FAILURE;
 	}
 
-	if (optind == argc) {
+	if (mrsh_optind == argc) {
 		mrsh_hashtable_for_each(&state->aliases, print_alias_iterator, NULL);
 		return EXIT_SUCCESS;
 	}
 
-	for (int i = optind; i < argc; ++i) {
+	for (int i = mrsh_optind; i < argc; ++i) {
 		char *alias = argv[i];
 		char *equal = strchr(alias, '=');
 		if (equal != NULL) {

M builtin/cd.c => builtin/cd.c +7 -6
@@ 1,6 1,7 @@
 #define _POSIX_C_SOURCE 200809L
 #include <errno.h>
 #include <limits.h>
+#include <mrsh/getopt.h>
 #include <mrsh/shell.h>
 #include <stdio.h>
 #include <stdlib.h>


@@ 36,9 37,9 @@ static int isdir(char *path) {
 }
 
 int builtin_cd(struct mrsh_state *state, int argc, char *argv[]) {
-	optind = 0;
+	mrsh_optind = 1;
 	int opt;
-	while ((opt = getopt(argc, argv, ":LP")) != -1) {
+	while ((opt = mrsh_getopt(argc, argv, ":LP")) != -1) {
 		switch (opt) {
 		case 'L':
 		case 'P':


@@ 46,17 47,17 @@ int builtin_cd(struct mrsh_state *state, int argc, char *argv[]) {
 			fprintf(stderr, "cd: `-L` and `-P` not yet implemented\n");
 			return EXIT_FAILURE;
 		default:
-			fprintf(stderr, "cd: unknown option -- %c\n", optopt);
+			fprintf(stderr, "cd: unknown option -- %c\n", mrsh_optopt);
 			fprintf(stderr, cd_usage);
 			return EXIT_FAILURE;
 		}
 	}
-	if (optind + 1 > argc) {
+	if (mrsh_optind + 1 > argc) {
 		fprintf(stderr, cd_usage);
 		return EXIT_FAILURE;
 	}
 
-	if (optind == argc) {
+	if (mrsh_optind == argc) {
 		const char *home = mrsh_env_get(state, "HOME", NULL);
 		if (home && home[0] != '\0') {
 			return cd(state, home);


@@ 66,7 67,7 @@ int builtin_cd(struct mrsh_state *state, int argc, char *argv[]) {
 		return EXIT_FAILURE;
 	}
 
-	char *curpath = argv[optind];
+	char *curpath = argv[mrsh_optind];
 	// `cd -`
 	if (strcmp(curpath, "-") == 0) {
 		// This case is special as we print `pwd` at the end

M builtin/getopts.c => builtin/getopts.c +21 -21
@@ 2,38 2,38 @@
 #include <errno.h>
 #include <limits.h>
 #include <mrsh/buffer.h>
+#include <mrsh/getopt.h>
 #include <mrsh/shell.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <unistd.h>
 #include "builtin.h"
 
 static const char getopts_usage[] = "usage: getopts optstring name [arg...]\n";
 
 int builtin_getopts(struct mrsh_state *state, int argc, char *argv[]) {
-	optind = 0;
-	if (getopt(argc, argv, ":") != -1) {
-		fprintf(stderr, "getopts: unknown option -- %c\n", optopt);
+	mrsh_optind = 1;
+	if (mrsh_getopt(argc, argv, ":") != -1) {
+		fprintf(stderr, "getopts: unknown option -- %c\n", mrsh_optopt);
 		fprintf(stderr, getopts_usage);
 		return EXIT_FAILURE;
 	}
-	if (optind + 2 < argc) {
+	if (mrsh_optind + 2 < argc) {
 		fprintf(stderr, getopts_usage);
 		return EXIT_FAILURE;
 	}
 
 	int optc;
 	char **optv;
-	if (optind + 2 > argc) {
-		optc = argc - optind - 2;
-		optv = &argv[optind + 2];
+	if (mrsh_optind + 2 > argc) {
+		optc = argc - mrsh_optind - 2;
+		optv = &argv[mrsh_optind + 2];
 	} else {
 		optc = state->args->argc;
 		optv = state->args->argv;
 	}
-	char *optstring = argv[optind];
-	char *name = argv[optind + 1];
+	char *optstring = argv[mrsh_optind];
+	char *name = argv[mrsh_optind + 1];
 
 	const char *optind_str = mrsh_env_get(state, "OPTIND", NULL);
 	if (optind_str == NULL) {


@@ 46,29 46,29 @@ int builtin_getopts(struct mrsh_state *state, int argc, char *argv[]) {
 		fprintf(stderr, "getopts: OPTIND is not a positive integer\n");
 		return EXIT_FAILURE;
 	}
-	optind = (int)optind_long;
+	mrsh_optind = (int)optind_long;
 
-	optopt = 0;
-	int opt = getopt(optc, optv, optstring);
+	mrsh_optopt = 0;
+	int opt = mrsh_getopt(optc, optv, optstring);
 
 	char optind_fmt[16];
-	snprintf(optind_fmt, sizeof(optind_fmt), "%d", optind);
+	snprintf(optind_fmt, sizeof(optind_fmt), "%d", mrsh_optind);
 	mrsh_env_set(state, "OPTIND", optind_fmt, MRSH_VAR_ATTRIB_NONE);
 
-	if (optopt != 0) {
+	if (mrsh_optopt != 0) {
 		if (opt == ':') {
-			char value[] = {(char)optopt, '\0'};
+			char value[] = {(char)mrsh_optopt, '\0'};
 			mrsh_env_set(state, "OPTARG", value, MRSH_VAR_ATTRIB_NONE);
 		} else if (optstring[0] != ':') {
 			mrsh_env_unset(state, "OPTARG");
 		} else {
 			// either missing option-argument or unknown option character
 			// in the former case, unset OPTARG
-			// in the latter case, set OPTARG to optopt
+			// in the latter case, set OPTARG to mrsh_optopt
 			bool opt_exists = false;
 			size_t len = strlen(optstring);
 			for (size_t i = 0; i < len; ++i) {
-				if (optstring[i] == optopt) {
+				if (optstring[i] == mrsh_optopt) {
 					opt_exists = true;
 					break;
 				}


@@ 76,12 76,12 @@ int builtin_getopts(struct mrsh_state *state, int argc, char *argv[]) {
 			if (opt_exists) {
 				mrsh_env_unset(state, "OPTARG");
 			} else {
-				char value[] = {(char)optopt, '\0'};
+				char value[] = {(char)mrsh_optopt, '\0'};
 				mrsh_env_set(state, "OPTARG", value, MRSH_VAR_ATTRIB_NONE);
 			}
 		}
-	} else if (optarg != NULL) {
-		mrsh_env_set(state, "OPTARG", optarg, MRSH_VAR_ATTRIB_NONE);
+	} else if (mrsh_optarg != NULL) {
+		mrsh_env_set(state, "OPTARG", mrsh_optarg, MRSH_VAR_ATTRIB_NONE);
 	} else {
 		mrsh_env_unset(state, "OPTARG");
 	}

M builtin/pwd.c => builtin/pwd.c +5 -5
@@ 1,5 1,5 @@
 #define _POSIX_C_SOURCE 200809L
-#include <getopt.h>
+#include <mrsh/getopt.h>
 #include <mrsh/shell.h>
 #include <stdio.h>
 #include <stdlib.h>


@@ 8,9 8,9 @@
 static const char pwd_usage[] = "usage: pwd [-L|-P]\n";
 
 int builtin_pwd(struct mrsh_state *state, int argc, char *argv[]) {
-	optind = 0;
+	mrsh_optind = 1;
 	int opt;
-	while ((opt = getopt(argc, argv, ":LP")) != -1) {
+	while ((opt = mrsh_getopt(argc, argv, ":LP")) != -1) {
 		switch (opt) {
 		case 'L':
 		case 'P':


@@ 18,12 18,12 @@ int builtin_pwd(struct mrsh_state *state, int argc, char *argv[]) {
 			fprintf(stderr, "pwd: `-L` and `-P` not yet implemented\n");
 			return EXIT_FAILURE;
 		default:
-			fprintf(stderr, "pwd: unknown option -- %c\n", optopt);
+			fprintf(stderr, "pwd: unknown option -- %c\n", mrsh_optopt);
 			fprintf(stderr, pwd_usage);
 			return EXIT_FAILURE;
 		}
 	}
-	if (optind < argc) {
+	if (mrsh_optind < argc) {
 		fprintf(stderr, pwd_usage);
 		return EXIT_FAILURE;
 	}

M builtin/read.c => builtin/read.c +12 -12
@@ 1,12 1,12 @@
 #define _POSIX_C_SOURCE 200809L
 #include <errno.h>
 #include <mrsh/buffer.h>
+#include <mrsh/getopt.h>
 #include <mrsh/shell.h>
 #include <shell/word.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <unistd.h>
 #include "builtin.h"
 
 static const char read_usage[] = "usage: read [-r] var...\n";


@@ 14,20 14,20 @@ static const char read_usage[] = "usage: read [-r] var...\n";
 int builtin_read(struct mrsh_state *state, int argc, char *argv[]) {
 	bool raw = false;
 
-	optind = 0;
+	mrsh_optind = 1;
 	int opt;
-	while ((opt = getopt(argc, argv, ":r")) != -1) {
+	while ((opt = mrsh_getopt(argc, argv, ":r")) != -1) {
 		switch (opt) {
 		case 'r':
 			raw = true;
 			break;
 		default:
-			fprintf(stderr, "read: unknown option -- %c\n", optopt);
+			fprintf(stderr, "read: unknown option -- %c\n", mrsh_optopt);
 			fprintf(stderr, read_usage);
 			return EXIT_FAILURE;
 		}
 	}
-	if (optind == argc) {
+	if (mrsh_optind == argc) {
 		fprintf(stderr, read_usage);
 		return EXIT_FAILURE;
 	}


@@ 60,19 60,19 @@ int builtin_read(struct mrsh_state *state, int argc, char *argv[]) {
 	split_fields(&fields, &ws->word, mrsh_env_get(state, "IFS", NULL));
 	mrsh_word_destroy(&ws->word);
 
-	if (fields.len <= (size_t)(argc - optind)) {
+	if (fields.len <= (size_t)(argc - mrsh_optind)) {
 		for (size_t i = 0; i < fields.len; ++i) {
-			mrsh_env_set(state, argv[optind + i], (char *)fields.data[i], MRSH_VAR_ATTRIB_NONE);
+			mrsh_env_set(state, argv[mrsh_optind + i], (char *)fields.data[i], MRSH_VAR_ATTRIB_NONE);
 		}
-		for (size_t i = fields.len; i < (size_t)(argc - optind); ++i) {
-			mrsh_env_set(state, argv[optind + i], "", MRSH_VAR_ATTRIB_NONE);
+		for (size_t i = fields.len; i < (size_t)(argc - mrsh_optind); ++i) {
+			mrsh_env_set(state, argv[mrsh_optind + i], "", MRSH_VAR_ATTRIB_NONE);
 		}
 	} else {
-		for (int i = 0; i < argc - optind - 1; ++i) {
-			mrsh_env_set(state, argv[optind + i], (char *)fields.data[i], MRSH_VAR_ATTRIB_NONE);
+		for (int i = 0; i < argc - mrsh_optind - 1; ++i) {
+			mrsh_env_set(state, argv[mrsh_optind + i], (char *)fields.data[i], MRSH_VAR_ATTRIB_NONE);
 		}
 		struct mrsh_buffer buf_last = {0};
-		for (size_t i = (size_t)(argc - optind - 1); i < fields.len; ++i) {
+		for (size_t i = (size_t)(argc - mrsh_optind - 1); i < fields.len; ++i) {
 			char *field = (char *)fields.data[i];
 			mrsh_buffer_append(&buf_last, field, strlen(field));
 			if (i != fields.len - 1) {

M builtin/type.c => builtin/type.c +6 -6
@@ 1,6 1,6 @@
 #define _POSIX_C_SOURCE 200809L
-#include <getopt.h>
 #include <mrsh/builtin.h>
+#include <mrsh/getopt.h>
 #include <mrsh/shell.h>
 #include <shell/path.h>
 #include <stdlib.h>


@@ 9,19 9,19 @@
 static const char type_usage[] = "usage: type name...\n";
 
 int builtin_type(struct mrsh_state *state, int argc, char *argv[]) {
-	optind = 0;
-	if (getopt(argc, argv, ":") != -1) {
-		fprintf(stderr, "type: unknown option -- %c\n", optopt);
+	mrsh_optind = 1;
+	if (mrsh_getopt(argc, argv, ":") != -1) {
+		fprintf(stderr, "type: unknown option -- %c\n", mrsh_optopt);
 		fprintf(stderr, type_usage);
 		return EXIT_FAILURE;
 	}
-	if (optind == argc) {
+	if (mrsh_optind == argc) {
 		fprintf(stderr, type_usage);
 		return EXIT_FAILURE;
 	}
 
 	bool error = false;
-	for (int i = optind; i < argc; ++i) {
+	for (int i = mrsh_optind; i < argc; ++i) {
 		char *name = argv[i];
 
 		char *alias = mrsh_hashtable_get(&state->aliases, name);

M builtin/umask.c => builtin/umask.c +7 -7
@@ 1,6 1,6 @@
 #define _POSIX_C_SOURCE 200809L
-#include <getopt.h>
 #include <mrsh/builtin.h>
+#include <mrsh/getopt.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/types.h>


@@ 162,23 162,23 @@ int builtin_umask(struct mrsh_state *state, int argc, char *argv[]) {
 	mode_t mode;
 	bool umask_symbolic = false;
 
-	optind = 0;
+	mrsh_optind = 1;
 	int opt;
 
-	while ((opt = getopt(argc, argv, ":S")) != -1) {
+	while ((opt = mrsh_getopt(argc, argv, ":S")) != -1) {
 		switch (opt) {
 		case 'S':
 			umask_symbolic = true;
 			break;
 
 		default:
-			fprintf(stderr, "Unknown option -- '%c'\n", optopt);
+			fprintf(stderr, "Unknown option -- '%c'\n", mrsh_optopt);
 			fprintf(stderr, umask_usage);
 			return EXIT_FAILURE;
 		}
 	}
 
-	if (optind == argc) {
+	if (mrsh_optind == argc) {
 		mode = umask_current_mask();
 
 		if (umask_symbolic) {


@@ 191,10 191,10 @@ int builtin_umask(struct mrsh_state *state, int argc, char *argv[]) {
 	}
 
 	char *endptr;
-	mode = strtol(argv[optind], &endptr, 8);
+	mode = strtol(argv[mrsh_optind], &endptr, 8);
 
 	if (*endptr != '\0') {
-		if (!umask_mode(&mode, argv[optind])) {
+		if (!umask_mode(&mode, argv[mrsh_optind])) {
 			fprintf(stderr, umask_usage);
 			return EXIT_FAILURE;
 		}

M builtin/unalias.c => builtin/unalias.c +7 -7
@@ 1,6 1,6 @@
 #define _POSIX_C_SOURCE 200809L
-#include <getopt.h>
 #include <mrsh/builtin.h>
+#include <mrsh/getopt.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>


@@ 15,22 15,22 @@ static void delete_alias_iterator(const char *key, void *_value, void *user_data
 int builtin_unalias(struct mrsh_state *state, int argc, char *argv[]) {
 	bool all = false;
 
-	optind = 0;
+	mrsh_optind = 1;
 	int opt;
-	while ((opt = getopt(argc, argv, ":a")) != -1) {
+	while ((opt = mrsh_getopt(argc, argv, ":a")) != -1) {
 		switch (opt) {
 		case 'a':
 			all = true;
 			break;
 		default:
-			fprintf(stderr, "unalias: unknown option -- %c\n", optopt);
+			fprintf(stderr, "unalias: unknown option -- %c\n", mrsh_optopt);
 			fprintf(stderr, unalias_usage);
 			return EXIT_FAILURE;
 		}
 	}
 
 	if (all) {
-		if (optind < argc) {
+		if (mrsh_optind < argc) {
 			fprintf(stderr, unalias_usage);
 			return EXIT_FAILURE;
 		}


@@ 38,12 38,12 @@ int builtin_unalias(struct mrsh_state *state, int argc, char *argv[]) {
 		return EXIT_SUCCESS;
 	}
 
-	if (optind == argc) {
+	if (mrsh_optind == argc) {
 		fprintf(stderr, unalias_usage);
 		return EXIT_FAILURE;
 	}
 
-	for (int i = optind; i < argc; ++i) {
+	for (int i = mrsh_optind; i < argc; ++i) {
 		free(mrsh_hashtable_del(&state->aliases, argv[i]));
 	}
 	return EXIT_SUCCESS;

M builtin/unset.c => builtin/unset.c +6 -6
@@ 1,9 1,9 @@
 #define _POSIX_C_SOURCE 200809L
 #include <mrsh/builtin.h>
+#include <mrsh/getopt.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <unistd.h>
 #include "builtin.h"
 
 static const char unset_usage[] = "usage: unset [-fv] name...\n";


@@ 11,9 11,9 @@ static const char unset_usage[] = "usage: unset [-fv] name...\n";
 int builtin_unset(struct mrsh_state *state, int argc, char *argv[]) {
 	bool funcs = false;
 
-	optind = 0;
+	mrsh_optind = 1;
 	int opt;
-	while ((opt = getopt(argc, argv, ":fv")) != -1) {
+	while ((opt = mrsh_getopt(argc, argv, ":fv")) != -1) {
 		switch (opt) {
 		case 'f':
 			funcs = true;


@@ 22,16 22,16 @@ int builtin_unset(struct mrsh_state *state, int argc, char *argv[]) {
 			funcs = false;
 			break;
 		default:
-			fprintf(stderr, "unset: unknown option -- %c\n", optopt);
+			fprintf(stderr, "unset: unknown option -- %c\n", mrsh_optopt);
 			fprintf(stderr, unset_usage);
 			return EXIT_FAILURE;
 		}
 	}
-	if (optind >= argc) {
+	if (mrsh_optind >= argc) {
 		fprintf(stderr, unset_usage);
 		return EXIT_FAILURE;
 	}
-	for (int i = optind; i < argc; ++i) {
+	for (int i = mrsh_optind; i < argc; ++i) {
 		if (!funcs) {
 			uint32_t prev_attribs = 0;
 			if (mrsh_env_get(state, argv[i], &prev_attribs)) {

A getopt.c => getopt.c +74 -0
@@ 0,0 1,74 @@
+#define _POSIX_C_SOURCE 200809L
+#include <assert.h>
+#include <mrsh/getopt.h>
+#include <stdio.h>
+
+char *mrsh_optarg = NULL;
+int mrsh_optind = 1;
+int mrsh_opterr = 1;
+int mrsh_optopt = 0;
+
+int mrsh_getopt(int argc, char *const argv[], const char *optstring) {
+	assert(argv[argc] == NULL);
+	mrsh_optarg = NULL;
+
+	if (mrsh_optind >= argc) {
+		return -1;
+	}
+
+	if (argv[mrsh_optind][0] != '-') {
+		return -1;
+	}
+
+	if (argv[mrsh_optind][1] == '\0') {
+		return -1;
+	}
+
+	if (argv[mrsh_optind][1] == '-') {
+		mrsh_optind++;
+		return -1;
+	}
+
+	const char *c = optstring;
+	if (*c == ':') {
+		c++;
+	}
+
+	mrsh_optopt = 0;
+	int opt = argv[mrsh_optind][1];
+	for (; *c != '\0'; c++) {
+		if (*c != opt) {
+			continue;
+		}
+
+		if (c[1] != ':') {
+			mrsh_optind++;
+			return opt;
+		}
+
+		if (argv[mrsh_optind][2] != '\0') {
+			mrsh_optarg = &argv[mrsh_optind][2]; 
+		} else {
+			if (mrsh_optind + 2 > argc) {
+				mrsh_optopt = opt;
+				if (mrsh_opterr != 0 && optstring[0] != ':') {
+					fprintf(stderr, "%s: Option '%c' requires an argument.\n",
+						argv[0], mrsh_optopt);
+				}
+
+				return optstring[0] == ':' ? ':' : '?';
+			}
+
+			mrsh_optarg = argv[++mrsh_optind];
+		}
+
+		mrsh_optind++;
+		return opt;
+	}
+
+	if (mrsh_opterr != 0 && optstring[0] != ':') {
+		fprintf(stderr, "%s: Option '%c' not found.\n", argv[0], opt);
+	}
+
+	return '?';
+}

A include/mrsh/getopt.h => include/mrsh/getopt.h +9 -0
@@ 0,0 1,9 @@
+#ifndef MRSH_GETOPT_H
+#define MRSH_GETOPT_H
+
+extern char *mrsh_optarg;
+extern int mrsh_opterr, mrsh_optind, mrsh_optopt;
+
+int mrsh_getopt(int argc, char * const argv[], const char *optstring);
+
+#endif

M meson.build => meson.build +1 -0
@@ 67,6 67,7 @@ lib_mrsh = library(
 		'builtin/umask.c',
 		'builtin/unalias.c',
 		'builtin/unset.c',
+		'getopt.c',
 		'hashtable.c',
 		'parser/arithm.c',
 		'parser/parser.c',