diff options
-rw-r--r-- | include/pathnames.h | 2 | ||||
-rw-r--r-- | term-utils/agetty.8 | 29 | ||||
-rw-r--r-- | term-utils/agetty.c | 170 |
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) |