From e00395b191fac38b78167fb94fdab33e1a9fdf94 Mon Sep 17 00:00:00 2001 From: Mateusz Majewski Date: Wed, 17 Feb 2021 15:21:43 +0100 Subject: [PATCH] Add redirector binary and lib Change-Id: I704c8d0eda44accd1692464f592e3476868f28b8 --- Makefile.am | 38 ++++- configure.ac | 2 +- dlog-redirect-stdout.pc.in | 11 ++ include/dlog-redirect-stdout.h | 25 ++++ packaging/dlog.spec | 42 ++++++ src/log-redirect-stdout/internal.c | 82 +++++++++++ src/log-redirect-stdout/internal.h | 5 + src/log-redirect-stdout/lib.c | 48 +++++++ src/log-redirect-stdout/main.c | 278 +++++++++++++++++++++++++++++++++++++ 9 files changed, 529 insertions(+), 2 deletions(-) create mode 100644 dlog-redirect-stdout.pc.in create mode 100644 include/dlog-redirect-stdout.h create mode 100644 src/log-redirect-stdout/internal.c create mode 100644 src/log-redirect-stdout/internal.h create mode 100644 src/log-redirect-stdout/lib.c create mode 100644 src/log-redirect-stdout/main.c diff --git a/Makefile.am b/Makefile.am index e9ba4fc..7a89c00 100644 --- a/Makefile.am +++ b/Makefile.am @@ -18,6 +18,7 @@ dlog_includedir = $(includedir)/dlog dlog_include_HEADERS = \ include/dlog.h \ include/dlogutil.h \ + include/dlog-redirect-stdout.h \ include/dlog-internal.h lib_LTLIBRARIES = libdlog.la @@ -218,6 +219,41 @@ dlogmetrics_SOURCES = \ src/shared/logcommon.c \ src/shared/metrics.c +lib_LTLIBRARIES += libdlog_redirect_stdout.la + +libdlog_redirect_stdout_la_CFLAGS = \ + $(AM_CFLAGS) \ + -fvisibility=hidden + +libdlog_redirect_stdout_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -lcap + +libdlog_redirect_stdout_la_SOURCES = \ + src/log-redirect-stdout/lib.c \ + src/log-redirect-stdout/internal.c \ + src/shared/connect_pipe.c \ + src/shared/logcommon.c \ + src/shared/logconfig.c \ + src/shared/parsers.c + +bin_PROGRAMS += dlog_redirect_stdout +dlog_redirect_stdout_CFLAGS = \ + $(AM_CFLAGS) \ + -fPIE + +dlog_redirect_stdout_LDFLAGS = \ + $(AM_LDFLAGS) \ + -pie + +dlog_redirect_stdout_SOURCES = \ + src/log-redirect-stdout/main.c \ + src/log-redirect-stdout/internal.c \ + src/shared/connect_pipe.c \ + src/shared/logcommon.c \ + src/shared/logconfig.c \ + src/shared/parsers.c + usrlibexeclibdlogdir = /usr/libexec/libdlog usrlibexeclibdlog_PROGRAMS = test_libdlog @@ -629,7 +665,7 @@ usrlibtmpfilesddir = /usr/lib/tmpfiles.d usrlibtmpfilesd_DATA = configs/dlog-run.conf pkgconfigdir = $(libdir)/pkgconfig -pkgconfig_DATA = dlog.pc dlogutil.pc +pkgconfig_DATA = dlog.pc dlogutil.pc dlog-redirect-stdout.pc data_DATA = \ configs/dlog-pipe.conf.test \ diff --git a/configure.ac b/configure.ac index 7bd1b3c..ad9e21b 100644 --- a/configure.ac +++ b/configure.ac @@ -88,7 +88,7 @@ fi # output files AC_SUBST([datadir]) AC_SUBST([libexecdir]) -AC_CONFIG_FILES([Makefile dlog.pc dlogutil.pc] +AC_CONFIG_FILES([Makefile dlog.pc dlogutil.pc dlog-redirect-stdout.pc] [dlog_test:tests/dlog_test.in] [dlog_cpu:tests/dlog_cpu.in] [README.testsuite:tests/README.testsuite.in] diff --git a/dlog-redirect-stdout.pc.in b/dlog-redirect-stdout.pc.in new file mode 100644 index 0000000..059e069 --- /dev/null +++ b/dlog-redirect-stdout.pc.in @@ -0,0 +1,11 @@ +prefix=/usr +exec_prefix=${prefix} +libdir=${exec_prefix}/lib +includedir=${prefix}/include + +Name: dlog-redirect-stdout +Description: Redirect standard streams to dlog log buffers +Version: @VERSION@ +Requires.private: capi-base-common +Libs: -L${libdir} -ldlog_redirect_stdout +Cflags: -I${includedir}/dlog diff --git a/include/dlog-redirect-stdout.h b/include/dlog-redirect-stdout.h new file mode 100644 index 0000000..400f07f --- /dev/null +++ b/include/dlog-redirect-stdout.h @@ -0,0 +1,25 @@ +/* MIT License + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + +#pragma once + +int connect_dlog(int buffer, int fileno, const char *tag, int prio); diff --git a/packaging/dlog.spec b/packaging/dlog.spec index 7ffe162..57e615e 100644 --- a/packaging/dlog.spec +++ b/packaging/dlog.spec @@ -127,6 +127,31 @@ Summary: enable quality of service module %description config-qos Enables Quality of Service module in dlog +%package -n libdlog-redirect-stdout +Summary: Redirect standard streams to dlog log buffers +Group: Development/Libraries +Requires: lib%{name} = %{version}-%{release} + +%description -n libdlog-redirect-stdout +Redirect standard streams to dlog log buffers + +%package -n libdlog-redirect-stdout-devel +Summary: Redirect standard streams to dlog log buffers +Group: Development/Libraries +Requires: libdlog-devel +Requires: lib%{name} = %{version}-%{release} + +%description -n libdlog-redirect-stdout-devel +Redirect standard streams to dlog log buffers + +%package -n dlog-redirect-stdout +Summary: Utility to launch any binary with output redirected to dlog +Group: Development/Libraries +Requires: lib%{name} = %{version}-%{release} + +%description -n dlog-redirect-stdout +Utility to launch any binary with output redirected to dlog + %package doc Summary: dlog documentation BuildRequires: doxygen @@ -325,6 +350,23 @@ chsmack -e 'System' %{_libexecdir}/dlog-log-critical %files config-qos %{_sysconfdir}/dlog.conf.d/15-qos.conf +%files -n libdlog-redirect-stdout +%manifest dlog.manifest +%license LICENSE.MIT +%{_libdir}/libdlog_redirect_stdout.so.* + +%files -n libdlog-redirect-stdout-devel +%manifest dlog.manifest +%license LICENSE.MIT +%{_includedir}/dlog/dlog-redirect-stdout.h +%{_libdir}/pkgconfig/dlog-redirect-stdout.pc +%{_libdir}/libdlog_redirect_stdout.so + +%files -n dlog-redirect-stdout +%manifest dlog.manifest +%license LICENSE.APACHE2.0 LICENSE.MIT +%attr(755,log,log) %{_bindir}/dlog_redirect_stdout + %files doc %manifest dlog.manifest %license LICENSE.APACHE2.0 LICENSE.MIT diff --git a/src/log-redirect-stdout/internal.c b/src/log-redirect-stdout/internal.c new file mode 100644 index 0000000..bc2a069 --- /dev/null +++ b/src/log-redirect-stdout/internal.c @@ -0,0 +1,82 @@ +/* MIT License + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + +#include "internal.h" + +#include + +static int setup_single_pipe(log_id_t buffer, const char *tag, log_priority prio, struct log_config *config, int *fd) +{ + if (!log_config_get_boolean(config, "enable_redirector_for_pipe", false)) + return -ENOTSUP; + + /* We don't really have a way to get the PID correctly in + * the daemon, so we send it on connection. This means that + * fork() invalidates the value; there is little we can do + * about it but nevertheless it's still better than nothing. */ + pid_t pid = getpid(); + + char key[20]; + snprintf(key, sizeof key, "%s_write_sock", log_name_by_id(buffer)); + const char *const conf_val = log_config_get(config, key); + if (!conf_val) + return -ENOKEY; + + int len = sizeof(struct dlog_control_msg) + sizeof(struct dlog_control_msg_stdout) + strlen(tag); + struct dlog_control_msg *req = alloca(len); + *req = (struct dlog_control_msg) { + .length = len, + .request = DLOG_REQ_STDOUT, + .result = 0, + }; + struct dlog_control_msg_stdout *data = (struct dlog_control_msg_stdout *)&req->data; + *data = (struct dlog_control_msg_stdout) { + .pid = pid, + .prio = prio, + }; + memcpy(data->tag, tag, strlen(tag)); + + *fd = connect_pipe(conf_val, req, DEFAULT_WAIT_PIPE_MS); + if (*fd < 0) + return *fd; + + return 0; +} + +int setup_single_unstructed(log_id_t buffer, const char *tag, log_priority prio, int *fd) +{ + __attribute__((cleanup(log_config_free))) struct log_config config; + + int r = log_config_read(&config); + if (r < 0) + return r; + + const char *const backend = log_config_get(&config, "backend"); + if (!backend) + return -ENOKEY; + else if (!strcmp(backend, "logger")) + return -ENOTSUP; // To be added after the AL interface is complete + else if (!strcmp(backend, "pipe")) + return setup_single_pipe(buffer, tag, prio, &config, fd); + else + return -ENOTSUP; +} diff --git a/src/log-redirect-stdout/internal.h b/src/log-redirect-stdout/internal.h new file mode 100644 index 0000000..f10f784 --- /dev/null +++ b/src/log-redirect-stdout/internal.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +int setup_single_unstructed(log_id_t buffer, const char *tag, log_priority prio, int *fd); diff --git a/src/log-redirect-stdout/lib.c b/src/log-redirect-stdout/lib.c new file mode 100644 index 0000000..d008a43 --- /dev/null +++ b/src/log-redirect-stdout/lib.c @@ -0,0 +1,48 @@ +/* MIT License + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + +#include +#include + +#include + +#include "internal.h" + +EXPORT_API int connect_dlog(int buffer, int fileno, const char *tag, int prio) +{ + if (!tag) + return -EINVAL; + + __attribute__((cleanup(close_fd))) int fd = -1; + + int r = setup_single_unstructed((log_id_t)buffer, tag, (log_priority)prio, &fd); + if (r < 0) + return r; + + r = TEMP_FAILURE_RETRY(dup2(fd, fileno)); + if (r == -1) + return -errno; + else if (r != fileno) + return -EIO; + + return 0; +} diff --git a/src/log-redirect-stdout/main.c b/src/log-redirect-stdout/main.c new file mode 100644 index 0000000..7b2bdf9 --- /dev/null +++ b/src/log-redirect-stdout/main.c @@ -0,0 +1,278 @@ +/* MIT License + * + * Copyright (c) 2020 Samsung Electronics Co., Ltd + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is furnished + * to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. */ + +#include +#include + +#include + +#include "internal.h" + +/* Tag length is limited by maximum request size. */ +#define MAX_TAG_LENGTH (MAX_LOGGER_REQUEST_LEN - sizeof(struct dlog_control_msg) - sizeof(struct dlog_control_msg_stdout)) + +struct parse_redirect_info { + bool enabled; + + log_id_t buffer; + char tag[MAX_TAG_LENGTH + 1]; + log_priority prio; +}; + +struct parse_info { + char *cmd; + char **argv; + struct parse_redirect_info out; + struct parse_redirect_info err; +}; + +struct parse_result { + enum { + PARSE_OK, + PARSE_INVALID_BUFNAME, + PARSE_INVALID_PRIORITY, + PARSE_UNKNOWN_OPTION, + PARSE_MISSING_ARGUMENT, + PARSE_MISSING_ARGV, + PARSE_DISABLED, + } result; + struct parse_info info; +}; + +struct parse_result parse_command_line(int argc, char **argv) +{ + struct parse_redirect_info out = { + .enabled = false, + .buffer = LOG_ID_INVALID, + .tag = {}, + .prio = DLOG_DEFAULT, + }; + struct parse_redirect_info err = { + .enabled = false, + .buffer = LOG_ID_INVALID, + .tag = {}, + .prio = DLOG_DEFAULT, + }; + + for (;;) { + int opt = getopt_long(argc, argv, ":", (struct option[]) { + { "stdout" , no_argument, NULL, 1 }, + { "stderr" , no_argument, NULL, 2 }, + { "outbuffer" , required_argument, NULL, 3 }, + { "errbuffer" , required_argument, NULL, 4 }, + { "outtag" , required_argument, NULL, 5 }, + { "errtag" , required_argument, NULL, 6 }, + { "outpriority", required_argument, NULL, 7 }, + { "errpriority", required_argument, NULL, 8 }, + { NULL, 0, NULL, 0 }, + }, NULL); + + switch (opt) { + case 1: // --stdout + out.enabled = true; + break; + + case 2: // --stderr + err.enabled = true; + break; + + case 3: // --outbuffer + out.buffer = log_id_by_name(optarg); + if (out.buffer == LOG_ID_INVALID) + return (struct parse_result) { .result = PARSE_INVALID_BUFNAME, }; + break; + + case 4: // --errbuffer + err.buffer = log_id_by_name(optarg); + if (err.buffer == LOG_ID_INVALID) + return (struct parse_result) { .result = PARSE_INVALID_BUFNAME, }; + break; + + case 5: // --outtag + snprintf(out.tag, sizeof(out.tag), "%s", optarg); + break; + + case 6: // --errtag + snprintf(err.tag, sizeof(err.tag), "%s", optarg); + break; + + case 7: // --outpriority + if (strlen(optarg) > 1 || strlen(optarg) == 0) + return (struct parse_result) { .result = PARSE_INVALID_PRIORITY, }; + out.prio = filter_char_to_pri(optarg[0]); + if (out.prio == DLOG_UNKNOWN) + return (struct parse_result) { .result = PARSE_INVALID_PRIORITY, }; + break; + + case 8: // --errpriority + if (strlen(optarg) > 1 || strlen(optarg) == 0) + return (struct parse_result) { .result = PARSE_INVALID_PRIORITY, }; + err.prio = filter_char_to_pri(optarg[0]); + if (err.prio == DLOG_UNKNOWN) + return (struct parse_result) { .result = PARSE_INVALID_PRIORITY, }; + break; + + case '?': + return (struct parse_result) { .result = PARSE_UNKNOWN_OPTION, }; + + case ':': + return (struct parse_result) { .result = PARSE_MISSING_ARGUMENT, }; + + case -1: + if (argc <= optind) + return (struct parse_result) { .result = PARSE_MISSING_ARGV, }; + + if (out.enabled) { + if (out.buffer == LOG_ID_INVALID) + out.buffer = LOG_ID_MAIN; + if (out.prio == DLOG_DEFAULT) + out.prio = DLOG_INFO; + if (out.tag[0] == '\0') { + /* Start with the constant part because dlogutil filters + * can do wildcards like "FOO*" but not "*FOO" */ + snprintf(out.tag, sizeof(out.tag), "STDOUT_%s", argv[optind]); + } + } else { + if (out.buffer != LOG_ID_INVALID || out.prio != DLOG_DEFAULT || out.tag[0] != '\0') + return (struct parse_result) { .result = PARSE_DISABLED, }; + } + if (err.enabled) { + if (err.buffer == LOG_ID_INVALID) + err.buffer = LOG_ID_MAIN; + if (err.prio == DLOG_DEFAULT) + err.prio = DLOG_ERROR; + if (err.tag[0] == '\0') + snprintf(err.tag, sizeof(err.tag), "STDERR_%s", argv[optind]); + } else { + if (err.buffer != LOG_ID_INVALID || err.prio != DLOG_DEFAULT || err.tag[0] != '\0') + return (struct parse_result) { .result = PARSE_DISABLED, }; + } + + return (struct parse_result) { + .result = PARSE_OK, + .info = { + .cmd = argv[optind], + .argv = argv + optind, + .out = out, + .err = err, + } + }; + } + } +} + +int setup_single(const struct parse_redirect_info *info, int *fd) +{ + if (!info->enabled) + return 0; + + return setup_single_unstructed(info->buffer, info->tag, info->prio, fd); +} + +int try_redirect(const struct parse_info *info) +{ + if (!info->out.enabled && !info->err.enabled) + return 0; + + __attribute__((cleanup(close_fd))) int outfd = -1; + __attribute__((cleanup(close_fd))) int errfd = -1; + + int r = setup_single(&info->out, &outfd); + if (r < 0) + return r; + + r = setup_single(&info->err, &errfd); + if (r < 0) + return r; + + /* We wait until both FDs are open, even though we could dup2 + * stdout right when opened (so that redirection would work + * for stdout even if the stderr sink fails to open), because + * failure shouldn't really happen and it's better not to let + * errors silently fester. + * + * If somebody happens to use this on a mismaintained system + * on which it is not unusual for opening devices to fail, + * it might be viable to move the dup2 here so that at least + * something gets redirected. */ + + if (info->out.enabled) { + r = TEMP_FAILURE_RETRY(dup2(outfd, STDOUT_FILENO)); + assert(r == STDOUT_FILENO); + } + + if (info->err.enabled) { + r = TEMP_FAILURE_RETRY(dup2(errfd, STDERR_FILENO)); + assert(r == STDERR_FILENO); + } + + return 0; +} + +int main(int argc, char **argv) +{ + struct parse_result result = parse_command_line(argc, argv); + switch (result.result) { + case PARSE_OK: + break; + case PARSE_INVALID_BUFNAME: + fprintf(stderr, "%s: invalid buffer name\n", argv[0]); + return EXIT_FAILURE; + case PARSE_INVALID_PRIORITY: + fprintf(stderr, "%s: invalid priority character\n", argv[0]); + return EXIT_FAILURE; + case PARSE_UNKNOWN_OPTION: + fprintf(stderr, "%s: unknown option\n", argv[0]); + return EXIT_FAILURE; + case PARSE_MISSING_ARGUMENT: + fprintf(stderr, "%s: missing required argument\n", argv[0]); + return EXIT_FAILURE; + case PARSE_MISSING_ARGV: + fprintf(stderr, "%s: missing argv\n", argv[0]); + return EXIT_FAILURE; + case PARSE_DISABLED: + fprintf(stderr, "%s: trying to set options of a redirection that is not enabled\n", argv[0]); + return EXIT_FAILURE; + } + + int r = try_redirect(&result.info); + if (r < 0) { + errno = -r; + fprintf(stderr, "%s: failed to redirect (%m); attempting to run the inner application anyway…\n", argv[0]); + /* We do NOT quit here; we want to redirect anyway. */ + } + + /* We use execv instead of execvp. We first intended to use execvp because + * we wanted the program to be a drop-in replacement for the inner binary, + * but we are considering integrating with systemd, which doesn't resolve + * the binaries like this (which is probably a good idea). We considered + * adding a separate flag to enable such resolution, but in most cases you + * can achieve the same efect by doing "$(which binary)". */ + r = execv(result.info.cmd, result.info.argv); + if (r < 0) { + r = -errno; + fprintf(stderr, "%s: failed to exec (%m)\n", argv[0]); + return EXIT_FAILURE; + } + + assert(false); +} -- 2.7.4