summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile30
-rw-r--r--arena.h149
-rw-r--r--main.c237
-rw-r--r--str.h162
4 files changed, 578 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..c8e803b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,30 @@
+EXE = xmenu
+OBJ != find . -name '*.c' | sed -e 's/\.c$$/.o/' -e 's|^\./||'
+
+CFLAGS += -Wall -Wpedantic
+LDFLAGS += -s
+
+PREFIX ?= ${HOME}
+BINDIR ?= ${PREFIX}/bin
+
+.PHONY: all install clean run
+
+all: ${EXE}
+
+install: ${EXE}
+ install -d ${BINDIR}
+ @for e in ${EXE}; do echo install $$e ${BINDIR};\
+ install $$e ${BINDIR}; done
+
+uninstall:
+ @for e in ${EXE}; do echo rm -f ${BINDIR}/$$e;\
+ rm -f ${BINDIR}/$$e; done
+
+clean:
+ rm -fv ${EXE} ${OBJ}
+
+run: ${EXE}
+ ls | ./${EXE}
+
+${EXE}: ${OBJ}
+ ${CC} ${OBJ} -o ${EXE} ${LDFLAGS}
diff --git a/arena.h b/arena.h
new file mode 100644
index 0000000..7013eb7
--- /dev/null
+++ b/arena.h
@@ -0,0 +1,149 @@
+#ifndef ARENA_H
+#define ARENA_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+
+typedef struct ArenaPg {
+ struct ArenaPg *prev, *next;
+ char *beg, *end;
+ char data[];
+} ArenaPg;
+
+typedef struct {
+ ArenaPg *pg;
+ char *beg;
+} ArenaMark;
+
+typedef struct {
+ ArenaPg *cur, *tail;
+} Arena;
+
+#define new(a, t)\
+ (t*)arena_zeroed(arena_alloc(a, sizeof(t), _Alignof(t)), sizeof(t))
+
+#define new_arr(a, t, n)\
+ arena_alloc(a, sizeof(t) * (n), _Alignof(t))
+
+#define resize(a, p, old, new)\
+ arena_realloc(a, p, (old) * sizeof(*(p)), (new) * sizeof(*(p)),\
+ _Alignof(__typeof__(*(p))))
+
+void arena_free(Arena *a);
+
+void arena_save(Arena *a, ArenaMark *m);
+void arena_load(Arena *a, ArenaMark *m);
+
+void arena_reset(Arena *a);
+void arena_reserve(Arena *a, ptrdiff_t n);
+
+void *arena_alloc(Arena *a, ptrdiff_t n, ptrdiff_t align);
+void *arena_realloc(Arena *a, void *ptr, ptrdiff_t old, ptrdiff_t new, ptrdiff_t align);
+void *arena_zeroed(void *p, size_t n);
+
+#define ARENA_BACKEND_MALLOC 0
+#define ARENA_BACKEND_MMAP 1
+
+#ifndef ARENA_BACKEND
+#if defined(__linux__)
+# define ARENA_BACKEND ARENA_BACKEND_MMAP
+#else
+# define ARENA_BACKEND ARENA_BACKEND_MALLOC
+#endif
+#endif
+
+#ifdef ARENA_IMPL
+
+#include <stdio.h>
+#include <stdlib.h>
+static void arena_pg_alloc_fail(void) {
+ fprintf(stderr, "failed to allocate arena page\n");
+ abort();
+}
+
+#if ARENA_BACKEND == ARENA_BACKEND_MMAP
+#include <sys/mman.h>
+#include <unistd.h>
+#define ARENA_PG_SIZE sysconf(_SC_PAGESIZE)
+static inline void *arena_pg_alloc(ptrdiff_t n) {
+ void *p = mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ return p == MAP_FAILED ? NULL : p;
+}
+static inline void arena_pg_free(void *ptr, ptrdiff_t n) { munmap(ptr, n); }
+#elif ARENA_BACKEND == ARENA_BACKEND_MALLOC
+#define ARENA_PG_SIZE 8192
+static inline void *arena_pg_alloc(ptrdiff_t n) { return malloc(n); }
+static inline void arena_pg_free(void *ptr, ptrdiff_t n) { free(ptr); (void)n; }
+#endif
+
+void arena_free(Arena *a) {
+ while (a->tail) {
+ a->cur = a->tail->prev;
+ arena_pg_free(a->tail, (uintptr_t)(a->tail->end - (char*)a->tail));
+ a->tail = a->cur;
+ }
+}
+
+void arena_reserve(Arena *a, ptrdiff_t n) {
+ while (a->cur && a->cur->beg + n >= a->cur->end) a->cur = a->cur->next;
+ if (a->cur) return;
+ ptrdiff_t cap = n + sizeof(ArenaPg);
+ cap += (uintptr_t)-cap & (ARENA_PG_SIZE - 1);
+ ArenaPg *p = arena_pg_alloc(cap);
+ if (!p) arena_pg_alloc_fail();
+ p->next = NULL;
+ p->prev = a->tail;
+ p->beg = p->data;
+ p->end = (char*)p + cap;
+ if (a->tail) a->tail->next = p;
+ a->cur = (a->tail = p);
+}
+
+void *arena_alloc(Arena *a, ptrdiff_t n, ptrdiff_t align) {
+ arena_reserve(a, n + (align - 1));
+ char *ptr = a->cur->beg + (-(uintptr_t)a->cur->beg & (align - 1));
+ a->cur->beg = ptr + n;
+ return ptr;
+}
+
+void *arena_realloc(Arena *a, void *ptr, ptrdiff_t old, ptrdiff_t new, ptrdiff_t align) {
+ if (a->cur && ptr == a->cur->beg - old && (char*)ptr + new < a->cur->end) {
+ a->cur->beg += new - old;
+ return ptr;
+ } else {
+ void *p = arena_alloc(a, new, align);
+ if (ptr) memcpy(p, ptr, old);
+ return p;
+ }
+}
+
+void *arena_zeroed(void *p, size_t n) {
+ memset(p, 0, n);
+ return p;
+}
+
+void arena_reset(Arena *a) {
+ if (!a->cur) return;
+ while (a->cur->prev) {
+ a->cur->beg = a->cur->data;
+ a->cur = a->cur->prev;
+ }
+ a->cur->beg = a->cur->data;
+}
+
+void arena_save(Arena *a, ArenaMark *m) {
+ m->pg = a->cur;
+ if (a->cur) m->beg = a->cur->beg;
+}
+
+void arena_load(Arena *a, ArenaMark *m) {
+ while (a->cur && a->cur != m->pg) {
+ a->cur->beg = a->cur->data;
+ a->cur = a->cur->prev;
+ }
+ if (a->cur) a->cur->beg = m->beg;
+}
+
+#endif
+#endif
diff --git a/main.c b/main.c
new file mode 100644
index 0000000..80ab2e3
--- /dev/null
+++ b/main.c
@@ -0,0 +1,237 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <limits.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+
+#define ARENA_IMPL
+#define STR_IMPL
+
+#include "arena.h"
+#include "str.h"
+
+/* address cache */
+
+#define ADDR_MAP_MAX 128
+#define ADDR_HOST_MAX 128
+
+typedef struct {
+ char host[ADDR_HOST_MAX];
+ int hostn, port;
+ struct addrinfo *ai;
+} AddrMap;
+
+static AddrMap addr_mapv[ADDR_MAP_MAX];
+static int addr_mapc = 0;
+
+Str
+uint_to_str(unsigned src, Arena *a)
+{
+ Str r = { 0 };
+ do {
+ str_catc(&r, (src % 10) + '0', a);
+ src /= 10;
+ } while(src);
+ for (int i = 0, j = r.n - 1; i < j; i++, j--) {
+ char t = r.s[i];
+ r.s[i] = r.s[j];
+ r.s[j] = t;
+ }
+ return r;
+}
+
+struct addrinfo *
+addr_get(Str host, int port, Arena *scratch)
+{
+ if (host.n > ADDR_HOST_MAX) {
+ fprintf(stderr, "hostname too long\n");
+ return NULL;
+ }
+
+ /* TODO: maybe move fetched addr to front of table? */
+
+ for (int i = 0; i < addr_mapc; i++) {
+ AddrMap *m = &addr_mapv[i];
+ if (m->port != port) continue;
+ if (str_eql((Str) { m->host, m->hostn }, host))
+ return m->ai;
+ }
+
+ puts("lookup");
+ struct addrinfo *ai;
+ int e = getaddrinfo(str_to_cstr(host, scratch),
+ str_to_cstr(uint_to_str(port, scratch), scratch),
+ &(struct addrinfo) {
+ .ai_family = AF_INET,
+ .ai_socktype = SOCK_STREAM,
+ .ai_protocol = IPPROTO_TCP,
+ .ai_flags = AI_NUMERICSERV
+ }, &ai);
+ if (e) {
+ fprintf(stderr, "bad address: %s\n", gai_strerror(e));
+ return NULL;
+ }
+
+ if (addr_mapc == ADDR_MAP_MAX) {
+ freeaddrinfo(addr_mapv[0].ai);
+ for (int i = 1; i < ADDR_MAP_MAX; i++)
+ addr_mapv[i-1] = addr_mapv[i];
+ addr_mapc--;
+ }
+ AddrMap *m = &addr_mapv[addr_mapc++];
+ memcpy(m->host, host.s, host.n);
+ m->hostn = host.n;
+ m->port = port;
+ m->ai = ai;
+ return ai;
+}
+
+void
+addr_init(void)
+{
+ /* do nothing */
+}
+
+void
+addr_fini(void)
+{
+ for (int i = 0; i < addr_mapc; i++)
+ freeaddrinfo(addr_mapv[i].ai);
+}
+
+int
+addr_conn(Str host, int port, Arena *scratch)
+{
+ struct addrinfo *ai = addr_get(host, port, scratch);
+ if (!ai)
+ return -1;
+
+ int s = socket(AF_INET, SOCK_STREAM, 0);
+ if (s == -1) {
+ fprintf(stderr, "couldn't create socket\n");
+ return -1;
+ }
+
+ if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) {
+ fprintf(stderr, "couldn't connect to host\n");
+ close(s);
+ return -1;
+ }
+
+ return s;
+}
+
+/* url parsing */
+
+#define PROT_DEFAULT PROT_GOPHER
+
+typedef enum {
+ PROT_UNKNOWN,
+ PROT_GOPHER,
+ PROT_FILE,
+} Proto;
+
+typedef enum {
+ GI_TEXT,
+ GI_MENU,
+ GI_SEARCH,
+ GI_UNSUPPORTED,
+} GopherItemType;
+
+typedef struct {
+ Str host, path;
+ Proto proto;
+ int port;
+} Request;
+
+int
+parse_port(Str s) {
+ int x = 0;
+ for (int i = 0; i < s.n; i++) {
+ if (s.s[i] < '0' || s.s[i] > '9') return -1;
+ if (x >= INT_MAX / 10 - 1) return -1;
+ x = (x * 10) + s.s[i] - '0';
+ }
+ return x;
+}
+
+int
+parse_request(Str url, Request *req)
+{
+ int i;
+ static struct { Str s; Proto p; int port; } prot_tbl[] = {
+ { Ss("gopher"), PROT_GOPHER, 70 },
+ { Ss("file"), PROT_FILE, 0 },
+ };
+
+ /* protocol */
+
+ req->proto = PROT_UNKNOWN;
+ Cut cprot = str_cuts(url, S("://"));
+ if (cprot.tail.n > 0) {
+ for (i = 0; i < sizeof prot_tbl / sizeof *prot_tbl; i++) {
+ if (str_eql(cprot.head, prot_tbl[i].s)) {
+ req->proto = prot_tbl[i].p;
+ req->port = prot_tbl[i].port;
+ }
+ }
+ url = cprot.tail;
+ switch (req->proto) {
+ case PROT_UNKNOWN:
+ fprintf(stderr, "bad protocol\n");
+ return -1;
+ case PROT_FILE:
+ req->path = url;
+ return 0;
+ default:
+ break;
+ }
+ } else {
+ req->proto = PROT_DEFAULT;
+ }
+
+ /* host & port */
+
+ Cut chost = str_cut(url, '/');
+ Cut cport = str_cut(chost.head, ':');
+ req->path = chost.tail;
+ req->host = cport.head;
+ if (cport.tail.n > 0) {
+ req->port = parse_port(cport.tail);
+ if (req->port == -1) {
+ fprintf(stderr, "bad port\n");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int
+main(void)
+{
+ Arena scratch = { 0 };
+ Request req = { 0 };
+ addr_init();
+
+ printf("%d\n", parse_request(S("gopher://sdf.org/users/solderpunk"), &req));
+ printf("host = %.*s\n", (int)req.host.n, req.host.s);
+ printf("path = %.*s\n", (int)req.path.n, req.path.s);
+ printf("prot = %d\n", req.proto);
+ printf("port = %d\n", req.port);
+
+ int s = addr_conn(req.host, req.port, &scratch);
+ write(s, req.path.s, req.path.n);
+ write(s, "\r\n", 2);
+ for (;;) {
+ char buf[256];
+ int n = read(s, buf, 256);
+ if (n <= 0) break;
+ fwrite(buf, 1, n, stdout);
+ }
+ close(s);
+
+ addr_fini();
+ return 0;
+}
diff --git a/str.h b/str.h
new file mode 100644
index 0000000..454cbee
--- /dev/null
+++ b/str.h
@@ -0,0 +1,162 @@
+#ifndef STR_H
+#define STR_H
+
+#include <string.h>
+#include <stddef.h>
+
+#include "arena.h"
+
+typedef struct {
+ char *s;
+ ptrdiff_t n;
+} Str;
+
+typedef struct {
+ Str head, tail;
+} Cut;
+
+#define Ss(s) {s,sizeof(s)-1}
+#define S(s) (Str)Ss(s)
+
+char *str_to_cstr(Str s, Arena *a);
+Str str_from_cstr(const char *s);
+int str_eql(Str a, Str b);
+int str_starts(Str a, Str b);
+int str_ends(Str a, Str b);
+void str_catc(Str *a, char b, Arena *m);
+Str str_skip(Str a, ptrdiff_t n);
+int is_space(char c);
+Str str_trim_left(Str a);
+Str str_trim_right(Str a);
+Str str_trim(Str a);
+Cut str_cut(Str s, char c);
+Cut str_cuts(Str s, Str subs);
+Str str_findc(Str s, char c);
+Str str_find(Str haystack, Str needle);
+int str_contains(Str a, Str b);
+Str str_dup(Str a, Arena *m);
+void str_cat(Str *a, Str b, Arena *m);
+Str str_replace_end(Str s, Str a, Str b, Arena *m);
+
+#ifdef STR_IMPL
+
+/* conversions */
+
+char *str_to_cstr(Str s, Arena *a) {
+ char *r = new_arr(a, char, s.n + 1);
+ memcpy(r, s.s, s.n);
+ r[s.n] = 0;
+ return r;
+}
+
+Str str_from_cstr(const char *s) {
+ return (Str) { (char*)s, strlen(s) };
+}
+
+/* pure functions */
+
+int str_eql(Str a, Str b) {
+ return a.n == b.n && !memcmp(a.s, b.s, b.n);
+}
+
+int str_starts(Str a, Str b) {
+ return a.n >= b.n && !memcmp(a.s, b.s, b.n);
+}
+
+int str_ends(Str a, Str b) {
+ return a.n >= b.n && !memcmp(&a.s[a.n - b.n], b.s, b.n);
+}
+
+void str_catc(Str *a, char b, Arena *m) {
+ a->s = resize(m, a->s, a->n, a->n + 1);
+ a->s[a->n++] = b;
+}
+
+Str str_skip(Str a, ptrdiff_t n) {
+ return (Str) { a.s + n, a.n - n };
+}
+
+int is_space(char c) {
+ return c == ' ' || c == '\t' || c == '\n' || c == '\r';
+}
+
+Str str_trim_left(Str a) {
+ while (a.n > 0 && is_space(a.s[0])) a.s++, a.n--;
+ return a;
+}
+
+Str str_trim_right(Str a) {
+ while (a.n > 0 && is_space(a.s[a.n - 1])) a.n--;
+ return a;
+}
+
+Str str_trim(Str a) {
+ return str_trim_left(str_trim_right(a));
+}
+
+/* splitting, searching */
+
+Cut str_cut(Str s, char c) {
+ char *p = memchr(s.s, c, s.n);
+ if (!p) {
+ return (Cut) { s, { &s.s[s.n], 0 } };
+ } else {
+ return (Cut) {
+ { s.s, p - s.s },
+ { p + 1, &s.s[s.n] - (p + 1) }
+ };
+ }
+}
+
+Cut str_cuts(Str s, Str subs) {
+ Str m = str_find(s, subs);
+ return (Cut) {
+ { s.s, m.s - s.s },
+ m.n > 0 ? str_skip(m, subs.n) : m
+ };
+}
+
+Str str_findc(Str s, char c) {
+ char *p = memchr(s.s, c, s.n);
+ return p ? (Str) { p, s.n - (p - s.s) } : (Str) { &s.s[s.n], 0 };
+}
+
+Str str_find(Str haystack, Str needle) {
+ if (needle.n < 1) return haystack;
+ while (haystack.n > 0) {
+ haystack = str_findc(haystack, needle.s[0]);
+ if (str_starts(haystack, needle)) break;
+ if (haystack.n > 0) haystack = str_skip(haystack, 1);
+ }
+ return haystack;
+}
+
+int str_contains(Str a, Str b) {
+ return str_find(a, b).n > 0;
+}
+
+/* allocating */
+
+Str str_dup(Str a, Arena *m) {
+ char *s = new_arr(m, char, a.n);
+ memcpy(s, a.s, a.n);
+ a.s = s;
+ return a;
+}
+
+void str_cat(Str *a, Str b, Arena *m) {
+ a->s = resize(m, a->s, a->n, a->n + b.n);
+ memcpy(&a->s[a->n], b.s, b.n);
+ a->n += b.n;
+}
+
+Str str_replace_end(Str s, Str a, Str b, Arena *m) {
+ if (!str_ends(s, a)) return s;
+ char *p = new_arr(m, char, s.n + b.n - a.n);
+ memcpy(p, s.s, s.n - a.n);
+ memcpy(p + s.n - a.n, b.s, b.n);
+ return (Str) { p, s.n + b.n - a.n };
+}
+
+#endif
+#endif