summaryrefslogtreecommitdiff
path: root/daemon.c
diff options
context:
space:
mode:
Diffstat (limited to 'daemon.c')
-rw-r--r--daemon.c553
1 files changed, 553 insertions, 0 deletions
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;
+}