]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
agetty: add support for /etc/issue.d
authorKarel Zak <kzak@redhat.com>
Tue, 7 Nov 2017 10:32:32 +0000 (11:32 +0100)
committerKarel Zak <kzak@redhat.com>
Tue, 7 Nov 2017 10:32:32 +0000 (11:32 +0100)
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>
include/pathnames.h
term-utils/agetty.8
term-utils/agetty.c

index 0778d4f203e7db53d954b97b57a69b9184e524cc..59cc66736216eb15614299a3bbe2048e790e0cea 100644 (file)
@@ -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"
index f216344940db713035797e828f1a851e2a8c4436..96d94bb48fa961b68bcfbb2260e14a658187824c 100644 (file)
@@ -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.
 
index 4fc5abc24b53e6a481aade9d436d857ab3e8ae29..9763fcd30caf6918aac9d2607c439ad962f394f3 100644 (file)
@@ -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"
 /*
  * 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
 
 /* 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)