Add redirector binary and lib 66/253766/3
authorMateusz Majewski <m.majewski2@samsung.com>
Wed, 17 Feb 2021 14:21:43 +0000 (15:21 +0100)
committerMichal Bloch <m.bloch@samsung.com>
Tue, 23 Feb 2021 00:29:39 +0000 (01:29 +0100)
Change-Id: I704c8d0eda44accd1692464f592e3476868f28b8

Makefile.am
configure.ac
dlog-redirect-stdout.pc.in [new file with mode: 0644]
include/dlog-redirect-stdout.h [new file with mode: 0644]
packaging/dlog.spec
src/log-redirect-stdout/internal.c [new file with mode: 0644]
src/log-redirect-stdout/internal.h [new file with mode: 0644]
src/log-redirect-stdout/lib.c [new file with mode: 0644]
src/log-redirect-stdout/main.c [new file with mode: 0644]

index e9ba4fc..7a89c00 100644 (file)
@@ -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 \
index 7bd1b3c..ad9e21b 100644 (file)
@@ -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 (file)
index 0000000..059e069
--- /dev/null
@@ -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 (file)
index 0000000..400f07f
--- /dev/null
@@ -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);
index 7ffe162..57e615e 100644 (file)
@@ -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 (file)
index 0000000..bc2a069
--- /dev/null
@@ -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 <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;
+}
diff --git a/src/log-redirect-stdout/internal.h b/src/log-redirect-stdout/internal.h
new file mode 100644 (file)
index 0000000..f10f784
--- /dev/null
@@ -0,0 +1,5 @@
+#pragma once
+
+#include <dlog.h>
+
+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 (file)
index 0000000..d008a43
--- /dev/null
@@ -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 <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;
+}
diff --git a/src/log-redirect-stdout/main.c b/src/log-redirect-stdout/main.c
new file mode 100644 (file)
index 0000000..7b2bdf9
--- /dev/null
@@ -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 <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);
+}