dlog_include_HEADERS = \
include/dlog.h \
include/dlogutil.h \
+ include/dlog-redirect-stdout.h \
include/dlog-internal.h
lib_LTLIBRARIES = libdlog.la
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
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 \
# 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]
--- /dev/null
+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
--- /dev/null
+/* 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);
%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
%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
--- /dev/null
+/* 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 <logconfig.h>
+
+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;
+}
--- /dev/null
+#pragma once
+
+#include <dlog.h>
+
+int setup_single_unstructed(log_id_t buffer, const char *tag, log_priority prio, int *fd);
--- /dev/null
+/* 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 <tizen.h>
+#include <dlog-redirect-stdout.h>
+
+#include <logcommon.h>
+
+#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;
+}
--- /dev/null
+/* 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 <getopt.h>
+#include <stdio.h>
+
+#include <logcommon.h>
+
+#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);
+}