~emersion/mrsh

ref: 448ee4f664f60d70d229af5462ef349240af8d7f mrsh/builtin/getopts.c -rw-r--r-- 2.5 KiB
448ee4f6 — Ben Brown Reset optind to 0 1 year, 9 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <limits.h>
#include <mrsh/buffer.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);
		fprintf(stderr, getopts_usage);
		return EXIT_FAILURE;
	}
	if (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];
	} else {
		optc = state->args->argc;
		optv = state->args->argv;
	}
	char *optstring = argv[optind];
	char *name = argv[optind + 1];

	const char *optind_str = mrsh_env_get(state, "OPTIND", NULL);
	if (optind_str == NULL) {
		fprintf(stderr, "getopts: OPTIND is not defined\n");
		return EXIT_FAILURE;
	}
	char *endptr;
	long optind_long = strtol(optind_str, &endptr, 10);
	if (endptr[0] != '\0' || optind_long <= 0 || optind_long > INT_MAX) {
		fprintf(stderr, "getopts: OPTIND is not a positive integer\n");
		return EXIT_FAILURE;
	}
	optind = (int)optind_long;

	optopt = 0;
	int opt = getopt(optc, optv, optstring);

	char optind_fmt[16];
	snprintf(optind_fmt, sizeof(optind_fmt), "%d", optind);
	mrsh_env_set(state, "OPTIND", optind_fmt, MRSH_VAR_ATTRIB_NONE);

	if (optopt != 0) {
		if (opt == ':') {
			char value[] = {(char)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
			bool opt_exists = false;
			size_t len = strlen(optstring);
			for (size_t i = 0; i < len; ++i) {
				if (optstring[i] == optopt) {
					opt_exists = true;
					break;
				}
			}
			if (opt_exists) {
				mrsh_env_unset(state, "OPTARG");
			} else {
				char value[] = {(char)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 {
		mrsh_env_unset(state, "OPTARG");
	}

	char value[] = {opt == -1 ? (char)'?' : (char)opt, '\0'};
	mrsh_env_set(state, name, value, MRSH_VAR_ATTRIB_NONE);

	if (opt == -1) {
		return EXIT_FAILURE;
	}
	return EXIT_SUCCESS;
}