From 116c393eb5a770988bf1abf0be8f24d0e29748b1 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Tue, 15 Mar 2016 17:47:59 -0400 Subject: http server: don't assume multipart/x-mixed-replace streams are JPEGs --- .gitignore | 2 +- Makefile | 6 +- freenect-server--http.c | 184 ---------------------------------------- freenect-server.sh | 2 +- mpjpeg.c | 128 ---------------------------- mpjpeg.h | 22 ----- multipart-replace-http-server.c | 184 ++++++++++++++++++++++++++++++++++++++++ multipart-replace.c | 145 +++++++++++++++++++++++++++++++ multipart-replace.h | 22 +++++ 9 files changed, 356 insertions(+), 339 deletions(-) delete mode 100644 freenect-server--http.c delete mode 100644 mpjpeg.c delete mode 100644 mpjpeg.h create mode 100644 multipart-replace-http-server.c create mode 100644 multipart-replace.c create mode 100644 multipart-replace.h diff --git a/.gitignore b/.gitignore index db97f5f..2e01979 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ /freenect-server /freenect-server--kinect -/freenect-server--http +/multipart-replace-http-server *.o .*.mk \ No newline at end of file diff --git a/Makefile b/Makefile index 3a54db5..5ae2d4b 100644 --- a/Makefile +++ b/Makefile @@ -4,14 +4,14 @@ CFLAGS += -g -std=c99 -Wall -Werror -Wextra -pedantic CPPFLAGS += -std=c99 -Wall -Werror -Wextra -pedantic CPPFLAGS += -D_GNU_SOURCE -all: freenect-server freenect-server--kinect freenect-server--http +all: freenect-server freenect-server--kinect multipart-replace-http-server .PHONY: all freenect-server--kinect: util.o -lfreenect -lusb-1.0 -freenect-server--http: util.o mpjpeg.o -lpthread +multipart-replace-http-server: util.o multipart-replace.o -lpthread clean: - rm -f -- *.o .*.mk freenect-server freenect-server--kinect freenect-server--http + rm -f -- *.o .*.mk freenect-server freenect-server--kinect multipart-replace-http-server .PHONY: clean .DELETE_ON_ERROR: diff --git a/freenect-server--http.c b/freenect-server--http.c deleted file mode 100644 index cda7ae1..0000000 --- a/freenect-server--http.c +++ /dev/null @@ -1,184 +0,0 @@ -/* Copyright 2016 Luke Shumaker */ - -#include -#include -#include /* for {get,free}addrinfo() */ -#include /* for EXIT_FAILURE */ -#include -#include -#include -#include /* for open */ -#include /* for open */ - -#include "util.h" -#include "mpjpeg.h" - -struct httpfile { - char name[256]; - struct mpjpeg_stream *stream; -}; - -size_t filec = 0; -struct httpfile *filev = NULL; - -struct reader_thread_args { - struct mpjpeg_stream *stream; - int fd; - const char *boundary; -}; - -void *reader_thread(void *args_anon) { - struct reader_thread_args *args = args_anon; - mpjpeg_reader(args->stream, args->fd, args->boundary); - error(EXIT_FAILURE, 0, "mpjpeg stream ended"); - return NULL; -} - -void start_mpjpeg_reader(struct mpjpeg_stream *s, int fd, const char *boundary) { - struct reader_thread_args *args = xrealloc(NULL, sizeof(struct reader_thread_args)); - args->stream = s; - args->fd = fd; - args->boundary = boundary; - pthread_t thread; - pthread_create(&thread, NULL, reader_thread, args); -} - -void file_add(const char *filename) { - struct mpjpeg_stream *stream = xrealloc(NULL, sizeof(struct mpjpeg_stream)); - init_mpjpeg_stream(stream); - int fd = open(filename, O_RDONLY); - if (fd < 0) - error(EXIT_FAILURE, errno, "opening file: %s", filename); - filev = xrealloc(filev, (++filec)*sizeof(*filev)); - const char *shortname = strrchr(filename, '/'); - if (shortname == NULL) - shortname = filename; - strncpy(filev[filec-1].name, shortname, sizeof(filev[filec-1].name)); - error(0, 0, "added file #%zd: %s", filec-1, filev[filec-1].name); - filev[filec-1].stream = stream; - start_mpjpeg_reader(stream, fd, "ffserver" /* FIXME */); -} - -struct mpjpeg_stream *file_get(const char *filename) { - for (size_t i = 0; i < filec; i++) { - if (strncmp(filename, filev[i].name, sizeof(filev[i].name)) == 0) { - return filev[i].stream; - } - } - return NULL; -} - -int tcp4_parse_listen(const char *port) { - int r; - struct addrinfo *addr = NULL; - struct addrinfo hints = { 0 }; - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_PASSIVE; - - if ((r = getaddrinfo(NULL, port, &hints, &addr)) != 0) - error(EXIT_FAILURE, r, _("Could not resolve TCP4 port %s"), port); - - int sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); - if (sock < 0) - error(EXIT_FAILURE, errno, _("Could not create a TCP4 socket")); - - int yes = 1; - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - - if (bind(sock, addr->ai_addr, addr->ai_addrlen) < 0) - error(EXIT_FAILURE, errno, _("Could not bind to TCP4 port %s"), port); - - if (listen(sock, 5) < 0) - error(EXIT_FAILURE, errno, _("Could not listen on TCP4 port %s"), port); - - freeaddrinfo(addr); - return sock; -} - -void connection_handler(int fd) { - FILE *netstream = fdopen(fd, "r"); - char *line_buf = NULL; - size_t line_cap = 0; - ssize_t line_len = 0; - - line_len = getline(&line_buf, &line_cap, netstream); - /* expect line to be "GET /path.mjpg HTTP/1.1" */ - if (strncmp(line_buf, "GET ", 4) != 0) { - dprintf(fd, - "HTTP/1.1 405 Method Not Allowed\r\n" - "Allow: GET\r\n" - "\r\n"); - close(fd); - return; - } - char *version = &line_buf[4]; - char *path = strsep(&version, " "); - if (strcmp(version, "HTTP/1.1\r\n") != 0 && strcmp(version, "HTTP/1.0\r\n")) { - close(fd); - return; - } - path = strdupa(path); - - /* run through the rest of the headers */ - while (strcmp(line_buf, "\r\n") != 0 && line_len >= 0) - line_len = getline(&line_buf, &line_cap, netstream); - - struct mpjpeg_stream *vidstream = file_get(path); - if (vidstream == NULL) { - error(0, 0, "404 %s", path); - dprintf(fd, - "HTTP/1.1 404 Not Found\r\n" - "\r\n"); - return; - } - - const char *boundary = "boundary" /* FIXME */; - error(0, 0, "200 %s", path); - dprintf(fd, - "HTTP/1.1 200 OK\r\n" - "Content-Type: multipart/x-mixed-replace;boundary=%s\r\n" - "\r\n", - boundary); - mpjpeg_writer(vidstream, fd, boundary); - close(fd); -} - -void *connection_thread(void *arg_anon) { - int fd = (int)(intptr_t)arg_anon; - error(0, 0, "Connection %d opened", fd); - connection_handler(fd); - error(0, 0, "Connection %d closed", fd); - return NULL; -} - -void usage() { - printf("Usage: %s [-h] PORT [FILENAME...]\n", program_invocation_name); -} - -int main(int argc, char *argv[]) { - if (argc < 2) { - dup2(2, 1); - usage(); - return EXIT_FAILURE; - } - if (strcmp(argv[1], "-h") == 0) { - usage(); - return EXIT_SUCCESS; - } - int sock = tcp4_parse_listen(argv[1]); - - for (int i = 2; i < argc; i++) - file_add(argv[i]); - - signal(SIGPIPE, SIG_IGN); - while (1) { - int conn = accept(sock, NULL, NULL); - if (conn < 0) - continue; - pthread_t thread; - pthread_create(&thread, NULL, connection_thread, (void*)(intptr_t)conn); - } - - return 0; -} diff --git a/freenect-server.sh b/freenect-server.sh index 27d3fd3..721243c 100644 --- a/freenect-server.sh +++ b/freenect-server.sh @@ -11,7 +11,7 @@ mkfifo $t/video.mjpg mkfifo $t/depth.mjpg ( freenect-server--kinect -v $t/video.rgb24 -d $t/depth.rgb24; echo "EXITED: freenect-server--kinect: $?") & pids+=($!) -( freenect-server--http 5800 $t/video.mjpg $t/depth.mjpg; echo "EXITED: freenect-server--http: $?") & pids+=($!) +( multipart-replace-http-server 5800 $t/video.mjpg $t/depth.mjpg; echo "EXITED: freenect-server--http: $?") & pids+=($!) rm -f {depth,video}.{avi,mjpg} ( ffmpeg -loglevel warning -pixel_format rgb24 -s 640x480 -f rawvideo -i $t/video.rgb24 -q:v 1 -f mpjpeg - > $t/video.mjpg; echo "EXITED: ffmpeg video: $?") & pids+=($!) ( ffmpeg -loglevel warning -pixel_format rgb24 -s 640x480 -f rawvideo -i $t/depth.rgb24 -q:v 1 -f mpjpeg - > $t/depth.mjpg; echo "EXITED: ffmpeg depth: $?") & pids+=($!) diff --git a/mpjpeg.c b/mpjpeg.c deleted file mode 100644 index 87ca2a4..0000000 --- a/mpjpeg.c +++ /dev/null @@ -1,128 +0,0 @@ -/* Copyright 2016 Luke Shumaker */ - -#include -#include -#include -#include -#include -#include - -#include "mpjpeg.h" -#include "util.h" - -static -char *boundary_line(const char *old) { - size_t len = strlen(old); - char *new = xrealloc(NULL, len+5); - new[0] = '-'; - new[1] = '-'; - strcpy(&new[2], old); - new[2+len+0] = '\r'; - new[2+len+1] = '\n'; - new[2+len+2] = '\0'; - return new; -} - -static -ssize_t safe_atoi(char *str) { - while (*str == ' ') - str++; - size_t len = 0; - while ('0' <= str[len] && str[len] <= '9') - len++; - size_t i = len; - while (str[i] == ' ') - i++; - if (len < 1 || strcmp(&str[i], "\r\n") != 0) - return -1; - return atoi(str); -} - -void mpjpeg_reader(struct mpjpeg_stream *s, int fd, const char *boundary) { - FILE *stream = fdopen(fd, "r"); - boundary = boundary_line(boundary); - - char *line_buf = NULL; - size_t line_cap = 0; - ssize_t line_len = 0; - - while (1) { - s->back->len = -1; - /* scan for the first non-empty line */ - do { - line_len = getline(&line_buf, &line_cap, stream); - } while (line_len >= 0 && strcmp(line_buf, "\r\n") == 0); - /* make sure it matches the boundary separator */ - if (strcmp(line_buf, boundary) != 0) { - error(0, 0, "line does not match boundary: \"%s\"", line_buf); - return; - } - /* read the frame header (MIME headers) */ - while (strcmp(line_buf, "\r\n") != 0 && line_len >= 0) { - line_len = getline(&line_buf, &line_cap, stream); - if (strncasecmp(line_buf, "Content-length:", strlen("Content-length:")) == 0) { - s->back->len = safe_atoi(&line_buf[strlen("Content-length:")]); - } - } - if (s->back->len < 0) { - error(0, 0, "did not get frame length"); - return; - } - - /* read the frame contents (JPEG) */ - if (s->back->cap < (size_t)s->back->len) - s->back->data = xrealloc(s->back->data, s->back->cap = s->back->len); - if (fread(s->back->data, s->back->len, 1, stream) != 1) { - error(0, ferror(stream), "fread(%zd)", s->back->len); - return; - } - - /* swap the frames */ - pthread_rwlock_wrlock(&s->frontlock); - struct frame *tmp = s->front; - s->front = s->back; - s->back = tmp; - s->framecount++; - pthread_rwlock_unlock(&s->frontlock); - } - -} - -void mpjpeg_writer(struct mpjpeg_stream *s, int fd, const char *boundary) { - struct frame myframe = { 0 }; - long lastframe = 0; - pthread_rwlock_rdlock(&s->frontlock); - while(1) { - /* get the most recent frame (copy front to myframe) */ - if (myframe.cap < (size_t)s->front->len) - myframe.data = xrealloc(myframe.data, myframe.cap = s->front->len); - memcpy(myframe.data, s->front->data, myframe.len = s->front->len); - lastframe = s->framecount; - pthread_rwlock_unlock(&s->frontlock); - /* send the frame to the client */ - if (dprintf(fd, "--%s\r\nContent-Type: image/jpeg\r\nContent-Length: %zd\r\n\r\n", boundary, myframe.len) < 0) { - error(0, errno, "dprintf"); - return; - } - if (write(fd, myframe.data, myframe.len) < myframe.len) { - error(0, errno, "write"); - return; - } - - /* poll until there's a new frame */ - pthread_rwlock_rdlock(&s->frontlock); - while (s->framecount == lastframe) { - pthread_rwlock_unlock(&s->frontlock); - usleep(30000); /* a bit over 30 FPS */ - pthread_rwlock_rdlock(&s->frontlock); - } - } -} - -void init_mpjpeg_stream(struct mpjpeg_stream *s) { - ZERO(s->a); - ZERO(s->b); - s->front = &s->a; - s->back = &s->b; - pthread_rwlock_init(&s->frontlock, NULL); -} diff --git a/mpjpeg.h b/mpjpeg.h deleted file mode 100644 index f850adc..0000000 --- a/mpjpeg.h +++ /dev/null @@ -1,22 +0,0 @@ -/* Copyright 2016 Luke Shumaker */ - -#pragma once - -#include - -struct frame { - ssize_t len; - size_t cap; - char *data; -}; - -struct mpjpeg_stream { - struct frame a, b; - struct frame *front, *back; - pthread_rwlock_t frontlock; - long framecount; -}; - -void mpjpeg_reader(struct mpjpeg_stream *s, int fd, const char *boundary); -void mpjpeg_writer(struct mpjpeg_stream *s, int fd, const char *boundary); -void init_mpjpeg_stream(); diff --git a/multipart-replace-http-server.c b/multipart-replace-http-server.c new file mode 100644 index 0000000..c3e6219 --- /dev/null +++ b/multipart-replace-http-server.c @@ -0,0 +1,184 @@ +/* Copyright 2016 Luke Shumaker */ + +#include +#include +#include /* for {get,free}addrinfo() */ +#include /* for EXIT_FAILURE */ +#include +#include +#include +#include /* for open */ +#include /* for open */ + +#include "util.h" +#include "multipart-replace.h" + +struct httpfile { + char name[256]; + struct multipart_replace_stream *stream; +}; + +size_t filec = 0; +struct httpfile *filev = NULL; + +struct reader_thread_args { + struct multipart_replace_stream *stream; + int fd; + const char *boundary; +}; + +void *reader_thread(void *args_anon) { + struct reader_thread_args *args = args_anon; + multipart_replace_reader(args->stream, args->fd, args->boundary); + error(EXIT_FAILURE, 0, "multipart_replace stream ended"); + return NULL; +} + +void start_multipart_replace_reader(struct multipart_replace_stream *s, int fd, const char *boundary) { + struct reader_thread_args *args = xrealloc(NULL, sizeof(struct reader_thread_args)); + args->stream = s; + args->fd = fd; + args->boundary = boundary; + pthread_t thread; + pthread_create(&thread, NULL, reader_thread, args); +} + +void file_add(const char *filename) { + struct multipart_replace_stream *stream = xrealloc(NULL, sizeof(struct multipart_replace_stream)); + init_multipart_replace_stream(stream); + int fd = open(filename, O_RDONLY); + if (fd < 0) + error(EXIT_FAILURE, errno, "opening file: %s", filename); + filev = xrealloc(filev, (++filec)*sizeof(*filev)); + const char *shortname = strrchr(filename, '/'); + if (shortname == NULL) + shortname = filename; + strncpy(filev[filec-1].name, shortname, sizeof(filev[filec-1].name)); + error(0, 0, "added file #%zd: %s", filec-1, filev[filec-1].name); + filev[filec-1].stream = stream; + start_multipart_replace_reader(stream, fd, "ffserver" /* FIXME */); +} + +struct multipart_replace_stream *file_get(const char *filename) { + for (size_t i = 0; i < filec; i++) { + if (strncmp(filename, filev[i].name, sizeof(filev[i].name)) == 0) { + return filev[i].stream; + } + } + return NULL; +} + +int tcp4_parse_listen(const char *port) { + int r; + struct addrinfo *addr = NULL; + struct addrinfo hints = { 0 }; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + if ((r = getaddrinfo(NULL, port, &hints, &addr)) != 0) + error(EXIT_FAILURE, r, _("Could not resolve TCP4 port %s"), port); + + int sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); + if (sock < 0) + error(EXIT_FAILURE, errno, _("Could not create a TCP4 socket")); + + int yes = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + + if (bind(sock, addr->ai_addr, addr->ai_addrlen) < 0) + error(EXIT_FAILURE, errno, _("Could not bind to TCP4 port %s"), port); + + if (listen(sock, 5) < 0) + error(EXIT_FAILURE, errno, _("Could not listen on TCP4 port %s"), port); + + freeaddrinfo(addr); + return sock; +} + +void connection_handler(int fd) { + FILE *netstream = fdopen(fd, "r"); + char *line_buf = NULL; + size_t line_cap = 0; + ssize_t line_len = 0; + + line_len = getline(&line_buf, &line_cap, netstream); + /* expect line to be "GET /path.mjpg HTTP/1.1" */ + if (strncmp(line_buf, "GET ", 4) != 0) { + dprintf(fd, + "HTTP/1.1 405 Method Not Allowed\r\n" + "Allow: GET\r\n" + "\r\n"); + close(fd); + return; + } + char *version = &line_buf[4]; + char *path = strsep(&version, " "); + if (strcmp(version, "HTTP/1.1\r\n") != 0 && strcmp(version, "HTTP/1.0\r\n")) { + close(fd); + return; + } + path = strdupa(path); + + /* run through the rest of the headers */ + while (strcmp(line_buf, "\r\n") != 0 && line_len >= 0) + line_len = getline(&line_buf, &line_cap, netstream); + + struct multipart_replace_stream *vidstream = file_get(path); + if (vidstream == NULL) { + error(0, 0, "404 %s", path); + dprintf(fd, + "HTTP/1.1 404 Not Found\r\n" + "\r\n"); + return; + } + + const char *boundary = "boundary" /* FIXME */; + error(0, 0, "200 %s", path); + dprintf(fd, + "HTTP/1.1 200 OK\r\n" + "Content-Type: multipart/x-mixed-replace;boundary=%s\r\n" + "\r\n", + boundary); + multipart_replace_writer(vidstream, fd, boundary); + close(fd); +} + +void *connection_thread(void *arg_anon) { + int fd = (int)(intptr_t)arg_anon; + error(0, 0, "Connection %d opened", fd); + connection_handler(fd); + error(0, 0, "Connection %d closed", fd); + return NULL; +} + +void usage() { + printf("Usage: %s [-h] PORT [FILENAME...]\n", program_invocation_name); +} + +int main(int argc, char *argv[]) { + if (argc < 2) { + dup2(2, 1); + usage(); + return EXIT_FAILURE; + } + if (strcmp(argv[1], "-h") == 0) { + usage(); + return EXIT_SUCCESS; + } + int sock = tcp4_parse_listen(argv[1]); + + for (int i = 2; i < argc; i++) + file_add(argv[i]); + + signal(SIGPIPE, SIG_IGN); + while (1) { + int conn = accept(sock, NULL, NULL); + if (conn < 0) + continue; + pthread_t thread; + pthread_create(&thread, NULL, connection_thread, (void*)(intptr_t)conn); + } + + return 0; +} diff --git a/multipart-replace.c b/multipart-replace.c new file mode 100644 index 0000000..30ae7bb --- /dev/null +++ b/multipart-replace.c @@ -0,0 +1,145 @@ +/* Copyright 2016 Luke Shumaker */ + +#include +#include +#include +#include +#include +#include + +#include "multipart-replace.h" +#include "util.h" + +static +char *boundary_line(const char *old) { + size_t len = strlen(old); + char *new = xrealloc(NULL, len+5); + new[0] = '-'; + new[1] = '-'; + strcpy(&new[2], old); + new[2+len+0] = '\r'; + new[2+len+1] = '\n'; + new[2+len+2] = '\0'; + return new; +} + +static +ssize_t safe_atoi(char *str) { + while (*str == ' ') + str++; + size_t len = 0; + while ('0' <= str[len] && str[len] <= '9') + len++; + size_t i = len; + while (str[i] == ' ') + i++; + if (len < 1 || strcmp(&str[i], "\r\n") != 0) + return -1; + return atoi(str); +} + +#define rgetline() do { if ((line_len = getline(&line_buf, &line_cap, stream)) < 0) { error(0, 0, "source hung up"); return; } } while(0) +void multipart_replace_reader(struct multipart_replace_stream *s, int fd, const char *boundary) { + FILE *stream = fdopen(fd, "r"); + boundary = boundary_line(boundary); + + char *line_buf = NULL; + size_t line_cap = 0; + ssize_t line_len = 0; + ssize_t content_length = -1; + + while (1) { + content_length = -1; + /* scan for the first non-empty line */ + do { + rgetline(); + } while (strcmp(line_buf, "\r\n") == 0); + /* make sure it matches the boundary separator */ + if (strcmp(line_buf, boundary) != 0) { + error(0, 0, "line does not match boundary: \"%s\"", line_buf); + return; + } + /* read the frame header (MIME headers) */ + do { + rgetline(); + /* append the line to the frame contents */ + if ((ssize_t)s->back->cap < s->back->len + line_len) + s->back->buf = xrealloc(s->back->buf, s->back->cap = s->back->len + line_len); + memcpy(&s->back->buf[s->back->len], line_buf, line_len); + s->back->len += line_len; + /* parse the Content-length (if applicable) */ + if (strncasecmp(line_buf, "Content-length:", strlen("Content-length:")) == 0) { + content_length = safe_atoi(&line_buf[strlen("Content-length:")]); + } + } while (strcmp(line_buf, "\r\n") != 0); + /* make sure that it included a Content-length header */ + if (content_length < 0) { + error(0, 0, "did not get frame length"); + return; + } + /* read the frame contents */ + if ((ssize_t)s->back->cap < s->back->len + content_length) + s->back->buf = xrealloc(s->back->buf, s->back->cap = s->back->len + content_length); + if (fread(s->back->buf, s->back->len, 1, stream) != 1) { + error(0, ferror(stream), "fread(%zd)", s->back->len); + return; + } + s->back->len += content_length; + + /* swap the frames */ + pthread_rwlock_wrlock(&s->frontlock); + struct frame *tmp = s->front; + s->front = s->back; + s->back = tmp; + s->framecount++; + pthread_rwlock_unlock(&s->frontlock); + } +} + +void multipart_replace_writer(struct multipart_replace_stream *s, int fd, const char *boundary) { + struct frame myframe = { 0 }; + long lastframe = 0; + boundary = boundary_line(boundary); + size_t boundary_len = strlen(boundary); + + while(1) { + /* poll until there's a new frame */ + pthread_rwlock_rdlock(&s->frontlock); + while (s->framecount == lastframe) { + pthread_rwlock_unlock(&s->frontlock); + usleep(30000); /* a bit over 30 FPS */ + pthread_rwlock_rdlock(&s->frontlock); + } + + /* get the most recent frame (copy `s->front` to `myframe`) */ + if (myframe.cap < (size_t)s->front->len) + myframe.buf = xrealloc(myframe.buf, myframe.cap = s->front->len); + memcpy(myframe.buf, s->front->buf, myframe.len = s->front->len); + lastframe = s->framecount; + + pthread_rwlock_unlock(&s->frontlock); + + /* send the frame to the client */ + if (write(fd, boundary, boundary_len) < (ssize_t)boundary_len) { + error(0, errno, "write"); + return; + } + if (write(fd, myframe.buf, myframe.len) < myframe.len) { + error(0, errno, "write"); + return; + } + /* send a blank line for pleasantness */ + if (write(fd, "\r\n", 2) < 2) { + error(0, errno, "write"); + return; + } + } +} + +void init_multipart_replace_stream(struct multipart_replace_stream *s) { + ZERO(s->a); + ZERO(s->b); + s->front = &s->a; + s->back = &s->b; + pthread_rwlock_init(&s->frontlock, NULL); +} diff --git a/multipart-replace.h b/multipart-replace.h new file mode 100644 index 0000000..126f6fa --- /dev/null +++ b/multipart-replace.h @@ -0,0 +1,22 @@ +/* Copyright 2016 Luke Shumaker */ + +#pragma once + +#include + +struct frame { + ssize_t len; + size_t cap; + char *buf; +}; + +struct multipart_replace_stream { + struct frame a, b; + struct frame *front, *back; + pthread_rwlock_t frontlock; + long framecount; +}; + +void multipart_replace_reader(struct multipart_replace_stream *s, int fd, const char *boundary); +void multipart_replace_writer(struct multipart_replace_stream *s, int fd, const char *boundary); +void init_multipart_replace_stream(); -- cgit v1.2.3-54-g00ecf