/* SPDX-License-Identifier: LGPL-2.1+ */
-/***
- This file is part of systemd.
-
- Copyright 2010 Lennart Poettering
-
- systemd is free software; you can redistribute it and/or modify it
- under the terms of the GNU Lesser General Public License as published by
- the Free Software Foundation; either version 2.1 of the License, or
- (at your option) any later version.
-
- systemd is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public License
- along with systemd; If not, see <http://www.gnu.org/licenses/>.
-***/
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
+#include <linux/kd.h>
+#include <linux/tiocl.h>
+#include <linux/vt.h>
+#include <poll.h>
+#include <signal.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
+#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/sysmacros.h>
#include <sys/time.h>
-#include <linux/kd.h>
-#include <linux/tiocl.h>
-#include <linux/vt.h>
-#include <poll.h>
-#include <signal.h>
-#include <sys/ioctl.h>
#include <sys/types.h>
+#include <sys/utsname.h>
#include <termios.h>
#include <unistd.h>
#include "alloc-util.h"
+#include "copy.h"
+#include "def.h"
#include "env-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "io-util.h"
#include "log.h"
#include "macro.h"
+#include "pager.h"
#include "parse-util.h"
#include "path-util.h"
#include "proc-cmdline.h"
*
* Note: strictly speaking this actually watches for the device being closed, it does *not* really watch
* whether a tty loses its controlling process. However, unless some rogue process uses TIOCNOTTY on /dev/tty
- * *after* closing its tty otherwise this will not become a problem. As long as the administrator makes sure
- * not configure any service on the same tty as an untrusted user this should not be a problem. (Which he
+ * *after* closing its tty otherwise this will not become a problem. As long as the administrator makes sure to
+ * not configure any service on the same tty as an untrusted user this should not be a problem. (Which they
* probably should not do anyway.) */
if ((flags & ~ACQUIRE_TERMINAL_PERMISSIVE) == ACQUIRE_TERMINAL_WAIT) {
fd = safe_close(fd);
}
- r = fd;
- fd = -1;
-
- return r;
+ return TAKE_FD(fd);
}
int release_terminal(void) {
if (r < 0)
log_warning_errno(r, "Failed to reset terminal, ignoring: %m");
- r = make_stdio(fd);
+ r = rearrange_stdio(fd, fd, fd); /* This invalidates 'fd' both on success and on failure. */
if (r < 0)
- return log_error_errno(r, "Failed to duplicate terminal fd: %m");
+ return log_error_errno(r, "Failed to make terminal stdin/stdout/stderr: %m");
reset_terminal_feature_caches();
tty = active;
}
- if (tty == active) {
- *ret = active;
- active = NULL;
- } else {
+ if (tty == active)
+ *ret = TAKE_PTR(active);
+ else {
char *tmp;
tmp = strdup(tty);
assert(ret);
- /* If we /sys is mounted read-only this means we are running in some kind of container environment. In that
+ /* If /sys is mounted read-only this means we are running in some kind of container environment. In that
* case /sys would reflect the host system, not us, hence ignore the data we can read from it. */
if (path_is_read_only_fs("/sys") > 0)
goto fallback;
goto fallback;
}
- *ret = l;
- l = NULL;
+ *ret = TAKE_PTR(l);
return 0;
if (r < 0)
return r;
- *ret = l;
- l = NULL;
+ *ret = TAKE_PTR(l);
return 0;
}
}
bool on_tty(void) {
- if (cached_on_tty < 0)
- cached_on_tty = isatty(STDOUT_FILENO) > 0;
-
- return cached_on_tty;
-}
-
-int make_stdio(int fd) {
- int r = 0;
-
- assert(fd >= 0);
-
- if (dup2(fd, STDIN_FILENO) < 0)
- r = -errno;
- if (dup2(fd, STDOUT_FILENO) < 0 && r >= 0)
- r = -errno;
- if (dup2(fd, STDERR_FILENO) < 0 && r >= 0)
- r = -errno;
- if (fd >= 3)
- safe_close(fd);
+ /* We check both stdout and stderr, so that situations where pipes on the shell are used are reliably
+ * recognized, regardless if only the output or the errors are piped to some place. Since on_tty() is generally
+ * used to default to a safer, non-interactive, non-color mode of operation it's probably good to be defensive
+ * here, and check for both. Note that we don't check for STDIN_FILENO, because it should fine to use fancy
+ * terminal functionality when outputting stuff, even if the input is piped to us. */
- /* Explicitly unset O_CLOEXEC, since if fd was < 3, then dup2() was a NOP and the bit hence possibly set. */
- stdio_unset_cloexec();
-
- return r;
-}
-
-int make_null_stdio(void) {
- int null_fd, r;
-
- null_fd = open("/dev/null", O_RDWR|O_NOCTTY|O_CLOEXEC);
- if (null_fd < 0)
- return -errno;
-
- r = make_stdio(null_fd);
-
- reset_terminal_feature_caches();
+ if (cached_on_tty < 0)
+ cached_on_tty =
+ isatty(STDOUT_FILENO) > 0 &&
+ isatty(STDERR_FILENO) > 0;
- return r;
+ return cached_on_tty;
}
int getttyname_malloc(int fd, char **ret) {
bool colors_enabled(void) {
/* Returns true if colors are considered supported on our stdout. For that we check $SYSTEMD_COLORS first
- * (which is the explicit way to turn off/on colors). If that didn't work we turn off colors unless we are on a
+ * (which is the explicit way to turn colors on/off). If that didn't work we turn colors off unless we are on a
* TTY. And if we are on a TTY we turn it off if $TERM is set to "dumb". There's one special tweak though: if
* we are PID 1 then we do not check whether we are connected to a TTY, because we don't keep /dev/console open
* continously due to fear of SAK, and hence things are a bit weird. */
/* Returns true if we assume that color is supported on /dev/console.
*
* For that we first check if we explicitly got told to use colors or not, by checking $SYSTEMD_COLORS. If that
- * didn't tell us anything we check whether PID 1 has $TERM set, and if not whether $TERM is set on the kernel
- * command line. If we find $TERM set we assume color if it's not set to "dumb", similar to regular
+ * isn't set we check whether PID 1 has $TERM set, and if not, whether TERM is set on the kernel command
+ * line. If we find $TERM set we assume color if it's not set to "dumb", similarly to how regular
* colors_enabled() operates. */
b = getenv_bool("SYSTEMD_COLORS");
return 0;
}
+
+static bool urlify_enabled(void) {
+ static int cached_urlify_enabled = -1;
+
+ /* Unfortunately 'less' doesn't support links like this yet ðŸ˜, hence let's disable this as long as there's a
+ * pager in effect. Let's drop this check as soon as less got fixed a and enough time passed so that it's safe
+ * to assume that a link-enabled 'less' version has hit most installations. */
+
+ if (cached_urlify_enabled < 0) {
+ int val;
+
+ val = getenv_bool("SYSTEMD_URLIFY");
+ if (val >= 0)
+ cached_urlify_enabled = val;
+ else
+ cached_urlify_enabled = colors_enabled() && !pager_have();
+ }
+
+ return cached_urlify_enabled;
+}
+
+int terminal_urlify(const char *url, const char *text, char **ret) {
+ char *n;
+
+ assert(url);
+
+ /* Takes an URL and a pretty string and formats it as clickable link for the terminal. See
+ * https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda for details. */
+
+ if (isempty(text))
+ text = url;
+
+ if (urlify_enabled())
+ n = strjoin("\x1B]8;;", url, "\a", text, "\x1B]8;;\a");
+ else
+ n = strdup(text);
+ if (!n)
+ return -ENOMEM;
+
+ *ret = n;
+ return 0;
+}
+
+int terminal_urlify_path(const char *path, const char *text, char **ret) {
+ _cleanup_free_ char *absolute = NULL;
+ struct utsname u;
+ const char *url;
+ int r;
+
+ assert(path);
+
+ /* Much like terminal_urlify() above, but takes a file system path as input
+ * and turns it into a proper file:// URL first. */
+
+ if (isempty(path))
+ return -EINVAL;
+
+ if (isempty(text))
+ text = path;
+
+ if (!urlify_enabled()) {
+ char *n;
+
+ n = strdup(text);
+ if (!n)
+ return -ENOMEM;
+
+ *ret = n;
+ return 0;
+ }
+
+ if (uname(&u) < 0)
+ return -errno;
+
+ if (!path_is_absolute(path)) {
+ r = path_make_absolute_cwd(path, &absolute);
+ if (r < 0)
+ return r;
+
+ path = absolute;
+ }
+
+ /* As suggested by https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda, let's include the local
+ * hostname here. Note that we don't use gethostname_malloc() or gethostname_strict() since we are interested
+ * in the raw string the kernel has set, whatever it may be, under the assumption that terminals are not overly
+ * careful with validating the strings either. */
+
+ url = strjoina("file://", u.nodename, path);
+
+ return terminal_urlify(url, text, ret);
+}
+
+static int cat_file(const char *filename, bool newline) {
+ _cleanup_fclose_ FILE *f = NULL;
+ _cleanup_free_ char *urlified = NULL;
+ int r;
+
+ f = fopen(filename, "re");
+ if (!f)
+ return -errno;
+
+ r = terminal_urlify_path(filename, NULL, &urlified);
+ if (r < 0)
+ return r;
+
+ printf("%s%s# %s%s\n",
+ newline ? "\n" : "",
+ ansi_highlight_blue(),
+ urlified,
+ ansi_normal());
+ fflush(stdout);
+
+ for (;;) {
+ _cleanup_free_ char *line = NULL;
+
+ r = read_line(f, LONG_LINE_MAX, &line);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read \"%s\": %m", filename);
+ if (r == 0)
+ break;
+
+ puts(line);
+ }
+
+ return 0;
+}
+
+int cat_files(const char *file, char **dropins, CatFlags flags) {
+ char **path;
+ int r;
+
+ if (file) {
+ r = cat_file(file, false);
+ if (r == -ENOENT && (flags & CAT_FLAGS_MAIN_FILE_OPTIONAL))
+ printf("%s# config file %s not found%s\n",
+ ansi_highlight_magenta(),
+ file,
+ ansi_normal());
+ else if (r < 0)
+ return log_warning_errno(r, "Failed to cat %s: %m", file);
+ }
+
+ STRV_FOREACH(path, dropins) {
+ r = cat_file(*path, file || path != dropins);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to cat %s: %m", *path);
+ }
+
+ return 0;
+}
+
+void print_separator(void) {
+
+ /* Outputs a separator line that resolves to whitespace when copied from the terminal. We do that by outputting
+ * one line filled with spaces with ANSI underline set, followed by a second (empty) line. */
+
+ if (underline_enabled()) {
+ size_t i, c;
+
+ c = columns();
+
+ flockfile(stdout);
+ fputs_unlocked(ANSI_UNDERLINE, stdout);
+
+ for (i = 0; i < c; i++)
+ fputc_unlocked(' ', stdout);
+
+ fputs_unlocked(ANSI_NORMAL "\n\n", stdout);
+ funlockfile(stdout);
+ } else
+ fputs("\n\n", stdout);
+}