diff --git a/include/data.h b/include/data.h index 0a848b10b..48aca36ca 100644 --- a/include/data.h +++ b/include/data.h @@ -186,6 +186,7 @@ typedef struct data_output { void (R_API_CALLCONV *print_int)(struct data_output *output, int data, char const *format); void (R_API_CALLCONV *output_start)(struct data_output *output, char const *const *fields, int num_fields); void (R_API_CALLCONV *output_print)(struct data_output *output, data_t *data); + void (R_API_CALLCONV *output_reopen)(struct data_output *output); void (R_API_CALLCONV *output_free)(struct data_output *output); int log_level; ///< the maximum log level (verbosity) allowed, more verbose messages must be ignored. } data_output_t; @@ -202,6 +203,9 @@ R_API void data_output_start(struct data_output *output, char const *const *fiel /** Prints a structured data object, flushes the output if applicable. */ R_API void data_output_print(struct data_output *output, data_t *data); +/** Reopen this output by closing and opening file descriptors as needed. */ +R_API void data_output_reopen(struct data_output *output); + R_API void data_output_free(struct data_output *output); /* data output helpers */ diff --git a/include/output_file.h b/include/output_file.h index 928199236..522f480bb 100644 --- a/include/output_file.h +++ b/include/output_file.h @@ -18,14 +18,14 @@ /** Construct data output for CSV printer. @param log_level the highest log level to process - @param file the output stream + @param path the output stream path, defaults to stdout if NULL @return The auxiliary data to pass along with data_csv_printer to data_print. You must release this object with data_output_free once you're done with it. */ -struct data_output *data_output_csv_create(int log_level, FILE *file); +struct data_output *data_output_csv_create(int log_level, char const *path); -struct data_output *data_output_json_create(int log_level, FILE *file); +struct data_output *data_output_json_create(int log_level, char const *path); -struct data_output *data_output_kv_create(int log_level, FILE *file); +struct data_output *data_output_kv_create(int log_level, char const *path); #endif /* INCLUDE_OUTPUT_FILE_H_ */ diff --git a/include/output_log.h b/include/output_log.h index 7941a3f1f..3a559c312 100644 --- a/include/output_log.h +++ b/include/output_log.h @@ -18,10 +18,10 @@ /** Construct data output for LOG printer. @param log_level the highest log level to process - @param file the optional output stream, defaults to stderr + @param path the optional output stream path, defaults to stderr if NULL @return The auxiliary data to pass along with data_log_printer to data_print. You must release this object with data_output_free once you're done with it. */ -struct data_output *data_output_log_create(int log_level, FILE *file); +struct data_output *data_output_log_create(int log_level, char const *path); #endif /* INCLUDE_OUTPUT_LOG_H_ */ diff --git a/include/output_trigger.h b/include/output_trigger.h index 3afa5f11e..6ddf93b4b 100644 --- a/include/output_trigger.h +++ b/include/output_trigger.h @@ -25,9 +25,9 @@ /// $ echo oneshot | sudo tee /sys/class/leds/led0/trigger /// $ rtl_433 ... -F trigger:/sys/class/leds/led0/shot /// -/// @param file a trigger output stream +/// @param path a trigger output stream path /// @return The initialized data output. /// You must release this object with data_output_free once you're done with it. -struct data_output *data_output_trigger_create(FILE *file); +struct data_output *data_output_trigger_create(char const *path); #endif /* INCLUDE_OUTPUT_TRIGGER_H_ */ diff --git a/include/r_api.h b/include/r_api.h index 19398f240..77e594d23 100644 --- a/include/r_api.h +++ b/include/r_api.h @@ -95,6 +95,8 @@ void add_rtltcp_output(struct r_cfg *cfg, char *param); void start_outputs(struct r_cfg *cfg, char const *const *well_known); +void reopen_outputs(struct r_cfg *cfg); + void add_sr_dumper(struct r_cfg *cfg, char const *spec, int overwrite); void reopen_dumpers(struct r_cfg *cfg); diff --git a/src/data.c b/src/data.c index 3bab79580..4f1b0a4b1 100644 --- a/src/data.c +++ b/src/data.c @@ -411,6 +411,13 @@ R_API void data_output_start(struct data_output *output, char const *const *fiel output->output_start(output, fields, num_fields); } +R_API void data_output_reopen(struct data_output *output) +{ + if (!output || !output->output_reopen) + return; + output->output_reopen(output); +} + R_API void data_output_free(data_output_t *output) { if (!output) diff --git a/src/output_file.c b/src/output_file.c index 0f342cb16..1b0e1be9e 100644 --- a/src/output_file.c +++ b/src/output_file.c @@ -26,6 +26,7 @@ typedef struct { struct data_output output; + char const *path; FILE *file; } data_output_json_t; @@ -122,15 +123,39 @@ static void R_API_CALLCONV data_output_json_print(data_output_t *output, data_t } } +static void R_API_CALLCONV data_output_json_reopen(data_output_t *output) +{ + data_output_json_t *json = (data_output_json_t *)output; + + if (json->file && json->file != stdout && json->file != stderr) { + fclose(json->file); + } + + if (!json->path || !*json->path) { + json->file = stdout; // No path given + } + else if (*json->path == '-' && json->path[1] == '\0') { + json->file = stdout; // STDOUT requested + } + else { + json->file = fopen(json->path, "a"); + if (!json->file) { + fprintf(stderr, "rtl_433: failed to open output file\n"); + exit(1); + } + } +} + static void R_API_CALLCONV data_output_json_free(data_output_t *output) { - if (!output) + if (!output) { return; + } free(output); } -struct data_output *data_output_json_create(int log_level, FILE *file) +struct data_output *data_output_json_create(int log_level, char const *path) { data_output_json_t *json = calloc(1, sizeof(data_output_json_t)); if (!json) { @@ -138,15 +163,18 @@ struct data_output *data_output_json_create(int log_level, FILE *file) return NULL; // NOTE: returns NULL on alloc failure. } - json->output.log_level = log_level; - json->output.print_data = print_json_data; - json->output.print_array = print_json_array; - json->output.print_string = print_json_string; - json->output.print_double = print_json_double; - json->output.print_int = print_json_int; - json->output.output_print = data_output_json_print; - json->output.output_free = data_output_json_free; - json->file = file; + json->output.log_level = log_level; + json->output.print_data = print_json_data; + json->output.print_array = print_json_array; + json->output.print_string = print_json_string; + json->output.print_double = print_json_double; + json->output.print_int = print_json_int; + json->output.output_print = data_output_json_print; + json->output.output_reopen = data_output_json_reopen; + json->output.output_free = data_output_json_free; + json->path = path; + + data_output_json_reopen(&json->output); return (struct data_output *)json; } @@ -190,6 +218,7 @@ static int kv_break_after_key(char const *key) typedef struct { struct data_output output; + char const *path; FILE *file; void *term; int color; @@ -389,19 +418,44 @@ static void R_API_CALLCONV data_output_kv_print(data_output_t *output, data_t *d } } +static void R_API_CALLCONV data_output_kv_reopen(data_output_t *output) +{ + data_output_kv_t *kv = (data_output_kv_t *)output; + + if (kv->file && kv->file != stdout && kv->file != stderr) { + fclose(kv->file); + } + + if (!kv->path || !*kv->path) { + kv->file = stdout; // No path given + } + else if (*kv->path == '-' && kv->path[1] == '\0') { + kv->file = stdout; // STDOUT requested + } + else { + kv->file = fopen(kv->path, "a"); + if (!kv->file) { + fprintf(stderr, "rtl_433: failed to open output file\n"); + exit(1); + } + } +} + static void R_API_CALLCONV data_output_kv_free(data_output_t *output) { data_output_kv_t *kv = (data_output_kv_t *)output; - if (!output) + if (!output) { return; + } - if (kv->color) + if (kv->color) { term_free(kv->term); + } free(output); } -struct data_output *data_output_kv_create(int log_level, FILE *file) +struct data_output *data_output_kv_create(int log_level, char const *path) { data_output_kv_t *kv = calloc(1, sizeof(data_output_kv_t)); if (!kv) { @@ -409,17 +463,20 @@ struct data_output *data_output_kv_create(int log_level, FILE *file) return NULL; // NOTE: returns NULL on alloc failure. } - kv->output.log_level = log_level; - kv->output.print_data = print_kv_data; - kv->output.print_array = print_kv_array; - kv->output.print_string = print_kv_string; - kv->output.print_double = print_kv_double; - kv->output.print_int = print_kv_int; - kv->output.output_print = data_output_kv_print; - kv->output.output_free = data_output_kv_free; - kv->file = file; + kv->output.log_level = log_level; + kv->output.print_data = print_kv_data; + kv->output.print_array = print_kv_array; + kv->output.print_string = print_kv_string; + kv->output.print_double = print_kv_double; + kv->output.print_int = print_kv_int; + kv->output.output_print = data_output_kv_print; + kv->output.output_reopen = data_output_kv_reopen; + kv->output.output_free = data_output_kv_free; + kv->path = path; - kv->term = term_init(file); + data_output_kv_reopen(&kv->output); + + kv->term = term_init(kv->file); kv->color = term_has_color(kv->term); kv->ring_bell = 0; // TODO: enable if requested... @@ -431,6 +488,7 @@ struct data_output *data_output_kv_create(int log_level, FILE *file) typedef struct { struct data_output output; + char const *path; FILE *file; const char **fields; const char *separator; @@ -611,15 +669,42 @@ static void R_API_CALLCONV data_output_csv_print(data_output_t *output, data_t * fflush(csv->file); } +static void R_API_CALLCONV data_output_csv_reopen(data_output_t *output) +{ + data_output_csv_t *csv = (data_output_csv_t *)output; + + if (csv->file && csv->file != stdout && csv->file != stderr) { + fclose(csv->file); + } + + if (!csv->path || !*csv->path) { + csv->file = stdout; // No path given + } + else if (*csv->path == '-' && csv->path[1] == '\0') { + csv->file = stdout; // STDOUT requested + } + else { + csv->file = fopen(csv->path, "a"); + if (!csv->file) { + fprintf(stderr, "rtl_433: failed to open output file\n"); + exit(1); + } + } +} + static void R_API_CALLCONV data_output_csv_free(data_output_t *output) { data_output_csv_t *csv = (data_output_csv_t *)output; + if (!output) { + return; + } + free((void *)csv->fields); free(csv); } -struct data_output *data_output_csv_create(int log_level, FILE *file) +struct data_output *data_output_csv_create(int log_level, char const *path) { data_output_csv_t *csv = calloc(1, sizeof(data_output_csv_t)); if (!csv) { @@ -627,16 +712,19 @@ struct data_output *data_output_csv_create(int log_level, FILE *file) return NULL; // NOTE: returns NULL on alloc failure. } - csv->output.log_level = log_level; - csv->output.print_data = print_csv_data; - csv->output.print_array = print_csv_array; - csv->output.print_string = print_csv_string; - csv->output.print_double = print_csv_double; - csv->output.print_int = print_csv_int; - csv->output.output_start = data_output_csv_start; - csv->output.output_print = data_output_csv_print; - csv->output.output_free = data_output_csv_free; - csv->file = file; + csv->output.log_level = log_level; + csv->output.print_data = print_csv_data; + csv->output.print_array = print_csv_array; + csv->output.print_string = print_csv_string; + csv->output.print_double = print_csv_double; + csv->output.print_int = print_csv_int; + csv->output.output_start = data_output_csv_start; + csv->output.output_print = data_output_csv_print; + csv->output.output_reopen = data_output_csv_reopen; + csv->output.output_free = data_output_csv_free; + csv->path = path; + + data_output_csv_reopen(&csv->output); return (struct data_output *)csv; } diff --git a/src/output_log.c b/src/output_log.c index 0973e2a89..07a5611db 100644 --- a/src/output_log.c +++ b/src/output_log.c @@ -24,6 +24,7 @@ typedef struct { struct data_output output; + char const *path; FILE *file; } data_output_log_t; @@ -134,15 +135,39 @@ static void R_API_CALLCONV data_output_log_print(data_output_t *output, data_t * fflush(log->file); } +static void R_API_CALLCONV data_output_log_reopen(data_output_t *output) +{ + data_output_log_t *log = (data_output_log_t *)output; + + if (log->file && log->file != stdout && log->file != stderr) { + fclose(log->file); + } + + if (!log->path || !*log->path) { + log->file = stderr; // No path given, note that we default to STDERR + } + else if (*log->path == '-' && log->path[1] == '\0') { + log->file = stdout; // STDOUT requested + } + else { + log->file = fopen(log->path, "a"); + if (!log->file) { + fprintf(stderr, "rtl_433: failed to open output file\n"); + exit(1); + } + } +} + static void R_API_CALLCONV data_output_log_free(data_output_t *output) { if (!output) { return; } + free(output); } -struct data_output *data_output_log_create(int log_level, FILE *file) +struct data_output *data_output_log_create(int log_level, char const *path) { data_output_log_t *log = calloc(1, sizeof(data_output_log_t)); if (!log) { @@ -150,19 +175,18 @@ struct data_output *data_output_log_create(int log_level, FILE *file) return NULL; // NOTE: returns NULL on alloc failure. } - if (!file) { - file = stderr; // print to stderr by default - } - - log->output.log_level = log_level; - log->output.print_data = print_log_data; - log->output.print_array = print_log_array; - log->output.print_string = print_log_string; - log->output.print_double = print_log_double; - log->output.print_int = print_log_int; - log->output.output_print = data_output_log_print; - log->output.output_free = data_output_log_free; - log->file = file; + log->output.log_level = log_level; + log->output.print_data = print_log_data; + log->output.print_array = print_log_array; + log->output.print_string = print_log_string; + log->output.print_double = print_log_double; + log->output.print_int = print_log_int; + log->output.output_print = data_output_log_print; + log->output.output_reopen = data_output_log_reopen; + log->output.output_free = data_output_log_free; + log->path = path; + + data_output_log_reopen(&log->output); // Note: print to stderr by default return (struct data_output *)log; } diff --git a/src/output_trigger.c b/src/output_trigger.c index 1e351b916..da233d744 100644 --- a/src/output_trigger.c +++ b/src/output_trigger.c @@ -22,6 +22,7 @@ typedef struct { struct data_output output; + char const *path; FILE *file; } data_output_trigger_t; @@ -34,15 +35,39 @@ static void R_API_CALLCONV data_output_trigger_print(data_output_t *output, data fflush(trigger->file); } +static void R_API_CALLCONV data_output_trigger_reopen(data_output_t *output) +{ + data_output_trigger_t *trigger = (data_output_trigger_t *)output; + + if (trigger->file && trigger->file != stdout && trigger->file != stderr) { + fclose(trigger->file); + } + + if (!trigger->path || !*trigger->path) { + trigger->file = stdout; // No path given + } + else if (*trigger->path == '-' && trigger->path[1] == '\0') { + trigger->file = stdout; // STDOUT requested + } + else { + trigger->file = fopen(trigger->path, "a"); + if (!trigger->file) { + fprintf(stderr, "rtl_433: failed to open output file\n"); + exit(1); + } + } +} + static void R_API_CALLCONV data_output_trigger_free(data_output_t *output) { - if (!output) + if (!output) { return; + } free(output); } -struct data_output *data_output_trigger_create(FILE *file) +struct data_output *data_output_trigger_create(char const *path) { data_output_trigger_t *trigger = calloc(1, sizeof(data_output_trigger_t)); if (!trigger) { @@ -50,9 +75,12 @@ struct data_output *data_output_trigger_create(FILE *file) return NULL; // NOTE: returns NULL on alloc failure. } - trigger->output.output_print = data_output_trigger_print; - trigger->output.output_free = data_output_trigger_free; - trigger->file = file; + trigger->output.output_print = data_output_trigger_print; + trigger->output.output_reopen = data_output_trigger_reopen; + trigger->output.output_free = data_output_trigger_free; + trigger->path = path; + + data_output_trigger_reopen(&trigger->output); return (struct data_output *)trigger; } diff --git a/src/r_api.c b/src/r_api.c index 8fe370f6a..4c3f17c34 100644 --- a/src/r_api.c +++ b/src/r_api.c @@ -1004,11 +1004,11 @@ static int lvlarg_param(char **param, int default_verb) return val; } -/// Opens the path @p param (or STDOUT if empty or `-`) for append writing, removes leading `,` and `:` from path name. -static FILE *fopen_output(char const *param) +/// Returns the path @p param by removing leading `,` and `:` from path name. +static char const *param_output_path(char const *param) { if (!param || !*param) { - return stdout; // No path given + return NULL; // No path given } while (*param == ',') { param++; // Skip all leading `,` @@ -1017,26 +1017,21 @@ static FILE *fopen_output(char const *param) param++; // Skip one leading `:` } if (*param == '-' && param[1] == '\0') { - return stdout; // STDOUT requested + return NULL; // STDOUT requested } - FILE *file = fopen(param, "a"); - if (!file) { - fprintf(stderr, "rtl_433: failed to open output file\n"); - exit(1); - } - return file; + return param; } void add_json_output(r_cfg_t *cfg, char *param) { int log_level = lvlarg_param(¶m, 0); - list_push(&cfg->output_handler, data_output_json_create(log_level, fopen_output(param))); + list_push(&cfg->output_handler, data_output_json_create(log_level, param_output_path(param))); } void add_csv_output(r_cfg_t *cfg, char *param) { int log_level = lvlarg_param(¶m, 0); - list_push(&cfg->output_handler, data_output_csv_create(log_level, fopen_output(param))); + list_push(&cfg->output_handler, data_output_csv_create(log_level, param_output_path(param))); } void start_outputs(r_cfg_t *cfg, char const *const *well_known) @@ -1055,13 +1050,13 @@ void start_outputs(r_cfg_t *cfg, char const *const *well_known) void add_log_output(r_cfg_t *cfg, char *param) { int log_level = lvlarg_param(¶m, LOG_TRACE); - list_push(&cfg->output_handler, data_output_log_create(log_level, fopen_output(param))); + list_push(&cfg->output_handler, data_output_log_create(log_level, param_output_path(param))); } void add_kv_output(r_cfg_t *cfg, char *param) { int log_level = lvlarg_param(¶m, LOG_TRACE); - list_push(&cfg->output_handler, data_output_kv_create(log_level, fopen_output(param))); + list_push(&cfg->output_handler, data_output_kv_create(log_level, param_output_path(param))); } void add_mqtt_output(r_cfg_t *cfg, char *param) @@ -1105,7 +1100,7 @@ void add_http_output(r_cfg_t *cfg, char *param) void add_trigger_output(r_cfg_t *cfg, char *param) { // Note: no log_level, we never trigger on logs. - list_push(&cfg->output_handler, data_output_trigger_create(fopen_output(param))); + list_push(&cfg->output_handler, data_output_trigger_create(param_output_path(param))); } void add_null_output(r_cfg_t *cfg, char *param) @@ -1127,6 +1122,14 @@ void add_rtltcp_output(r_cfg_t *cfg, char *param) list_push(&cfg->raw_handler, raw_output_rtltcp_create(host, port, extra, cfg)); } +void reopen_outputs(struct r_cfg *cfg) +{ + for (size_t i = 0; i < cfg->output_handler.len; ++i) { // list might contain NULLs + data_output_t *output = cfg->output_handler.elems[i]; + data_output_reopen(output); + } +} + void add_sr_dumper(r_cfg_t *cfg, char const *spec, int overwrite) { // create channels diff --git a/src/rtl_433.c b/src/rtl_433.c index 0b2ca1639..d4282571a 100644 --- a/src/rtl_433.c +++ b/src/rtl_433.c @@ -1532,6 +1532,7 @@ static void timer_handler(struct mg_connection *nc, int ev, void *ev_data) //fprintf(stderr, "%s: %d, %d, %p, %p\n", __func__, nc->sock, ev, nc->user_data, ev_data); r_cfg_t *cfg = (r_cfg_t *)nc->user_data; if (sig_hup) { + reopen_outputs(cfg); reopen_dumpers(cfg); sig_hup = 0; } diff --git a/tests/data-test.c b/tests/data-test.c index f534c5e82..821fdad5b 100644 --- a/tests/data-test.c +++ b/tests/data-test.c @@ -40,9 +40,9 @@ int main(void) /* clang-format on */ const char *fields[] = { "label", "house_code", "temp", "array", "array2", "array3", "data", "house_code" }; - void *json_output = data_output_json_create(0, stdout); - void *kv_output = data_output_kv_create(0, stdout); - void *csv_output = data_output_csv_create(0, stdout); + void *json_output = data_output_json_create(0, NULL/* stdout */); + void *kv_output = data_output_kv_create(0, NULL/* stdout */); + void *csv_output = data_output_csv_create(0, NULL/* stdout */); data_output_start(csv_output, fields, sizeof fields / sizeof *fields); data_output_print(json_output, data); fprintf(stdout, "\n");