summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--include/pathnames.h2
-rw-r--r--term-utils/agetty.829
-rw-r--r--term-utils/agetty.c170
3 files changed, 171 insertions, 30 deletions
diff --git a/include/pathnames.h b/include/pathnames.h
index 0778d4f20..59cc66736 100644
--- a/include/pathnames.h
+++ b/include/pathnames.h
@@ -69,6 +69,8 @@
#endif
#define _PATH_ISSUE "/etc/issue"
+#define _PATH_ISSUEDIR _PATH_ISSUE ".d"
+
#define _PATH_OS_RELEASE_ETC "/etc/os-release"
#define _PATH_OS_RELEASE_USR "/usr/lib/os-release"
#define _PATH_NUMLOCK_ON _PATH_RUNSTATEDIR "/numlock-on"
diff --git a/term-utils/agetty.8 b/term-utils/agetty.8
index f21634494..96d94bb48 100644
--- a/term-utils/agetty.8
+++ b/term-utils/agetty.8
@@ -32,7 +32,7 @@ Optionally does not hang up when it is given an already opened line
.IP \(bu
Optionally does not display the contents of the \fI/etc/issue\fP file.
.IP \(bu
-Optionally displays an alternative issue file instead of \fI/etc/issue\fP.
+Optionally displays an alternative issue file or directory instead of \fI/etc/issue\fP or \fI/etc/issue.d\fP.
.IP \(bu
Optionally does not ask for a login name.
.IP \(bu
@@ -113,10 +113,12 @@ is added to the \fB/bin/login\fP command line.
.IP
See \fB\-\-login\-options\fR.
.TP
-\-f, \-\-issue\-file \fIissue_file\fP
-Display the contents of \fIissue_file\fP instead of \fI/etc/issue\fP.
-This allows custom messages to be displayed on different terminals.
-The \-\-noissue option will override this option.
+\-f, \-\-issue\-file \fIfile|directory\fP
+Display the contents of \fIfile\fP instead of \fI/etc/issue\fP. If the
+specified path is a \fIdirectory\fP then displays all files with .issue file
+extension in version-sort order from the directory. This allows custom
+messages to be displayed on different terminals. The
+\-\-noissue option will override this option.
.TP
\-h, \-\-flow\-control
Enable hardware (RTS/CTS) flow control. It is left up to the
@@ -337,9 +339,20 @@ Some programs use "\-\-" to indicate that the rest of the commandline should
not be interpreted as options. Use this feature if available by passing "\-\-"
before the username gets passed by \\u.
-.SH ISSUE ESCAPES
-The issue-file (\fI/etc/issue\fP, or the file set with the \fB\-\-issue\-file\fP option)
-may contain certain escape codes to display the system name, date, time
+.SH ISSUE FILES
+The default issue file is \fI/etc/issue\fP. If the file exists then agetty also
+checks for \fI/etc/issue.d\fP directory. The directory is optional extension to
+the default issue file and content of the directory is printed after
+\fI/etc/issue\fP content. If the \fI/etc/issue\fP does not exist than the
+directory is ignored. All files with .issue extension from the directory are
+printed in version-sort order. The directory allow to maintain 3rd-party
+messages independently on the primary system \fI/etc/issue\fP file.
+
+The default path maybe overrided by \fB\-\-issue\-file\fP option. In this case
+specified path has to be file or directory and the default \fI/etc/issue\fP as
+well as \fI/etc/issue.d\fP are ignored.
+
+The issue files may contain certain escape codes to display the system name, date, time
etcetera. All escape codes consist of a backslash (\\) immediately
followed by one of the characters listed below.
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)