From 41bfc4bb5e1042ec9eca48ed645393b12978d703 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Fri, 8 Apr 2016 17:00:05 -0400 Subject: Actual install process --- .gitignore | 8 +- Makefile | 41 +++- config.mk | 7 + freenect-server-http.service | 15 -- freenect-server-http.socket | 8 - freenect-server.c | 289 ------------------------- freenect-server.service | 12 -- freenect-server@.socket | 10 - multipart-replace-http-server.c | 367 -------------------------------- multipart-replace.c | 168 --------------- multipart-replace.h | 26 --- src/.gitignore | 5 + src/freenect-server.c | 289 +++++++++++++++++++++++++ src/multipart-replace-http-server.c | 367 ++++++++++++++++++++++++++++++++ src/multipart-replace.c | 168 +++++++++++++++ src/multipart-replace.h | 26 +++ src/util.c | 72 +++++++ src/util.h | 21 ++ src/wg.c | 45 ++++ src/wg.h | 19 ++ systemd/.gitignore | 2 + systemd/freenect-server-http.service.in | 15 ++ systemd/freenect-server-http.socket.in | 8 + systemd/freenect-server.service.in | 12 ++ systemd/freenect-server@.socket.in | 10 + util.c | 72 ------- util.h | 21 -- wg.c | 45 ---- wg.h | 19 -- write-ifchanged | 11 + 30 files changed, 1114 insertions(+), 1064 deletions(-) create mode 100644 config.mk delete mode 100644 freenect-server-http.service delete mode 100644 freenect-server-http.socket delete mode 100644 freenect-server.c delete mode 100644 freenect-server.service delete mode 100644 freenect-server@.socket delete mode 100644 multipart-replace-http-server.c delete mode 100644 multipart-replace.c delete mode 100644 multipart-replace.h create mode 100644 src/.gitignore create mode 100644 src/freenect-server.c create mode 100644 src/multipart-replace-http-server.c create mode 100644 src/multipart-replace.c create mode 100644 src/multipart-replace.h create mode 100644 src/util.c create mode 100644 src/util.h create mode 100644 src/wg.c create mode 100644 src/wg.h create mode 100644 systemd/.gitignore create mode 100644 systemd/freenect-server-http.service.in create mode 100644 systemd/freenect-server-http.socket.in create mode 100644 systemd/freenect-server.service.in create mode 100644 systemd/freenect-server@.socket.in delete mode 100644 util.c delete mode 100644 util.h delete mode 100644 wg.c delete mode 100644 wg.h create mode 100755 write-ifchanged diff --git a/.gitignore b/.gitignore index 52ef0a6..cc6f0bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,2 @@ -/freenect-server -/freenect-server--kinect -/multipart-replace-http-server - -*.o -.*.mk +.var.* +.tmp.* diff --git a/Makefile b/Makefile index 9b0bf86..bfa2b30 100644 --- a/Makefile +++ b/Makefile @@ -20,18 +20,47 @@ CFLAGS += -fstack-protector CFLAGS += -O2 CPPFLAGS += -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -all: freenect-server multipart-replace-http-server +all: build .PHONY: all -freenect-server: util.o -lfreenect -lusb-1.0 -ljpeg -multipart-replace-http-server: util.o wg.o multipart-replace.o -lpthread +include config.mk + +progs = freenect-server multipart-replace-http-server +units = freenect-server-http.service freenect-server-http.socket freenect-server.service freenect-server@.socket + +build: $(addprefix src/,$(progs)) $(addprefix systemd/,$(units)) +.PHONY: build + +src/freenect-server: src/util.o -lfreenect -lusb-1.0 -ljpeg +src/multipart-replace-http-server: src/util.o src/wg.o src/multipart-replace.o -lpthread +systemd/freenect-server-http.service: .var.user .var.group .var.bindir +systemd/freenect-server-http.socket: .var.httpstream +systemd/freenect-server.service: .var.user .var.group .var.bindir +systemd/freenect-server@.socket: .var.user .var.group + +systemd/%: systemd/%.in + sed $(foreach v,$(patsubst .var.%,%,$(filter .var.%,$^)), -e 's|@$v@|$($v)|' ) < $< > $@ + +.var.%: FORCE + @printf '%s' '$($*)' | ./write-ifchanged $@ + +install: $(addprefix $(DESTDIR)$(bindir)/,$(progs)) $(addprefix $(DESTDIR)$(systemddir)/,$(units)) + +$(DESTDIR)$(bindir)/%: src/% + install -Dm755 $< $@ + +$(DESTDIR)$(systemddir)/%: systemd/% + install -Dm644 $< $@ clean: - rm -f -- *.o .*.mk freenect-server freenect-server--kinect multipart-replace-http-server + rm -f -- src/*.o src/.*.mk + rm -f -- src/freenect-server src/multipart-replace-http-server + rm -f -- systemd/*.socket systemd/*.service .PHONY: clean +.PHONY: FORCE .DELETE_ON_ERROR: .SECONDARY: -CPPFLAGS += -MD -MF ${@:%.o=.%.mk} -MP --include .*.mk +CPPFLAGS += -MD -MF $(@D)/$(@F:%.o=.%.mk) -MP +-include src/.*.mk diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..7f6f673 --- /dev/null +++ b/config.mk @@ -0,0 +1,7 @@ +DESTDIR = +prefix = /usr/local +bindir = $(prefix)/bin +systemddir = $(prefix)/lib/systemd/system +user = alarm +group = alarm +httpstream=5800 diff --git a/freenect-server-http.service b/freenect-server-http.service deleted file mode 100644 index 6b4d91b..0000000 --- a/freenect-server-http.service +++ /dev/null @@ -1,15 +0,0 @@ -[Unit] -Description=Kinect HTTP media streamer -After=network.target -Requires=freenect-server-http.socket freenect-server.service -PartOf=freenect-server.service -After=freenect-server.service - -[Service] -Type=simple -User=alarm -Group=alarm -ExecStart=/home/alarm/freenect-server/multipart-replace-http-server fd systemd /run/freenect-server/video.mjpg /run/freenect-server/depth.mjpg /run/freenect-server/accel.mjson - -[Install] -WantedBy=multi-user.target diff --git a/freenect-server-http.socket b/freenect-server-http.socket deleted file mode 100644 index 8c54b10..0000000 --- a/freenect-server-http.socket +++ /dev/null @@ -1,8 +0,0 @@ -[Unit] -Description=Kinect HTTP media streamer socket - -[Socket] -ListenStream=5800 - -[Install] -WantedBy=sockets.target diff --git a/freenect-server.c b/freenect-server.c deleted file mode 100644 index c5ed99a..0000000 --- a/freenect-server.c +++ /dev/null @@ -1,289 +0,0 @@ -/* - * This file is part of the OpenKinect Project. http://www.openkinect.org - * - * Copyright (c) 2010 Brandyn White (bwhite@dappervision.com) - * Copyright (c) 2016 Luke Shumaker (lukeshu@sbcglobal.net) - * - * This code is licensed to you under the terms of the Apache License, version - * 2.0, or, at your option, the terms of the GNU General Public License, - * version 2.0. See the APACHE20 and GPL2 files for the text of the licenses, - * or the following URLs: - * http://www.apache.org/licenses/LICENSE-2.0 - * http://www.gnu.org/licenses/gpl-2.0.txt - * - * If you redistribute this file in source form, modified or unmodified, you - * may: - * 1) Leave this header intact and distribute it under the same terms, - * accompanying it with the APACHE20 and GPL20 files, or - * 2) Delete the Apache 2.0 clause and accompany it with the GPL2 file, or - * 3) Delete the GPL v2 clause and accompany it with the APACHE20 file - * In all cases you must keep the copyright notice intact and include a copy - * of the CONTRIB file. - * - * Binary distributions must follow the binary distribution requirements of - * either License. - */ -#include -#include -#include -#include -#include /* atexit */ -#include /* dup2 */ - -#include -#include - -#include - -#include "util.h" - -FILE *depth_stream = NULL; -FILE *video_stream = NULL; -FILE *accel_stream = NULL; -freenect_context *ctx; - -struct jpeg_error_mgr jpeg_encoder_err; -struct jpeg_compress_struct jpeg_encoder; -unsigned char *jpeg_encoder_buf; -unsigned long jpeg_encoder_len; - -void jpeg_init() { - jpeg_encoder.err = jpeg_std_error(&jpeg_encoder_err); - jpeg_create_compress(&jpeg_encoder); - jpeg_encoder.image_width = 640; - jpeg_encoder.image_height = 480; - jpeg_encoder.input_components = 3; - jpeg_encoder.in_color_space = JCS_RGB; - jpeg_set_defaults(&jpeg_encoder); - jpeg_mem_dest(&jpeg_encoder, &jpeg_encoder_buf, &jpeg_encoder_len); -} - -void write_imageframe(FILE *stream, JSAMPLE *data) { - JSAMPLE *row_pointers[480]; - for (size_t i = 0; i < 480; i++) - row_pointers[i] = &data[i*640*3]; - - jpeg_start_compress(&jpeg_encoder, TRUE); - while (jpeg_encoder.next_scanline < jpeg_encoder.image_height) - jpeg_write_scanlines(&jpeg_encoder, &row_pointers[jpeg_encoder.next_scanline], 480-jpeg_encoder.next_scanline); - jpeg_finish_compress(&jpeg_encoder); - - if (fprintf(stream, - "--ffserver\r\n" - "Content-type: image/jpeg\r\n" - "Content-length: %lu\r\n" - "\r\n", - jpeg_encoder_len) < 1) - error(EXIT_FAILURE, ferror(stream), "writing header"); - if (fwrite(jpeg_encoder_buf, jpeg_encoder_len, 1, stream) < 1) - error(EXIT_FAILURE, ferror(stream), "writing body"); - if (fwrite("\r\n", 2, 1, stream) < 1) - error(EXIT_FAILURE, ferror(stream), "writing footer"); - fflush(stream); -} - -void handle_accel(freenect_device *dev UNUSED, freenect_raw_tilt_state* data) { - double x, y, z, angle; - freenect_get_mks_accel(data, &x, &y, &z); - angle = freenect_get_tilt_degs(data); - - const char *motor; - switch (data->tilt_status) { - case TILT_STATUS_STOPPED: - motor = "stopped"; - break; - case TILT_STATUS_LIMIT: - motor = "limit"; - break; - case TILT_STATUS_MOVING: - motor = "moving"; - break; - default: - motor = "unknown"; - } - - char *json = NULL; - int len = asprintf(&json, - "{ \"x\": %f,\n" - " \"y\": %f,\n" - " \"z\": %f,\n" - " \"angle\": %f,\n" - " \"motor\": \"%s\" }\n", - x, y, z, angle, motor); - if (len < 0) - error(EXIT_FAILURE, errno, "asprintf"); - fprintf(accel_stream, - "--ffserver\r\n" - "Content-type: application/json\r\n" - "Content-length: %d\r\n" - "\r\n" - "%s\r\n", - len, json); - fflush(accel_stream); -} - -uint8_t depth_rgb[640*480*3]; -void handle_depth(freenect_device *dev, void *depth, uint32_t timestamp UNUSED) { - uint32_t size = freenect_get_current_depth_mode(dev).bytes; - if (size != 640*480*2) - error(EXIT_FAILURE, 0, "handle_depth: expected 640*480*2 byte frame, but got %"PRId32, size); - /* scale the 11-bit values into 8-bit values */ - uint16_t *depth_grey = depth; - for (size_t i = 0; i < 640*480; i++) - depth_rgb[i*3+0] = depth_rgb[i*3+1] = depth_rgb[i*3+2] = (uint8_t)(depth_grey[i]*8.0/11.0); - /* write the image */ - write_imageframe(depth_stream, depth_rgb); -} - -void handle_video(freenect_device *dev, void *rgb, uint32_t timestamp UNUSED) { - uint32_t size = freenect_get_current_video_mode(dev).bytes; - if (size != 640*480*3) - error(EXIT_FAILURE, 0, "handle_video: expected 640*480*3 byte frame, but got %"PRId32, size); - /* write the image */ - write_imageframe(video_stream, rgb); -} - -void print_mode(const char *name, freenect_frame_mode mode) { - /* This is just a courtesy function to let the user know the mode - if it becomes a bother for maintainability just comment out the - code in its body. It will only break if struct entries go missing. - */ - printf("%s Mode: {%d, %d, {%d}, %d, %d, %d, %d, %d, %d, %d}\n", name, - mode.reserved, (int)mode.resolution, (int)mode.video_format, mode.bytes, mode.width, - mode.height, mode.data_bits_per_pixel, mode.padding_bits_per_pixel, - mode.framerate, mode.is_valid); -} - -void cleanup() { - log("STOPPING=1"); - if (ctx) - freenect_shutdown(ctx); - if (video_stream) - fclose(video_stream); - if (depth_stream) - fclose(depth_stream); - if (accel_stream) - fclose(accel_stream); - fflush(stderr); - sleep(5); /* work around systemd bug dropping log messages */ -} - -FILE *xfopen(const char *path, const char *mode) { - FILE *stream = fopen(path, mode); - if (stream == NULL) - error(EXIT_FAILURE, errno, "fopen: %s", path); - return stream; -} - -FILE *xfdopen(const char *path, const char *mode) { - int fd = get_fd(path); - if (fd < 0) - error(EXIT_FAILURE, -fd, "get_fd: %s", path); - FILE *stream = fdopen(fd, mode); - if (stream == NULL) - error(EXIT_FAILURE, errno, "fdopen: %s (%d)", path, fd); - return stream; -} - -void usage() { - printf("Usage: %s [-h] [-v video.mjpg|-V video_fd] [-d depth.mjpg|-D depth_fd] [-a accel.mjson|-A accel_fd]\n", program_invocation_name); -} - -int main(int argc, char *argv[]) { - int res = 0; - freenect_device *dev; - - atexit(cleanup); - - for (int i = 1; i < argc; i++) { - if (argv[i][0] == '-' && argv[i][1] != '\0' && argv[i][2] == '\0') { - switch (argv[i][1]) { - case 'h': - usage(); - return EXIT_SUCCESS; - case 'v': - i++; - video_stream = xfopen(argv[i], "w"); - break; - case 'd': - i++; - depth_stream = xfopen(argv[i], "w"); - break; - case 'a': - i++; - accel_stream = xfopen(argv[i], "w"); - break; - case 'V': - i++; - video_stream = xfdopen(argv[i], "w"); - break; - case 'D': - i++; - depth_stream = xfdopen(argv[i], "w"); - break; - case 'A': - i++; - accel_stream = xfdopen(argv[i], "w"); - break; - default: - dup2(2, 1); - usage(); - return EXIT_FAILURE; - } - } else { - dup2(2, 1); - usage(); - return EXIT_FAILURE; - } - } - if (!(video_stream || depth_stream || accel_stream)) { - dup2(2, 1); - usage(); - return EXIT_FAILURE; - } - - jpeg_init(); - - res = freenect_init(&ctx, 0); - if (res) { - error(EXIT_FAILURE, 0, "freenect_init: %s", libusb_strerror(res)); - } - - freenect_device_flags devices = 0; - if (video_stream || depth_stream) - devices |= FREENECT_DEVICE_CAMERA; - if (accel_stream) - devices |= FREENECT_DEVICE_MOTOR; - - freenect_select_subdevices(ctx, devices); - if ((res = freenect_open_device(ctx, &dev, 0))) { - error(EXIT_FAILURE, 0, "freenect_open_device: %s", libusb_strerror(res)); - } - - if (video_stream) { - print_mode("Video", freenect_find_video_mode(FREENECT_RESOLUTION_MEDIUM, FREENECT_VIDEO_RGB)); - freenect_set_video_mode(dev, freenect_find_video_mode(FREENECT_RESOLUTION_MEDIUM, FREENECT_VIDEO_RGB)); - freenect_start_video(dev); - freenect_set_video_callback(dev, handle_video); - } - - if (depth_stream) { - print_mode("Depth", freenect_find_depth_mode(FREENECT_RESOLUTION_MEDIUM, FREENECT_DEPTH_11BIT)); - freenect_set_depth_mode(dev, freenect_find_depth_mode(FREENECT_RESOLUTION_MEDIUM, FREENECT_DEPTH_11BIT)); - freenect_start_depth(dev); - freenect_set_depth_callback(dev, handle_depth); - } - - while (freenect_process_events(ctx) >= 0) { - if (accel_stream) { - freenect_raw_tilt_state* state; - freenect_update_tilt_state(dev); - state = freenect_get_tilt_state(dev); - handle_accel(dev, state); - } - } - - freenect_stop_depth(dev); - freenect_stop_video(dev); - freenect_close_device(dev); -} diff --git a/freenect-server.service b/freenect-server.service deleted file mode 100644 index 4ccf7f6..0000000 --- a/freenect-server.service +++ /dev/null @@ -1,12 +0,0 @@ -[Unit] -Description=Kinect media streamer backend -After=network.target -Requires=freenect-server@accel.mjson.socket freenect-server@depth.mjpg.socket freenect-server@video.mjpg.socket - -[Service] -Type=simple -User=alarm -Group=alarm -ExecStart=/home/alarm/freenect-server/freenect-server -V systemd:freenect-server@video.mjpg.socket -D systemd:freenect-server@depth.mjpg.socket -A systemd:freenect-server@accel.mjson.socket - -Restart=always diff --git a/freenect-server@.socket b/freenect-server@.socket deleted file mode 100644 index 78df40e..0000000 --- a/freenect-server@.socket +++ /dev/null @@ -1,10 +0,0 @@ -[Unit] -Description=Kinect media streamer backend %I stream -After=network.target - -[Socket] -SocketUser=alarm -SocketGroup=alarm -ListenFIFO=/run/freenect-server/%I -Service=freenect-server.service -RemoveOnStop=true \ No newline at end of file diff --git a/multipart-replace-http-server.c b/multipart-replace-http-server.c deleted file mode 100644 index 726714f..0000000 --- a/multipart-replace-http-server.c +++ /dev/null @@ -1,367 +0,0 @@ -/* Copyright 2016 Luke Shumaker */ - -#include -#include -#include /* for open */ -#include /* for {get,free}addrinfo() */ -#include -#include /* atexit */ -#include /* for EXIT_FAILURE */ -#include -#include -#include /* for open */ -#include -#include - -#include "util.h" -#include "wg.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 *filename; - const char *boundary; -}; - - -bool running = true; -int sock; -struct wg wg; - -void stop(int sig) { - log("Caught %d", sig); - log("STOPPING=1"); - running = false; - close(sock); -} - -#define threaderror(stat, errnum, ...) do { error(0, errnum, __VA_ARGS__); stop(0); } while(0) - -void *reader_thread(void *args_anon) { - struct reader_thread_args *args = args_anon; - - char *name = alloca(strlen("read ")+strlen(args->filename)+1); - strcpy(name, "read "); - strcpy(&name[strlen("read ")], args->filename); - pthread_setname_np(pthread_self(), name); - debug("starting thread: %s", name); - - multipart_replace_reader(args->stream, args->fd, args->boundary); - sleep(5); - threaderror(EXIT_FAILURE, 0, "multipart_replace stream ended: %s", args->filename); - wg_sub(&wg, 1); - return NULL; -} - -void start_multipart_replace_reader(struct multipart_replace_stream *s, int fd, const char *filename, const char *boundary) { - struct reader_thread_args *args = xrealloc(NULL, sizeof(struct reader_thread_args)); - args->stream = s; - args->fd = fd; - args->filename = strdup(filename); - args->boundary = boundary; - pthread_t thread; - wg_add(&wg, 1); - 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) { - threaderror(EXIT_FAILURE, errno, "opening file: %s", filename); - return; - } - filev = xrealloc(filev, (++filec)*sizeof(*filev)); - char *shortname = strrchr(filename, '/'); - if (shortname == NULL) { - shortname = alloca(strlen(filename)+2); - shortname[0] = '/'; - strcpy(&shortname[1], filename); - } - strncpy(filev[filec-1].name, shortname, sizeof(filev[filec-1].name)); - log("added file #%zd: %s", filec-1, filev[filec-1].name); - filev[filec-1].stream = stream; - start_multipart_replace_reader(stream, fd, filev[filec-1].name, "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; -} - -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); - - if (strcmp(path, "/") == 0) { - log("200 %s", path); - dprintf(fd, - "HTTP/1.1 200 OK\r\n" - "Content-Type: text/html; charset=utf-8\r\n" - "\r\n" - "\n" - "\n" - "\n" - " multipart/x-mixed-replace HTTP server\n" - "\n" - "\n" - "
    \n" - ); - for (size_t i = 0; i < filec; i++) - dprintf(fd, "
  • %s
  • \n", filev[i].name, filev[i].name); - dprintf(fd, - "
\n" - "\n" - "\n"); - close(fd); - return; - } - struct multipart_replace_stream *vidstream = file_get(path); - if (vidstream == NULL) { - log("404 %s", path); - dprintf(fd, - "HTTP/1.1 404 Not Found\r\n" - "\r\n"); - return; - } - - const char *boundary = "boundary" /* FIXME */; - log("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) { - pthread_setname_np(pthread_self(), "connection"); - int fd = (int)(intptr_t)arg_anon; - log("Connection %d opened", fd); - connection_handler(fd); - log("Connection %d closed", fd); - wg_sub(&wg, 1); - return NULL; -} - - -/* same error codes values as -getaddrinfo(); */ -int sockstream_listen(const char *type, const char *addr) { - int sock; - if (strcmp(type, "fd") == 0) { - sock = get_fd(addr); - if (sock < 0) { - errno = -sock; - return -EAI_SYSTEM; - } - /* make sure it's a socket */ - struct stat st; - if (fstat(sock, &st) < 0) - return -EAI_SYSTEM; - if (!S_ISSOCK(st.st_mode)) { - errno = ENOTSOCK; - return -EAI_SYSTEM; - } - /* make sure the socket is a stream */ - int fd_socktype = 0; - socklen_t l = sizeof(fd_socktype); - if (getsockopt(sock, SOL_SOCKET, SO_TYPE, &fd_socktype, &l) < 0) - return -EAI_SYSTEM; - if (l != sizeof(fd_socktype)) { - errno = EINVAL; - return -EAI_SYSTEM; - } - if (fd_socktype != SOCK_STREAM) { - errno = ENOSTR; - return -EAI_SYSTEM; - } - /* make sure the socket is listening */ - int fd_listening = 0; - l = sizeof(fd_listening); - if (getsockopt(sock, SOL_SOCKET, SO_ACCEPTCONN, &fd_listening, &l) < 0) - return -EAI_SYSTEM; - if (l != sizeof(fd_listening)) { - errno = EINVAL; - return -EAI_SYSTEM; - } - if (!fd_listening) - listen(sock, SOMAXCONN); - /* return the socket */ - return sock; - } else if (strcmp(type, "unix") == 0) { - struct sockaddr_un un_addr = { 0 }; - if (strlen(addr)+1 > sizeof(un_addr.sun_path)) { - errno = ENAMETOOLONG; - return -EAI_SYSTEM; - } - un_addr.sun_family = AF_UNIX; - strcpy(un_addr.sun_path, addr); - int sock = socket(AF_UNIX, SOCK_STREAM, 0); - if (sock < 0) - return -EAI_SYSTEM; - unlink(addr); - if (bind(sock, (struct sockaddr *)&un_addr, sizeof(un_addr)) < 0) - return -EAI_SYSTEM; - if (listen(sock, SOMAXCONN) < 0) - return -EAI_SYSTEM; - return sock; - } else if (strcmp(type, "tcp4") == 0 || strcmp(type, "tcp6") == 0) { - struct addrinfo *ai_addr = NULL; - struct addrinfo ai_hints = { 0 }; - - char *host = strdupa(addr); - char *col = strrchr(host, ':'); - if (col == NULL) { - errno = EINVAL; - return -EAI_SYSTEM; - } - char *port = &col[1]; - *col = '\0'; - if (host[0] == '\0') - host = NULL; - - ai_hints.ai_family = (strcmp(type, "tcp6") == 0) ? AF_INET6 : AF_INET; - ai_hints.ai_socktype = SOCK_STREAM; - ai_hints.ai_flags = AI_PASSIVE; - - int r = getaddrinfo(host, port, &ai_hints, &ai_addr); - if (r < 0) - return r; - if (r > 0) - return -r; - - int sock = socket(ai_addr->ai_family, ai_addr->ai_socktype, ai_addr->ai_protocol); - if (sock < 0) - return -EAI_SYSTEM; - - int yes = 1; - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - - if (bind(sock, ai_addr->ai_addr, ai_addr->ai_addrlen) < 0) - return -EAI_SYSTEM; - - if (listen(sock, SOMAXCONN) < 0) - return -EAI_SYSTEM; - - freeaddrinfo(ai_addr); - return sock; - } else { - errno = EINVAL; - return -EAI_SYSTEM; - } -} - -void usage() { - printf("Usage: %s [-h] ADDRTYPE ADDR [FILENAME...]\n", program_invocation_name); - printf("Multiplex several multipart/x-mixed-replace streams over HTTP.\n" - "\n" - "ADDRTYPE is \"tcp4\", \"tcp6\", \"fd\", or \"unix\". For tcp4/6, ADDR\n" - "is a socket address in standard notation. For fd, ADDR is \"stdin\",\n" - "\"stdout\", \"stder\", \"systemd\", or an unsigned integer. For unix,\n" - "ADDR is a file path.\n" - "\n" - "The leading directories of the FILENAMEs are stripped; they are served\n" - "at just the base filename. As common sense should eventually suggest,\n" - "the FILENAMEs should be pipes or sockets.\n"); -} - -void cleanup() { - fflush(stderr); - sleep(5); /* work around systemd bug dropping log messages */ -} - -pthread_t main_thread; - -void progname() { - char threadname[16]; - if (pthread_self() != main_thread && pthread_getname_np(pthread_self(), threadname, sizeof(threadname)) == 0) - fprintf(stderr, "%s:%s: ", program_invocation_name, threadname); - else - fprintf(stderr, "%s: ", program_invocation_name); -} - -int main(int argc, char *argv[]) { - if (argc >=2 && strcmp(argv[1], "-h") == 0) { - usage(); - return EXIT_SUCCESS; - } - if (argc < 3) { - dup2(2, 1); - usage(); - return EXIT_FAILURE; - } - - main_thread = pthread_self(); - error_print_progname = progname; - atexit(cleanup); - - sock = sockstream_listen(argv[1], argv[2]); - if (sock < 0) { - if (sock == -EAI_SYSTEM) - error(EXIT_FAILURE, errno, "Opening socket %s %s", argv[1], argv[2]); - else - error(EXIT_FAILURE, 0, "Opening socket %s %s: %s", argv[1], argv[2], gai_strerror(-sock)); - } - - wg_init(&wg); - for (int i = 3; i < argc; i++) - file_add(argv[i]); - - signal(SIGPIPE, SIG_IGN); - signal(SIGTERM, stop); - signal(SIGQUIT, stop); - signal(SIGINT, stop); - while (running) { - int conn = accept(sock, NULL, NULL); - if (conn < 0) - continue; - pthread_t thread; - wg_add(&wg, 1); - pthread_create(&thread, NULL, connection_thread, (void*)(intptr_t)conn); - } - wg_wait(&wg); - for (size_t i = 0; i < filec; i++) - destroy_multipart_replace_stream(filev[i].stream); - free(filev); - return 0; -} diff --git a/multipart-replace.c b/multipart-replace.c deleted file mode 100644 index 9a5e268..0000000 --- a/multipart-replace.c +++ /dev/null @@ -1,168 +0,0 @@ -/* Copyright 2016 Luke Shumaker */ - -#include -#include -#include -#include -#include -#include -#include - -#include "multipart-replace.h" -#include "util.h" - -#define error(stat, errnum, ...) do { error(0, errnum, __VA_ARGS__); goto end; } while(0) - -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 multipart_replace_reader(struct multipart_replace_stream *s, int fd, const char *_boundary) { - FILE *stream = fdopen(fd, "r"); - char *boundary = boundary_line(_boundary); - - char *line_buf = NULL; - size_t line_cap = 0; - ssize_t line_len = 0; - ssize_t content_length = -1; - - bool first = true; - while (running) { - content_length = -1; - /* scan for the first non-empty line */ - do { - line_len = getline(&line_buf, &line_cap, stream); - if (line_len < 0) - error(EXIT_FAILURE, ferror(stream), "source hung up"); - } while (strcmp(line_buf, "\r\n") == 0); - /* make sure it matches the boundary separator */ - if (first) { - while (strcmp(line_buf, boundary) != 0) { - line_len = getline(&line_buf, &line_cap, stream); - if (line_len < 0) - error(EXIT_FAILURE, ferror(stream), "source hung up"); - } - first = false; - } else { - if (strcmp(line_buf, boundary) != 0) - error(EXIT_FAILURE, 0, "line does not match boundary: \"%s\"", line_buf); - } - /* read the frame header (MIME headers) */ - s->back->len = 0; - do { - line_len = getline(&line_buf, &line_cap, stream); - if (line_len < 0) - error(EXIT_FAILURE, ferror(stream), "source hung up"); - /* 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(EXIT_FAILURE, 0, "did not get frame length"); - /* 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], content_length, 1, stream) != 1) - error(EXIT_FALURE, ferror(stream), "fread(%zd)", s->back->len); - 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); - } - end: - free(boundary); - fclose(stream); -} - -void multipart_replace_writer(struct multipart_replace_stream *s, int fd, const char *_boundary) { - FILE *stream = fdopen(fd, "w"); - struct frame myframe = { 0 }; - long lastframe = 0; - char *boundary = boundary_line(_boundary); - size_t boundary_len = strlen(boundary); - - pthread_rwlock_rdlock(&s->frontlock); - while (running) { - /* 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 (fwrite(boundary, boundary_len, 1, stream) < 1) - error(EXIT_FAILURE, ferror(stream), "fwrite(boundary)"); - if (fwrite(myframe.buf, myframe.len, 1, stream) < 1) - error(EXIT_FAILURE, ferror(stream), "fwrite(frame.buf)"); - /* send a blank line for pleasantness */ - if (fwrite("\r\n", 2, 1, stream) < 1) - error(EXIT_FAILURE, ferror(stream), "fwrite(\"\\r\\n\")"); - fflush(stream); - - /* poll until there's a new frame */ - pthread_rwlock_rdlock(&s->frontlock); - while (s->framecount == lastframe && running) { - pthread_rwlock_unlock(&s->frontlock); - usleep(30000); /* a bit over 30 FPS */ - pthread_rwlock_rdlock(&s->frontlock); - } - } - pthread_rwlock_unlock(&s->frontlock); - end: - free(boundary); - free(myframe.buf); - fclose(stream); -} - -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); - s->framecount = 0; -} - -void destroy_multipart_replace_stream(struct multipart_replace_stream *s) { - free(s->a.buf); - free(s->b.buf); - pthread_rwlock_destroy(&s->frontlock); -} diff --git a/multipart-replace.h b/multipart-replace.h deleted file mode 100644 index 2aa5e61..0000000 --- a/multipart-replace.h +++ /dev/null @@ -1,26 +0,0 @@ -/* Copyright 2016 Luke Shumaker */ - -#pragma once - -#include -#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(struct multipart_replace_stream *s); -void destroy_multipart_replace_stream(struct multipart_replace_stream *s); - -extern bool running; diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..bf84729 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,5 @@ +/freenect-server +/multipart-replace-http-server + +*.o +.*.mk diff --git a/src/freenect-server.c b/src/freenect-server.c new file mode 100644 index 0000000..c5ed99a --- /dev/null +++ b/src/freenect-server.c @@ -0,0 +1,289 @@ +/* + * This file is part of the OpenKinect Project. http://www.openkinect.org + * + * Copyright (c) 2010 Brandyn White (bwhite@dappervision.com) + * Copyright (c) 2016 Luke Shumaker (lukeshu@sbcglobal.net) + * + * This code is licensed to you under the terms of the Apache License, version + * 2.0, or, at your option, the terms of the GNU General Public License, + * version 2.0. See the APACHE20 and GPL2 files for the text of the licenses, + * or the following URLs: + * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.gnu.org/licenses/gpl-2.0.txt + * + * If you redistribute this file in source form, modified or unmodified, you + * may: + * 1) Leave this header intact and distribute it under the same terms, + * accompanying it with the APACHE20 and GPL20 files, or + * 2) Delete the Apache 2.0 clause and accompany it with the GPL2 file, or + * 3) Delete the GPL v2 clause and accompany it with the APACHE20 file + * In all cases you must keep the copyright notice intact and include a copy + * of the CONTRIB file. + * + * Binary distributions must follow the binary distribution requirements of + * either License. + */ +#include +#include +#include +#include +#include /* atexit */ +#include /* dup2 */ + +#include +#include + +#include + +#include "util.h" + +FILE *depth_stream = NULL; +FILE *video_stream = NULL; +FILE *accel_stream = NULL; +freenect_context *ctx; + +struct jpeg_error_mgr jpeg_encoder_err; +struct jpeg_compress_struct jpeg_encoder; +unsigned char *jpeg_encoder_buf; +unsigned long jpeg_encoder_len; + +void jpeg_init() { + jpeg_encoder.err = jpeg_std_error(&jpeg_encoder_err); + jpeg_create_compress(&jpeg_encoder); + jpeg_encoder.image_width = 640; + jpeg_encoder.image_height = 480; + jpeg_encoder.input_components = 3; + jpeg_encoder.in_color_space = JCS_RGB; + jpeg_set_defaults(&jpeg_encoder); + jpeg_mem_dest(&jpeg_encoder, &jpeg_encoder_buf, &jpeg_encoder_len); +} + +void write_imageframe(FILE *stream, JSAMPLE *data) { + JSAMPLE *row_pointers[480]; + for (size_t i = 0; i < 480; i++) + row_pointers[i] = &data[i*640*3]; + + jpeg_start_compress(&jpeg_encoder, TRUE); + while (jpeg_encoder.next_scanline < jpeg_encoder.image_height) + jpeg_write_scanlines(&jpeg_encoder, &row_pointers[jpeg_encoder.next_scanline], 480-jpeg_encoder.next_scanline); + jpeg_finish_compress(&jpeg_encoder); + + if (fprintf(stream, + "--ffserver\r\n" + "Content-type: image/jpeg\r\n" + "Content-length: %lu\r\n" + "\r\n", + jpeg_encoder_len) < 1) + error(EXIT_FAILURE, ferror(stream), "writing header"); + if (fwrite(jpeg_encoder_buf, jpeg_encoder_len, 1, stream) < 1) + error(EXIT_FAILURE, ferror(stream), "writing body"); + if (fwrite("\r\n", 2, 1, stream) < 1) + error(EXIT_FAILURE, ferror(stream), "writing footer"); + fflush(stream); +} + +void handle_accel(freenect_device *dev UNUSED, freenect_raw_tilt_state* data) { + double x, y, z, angle; + freenect_get_mks_accel(data, &x, &y, &z); + angle = freenect_get_tilt_degs(data); + + const char *motor; + switch (data->tilt_status) { + case TILT_STATUS_STOPPED: + motor = "stopped"; + break; + case TILT_STATUS_LIMIT: + motor = "limit"; + break; + case TILT_STATUS_MOVING: + motor = "moving"; + break; + default: + motor = "unknown"; + } + + char *json = NULL; + int len = asprintf(&json, + "{ \"x\": %f,\n" + " \"y\": %f,\n" + " \"z\": %f,\n" + " \"angle\": %f,\n" + " \"motor\": \"%s\" }\n", + x, y, z, angle, motor); + if (len < 0) + error(EXIT_FAILURE, errno, "asprintf"); + fprintf(accel_stream, + "--ffserver\r\n" + "Content-type: application/json\r\n" + "Content-length: %d\r\n" + "\r\n" + "%s\r\n", + len, json); + fflush(accel_stream); +} + +uint8_t depth_rgb[640*480*3]; +void handle_depth(freenect_device *dev, void *depth, uint32_t timestamp UNUSED) { + uint32_t size = freenect_get_current_depth_mode(dev).bytes; + if (size != 640*480*2) + error(EXIT_FAILURE, 0, "handle_depth: expected 640*480*2 byte frame, but got %"PRId32, size); + /* scale the 11-bit values into 8-bit values */ + uint16_t *depth_grey = depth; + for (size_t i = 0; i < 640*480; i++) + depth_rgb[i*3+0] = depth_rgb[i*3+1] = depth_rgb[i*3+2] = (uint8_t)(depth_grey[i]*8.0/11.0); + /* write the image */ + write_imageframe(depth_stream, depth_rgb); +} + +void handle_video(freenect_device *dev, void *rgb, uint32_t timestamp UNUSED) { + uint32_t size = freenect_get_current_video_mode(dev).bytes; + if (size != 640*480*3) + error(EXIT_FAILURE, 0, "handle_video: expected 640*480*3 byte frame, but got %"PRId32, size); + /* write the image */ + write_imageframe(video_stream, rgb); +} + +void print_mode(const char *name, freenect_frame_mode mode) { + /* This is just a courtesy function to let the user know the mode + if it becomes a bother for maintainability just comment out the + code in its body. It will only break if struct entries go missing. + */ + printf("%s Mode: {%d, %d, {%d}, %d, %d, %d, %d, %d, %d, %d}\n", name, + mode.reserved, (int)mode.resolution, (int)mode.video_format, mode.bytes, mode.width, + mode.height, mode.data_bits_per_pixel, mode.padding_bits_per_pixel, + mode.framerate, mode.is_valid); +} + +void cleanup() { + log("STOPPING=1"); + if (ctx) + freenect_shutdown(ctx); + if (video_stream) + fclose(video_stream); + if (depth_stream) + fclose(depth_stream); + if (accel_stream) + fclose(accel_stream); + fflush(stderr); + sleep(5); /* work around systemd bug dropping log messages */ +} + +FILE *xfopen(const char *path, const char *mode) { + FILE *stream = fopen(path, mode); + if (stream == NULL) + error(EXIT_FAILURE, errno, "fopen: %s", path); + return stream; +} + +FILE *xfdopen(const char *path, const char *mode) { + int fd = get_fd(path); + if (fd < 0) + error(EXIT_FAILURE, -fd, "get_fd: %s", path); + FILE *stream = fdopen(fd, mode); + if (stream == NULL) + error(EXIT_FAILURE, errno, "fdopen: %s (%d)", path, fd); + return stream; +} + +void usage() { + printf("Usage: %s [-h] [-v video.mjpg|-V video_fd] [-d depth.mjpg|-D depth_fd] [-a accel.mjson|-A accel_fd]\n", program_invocation_name); +} + +int main(int argc, char *argv[]) { + int res = 0; + freenect_device *dev; + + atexit(cleanup); + + for (int i = 1; i < argc; i++) { + if (argv[i][0] == '-' && argv[i][1] != '\0' && argv[i][2] == '\0') { + switch (argv[i][1]) { + case 'h': + usage(); + return EXIT_SUCCESS; + case 'v': + i++; + video_stream = xfopen(argv[i], "w"); + break; + case 'd': + i++; + depth_stream = xfopen(argv[i], "w"); + break; + case 'a': + i++; + accel_stream = xfopen(argv[i], "w"); + break; + case 'V': + i++; + video_stream = xfdopen(argv[i], "w"); + break; + case 'D': + i++; + depth_stream = xfdopen(argv[i], "w"); + break; + case 'A': + i++; + accel_stream = xfdopen(argv[i], "w"); + break; + default: + dup2(2, 1); + usage(); + return EXIT_FAILURE; + } + } else { + dup2(2, 1); + usage(); + return EXIT_FAILURE; + } + } + if (!(video_stream || depth_stream || accel_stream)) { + dup2(2, 1); + usage(); + return EXIT_FAILURE; + } + + jpeg_init(); + + res = freenect_init(&ctx, 0); + if (res) { + error(EXIT_FAILURE, 0, "freenect_init: %s", libusb_strerror(res)); + } + + freenect_device_flags devices = 0; + if (video_stream || depth_stream) + devices |= FREENECT_DEVICE_CAMERA; + if (accel_stream) + devices |= FREENECT_DEVICE_MOTOR; + + freenect_select_subdevices(ctx, devices); + if ((res = freenect_open_device(ctx, &dev, 0))) { + error(EXIT_FAILURE, 0, "freenect_open_device: %s", libusb_strerror(res)); + } + + if (video_stream) { + print_mode("Video", freenect_find_video_mode(FREENECT_RESOLUTION_MEDIUM, FREENECT_VIDEO_RGB)); + freenect_set_video_mode(dev, freenect_find_video_mode(FREENECT_RESOLUTION_MEDIUM, FREENECT_VIDEO_RGB)); + freenect_start_video(dev); + freenect_set_video_callback(dev, handle_video); + } + + if (depth_stream) { + print_mode("Depth", freenect_find_depth_mode(FREENECT_RESOLUTION_MEDIUM, FREENECT_DEPTH_11BIT)); + freenect_set_depth_mode(dev, freenect_find_depth_mode(FREENECT_RESOLUTION_MEDIUM, FREENECT_DEPTH_11BIT)); + freenect_start_depth(dev); + freenect_set_depth_callback(dev, handle_depth); + } + + while (freenect_process_events(ctx) >= 0) { + if (accel_stream) { + freenect_raw_tilt_state* state; + freenect_update_tilt_state(dev); + state = freenect_get_tilt_state(dev); + handle_accel(dev, state); + } + } + + freenect_stop_depth(dev); + freenect_stop_video(dev); + freenect_close_device(dev); +} diff --git a/src/multipart-replace-http-server.c b/src/multipart-replace-http-server.c new file mode 100644 index 0000000..726714f --- /dev/null +++ b/src/multipart-replace-http-server.c @@ -0,0 +1,367 @@ +/* Copyright 2016 Luke Shumaker */ + +#include +#include +#include /* for open */ +#include /* for {get,free}addrinfo() */ +#include +#include /* atexit */ +#include /* for EXIT_FAILURE */ +#include +#include +#include /* for open */ +#include +#include + +#include "util.h" +#include "wg.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 *filename; + const char *boundary; +}; + + +bool running = true; +int sock; +struct wg wg; + +void stop(int sig) { + log("Caught %d", sig); + log("STOPPING=1"); + running = false; + close(sock); +} + +#define threaderror(stat, errnum, ...) do { error(0, errnum, __VA_ARGS__); stop(0); } while(0) + +void *reader_thread(void *args_anon) { + struct reader_thread_args *args = args_anon; + + char *name = alloca(strlen("read ")+strlen(args->filename)+1); + strcpy(name, "read "); + strcpy(&name[strlen("read ")], args->filename); + pthread_setname_np(pthread_self(), name); + debug("starting thread: %s", name); + + multipart_replace_reader(args->stream, args->fd, args->boundary); + sleep(5); + threaderror(EXIT_FAILURE, 0, "multipart_replace stream ended: %s", args->filename); + wg_sub(&wg, 1); + return NULL; +} + +void start_multipart_replace_reader(struct multipart_replace_stream *s, int fd, const char *filename, const char *boundary) { + struct reader_thread_args *args = xrealloc(NULL, sizeof(struct reader_thread_args)); + args->stream = s; + args->fd = fd; + args->filename = strdup(filename); + args->boundary = boundary; + pthread_t thread; + wg_add(&wg, 1); + 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) { + threaderror(EXIT_FAILURE, errno, "opening file: %s", filename); + return; + } + filev = xrealloc(filev, (++filec)*sizeof(*filev)); + char *shortname = strrchr(filename, '/'); + if (shortname == NULL) { + shortname = alloca(strlen(filename)+2); + shortname[0] = '/'; + strcpy(&shortname[1], filename); + } + strncpy(filev[filec-1].name, shortname, sizeof(filev[filec-1].name)); + log("added file #%zd: %s", filec-1, filev[filec-1].name); + filev[filec-1].stream = stream; + start_multipart_replace_reader(stream, fd, filev[filec-1].name, "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; +} + +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); + + if (strcmp(path, "/") == 0) { + log("200 %s", path); + dprintf(fd, + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html; charset=utf-8\r\n" + "\r\n" + "\n" + "\n" + "\n" + " multipart/x-mixed-replace HTTP server\n" + "\n" + "\n" + "
    \n" + ); + for (size_t i = 0; i < filec; i++) + dprintf(fd, "
  • %s
  • \n", filev[i].name, filev[i].name); + dprintf(fd, + "
\n" + "\n" + "\n"); + close(fd); + return; + } + struct multipart_replace_stream *vidstream = file_get(path); + if (vidstream == NULL) { + log("404 %s", path); + dprintf(fd, + "HTTP/1.1 404 Not Found\r\n" + "\r\n"); + return; + } + + const char *boundary = "boundary" /* FIXME */; + log("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) { + pthread_setname_np(pthread_self(), "connection"); + int fd = (int)(intptr_t)arg_anon; + log("Connection %d opened", fd); + connection_handler(fd); + log("Connection %d closed", fd); + wg_sub(&wg, 1); + return NULL; +} + + +/* same error codes values as -getaddrinfo(); */ +int sockstream_listen(const char *type, const char *addr) { + int sock; + if (strcmp(type, "fd") == 0) { + sock = get_fd(addr); + if (sock < 0) { + errno = -sock; + return -EAI_SYSTEM; + } + /* make sure it's a socket */ + struct stat st; + if (fstat(sock, &st) < 0) + return -EAI_SYSTEM; + if (!S_ISSOCK(st.st_mode)) { + errno = ENOTSOCK; + return -EAI_SYSTEM; + } + /* make sure the socket is a stream */ + int fd_socktype = 0; + socklen_t l = sizeof(fd_socktype); + if (getsockopt(sock, SOL_SOCKET, SO_TYPE, &fd_socktype, &l) < 0) + return -EAI_SYSTEM; + if (l != sizeof(fd_socktype)) { + errno = EINVAL; + return -EAI_SYSTEM; + } + if (fd_socktype != SOCK_STREAM) { + errno = ENOSTR; + return -EAI_SYSTEM; + } + /* make sure the socket is listening */ + int fd_listening = 0; + l = sizeof(fd_listening); + if (getsockopt(sock, SOL_SOCKET, SO_ACCEPTCONN, &fd_listening, &l) < 0) + return -EAI_SYSTEM; + if (l != sizeof(fd_listening)) { + errno = EINVAL; + return -EAI_SYSTEM; + } + if (!fd_listening) + listen(sock, SOMAXCONN); + /* return the socket */ + return sock; + } else if (strcmp(type, "unix") == 0) { + struct sockaddr_un un_addr = { 0 }; + if (strlen(addr)+1 > sizeof(un_addr.sun_path)) { + errno = ENAMETOOLONG; + return -EAI_SYSTEM; + } + un_addr.sun_family = AF_UNIX; + strcpy(un_addr.sun_path, addr); + int sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) + return -EAI_SYSTEM; + unlink(addr); + if (bind(sock, (struct sockaddr *)&un_addr, sizeof(un_addr)) < 0) + return -EAI_SYSTEM; + if (listen(sock, SOMAXCONN) < 0) + return -EAI_SYSTEM; + return sock; + } else if (strcmp(type, "tcp4") == 0 || strcmp(type, "tcp6") == 0) { + struct addrinfo *ai_addr = NULL; + struct addrinfo ai_hints = { 0 }; + + char *host = strdupa(addr); + char *col = strrchr(host, ':'); + if (col == NULL) { + errno = EINVAL; + return -EAI_SYSTEM; + } + char *port = &col[1]; + *col = '\0'; + if (host[0] == '\0') + host = NULL; + + ai_hints.ai_family = (strcmp(type, "tcp6") == 0) ? AF_INET6 : AF_INET; + ai_hints.ai_socktype = SOCK_STREAM; + ai_hints.ai_flags = AI_PASSIVE; + + int r = getaddrinfo(host, port, &ai_hints, &ai_addr); + if (r < 0) + return r; + if (r > 0) + return -r; + + int sock = socket(ai_addr->ai_family, ai_addr->ai_socktype, ai_addr->ai_protocol); + if (sock < 0) + return -EAI_SYSTEM; + + int yes = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + + if (bind(sock, ai_addr->ai_addr, ai_addr->ai_addrlen) < 0) + return -EAI_SYSTEM; + + if (listen(sock, SOMAXCONN) < 0) + return -EAI_SYSTEM; + + freeaddrinfo(ai_addr); + return sock; + } else { + errno = EINVAL; + return -EAI_SYSTEM; + } +} + +void usage() { + printf("Usage: %s [-h] ADDRTYPE ADDR [FILENAME...]\n", program_invocation_name); + printf("Multiplex several multipart/x-mixed-replace streams over HTTP.\n" + "\n" + "ADDRTYPE is \"tcp4\", \"tcp6\", \"fd\", or \"unix\". For tcp4/6, ADDR\n" + "is a socket address in standard notation. For fd, ADDR is \"stdin\",\n" + "\"stdout\", \"stder\", \"systemd\", or an unsigned integer. For unix,\n" + "ADDR is a file path.\n" + "\n" + "The leading directories of the FILENAMEs are stripped; they are served\n" + "at just the base filename. As common sense should eventually suggest,\n" + "the FILENAMEs should be pipes or sockets.\n"); +} + +void cleanup() { + fflush(stderr); + sleep(5); /* work around systemd bug dropping log messages */ +} + +pthread_t main_thread; + +void progname() { + char threadname[16]; + if (pthread_self() != main_thread && pthread_getname_np(pthread_self(), threadname, sizeof(threadname)) == 0) + fprintf(stderr, "%s:%s: ", program_invocation_name, threadname); + else + fprintf(stderr, "%s: ", program_invocation_name); +} + +int main(int argc, char *argv[]) { + if (argc >=2 && strcmp(argv[1], "-h") == 0) { + usage(); + return EXIT_SUCCESS; + } + if (argc < 3) { + dup2(2, 1); + usage(); + return EXIT_FAILURE; + } + + main_thread = pthread_self(); + error_print_progname = progname; + atexit(cleanup); + + sock = sockstream_listen(argv[1], argv[2]); + if (sock < 0) { + if (sock == -EAI_SYSTEM) + error(EXIT_FAILURE, errno, "Opening socket %s %s", argv[1], argv[2]); + else + error(EXIT_FAILURE, 0, "Opening socket %s %s: %s", argv[1], argv[2], gai_strerror(-sock)); + } + + wg_init(&wg); + for (int i = 3; i < argc; i++) + file_add(argv[i]); + + signal(SIGPIPE, SIG_IGN); + signal(SIGTERM, stop); + signal(SIGQUIT, stop); + signal(SIGINT, stop); + while (running) { + int conn = accept(sock, NULL, NULL); + if (conn < 0) + continue; + pthread_t thread; + wg_add(&wg, 1); + pthread_create(&thread, NULL, connection_thread, (void*)(intptr_t)conn); + } + wg_wait(&wg); + for (size_t i = 0; i < filec; i++) + destroy_multipart_replace_stream(filev[i].stream); + free(filev); + return 0; +} diff --git a/src/multipart-replace.c b/src/multipart-replace.c new file mode 100644 index 0000000..9a5e268 --- /dev/null +++ b/src/multipart-replace.c @@ -0,0 +1,168 @@ +/* Copyright 2016 Luke Shumaker */ + +#include +#include +#include +#include +#include +#include +#include + +#include "multipart-replace.h" +#include "util.h" + +#define error(stat, errnum, ...) do { error(0, errnum, __VA_ARGS__); goto end; } while(0) + +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 multipart_replace_reader(struct multipart_replace_stream *s, int fd, const char *_boundary) { + FILE *stream = fdopen(fd, "r"); + char *boundary = boundary_line(_boundary); + + char *line_buf = NULL; + size_t line_cap = 0; + ssize_t line_len = 0; + ssize_t content_length = -1; + + bool first = true; + while (running) { + content_length = -1; + /* scan for the first non-empty line */ + do { + line_len = getline(&line_buf, &line_cap, stream); + if (line_len < 0) + error(EXIT_FAILURE, ferror(stream), "source hung up"); + } while (strcmp(line_buf, "\r\n") == 0); + /* make sure it matches the boundary separator */ + if (first) { + while (strcmp(line_buf, boundary) != 0) { + line_len = getline(&line_buf, &line_cap, stream); + if (line_len < 0) + error(EXIT_FAILURE, ferror(stream), "source hung up"); + } + first = false; + } else { + if (strcmp(line_buf, boundary) != 0) + error(EXIT_FAILURE, 0, "line does not match boundary: \"%s\"", line_buf); + } + /* read the frame header (MIME headers) */ + s->back->len = 0; + do { + line_len = getline(&line_buf, &line_cap, stream); + if (line_len < 0) + error(EXIT_FAILURE, ferror(stream), "source hung up"); + /* 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(EXIT_FAILURE, 0, "did not get frame length"); + /* 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], content_length, 1, stream) != 1) + error(EXIT_FALURE, ferror(stream), "fread(%zd)", s->back->len); + 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); + } + end: + free(boundary); + fclose(stream); +} + +void multipart_replace_writer(struct multipart_replace_stream *s, int fd, const char *_boundary) { + FILE *stream = fdopen(fd, "w"); + struct frame myframe = { 0 }; + long lastframe = 0; + char *boundary = boundary_line(_boundary); + size_t boundary_len = strlen(boundary); + + pthread_rwlock_rdlock(&s->frontlock); + while (running) { + /* 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 (fwrite(boundary, boundary_len, 1, stream) < 1) + error(EXIT_FAILURE, ferror(stream), "fwrite(boundary)"); + if (fwrite(myframe.buf, myframe.len, 1, stream) < 1) + error(EXIT_FAILURE, ferror(stream), "fwrite(frame.buf)"); + /* send a blank line for pleasantness */ + if (fwrite("\r\n", 2, 1, stream) < 1) + error(EXIT_FAILURE, ferror(stream), "fwrite(\"\\r\\n\")"); + fflush(stream); + + /* poll until there's a new frame */ + pthread_rwlock_rdlock(&s->frontlock); + while (s->framecount == lastframe && running) { + pthread_rwlock_unlock(&s->frontlock); + usleep(30000); /* a bit over 30 FPS */ + pthread_rwlock_rdlock(&s->frontlock); + } + } + pthread_rwlock_unlock(&s->frontlock); + end: + free(boundary); + free(myframe.buf); + fclose(stream); +} + +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); + s->framecount = 0; +} + +void destroy_multipart_replace_stream(struct multipart_replace_stream *s) { + free(s->a.buf); + free(s->b.buf); + pthread_rwlock_destroy(&s->frontlock); +} diff --git a/src/multipart-replace.h b/src/multipart-replace.h new file mode 100644 index 0000000..2aa5e61 --- /dev/null +++ b/src/multipart-replace.h @@ -0,0 +1,26 @@ +/* Copyright 2016 Luke Shumaker */ + +#pragma once + +#include +#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(struct multipart_replace_stream *s); +void destroy_multipart_replace_stream(struct multipart_replace_stream *s); + +extern bool running; diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..772dbf8 --- /dev/null +++ b/src/util.c @@ -0,0 +1,72 @@ +/* Copyright 2016 Luke Shumaker */ + +#include /* for isdigit */ +#include +#include +#include + +#include "util.h" + +void *xrealloc(void *ptr, size_t size) +{ + void *ret = realloc(ptr, size); + if (ret == NULL) { + if (ptr==NULL) + error(1, errno, "Could not allocate memory"); + else + error(1, errno, "Could not re-allocate memory"); + } + return ret; +} + +bool is_numeric(const char *str) { + for (size_t i = 0; str[i] != '\0'; i++) + if (!isdigit(str[i])) + return false; + if (str[0] == '\0') + return false; + return true; +} + +int get_fd(const char *addr) { + int sock; + if (strcmp(addr, "stdin") == 0) { + sock = 0; + } else if (strcmp(addr, "stdout") == 0) { + sock = 1; + } else if (strcmp(addr, "stderr") == 0) { + sock = 2; + } else if (strncmp(addr, "systemd", strlen("systemd")) == 0) { + sock = 3; /* :SD_LISTEN_FDS_START */ + addr = &addr[strlen("systemd")]; + switch (addr[0]) { + case '\0': + /* do nothing */ + break; + case ':': + addr = &addr[1]; + if (is_numeric(addr)) { + sock += atoi(addr); + } else { + const char *e = getenv("LISTEN_FDNAMES"); + if (e == NULL) + return -ENOTCONN; + char *names = strdupa(e); + char *name = NULL; + int i = -1; + do { + name = strsep(&names, ":"); + i++; + } while (name != NULL && strcmp(name, addr) != 0); + if (name == NULL) + return -ENOENT; + sock += i; + } + } + } else { + if (!is_numeric(addr)) + return -EINVAL; + sock = atoi(addr); + } + return sock; +} diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..0432a57 --- /dev/null +++ b/src/util.h @@ -0,0 +1,21 @@ +/* Copyright 2016 Luke Shumaker */ + +#pragma once + +#include /* for sig_atomic_t */ +#include /* for memset(3) */ +#include + +#define UNUSED __attribute__((__unused__)) +#define ZERO(x) memset(&(x), 0, sizeof(x)) + +#ifndef _ +#define _(str) str +#endif + +#define log(...) error(0, 0, __VA_ARGS__) +#define debug(...) error(0, 0, __VA_ARGS__) + +void *xrealloc(void *ptr, size_t size); +bool is_numeric(const char *str); +int get_fd(const char *addr); diff --git a/src/wg.c b/src/wg.c new file mode 100644 index 0000000..4f60765 --- /dev/null +++ b/src/wg.c @@ -0,0 +1,45 @@ +#include +#include +#include /* for EXIT_FAILURE */ +#include + +#include "wg.h" + +/* Thread management tools modeled on https://golang.org/pkg/sync/#WaitGroup */ + +/* pthread_cond_t is overly complicated. Just use a self-pipe. */ + +void wg_init(struct wg *wg) { + wg->count = 0; + pthread_mutex_init(&wg->lock, NULL); + int fds[2]; + if (pipe(fds) != 0) + error(EXIT_FAILURE, errno, "pipe"); + wg->fd_wait = fds[0]; + wg->fd_signal = fds[1]; +} + +void wg_add(struct wg *wg, unsigned int n) { + pthread_mutex_lock(&wg->lock); + wg->count += n; + pthread_mutex_unlock(&wg->lock); +} + +void wg_sub(struct wg *wg, unsigned int n) { + pthread_mutex_lock(&wg->lock); + wg->count -= n; + if (wg->count == 0) + if (write(wg->fd_signal, " ", 1) < 1) + error(EXIT_FAILURE, errno, "write"); + pthread_mutex_unlock(&wg->lock); +} + +void wg_wait(struct wg *wg) { + char b; + retry: + if (read(wg->fd_wait, &b, 1) == -1) { + if (errno == EINTR) + goto retry; + error(EXIT_FAILURE, errno, "wg_wait"); + } +} diff --git a/src/wg.h b/src/wg.h new file mode 100644 index 0000000..777e8a3 --- /dev/null +++ b/src/wg.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +/* Thread management tools modeled on https://golang.org/pkg/sync/#WaitGroup */ + +/* pthread_cond_t is overly complicated. Just use a self-pipe. */ + +struct wg { + int count; + pthread_mutex_t lock; + int fd_wait; + int fd_signal; +}; + +void wg_init(struct wg *); +void wg_add(struct wg *, unsigned int); +void wg_sub(struct wg *, unsigned int); +void wg_wait(struct wg*); diff --git a/systemd/.gitignore b/systemd/.gitignore new file mode 100644 index 0000000..b5d3911 --- /dev/null +++ b/systemd/.gitignore @@ -0,0 +1,2 @@ +*.socket +*.service diff --git a/systemd/freenect-server-http.service.in b/systemd/freenect-server-http.service.in new file mode 100644 index 0000000..219e4f3 --- /dev/null +++ b/systemd/freenect-server-http.service.in @@ -0,0 +1,15 @@ +[Unit] +Description=Kinect HTTP media streamer +After=network.target +Requires=freenect-server-http.socket freenect-server.service +PartOf=freenect-server.service +After=freenect-server.service + +[Service] +Type=simple +User=@user@ +Group=@group@ +ExecStart=@bindir@/multipart-replace-http-server fd systemd /run/freenect-server/video.mjpg /run/freenect-server/depth.mjpg /run/freenect-server/accel.mjson + +[Install] +WantedBy=multi-user.target diff --git a/systemd/freenect-server-http.socket.in b/systemd/freenect-server-http.socket.in new file mode 100644 index 0000000..938a0e8 --- /dev/null +++ b/systemd/freenect-server-http.socket.in @@ -0,0 +1,8 @@ +[Unit] +Description=Kinect HTTP media streamer socket + +[Socket] +ListenStream=@httpstream@ + +[Install] +WantedBy=sockets.target diff --git a/systemd/freenect-server.service.in b/systemd/freenect-server.service.in new file mode 100644 index 0000000..5473f7d --- /dev/null +++ b/systemd/freenect-server.service.in @@ -0,0 +1,12 @@ +[Unit] +Description=Kinect media streamer backend +After=network.target +Requires=freenect-server@accel.mjson.socket freenect-server@depth.mjpg.socket freenect-server@video.mjpg.socket + +[Service] +Type=simple +User=@user@ +Group=@group@ +ExecStart=@bindir@/freenect-server -V systemd:freenect-server@video.mjpg.socket -D systemd:freenect-server@depth.mjpg.socket -A systemd:freenect-server@accel.mjson.socket + +Restart=always diff --git a/systemd/freenect-server@.socket.in b/systemd/freenect-server@.socket.in new file mode 100644 index 0000000..bcacd0a --- /dev/null +++ b/systemd/freenect-server@.socket.in @@ -0,0 +1,10 @@ +[Unit] +Description=Kinect media streamer backend %I stream +After=network.target + +[Socket] +SocketUser=@user@ +SocketGroup=@group@ +ListenFIFO=/run/freenect-server/%I +Service=freenect-server.service +RemoveOnStop=true \ No newline at end of file diff --git a/util.c b/util.c deleted file mode 100644 index 772dbf8..0000000 --- a/util.c +++ /dev/null @@ -1,72 +0,0 @@ -/* Copyright 2016 Luke Shumaker */ - -#include /* for isdigit */ -#include -#include -#include - -#include "util.h" - -void *xrealloc(void *ptr, size_t size) -{ - void *ret = realloc(ptr, size); - if (ret == NULL) { - if (ptr==NULL) - error(1, errno, "Could not allocate memory"); - else - error(1, errno, "Could not re-allocate memory"); - } - return ret; -} - -bool is_numeric(const char *str) { - for (size_t i = 0; str[i] != '\0'; i++) - if (!isdigit(str[i])) - return false; - if (str[0] == '\0') - return false; - return true; -} - -int get_fd(const char *addr) { - int sock; - if (strcmp(addr, "stdin") == 0) { - sock = 0; - } else if (strcmp(addr, "stdout") == 0) { - sock = 1; - } else if (strcmp(addr, "stderr") == 0) { - sock = 2; - } else if (strncmp(addr, "systemd", strlen("systemd")) == 0) { - sock = 3; /* :SD_LISTEN_FDS_START */ - addr = &addr[strlen("systemd")]; - switch (addr[0]) { - case '\0': - /* do nothing */ - break; - case ':': - addr = &addr[1]; - if (is_numeric(addr)) { - sock += atoi(addr); - } else { - const char *e = getenv("LISTEN_FDNAMES"); - if (e == NULL) - return -ENOTCONN; - char *names = strdupa(e); - char *name = NULL; - int i = -1; - do { - name = strsep(&names, ":"); - i++; - } while (name != NULL && strcmp(name, addr) != 0); - if (name == NULL) - return -ENOENT; - sock += i; - } - } - } else { - if (!is_numeric(addr)) - return -EINVAL; - sock = atoi(addr); - } - return sock; -} diff --git a/util.h b/util.h deleted file mode 100644 index 0432a57..0000000 --- a/util.h +++ /dev/null @@ -1,21 +0,0 @@ -/* Copyright 2016 Luke Shumaker */ - -#pragma once - -#include /* for sig_atomic_t */ -#include /* for memset(3) */ -#include - -#define UNUSED __attribute__((__unused__)) -#define ZERO(x) memset(&(x), 0, sizeof(x)) - -#ifndef _ -#define _(str) str -#endif - -#define log(...) error(0, 0, __VA_ARGS__) -#define debug(...) error(0, 0, __VA_ARGS__) - -void *xrealloc(void *ptr, size_t size); -bool is_numeric(const char *str); -int get_fd(const char *addr); diff --git a/wg.c b/wg.c deleted file mode 100644 index 4f60765..0000000 --- a/wg.c +++ /dev/null @@ -1,45 +0,0 @@ -#include -#include -#include /* for EXIT_FAILURE */ -#include - -#include "wg.h" - -/* Thread management tools modeled on https://golang.org/pkg/sync/#WaitGroup */ - -/* pthread_cond_t is overly complicated. Just use a self-pipe. */ - -void wg_init(struct wg *wg) { - wg->count = 0; - pthread_mutex_init(&wg->lock, NULL); - int fds[2]; - if (pipe(fds) != 0) - error(EXIT_FAILURE, errno, "pipe"); - wg->fd_wait = fds[0]; - wg->fd_signal = fds[1]; -} - -void wg_add(struct wg *wg, unsigned int n) { - pthread_mutex_lock(&wg->lock); - wg->count += n; - pthread_mutex_unlock(&wg->lock); -} - -void wg_sub(struct wg *wg, unsigned int n) { - pthread_mutex_lock(&wg->lock); - wg->count -= n; - if (wg->count == 0) - if (write(wg->fd_signal, " ", 1) < 1) - error(EXIT_FAILURE, errno, "write"); - pthread_mutex_unlock(&wg->lock); -} - -void wg_wait(struct wg *wg) { - char b; - retry: - if (read(wg->fd_wait, &b, 1) == -1) { - if (errno == EINTR) - goto retry; - error(EXIT_FAILURE, errno, "wg_wait"); - } -} diff --git a/wg.h b/wg.h deleted file mode 100644 index 777e8a3..0000000 --- a/wg.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include - -/* Thread management tools modeled on https://golang.org/pkg/sync/#WaitGroup */ - -/* pthread_cond_t is overly complicated. Just use a self-pipe. */ - -struct wg { - int count; - pthread_mutex_t lock; - int fd_wait; - int fd_signal; -}; - -void wg_init(struct wg *); -void wg_add(struct wg *, unsigned int); -void wg_sub(struct wg *, unsigned int); -void wg_wait(struct wg*); diff --git a/write-ifchanged b/write-ifchanged new file mode 100755 index 0000000..c65fa16 --- /dev/null +++ b/write-ifchanged @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +outfile=$1 +tmpfile="$(dirname "$outfile")/.tmp${outfile##*/}" + +cat > "$tmpfile" || exit $? +if cmp -s "$tmpfile" "$outfile"; then + rm -f "$tmpfile" || : +else + mv -f "$tmpfile" "$outfile" +fi -- cgit v1.2.3