summaryrefslogtreecommitdiffstats
path: root/term-utils/agetty.c
diff options
context:
space:
mode:
authorKarel Zak2017-11-07 11:32:32 +0100
committerKarel Zak2017-11-07 11:32:32 +0100
commit1fc82a1360305f696dc1be6105c9c56a9ea03f52 (patch)
tree035990932961deeed632b647594797da3325c2e7 /term-utils/agetty.c
parentagetty: remove unused flag (diff)
downloadkernel-qcow2-util-linux-1fc82a1360305f696dc1be6105c9c56a9ea03f52.tar.gz
kernel-qcow2-util-linux-1fc82a1360305f696dc1be6105c9c56a9ea03f52.tar.xz
kernel-qcow2-util-linux-1fc82a1360305f696dc1be6105c9c56a9ea03f52.zip
agetty: add support for /etc/issue.d
The /etc/issue file has been originally designed to inform users about the system (version, name, etc.). In last years is growing number of additional tools (containers, maintenance tools and interfaces, ...) and many admins and downstream maintainer want to add some tool specific hints to the issue file, but it mess to share one file between more packages and/or scripts. The solution is /etc/issue.d directory. The directory is extension to the standard system /etc/issue. The /etc/issue file has to exist, otherwise the directory will be ignored. It means "rm /etc/issue" (or --onissue) is still the way how keep our system silent independently on 3rd-party installed files in the /etc/issue.d directory. The content of the files in the directory are printed after content of the /etc/issue. The files are printed in version-sort order and .issue file extension is required (00-foo.issue 01-bar.issue ...). The change is backwardly compatible. Signed-off-by: Karel Zak <kzak@redhat.com>
Diffstat (limited to 'term-utils/agetty.c')
-rw-r--r--term-utils/agetty.c170
1 files changed, 148 insertions, 22 deletions
diff --git a/term-utils/agetty.c b/term-utils/agetty.c
index 4fc5abc24..9763fcd30 100644
--- a/term-utils/agetty.c
+++ b/term-utils/agetty.c
@@ -35,6 +35,7 @@
#include <netdb.h>
#include <ifaddrs.h>
#include <net/if.h>
+#include <sys/utsname.h>
#include "strutils.h"
#include "all-io.h"
@@ -116,9 +117,9 @@
/*
* Things you may want to modify.
*
- * If ISSUE is not defined, agetty will never display the contents of the
- * /etc/issue file. You will not want to spit out large "issue" files at the
- * wrong baud rate. Relevant for System V only.
+ * If ISSUE_SUPPORT is not defined, agetty will never display the contents of
+ * the /etc/issue file. You will not want to spit out large "issue" files at
+ * the wrong baud rate. Relevant for System V only.
*
* You may disagree with the default line-editing etc. characters defined
* below. Note, however, that DEL cannot be used for interrupt generation
@@ -127,8 +128,14 @@
/* Displayed before the login prompt. */
#ifdef SYSV_STYLE
-# define ISSUE _PATH_ISSUE
-# include <sys/utsname.h>
+# define ISSUE_SUPPORT
+# if defined(HAVE_SCANDIRAT) && defined(HAVE_OPENAT)
+# include <dirent.h>
+# include "fileutils.h"
+# define ISSUEDIR_SUPPORT
+# define ISSUEDIR_EXT ".issue"
+# define ISSUEDIR_EXTSIZ (sizeof(ISSUEDIR_EXT) - 1)
+# endif
#endif
/* Login prompt. */
@@ -169,7 +176,7 @@ struct options {
char *vcline; /* line of virtual console */
char *term; /* terminal type */
char *initstring; /* modem init string */
- char *issue; /* alternative issue file */
+ char *issue; /* alternative issue file or directory */
char *erasechars; /* string with erase chars */
char *killchars; /* string with kill chars */
char *osrelease; /* /etc/os-release data */
@@ -188,7 +195,7 @@ enum {
};
#define F_PARSE (1<<0) /* process modem status messages */
-#define F_ISSUE (1<<1) /* display /etc/issue */
+#define F_ISSUE (1<<1) /* display /etc/issue or /etc/issue.d */
#define F_RTSCTS (1<<2) /* enable RTS/CTS flow control */
#define F_INITSTRING (1<<4) /* initstring is set */
@@ -342,8 +349,7 @@ int main(int argc, char **argv)
struct options options = {
.flags = F_ISSUE, /* show /etc/issue (SYSV_STYLE) */
.login = _PATH_LOGIN, /* default login program */
- .tty = "tty1", /* default tty line */
- .issue = ISSUE /* default issue file */
+ .tty = "tty1" /* default tty line */
};
char *login_argv[LOGIN_ARGV_MAX + 1];
int login_argc = 0;
@@ -629,9 +635,12 @@ static void output_version(void)
#ifdef KDGKBLED
"hints",
#endif
-#ifdef ISSUE
+#ifdef ISSUE_SUPPORT
"issue",
#endif
+#ifdef ISSUEDIR_SUPPORT
+ "issue.d",
+#endif
#ifdef KDGKBMODE
"keyboard mode",
#endif
@@ -1657,18 +1666,117 @@ static int wait_for_term_input(int fd)
}
}
#endif /* AGETTY_RELOAD */
+
+#ifdef ISSUEDIR_SUPPORT
+static int issuedir_filter(const struct dirent *d)
+{
+ size_t namesz;
+
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (d->d_type != DT_UNKNOWN && d->d_type != DT_REG &&
+ d->d_type != DT_LNK)
+ return 0;
+#endif
+ if (*d->d_name == '.')
+ return 0;
+
+ namesz = strlen(d->d_name);
+ if (!namesz || namesz < ISSUEDIR_EXTSIZ + 1 ||
+ strcmp(d->d_name + (namesz - ISSUEDIR_EXTSIZ), ISSUEDIR_EXT))
+ return 0;
+
+ /* Accept this */
+ return 1;
+}
+
+static FILE *issuedir_next_file(int dd, struct dirent **namelist, int nfiles, int *n)
+{
+ while (*n < nfiles) {
+ struct dirent *d = namelist[*n];
+ struct stat st;
+ FILE *f;
+
+ (*n)++;
+
+ if (fstatat(dd, d->d_name, &st, 0) ||
+ !S_ISREG(st.st_mode))
+ continue;
+
+ f = fopen_at(dd, d->d_name, O_RDONLY|O_CLOEXEC, "r" UL_CLOEXECSTR);
+ if (f)
+ return f;
+ }
+ return NULL;
+}
+
+#endif /* ISSUEDIR_SUPPORT */
+
+#ifndef ISSUE_SUPPORT
+static void print_issue_file(struct options *op, struct termios *tp __attribute__((__unused__)))
+{
+ if ((op->flags & F_NONL) == 0) {
+ /* Issue not in use, start with a new line. */
+ write_all(STDOUT_FILENO, "\r\n", 2);
+ }
+}
+#else /* ISSUE_SUPPORT */
+
static void print_issue_file(struct options *op, struct termios *tp)
{
-#ifdef ISSUE
- FILE *fd;
+ const char *filename, *dirname = NULL;
+ FILE *f = NULL;
+#ifdef ISSUEDIR_SUPPORT
+ int dd = -1, nfiles = 0, i;
+ struct dirent **namelist = NULL;
#endif
if ((op->flags & F_NONL) == 0) {
/* Issue not in use, start with a new line. */
write_all(STDOUT_FILENO, "\r\n", 2);
}
-#ifdef ISSUE
- if ((op->flags & F_ISSUE) && (fd = fopen(op->issue, "r"))) {
+ if (!(op->flags & F_ISSUE))
+ return;
+
+ /*
+ * The custom issue file or directory specified by: agetty -f <path>.
+ * Note that nothing is printed if the file/dir does not exist.
+ */
+ filename = op->issue;
+ if (filename) {
+ struct stat st;
+
+ if (stat(filename, &st) < 0)
+ return;
+ if (S_ISDIR(st.st_mode)) {
+ dirname = filename;
+ filename = NULL;
+ }
+ } else {
+ /* The default /etc/issue and optional /etc/issue.d directory
+ * as extension to the file. The /etc/issue.d directory is
+ * ignored if there is no /etc/issue file. The file may be
+ * empty or symlink.
+ */
+ if (access(_PATH_ISSUE, F_OK|R_OK) != 0)
+ return;
+ filename = _PATH_ISSUE;
+ dirname = _PATH_ISSUEDIR;
+ }
+
+#ifdef ISSUEDIR_SUPPORT
+ if (dirname) {
+ dd = open(dirname, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
+ if (dd >= 0)
+ nfiles = scandirat(dd, ".", &namelist, issuedir_filter, versionsort);
+ if (nfiles <= 0)
+ dirname = NULL;
+ }
+ i = 0;
+#endif
+ if (filename)
+ f = fopen(filename, "r");
+
+ if (f || dirname) {
int c, oflag = tp->c_oflag; /* Save current setting. */
if ((op->flags & F_VCONSOLE) == 0) {
@@ -1677,12 +1785,23 @@ static void print_issue_file(struct options *op, struct termios *tp)
tcsetattr(STDIN_FILENO, TCSADRAIN, tp);
}
- while ((c = getc(fd)) != EOF) {
- if (c == '\\')
- output_special_char(getc(fd), op, tp, fd);
- else
- putchar(c);
- }
+ do {
+#ifdef ISSUEDIR_SUPPORT
+ if (!f && i < nfiles)
+ f = issuedir_next_file(dd, namelist, nfiles, &i);
+#endif
+ if (!f)
+ break;
+ while ((c = getc(f)) != EOF) {
+ if (c == '\\')
+ output_special_char(getc(f), op, tp, f);
+ else
+ putchar(c);
+ }
+ fclose(f);
+ f = NULL;
+ } while (dirname);
+
fflush(stdout);
if ((op->flags & F_VCONSOLE) == 0) {
@@ -1691,10 +1810,17 @@ static void print_issue_file(struct options *op, struct termios *tp)
/* Wait till output is gone. */
tcsetattr(STDIN_FILENO, TCSADRAIN, tp);
}
- fclose(fd);
}
-#endif /* ISSUE */
+
+#ifdef ISSUEDIR_SUPPORT
+ for (i = 0; i < nfiles; i++)
+ free(namelist[i]);
+ free(namelist);
+ if (dd >= 0)
+ close(dd);
+#endif
}
+#endif /* ISSUE_SUPPORT */
/* Show login prompt, optionally preceded by /etc/issue contents. */
static void do_prompt(struct options *op, struct termios *tp)