summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOlivier Gayot <duskcoder@gmail.com>2016-01-27 20:39:25 +0100
committerOlivier Gayot <duskcoder@gmail.com>2016-01-27 20:39:51 +0100
commite8d769d68a1d23aa4b595a6aa5c2548801a7d7ed (patch)
treebb7e98b57f2ffa2f90e3c8ec73617c068e5dda7f
parent34f3ab900a9cac78acb7a10d1ed60555d98ff822 (diff)
caod: imported basics for websocket management
Signed-off-by: Olivier Gayot <duskcoder@gmail.com>
-rw-r--r--Makefile71
-rw-r--r--daemon.c553
-rw-r--r--log.c109
-rw-r--r--log.h37
-rw-r--r--misc.c65
-rw-r--r--misc.h58
-rw-r--r--platform.h35
7 files changed, 928 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..1be6b8e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,71 @@
+## Copyright (C) 2014-2016
+## Olivier Gayot <ogayot@baylibre.com>
+## Bartosz Golaszewski <bgolaszewski@baylibre.com>
+## Olivier Gayot <olivier.gayot@sigexec.com>
+##
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License along
+## with this program; if not, write to the Free Software Foundation, Inc.,
+## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+CC = $(CROSS_COMPILE)gcc
+USERFLAGS =
+CFLAGS += -I./include -I/usr/local/include -W -Wall -Wextra
+CFLAGS += -D_GNU_SOURCE $(USERFLAGS)
+LDFLAGS += -L./lib -L/usr/local/lib -lwebsockets -lconfig $(USERFLAGS)
+PROGNAME = caod
+OBJS = daemon.o log.o misc.o
+
+ifdef DEBUG
+CFLAGS += -g$(DEBUG) -D CONFIG_DEBUG -O0
+else
+CFLAGS += -O2
+endif
+
+all: $(PROGNAME)
+
+$(PROGNAME): $(OBJS)
+ $(CC) -o $@ $(OBJS) $(LDFLAGS)
+
+clean:
+ $(RM) $(OBJS)
+ $(RM) $(PROGNAME)
+
+mrproper: clean
+
+help:
+ @echo "CAO daemon Makefile targets:"
+ @echo
+ @echo "Build:"
+ @echo " all\t\t- build all cao objects, libraries and executables,"
+ @echo
+ @echo "Cleaning:"
+ @echo " clean\t\t- remove caod objects and executables,"
+ @echo " libclean\t- remove external library build files,"
+ @echo " mrproper\t- clean everything,"
+ @echo
+ @echo "Makefile variables:"
+ @echo " CROSS_COMPILE\t- cross-toolchain prefix,"
+ @echo " ARCH\t\t- build architecture,"
+ @echo " DEBUG\t\t- compile additional debug messages and pass the -g"
+ @echo " \t\t flag to gcc, you can also specify a level for said flag,"
+ @echo
+
+.PHONY: all clean mrproper help
+.PRECIOUS: %.c
+.SUFFIXES:
+.SUFFIXES: .o .c
+.DEFAULT_GOAL :=
+.DEFAULT_GOAL := all
+
+.c.o:
+ $(CC) -c -o $*.o $(CFLAGS) $(DEBUGFLAGS) $*.c
diff --git a/daemon.c b/daemon.c
new file mode 100644
index 0000000..dc4cc5a
--- /dev/null
+++ b/daemon.c
@@ -0,0 +1,553 @@
+/*
+ * Copyright (C) 2014
+ * Olivier Gayot <ogayot@baylibre.com>
+ * Bartosz Golaszewski <bgolaszewski@baylibre.com>
+ * Olivier Gayot <olivier.gayot@sigexec.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * This software is based on Andy Green's test-server available here
+ * http://git.libwebsockets.org/cgi-bin/cgit/libwebsockets/
+ */
+
+#include <libwebsockets.h>
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <signal.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sysexits.h>
+#include <getopt.h>
+#include <limits.h>
+#include <errno.h>
+#include <sys/wait.h>
+#include <stdarg.h>
+#include <time.h>
+
+#include "misc.h"
+#include "log.h"
+
+#define DEFAULT_PORT 8080
+#define DEFAULT_PAGES_DIR "/var/www/"
+#define DEFAULT_HOMEPAGE "index.html"
+#define DEFAULT_CONFIG_FILE "/etc/caod/config.cfg"
+#define DEFAULT_SAMPLING_RATE 1 /* one ms */
+
+#define DEFAULT_CLIENT_REFRESH_RATE 100 /* in ms */
+
+#define USER_DATA_BUFSIZE 1048576
+
+struct per_session_data_cao_prot {
+ bool handshake_ok: 1;
+
+ /*
+ * libwebsockets requires a padding before and after the buffer.
+ * Since we want to be able to use sizeof(buffer) anyway, we will
+ * make a packed structure containing the buffer and its padding.
+ */
+ struct {
+ unsigned char _pre_padding[LWS_SEND_BUFFER_PRE_PADDING];
+ unsigned char buffer[USER_DATA_BUFSIZE];
+ unsigned char _post_padding[LWS_SEND_BUFFER_POST_PADDING];
+ };
+
+ unsigned short len;
+
+ struct {
+ unsigned short sps; /* snaps per second */
+ } params;
+};
+
+struct mime_type {
+ char *ext;
+ size_t ext_len;
+ char *type;
+};
+
+struct pws_event {
+ bool set;
+ int probe_id;
+ int state;
+};
+
+static bool daemonize_g = false;
+static int verbosity_g = 0;
+static volatile bool asked_to_quit_g = false;
+static const char *pages_dir_g = DEFAULT_PAGES_DIR;
+static const char *homepage_g = DEFAULT_HOMEPAGE;
+static const char *config_file_g = DEFAULT_CONFIG_FILE;
+static struct lws_context *context_g;
+
+#define MIME_EXT_ARGS(_ext) _ext, (sizeof(_ext) - 1)
+
+static struct mime_type mimes_g[] = {
+ { MIME_EXT_ARGS(".ico"), "image/x-icon", },
+ { MIME_EXT_ARGS(".png"), "image/png" },
+ { MIME_EXT_ARGS(".html"), "text/html" },
+ { MIME_EXT_ARGS(".css"), "text/css" },
+ { MIME_EXT_ARGS(".js"), "application/javascript" },
+#define SHORTEST_MIME_EXT 3 /* size of the shortest extension (incl. the dot) */
+};
+
+#undef MIME_EXT_ARGS
+
+/* return a string describing the mime type if supported, otherwise NULL */
+static const char *get_mimetype(const char *filename)
+{
+ int n, i;
+
+ n = strlen(filename);
+ if (n < SHORTEST_MIME_EXT + 1)
+ return NULL;
+
+ for (i = 0; i < countof(mimes_g); ++i) {
+ const struct mime_type *mimep = &mimes_g[i];
+
+ if (!strcmp(&filename[n - mimep->ext_len], mimep->ext)) {
+ return mimep->type;
+ }
+ }
+
+ return NULL;
+}
+
+static int callback_http_only(struct lws *wsi,
+ enum lws_callback_reasons reason, void *user UNUSED_PARAM,
+ void *in, size_t len)
+{
+ const char *mimetype;
+ static char path[PATH_MAX];
+ static char real_path[PATH_MAX];
+ size_t path_len;
+ size_t def_path_len;
+
+ switch (reason) {
+ case LWS_CALLBACK_HTTP:
+
+ if (len < 1) {
+ lws_return_http_status(wsi,
+ HTTP_STATUS_BAD_REQUEST, NULL);
+ return -1;
+ }
+
+ /* serve either the specified page or the default one */
+ if (strcmp(in, "/") != 0) {
+ snprintf(path, sizeof(path), "%s/%s", pages_dir_g,
+ (char *)in);
+ } else {
+ snprintf(path, sizeof(path), "%s/%s", pages_dir_g,
+ homepage_g);
+ }
+
+ /*
+ * avoid a security issue: "jail" inside pages_dir_g
+ * XXX: beware, a race condition might occur between realpath() and
+ * open() which is called by lwss_serve_http_file()
+ */
+ if (realpath(path, real_path) == NULL) {
+ lws_return_http_status(wsi,
+ HTTP_STATUS_NOT_FOUND, NULL);
+ return -1;
+ }
+ path_len = strlen(real_path);
+ def_path_len = strlen(pages_dir_g);
+ if (path_len <= def_path_len ||
+ strncmp(real_path, pages_dir_g, def_path_len) != 0)
+ {
+ lws_return_http_status(wsi,
+ HTTP_STATUS_FORBIDDEN, NULL);
+
+ return -1;
+ }
+
+ /* check if the mimetype if supported */
+ mimetype = get_mimetype(path);
+ if (mimetype == NULL) {
+ caod_err("Unknown mimetype for %s", path);
+ lws_return_http_status(wsi,
+ HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL);
+
+ return -1;
+ }
+
+ if (lws_serve_http_file(wsi, path, mimetype, NULL, 0))
+ {
+ /*
+ * close the socket if either an error occured or the file has
+ * been sent completely
+ */
+ return -1;
+ }
+
+ break;
+ case LWS_CALLBACK_HTTP_BODY:
+ break;
+ case LWS_CALLBACK_HTTP_BODY_COMPLETION:
+ break;
+ case LWS_CALLBACK_HTTP_FILE_COMPLETION:
+ break;
+ case LWS_CALLBACK_HTTP_WRITEABLE:
+ break;
+ case LWS_CALLBACK_FILTER_NETWORK_CONNECTION:
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void set_shunt_vals(int *vals)
+{
+ FILE *shfd;
+ int numvals = 8;
+ int i;
+ FILE *chfd;
+ char channels[8];
+ int hwind = 1;
+ ssize_t rd;
+
+ chfd = fopen("/etc/caod/channels", "r");
+ if (!chfd)
+ goto err;
+
+ shfd = fopen("/etc/caod/shunt_vals", "w");
+ if (!shfd)
+ goto err;
+
+ for (i = 0; i < numvals; i++) {
+ if (vals[i] > 0) {
+ fprintf(shfd, "%d\n", vals[i]);
+ }
+ }
+ fclose(shfd);
+
+ rd = fread(channels, 1, sizeof(channels), chfd);
+ if (rd != sizeof(channels)) {
+ goto err;
+ }
+ fclose(chfd);
+
+ for (i = 0; i < numvals; i++) {
+ if (channels[i] == 'I') {
+ char cmd[256];
+
+ snprintf(cmd, sizeof(cmd),
+ "/root/cao-suite/scripts/cao_ctl.py set_shunt %d %d %d",
+ hwind, i+1, vals[i]);
+ caod_info("Executing: %s", cmd);
+ system(cmd);
+ hwind++;
+ } else if (channels[i] == 'T') {
+ hwind++;
+ }
+ }
+
+ sync();
+
+ return;
+
+err:
+ caod_err("Error setting shunt values");
+}
+
+static int callback_cao_prot(struct lws *wsi UNUSED_PARAM,
+ enum lws_callback_reasons reason,
+ void *user, void *in UNUSED_PARAM, size_t len UNUSED_PARAM)
+{
+ struct per_session_data_cao_prot *user_data = user;
+
+ switch (reason) {
+ case LWS_CALLBACK_ESTABLISHED:
+ user_data->params.sps = 0;
+ user_data->handshake_ok = false;
+ break;
+ case LWS_CALLBACK_CLOSED:
+ break;
+ case LWS_CALLBACK_SERVER_WRITEABLE:
+ break;
+ case LWS_CALLBACK_RECEIVE:
+ set_shunt_vals(in);
+ break;
+ case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static struct lws_context_creation_info info_g = {
+ .port = DEFAULT_PORT,
+ .iface = NULL,
+ /* list the protocols available */
+ .protocols = (struct lws_protocols[]) {
+ {
+ .name = "http-only", /* name */
+ .callback = callback_http_only,
+ .per_session_data_size = 0,
+ }, {
+ .name = "cao-prot",
+ .callback = callback_cao_prot,
+ .per_session_data_size =
+ sizeof(struct per_session_data_cao_prot),
+ }, { /* empty field needed at the end */
+ .name = NULL,
+ .callback = NULL,
+ .per_session_data_size = 0,
+ },
+ },
+ .options = 0,
+};
+
+/* display an usage message and exit */
+NORETURN static void usage(const char *progname)
+{
+ fprintf(stderr, "Usage: %s [OPTIONS]\n"
+ "\n"
+ "-p - override the default port (%d),\n"
+ "-h - display this help and exit,\n"
+ "-d - fork in the background,\n"
+ "-v - increase verbosity (1-info, 2-debug),\n"
+ "-w - set the path to the WWW directory (default: '%s'),\n"
+ "-H - set the filename of the homepage in the www directory\n"
+ "-c - specify a custom path to the config file (default: '%s'),"
+ " (default: '%s').\n",
+ progname,
+ DEFAULT_PORT,
+ DEFAULT_PAGES_DIR,
+ DEFAULT_HOMEPAGE,
+ DEFAULT_CONFIG_FILE);
+
+ exit(EX_USAGE);
+}
+
+static void errmsg_and_usage(const char *progname, const char *fmt, ...)
+{
+ va_list va;
+
+ va_start(va, fmt);
+ vfprintf(stderr, fmt, va);
+ fprintf(stderr, "\n");
+ va_end(va);
+
+ usage(progname);
+}
+
+/*
+ * search options passed to the program and handle them
+ * argc will be modified accordingly
+ * argv will be returned shifted accordingly
+ */
+static char **parse_opt(int *argc, char *argv[])
+{
+ int opt;
+ char *errptr;
+
+ while ((opt = getopt(*argc, argv, "v:hdp:w:H:c:")) != -1) {
+ switch (opt) {
+ case 'p':
+ /* change the default port */
+ info_g.port = strtol(optarg, &errptr, 10);
+
+ if (*errptr != '\0') {
+ usage(argv[0]);
+ }
+
+ break;
+ case 'h':
+ /* display the help (and exit) */
+ usage(argv[0]);
+ break;
+ case 'd':
+ /* fork on the background */
+ daemonize_g = true;
+ break;
+ case 'v':
+ /* increase verbosity */
+ if ((strcmp(optarg, "1") == 0) || (strcmp(optarg, "2") == 0))
+ verbosity_g = atoi(optarg);
+ else
+ errmsg_and_usage(argv[0], "Invalid verbosity level\n");
+ break;
+ case 'w':
+ if (optarg[0] != '/')
+ errmsg_and_usage(argv[0],
+ "Must specify an absolute path for -D\n");
+ pages_dir_g = optarg;
+ break;
+ case 'H':
+ homepage_g = optarg;
+ break;
+ case 'c':
+ config_file_g = optarg;
+ break;
+ default:
+ /* noreturn */
+ usage(argv[0]);
+ }
+ }
+
+ *argc -= optind;
+
+ return &(argv[optind]);
+}
+
+static void handle_signal(int sig)
+{
+ switch (sig) {
+ case SIGINT:
+ case SIGTERM:
+ asked_to_quit_g = true;
+ break;
+ }
+}
+
+static int callback_http_on_bug(struct lws *wsi,
+ enum lws_callback_reasons reason,
+ void *user UNUSED_PARAM, void *in UNUSED_PARAM, size_t len)
+{
+ char path[256];
+
+ memset(path, 0, sizeof(path));
+ snprintf(path, sizeof(path), "%s/error.html", pages_dir_g);
+
+ switch (reason) {
+ case LWS_CALLBACK_HTTP:
+
+ if (len < 1) {
+ lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL);
+ return -1;
+ }
+
+ if (lws_serve_http_file(wsi, path, "text/html", NULL, 0)) {
+ /*
+ * close the socket if either an error occured or the file has
+ * been sent completely
+ */
+ return -1;
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static struct lws_context_creation_info info_bug = {
+ .port = DEFAULT_PORT,
+ .iface = NULL,
+ /* list the protocols available */
+ .protocols = (struct lws_protocols[]) {
+ {
+ .name = "http-only", /* name */
+ .callback = callback_http_on_bug,
+ .per_session_data_size = 0,
+ }, { /* empty field needed at the end */
+ .name = NULL,
+ .callback = NULL,
+ .per_session_data_size = 0,
+ },
+ },
+ .options = 0,
+};
+
+/*
+ * If an error happened - let this function run eternally to inform users
+ * about the bug via HTTP on default port.
+ */
+static void bug_on(void)
+{
+ struct lws_context *context;
+
+ if ((context = lws_create_context(&info_bug)) == NULL) {
+ caod_err("lws init failed");
+ abort();
+ }
+
+ caod_err("Entering bug_on state...");
+ while (!asked_to_quit_g) {
+ if (lws_service(context, 5) < 0) {
+ caod_err("lws_service() returned with an error");
+ }
+ }
+}
+
+/*
+ * The general idea is to read the configuration and create the data model,
+ * then create a pipe and fork. The child process will then try to stick to
+ * the given sampling rate and perform the measurements on time, feeding all
+ * the results into the pipe, while the parent will read from it, store these
+ * values in a buffer and send them periodically to the client.
+ *
+ * The buffer size should be (client_refresh_rate / sampling_rate). The buffer
+ * is only allocated in the parent process, although both processes share
+ * the rest of the data model.
+ */
+int main(int argc, char *argv[])
+{
+ const char *progname = argv[0];
+ int ret = EXIT_SUCCESS;
+ int dbglvl;
+
+ argv = parse_opt(&argc, argv);
+ /* XXX argc and argv have been shifted */
+
+ /* no argument are accepted for now on */
+ if (argc > 0) {
+ usage(progname);
+ }
+
+ if (daemonize_g && lws_daemonize("/tmp/.caod-lock") != 0) {
+ caod_err("failed to daemonize");
+ return -1;
+ }
+
+ caod_setup_logs();
+ dbglvl = LLL_ERR | LLL_WARN | LLL_NOTICE;
+
+ switch (verbosity_g) {
+ case 2: dbglvl |= LLL_DEBUG; /* Fall through. */
+ case 1: dbglvl |= LLL_INFO;
+ }
+
+ lws_set_log_level(dbglvl, &caod_log_hook);
+
+ signal(SIGINT, &handle_signal);
+ signal(SIGTERM, &handle_signal);
+
+ if ((context_g = lws_create_context(&info_g)) == NULL) {
+ caod_err("lws init failed");
+ ret = EXIT_FAILURE;
+ return ret;
+ }
+
+ while (!asked_to_quit_g) {
+ if (lws_service(context_g, 5) < 0) {
+ caod_err("lws_service() returned with an error");
+ ret = EXIT_FAILURE;
+ break;
+ }
+ }
+
+ lws_context_destroy(context_g);
+
+ return ret;
+}
diff --git a/log.c b/log.c
new file mode 100644
index 0000000..bae3fb5
--- /dev/null
+++ b/log.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2014-2016
+ * Olivier Gayot <ogayot@baylibre.com>
+ * Bartosz Golaszewski <bgolaszewski@baylibre.com>
+ * Olivier Gayot <olivier.gayot@sigexec.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <libwebsockets.h>
+
+#include "log.h"
+
+#define PRINT_FROM_VA(STREAM, LVL, FMT) \
+ do { \
+ va_list va; \
+ va_start(va, FMT); \
+ fprintf(STREAM, "caod ["LVL"] PID: %d\t", getpid()); \
+ vfprintf(STREAM, FMT, va); \
+ fprintf(STREAM, "\n"); \
+ va_end(va); \
+ } while (0)
+
+static const char* const logpath = "/tmp/caod.log";
+static FILE* logfd;
+
+static void close_log(void)
+{
+ fclose(logfd);
+}
+
+void caod_setup_logs(void)
+{
+ (void)unlink(logpath);
+ logfd = fopen(logpath, "w");
+ if (logfd)
+ atexit(&close_log);
+}
+
+void caod_info(const char *fmt, ...)
+{
+ PRINT_FROM_VA(stdout, "INFO", fmt);
+ if (logfd) {
+ PRINT_FROM_VA(logfd, "INFO", fmt);
+ }
+}
+
+void caod_warn(const char *fmt, ...)
+{
+ PRINT_FROM_VA(stdout, "WARN", fmt);
+ if (logfd) {
+ PRINT_FROM_VA(logfd, "WARN", fmt);
+ }
+}
+
+void caod_err(const char *fmt, ...)
+{
+ PRINT_FROM_VA(stdout, "ERR", fmt);
+ if (logfd) {
+ PRINT_FROM_VA(logfd, "ERR", fmt);
+ }
+}
+
+void caod_log_hook(int level, const char *line)
+{
+ char buf[256];
+ int last;
+
+ memset(buf, 0, sizeof(buf));
+ strncpy(buf, line, sizeof(buf));
+ last = strlen(buf)-1;
+ if (buf[last] == '\n') {
+ buf[last] = '\0';
+ }
+
+ switch (level) {
+ case LLL_DEBUG:
+ case LLL_INFO:
+ case LLL_NOTICE:
+ caod_info("%s", buf);
+ break;
+ case LLL_WARN:
+ caod_warn("%s", buf);
+ break;
+ case LLL_ERR:
+ caod_err("%s", buf);
+ break;
+ default:
+ break;
+ }
+}
diff --git a/log.h b/log.h
new file mode 100644
index 0000000..fbacbc1
--- /dev/null
+++ b/log.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2014-2016
+ * Olivier Gayot <ogayot@baylibre.com>
+ * Bartosz Golaszewski <bgolaszewski@baylibre.com>
+ * Olivier Gayot <olivier.gayot@sigexec.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef LOG_H
+#define LOG_H
+
+#include "platform.h"
+
+/* Message printing helpers. */
+
+void caod_setup_logs(void);
+
+void caod_info(const char *fmt, ...) CAOD_PRINTF_FUNC(1, 2);
+void caod_warn(const char *fmt, ...) CAOD_PRINTF_FUNC(1, 2);
+void caod_err(const char *fmt, ...) CAOD_PRINTF_FUNC(1, 2);
+
+void caod_log_hook(int level, const char *line);
+
+#endif /* LOG_H */
diff --git a/misc.c b/misc.c
new file mode 100644
index 0000000..9570e44
--- /dev/null
+++ b/misc.c
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2014-2016
+ * Olivier Gayot <ogayot@baylibre.com>
+ * Bartosz Golaszewski <bgolaszewski@baylibre.com>
+ * Olivier Gayot <olivier.gayot@sigexec.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/select.h>
+
+#include "misc.h"
+
+#define SELECT_INIT(FDSET, SOCK) \
+ do { \
+ FD_ZERO(&(FDSET)); \
+ FD_SET((SOCK), &(FDSET)); \
+ } while (0)
+
+#define SELECT_LOOP(RETVAL, CALL, TV) \
+ do { \
+ do { \
+ (RETVAL) = (CALL); \
+ if ((((RETVAL) < 0) && (errno != EINTR)) || (RETVAL > 0)) { \
+ break; \
+ } \
+ } while ((TV)->tv_sec || (TV)->tv_usec); \
+ } while(0)
+
+int caod_fd_wrready(int sock, struct timeval *tv)
+{
+ fd_set wr_set;
+ int r;
+
+ SELECT_INIT(wr_set, sock);
+ SELECT_LOOP(r, select(sock+1, NULL, &wr_set, NULL, tv), tv);
+
+ return r;
+}
+
+int caod_fd_rdready(int sock, struct timeval *tv)
+{
+ fd_set rd_set;
+ int r;
+
+ SELECT_INIT(rd_set, sock);
+ SELECT_LOOP(r, select(sock+1, &rd_set, NULL, NULL, tv), tv);
+
+ return r;
+}
diff --git a/misc.h b/misc.h
new file mode 100644
index 0000000..7a95681
--- /dev/null
+++ b/misc.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2014-2016
+ * Olivier Gayot <ogayot@baylibre.com>
+ * Bartosz Golaszewski <bgolaszewski@baylibre.com>
+ * Olivier Gayot <olivier.gayot@sigexec.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MISC_H
+#define MISC_H
+
+#include <sys/time.h>
+
+#define ssizeof(_foo) ((signed long)(sizeof((_foo))))
+#define countof(_array) (ssizeof((_array)) / ssizeof((_array)[0]))
+
+#define unsafe_free(_ptr) free((_ptr))
+#define safe_free(_ptr) \
+ do { \
+ free((_ptr)); \
+ (_ptr) = NULL; \
+ } while (0)
+
+#ifdef CONFIG_DEBUG
+#define IF_DEBUG(...) __VA_ARGS__
+#else
+#define IF_DEBUG(...)
+#endif
+
+#define TV_FMT "(%d.%d)"
+#define TV_PRINT(TV) (int)(TV).tv_sec, (int)(TV).tv_usec
+
+/*
+ * Returns 1 if a file descriptor is ready for writing, 0 otherwise or -1
+ * on error.
+ */
+int caod_fd_wrready(int sock, struct timeval *tv);
+
+/*
+ * Returns 1 if a file descriptor is ready for reading, 0 otherwise or -1
+ * on error.
+ */
+int caod_fd_rdready(int sock, struct timeval *tv);
+
+#endif /* MISC_H */
diff --git a/platform.h b/platform.h
new file mode 100644
index 0000000..0594c59
--- /dev/null
+++ b/platform.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014-2016
+ * Bartosz Golaszewski <bgolaszewski@baylibre.com>
+ * Olivier Gayot <olivier.gayot@sigexec.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PLATFORM_H
+#define PLATFORM_H
+
+#ifdef __GNUC__
+#define UNUSED_PARAM __attribute__((__unused__))
+#define NORETURN __attribute__((noreturn))
+#define CAOD_PRINTF_FUNC(FORMAT, PARAMS) \
+ __attribute__((format(printf, FORMAT, PARAMS)))
+#else
+#define UNUSED_PARAM
+#define NORETURN
+#define CAOD_PRINTF_FUNC(FORMAT, PARAMS)
+#endif
+
+#endif /* PLATFORM_H */