Argument parsing

This commit is contained in:
2026-05-24 23:24:25 -07:00
parent 68bc009fe9
commit 6ba105a14b
7 changed files with 232 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
#include "util.h"
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
typedef struct {
bool opt_error;
bool help_flag;
int port;
int parallelism;
} GlobalFlags;
static const GlobalFlags DEFAULT_FLAGS = {
.opt_error = false,
.help_flag = false,
.port = 0,
.parallelism = 1,
};
// Return false on failure, true on success
static bool parse_int(const char *str, int min, int max, int *output) {
errno = 0;
char *endptr;
long conv = strtol(str, &endptr, 10);
if (!*str || *endptr) {
log_error("malformed number: \"%s\"", str);
return false;
} else if (((conv == LONG_MIN || conv == LONG_MAX) && errno == ERANGE)
|| conv < min || conv > max) {
log_error("out of range: %s", str);
return false;
}
*output = conv;
return true;
}
void parse_cli_options(int argc, const char **argv, GlobalFlags *flags) {
constexpr unsigned long MAX_PARALLELISM = INT_MAX;
constexpr unsigned long MAX_PORT = 65535;
opterr = false;
int c;
char pretty_flag[8];
while ((c = getopt(argc, (char *const *) argv, "hp:")) >= 0) {
if (isprint(c)) {
snprintf(pretty_flag, sizeof(pretty_flag), "%c", c);
} else {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-truncation"
// `c` is actually in the range [0,255], and so will be at most 2
// hex digits
snprintf(pretty_flag, sizeof(pretty_flag), "0x%02x", c);
#pragma GCC diagnostic pop
}
switch (c) {
case 'h':
flags->help_flag = true;
return;
case 'p':
if (!parse_int(optarg, 1, MAX_PARALLELISM, &flags->parallelism)) {
flags->opt_error = true;
}
break;
case ':':
flags->opt_error = true;
log_error("flag requires argument: '%s'", pretty_flag);
break;
case '?':
flags->opt_error = true;
log_error("unknown flag: '%s'", pretty_flag);
break;
}
}
if (optind == argc) {
flags->opt_error = true;
log_error("no port provided");
} else if (optind != argc - 1) {
flags->opt_error = true;
log_error("extra positional arguments after port");
} else if (!parse_int(argv[optind], 1, MAX_PORT, &flags->port)) {
flags->opt_error = true;
}
}
void print_help(FILE *file) {
fprintf(file, "usage: httpserver [-h] [-p PARALLELISM] <PORT>\n");
fprintf(file, " -h print this message, then exit\n");
fprintf(
file,
" -p use PARALLELISM threads for processing requests (default: 1)\n");
}
int main(int argc, const char **argv) {
setenv("POSIXLY_CORRECT", "1", true);
set_exec_name(argv[0]);
GlobalFlags flags = DEFAULT_FLAGS;
parse_cli_options(argc, argv, &flags);
if (flags.help_flag) {
print_help(stdout);
return flags.opt_error ? EXIT_FAILURE : 0;
} else if (flags.opt_error) {
return EXIT_FAILURE;
}
printf("Port: %d\n", flags.port);
printf("Parallelism: %d\n", flags.parallelism);
return 0;
}