├── .gitignore ├── .gitlab-ci.yml ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cmdline.c ├── cmdline.h ├── config.c ├── config.h ├── main.c ├── options.ggo ├── rtlmux.c ├── rtlmux.h ├── slog.c └── slog.h /.gitignore: -------------------------------------------------------------------------------- 1 | /*.o 2 | /rtlmux 3 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - build 3 | 4 | build:make: 5 | image: alpine 6 | stage: build 7 | script: 8 | - "apk --update add --virtual build-dependencies build-base libevent-dev bsd-compat-headers" 9 | - make 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | 3 | RUN apk --no-cache add libevent 4 | 5 | COPY Makefile *.c *.h options.ggo /app/ 6 | 7 | WORKDIR /app 8 | 9 | RUN apk --no-cache add --virtual build-dependencies build-base libevent-dev bsd-compat-headers \ 10 | && make \ 11 | && apk del build-dependencies 12 | 13 | CMD ["/app/rtlmux"] 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Stephen Olesen 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of rtlmux nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS=-Wall -I/usr/local/include -O3 3 | 4 | LIBS=-pthread `pkg-config libevent --libs-only-L` -levent -levent_pthreads 5 | 6 | ifeq ($(shell uname -m),armv7l) 7 | CFLAGS+=-O3 -mfpu=neon-vfpv4 -mfloat-abi=hard -march=armv7-a -ffast-math -funsafe-math-optimizations 8 | else 9 | CFLAGS+=-O3 10 | endif 11 | 12 | ifneq ($(shell uname),Darwin) 13 | LIBS+=-lrt 14 | endif 15 | 16 | ifeq ($(shell uname),Darwin) 17 | CFLAGS+=-glldb 18 | else 19 | CFLAGS+=-ggdb3 20 | endif 21 | 22 | SRC=slog.c rtlmux.c config.c cmdline.c main.c 23 | OBJS=slog.o rtlmux.o config.o cmdline.o main.o 24 | 25 | all: cmdline.c rtlmux 26 | 27 | clean: 28 | rm -f rtlmux $(OBJS) 29 | 30 | depend: 31 | makedepend -- $(CFLAGS) -- $(SRC) 32 | 33 | cmdline.h: options.ggo 34 | gengetopt -C -i $< -f cmdline.c 35 | 36 | cmdline.c: cmdline.h 37 | 38 | rtlmux: $(OBJS) 39 | $(CC) $(CFLAGS) -o $@ $^ $(LIBS) 40 | 41 | # DO NOT DELETE 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RTL TCP Client Multiplexer / Relay 2 | ================================== 3 | 4 | [![Build Status](https://git.vocti.ca/slepp/rtlmux/badges/master/build.svg)](https://git.vocti.ca/slepp/rtlmux/builds) 5 | 6 | This is a simple server to connec to an rtl_tcp server and allow multiple clients 7 | to attach to the same receiver. It relays the ID information to the clients, 8 | and allows all clients to send commands for configuration to the rtl_tcp server. 9 | 10 | It provides status output of the server and clients on the client port + 1, at 11 | the URL /stats.json, ie. http://localhost:7879/stats.json when `-l` is set to 7878. 12 | 13 | Usage 14 | ===== 15 | 16 | Usage takes up to 3 command line options: 17 | 18 | * `-h host` Server host to connect to 19 | * `-p port` Server port to connect to 20 | * `-l port` Listening port for clients to connect to, HTTP status port will be 21 | equal to this plus one 22 | -------------------------------------------------------------------------------- /cmdline.c: -------------------------------------------------------------------------------- 1 | /* 2 | File autogenerated by gengetopt version 2.22.6 3 | generated with the following command: 4 | gengetopt -C -i options.ggo -f cmdline.c 5 | 6 | The developers of gengetopt consider the fixed text that goes in all 7 | gengetopt output files to be in the public domain: 8 | we make no copyright claims on it. 9 | */ 10 | 11 | /* If we use autoconf. */ 12 | #ifdef HAVE_CONFIG_H 13 | #include "config.h" 14 | #endif 15 | 16 | #include 17 | #include 18 | #include 19 | 20 | #ifndef FIX_UNUSED 21 | #define FIX_UNUSED(X) (void) (X) /* avoid warnings for unused params */ 22 | #endif 23 | 24 | #include 25 | 26 | #include "cmdline.h" 27 | 28 | const char *gengetopt_args_info_purpose = "Relay RTL TCP data to multiple clients."; 29 | 30 | const char *gengetopt_args_info_usage = "Usage: rtlmux [OPTIONS]..."; 31 | 32 | const char *gengetopt_args_info_versiontext = "(c) 2016 Stephen Olesen"; 33 | 34 | 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."; 35 | 36 | const char *gengetopt_args_info_help[] = { 37 | " --help Print help and exit", 38 | " -V, --version Print version and exit", 39 | " -p, --port=port rtl_tcp port (default=`1234')", 40 | " -h, --host=address rtl_tcp host address (default=`localhost')", 41 | " -l, --listen=port Listening port for client connections (default=`7878')", 42 | 0 43 | }; 44 | 45 | typedef enum {ARG_NO 46 | , ARG_STRING 47 | , ARG_INT 48 | } cmdline_c_arg_type; 49 | 50 | static 51 | void clear_given (struct gengetopt_args_info *args_info); 52 | static 53 | void clear_args (struct gengetopt_args_info *args_info); 54 | 55 | static int 56 | cmdline_c_internal (int argc, char **argv, struct gengetopt_args_info *args_info, 57 | struct cmdline_c_params *params, const char *additional_error); 58 | 59 | struct line_list 60 | { 61 | char * string_arg; 62 | struct line_list * next; 63 | }; 64 | 65 | static struct line_list *cmd_line_list = 0; 66 | static struct line_list *cmd_line_list_tmp = 0; 67 | 68 | static void 69 | free_cmd_list(void) 70 | { 71 | /* free the list of a previous call */ 72 | if (cmd_line_list) 73 | { 74 | while (cmd_line_list) { 75 | cmd_line_list_tmp = cmd_line_list; 76 | cmd_line_list = cmd_line_list->next; 77 | free (cmd_line_list_tmp->string_arg); 78 | free (cmd_line_list_tmp); 79 | } 80 | } 81 | } 82 | 83 | 84 | static char * 85 | gengetopt_strdup (const char *s); 86 | 87 | static 88 | void clear_given (struct gengetopt_args_info *args_info) 89 | { 90 | args_info->help_given = 0 ; 91 | args_info->version_given = 0 ; 92 | args_info->port_given = 0 ; 93 | args_info->host_given = 0 ; 94 | args_info->listen_given = 0 ; 95 | } 96 | 97 | static 98 | void clear_args (struct gengetopt_args_info *args_info) 99 | { 100 | FIX_UNUSED (args_info); 101 | args_info->port_arg = 1234; 102 | args_info->port_orig = NULL; 103 | args_info->host_arg = gengetopt_strdup ("localhost"); 104 | args_info->host_orig = NULL; 105 | args_info->listen_arg = 7878; 106 | args_info->listen_orig = NULL; 107 | 108 | } 109 | 110 | static 111 | void init_args_info(struct gengetopt_args_info *args_info) 112 | { 113 | 114 | 115 | args_info->help_help = gengetopt_args_info_help[0] ; 116 | args_info->version_help = gengetopt_args_info_help[1] ; 117 | args_info->port_help = gengetopt_args_info_help[2] ; 118 | args_info->host_help = gengetopt_args_info_help[3] ; 119 | args_info->listen_help = gengetopt_args_info_help[4] ; 120 | 121 | } 122 | 123 | void 124 | cmdline_c_print_version (void) 125 | { 126 | printf ("%s %s\n", 127 | (strlen(CMDLINE_C_PACKAGE_NAME) ? CMDLINE_C_PACKAGE_NAME : CMDLINE_C_PACKAGE), 128 | CMDLINE_C_VERSION); 129 | 130 | if (strlen(gengetopt_args_info_versiontext) > 0) 131 | printf("\n%s\n", gengetopt_args_info_versiontext); 132 | } 133 | 134 | static void print_help_common(void) { 135 | cmdline_c_print_version (); 136 | 137 | if (strlen(gengetopt_args_info_purpose) > 0) 138 | printf("\n%s\n", gengetopt_args_info_purpose); 139 | 140 | if (strlen(gengetopt_args_info_usage) > 0) 141 | printf("\n%s\n", gengetopt_args_info_usage); 142 | 143 | printf("\n"); 144 | 145 | if (strlen(gengetopt_args_info_description) > 0) 146 | printf("%s\n\n", gengetopt_args_info_description); 147 | } 148 | 149 | void 150 | cmdline_c_print_help (void) 151 | { 152 | int i = 0; 153 | print_help_common(); 154 | while (gengetopt_args_info_help[i]) 155 | printf("%s\n", gengetopt_args_info_help[i++]); 156 | } 157 | 158 | void 159 | cmdline_c_init (struct gengetopt_args_info *args_info) 160 | { 161 | clear_given (args_info); 162 | clear_args (args_info); 163 | init_args_info (args_info); 164 | } 165 | 166 | void 167 | cmdline_c_params_init(struct cmdline_c_params *params) 168 | { 169 | if (params) 170 | { 171 | params->override = 0; 172 | params->initialize = 1; 173 | params->check_required = 1; 174 | params->check_ambiguity = 0; 175 | params->print_errors = 1; 176 | } 177 | } 178 | 179 | struct cmdline_c_params * 180 | cmdline_c_params_create(void) 181 | { 182 | struct cmdline_c_params *params = 183 | (struct cmdline_c_params *)malloc(sizeof(struct cmdline_c_params)); 184 | cmdline_c_params_init(params); 185 | return params; 186 | } 187 | 188 | static void 189 | free_string_field (char **s) 190 | { 191 | if (*s) 192 | { 193 | free (*s); 194 | *s = 0; 195 | } 196 | } 197 | 198 | 199 | static void 200 | cmdline_c_release (struct gengetopt_args_info *args_info) 201 | { 202 | 203 | free_string_field (&(args_info->port_orig)); 204 | free_string_field (&(args_info->host_arg)); 205 | free_string_field (&(args_info->host_orig)); 206 | free_string_field (&(args_info->listen_orig)); 207 | 208 | 209 | 210 | clear_given (args_info); 211 | } 212 | 213 | 214 | static void 215 | write_into_file(FILE *outfile, const char *opt, const char *arg, const char *values[]) 216 | { 217 | FIX_UNUSED (values); 218 | if (arg) { 219 | fprintf(outfile, "%s=\"%s\"\n", opt, arg); 220 | } else { 221 | fprintf(outfile, "%s\n", opt); 222 | } 223 | } 224 | 225 | 226 | int 227 | cmdline_c_dump(FILE *outfile, struct gengetopt_args_info *args_info) 228 | { 229 | int i = 0; 230 | 231 | if (!outfile) 232 | { 233 | fprintf (stderr, "%s: cannot dump options to stream\n", CMDLINE_C_PACKAGE); 234 | return EXIT_FAILURE; 235 | } 236 | 237 | if (args_info->help_given) 238 | write_into_file(outfile, "help", 0, 0 ); 239 | if (args_info->version_given) 240 | write_into_file(outfile, "version", 0, 0 ); 241 | if (args_info->port_given) 242 | write_into_file(outfile, "port", args_info->port_orig, 0); 243 | if (args_info->host_given) 244 | write_into_file(outfile, "host", args_info->host_orig, 0); 245 | if (args_info->listen_given) 246 | write_into_file(outfile, "listen", args_info->listen_orig, 0); 247 | 248 | 249 | i = EXIT_SUCCESS; 250 | return i; 251 | } 252 | 253 | int 254 | cmdline_c_file_save(const char *filename, struct gengetopt_args_info *args_info) 255 | { 256 | FILE *outfile; 257 | int i = 0; 258 | 259 | outfile = fopen(filename, "w"); 260 | 261 | if (!outfile) 262 | { 263 | fprintf (stderr, "%s: cannot open file for writing: %s\n", CMDLINE_C_PACKAGE, filename); 264 | return EXIT_FAILURE; 265 | } 266 | 267 | i = cmdline_c_dump(outfile, args_info); 268 | fclose (outfile); 269 | 270 | return i; 271 | } 272 | 273 | void 274 | cmdline_c_free (struct gengetopt_args_info *args_info) 275 | { 276 | cmdline_c_release (args_info); 277 | } 278 | 279 | /** @brief replacement of strdup, which is not standard */ 280 | char * 281 | gengetopt_strdup (const char *s) 282 | { 283 | char *result = 0; 284 | if (!s) 285 | return result; 286 | 287 | result = (char*)malloc(strlen(s) + 1); 288 | if (result == (char*)0) 289 | return (char*)0; 290 | strcpy(result, s); 291 | return result; 292 | } 293 | 294 | int 295 | cmdline_c (int argc, char **argv, struct gengetopt_args_info *args_info) 296 | { 297 | return cmdline_c2 (argc, argv, args_info, 0, 1, 1); 298 | } 299 | 300 | int 301 | cmdline_c_ext (int argc, char **argv, struct gengetopt_args_info *args_info, 302 | struct cmdline_c_params *params) 303 | { 304 | int result; 305 | result = cmdline_c_internal (argc, argv, args_info, params, 0); 306 | 307 | if (result == EXIT_FAILURE) 308 | { 309 | cmdline_c_free (args_info); 310 | exit (EXIT_FAILURE); 311 | } 312 | 313 | return result; 314 | } 315 | 316 | int 317 | cmdline_c2 (int argc, char **argv, struct gengetopt_args_info *args_info, int override, int initialize, int check_required) 318 | { 319 | int result; 320 | struct cmdline_c_params params; 321 | 322 | params.override = override; 323 | params.initialize = initialize; 324 | params.check_required = check_required; 325 | params.check_ambiguity = 0; 326 | params.print_errors = 1; 327 | 328 | result = cmdline_c_internal (argc, argv, args_info, ¶ms, 0); 329 | 330 | if (result == EXIT_FAILURE) 331 | { 332 | cmdline_c_free (args_info); 333 | exit (EXIT_FAILURE); 334 | } 335 | 336 | return result; 337 | } 338 | 339 | int 340 | cmdline_c_required (struct gengetopt_args_info *args_info, const char *prog_name) 341 | { 342 | FIX_UNUSED (args_info); 343 | FIX_UNUSED (prog_name); 344 | return EXIT_SUCCESS; 345 | } 346 | 347 | 348 | static char *package_name = 0; 349 | 350 | /** 351 | * @brief updates an option 352 | * @param field the generic pointer to the field to update 353 | * @param orig_field the pointer to the orig field 354 | * @param field_given the pointer to the number of occurrence of this option 355 | * @param prev_given the pointer to the number of occurrence already seen 356 | * @param value the argument for this option (if null no arg was specified) 357 | * @param possible_values the possible values for this option (if specified) 358 | * @param default_value the default value (in case the option only accepts fixed values) 359 | * @param arg_type the type of this option 360 | * @param check_ambiguity @see cmdline_c_params.check_ambiguity 361 | * @param override @see cmdline_c_params.override 362 | * @param no_free whether to free a possible previous value 363 | * @param multiple_option whether this is a multiple option 364 | * @param long_opt the corresponding long option 365 | * @param short_opt the corresponding short option (or '-' if none) 366 | * @param additional_error possible further error specification 367 | */ 368 | static 369 | int update_arg(void *field, char **orig_field, 370 | unsigned int *field_given, unsigned int *prev_given, 371 | char *value, const char *possible_values[], 372 | const char *default_value, 373 | cmdline_c_arg_type arg_type, 374 | int check_ambiguity, int override, 375 | int no_free, int multiple_option, 376 | const char *long_opt, char short_opt, 377 | const char *additional_error) 378 | { 379 | char *stop_char = 0; 380 | const char *val = value; 381 | int found; 382 | char **string_field; 383 | FIX_UNUSED (field); 384 | 385 | stop_char = 0; 386 | found = 0; 387 | 388 | if (!multiple_option && prev_given && (*prev_given || (check_ambiguity && *field_given))) 389 | { 390 | if (short_opt != '-') 391 | fprintf (stderr, "%s: `--%s' (`-%c') option given more than once%s\n", 392 | package_name, long_opt, short_opt, 393 | (additional_error ? additional_error : "")); 394 | else 395 | fprintf (stderr, "%s: `--%s' option given more than once%s\n", 396 | package_name, long_opt, 397 | (additional_error ? additional_error : "")); 398 | return 1; /* failure */ 399 | } 400 | 401 | FIX_UNUSED (default_value); 402 | 403 | if (field_given && *field_given && ! override) 404 | return 0; 405 | if (prev_given) 406 | (*prev_given)++; 407 | if (field_given) 408 | (*field_given)++; 409 | if (possible_values) 410 | val = possible_values[found]; 411 | 412 | switch(arg_type) { 413 | case ARG_INT: 414 | if (val) *((int *)field) = strtol (val, &stop_char, 0); 415 | break; 416 | case ARG_STRING: 417 | if (val) { 418 | string_field = (char **)field; 419 | if (!no_free && *string_field) 420 | free (*string_field); /* free previous string */ 421 | *string_field = gengetopt_strdup (val); 422 | } 423 | break; 424 | default: 425 | break; 426 | }; 427 | 428 | /* check numeric conversion */ 429 | switch(arg_type) { 430 | case ARG_INT: 431 | if (val && !(stop_char && *stop_char == '\0')) { 432 | fprintf(stderr, "%s: invalid numeric value: %s\n", package_name, val); 433 | return 1; /* failure */ 434 | } 435 | break; 436 | default: 437 | ; 438 | }; 439 | 440 | /* store the original value */ 441 | switch(arg_type) { 442 | case ARG_NO: 443 | break; 444 | default: 445 | if (value && orig_field) { 446 | if (no_free) { 447 | *orig_field = value; 448 | } else { 449 | if (*orig_field) 450 | free (*orig_field); /* free previous string */ 451 | *orig_field = gengetopt_strdup (value); 452 | } 453 | } 454 | }; 455 | 456 | return 0; /* OK */ 457 | } 458 | 459 | 460 | int 461 | cmdline_c_internal ( 462 | int argc, char **argv, struct gengetopt_args_info *args_info, 463 | struct cmdline_c_params *params, const char *additional_error) 464 | { 465 | int c; /* Character of the parsed option. */ 466 | 467 | int error_occurred = 0; 468 | struct gengetopt_args_info local_args_info; 469 | 470 | int override; 471 | int initialize; 472 | int check_required; 473 | int check_ambiguity; 474 | 475 | package_name = argv[0]; 476 | 477 | override = params->override; 478 | initialize = params->initialize; 479 | check_required = params->check_required; 480 | check_ambiguity = params->check_ambiguity; 481 | 482 | if (initialize) 483 | cmdline_c_init (args_info); 484 | 485 | cmdline_c_init (&local_args_info); 486 | 487 | optarg = 0; 488 | optind = 0; 489 | opterr = params->print_errors; 490 | optopt = '?'; 491 | 492 | while (1) 493 | { 494 | int option_index = 0; 495 | 496 | static struct option long_options[] = { 497 | { "help", 0, NULL, 0 }, 498 | { "version", 0, NULL, 'V' }, 499 | { "port", 1, NULL, 'p' }, 500 | { "host", 1, NULL, 'h' }, 501 | { "listen", 1, NULL, 'l' }, 502 | { 0, 0, 0, 0 } 503 | }; 504 | 505 | c = getopt_long (argc, argv, "Vp:h:l:", long_options, &option_index); 506 | 507 | if (c == -1) break; /* Exit from `while (1)' loop. */ 508 | 509 | switch (c) 510 | { 511 | case 'V': /* Print version and exit. */ 512 | cmdline_c_print_version (); 513 | cmdline_c_free (&local_args_info); 514 | exit (EXIT_SUCCESS); 515 | 516 | case 'p': /* rtl_tcp port. */ 517 | 518 | 519 | if (update_arg( (void *)&(args_info->port_arg), 520 | &(args_info->port_orig), &(args_info->port_given), 521 | &(local_args_info.port_given), optarg, 0, "1234", ARG_INT, 522 | check_ambiguity, override, 0, 0, 523 | "port", 'p', 524 | additional_error)) 525 | goto failure; 526 | 527 | break; 528 | case 'h': /* rtl_tcp host address. */ 529 | 530 | 531 | if (update_arg( (void *)&(args_info->host_arg), 532 | &(args_info->host_orig), &(args_info->host_given), 533 | &(local_args_info.host_given), optarg, 0, "localhost", ARG_STRING, 534 | check_ambiguity, override, 0, 0, 535 | "host", 'h', 536 | additional_error)) 537 | goto failure; 538 | 539 | break; 540 | case 'l': /* Listening port for client connections. */ 541 | 542 | 543 | if (update_arg( (void *)&(args_info->listen_arg), 544 | &(args_info->listen_orig), &(args_info->listen_given), 545 | &(local_args_info.listen_given), optarg, 0, "7878", ARG_INT, 546 | check_ambiguity, override, 0, 0, 547 | "listen", 'l', 548 | additional_error)) 549 | goto failure; 550 | 551 | break; 552 | 553 | case 0: /* Long option with no short option */ 554 | if (strcmp (long_options[option_index].name, "help") == 0) { 555 | cmdline_c_print_help (); 556 | cmdline_c_free (&local_args_info); 557 | exit (EXIT_SUCCESS); 558 | } 559 | 560 | case '?': /* Invalid option. */ 561 | /* `getopt_long' already printed an error message. */ 562 | goto failure; 563 | 564 | default: /* bug: option not considered. */ 565 | fprintf (stderr, "%s: option unknown: %c%s\n", CMDLINE_C_PACKAGE, c, (additional_error ? additional_error : "")); 566 | abort (); 567 | } /* switch */ 568 | } /* while */ 569 | 570 | 571 | 572 | 573 | cmdline_c_release (&local_args_info); 574 | 575 | if ( error_occurred ) 576 | return (EXIT_FAILURE); 577 | 578 | return 0; 579 | 580 | failure: 581 | 582 | cmdline_c_release (&local_args_info); 583 | return (EXIT_FAILURE); 584 | } 585 | 586 | #ifndef CONFIG_FILE_LINE_SIZE 587 | #define CONFIG_FILE_LINE_SIZE 2048 588 | #endif 589 | #define ADDITIONAL_ERROR " in configuration file " 590 | 591 | #define CONFIG_FILE_LINE_BUFFER_SIZE (CONFIG_FILE_LINE_SIZE+3) 592 | /* 3 is for "--" and "=" */ 593 | 594 | static int 595 | _cmdline_c_configfile (const char *filename, int *my_argc) 596 | { 597 | FILE* file; 598 | char my_argv[CONFIG_FILE_LINE_BUFFER_SIZE+1]; 599 | char linebuf[CONFIG_FILE_LINE_SIZE]; 600 | int line_num = 0; 601 | int result = 0, equal; 602 | char *fopt, *farg; 603 | char *str_index; 604 | size_t len, next_token; 605 | char delimiter; 606 | 607 | if ((file = fopen(filename, "r")) == 0) 608 | { 609 | fprintf (stderr, "%s: Error opening configuration file '%s'\n", 610 | CMDLINE_C_PACKAGE, filename); 611 | return EXIT_FAILURE; 612 | } 613 | 614 | while ((fgets(linebuf, CONFIG_FILE_LINE_SIZE, file)) != 0) 615 | { 616 | ++line_num; 617 | my_argv[0] = '\0'; 618 | len = strlen(linebuf); 619 | if (len > (CONFIG_FILE_LINE_BUFFER_SIZE-1)) 620 | { 621 | fprintf (stderr, "%s:%s:%d: Line too long in configuration file\n", 622 | CMDLINE_C_PACKAGE, filename, line_num); 623 | result = EXIT_FAILURE; 624 | break; 625 | } 626 | 627 | /* find first non-whitespace character in the line */ 628 | next_token = strspn (linebuf, " \t\r\n"); 629 | str_index = linebuf + next_token; 630 | 631 | if ( str_index[0] == '\0' || str_index[0] == '#') 632 | continue; /* empty line or comment line is skipped */ 633 | 634 | fopt = str_index; 635 | 636 | /* truncate fopt at the end of the first non-valid character */ 637 | next_token = strcspn (fopt, " \t\r\n="); 638 | 639 | if (fopt[next_token] == '\0') /* the line is over */ 640 | { 641 | farg = 0; 642 | equal = 0; 643 | goto noarg; 644 | } 645 | 646 | /* remember if equal sign is present */ 647 | equal = (fopt[next_token] == '='); 648 | fopt[next_token++] = '\0'; 649 | 650 | /* advance pointers to the next token after the end of fopt */ 651 | next_token += strspn (fopt + next_token, " \t\r\n"); 652 | 653 | /* check for the presence of equal sign, and if so, skip it */ 654 | if ( !equal ) 655 | if ((equal = (fopt[next_token] == '='))) 656 | { 657 | next_token++; 658 | next_token += strspn (fopt + next_token, " \t\r\n"); 659 | } 660 | str_index += next_token; 661 | 662 | /* find argument */ 663 | farg = str_index; 664 | if ( farg[0] == '\"' || farg[0] == '\'' ) 665 | { /* quoted argument */ 666 | str_index = strchr (++farg, str_index[0] ); /* skip opening quote */ 667 | if (! str_index) 668 | { 669 | fprintf 670 | (stderr, 671 | "%s:%s:%d: unterminated string in configuration file\n", 672 | CMDLINE_C_PACKAGE, filename, line_num); 673 | result = EXIT_FAILURE; 674 | break; 675 | } 676 | } 677 | else 678 | { /* read up the remaining part up to a delimiter */ 679 | next_token = strcspn (farg, " \t\r\n#\'\""); 680 | str_index += next_token; 681 | } 682 | 683 | /* truncate farg at the delimiter and store it for further check */ 684 | delimiter = *str_index, *str_index++ = '\0'; 685 | 686 | /* everything but comment is illegal at the end of line */ 687 | if (delimiter != '\0' && delimiter != '#') 688 | { 689 | str_index += strspn(str_index, " \t\r\n"); 690 | if (*str_index != '\0' && *str_index != '#') 691 | { 692 | fprintf 693 | (stderr, 694 | "%s:%s:%d: malformed string in configuration file\n", 695 | CMDLINE_C_PACKAGE, filename, line_num); 696 | result = EXIT_FAILURE; 697 | break; 698 | } 699 | } 700 | 701 | noarg: 702 | if (!strcmp(fopt,"include")) { 703 | if (farg && *farg) { 704 | result = _cmdline_c_configfile(farg, my_argc); 705 | } else { 706 | fprintf(stderr, "%s:%s:%d: include requires a filename argument.\n", 707 | CMDLINE_C_PACKAGE, filename, line_num); 708 | } 709 | continue; 710 | } 711 | len = strlen(fopt); 712 | strcat (my_argv, len > 1 ? "--" : "-"); 713 | strcat (my_argv, fopt); 714 | if (len > 1 && ((farg && *farg) || equal)) 715 | strcat (my_argv, "="); 716 | if (farg && *farg) 717 | strcat (my_argv, farg); 718 | ++(*my_argc); 719 | 720 | cmd_line_list_tmp = (struct line_list *) malloc (sizeof (struct line_list)); 721 | cmd_line_list_tmp->next = cmd_line_list; 722 | cmd_line_list = cmd_line_list_tmp; 723 | cmd_line_list->string_arg = gengetopt_strdup(my_argv); 724 | } /* while */ 725 | 726 | if (file) 727 | fclose(file); 728 | return result; 729 | } 730 | 731 | int 732 | cmdline_c_configfile ( 733 | const char *filename, 734 | struct gengetopt_args_info *args_info, 735 | int override, int initialize, int check_required) 736 | { 737 | struct cmdline_c_params params; 738 | 739 | params.override = override; 740 | params.initialize = initialize; 741 | params.check_required = check_required; 742 | params.check_ambiguity = 0; 743 | params.print_errors = 1; 744 | 745 | return cmdline_c_config_file (filename, args_info, ¶ms); 746 | } 747 | 748 | int 749 | cmdline_c_config_file (const char *filename, 750 | struct gengetopt_args_info *args_info, 751 | struct cmdline_c_params *params) 752 | { 753 | int i, result; 754 | int my_argc = 1; 755 | char **my_argv_arg; 756 | char *additional_error; 757 | 758 | /* store the program name */ 759 | cmd_line_list_tmp = (struct line_list *) malloc (sizeof (struct line_list)); 760 | cmd_line_list_tmp->next = cmd_line_list; 761 | cmd_line_list = cmd_line_list_tmp; 762 | cmd_line_list->string_arg = gengetopt_strdup (CMDLINE_C_PACKAGE); 763 | 764 | result = _cmdline_c_configfile(filename, &my_argc); 765 | 766 | if (result != EXIT_FAILURE) { 767 | my_argv_arg = (char **) malloc((my_argc+1) * sizeof(char *)); 768 | cmd_line_list_tmp = cmd_line_list; 769 | 770 | for (i = my_argc - 1; i >= 0; --i) { 771 | my_argv_arg[i] = cmd_line_list_tmp->string_arg; 772 | cmd_line_list_tmp = cmd_line_list_tmp->next; 773 | } 774 | 775 | my_argv_arg[my_argc] = 0; 776 | 777 | additional_error = (char *)malloc(strlen(filename) + strlen(ADDITIONAL_ERROR) + 1); 778 | strcpy (additional_error, ADDITIONAL_ERROR); 779 | strcat (additional_error, filename); 780 | result = 781 | cmdline_c_internal (my_argc, my_argv_arg, args_info, 782 | params, 783 | additional_error); 784 | 785 | free (additional_error); 786 | free (my_argv_arg); 787 | } 788 | 789 | free_cmd_list(); 790 | if (result == EXIT_FAILURE) 791 | { 792 | cmdline_c_free (args_info); 793 | exit (EXIT_FAILURE); 794 | } 795 | 796 | return result; 797 | } 798 | -------------------------------------------------------------------------------- /cmdline.h: -------------------------------------------------------------------------------- 1 | /** @file cmdline.h 2 | * @brief The header file for the command line option parser 3 | * generated by GNU Gengetopt version 2.22.6 4 | * http://www.gnu.org/software/gengetopt. 5 | * DO NOT modify this file, since it can be overwritten 6 | * @author GNU Gengetopt by Lorenzo Bettini */ 7 | 8 | #ifndef CMDLINE_H 9 | #define CMDLINE_H 10 | 11 | /* If we use autoconf. */ 12 | #ifdef HAVE_CONFIG_H 13 | #include "config.h" 14 | #endif 15 | 16 | #include /* for FILE */ 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif /* __cplusplus */ 21 | 22 | #ifndef CMDLINE_C_PACKAGE 23 | /** @brief the program name (used for printing errors) */ 24 | #define CMDLINE_C_PACKAGE "rtlmux" 25 | #endif 26 | 27 | #ifndef CMDLINE_C_PACKAGE_NAME 28 | /** @brief the complete program name (used for help and version) */ 29 | #define CMDLINE_C_PACKAGE_NAME "rtlmux" 30 | #endif 31 | 32 | #ifndef CMDLINE_C_VERSION 33 | /** @brief the program version */ 34 | #define CMDLINE_C_VERSION "1.0.0" 35 | #endif 36 | 37 | /** @brief Where the command line options are stored */ 38 | struct gengetopt_args_info 39 | { 40 | const char *help_help; /**< @brief Print help and exit help description. */ 41 | const char *version_help; /**< @brief Print version and exit help description. */ 42 | int port_arg; /**< @brief rtl_tcp port (default='1234'). */ 43 | char * port_orig; /**< @brief rtl_tcp port original value given at command line. */ 44 | const char *port_help; /**< @brief rtl_tcp port help description. */ 45 | char * host_arg; /**< @brief rtl_tcp host address (default='localhost'). */ 46 | char * host_orig; /**< @brief rtl_tcp host address original value given at command line. */ 47 | const char *host_help; /**< @brief rtl_tcp host address help description. */ 48 | int listen_arg; /**< @brief Listening port for client connections (default='7878'). */ 49 | char * listen_orig; /**< @brief Listening port for client connections original value given at command line. */ 50 | const char *listen_help; /**< @brief Listening port for client connections help description. */ 51 | 52 | unsigned int help_given ; /**< @brief Whether help was given. */ 53 | unsigned int version_given ; /**< @brief Whether version was given. */ 54 | unsigned int port_given ; /**< @brief Whether port was given. */ 55 | unsigned int host_given ; /**< @brief Whether host was given. */ 56 | unsigned int listen_given ; /**< @brief Whether listen was given. */ 57 | 58 | } ; 59 | 60 | /** @brief The additional parameters to pass to parser functions */ 61 | struct cmdline_c_params 62 | { 63 | int override; /**< @brief whether to override possibly already present options (default 0) */ 64 | int initialize; /**< @brief whether to initialize the option structure gengetopt_args_info (default 1) */ 65 | int check_required; /**< @brief whether to check that all required options were provided (default 1) */ 66 | int check_ambiguity; /**< @brief whether to check for options already specified in the option structure gengetopt_args_info (default 0) */ 67 | int print_errors; /**< @brief whether getopt_long should print an error message for a bad option (default 1) */ 68 | } ; 69 | 70 | /** @brief the purpose string of the program */ 71 | extern const char *gengetopt_args_info_purpose; 72 | /** @brief the usage string of the program */ 73 | extern const char *gengetopt_args_info_usage; 74 | /** @brief the description string of the program */ 75 | extern const char *gengetopt_args_info_description; 76 | /** @brief all the lines making the help output */ 77 | extern const char *gengetopt_args_info_help[]; 78 | 79 | /** 80 | * The command line parser 81 | * @param argc the number of command line options 82 | * @param argv the command line options 83 | * @param args_info the structure where option information will be stored 84 | * @return 0 if everything went fine, NON 0 if an error took place 85 | */ 86 | int cmdline_c (int argc, char **argv, 87 | struct gengetopt_args_info *args_info); 88 | 89 | /** 90 | * The command line parser (version with additional parameters - deprecated) 91 | * @param argc the number of command line options 92 | * @param argv the command line options 93 | * @param args_info the structure where option information will be stored 94 | * @param override whether to override possibly already present options 95 | * @param initialize whether to initialize the option structure my_args_info 96 | * @param check_required whether to check that all required options were provided 97 | * @return 0 if everything went fine, NON 0 if an error took place 98 | * @deprecated use cmdline_c_ext() instead 99 | */ 100 | int cmdline_c2 (int argc, char **argv, 101 | struct gengetopt_args_info *args_info, 102 | int override, int initialize, int check_required); 103 | 104 | /** 105 | * The command line parser (version with additional parameters) 106 | * @param argc the number of command line options 107 | * @param argv the command line options 108 | * @param args_info the structure where option information will be stored 109 | * @param params additional parameters for the parser 110 | * @return 0 if everything went fine, NON 0 if an error took place 111 | */ 112 | int cmdline_c_ext (int argc, char **argv, 113 | struct gengetopt_args_info *args_info, 114 | struct cmdline_c_params *params); 115 | 116 | /** 117 | * Save the contents of the option struct into an already open FILE stream. 118 | * @param outfile the stream where to dump options 119 | * @param args_info the option struct to dump 120 | * @return 0 if everything went fine, NON 0 if an error took place 121 | */ 122 | int cmdline_c_dump(FILE *outfile, 123 | struct gengetopt_args_info *args_info); 124 | 125 | /** 126 | * Save the contents of the option struct into a (text) file. 127 | * This file can be read by the config file parser (if generated by gengetopt) 128 | * @param filename the file where to save 129 | * @param args_info the option struct to save 130 | * @return 0 if everything went fine, NON 0 if an error took place 131 | */ 132 | int cmdline_c_file_save(const char *filename, 133 | struct gengetopt_args_info *args_info); 134 | 135 | /** 136 | * Print the help 137 | */ 138 | void cmdline_c_print_help(void); 139 | /** 140 | * Print the version 141 | */ 142 | void cmdline_c_print_version(void); 143 | 144 | /** 145 | * Initializes all the fields a cmdline_c_params structure 146 | * to their default values 147 | * @param params the structure to initialize 148 | */ 149 | void cmdline_c_params_init(struct cmdline_c_params *params); 150 | 151 | /** 152 | * Allocates dynamically a cmdline_c_params structure and initializes 153 | * all its fields to their default values 154 | * @return the created and initialized cmdline_c_params structure 155 | */ 156 | struct cmdline_c_params *cmdline_c_params_create(void); 157 | 158 | /** 159 | * Initializes the passed gengetopt_args_info structure's fields 160 | * (also set default values for options that have a default) 161 | * @param args_info the structure to initialize 162 | */ 163 | void cmdline_c_init (struct gengetopt_args_info *args_info); 164 | /** 165 | * Deallocates the string fields of the gengetopt_args_info structure 166 | * (but does not deallocate the structure itself) 167 | * @param args_info the structure to deallocate 168 | */ 169 | void cmdline_c_free (struct gengetopt_args_info *args_info); 170 | 171 | /** 172 | * The config file parser (deprecated version) 173 | * @param filename the name of the config file 174 | * @param args_info the structure where option information will be stored 175 | * @param override whether to override possibly already present options 176 | * @param initialize whether to initialize the option structure my_args_info 177 | * @param check_required whether to check that all required options were provided 178 | * @return 0 if everything went fine, NON 0 if an error took place 179 | * @deprecated use cmdline_c_config_file() instead 180 | */ 181 | int cmdline_c_configfile (const char *filename, 182 | struct gengetopt_args_info *args_info, 183 | int override, int initialize, int check_required); 184 | 185 | /** 186 | * The config file parser 187 | * @param filename the name of the config file 188 | * @param args_info the structure where option information will be stored 189 | * @param params additional parameters for the parser 190 | * @return 0 if everything went fine, NON 0 if an error took place 191 | */ 192 | int cmdline_c_config_file (const char *filename, 193 | struct gengetopt_args_info *args_info, 194 | struct cmdline_c_params *params); 195 | 196 | /** 197 | * Checks that all the required options were specified 198 | * @param args_info the structure to check 199 | * @param prog_name the name of the program that will be used to print 200 | * possible errors 201 | * @return 202 | */ 203 | int cmdline_c_required (struct gengetopt_args_info *args_info, 204 | const char *prog_name); 205 | 206 | 207 | #ifdef __cplusplus 208 | } 209 | #endif /* __cplusplus */ 210 | #endif /* CMDLINE_H */ 211 | -------------------------------------------------------------------------------- /config.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "cmdline.h" 10 | #include "config.h" 11 | 12 | #include "slog.h" 13 | 14 | struct config config; 15 | 16 | static struct gengetopt_args_info args; 17 | 18 | int convertConfig(struct gengetopt_args_info *args) { 19 | config.host = args->host_arg; 20 | config.port = args->port_arg; 21 | config.clientPort = args->listen_arg; 22 | 23 | return 1; 24 | } 25 | 26 | int parseConfig(int argc, char **argv) { 27 | int cr; 28 | struct cmdline_c_params params; 29 | cmdline_c_params_init(¶ms); 30 | params.initialize = 1; 31 | params.override = 0; 32 | params.check_required = 0; 33 | params.check_ambiguity = 0; 34 | if((cr = cmdline_c_ext(argc, argv, &args, ¶ms)) != 0) { 35 | exit(2); 36 | } 37 | 38 | if(cmdline_c_required(&args, argv[0]) != 0) { 39 | printf("Please try again."); 40 | exit(4); 41 | } 42 | 43 | return convertConfig(&args); 44 | } 45 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | #ifndef _CONFIG_H_ 2 | #define _CONFIG_H_ 3 | 4 | #include 5 | #include 6 | 7 | struct config { 8 | char *host; 9 | uint16_t port; 10 | uint16_t clientPort; 11 | }; 12 | 13 | extern struct config config; 14 | 15 | extern int parseConfig(int,char **); 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include "slog.h" 2 | #include "config.h" 3 | 4 | #include "rtlmux.h" 5 | 6 | #include 7 | 8 | volatile unsigned char timeToExit = 0; 9 | 10 | void signalExit(int sig) { 11 | timeToExit = 1; 12 | } 13 | 14 | int main(int argc, char **argv) { 15 | pthread_t threadServer; 16 | 17 | parseConfig(argc, argv); 18 | slog_init(NULL, NULL, LOG_EXTRA, LOG_DEBUG, 1); 19 | 20 | struct sigaction sigact; 21 | sigact.sa_handler = signalExit; 22 | sigact.sa_flags = 0; 23 | sigaction(SIGTERM, &sigact, NULL); 24 | sigaction(SIGINT, &sigact, NULL); 25 | 26 | pthread_create(&threadServer, NULL, serverThread, NULL); 27 | 28 | pthread_join(threadServer, NULL); 29 | } 30 | -------------------------------------------------------------------------------- /options.ggo: -------------------------------------------------------------------------------- 1 | package "rtlmux" 2 | version "1.0.0" 3 | purpose "Relay RTL TCP data to multiple clients." 4 | description "This will connect to an rtl_tcp server and allow multiple end clients to connect and control the RTL." 5 | 6 | versiontext "(c) 2016 Stephen Olesen" 7 | 8 | option "port" p "rtl_tcp port" 9 | int typestr="port" default="1234" optional 10 | option "host" h "rtl_tcp host address" 11 | string typestr="address" default="localhost" optional 12 | option "listen" l "Listening port for client connections" 13 | int typestr="port" default="7878" optional 14 | -------------------------------------------------------------------------------- /rtlmux.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | 21 | #include 22 | 23 | #include "config.h" 24 | 25 | #include "slog.h" 26 | 27 | #include "rtlmux.h" 28 | 29 | #include 30 | 31 | // Define this to enable thread safety around the lists 32 | //#define THREADED 33 | 34 | #ifndef THREADED 35 | #define pthread_rwlock_wrlock(a) 36 | #define pthread_rwlock_rdlock(a) 37 | #define pthread_rwlock_unlock(a) 38 | #endif 39 | 40 | struct event_base *event_base = NULL; 41 | struct bufferevent *serverConnection = NULL; 42 | 43 | unsigned long dataBlocks = 0; 44 | unsigned long dataBlocksSize = 0; 45 | struct rtlData { 46 | LIST_ENTRY(rtlData) next; 47 | uint32_t references; 48 | uint32_t len; 49 | uint8_t *data; 50 | }; 51 | static LIST_HEAD(rtlDataHead, rtlData) rtlDataList = LIST_HEAD_INITIALIZER(rtlDataList); 52 | static pthread_rwlock_t rtlDataLock; 53 | 54 | #define CLIENT_UNKNOWN 1 55 | #define CLIENT_READY 2 56 | #define CLIENT_INIT 4 57 | struct client { 58 | LIST_ENTRY(client) peer; 59 | struct bufferevent *bev; 60 | union { 61 | struct sockaddr sa; 62 | struct sockaddr_in sin; 63 | struct sockaddr_in6 sin6; 64 | }; 65 | struct { 66 | uint64_t in; 67 | uint64_t out; 68 | uint64_t dropped; 69 | uint64_t droppedCount; 70 | } data; 71 | time_t connected; 72 | uint32_t flags; 73 | }; 74 | 75 | static LIST_HEAD(clienthead, client) clients = LIST_HEAD_INITIALIZER(clients); 76 | static pthread_rwlock_t clientLock; 77 | 78 | static struct client *addClient(struct bufferevent *bev, void *ptr) { 79 | uint32_t clientFlags = CLIENT_INIT; 80 | 81 | struct client *client = (struct client *)calloc(1, sizeof(struct client)); 82 | 83 | client->bev = bev; 84 | client->flags = clientFlags; 85 | client->data.in = client->data.out = 0; 86 | client->connected = time(NULL); 87 | 88 | pthread_rwlock_wrlock(&clientLock); 89 | LIST_INSERT_HEAD(&clients, client, peer); 90 | pthread_rwlock_unlock(&clientLock); 91 | 92 | return client; 93 | } 94 | 95 | static void removeClient(struct client *client) { 96 | if(!client) 97 | return; 98 | 99 | pthread_rwlock_wrlock(&clientLock); 100 | LIST_REMOVE(client, peer); 101 | free(client); 102 | pthread_rwlock_unlock(&clientLock); 103 | } 104 | 105 | void releaseDataRef(const void *d, unsigned long len, void *ptr) { 106 | struct rtlData *data = (struct rtlData *)ptr; 107 | --data->references; 108 | if(data->references == 0) { 109 | //pthread_rwlock_wrlock(&rtlDataLock); 110 | //LIST_REMOVE(data, next); 111 | //pthread_rwlock_unlock(&rtlDataLock); 112 | dataBlocks--; 113 | dataBlocksSize -= data->len; 114 | free(data); // This is a single malloc for both the data and header 115 | } 116 | } 117 | 118 | int sendDataToAllClients(struct rtlData *data) { 119 | struct client *client; 120 | pthread_rwlock_rdlock(&clientLock); 121 | LIST_FOREACH(client, &clients, peer) { 122 | if(client->flags == CLIENT_READY) { 123 | struct evbuffer *ev = bufferevent_get_output(client->bev); 124 | if(evbuffer_get_length(ev) > 4*1024*1024) { // If we've already buffered 4MByte, then start dropping frames 125 | client->data.dropped += data->len; 126 | client->data.droppedCount ++; 127 | continue; 128 | } 129 | ++data->references; 130 | evbuffer_add_reference(ev, data->data, data->len, releaseDataRef, data); 131 | client->data.out += data->len; 132 | } 133 | } 134 | pthread_rwlock_unlock(&clientLock); 135 | return data->references; 136 | } 137 | 138 | void sendToAllClients(char *buf, size_t len, uint32_t flags) { 139 | struct client *client; 140 | pthread_rwlock_rdlock(&clientLock); 141 | LIST_FOREACH(client, &clients, peer) { 142 | if((client->flags & flags) != 0) 143 | bufferevent_write(client->bev, buf, len); 144 | } 145 | pthread_rwlock_unlock(&clientLock); 146 | } 147 | 148 | static void logCB(int severity, const char *msg) { 149 | int level; 150 | int flag; 151 | switch(severity) { 152 | case EVENT_LOG_DEBUG: level = LOG_DEBUG; flag = SLOG_DEBUG; break; 153 | case EVENT_LOG_MSG: level = LOG_INFO; flag = SLOG_INFO; break; 154 | case EVENT_LOG_WARN: level = LOG_WARN; flag = SLOG_WARN; break; 155 | case EVENT_LOG_ERR: level = LOG_ERROR; flag = SLOG_ERROR; break; 156 | default: level = LOG_LIVE; flag = LOG_LIVE; break; 157 | } 158 | 159 | slog(level, flag, msg); 160 | } 161 | 162 | struct serverInfo { 163 | enum { SERVER_NEW, SERVER_CONNECTED, SERVER_DISCONNECTED } state; 164 | char magic[4]; 165 | uint32_t tuner_type; 166 | uint32_t tuner_gain_count; 167 | struct { 168 | unsigned int value; 169 | unsigned char set; 170 | } params[0xd]; // Store all the parameters as a simple command array 171 | struct { 172 | uint64_t in; 173 | uint64_t out; 174 | } data; 175 | } serverInfo; 176 | 177 | static void serverErrorEventCB(struct bufferevent *, short, void *); 178 | static void serverReadCB(struct bufferevent *, void *); 179 | 180 | static void connectToServer(void *arg) { 181 | struct bufferevent **serverConnection = (struct bufferevent **)arg; 182 | slog(LOG_INFO, SLOG_INFO, "Starting connection lookup for %s:%d", config.host, config.port); 183 | *serverConnection = bufferevent_socket_new(event_base, -1, BEV_OPT_CLOSE_ON_FREE); 184 | bufferevent_socket_connect_hostname(*serverConnection, NULL, AF_UNSPEC, config.host, config.port); 185 | slog(LOG_INFO, SLOG_INFO, "Started to connect to %s:%d", config.host, config.port); 186 | bufferevent_setcb(*serverConnection, serverReadCB, NULL, serverErrorEventCB, serverConnection); 187 | bufferevent_setwatermark(*serverConnection, EV_READ, 16384, 0); 188 | bufferevent_enable(*serverConnection, EV_READ|EV_WRITE); 189 | } 190 | 191 | static void connectToServerCB(int a, short b, void *arg) { 192 | connectToServer(arg); 193 | } 194 | 195 | static void connectToServerSoon(void *ctx) { 196 | struct event *ev; 197 | struct timeval tv; 198 | 199 | tv.tv_sec = 1; 200 | tv.tv_usec = 0; 201 | 202 | ev = evtimer_new(event_base, connectToServerCB, ctx); 203 | evtimer_add(ev, &tv); 204 | } 205 | 206 | static void serverReadCB(struct bufferevent *bev, void *ctx) { 207 | struct rtlData *data; 208 | 209 | if(serverInfo.state == SERVER_NEW) { 210 | serverInfo.data.in += bufferevent_read(bev, serverInfo.magic, 4); 211 | serverInfo.data.in += bufferevent_read(bev, &serverInfo.tuner_type, 4); 212 | serverInfo.data.in += bufferevent_read(bev, &serverInfo.tuner_gain_count, 4); 213 | if(serverInfo.magic[0] == 'R' && serverInfo.magic[1] == 'T' && serverInfo.magic[2] == 'L' && serverInfo.magic[3] == '0') { 214 | serverInfo.state = SERVER_CONNECTED; 215 | slog(LOG_INFO, SLOG_INFO, "Connected to server."); 216 | } else { // Failed to receive the magic header 217 | slog(LOG_ERROR, SLOG_ERROR, "Failed to receive magic header from server."); 218 | bufferevent_free(bev); 219 | connectToServerSoon(ctx); 220 | return; 221 | } 222 | // Send stored and set parameters on reconnect 223 | int i; 224 | for(i = 0; i < 0xd; i++) { 225 | if(serverInfo.params[i].set) { 226 | struct command cmd; 227 | cmd.cmd = i+1; 228 | cmd.param = serverInfo.params[i].value; 229 | slog(LOG_INFO, SLOG_INFO, "Sending command %d with param %lu", cmd.cmd, ntohl(cmd.param)); 230 | serverInfo.data.out += sizeof(cmd); 231 | bufferevent_write(bev, &cmd, sizeof(cmd)); 232 | } 233 | } 234 | } 235 | 236 | struct evbuffer *ev = bufferevent_get_input(bev); 237 | size_t availLen = evbuffer_get_length(ev); 238 | 239 | if(availLen == 0) // We may not have data, so return 240 | return; 241 | 242 | if(availLen > 256*1024) 243 | availLen = 256*1024; // Limit our input sizes to 256k chunks 244 | 245 | data = (struct rtlData *)malloc(sizeof(struct rtlData) + availLen); 246 | memset(data, 0, sizeof(struct rtlData)); 247 | data->data = (void *)data + sizeof(struct rtlData); 248 | data->references = 0; 249 | serverInfo.data.in += data->len = bufferevent_read(bev, data->data, availLen); 250 | 251 | if(sendDataToAllClients(data) == 0) { 252 | // No one was listening 253 | free(data); 254 | } else { 255 | dataBlocks++; 256 | dataBlocksSize += data->len; 257 | // Track the data block 258 | //pthread_rwlock_wrlock(&rtlDataLock); 259 | //LIST_INSERT_HEAD(&rtlDataList, data, next); 260 | //pthread_rwlock_unlock(&rtlDataLock); 261 | } 262 | } 263 | 264 | static void serverErrorEventCB(struct bufferevent *bev, short events, void *ctx) { 265 | if (events & BEV_EVENT_ERROR) 266 | slog(LOG_ERROR, SLOG_ERROR, "Error from server side bufferevent: %s", strerror(errno)); 267 | if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) { 268 | bufferevent_free(bev); 269 | slog(LOG_INFO, SLOG_INFO, "Disconnecting server."); 270 | serverInfo.state = SERVER_NEW; 271 | connectToServerSoon(ctx); 272 | } 273 | } 274 | 275 | static void errorEventCB(struct bufferevent *bev, short events, void *ctx) { 276 | if (events & BEV_EVENT_ERROR) 277 | slog(LOG_ERROR, SLOG_ERROR, "Error from bufferevent: %s", strerror(errno)); 278 | if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) { 279 | struct client *client = (struct client *)ctx; 280 | char ipBuf[128]; 281 | if(client->sa.sa_family == AF_INET) 282 | evutil_inet_ntop(client->sa.sa_family, &client->sin.sin_addr, ipBuf, 128); 283 | else if(client->sa.sa_family == AF_INET6) 284 | evutil_inet_ntop(client->sa.sa_family, &client->sin6.sin6_addr, ipBuf, 128); 285 | else 286 | snprintf(ipBuf, 128, "from unknown address"); 287 | slog(LOG_INFO, SLOG_INFO, "Disconnecting client %s", ipBuf); 288 | bufferevent_free(bev); 289 | removeClient(client); 290 | } 291 | } 292 | 293 | void serverSendCommand(struct command cmd) { 294 | serverInfo.params[cmd.cmd-1].value = cmd.param; 295 | serverInfo.params[cmd.cmd-1].set = 1; 296 | slog(LOG_LIVE, SLOG_DEBUG, "Sending command to server: %d: %lu", cmd.cmd, ntohl(cmd.param)); 297 | serverInfo.data.out += sizeof(cmd); 298 | bufferevent_write(serverConnection, &cmd, sizeof(cmd)); 299 | } 300 | 301 | #define RTL_FREQUENCY 0x01 302 | #define RTL_SAMPLE_RATE 0x02 303 | #define RTL_GAIN_MODE 0x03 304 | #define RTL_GAIN 0x04 305 | #define RTL_FREQ_CORRECTION 0x05 306 | #define RTL_STAGE_GAIN 0x06 307 | #define RTL_TEST_MODE 0x07 308 | #define RTL_AGC_MODE 0x08 309 | #define RTL_DIRECT_SAMPLING 0x09 310 | #define RTL_OFFSET_TUNING 0x0a 311 | #define RTL_XTAL 0x0b 312 | #define RTL_TUNER_XTAL 0x0c 313 | #define RTL_GAIN_BY_INDEX 0x0d 314 | 315 | static void clientReadCB(struct bufferevent *bev, void *ctx) { 316 | struct command cmd; 317 | size_t l; 318 | struct client *client = (struct client *)ctx; 319 | while((l = bufferevent_read(bev, &cmd, sizeof(cmd))) > 0) { 320 | client->data.in += l; 321 | slog(LOG_INFO, SLOG_INFO, "Read from client: %x", cmd.cmd); 322 | 323 | switch(cmd.cmd) { 324 | case RTL_FREQUENCY: // Frequency 325 | slog(LOG_INFO, SLOG_INFO, "Set frequency: %lu", ntohl(cmd.param)); 326 | serverSendCommand(cmd); 327 | break; 328 | case RTL_SAMPLE_RATE: // Sample rate 329 | slog(LOG_INFO, SLOG_INFO, "Set sample rate: %lu", ntohl(cmd.param)); 330 | serverSendCommand(cmd); 331 | break; 332 | case RTL_GAIN_MODE: // Gain mode 333 | slog(LOG_INFO, SLOG_INFO, "Set gain mode: %lu", ntohl(cmd.param)); 334 | serverSendCommand(cmd); 335 | break; 336 | case RTL_GAIN: // Set Gain 337 | slog(LOG_INFO, SLOG_INFO, "Set gain: %lu", ntohl(cmd.param)); 338 | serverSendCommand(cmd); 339 | break; 340 | case RTL_FREQ_CORRECTION: // Set freq correction 341 | slog(LOG_INFO, SLOG_INFO, "Set freq correction: %lu", ntohl(cmd.param)); 342 | serverSendCommand(cmd); 343 | break; 344 | case RTL_STAGE_GAIN: // Stage Gain 345 | slog(LOG_INFO, SLOG_INFO, "Set stage gain: %lu", ntohl(cmd.param)); 346 | serverSendCommand(cmd); 347 | break; 348 | case RTL_TEST_MODE: // Test mode 349 | slog(LOG_INFO, SLOG_INFO, "Set test mode: %lu", ntohl(cmd.param)); 350 | break; 351 | case RTL_AGC_MODE: // AGC mode 352 | slog(LOG_INFO, SLOG_INFO, "Set AGC mode: %lu", ntohl(cmd.param)); 353 | serverSendCommand(cmd); 354 | break; 355 | case RTL_DIRECT_SAMPLING: // Direct sampling 356 | slog(LOG_INFO, SLOG_INFO, "Set direct sampling: %lu", ntohl(cmd.param)); 357 | break; 358 | case RTL_OFFSET_TUNING: // Offset tuning 359 | slog(LOG_INFO, SLOG_INFO, "Set offset tuning: %lu", ntohl(cmd.param)); 360 | serverSendCommand(cmd); 361 | break; 362 | case RTL_XTAL: // RTL Xtal 363 | slog(LOG_INFO, SLOG_INFO, "Set RTL xtal: %lu", ntohl(cmd.param)); 364 | serverSendCommand(cmd); 365 | break; 366 | case RTL_TUNER_XTAL: // Tuner Xtal 367 | slog(LOG_INFO, SLOG_INFO, "Set tuner xtal: %lu", ntohl(cmd.param)); 368 | serverSendCommand(cmd); 369 | break; 370 | case RTL_GAIN_BY_INDEX: // Gain by index 371 | slog(LOG_INFO, SLOG_INFO, "Set gain by index: %lu", ntohl(cmd.param)); 372 | serverSendCommand(cmd); 373 | break; 374 | default: // Ignore it 375 | slog(LOG_INFO, SLOG_INFO, "Ignored client command: %x", cmd.cmd); 376 | } 377 | } 378 | } 379 | 380 | static void connectCB(struct evconnlistener *listener, 381 | evutil_socket_t sock, struct sockaddr *addr, int len, void *ptr) { 382 | struct event_base *base = evconnlistener_get_base(listener); 383 | #ifdef THREADED 384 | struct bufferevent *bev = bufferevent_socket_new( 385 | base, sock, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE); 386 | #else 387 | struct bufferevent *bev = bufferevent_socket_new( 388 | base, sock, BEV_OPT_CLOSE_ON_FREE); 389 | #endif 390 | 391 | struct client *client = addClient(bev, ptr); 392 | memcpy(&client->sa, addr, len); 393 | char ipBuf[128]; 394 | if(client->sa.sa_family == AF_INET) 395 | evutil_inet_ntop(client->sa.sa_family, &client->sin.sin_addr, ipBuf, 128); 396 | else if(client->sa.sa_family == AF_INET6) 397 | evutil_inet_ntop(client->sa.sa_family, &client->sin6.sin6_addr, ipBuf, 128); 398 | else 399 | snprintf(ipBuf, 128, "from unknown address"); 400 | slog(LOG_INFO, SLOG_INFO, "Connection from client %s", ipBuf); 401 | bufferevent_setcb(bev, clientReadCB, NULL, errorEventCB, client); 402 | bufferevent_setwatermark(bev, EV_WRITE, 0, 4*1024*1024); // Limit output to 4MB? 403 | bufferevent_enable(bev, EV_READ|EV_WRITE); 404 | bufferevent_write(bev, serverInfo.magic, 4); 405 | bufferevent_write(bev, &serverInfo.tuner_type, 4); 406 | bufferevent_write(bev, &serverInfo.tuner_gain_count, 4); 407 | serverInfo.data.out += 12; 408 | client->flags = CLIENT_READY; 409 | } 410 | 411 | static void dumpClients(struct evhttp_request *req, void *arg) { 412 | struct evbuffer *evb = NULL; 413 | 414 | evb = evbuffer_new(); 415 | 416 | pthread_rwlock_rdlock(&clientLock); 417 | 418 | evbuffer_add_printf(evb, "{\"server\":{\"dataIn\":%lu,\"dataOut\":%lu},\"clients\":[", 419 | serverInfo.data.in, serverInfo.data.out); 420 | struct client *client; 421 | LIST_FOREACH(client, &clients, peer) { 422 | char ipBuf[128]; 423 | if(client->sa.sa_family == AF_INET) 424 | evutil_inet_ntop(client->sa.sa_family, &client->sin.sin_addr, ipBuf, 128); 425 | else if(client->sa.sa_family == AF_INET6) 426 | evutil_inet_ntop(client->sa.sa_family, &client->sin6.sin6_addr, ipBuf, 128); 427 | else 428 | snprintf(ipBuf, 128, "from unknown address"); 429 | evbuffer_add_printf(evb, "{\"client\":{\"host\":\"%s\",\"port\":%u},\"dataIn\":%lu,\"dataOut\":%lu,\"dropped\":{\"size\":%lu,\"count\":%lu},\"connected\":%ld}", 430 | ipBuf, ntohs(client->sa.sa_family == AF_INET ? client->sin.sin_port : client->sin6.sin6_port), 431 | client->data.in, 432 | client->data.out, 433 | client->data.dropped, 434 | client->data.droppedCount, 435 | client->connected 436 | ); 437 | if(LIST_NEXT(client, peer) != NULL) { 438 | evbuffer_add_printf(evb, ","); 439 | } 440 | } 441 | evbuffer_add_printf(evb, "]}"); 442 | 443 | pthread_rwlock_unlock(&clientLock); 444 | 445 | evhttp_add_header(evhttp_request_get_output_headers(req), "Content-Type", "application/json"); 446 | evhttp_add_header(evhttp_request_get_output_headers(req), "Access-Control-Allow-Origin", "*"); 447 | evhttp_send_reply(req, 200, "OK", evb); 448 | } 449 | 450 | void *serverThread(void *arg) { 451 | memset(&serverInfo, 0, sizeof(serverInfo)); 452 | 453 | slog(LOG_INFO, SLOG_INFO, "Starting server thread."); 454 | 455 | LIST_INIT(&rtlDataList); 456 | LIST_INIT(&clients); 457 | 458 | pthread_rwlock_init(&rtlDataLock, NULL); 459 | pthread_rwlock_init(&clientLock, NULL); 460 | 461 | event_set_log_callback(logCB); 462 | 463 | evthread_use_pthreads(); 464 | 465 | // Libevent loop 466 | event_base = event_base_new(); 467 | 468 | struct sockaddr_in6 sa; 469 | socklen_t salen = sizeof(sa); 470 | memset(&sa, 0, sizeof(sa)); 471 | sa.sin6_family = AF_INET6; 472 | sa.sin6_addr = in6addr_any; 473 | sa.sin6_port = htons(config.clientPort); 474 | 475 | struct evconnlistener *clientListener; 476 | clientListener = evconnlistener_new_bind(event_base, connectCB, NULL, 477 | LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1, 478 | (struct sockaddr *)&sa, salen); 479 | if(!clientListener) { 480 | timeToExit = 1; 481 | slog(LOG_FATAL, SLOG_FATAL, "Could not listen on the client streaming port."); 482 | return NULL; 483 | } 484 | 485 | slog(LOG_INFO, SLOG_INFO, "Listening for clients on port %d", config.clientPort); 486 | 487 | connectToServer(&serverConnection); 488 | 489 | struct evhttp *http; 490 | struct evhttp_bound_socket *handle; 491 | http = evhttp_new(event_base); 492 | 493 | evhttp_set_cb(http, "/stats.json", dumpClients, "clients"); 494 | 495 | handle = evhttp_bind_socket_with_handle(http, "::", config.clientPort + 1); 496 | 497 | if(!handle) { 498 | slog(LOG_FATAL, SLOG_FATAL, "Could not bind HTTP listener."); 499 | timeToExit = 1; 500 | return NULL; 501 | } 502 | 503 | int loopCounter = 0; 504 | while(!timeToExit) { 505 | struct timeval tv; 506 | tv.tv_sec = 0; 507 | tv.tv_usec = 100000; // 100ms 508 | 509 | event_base_loopexit(event_base, &tv); 510 | event_base_dispatch(event_base); 511 | 512 | if((++loopCounter%600) == 0) { 513 | loopCounter = 0; 514 | /* pthread_rwlock_rdlock(&clientLock); 515 | struct client *client; 516 | unsigned long clientCount = 0; 517 | LIST_FOREACH(client, &clients, peer) { 518 | clientCount++; 519 | } 520 | slog(LOG_INFO, SLOG_INFO, "Clients currently connected: %lu", clientCount); 521 | pthread_rwlock_unlock(&clientLock);*/ 522 | pthread_rwlock_rdlock(&rtlDataLock); 523 | if(dataBlocks > 0) 524 | slog(LOG_INFO, SLOG_INFO, "Maintaining %lu data buffers, total of %lu bytes.", dataBlocks, dataBlocksSize); 525 | pthread_rwlock_unlock(&rtlDataLock); 526 | } 527 | } 528 | 529 | pthread_rwlock_wrlock(&clientLock); 530 | while(LIST_FIRST(&clients) != NULL) { 531 | struct client *client = LIST_FIRST(&clients); 532 | LIST_REMOVE(client, peer); 533 | bufferevent_free(client->bev); 534 | free(client); 535 | } 536 | pthread_rwlock_unlock(&clientLock); 537 | 538 | evconnlistener_free(clientListener); 539 | evhttp_free(http); 540 | 541 | event_base_free(event_base); 542 | 543 | return NULL; 544 | } 545 | -------------------------------------------------------------------------------- /rtlmux.h: -------------------------------------------------------------------------------- 1 | #ifndef _SERVER_H_ 2 | #define _SERVER_H_ 3 | 4 | extern volatile unsigned char timeToExit; 5 | 6 | extern void *serverThread(void *); 7 | 8 | struct command { 9 | unsigned char cmd; 10 | unsigned int param; 11 | }__attribute__((packed)); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /slog.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyleft (C) 2015 Sun Dro (a.k.a. kala13x) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE 23 | */ 24 | 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include "slog.h" 35 | 36 | #ifdef __MACH__ 37 | #include 38 | #define ORWL_NANO (+1.0E-9) 39 | #define ORWL_GIGA UINT64_C(1000000000) 40 | 41 | static double orwl_timebase = 0.0; 42 | static uint64_t orwl_timestart = 0; 43 | 44 | struct timespec orwl_gettime(void) { 45 | // be more careful in a multithreaded environement 46 | if (!orwl_timestart) { 47 | mach_timebase_info_data_t tb = { 0 }; 48 | mach_timebase_info(&tb); 49 | orwl_timebase = tb.numer; 50 | orwl_timebase /= tb.denom; 51 | orwl_timestart = mach_absolute_time(); 52 | } 53 | struct timespec t; 54 | double diff = (mach_absolute_time() - orwl_timestart) * orwl_timebase; 55 | t.tv_sec = diff * ORWL_NANO; 56 | t.tv_nsec = diff - (t.tv_sec * ORWL_GIGA); 57 | return t; 58 | } 59 | #endif 60 | 61 | /* Max size of string */ 62 | #define MAXMSG 8196 63 | 64 | /* Flags */ 65 | static SlogFlags slg; 66 | static pthread_mutex_t slog_mutex; 67 | 68 | 69 | /* 70 | * slog_get_date - Intialize date with system date. 71 | * Argument is pointer of SlogDate structure. 72 | */ 73 | void slog_get_date(SlogDate *sdate) 74 | { 75 | time_t rawtime; 76 | struct tm timeinfo; 77 | struct timespec now; 78 | rawtime = time(NULL); 79 | localtime_r(&rawtime, &timeinfo); 80 | 81 | /* Get System Date */ 82 | sdate->year = timeinfo.tm_year+1900; 83 | sdate->mon = timeinfo.tm_mon+1; 84 | sdate->day = timeinfo.tm_mday; 85 | sdate->hour = timeinfo.tm_hour; 86 | sdate->min = timeinfo.tm_min; 87 | sdate->sec = timeinfo.tm_sec; 88 | 89 | /* Get micro seconds */ 90 | #ifdef __MACH__ 91 | now = orwl_gettime(); 92 | #else 93 | clock_gettime(CLOCK_MONOTONIC, &now); 94 | #endif 95 | sdate->usec = now.tv_nsec / 10000000; 96 | } 97 | 98 | 99 | /* 100 | * Get library version. Function returns version and build number of slog 101 | * library. Return value is char pointer. Argument min is flag for output 102 | * format. If min is 1, function returns version in full format, if flag 103 | * is 0 function returns only version numbers, For examle: 1.3.0 104 | -*/ 105 | const char* slog_version(int min) 106 | { 107 | static char verstr[128]; 108 | 109 | /* Version short */ 110 | if (min) sprintf(verstr, "%d.%d.%d", 111 | SLOGVERSION_MAX, SLOGVERSION_MIN, SLOGBUILD_NUM); 112 | 113 | /* Version long */ 114 | else sprintf(verstr, "%d.%d build %d (%s)", 115 | SLOGVERSION_MAX, SLOGVERSION_MIN, SLOGBUILD_NUM, __DATE__); 116 | 117 | return verstr; 118 | } 119 | 120 | 121 | /* 122 | * strclr - Colorize string. Function takes color value and string 123 | * and returns colorized string as char pointer. First argument clr 124 | * is color value (if it is invalid, function retunrs NULL) and second 125 | * is string with va_list of arguments which one we want to colorize. 126 | */ 127 | char* strclr(const char* clr, char* str, ...) 128 | { 129 | /* String buffers */ 130 | static char output[MAXMSG]; 131 | char string[MAXMSG]; 132 | 133 | /* Read args */ 134 | va_list args; 135 | va_start(args, str); 136 | vsprintf(string, str, args); 137 | va_end(args); 138 | 139 | /* Colorize string */ 140 | sprintf(output, "%s%s%s", clr, string, CLR_RESET); 141 | 142 | return output; 143 | } 144 | 145 | 146 | /* 147 | * log_to_file - Save log in file. Argument aut is string which 148 | * we want to log. Argument fname is log file path and sdate is 149 | * SlogDate structure variable, we need it to create filename. 150 | */ 151 | void slog_to_file(char *out, const char *fname, SlogDate *sdate) 152 | { 153 | /* Used variables */ 154 | char filename[PATH_MAX]; 155 | 156 | /* Create log filename with date */ 157 | if (slg.filestamp) 158 | { 159 | snprintf(filename, sizeof(filename), "%s-%02d-%02d-%02d.log", 160 | fname, sdate->year, sdate->mon, sdate->day); 161 | } 162 | else snprintf(filename, sizeof(filename), "%s.log", fname); 163 | 164 | /* Open file pointer */ 165 | FILE *fp = fopen(filename, "a"); 166 | if (fp == NULL) return; 167 | 168 | /* Write key in file */ 169 | fprintf(fp, "%s", out); 170 | 171 | /* Close file pointer */ 172 | fclose(fp); 173 | } 174 | 175 | 176 | /* 177 | * parse_config - Parse config file. Argument cfg_name is path 178 | * of config file name to be parsed. Function opens config file 179 | * and parses LOGLEVEL and LOGTOFILE flags from it. 180 | */ 181 | int parse_config(const char *cfg_name) 182 | { 183 | /* Used variables */ 184 | FILE *file; 185 | char *line = NULL; 186 | size_t len = 0; 187 | ssize_t read; 188 | int ret = 0; 189 | 190 | /* Open file pointer */ 191 | file = fopen(cfg_name, "r"); 192 | if(file == NULL) return 0; 193 | 194 | /* Line-by-line read cfg file */ 195 | while ((read = getline(&line, &len, file)) != -1) 196 | { 197 | /* Find level in file */ 198 | if(strstr(line, "LOGLEVEL") != NULL) 199 | { 200 | /* Get logtofile flag */ 201 | slg.level = atoi(line+8); 202 | ret = 1; 203 | } 204 | if(strstr(line, "LOGFILELEVEL") != NULL) 205 | { 206 | /* Get logtofile flag */ 207 | slg.file_level = atoi(line+12); 208 | ret = 1; 209 | } 210 | else if(strstr(line, "LOGTOFILE") != NULL) 211 | { 212 | /* Get log level */ 213 | slg.to_file = atoi(line+9); 214 | ret = 1; 215 | } 216 | else if(strstr(line, "PRETTYLOG") != NULL) 217 | { 218 | /* Get log type */ 219 | slg.pretty = atoi(line+9); 220 | ret = 1; 221 | } 222 | else if(strstr(line, "FILESTAMP") != NULL) 223 | { 224 | /* Get filestamp */ 225 | slg.filestamp = atoi(line+9); 226 | ret = 1; 227 | } 228 | } 229 | 230 | /* Cleanup */ 231 | if (line) free(line); 232 | fclose(file); 233 | 234 | return ret; 235 | } 236 | 237 | 238 | /* 239 | * Retunr string in slog format. Function takes arguments 240 | * and returns string in slog format without printing and 241 | * saveing in file. Return value is char pointer. 242 | */ 243 | char* slog_get(SlogDate *pDate, char *msg, ...) 244 | { 245 | /* Used variables */ 246 | static char output[MAXMSG]; 247 | char string[MAXMSG]; 248 | 249 | /* Read args */ 250 | va_list args; 251 | va_start(args, msg); 252 | vsprintf(string, msg, args); 253 | va_end(args); 254 | 255 | /* Generate output string with date */ 256 | sprintf(output, "%02d.%02d.%02d-%02d:%02d:%02d.%02d - %s", 257 | pDate->year, pDate->mon, pDate->day, pDate->hour, 258 | pDate->min, pDate->sec, pDate->usec, string); 259 | 260 | /* Return output */ 261 | return output; 262 | } 263 | 264 | 265 | /* 266 | * slog - Log exiting process. Function takes arguments and saves 267 | * log in file if LOGTOFILE flag is enabled from config. Otherwise 268 | * it just prints log without saveing in file. Argument level is 269 | * logging level and flag is slog flags defined in slog.h header. 270 | */ 271 | void slog(int level, int flag, const char *msg, ...) 272 | { 273 | /* Lock for safe */ 274 | if (slg.td_safe) 275 | { 276 | if (pthread_mutex_lock(&slog_mutex)) 277 | { 278 | printf("<%s:%d> %s: [ERROR] Can not lock mutex: %d\n", 279 | __FILE__, __LINE__, __FUNCTION__, errno); 280 | exit(EXIT_FAILURE); 281 | } 282 | } 283 | 284 | /* Used variables */ 285 | SlogDate mdate; 286 | char string[MAXMSG]; 287 | char prints[MAXMSG]; 288 | char color[32], alarm[32]; 289 | char *output; 290 | 291 | slog_get_date(&mdate); 292 | bzero(string, sizeof(string)); 293 | bzero(prints, sizeof(prints)); 294 | bzero(color, sizeof(color)); 295 | bzero(alarm, sizeof(alarm)); 296 | 297 | /* Read args */ 298 | va_list args; 299 | va_start(args, msg); 300 | vsprintf(string, msg, args); 301 | va_end(args); 302 | 303 | /* Check logging levels */ 304 | if(!level || level <= slg.level || level <= slg.file_level) 305 | { 306 | /* Handle flags */ 307 | switch(flag) 308 | { 309 | case SLOG_LIVE: 310 | strncpy(color, CLR_NORMAL, sizeof(color)); 311 | strncpy(alarm, "LIVE", sizeof(alarm)); 312 | break; 313 | case SLOG_INFO: 314 | strncpy(color, CLR_GREEN, sizeof(color)); 315 | strncpy(alarm, "INFO", sizeof(alarm)); 316 | break; 317 | case SLOG_WARN: 318 | strncpy(color, CLR_YELLOW, sizeof(color)); 319 | strncpy(alarm, "WARN", sizeof(alarm)); 320 | break; 321 | case SLOG_DEBUG: 322 | strncpy(color, CLR_BLUE, sizeof(color)); 323 | strncpy(alarm, "DEBUG", sizeof(alarm)); 324 | break; 325 | case SLOG_ERROR: 326 | strncpy(color, CLR_RED, sizeof(color)); 327 | strncpy(alarm, "ERROR", sizeof(alarm)); 328 | break; 329 | case SLOG_FATAL: 330 | strncpy(color, CLR_RED, sizeof(color)); 331 | strncpy(alarm, "FATAL", sizeof(alarm)); 332 | break; 333 | case SLOG_PANIC: 334 | strncpy(color, CLR_WHITE, sizeof(color)); 335 | strncpy(alarm, "PANIC", sizeof(alarm)); 336 | break; 337 | case SLOG_NONE: 338 | strncpy(prints, string, sizeof(string)); 339 | break; 340 | default: 341 | strncpy(prints, string, sizeof(string)); 342 | flag = SLOG_NONE; 343 | break; 344 | } 345 | 346 | /* Print output */ 347 | if (level <= slg.level || slg.pretty) 348 | { 349 | if (flag != SLOG_NONE) sprintf(prints, "[%s] %s", strclr(color, alarm), string); 350 | if (level <= slg.level) printf("%s", slog_get(&mdate, "%s\n", prints)); 351 | } 352 | 353 | /* Save log in file */ 354 | if (slg.to_file && level <= slg.file_level) 355 | { 356 | if (slg.pretty) output = slog_get(&mdate, "%s\n", prints); 357 | else 358 | { 359 | if (flag != SLOG_NONE) sprintf(prints, "[%s] %s", alarm, string); 360 | output = slog_get(&mdate, "%s\n", prints); 361 | } 362 | 363 | /* Add log line to file */ 364 | slog_to_file(output, slg.fname, &mdate); 365 | } 366 | } 367 | 368 | /* Done, unlock mutex */ 369 | if (slg.td_safe) 370 | { 371 | if (pthread_mutex_unlock(&slog_mutex)) 372 | { 373 | printf("<%s:%d> %s: [ERROR] Can not deinitialize mutex: %d\n", 374 | __FILE__, __LINE__, __FUNCTION__, errno); 375 | exit(EXIT_FAILURE); 376 | } 377 | } 378 | } 379 | 380 | 381 | /* 382 | * Initialize slog library. Function parses config file and reads log 383 | * level and save to file flag from config. First argument is file name 384 | * where log will be saved and second argument conf is config file path 385 | * to be parsedand third argument lvl is log level for this message. 386 | */ 387 | void slog_init(const char* fname, const char* conf, int lvl, int flvl, int t_safe) 388 | { 389 | int status = 0; 390 | 391 | /* Set up default values */ 392 | slg.level = lvl; 393 | slg.file_level = flvl; 394 | slg.to_file = 0; 395 | slg.pretty = 0; 396 | slg.filestamp = 1; 397 | slg.td_safe = t_safe; 398 | 399 | /* Init mutex sync */ 400 | if (t_safe) 401 | { 402 | /* Init mutex attribute */ 403 | pthread_mutexattr_t m_attr; 404 | if (pthread_mutexattr_init(&m_attr) || 405 | pthread_mutexattr_settype(&m_attr, PTHREAD_MUTEX_RECURSIVE) || 406 | pthread_mutex_init(&slog_mutex, &m_attr) || 407 | pthread_mutexattr_destroy(&m_attr)) 408 | { 409 | printf("<%s:%d> %s: [ERROR] Can not initialize mutex: %d\n", 410 | __FILE__, __LINE__, __FUNCTION__, errno); 411 | slg.td_safe = 0; 412 | } 413 | } 414 | 415 | /* Parse config file */ 416 | if (conf != NULL) 417 | { 418 | slg.fname = fname; 419 | status = parse_config(conf); 420 | } 421 | 422 | /* Handle config parser status */ 423 | if (!status) slog(0, SLOG_INFO, "Initializing logger values without config"); 424 | else slog(0, SLOG_INFO, "Loading logger config from: %s", conf); 425 | } 426 | -------------------------------------------------------------------------------- /slog.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyleft (C) 2015 Sun Dro (a.k.a. kala13x) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE 23 | */ 24 | 25 | 26 | #ifndef __SLOG_H__ 27 | #define __SLOG_H__ 28 | 29 | 30 | /* For include header in CPP code */ 31 | #ifdef __cplusplus 32 | extern "C" { 33 | #endif 34 | 35 | #include 36 | 37 | /* Definations for version info */ 38 | #define SLOGVERSION_MAX 1 39 | #define SLOGVERSION_MIN 4 40 | #define SLOGBUILD_NUM 82 41 | 42 | /* Defined app logging levels */ 43 | #define LOG_NONE 0 44 | #define LOG_PANIC 1 45 | #define LOG_FATAL 2 46 | #define LOG_ERROR 3 47 | #define LOG_WARN 4 48 | #define LOG_INFO 5 49 | #define LOG_DEBUG 6 50 | #define LOG_LIVE 7 51 | #define LOG_EXTRA 8 52 | 53 | /* Loging flags */ 54 | #define SLOG_NONE 0 55 | #define SLOG_LIVE 1 56 | #define SLOG_INFO 2 57 | #define SLOG_WARN 3 58 | #define SLOG_DEBUG 4 59 | #define SLOG_ERROR 5 60 | #define SLOG_FATAL 6 61 | #define SLOG_PANIC 7 62 | 63 | 64 | /* Supported colors */ 65 | #define CLR_NORMAL "\x1B[0m" 66 | #define CLR_RED "\x1B[31m" 67 | #define CLR_GREEN "\x1B[32m" 68 | #define CLR_YELLOW "\x1B[33m" 69 | #define CLR_BLUE "\x1B[34m" 70 | #define CLR_NAGENTA "\x1B[35m" 71 | #define CLR_CYAN "\x1B[36m" 72 | #define CLR_WHITE "\x1B[37m" 73 | #define CLR_RESET "\033[0m" 74 | 75 | 76 | /* Flags */ 77 | typedef struct { 78 | const char* fname; 79 | short file_level; 80 | short level; 81 | short to_file; 82 | short pretty; 83 | short filestamp; 84 | short td_safe; 85 | } SlogFlags; 86 | 87 | 88 | /* Date variables */ 89 | typedef struct { 90 | int year; 91 | int mon; 92 | int day; 93 | int hour; 94 | int min; 95 | int sec; 96 | int usec; 97 | } SlogDate; 98 | 99 | 100 | /* 101 | * Get library version. Function returns version and build number of slog 102 | * library. Return value is char pointer. Argument min is flag for output 103 | * format. If min is 1, function returns version in full format, if flag 104 | * is 0 function returns only version numbers, For examle: 1.0.52. 105 | -*/ 106 | const char* slog_version(int min); 107 | 108 | 109 | /* 110 | * strclr - Colorize string. Function takes color value and string 111 | * and returns colorized string as char pointer. First argument clr 112 | * is color value (if it is invalid, function retunrs NULL) and second 113 | * is string with va_list of arguments which one we want to colorize. 114 | */ 115 | char* strclr(const char* clr, char* str, ...); 116 | 117 | 118 | /* 119 | * Return string in slog format. Function takes arguments 120 | * and returns string in slog format without printing and 121 | * saveing in file. Return value is char pointer. 122 | */ 123 | char* slog_get(SlogDate *pDate, char *msg, ...); 124 | 125 | 126 | /* 127 | * slog - Log exiting process. Function takes arguments and saves 128 | * log in file if LOGTOFILE flag is enabled from config. Otherwise 129 | * it just prints log without saveing in file. Argument level is 130 | * logging level and flag is slog flags defined in slog.h header. 131 | */ 132 | void slog(int level, int flag, const char *msg, ...); 133 | 134 | 135 | /* 136 | * Initialize slog library. Function parses config file and reads log 137 | * level and save to file flag from config. First argument is file name 138 | * where log will be saved and second argument conf is config file path 139 | * to be parsed and third argument lvl is log level for this message. 140 | */ 141 | void slog_init(const char* fname, const char* conf, int lvl, int flvl, int t_safe); 142 | 143 | 144 | /* For include header in CPP code */ 145 | #ifdef __cplusplus 146 | } 147 | #endif 148 | 149 | 150 | #endif /* __SLOG_H__ */ 151 | --------------------------------------------------------------------------------