From 38def9103245607e2c4bd718f34aceac86576e19 Mon Sep 17 00:00:00 2001 From: Stephen Olesen Date: Sun, 23 Oct 2016 13:36:05 -0600 Subject: [PATCH] Initial commit for RTLMux. --- Dockerfile | 11 + Makefile | 41 +++ cmdline.c | 797 ++++++++++++++++++++++++++++++++++++++++++++++++++++ cmdline.h | 210 ++++++++++++++ config.c | 44 +++ config.h | 17 ++ main.c | 10 + options.ggo | 13 + rtlmux.c | 428 ++++++++++++++++++++++++++++ rtlmux.h | 11 + slog.c | 425 ++++++++++++++++++++++++++++ slog.h | 150 ++++++++++ 12 files changed, 2157 insertions(+) create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 cmdline.c create mode 100644 cmdline.h create mode 100644 config.c create mode 100644 config.h create mode 100644 main.c create mode 100644 options.ggo create mode 100644 rtlmux.c create mode 100644 rtlmux.h create mode 100644 slog.c create mode 100644 slog.h diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..99727e8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM alpine + +RUN apk --no-cache add libevent + +COPY . /app + +WORKDIR /app + +RUN apk --no-cache add --virtual build-dependencies build-base libevent-dev bsd-compat-headers \ + && make \ + && apk del build-dependencies diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3af835d --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ +CC=gcc +CFLAGS=-Wall -I/usr/local/include + +LIBS=-pthread `pkg-config libevent --libs-only-L` -levent -levent_pthreads + +ifeq ($(shell uname -m),armv7l) + CFLAGS+=-O3 -mfpu=neon-vfpv4 -mfloat-abi=hard -march=armv7-a -ffast-math -funsafe-math-optimizations +else + CFLAGS+=-O3 +endif + +ifneq ($(shell uname),Darwin) + LIBS+=-lrt +endif + +ifeq ($(shell uname),Darwin) + CFLAGS+=-glldb +else + CFLAGS+=-ggdb3 +endif + +SRC=slog.c rtlmux.c config.c cmdline.c main.c +OBJS=slog.o rtlmux.o config.o cmdline.o main.o + +all: cmdline.c rtlmux + +clean: + rm -f rtlmux $(OBJS) + +depend: + makedepend -- $(CFLAGS) -- $(SRC) + +cmdline.h: options.ggo + gengetopt -C -i $< -f cmdline.c + +cmdline.c: cmdline.h + +rtlmux: $(OBJS) + $(CC) $(CFLAGS) -o $@ $^ $(LIBS) + +# DO NOT DELETE diff --git a/cmdline.c b/cmdline.c new file mode 100644 index 0000000..c9d38ba --- /dev/null +++ b/cmdline.c @@ -0,0 +1,797 @@ +/* + File autogenerated by gengetopt version 2.22.6 + generated with the following command: + gengetopt -C -i options.ggo -f cmdline.c + + The developers of gengetopt consider the fixed text that goes in all + gengetopt output files to be in the public domain: + we make no copyright claims on it. +*/ + +/* If we use autoconf. */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#ifndef FIX_UNUSED +#define FIX_UNUSED(X) (void) (X) /* avoid warnings for unused params */ +#endif + +#include + +#include "cmdline.h" + +const char *gengetopt_args_info_purpose = "Relay RTL TCP data to multiple clients."; + +const char *gengetopt_args_info_usage = "Usage: rtlmux [OPTIONS]..."; + +const char *gengetopt_args_info_versiontext = "(c) 2016 Stephen Olesen"; + +const char *gengetopt_args_info_description = "This will connect to an rtl_tcp server and allow multiple end clients to\nconnect and control the RTL."; + +const char *gengetopt_args_info_help[] = { + " --help Print help and exit", + " -V, --version Print version and exit", + " -p, --port=port rtl_tcp port (default=`1234')", + " -h, --host=address rtl_tcp host address (default=`localhost')", + " -l, --listen=port Listening port for client connections (default=`7878')", + 0 +}; + +typedef enum {ARG_NO + , ARG_STRING + , ARG_INT +} cmdline_c_arg_type; + +static +void clear_given (struct gengetopt_args_info *args_info); +static +void clear_args (struct gengetopt_args_info *args_info); + +static int +cmdline_c_internal (int argc, char **argv, struct gengetopt_args_info *args_info, + struct cmdline_c_params *params, const char *additional_error); + +struct line_list +{ + char * string_arg; + struct line_list * next; +}; + +static struct line_list *cmd_line_list = 0; +static struct line_list *cmd_line_list_tmp = 0; + +static void +free_cmd_list(void) +{ + /* free the list of a previous call */ + if (cmd_line_list) + { + while (cmd_line_list) { + cmd_line_list_tmp = cmd_line_list; + cmd_line_list = cmd_line_list->next; + free (cmd_line_list_tmp->string_arg); + free (cmd_line_list_tmp); + } + } +} + + +static char * +gengetopt_strdup (const char *s); + +static +void clear_given (struct gengetopt_args_info *args_info) +{ + args_info->help_given = 0 ; + args_info->version_given = 0 ; + args_info->port_given = 0 ; + args_info->host_given = 0 ; + args_info->listen_given = 0 ; +} + +static +void clear_args (struct gengetopt_args_info *args_info) +{ + FIX_UNUSED (args_info); + args_info->port_arg = 1234; + args_info->port_orig = NULL; + args_info->host_arg = gengetopt_strdup ("localhost"); + args_info->host_orig = NULL; + args_info->listen_arg = 7878; + args_info->listen_orig = NULL; + +} + +static +void init_args_info(struct gengetopt_args_info *args_info) +{ + + + args_info->help_help = gengetopt_args_info_help[0] ; + args_info->version_help = gengetopt_args_info_help[1] ; + args_info->port_help = gengetopt_args_info_help[2] ; + args_info->host_help = gengetopt_args_info_help[3] ; + args_info->listen_help = gengetopt_args_info_help[4] ; + +} + +void +cmdline_c_print_version (void) +{ + printf ("%s %s\n", + (strlen(CMDLINE_C_PACKAGE_NAME) ? CMDLINE_C_PACKAGE_NAME : CMDLINE_C_PACKAGE), + CMDLINE_C_VERSION); + + if (strlen(gengetopt_args_info_versiontext) > 0) + printf("\n%s\n", gengetopt_args_info_versiontext); +} + +static void print_help_common(void) { + cmdline_c_print_version (); + + if (strlen(gengetopt_args_info_purpose) > 0) + printf("\n%s\n", gengetopt_args_info_purpose); + + if (strlen(gengetopt_args_info_usage) > 0) + printf("\n%s\n", gengetopt_args_info_usage); + + printf("\n"); + + if (strlen(gengetopt_args_info_description) > 0) + printf("%s\n\n", gengetopt_args_info_description); +} + +void +cmdline_c_print_help (void) +{ + int i = 0; + print_help_common(); + while (gengetopt_args_info_help[i]) + printf("%s\n", gengetopt_args_info_help[i++]); +} + +void +cmdline_c_init (struct gengetopt_args_info *args_info) +{ + clear_given (args_info); + clear_args (args_info); + init_args_info (args_info); +} + +void +cmdline_c_params_init(struct cmdline_c_params *params) +{ + if (params) + { + params->override = 0; + params->initialize = 1; + params->check_required = 1; + params->check_ambiguity = 0; + params->print_errors = 1; + } +} + +struct cmdline_c_params * +cmdline_c_params_create(void) +{ + struct cmdline_c_params *params = + (struct cmdline_c_params *)malloc(sizeof(struct cmdline_c_params)); + cmdline_c_params_init(params); + return params; +} + +static void +free_string_field (char **s) +{ + if (*s) + { + free (*s); + *s = 0; + } +} + + +static void +cmdline_c_release (struct gengetopt_args_info *args_info) +{ + + free_string_field (&(args_info->port_orig)); + free_string_field (&(args_info->host_arg)); + free_string_field (&(args_info->host_orig)); + free_string_field (&(args_info->listen_orig)); + + + + clear_given (args_info); +} + + +static void +write_into_file(FILE *outfile, const char *opt, const char *arg, const char *values[]) +{ + FIX_UNUSED (values); + if (arg) { + fprintf(outfile, "%s=\"%s\"\n", opt, arg); + } else { + fprintf(outfile, "%s\n", opt); + } +} + + +int +cmdline_c_dump(FILE *outfile, struct gengetopt_args_info *args_info) +{ + int i = 0; + + if (!outfile) + { + fprintf (stderr, "%s: cannot dump options to stream\n", CMDLINE_C_PACKAGE); + return EXIT_FAILURE; + } + + if (args_info->help_given) + write_into_file(outfile, "help", 0, 0 ); + if (args_info->version_given) + write_into_file(outfile, "version", 0, 0 ); + if (args_info->port_given) + write_into_file(outfile, "port", args_info->port_orig, 0); + if (args_info->host_given) + write_into_file(outfile, "host", args_info->host_orig, 0); + if (args_info->listen_given) + write_into_file(outfile, "listen", args_info->listen_orig, 0); + + + i = EXIT_SUCCESS; + return i; +} + +int +cmdline_c_file_save(const char *filename, struct gengetopt_args_info *args_info) +{ + FILE *outfile; + int i = 0; + + outfile = fopen(filename, "w"); + + if (!outfile) + { + fprintf (stderr, "%s: cannot open file for writing: %s\n", CMDLINE_C_PACKAGE, filename); + return EXIT_FAILURE; + } + + i = cmdline_c_dump(outfile, args_info); + fclose (outfile); + + return i; +} + +void +cmdline_c_free (struct gengetopt_args_info *args_info) +{ + cmdline_c_release (args_info); +} + +/** @brief replacement of strdup, which is not standard */ +char * +gengetopt_strdup (const char *s) +{ + char *result = 0; + if (!s) + return result; + + result = (char*)malloc(strlen(s) + 1); + if (result == (char*)0) + return (char*)0; + strcpy(result, s); + return result; +} + +int +cmdline_c (int argc, char **argv, struct gengetopt_args_info *args_info) +{ + return cmdline_c2 (argc, argv, args_info, 0, 1, 1); +} + +int +cmdline_c_ext (int argc, char **argv, struct gengetopt_args_info *args_info, + struct cmdline_c_params *params) +{ + int result; + result = cmdline_c_internal (argc, argv, args_info, params, 0); + + if (result == EXIT_FAILURE) + { + cmdline_c_free (args_info); + exit (EXIT_FAILURE); + } + + return result; +} + +int +cmdline_c2 (int argc, char **argv, struct gengetopt_args_info *args_info, int override, int initialize, int check_required) +{ + int result; + struct cmdline_c_params params; + + params.override = override; + params.initialize = initialize; + params.check_required = check_required; + params.check_ambiguity = 0; + params.print_errors = 1; + + result = cmdline_c_internal (argc, argv, args_info, ¶ms, 0); + + if (result == EXIT_FAILURE) + { + cmdline_c_free (args_info); + exit (EXIT_FAILURE); + } + + return result; +} + +int +cmdline_c_required (struct gengetopt_args_info *args_info, const char *prog_name) +{ + FIX_UNUSED (args_info); + FIX_UNUSED (prog_name); + return EXIT_SUCCESS; +} + + +static char *package_name = 0; + +/** + * @brief updates an option + * @param field the generic pointer to the field to update + * @param orig_field the pointer to the orig field + * @param field_given the pointer to the number of occurrence of this option + * @param prev_given the pointer to the number of occurrence already seen + * @param value the argument for this option (if null no arg was specified) + * @param possible_values the possible values for this option (if specified) + * @param default_value the default value (in case the option only accepts fixed values) + * @param arg_type the type of this option + * @param check_ambiguity @see cmdline_c_params.check_ambiguity + * @param override @see cmdline_c_params.override + * @param no_free whether to free a possible previous value + * @param multiple_option whether this is a multiple option + * @param long_opt the corresponding long option + * @param short_opt the corresponding short option (or '-' if none) + * @param additional_error possible further error specification + */ +static +int update_arg(void *field, char **orig_field, + unsigned int *field_given, unsigned int *prev_given, + char *value, const char *possible_values[], + const char *default_value, + cmdline_c_arg_type arg_type, + int check_ambiguity, int override, + int no_free, int multiple_option, + const char *long_opt, char short_opt, + const char *additional_error) +{ + char *stop_char = 0; + const char *val = value; + int found; + char **string_field; + FIX_UNUSED (field); + + stop_char = 0; + found = 0; + + if (!multiple_option && prev_given && (*prev_given || (check_ambiguity && *field_given))) + { + if (short_opt != '-') + fprintf (stderr, "%s: `--%s' (`-%c') option given more than once%s\n", + package_name, long_opt, short_opt, + (additional_error ? additional_error : "")); + else + fprintf (stderr, "%s: `--%s' option given more than once%s\n", + package_name, long_opt, + (additional_error ? additional_error : "")); + return 1; /* failure */ + } + + FIX_UNUSED (default_value); + + if (field_given && *field_given && ! override) + return 0; + if (prev_given) + (*prev_given)++; + if (field_given) + (*field_given)++; + if (possible_values) + val = possible_values[found]; + + switch(arg_type) { + case ARG_INT: + if (val) *((int *)field) = strtol (val, &stop_char, 0); + break; + case ARG_STRING: + if (val) { + string_field = (char **)field; + if (!no_free && *string_field) + free (*string_field); /* free previous string */ + *string_field = gengetopt_strdup (val); + } + break; + default: + break; + }; + + /* check numeric conversion */ + switch(arg_type) { + case ARG_INT: + if (val && !(stop_char && *stop_char == '\0')) { + fprintf(stderr, "%s: invalid numeric value: %s\n", package_name, val); + return 1; /* failure */ + } + break; + default: + ; + }; + + /* store the original value */ + switch(arg_type) { + case ARG_NO: + break; + default: + if (value && orig_field) { + if (no_free) { + *orig_field = value; + } else { + if (*orig_field) + free (*orig_field); /* free previous string */ + *orig_field = gengetopt_strdup (value); + } + } + }; + + return 0; /* OK */ +} + + +int +cmdline_c_internal ( + int argc, char **argv, struct gengetopt_args_info *args_info, + struct cmdline_c_params *params, const char *additional_error) +{ + int c; /* Character of the parsed option. */ + + int error_occurred = 0; + struct gengetopt_args_info local_args_info; + + int override; + int initialize; + int check_required; + int check_ambiguity; + + package_name = argv[0]; + + override = params->override; + initialize = params->initialize; + check_required = params->check_required; + check_ambiguity = params->check_ambiguity; + + if (initialize) + cmdline_c_init (args_info); + + cmdline_c_init (&local_args_info); + + optarg = 0; + optind = 0; + opterr = params->print_errors; + optopt = '?'; + + while (1) + { + int option_index = 0; + + static struct option long_options[] = { + { "help", 0, NULL, 0 }, + { "version", 0, NULL, 'V' }, + { "port", 1, NULL, 'p' }, + { "host", 1, NULL, 'h' }, + { "listen", 1, NULL, 'l' }, + { 0, 0, 0, 0 } + }; + + c = getopt_long (argc, argv, "Vp:h:l:", long_options, &option_index); + + if (c == -1) break; /* Exit from `while (1)' loop. */ + + switch (c) + { + case 'V': /* Print version and exit. */ + cmdline_c_print_version (); + cmdline_c_free (&local_args_info); + exit (EXIT_SUCCESS); + + case 'p': /* rtl_tcp port. */ + + + if (update_arg( (void *)&(args_info->port_arg), + &(args_info->port_orig), &(args_info->port_given), + &(local_args_info.port_given), optarg, 0, "1234", ARG_INT, + check_ambiguity, override, 0, 0, + "port", 'p', + additional_error)) + goto failure; + + break; + case 'h': /* rtl_tcp host address. */ + + + if (update_arg( (void *)&(args_info->host_arg), + &(args_info->host_orig), &(args_info->host_given), + &(local_args_info.host_given), optarg, 0, "localhost", ARG_STRING, + check_ambiguity, override, 0, 0, + "host", 'h', + additional_error)) + goto failure; + + break; + case 'l': /* Listening port for client connections. */ + + + if (update_arg( (void *)&(args_info->listen_arg), + &(args_info->listen_orig), &(args_info->listen_given), + &(local_args_info.listen_given), optarg, 0, "7878", ARG_INT, + check_ambiguity, override, 0, 0, + "listen", 'l', + additional_error)) + goto failure; + + break; + + case 0: /* Long option with no short option */ + if (strcmp (long_options[option_index].name, "help") == 0) { + cmdline_c_print_help (); + cmdline_c_free (&local_args_info); + exit (EXIT_SUCCESS); + } + + case '?': /* Invalid option. */ + /* `getopt_long' already printed an error message. */ + goto failure; + + default: /* bug: option not considered. */ + fprintf (stderr, "%s: option unknown: %c%s\n", CMDLINE_C_PACKAGE, c, (additional_error ? additional_error : "")); + abort (); + } /* switch */ + } /* while */ + + + + + cmdline_c_release (&local_args_info); + + if ( error_occurred ) + return (EXIT_FAILURE); + + return 0; + +failure: + + cmdline_c_release (&local_args_info); + return (EXIT_FAILURE); +} + +#ifndef CONFIG_FILE_LINE_SIZE +#define CONFIG_FILE_LINE_SIZE 2048 +#endif +#define ADDITIONAL_ERROR " in configuration file " + +#define CONFIG_FILE_LINE_BUFFER_SIZE (CONFIG_FILE_LINE_SIZE+3) +/* 3 is for "--" and "=" */ + +static int +_cmdline_c_configfile (const char *filename, int *my_argc) +{ + FILE* file; + char my_argv[CONFIG_FILE_LINE_BUFFER_SIZE+1]; + char linebuf[CONFIG_FILE_LINE_SIZE]; + int line_num = 0; + int result = 0, equal; + char *fopt, *farg; + char *str_index; + size_t len, next_token; + char delimiter; + + if ((file = fopen(filename, "r")) == 0) + { + fprintf (stderr, "%s: Error opening configuration file '%s'\n", + CMDLINE_C_PACKAGE, filename); + return EXIT_FAILURE; + } + + while ((fgets(linebuf, CONFIG_FILE_LINE_SIZE, file)) != 0) + { + ++line_num; + my_argv[0] = '\0'; + len = strlen(linebuf); + if (len > (CONFIG_FILE_LINE_BUFFER_SIZE-1)) + { + fprintf (stderr, "%s:%s:%d: Line too long in configuration file\n", + CMDLINE_C_PACKAGE, filename, line_num); + result = EXIT_FAILURE; + break; + } + + /* find first non-whitespace character in the line */ + next_token = strspn (linebuf, " \t\r\n"); + str_index = linebuf + next_token; + + if ( str_index[0] == '\0' || str_index[0] == '#') + continue; /* empty line or comment line is skipped */ + + fopt = str_index; + + /* truncate fopt at the end of the first non-valid character */ + next_token = strcspn (fopt, " \t\r\n="); + + if (fopt[next_token] == '\0') /* the line is over */ + { + farg = 0; + equal = 0; + goto noarg; + } + + /* remember if equal sign is present */ + equal = (fopt[next_token] == '='); + fopt[next_token++] = '\0'; + + /* advance pointers to the next token after the end of fopt */ + next_token += strspn (fopt + next_token, " \t\r\n"); + + /* check for the presence of equal sign, and if so, skip it */ + if ( !equal ) + if ((equal = (fopt[next_token] == '='))) + { + next_token++; + next_token += strspn (fopt + next_token, " \t\r\n"); + } + str_index += next_token; + + /* find argument */ + farg = str_index; + if ( farg[0] == '\"' || farg[0] == '\'' ) + { /* quoted argument */ + str_index = strchr (++farg, str_index[0] ); /* skip opening quote */ + if (! str_index) + { + fprintf + (stderr, + "%s:%s:%d: unterminated string in configuration file\n", + CMDLINE_C_PACKAGE, filename, line_num); + result = EXIT_FAILURE; + break; + } + } + else + { /* read up the remaining part up to a delimiter */ + next_token = strcspn (farg, " \t\r\n#\'\""); + str_index += next_token; + } + + /* truncate farg at the delimiter and store it for further check */ + delimiter = *str_index, *str_index++ = '\0'; + + /* everything but comment is illegal at the end of line */ + if (delimiter != '\0' && delimiter != '#') + { + str_index += strspn(str_index, " \t\r\n"); + if (*str_index != '\0' && *str_index != '#') + { + fprintf + (stderr, + "%s:%s:%d: malformed string in configuration file\n", + CMDLINE_C_PACKAGE, filename, line_num); + result = EXIT_FAILURE; + break; + } + } + + noarg: + if (!strcmp(fopt,"include")) { + if (farg && *farg) { + result = _cmdline_c_configfile(farg, my_argc); + } else { + fprintf(stderr, "%s:%s:%d: include requires a filename argument.\n", + CMDLINE_C_PACKAGE, filename, line_num); + } + continue; + } + len = strlen(fopt); + strcat (my_argv, len > 1 ? "--" : "-"); + strcat (my_argv, fopt); + if (len > 1 && ((farg && *farg) || equal)) + strcat (my_argv, "="); + if (farg && *farg) + strcat (my_argv, farg); + ++(*my_argc); + + cmd_line_list_tmp = (struct line_list *) malloc (sizeof (struct line_list)); + cmd_line_list_tmp->next = cmd_line_list; + cmd_line_list = cmd_line_list_tmp; + cmd_line_list->string_arg = gengetopt_strdup(my_argv); + } /* while */ + + if (file) + fclose(file); + return result; +} + +int +cmdline_c_configfile ( + const char *filename, + struct gengetopt_args_info *args_info, + int override, int initialize, int check_required) +{ + struct cmdline_c_params params; + + params.override = override; + params.initialize = initialize; + params.check_required = check_required; + params.check_ambiguity = 0; + params.print_errors = 1; + + return cmdline_c_config_file (filename, args_info, ¶ms); +} + +int +cmdline_c_config_file (const char *filename, + struct gengetopt_args_info *args_info, + struct cmdline_c_params *params) +{ + int i, result; + int my_argc = 1; + char **my_argv_arg; + char *additional_error; + + /* store the program name */ + cmd_line_list_tmp = (struct line_list *) malloc (sizeof (struct line_list)); + cmd_line_list_tmp->next = cmd_line_list; + cmd_line_list = cmd_line_list_tmp; + cmd_line_list->string_arg = gengetopt_strdup (CMDLINE_C_PACKAGE); + + result = _cmdline_c_configfile(filename, &my_argc); + + if (result != EXIT_FAILURE) { + my_argv_arg = (char **) malloc((my_argc+1) * sizeof(char *)); + cmd_line_list_tmp = cmd_line_list; + + for (i = my_argc - 1; i >= 0; --i) { + my_argv_arg[i] = cmd_line_list_tmp->string_arg; + cmd_line_list_tmp = cmd_line_list_tmp->next; + } + + my_argv_arg[my_argc] = 0; + + additional_error = (char *)malloc(strlen(filename) + strlen(ADDITIONAL_ERROR) + 1); + strcpy (additional_error, ADDITIONAL_ERROR); + strcat (additional_error, filename); + result = + cmdline_c_internal (my_argc, my_argv_arg, args_info, + params, + additional_error); + + free (additional_error); + free (my_argv_arg); + } + + free_cmd_list(); + if (result == EXIT_FAILURE) + { + cmdline_c_free (args_info); + exit (EXIT_FAILURE); + } + + return result; +} diff --git a/cmdline.h b/cmdline.h new file mode 100644 index 0000000..b7f6710 --- /dev/null +++ b/cmdline.h @@ -0,0 +1,210 @@ +/** @file cmdline.h + * @brief The header file for the command line option parser + * generated by GNU Gengetopt version 2.22.6 + * http://www.gnu.org/software/gengetopt. + * DO NOT modify this file, since it can be overwritten + * @author GNU Gengetopt by Lorenzo Bettini */ + +#ifndef CMDLINE_H +#define CMDLINE_H + +/* If we use autoconf. */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include /* for FILE */ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#ifndef CMDLINE_C_PACKAGE +/** @brief the program name (used for printing errors) */ +#define CMDLINE_C_PACKAGE "rtlmux" +#endif + +#ifndef CMDLINE_C_PACKAGE_NAME +/** @brief the complete program name (used for help and version) */ +#define CMDLINE_C_PACKAGE_NAME "rtlmux" +#endif + +#ifndef CMDLINE_C_VERSION +/** @brief the program version */ +#define CMDLINE_C_VERSION "1.0.0" +#endif + +/** @brief Where the command line options are stored */ +struct gengetopt_args_info +{ + const char *help_help; /**< @brief Print help and exit help description. */ + const char *version_help; /**< @brief Print version and exit help description. */ + int port_arg; /**< @brief rtl_tcp port (default='1234'). */ + char * port_orig; /**< @brief rtl_tcp port original value given at command line. */ + const char *port_help; /**< @brief rtl_tcp port help description. */ + char * host_arg; /**< @brief rtl_tcp host address (default='localhost'). */ + char * host_orig; /**< @brief rtl_tcp host address original value given at command line. */ + const char *host_help; /**< @brief rtl_tcp host address help description. */ + int listen_arg; /**< @brief Listening port for client connections (default='7878'). */ + char * listen_orig; /**< @brief Listening port for client connections original value given at command line. */ + const char *listen_help; /**< @brief Listening port for client connections help description. */ + + unsigned int help_given ; /**< @brief Whether help was given. */ + unsigned int version_given ; /**< @brief Whether version was given. */ + unsigned int port_given ; /**< @brief Whether port was given. */ + unsigned int host_given ; /**< @brief Whether host was given. */ + unsigned int listen_given ; /**< @brief Whether listen was given. */ + +} ; + +/** @brief The additional parameters to pass to parser functions */ +struct cmdline_c_params +{ + int override; /**< @brief whether to override possibly already present options (default 0) */ + int initialize; /**< @brief whether to initialize the option structure gengetopt_args_info (default 1) */ + int check_required; /**< @brief whether to check that all required options were provided (default 1) */ + int check_ambiguity; /**< @brief whether to check for options already specified in the option structure gengetopt_args_info (default 0) */ + int print_errors; /**< @brief whether getopt_long should print an error message for a bad option (default 1) */ +} ; + +/** @brief the purpose string of the program */ +extern const char *gengetopt_args_info_purpose; +/** @brief the usage string of the program */ +extern const char *gengetopt_args_info_usage; +/** @brief the description string of the program */ +extern const char *gengetopt_args_info_description; +/** @brief all the lines making the help output */ +extern const char *gengetopt_args_info_help[]; + +/** + * The command line parser + * @param argc the number of command line options + * @param argv the command line options + * @param args_info the structure where option information will be stored + * @return 0 if everything went fine, NON 0 if an error took place + */ +int cmdline_c (int argc, char **argv, + struct gengetopt_args_info *args_info); + +/** + * The command line parser (version with additional parameters - deprecated) + * @param argc the number of command line options + * @param argv the command line options + * @param args_info the structure where option information will be stored + * @param override whether to override possibly already present options + * @param initialize whether to initialize the option structure my_args_info + * @param check_required whether to check that all required options were provided + * @return 0 if everything went fine, NON 0 if an error took place + * @deprecated use cmdline_c_ext() instead + */ +int cmdline_c2 (int argc, char **argv, + struct gengetopt_args_info *args_info, + int override, int initialize, int check_required); + +/** + * The command line parser (version with additional parameters) + * @param argc the number of command line options + * @param argv the command line options + * @param args_info the structure where option information will be stored + * @param params additional parameters for the parser + * @return 0 if everything went fine, NON 0 if an error took place + */ +int cmdline_c_ext (int argc, char **argv, + struct gengetopt_args_info *args_info, + struct cmdline_c_params *params); + +/** + * Save the contents of the option struct into an already open FILE stream. + * @param outfile the stream where to dump options + * @param args_info the option struct to dump + * @return 0 if everything went fine, NON 0 if an error took place + */ +int cmdline_c_dump(FILE *outfile, + struct gengetopt_args_info *args_info); + +/** + * Save the contents of the option struct into a (text) file. + * This file can be read by the config file parser (if generated by gengetopt) + * @param filename the file where to save + * @param args_info the option struct to save + * @return 0 if everything went fine, NON 0 if an error took place + */ +int cmdline_c_file_save(const char *filename, + struct gengetopt_args_info *args_info); + +/** + * Print the help + */ +void cmdline_c_print_help(void); +/** + * Print the version + */ +void cmdline_c_print_version(void); + +/** + * Initializes all the fields a cmdline_c_params structure + * to their default values + * @param params the structure to initialize + */ +void cmdline_c_params_init(struct cmdline_c_params *params); + +/** + * Allocates dynamically a cmdline_c_params structure and initializes + * all its fields to their default values + * @return the created and initialized cmdline_c_params structure + */ +struct cmdline_c_params *cmdline_c_params_create(void); + +/** + * Initializes the passed gengetopt_args_info structure's fields + * (also set default values for options that have a default) + * @param args_info the structure to initialize + */ +void cmdline_c_init (struct gengetopt_args_info *args_info); +/** + * Deallocates the string fields of the gengetopt_args_info structure + * (but does not deallocate the structure itself) + * @param args_info the structure to deallocate + */ +void cmdline_c_free (struct gengetopt_args_info *args_info); + +/** + * The config file parser (deprecated version) + * @param filename the name of the config file + * @param args_info the structure where option information will be stored + * @param override whether to override possibly already present options + * @param initialize whether to initialize the option structure my_args_info + * @param check_required whether to check that all required options were provided + * @return 0 if everything went fine, NON 0 if an error took place + * @deprecated use cmdline_c_config_file() instead + */ +int cmdline_c_configfile (const char *filename, + struct gengetopt_args_info *args_info, + int override, int initialize, int check_required); + +/** + * The config file parser + * @param filename the name of the config file + * @param args_info the structure where option information will be stored + * @param params additional parameters for the parser + * @return 0 if everything went fine, NON 0 if an error took place + */ +int cmdline_c_config_file (const char *filename, + struct gengetopt_args_info *args_info, + struct cmdline_c_params *params); + +/** + * Checks that all the required options were specified + * @param args_info the structure to check + * @param prog_name the name of the program that will be used to print + * possible errors + * @return + */ +int cmdline_c_required (struct gengetopt_args_info *args_info, + const char *prog_name); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* CMDLINE_H */ diff --git a/config.c b/config.c new file mode 100644 index 0000000..ac3baf3 --- /dev/null +++ b/config.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include + +#include + +#include "cmdline.h" +#include "config.h" + +#include "slog.h" + +struct config config; + +static struct gengetopt_args_info args; + +int convertConfig(struct gengetopt_args_info *args) { + config.host = args->host_arg; + config.port = args->port_arg; + config.clientPort = args->listen_arg; + + return 1; +} + +int parseConfig(int argc, char **argv) { + int cr; + struct cmdline_c_params params; + cmdline_c_params_init(¶ms); + params.initialize = 1; + params.override = 0; + params.check_required = 0; + params.check_ambiguity = 0; + if((cr = cmdline_c_ext(argc, argv, &args, ¶ms)) != 0) { + exit(2); + } + + if(cmdline_c_required(&args, argv[0]) != 0) { + printf("Please try again."); + exit(4); + } + + return convertConfig(&args); +} diff --git a/config.h b/config.h new file mode 100644 index 0000000..f26505a --- /dev/null +++ b/config.h @@ -0,0 +1,17 @@ +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +#include +#include + +struct config { + char *host; + uint16_t port; + uint16_t clientPort; +}; + +extern struct config config; + +extern int parseConfig(int,char **); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..10f7f32 --- /dev/null +++ b/main.c @@ -0,0 +1,10 @@ +#include "slog.h" +#include "config.h" + +#include "rtlmux.h" + +int main(int argc, char **argv) { + parseConfig(argc, argv); + slog_init(NULL, NULL, LOG_EXTRA, LOG_DEBUG, 1); + serverThread(0); +} diff --git a/options.ggo b/options.ggo new file mode 100644 index 0000000..a0bfc0e --- /dev/null +++ b/options.ggo @@ -0,0 +1,13 @@ +package "rtlmux" +version "1.0.0" +purpose "Relay RTL TCP data to multiple clients." +description "This will connect to an rtl_tcp server and allow multiple end clients to connect and control the RTL." + +versiontext "(c) 2016 Stephen Olesen" + +option "port" p "rtl_tcp port" + int typestr="port" default="1234" optional +option "host" h "rtl_tcp host address" + string typestr="address" default="localhost" optional +option "listen" l "Listening port for client connections" + int typestr="port" default="7878" optional diff --git a/rtlmux.c b/rtlmux.c new file mode 100644 index 0000000..cddab9b --- /dev/null +++ b/rtlmux.c @@ -0,0 +1,428 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "config.h" + +#include "slog.h" + +#include "rtlmux.h" + +#include + +struct event_base *event_base = NULL; +struct bufferevent *serverConnection = NULL; + +struct rtlData { + LIST_ENTRY(rtlData) next; + uint32_t references; + uint32_t len; + uint8_t *data; +}; +static LIST_HEAD(rtlDataHead, rtlData) rtlDataList = LIST_HEAD_INITIALIZER(rtlDataList); +static pthread_rwlock_t rtlDataLock; + +#define CLIENT_UNKNOWN 1 +#define CLIENT_READY 2 +#define CLIENT_INIT 4 +struct client { + LIST_ENTRY(client) peer; + struct bufferevent *bev; + union { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + }; + uint32_t flags; +}; + +static LIST_HEAD(clienthead, client) clients = LIST_HEAD_INITIALIZER(clients); +static pthread_rwlock_t clientLock; + +static struct client *addClient(struct bufferevent *bev, void *ptr) { + uint32_t clientFlags = CLIENT_INIT; + + struct client *client = (struct client *)calloc(1, sizeof(struct client)); + + client->bev = bev; + client->flags = clientFlags; + + pthread_rwlock_wrlock(&clientLock); + LIST_INSERT_HEAD(&clients, client, peer); + pthread_rwlock_unlock(&clientLock); + + return client; +} + +static void removeClient(struct client *client) { + if(!client) + return; + + pthread_rwlock_wrlock(&clientLock); + LIST_REMOVE(client, peer); + free(client); + pthread_rwlock_unlock(&clientLock); +} + +void releaseDataRef(const void *d, unsigned long len, void *ptr) { + struct rtlData *data = (struct rtlData *)ptr; + --data->references; + if(data->references == 0) { + pthread_rwlock_wrlock(&rtlDataLock); + LIST_REMOVE(data, next); + pthread_rwlock_unlock(&rtlDataLock); + free(data); // This is a single malloc for both the data and header + } +} + +int sendDataToAllClients(struct rtlData *data) { + struct client *client; + pthread_rwlock_rdlock(&clientLock); + LIST_FOREACH(client, &clients, peer) { + if(client->flags == CLIENT_READY) { + struct evbuffer *ev = bufferevent_get_output(client->bev); + ++data->references; + evbuffer_add_reference(ev, data->data, data->len, releaseDataRef, data); + } + } + pthread_rwlock_unlock(&clientLock); + return data->references; +} + +void sendToAllClients(char *buf, size_t len, uint32_t flags) { + struct client *client; + pthread_rwlock_rdlock(&clientLock); + LIST_FOREACH(client, &clients, peer) { + if((client->flags & flags) != 0) + bufferevent_write(client->bev, buf, len); + } + pthread_rwlock_unlock(&clientLock); +} + +static void logCB(int severity, const char *msg) { + int level; + int flag; + switch(severity) { + case EVENT_LOG_DEBUG: level = LOG_DEBUG; flag = SLOG_DEBUG; break; + case EVENT_LOG_MSG: level = LOG_INFO; flag = SLOG_INFO; break; + case EVENT_LOG_WARN: level = LOG_WARN; flag = SLOG_WARN; break; + case EVENT_LOG_ERR: level = LOG_ERROR; flag = SLOG_ERROR; break; + default: level = LOG_LIVE; flag = LOG_LIVE; break; + } + + slog(level, flag, msg); +} + +struct serverInfo { + enum { SERVER_NEW, SERVER_CONNECTED, SERVER_DISCONNECTED } state; + char magic[4]; + uint32_t tuner_type; + uint32_t tuner_gain_count; + struct { + unsigned int value; + unsigned char set; + } params[0xd]; // Store all the parameters as a simple command array +} serverInfo; + +static void serverErrorEventCB(struct bufferevent *, short, void *); +static void serverReadCB(struct bufferevent *, void *); + +static void connectToServer(void *arg) { + struct bufferevent **serverConnection = (struct bufferevent **)arg; + *serverConnection = bufferevent_socket_new(event_base, -1, BEV_OPT_CLOSE_ON_FREE); + bufferevent_socket_connect_hostname(*serverConnection, NULL, AF_UNSPEC, config.host, config.port); + bufferevent_setcb(*serverConnection, serverReadCB, NULL, serverErrorEventCB, serverConnection); + bufferevent_setwatermark(*serverConnection, EV_READ, 16384, 8*1024*1024); + bufferevent_enable(*serverConnection, EV_READ|EV_WRITE); +} + +static void connectToServerCB(int a, short b, void *arg) { + connectToServer(arg); +} + +static void connectToServerSoon(void *ctx) { + struct event *ev; + struct timeval tv; + + tv.tv_sec = 1; + tv.tv_usec = 0; + + ev = evtimer_new(event_base, connectToServerCB, ctx); + evtimer_add(ev, &tv); +} + +static void serverReadCB(struct bufferevent *bev, void *ctx) { + struct rtlData *data; + + if(serverInfo.state == SERVER_NEW) { + bufferevent_read(bev, serverInfo.magic, 4); + bufferevent_read(bev, &serverInfo.tuner_type, 4); + bufferevent_read(bev, &serverInfo.tuner_gain_count, 4); + if(serverInfo.magic[0] == 'R' && serverInfo.magic[1] == 'T' && serverInfo.magic[2] == 'L' && serverInfo.magic[3] == '0') { + serverInfo.state = SERVER_CONNECTED; + slog(LOG_INFO, SLOG_INFO, "Connected to server."); + } else { // Failed to receive the magic header + slog(LOG_ERROR, SLOG_ERROR, "Failed to receive magic header from server."); + bufferevent_free(bev); + connectToServerSoon(ctx); + return; + } + // Send stored and set parameters on reconnect + int i; + for(i = 0; i < 0xd; i++) { + if(serverInfo.params[i].set) { + struct command cmd; + cmd.cmd = i+1; + cmd.param = serverInfo.params[i].value; + slog(LOG_INFO, SLOG_INFO, "Sending command %d with param %lu", cmd.cmd, ntohl(cmd.param)); + bufferevent_write(bev, &cmd, sizeof(cmd)); + } + } + } + + data = (struct rtlData *)calloc(1, sizeof(struct rtlData) + 16384); + data->data = (void *)data + sizeof(struct rtlData); + data->references = 0; + data->len = bufferevent_read(bev, data->data, 16384); + + if(sendDataToAllClients(data) == 0) { + free(data); + } else { + pthread_rwlock_wrlock(&rtlDataLock); + LIST_INSERT_HEAD(&rtlDataList, data, next); + pthread_rwlock_unlock(&rtlDataLock); + } +} + +static void serverErrorEventCB(struct bufferevent *bev, short events, void *ctx) { + if (events & BEV_EVENT_ERROR) + slog(LOG_ERROR, SLOG_ERROR, "Error from server side bufferevent: %s", strerror(errno)); + if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) { + bufferevent_free(bev); + slog(LOG_INFO, SLOG_INFO, "Disconnecting server."); + serverInfo.state = SERVER_NEW; + connectToServerSoon(ctx); + } +} + +static void errorEventCB(struct bufferevent *bev, short events, void *ctx) { + if (events & BEV_EVENT_ERROR) + slog(LOG_ERROR, SLOG_ERROR, "Error from bufferevent: %s", strerror(errno)); + if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) { + struct client *client = (struct client *)ctx; + char ipBuf[128]; + if(client->sa.sa_family == AF_INET) + evutil_inet_ntop(client->sa.sa_family, &client->sin.sin_addr, ipBuf, 128); + else if(client->sa.sa_family == AF_INET6) + evutil_inet_ntop(client->sa.sa_family, &client->sin6.sin6_addr, ipBuf, 128); + else + snprintf(ipBuf, 128, "from unknown address"); + slog(LOG_INFO, SLOG_INFO, "Disconnecting client %s", ipBuf); + bufferevent_free(bev); + removeClient(client); + } +} + +void serverSendCommand(struct command cmd) { + serverInfo.params[cmd.cmd-1].value = cmd.param; + serverInfo.params[cmd.cmd-1].set = 1; + slog(LOG_LIVE, SLOG_DEBUG, "Sending command to server: %d: %lu", cmd.cmd, ntohl(cmd.param)); + bufferevent_write(serverConnection, &cmd, sizeof(cmd)); +} + +#define RTL_FREQUENCY 0x01 +#define RTL_SAMPLE_RATE 0x02 +#define RTL_GAIN_MODE 0x03 +#define RTL_GAIN 0x04 +#define RTL_FREQ_CORRECTION 0x05 +#define RTL_STAGE_GAIN 0x06 +#define RTL_TEST_MODE 0x07 +#define RTL_AGC_MODE 0x08 +#define RTL_DIRECT_SAMPLING 0x09 +#define RTL_OFFSET_TUNING 0x0a +#define RTL_XTAL 0x0b +#define RTL_TUNER_XTAL 0x0c +#define RTL_GAIN_BY_INDEX 0x0d + +static void clientReadCB(struct bufferevent *bev, void *ctx) { + struct command cmd; + while(bufferevent_read(bev, &cmd, sizeof(cmd)) > 0) { + slog(LOG_INFO, SLOG_INFO, "Read from client: %x", cmd.cmd); + + switch(cmd.cmd) { + case RTL_FREQUENCY: // Frequency + slog(LOG_INFO, SLOG_INFO, "Set frequency: %lu", ntohl(cmd.param)); + serverSendCommand(cmd); + break; + case RTL_SAMPLE_RATE: // Sample rate + slog(LOG_INFO, SLOG_INFO, "Set sample rate: %lu", ntohl(cmd.param)); + serverSendCommand(cmd); + break; + case RTL_GAIN_MODE: // Gain mode + slog(LOG_INFO, SLOG_INFO, "Set gain mode: %lu", ntohl(cmd.param)); + serverSendCommand(cmd); + break; + case RTL_GAIN: // Set Gain + slog(LOG_INFO, SLOG_INFO, "Set gain: %lu", ntohl(cmd.param)); + serverSendCommand(cmd); + break; + case RTL_FREQ_CORRECTION: // Set freq correction + slog(LOG_INFO, SLOG_INFO, "Set freq correction: %lu", ntohl(cmd.param)); + serverSendCommand(cmd); + break; + case RTL_STAGE_GAIN: // Stage Gain + slog(LOG_INFO, SLOG_INFO, "Set stage gain: %lu", ntohl(cmd.param)); + serverSendCommand(cmd); + break; + case RTL_TEST_MODE: // Test mode + slog(LOG_INFO, SLOG_INFO, "Set test mode: %lu", ntohl(cmd.param)); + break; + case RTL_AGC_MODE: // AGC mode + slog(LOG_INFO, SLOG_INFO, "Set AGC mode: %lu", ntohl(cmd.param)); + serverSendCommand(cmd); + break; + case RTL_DIRECT_SAMPLING: // Direct sampling + slog(LOG_INFO, SLOG_INFO, "Set direct sampling: %lu", ntohl(cmd.param)); + break; + case RTL_OFFSET_TUNING: // Offset tuning + slog(LOG_INFO, SLOG_INFO, "Set offset tuning: %lu", ntohl(cmd.param)); + serverSendCommand(cmd); + break; + case RTL_XTAL: // RTL Xtal + slog(LOG_INFO, SLOG_INFO, "Set RTL xtal: %lu", ntohl(cmd.param)); + serverSendCommand(cmd); + break; + case RTL_TUNER_XTAL: // Tuner Xtal + slog(LOG_INFO, SLOG_INFO, "Set tuner xtal: %lu", ntohl(cmd.param)); + serverSendCommand(cmd); + break; + case RTL_GAIN_BY_INDEX: // Gain by index + slog(LOG_INFO, SLOG_INFO, "Set gain by index: %lu", ntohl(cmd.param)); + serverSendCommand(cmd); + break; + default: // Ignore it + slog(LOG_INFO, SLOG_INFO, "Ignored client command: %x", cmd.cmd); + } + } +} + +static void connectCB(struct evconnlistener *listener, + evutil_socket_t sock, struct sockaddr *addr, int len, void *ptr) { + struct event_base *base = evconnlistener_get_base(listener); + struct bufferevent *bev = bufferevent_socket_new( + base, sock, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE); + + struct client *client = addClient(bev, ptr); + memcpy(&client->sa, addr, len); + char ipBuf[128]; + if(client->sa.sa_family == AF_INET) + evutil_inet_ntop(client->sa.sa_family, &client->sin.sin_addr, ipBuf, 128); + else if(client->sa.sa_family == AF_INET6) + evutil_inet_ntop(client->sa.sa_family, &client->sin6.sin6_addr, ipBuf, 128); + else + snprintf(ipBuf, 128, "from unknown address"); + slog(LOG_INFO, SLOG_INFO, "Connection from client %s", ipBuf); + bufferevent_setcb(bev, clientReadCB, NULL, errorEventCB, client); + bufferevent_enable(bev, EV_READ|EV_WRITE); + bufferevent_write(bev, serverInfo.magic, 4); + bufferevent_write(bev, &serverInfo.tuner_type, 4); + bufferevent_write(bev, &serverInfo.tuner_gain_count, 4); + client->flags = CLIENT_READY; +} + +void *serverThread(void *arg) { + int timeToExit = 0; + + memset(&serverInfo, 0, sizeof(serverInfo)); + + slog(LOG_INFO, SLOG_INFO, "Starting server thread."); + + LIST_INIT(&rtlDataList); + LIST_INIT(&clients); + + pthread_rwlock_init(&rtlDataLock, NULL); + pthread_rwlock_init(&clientLock, NULL); + + event_set_log_callback(logCB); + + evthread_use_pthreads(); + + // Libevent loop + event_base = event_base_new(); + + struct sockaddr_in6 sa; + socklen_t salen = sizeof(sa); + memset(&sa, 0, sizeof(sa)); + sa.sin6_family = AF_INET6; + sa.sin6_addr = in6addr_any; + sa.sin6_port = htons(config.clientPort); + + struct evconnlistener *clientListener; + clientListener = evconnlistener_new_bind(event_base, connectCB, NULL, + LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1, + (struct sockaddr *)&sa, salen); + if(!clientListener) { + timeToExit = 1; + slog(LOG_FATAL, SLOG_FATAL, "Could not listen on the client streaming port."); + return NULL; + } + + slog(LOG_INFO, SLOG_INFO, "Listening for clients on port %d", config.clientPort); + + connectToServer(&serverConnection); + + struct evhttp *http; + struct evhttp_bound_socket *handle; + http = evhttp_new(event_base); + + handle = evhttp_bind_socket_with_handle(http, "::", 7879); + + if(!handle) { + slog(LOG_FATAL, SLOG_FATAL, "Could not bind HTTP listener."); + timeToExit = 1; + return NULL; + } + + while(!timeToExit) { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 100000; // 100ms + + event_base_loopexit(event_base, &tv); + event_base_dispatch(event_base); + } + + pthread_rwlock_wrlock(&clientLock); + while(LIST_FIRST(&clients) != NULL) { + struct client *client = LIST_FIRST(&clients); + LIST_REMOVE(client, peer); + bufferevent_free(client->bev); + free(client); + } + pthread_rwlock_unlock(&clientLock); + + evconnlistener_free(clientListener); + evhttp_free(http); + + event_base_free(event_base); + + return NULL; +} diff --git a/rtlmux.h b/rtlmux.h new file mode 100644 index 0000000..adda49c --- /dev/null +++ b/rtlmux.h @@ -0,0 +1,11 @@ +#ifndef _SERVER_H_ +#define _SERVER_H_ + +extern void *serverThread(void *); + +struct command { + unsigned char cmd; + unsigned int param; +}__attribute__((packed)); + +#endif diff --git a/slog.c b/slog.c new file mode 100644 index 0000000..e992024 --- /dev/null +++ b/slog.c @@ -0,0 +1,425 @@ +/* + * The MIT License (MIT) + * + * Copyleft (C) 2015 Sun Dro (a.k.a. kala13x) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include "slog.h" + +#ifdef __MACH__ +#include +#define ORWL_NANO (+1.0E-9) +#define ORWL_GIGA UINT64_C(1000000000) + +static double orwl_timebase = 0.0; +static uint64_t orwl_timestart = 0; + +struct timespec orwl_gettime(void) { + // be more careful in a multithreaded environement + if (!orwl_timestart) { + mach_timebase_info_data_t tb = { 0 }; + mach_timebase_info(&tb); + orwl_timebase = tb.numer; + orwl_timebase /= tb.denom; + orwl_timestart = mach_absolute_time(); + } + struct timespec t; + double diff = (mach_absolute_time() - orwl_timestart) * orwl_timebase; + t.tv_sec = diff * ORWL_NANO; + t.tv_nsec = diff - (t.tv_sec * ORWL_GIGA); + return t; +} +#endif + +/* Max size of string */ +#define MAXMSG 8196 + +/* Flags */ +static SlogFlags slg; +static pthread_mutex_t slog_mutex; + + +/* + * slog_get_date - Intialize date with system date. + * Argument is pointer of SlogDate structure. + */ +void slog_get_date(SlogDate *sdate) +{ + time_t rawtime; + struct tm timeinfo; + struct timespec now; + rawtime = time(NULL); + localtime_r(&rawtime, &timeinfo); + + /* Get System Date */ + sdate->year = timeinfo.tm_year+1900; + sdate->mon = timeinfo.tm_mon+1; + sdate->day = timeinfo.tm_mday; + sdate->hour = timeinfo.tm_hour; + sdate->min = timeinfo.tm_min; + sdate->sec = timeinfo.tm_sec; + + /* Get micro seconds */ + #ifdef __MACH__ + now = orwl_gettime(); + #else + clock_gettime(CLOCK_MONOTONIC, &now); + #endif + sdate->usec = now.tv_nsec / 10000000; +} + + +/* + * Get library version. Function returns version and build number of slog + * library. Return value is char pointer. Argument min is flag for output + * format. If min is 1, function returns version in full format, if flag + * is 0 function returns only version numbers, For examle: 1.3.0 +-*/ +const char* slog_version(int min) +{ + static char verstr[128]; + + /* Version short */ + if (min) sprintf(verstr, "%d.%d.%d", + SLOGVERSION_MAX, SLOGVERSION_MIN, SLOGBUILD_NUM); + + /* Version long */ + else sprintf(verstr, "%d.%d build %d (%s)", + SLOGVERSION_MAX, SLOGVERSION_MIN, SLOGBUILD_NUM, __DATE__); + + return verstr; +} + + +/* + * strclr - Colorize string. Function takes color value and string + * and returns colorized string as char pointer. First argument clr + * is color value (if it is invalid, function retunrs NULL) and second + * is string with va_list of arguments which one we want to colorize. + */ +char* strclr(const char* clr, char* str, ...) +{ + /* String buffers */ + static char output[MAXMSG]; + char string[MAXMSG]; + + /* Read args */ + va_list args; + va_start(args, str); + vsprintf(string, str, args); + va_end(args); + + /* Colorize string */ + sprintf(output, "%s%s%s", clr, string, CLR_RESET); + + return output; +} + + +/* + * log_to_file - Save log in file. Argument aut is string which + * we want to log. Argument fname is log file path and sdate is + * SlogDate structure variable, we need it to create filename. + */ +void slog_to_file(char *out, const char *fname, SlogDate *sdate) +{ + /* Used variables */ + char filename[PATH_MAX]; + + /* Create log filename with date */ + if (slg.filestamp) + { + snprintf(filename, sizeof(filename), "%s-%02d-%02d-%02d.log", + fname, sdate->year, sdate->mon, sdate->day); + } + else snprintf(filename, sizeof(filename), "%s.log", fname); + + /* Open file pointer */ + FILE *fp = fopen(filename, "a"); + if (fp == NULL) return; + + /* Write key in file */ + fprintf(fp, "%s", out); + + /* Close file pointer */ + fclose(fp); +} + + +/* + * parse_config - Parse config file. Argument cfg_name is path + * of config file name to be parsed. Function opens config file + * and parses LOGLEVEL and LOGTOFILE flags from it. + */ +int parse_config(const char *cfg_name) +{ + /* Used variables */ + FILE *file; + char *line = NULL; + size_t len = 0; + ssize_t read; + int ret = 0; + + /* Open file pointer */ + file = fopen(cfg_name, "r"); + if(file == NULL) return 0; + + /* Line-by-line read cfg file */ + while ((read = getline(&line, &len, file)) != -1) + { + /* Find level in file */ + if(strstr(line, "LOGLEVEL") != NULL) + { + /* Get logtofile flag */ + slg.level = atoi(line+8); + ret = 1; + } + if(strstr(line, "LOGFILELEVEL") != NULL) + { + /* Get logtofile flag */ + slg.file_level = atoi(line+12); + ret = 1; + } + else if(strstr(line, "LOGTOFILE") != NULL) + { + /* Get log level */ + slg.to_file = atoi(line+9); + ret = 1; + } + else if(strstr(line, "PRETTYLOG") != NULL) + { + /* Get log type */ + slg.pretty = atoi(line+9); + ret = 1; + } + else if(strstr(line, "FILESTAMP") != NULL) + { + /* Get filestamp */ + slg.filestamp = atoi(line+9); + ret = 1; + } + } + + /* Cleanup */ + if (line) free(line); + fclose(file); + + return ret; +} + + +/* + * Retunr string in slog format. Function takes arguments + * and returns string in slog format without printing and + * saveing in file. Return value is char pointer. + */ +char* slog_get(SlogDate *pDate, char *msg, ...) +{ + /* Used variables */ + static char output[MAXMSG]; + char string[MAXMSG]; + + /* Read args */ + va_list args; + va_start(args, msg); + vsprintf(string, msg, args); + va_end(args); + + /* Generate output string with date */ + sprintf(output, "%02d.%02d.%02d-%02d:%02d:%02d.%02d - %s", + pDate->year, pDate->mon, pDate->day, pDate->hour, + pDate->min, pDate->sec, pDate->usec, string); + + /* Return output */ + return output; +} + + +/* + * slog - Log exiting process. Function takes arguments and saves + * log in file if LOGTOFILE flag is enabled from config. Otherwise + * it just prints log without saveing in file. Argument level is + * logging level and flag is slog flags defined in slog.h header. + */ +void slog(int level, int flag, const char *msg, ...) +{ + /* Lock for safe */ + if (slg.td_safe) + { + if (pthread_mutex_lock(&slog_mutex)) + { + printf("<%s:%d> %s: [ERROR] Can not lock mutex: %d\n", + __FILE__, __LINE__, __FUNCTION__, errno); + exit(EXIT_FAILURE); + } + } + + /* Used variables */ + SlogDate mdate; + char string[MAXMSG]; + char prints[MAXMSG]; + char color[32], alarm[32]; + char *output; + + slog_get_date(&mdate); + bzero(string, sizeof(string)); + bzero(prints, sizeof(prints)); + bzero(color, sizeof(color)); + bzero(alarm, sizeof(alarm)); + + /* Read args */ + va_list args; + va_start(args, msg); + vsprintf(string, msg, args); + va_end(args); + + /* Check logging levels */ + if(!level || level <= slg.level || level <= slg.file_level) + { + /* Handle flags */ + switch(flag) + { + case SLOG_LIVE: + strncpy(color, CLR_NORMAL, sizeof(color)); + strncpy(alarm, "LIVE", sizeof(alarm)); + break; + case SLOG_INFO: + strncpy(color, CLR_GREEN, sizeof(color)); + strncpy(alarm, "INFO", sizeof(alarm)); + break; + case SLOG_WARN: + strncpy(color, CLR_YELLOW, sizeof(color)); + strncpy(alarm, "WARN", sizeof(alarm)); + break; + case SLOG_DEBUG: + strncpy(color, CLR_BLUE, sizeof(color)); + strncpy(alarm, "DEBUG", sizeof(alarm)); + break; + case SLOG_ERROR: + strncpy(color, CLR_RED, sizeof(color)); + strncpy(alarm, "ERROR", sizeof(alarm)); + break; + case SLOG_FATAL: + strncpy(color, CLR_RED, sizeof(color)); + strncpy(alarm, "FATAL", sizeof(alarm)); + break; + case SLOG_PANIC: + strncpy(color, CLR_WHITE, sizeof(color)); + strncpy(alarm, "PANIC", sizeof(alarm)); + break; + case SLOG_NONE: + strncpy(prints, string, sizeof(string)); + break; + default: + strncpy(prints, string, sizeof(string)); + flag = SLOG_NONE; + break; + } + + /* Print output */ + if (level <= slg.level || slg.pretty) + { + if (flag != SLOG_NONE) sprintf(prints, "[%s] %s", strclr(color, alarm), string); + if (level <= slg.level) printf("%s", slog_get(&mdate, "%s\n", prints)); + } + + /* Save log in file */ + if (slg.to_file && level <= slg.file_level) + { + if (slg.pretty) output = slog_get(&mdate, "%s\n", prints); + else + { + if (flag != SLOG_NONE) sprintf(prints, "[%s] %s", alarm, string); + output = slog_get(&mdate, "%s\n", prints); + } + + /* Add log line to file */ + slog_to_file(output, slg.fname, &mdate); + } + } + + /* Done, unlock mutex */ + if (slg.td_safe) + { + if (pthread_mutex_unlock(&slog_mutex)) + { + printf("<%s:%d> %s: [ERROR] Can not deinitialize mutex: %d\n", + __FILE__, __LINE__, __FUNCTION__, errno); + exit(EXIT_FAILURE); + } + } +} + + +/* + * Initialize slog library. Function parses config file and reads log + * level and save to file flag from config. First argument is file name + * where log will be saved and second argument conf is config file path + * to be parsedand third argument lvl is log level for this message. + */ +void slog_init(const char* fname, const char* conf, int lvl, int flvl, int t_safe) +{ + int status = 0; + + /* Set up default values */ + slg.level = lvl; + slg.file_level = flvl; + slg.to_file = 0; + slg.pretty = 0; + slg.filestamp = 1; + slg.td_safe = t_safe; + + /* Init mutex sync */ + if (t_safe) + { + /* Init mutex attribute */ + pthread_mutexattr_t m_attr; + if (pthread_mutexattr_init(&m_attr) || + pthread_mutexattr_settype(&m_attr, PTHREAD_MUTEX_RECURSIVE) || + pthread_mutex_init(&slog_mutex, &m_attr) || + pthread_mutexattr_destroy(&m_attr)) + { + printf("<%s:%d> %s: [ERROR] Can not initialize mutex: %d\n", + __FILE__, __LINE__, __FUNCTION__, errno); + slg.td_safe = 0; + } + } + + /* Parse config file */ + if (conf != NULL) + { + slg.fname = fname; + status = parse_config(conf); + } + + /* Handle config parser status */ + if (!status) slog(0, SLOG_INFO, "Initializing logger values without config"); + else slog(0, SLOG_INFO, "Loading logger config from: %s", conf); +} diff --git a/slog.h b/slog.h new file mode 100644 index 0000000..3fdc623 --- /dev/null +++ b/slog.h @@ -0,0 +1,150 @@ +/* + * The MIT License (MIT) + * + * Copyleft (C) 2015 Sun Dro (a.k.a. kala13x) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE + */ + + +#ifndef __SLOG_H__ +#define __SLOG_H__ + + +/* For include header in CPP code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Definations for version info */ +#define SLOGVERSION_MAX 1 +#define SLOGVERSION_MIN 4 +#define SLOGBUILD_NUM 82 + +/* Defined app logging levels */ +#define LOG_NONE 0 +#define LOG_PANIC 1 +#define LOG_FATAL 2 +#define LOG_ERROR 3 +#define LOG_WARN 4 +#define LOG_INFO 5 +#define LOG_DEBUG 6 +#define LOG_LIVE 7 +#define LOG_EXTRA 8 + +/* Loging flags */ +#define SLOG_NONE 0 +#define SLOG_LIVE 1 +#define SLOG_INFO 2 +#define SLOG_WARN 3 +#define SLOG_DEBUG 4 +#define SLOG_ERROR 5 +#define SLOG_FATAL 6 +#define SLOG_PANIC 7 + + +/* Supported colors */ +#define CLR_NORMAL "\x1B[0m" +#define CLR_RED "\x1B[31m" +#define CLR_GREEN "\x1B[32m" +#define CLR_YELLOW "\x1B[33m" +#define CLR_BLUE "\x1B[34m" +#define CLR_NAGENTA "\x1B[35m" +#define CLR_CYAN "\x1B[36m" +#define CLR_WHITE "\x1B[37m" +#define CLR_RESET "\033[0m" + + +/* Flags */ +typedef struct { + const char* fname; + short file_level; + short level; + short to_file; + short pretty; + short filestamp; + short td_safe; +} SlogFlags; + + +/* Date variables */ +typedef struct { + int year; + int mon; + int day; + int hour; + int min; + int sec; + int usec; +} SlogDate; + + +/* + * Get library version. Function returns version and build number of slog + * library. Return value is char pointer. Argument min is flag for output + * format. If min is 1, function returns version in full format, if flag + * is 0 function returns only version numbers, For examle: 1.0.52. +-*/ +const char* slog_version(int min); + + +/* + * strclr - Colorize string. Function takes color value and string + * and returns colorized string as char pointer. First argument clr + * is color value (if it is invalid, function retunrs NULL) and second + * is string with va_list of arguments which one we want to colorize. + */ +char* strclr(const char* clr, char* str, ...); + + +/* + * Return string in slog format. Function takes arguments + * and returns string in slog format without printing and + * saveing in file. Return value is char pointer. + */ +char* slog_get(SlogDate *pDate, char *msg, ...); + + +/* + * slog - Log exiting process. Function takes arguments and saves + * log in file if LOGTOFILE flag is enabled from config. Otherwise + * it just prints log without saveing in file. Argument level is + * logging level and flag is slog flags defined in slog.h header. + */ +void slog(int level, int flag, const char *msg, ...); + + +/* + * Initialize slog library. Function parses config file and reads log + * level and save to file flag from config. First argument is file name + * where log will be saved and second argument conf is config file path + * to be parsed and third argument lvl is log level for this message. + */ +void slog_init(const char* fname, const char* conf, int lvl, int flvl, int t_safe); + + +/* For include header in CPP code */ +#ifdef __cplusplus +} +#endif + + +#endif /* __SLOG_H__ */