Argument parsing

This commit is contained in:
2026-05-24 23:24:25 -07:00
parent 6ec79f686f
commit 92acec0893
7 changed files with 232 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
httpserver
compile_commands.json
bin/
+22
View File
@@ -0,0 +1,22 @@
CC=gcc
CFLAGS=-Wall -Wextra -Wpedantic -std=c23 -D_POSIX_C_SOURCE=200112L -O2
CFLAGS+=-Og -g -fsanitize=address,undefined
SRCS=main.c threadpool.c util.c
OBJS=$(SRCS:%.c=bin/%.o)
all: httpserver
httpserver: $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
bin/%.o: src/%.c
@mkdir -p bin/deps/
$(CC) $(CFLAGS) -MD -MF $(patsubst src/%.c,bin/deps/%.d,$^) -c -o $@ $^
include $(SRCS:%.c/bin/deps/%.d)
clean:
rm -rf bin/ httpserver
.PHONY: all clean
+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;
}
+26
View File
@@ -0,0 +1,26 @@
#include "threadpool.h"
#include <stdlib.h>
#include <threads.h>
struct thread_pool_queue {
Task task;
void *arg;
struct thread_pool_queue *next;
};
struct _ThreadPool {
size_t nthreads;
thrd_t *threads;
struct thread_pool_queue *queue;
};
ThreadPool *make_thread_pool(int parallelism) {
ThreadPool *pool = malloc(sizeof(ThreadPool));
pool->nthreads = parallelism;
}
void destroy_thread_pool(ThreadPool *pool) {}
void thread_pool_enqueue(Task task, void *arg) {}
+13
View File
@@ -0,0 +1,13 @@
#ifndef INCLUDED_THREAD_POOL_H
#define INCLUDED_THREAD_POOL_H
typedef struct _ThreadPool ThreadPool;
typedef void (*Task)(void *);
ThreadPool *make_thread_pool(int parallelism);
void destroy_thread_pool(ThreadPool *pool);
void thread_pool_enqueue(Task task, void *arg);
#endif
+35
View File
@@ -0,0 +1,35 @@
#include "util.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define DEFAULT_EXEC_NAME "httpserver"
const char *EXEC_NAME = DEFAULT_EXEC_NAME;
static bool need_free_out_of_memory_msg = false;
NO_SANITIZE
static const char *out_of_memory_msg =
DEFAULT_EXEC_NAME ": error: out of memory\n";
void set_exec_name(const char *argv0) {
const char *slash = strrchr(argv0, '/');
if (!slash) {
EXEC_NAME = argv0;
} else {
EXEC_NAME = slash + 1;
}
if (need_free_out_of_memory_msg) {
free((char *) out_of_memory_msg);
}
}
void log_error(const char *fmt, ...) {
fprintf(stderr, "%s: error: ", EXEC_NAME);
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fputc('\n', stderr);
}
+23
View File
@@ -0,0 +1,23 @@
#ifndef INCLUDED_UTIL_H
#define INCLUDED_UTIL_H
#if __has_attribute(format)
# define PRINTF_LIKE(i, j) __attribute__((format(printf, i, j)))
#else
# define PRINTF_LIKE(i, j)
#endif
#if __has_attribute(no_sanitize_address)
# define NO_SANITIZE __attribute__((no_sanitize_address))
#else
# define NO_SANITIZE
#endif
extern const char *EXEC_NAME;
void set_exec_name(const char *argv0);
PRINTF_LIKE(1, 2)
void log_error(const char *fmt, ...);
#endif