Make the serving process interruptible - quark - quark web server | |
git clone git://git.suckless.org/quark | |
Log | |
Files | |
Refs | |
LICENSE | |
--- | |
commit 2714819dfc639098d0531eb3d4f0f5f23708059a | |
parent 0823ba4c3e480fb5e2c246b8ac6c4783d866ab87 | |
Author: Laslo Hunhold <[email protected]> | |
Date: Mon, 14 Sep 2020 13:45:24 +0200 | |
Make the serving process interruptible | |
Ever since I joined suckless and found out that there had been an | |
(inofficial and cancelled) effort to turn quark into a polling-webserver | |
(instead of a forking-webserver), I was intrigued to pick up the task | |
and make it happen. | |
Back then, my C skills weren't nearly as good, and I had no hopes of | |
making it possible. Now, this commit marks a major step towards this | |
goal. | |
Given the static nature of quark, I wanted to try something out that | |
is not really possible with a "dynamic" server: Making the serving | |
process interruptible in constant memory (except dir-listings of | |
course). This can easily be extended to a polling architecture later | |
on, but it most importantly warrants a non-blocking I/O scheme and | |
makes the server more or less immune to sloth attacks (i.e. clients | |
sending requests very slowly), and provides a more flexible approach to | |
connections. Any thread can pick up a connection and continue work on | |
it, without requiring a separate process for each (which might hit the | |
forking limit at some point). If we hit a point where all connections | |
are busy (due to many sloth attacks), one can apply arbitrary complex | |
logic to "cancel" connections that show malicious behaviour (e.g. taking | |
a long time to send the request header, etc.). | |
The following aspects were added/changed to introduce the | |
interruptibility. | |
- Define a general purpose "buffer" struct with a buffer_appendf() | |
utility function. | |
- Change http_send_header() to http_prepare_header_buf() and separate | |
the sending part into a general-purpose function http_send_buf(). | |
- Modify the data_* functions to be based on a progress and operate | |
on buffers. This way, we can indefinitely "interrupt" request | |
serving and always "pick up" where we left off. | |
- Refactor http_recv_header() to operate on the buffer struct instead | |
of "raw" parameters. | |
- Refactor serve() in main.c accordingly. | |
- Introduce BUFFER_SIZE in config.h, which controls the buffer size each | |
connection has. | |
- Refactor Makefile dependencies and employ strict first-level-header- | |
usage (i.e. we explicitly specify what we use with includes in each | |
compilation unit, so make(1) can figure the dependencies out; most | |
prominently, this moves the arg.h-include into main.c, and requires | |
ifdef-guards for config.h). | |
Signed-off-by: Laslo Hunhold <[email protected]> | |
Diffstat: | |
M Makefile | 6 +++--- | |
M config.def.h | 9 +++++++-- | |
M data.c | 131 ++++++++++++++++++-----------… | |
M data.h | 13 ++++++++++--- | |
M http.c | 120 ++++++++++++++++++-----------… | |
M http.h | 18 ++++++------------ | |
M main.c | 61 ++++++++++++++++++++++++++---… | |
M util.c | 24 ++++++++++++++++++++++++ | |
M util.h | 11 ++++++++++- | |
9 files changed, 260 insertions(+), 133 deletions(-) | |
--- | |
diff --git a/Makefile b/Makefile | |
@@ -8,9 +8,9 @@ COMPONENTS = data http sock util | |
all: quark | |
-data.o: data.c data.h util.h http.h config.mk | |
-http.o: http.c http.h util.h http.h data.h config.h config.mk | |
-main.o: main.c util.h sock.h http.h arg.h config.h config.mk | |
+data.o: data.c data.h http.h util.h config.mk | |
+http.o: http.c config.h http.h util.h config.mk | |
+main.o: main.c arg.h data.h http.h sock.h util.h config.mk | |
sock.o: sock.c sock.h util.h config.mk | |
util.o: util.c util.h config.mk | |
diff --git a/config.def.h b/config.def.h | |
@@ -1,5 +1,8 @@ | |
-#define HEADER_MAX 4096 | |
-#define FIELD_MAX 200 | |
+#ifndef CONFIG_H | |
+#define CONFIG_H | |
+ | |
+#define BUFFER_SIZE 4096 | |
+#define FIELD_MAX 200 | |
/* mime-types */ | |
static const struct { | |
@@ -32,3 +35,5 @@ static const struct { | |
{ "ogv", "video/ogg" }, | |
{ "webm", "video/webm" }, | |
}; | |
+ | |
+#endif /* CONFIG_H */ | |
diff --git a/data.c b/data.c | |
@@ -7,10 +7,17 @@ | |
#include <time.h> | |
#include <unistd.h> | |
-#include "http.h" | |
#include "data.h" | |
+#include "http.h" | |
#include "util.h" | |
+enum status (* const data_fct[])(const struct response *, | |
+ struct buffer *, size_t *) = { | |
+ [RESTYPE_ERROR] = data_prepare_error_buf, | |
+ [RESTYPE_FILE] = data_prepare_file_buf, | |
+ [RESTYPE_DIRLISTING] = data_prepare_dirlisting_buf, | |
+}; | |
+ | |
static int | |
compareent(const struct dirent **d1, const struct dirent **d2) | |
{ | |
@@ -84,7 +91,8 @@ html_escape(const char *src, char *dst, size_t dst_siz) | |
} | |
enum status | |
-data_send_dirlisting(int fd, const struct response *res) | |
+data_prepare_dirlisting_buf(const struct response *res, | |
+ struct buffer *buf, size_t *progress) | |
{ | |
enum status ret = 0; | |
struct dirent **e; | |
@@ -92,24 +100,29 @@ data_send_dirlisting(int fd, const struct response *res) | |
int dirlen; | |
char esc[PATH_MAX /* > NAME_MAX */ * 6]; /* strlen("&...;") <= 6 */ | |
+ /* reset buffer */ | |
+ memset(buf, 0, sizeof(*buf)); | |
+ | |
/* read directory */ | |
if ((dirlen = scandir(res->path, &e, NULL, compareent)) < 0) { | |
return S_FORBIDDEN; | |
} | |
- /* listing header (we use esc because sizeof(esc) >= PATH_MAX) */ | |
- html_escape(res->uri, esc, MIN(PATH_MAX, sizeof(esc))); | |
- if (dprintf(fd, | |
- "<!DOCTYPE html>\n<html>\n\t<head>" | |
- "<title>Index of %s</title></head>\n" | |
- "\t<body>\n\t\t<a href=\"..\">..</a>", | |
- esc) < 0) { | |
- ret = S_REQUEST_TIMEOUT; | |
- goto cleanup; | |
+ if (*progress == 0) { | |
+ /* write listing header (sizeof(esc) >= PATH_MAX) */ | |
+ html_escape(res->uri, esc, MIN(PATH_MAX, sizeof(esc))); | |
+ if (buffer_appendf(buf, | |
+ "<!DOCTYPE html>\n<html>\n\t<head>" | |
+ "<title>Index of %s</title></head>\n" | |
+ "\t<body>\n\t\t<a href=\"..\">..</a>", | |
+ esc) < 0) { | |
+ ret = S_REQUEST_TIMEOUT; | |
+ goto cleanup; | |
+ } | |
} | |
- /* listing */ | |
- for (i = 0; i < (size_t)dirlen; i++) { | |
+ /* listing entries */ | |
+ for (i = *progress; i < (size_t)dirlen; i++) { | |
/* skip hidden files, "." and ".." */ | |
if (e[i]->d_name[0] == '.') { | |
continue; | |
@@ -117,20 +130,25 @@ data_send_dirlisting(int fd, const struct response *res) | |
/* entry line */ | |
html_escape(e[i]->d_name, esc, sizeof(esc)); | |
- if (dprintf(fd, "<br />\n\t\t<a href=\"%s%s\">%s%s</a>", | |
- esc, | |
- (e[i]->d_type == DT_DIR) ? "/" : "", | |
- esc, | |
- suffix(e[i]->d_type)) < 0) { | |
- ret = S_REQUEST_TIMEOUT; | |
- goto cleanup; | |
+ if (buffer_appendf(buf, | |
+ "<br />\n\t\t<a href=\"%s%s\">%s%s</a>", | |
+ esc, | |
+ (e[i]->d_type == DT_DIR) ? "/" : "", | |
+ esc, | |
+ suffix(e[i]->d_type))) { | |
+ /* buffer full */ | |
+ break; | |
} | |
} | |
+ *progress = i; | |
- /* listing footer */ | |
- if (dprintf(fd, "\n\t</body>\n</html>\n") < 0) { | |
- ret = S_REQUEST_TIMEOUT; | |
- goto cleanup; | |
+ if (*progress == (size_t)dirlen) { | |
+ /* listing footer */ | |
+ if (buffer_appendf(buf, "\n\t</body>\n</html>\n") < 0) { | |
+ ret = S_REQUEST_TIMEOUT; | |
+ goto cleanup; | |
+ } | |
+ (*progress)++; | |
} | |
cleanup: | |
@@ -143,28 +161,40 @@ cleanup: | |
} | |
enum status | |
-data_send_error(int fd, const struct response *res) | |
+data_prepare_error_buf(const struct response *res, struct buffer *buf, | |
+ size_t *progress) | |
{ | |
- if (dprintf(fd, | |
- "<!DOCTYPE html>\n<html>\n\t<head>\n" | |
- "\t\t<title>%d %s</title>\n\t</head>\n\t<body>\n" | |
- "\t\t<h1>%d %s</h1>\n\t</body>\n</html>\n", | |
- res->status, status_str[res->status], | |
- res->status, status_str[res->status]) < 0) { | |
- return S_REQUEST_TIMEOUT; | |
+ /* reset buffer */ | |
+ memset(buf, 0, sizeof(*buf)); | |
+ | |
+ if (*progress == 0) { | |
+ /* write error body */ | |
+ if (buffer_appendf(buf, | |
+ "<!DOCTYPE html>\n<html>\n\t<head>\n" | |
+ "\t\t<title>%d %s</title>\n\t</head>\n" | |
+ "\t<body>\n\t\t<h1>%d %s</h1>\n" | |
+ "\t</body>\n</html>\n", | |
+ res->status, status_str[res->status], | |
+ res->status, status_str[res->status])) { | |
+ return S_INTERNAL_SERVER_ERROR; | |
+ } | |
+ (*progress)++; | |
} | |
return 0; | |
} | |
enum status | |
-data_send_file(int fd, const struct response *res) | |
+data_prepare_file_buf(const struct response *res, struct buffer *buf, | |
+ size_t *progress) | |
{ | |
FILE *fp; | |
enum status ret = 0; | |
- ssize_t bread, bwritten; | |
+ ssize_t r; | |
size_t remaining; | |
- static char buf[BUFSIZ], *p; | |
+ | |
+ /* reset buffer */ | |
+ memset(buf, 0, sizeof(*buf)); | |
/* open file */ | |
if (!(fp = fopen(res->path, "r"))) { | |
@@ -172,33 +202,26 @@ data_send_file(int fd, const struct response *res) | |
goto cleanup; | |
} | |
- /* seek to lower bound */ | |
- if (fseek(fp, res->file.lower, SEEK_SET)) { | |
+ /* seek to lower bound + progress */ | |
+ if (fseek(fp, res->file.lower + *progress, SEEK_SET)) { | |
ret = S_INTERNAL_SERVER_ERROR; | |
goto cleanup; | |
} | |
- /* write data until upper bound is hit */ | |
- remaining = res->file.upper - res->file.lower + 1; | |
- | |
- while ((bread = fread(buf, 1, MIN(sizeof(buf), | |
- remaining), fp))) { | |
- if (bread < 0) { | |
+ /* read data into buf */ | |
+ remaining = res->file.upper - res->file.lower + 1 - *progress; | |
+ while ((r = fread(buf->data + buf->len, 1, | |
+ MIN(sizeof(buf->data) - buf->len, | |
+ remaining), fp))) { | |
+ if (r < 0) { | |
ret = S_INTERNAL_SERVER_ERROR; | |
goto cleanup; | |
} | |
- remaining -= bread; | |
- p = buf; | |
- while (bread > 0) { | |
- bwritten = write(fd, p, bread); | |
- if (bwritten <= 0) { | |
- ret = S_REQUEST_TIMEOUT; | |
- goto cleanup; | |
- } | |
- bread -= bwritten; | |
- p += bwritten; | |
- } | |
+ buf->len += r; | |
+ *progress += r; | |
+ remaining -= r; | |
} | |
+ | |
cleanup: | |
if (fp) { | |
fclose(fp); | |
diff --git a/data.h b/data.h | |
@@ -3,9 +3,16 @@ | |
#define DATA_H | |
#include "http.h" | |
+#include "util.h" | |
-enum status data_send_dirlisting(int, const struct response *); | |
-enum status data_send_error(int, const struct response *); | |
-enum status data_send_file(int, const struct response *); | |
+extern enum status (* const data_fct[])(const struct response *, | |
+ struct buffer *, size_t *); | |
+ | |
+enum status data_prepare_dirlisting_buf(const struct response *, | |
+ struct buffer *, size_t *); | |
+enum status data_prepare_error_buf(const struct response *, | |
+ struct buffer *, size_t *); | |
+enum status data_prepare_file_buf(const struct response *, | |
+ struct buffer *, size_t *); | |
#endif /* DATA_H */ | |
diff --git a/http.c b/http.c | |
@@ -17,7 +17,6 @@ | |
#include <unistd.h> | |
#include "config.h" | |
-#include "data.h" | |
#include "http.h" | |
#include "util.h" | |
@@ -58,43 +57,69 @@ const char *res_field_str[] = { | |
[RES_CONTENT_TYPE] = "Content-Type", | |
}; | |
-enum status (* const body_fct[])(int, const struct response *) = { | |
- [RESTYPE_ERROR] = data_send_error, | |
- [RESTYPE_FILE] = data_send_file, | |
- [RESTYPE_DIRLISTING] = data_send_dirlisting, | |
-}; | |
- | |
enum status | |
-http_send_header(int fd, const struct response *res) | |
+http_prepare_header_buf(const struct response *res, struct buffer *buf) | |
{ | |
- char t[FIELD_MAX]; | |
+ char tstmp[FIELD_MAX]; | |
size_t i; | |
- if (timestamp(t, sizeof(t), time(NULL))) { | |
- return S_INTERNAL_SERVER_ERROR; | |
+ /* reset buffer */ | |
+ memset(buf, 0, sizeof(*buf)); | |
+ | |
+ /* generate timestamp */ | |
+ if (timestamp(tstmp, sizeof(tstmp), time(NULL))) { | |
+ goto err; | |
} | |
- if (dprintf(fd, | |
- "HTTP/1.1 %d %s\r\n" | |
- "Date: %s\r\n" | |
- "Connection: close\r\n", | |
- res->status, status_str[res->status], t) < 0) { | |
- return S_REQUEST_TIMEOUT; | |
+ /* write data */ | |
+ if (buffer_appendf(buf, | |
+ "HTTP/1.1 %d %s\r\n" | |
+ "Date: %s\r\n" | |
+ "Connection: close\r\n", | |
+ res->status, status_str[res->status], tstmp)) { | |
+ goto err; | |
} | |
for (i = 0; i < NUM_RES_FIELDS; i++) { | |
- if (res->field[i][0] != '\0') { | |
- if (dprintf(fd, "%s: %s\r\n", res_field_str[i], | |
- res->field[i]) < 0) { | |
- return S_REQUEST_TIMEOUT; | |
- } | |
+ if (res->field[i][0] != '\0' && | |
+ buffer_appendf(buf, "%s: %s\r\n", res_field_str[i], | |
+ res->field[i])) { | |
+ goto err; | |
} | |
} | |
- if (dprintf(fd, "\r\n") < 0) { | |
- return S_REQUEST_TIMEOUT; | |
+ if (buffer_appendf(buf, "\r\n")) { | |
+ goto err; | |
+ } | |
+ | |
+ return 0; | |
+err: | |
+ memset(buf, 0, sizeof(*buf)); | |
+ return S_INTERNAL_SERVER_ERROR; | |
+} | |
+ | |
+enum status | |
+http_send_buf(int fd, struct buffer *buf) | |
+{ | |
+ size_t remaining; | |
+ ssize_t r; | |
+ | |
+ if (buf == NULL || buf->off > sizeof(buf->data)) { | |
+ return S_INTERNAL_SERVER_ERROR; | |
+ } | |
+ | |
+ remaining = buf->len - buf->off; | |
+ while (remaining > 0) { | |
+ if ((r = write(fd, buf->data + buf->off, remaining)) <= 0) { | |
+ return S_REQUEST_TIMEOUT; | |
+ } | |
+ buf->off += r; | |
+ remaining -= r; | |
} | |
+ /* set off to 0 to indicate that we have finished */ | |
+ buf->off = 0; | |
+ | |
return 0; | |
} | |
@@ -117,38 +142,48 @@ decode(const char src[PATH_MAX], char dest[PATH_MAX]) | |
} | |
enum status | |
-http_recv_header(int fd, char *h, size_t hsiz, size_t *off) | |
+http_recv_header(int fd, struct buffer *buf) | |
{ | |
+ enum status s; | |
ssize_t r; | |
- if (h == NULL || off == NULL || *off > hsiz) { | |
- return S_INTERNAL_SERVER_ERROR; | |
+ if (buf->off > sizeof(buf->data)) { | |
+ s = S_INTERNAL_SERVER_ERROR; | |
+ goto err; | |
} | |
while (1) { | |
- if ((r = read(fd, h + *off, hsiz - *off)) <= 0) { | |
- return S_REQUEST_TIMEOUT; | |
+ if ((r = read(fd, buf->data + buf->off, | |
+ sizeof(buf->data) - buf->off)) <= 0) { | |
+ s = S_REQUEST_TIMEOUT; | |
+ goto err; | |
} | |
- *off += r; | |
+ buf->off += r; | |
/* check if we are done (header terminated) */ | |
- if (*off >= 4 && !memcmp(h + *off - 4, "\r\n\r\n", 4)) { | |
+ if (buf->off >= 4 && !memcmp(buf->data + buf->off - 4, | |
+ "\r\n\r\n", 4)) { | |
break; | |
} | |
/* buffer is full or read over, but header is not terminated */ | |
- if (r == 0 || *off == hsiz) { | |
- return S_REQUEST_TOO_LARGE; | |
+ if (r == 0 || buf->off == sizeof(buf->data)) { | |
+ s = S_REQUEST_TOO_LARGE; | |
+ goto err; | |
} | |
} | |
/* header is complete, remove last \r\n and null-terminate */ | |
- h[*off - 2] = '\0'; | |
+ buf->data[buf->off - 2] = '\0'; | |
- /* set *off to 0 to indicate we are finished */ | |
- *off = 0; | |
+ /* set buffer length to length and offset to 0 to indicate success */ | |
+ buf->len = buf->off - 2; | |
+ buf->off = 0; | |
return 0; | |
+err: | |
+ memset(buf, 0, sizeof(*buf)); | |
+ return s; | |
} | |
enum status | |
@@ -840,16 +875,3 @@ http_prepare_error_response(const struct request *req, | |
} | |
} | |
} | |
- | |
-enum status | |
-http_send_body(int fd, const struct response *res, | |
- const struct request *req) | |
-{ | |
- enum status s; | |
- | |
- if (req->method == M_GET && (s = body_fct[res->type](fd, res))) { | |
- return s; | |
- } | |
- | |
- return 0; | |
-} | |
diff --git a/http.h b/http.h | |
@@ -5,11 +5,9 @@ | |
#include <limits.h> | |
#include <sys/socket.h> | |
+#include "config.h" | |
#include "util.h" | |
-#define HEADER_MAX 4096 | |
-#define FIELD_MAX 200 | |
- | |
enum req_field { | |
REQ_HOST, | |
REQ_RANGE, | |
@@ -83,8 +81,6 @@ struct response { | |
} file; | |
}; | |
-extern enum status (* const body_fct[])(int, const struct response *); | |
- | |
enum conn_state { | |
C_VACANT, | |
C_RECV_HEADER, | |
@@ -97,21 +93,19 @@ struct connection { | |
enum conn_state state; | |
int fd; | |
struct sockaddr_storage ia; | |
- char header[HEADER_MAX]; /* general req/res-header buffer */ | |
- size_t off; /* general offset (header/file/dir) */ | |
struct request req; | |
struct response res; | |
+ struct buffer buf; | |
+ size_t progress; | |
}; | |
-enum status http_send_header(int, const struct response *); | |
-enum status http_send_status(int, enum status); | |
-enum status http_recv_header(int, char *, size_t, size_t *); | |
+enum status http_prepare_header_buf(const struct response *, struct buffer *); | |
+enum status http_send_buf(int, struct buffer *); | |
+enum status http_recv_header(int, struct buffer *); | |
enum status http_parse_header(const char *, struct request *); | |
void http_prepare_response(const struct request *, struct response *, | |
const struct server *); | |
void http_prepare_error_response(const struct request *, | |
struct response *, enum status); | |
-enum status http_send_body(int, const struct response *, | |
- const struct request *); | |
#endif /* HTTP_H */ | |
diff --git a/main.c b/main.c | |
@@ -16,6 +16,7 @@ | |
#include <time.h> | |
#include <unistd.h> | |
+#include "arg.h" | |
#include "data.h" | |
#include "http.h" | |
#include "sock.h" | |
@@ -53,24 +54,66 @@ serve(struct connection *c, const struct server *srv) | |
/* set connection timeout */ | |
if (sock_set_timeout(c->fd, 30)) { | |
- goto cleanup; | |
+ warn("sock_set_timeout: Failed"); | |
} | |
- /* handle request */ | |
- if ((s = http_recv_header(c->fd, c->header, LEN(c->header), &c->off)) … | |
- (s = http_parse_header(c->header, &c->req))) { | |
+ /* read header */ | |
+ memset(&c->buf, 0, sizeof(c->buf)); | |
+ if ((s = http_recv_header(c->fd, &c->buf))) { | |
http_prepare_error_response(&c->req, &c->res, s); | |
- } else { | |
- http_prepare_response(&c->req, &c->res, srv); | |
+ goto response; | |
} | |
- if ((s = http_send_header(c->fd, &c->res)) || | |
- (s = http_send_body(c->fd, &c->res, &c->req))) { | |
+ /* parse header */ | |
+ if ((s = http_parse_header(c->buf.data, &c->req))) { | |
+ http_prepare_error_response(&c->req, &c->res, s); | |
+ goto response; | |
+ } | |
+ | |
+ /* prepare response struct */ | |
+ http_prepare_response(&c->req, &c->res, srv); | |
+ | |
+response: | |
+ /* generate response header */ | |
+ if ((s = http_prepare_header_buf(&c->res, &c->buf))) { | |
+ http_prepare_error_response(&c->req, &c->res, s); | |
+ if ((s = http_prepare_header_buf(&c->res, &c->buf))) { | |
+ /* couldn't generate the header, we failed for good */ | |
+ c->res.status = s; | |
+ goto err; | |
+ } | |
+ } | |
+ | |
+ /* send header */ | |
+ if ((s = http_send_buf(c->fd, &c->buf))) { | |
c->res.status = s; | |
+ goto err; | |
} | |
+ /* send body */ | |
+ if (c->req.method == M_GET) { | |
+ for (;;) { | |
+ /* fill buffer with body data */ | |
+ if ((s = data_fct[c->res.type](&c->res, &c->buf, | |
+ &c->progress))) { | |
+ c->res.status = s; | |
+ goto err; | |
+ } | |
+ | |
+ /* if done, exit loop */ | |
+ if (c->buf.len == 0) { | |
+ break; | |
+ } | |
+ | |
+ /* send buffer */ | |
+ if ((s = http_send_buf(c->fd, &c->buf))) { | |
+ c->res.status = s; | |
+ } | |
+ } | |
+ } | |
+err: | |
logmsg(c); | |
-cleanup: | |
+ | |
/* clean up and finish */ | |
shutdown(c->fd, SHUT_RD); | |
shutdown(c->fd, SHUT_WR); | |
diff --git a/util.c b/util.c | |
@@ -182,3 +182,27 @@ reallocarray(void *optr, size_t nmemb, size_t size) | |
} | |
return realloc(optr, size * nmemb); | |
} | |
+ | |
+int | |
+buffer_appendf(struct buffer *buf, const char *suffixfmt, ...) | |
+{ | |
+ va_list ap; | |
+ int ret; | |
+ | |
+ va_start(ap, suffixfmt); | |
+ ret = vsnprintf(buf->data + buf->len, | |
+ sizeof(buf->data) - buf->len, suffixfmt, ap); | |
+ va_end(ap); | |
+ | |
+ if (ret < 0 || (size_t)ret >= (sizeof(buf->data) - buf->len)) { | |
+ /* truncation occured, discard and error out */ | |
+ memset(buf->data + buf->len, 0, | |
+ sizeof(buf->data) - buf->len); | |
+ return 1; | |
+ } | |
+ | |
+ /* increase buffer length by number of bytes written */ | |
+ buf->len += ret; | |
+ | |
+ return 0; | |
+} | |
diff --git a/util.h b/util.h | |
@@ -6,7 +6,7 @@ | |
#include <stddef.h> | |
#include <time.h> | |
-#include "arg.h" | |
+#include "config.h" | |
/* main server struct */ | |
struct vhost { | |
@@ -34,6 +34,13 @@ struct server { | |
size_t map_len; | |
}; | |
+/* general purpose buffer */ | |
+struct buffer { | |
+ char data[BUFFER_SIZE]; | |
+ size_t len; | |
+ size_t off; | |
+}; | |
+ | |
#undef MIN | |
#define MIN(x,y) ((x) < (y) ? (x) : (y)) | |
#undef MAX | |
@@ -56,4 +63,6 @@ int prepend(char *, size_t, const char *); | |
void *reallocarray(void *, size_t, size_t); | |
long long strtonum(const char *, long long, long long, const char **); | |
+int buffer_appendf(struct buffer *, const char *, ...); | |
+ | |
#endif /* UTIL_H */ |