diff options
Diffstat (limited to 'daemon.c')
-rw-r--r-- | daemon.c | 553 |
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; +} |