Skip to content

Commit

Permalink
agetty: add support for /etc/issue.d
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
karelzak committed Nov 7, 2017
1 parent e7f9744 commit 1fc82a1
Show file tree
Hide file tree
Showing 3 changed files with 171 additions and 30 deletions.
2 changes: 2 additions & 0 deletions include/pathnames.h
Expand Up @@ -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"
Expand Down
29 changes: 21 additions & 8 deletions term-utils/agetty.8
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand Down
170 changes: 148 additions & 22 deletions term-utils/agetty.c
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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. */
Expand Down Expand Up @@ -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 */
Expand All @@ -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 */
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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)
Expand Down

6 comments on commit 1fc82a1

@thkukuk
Copy link
Contributor

@thkukuk thkukuk commented on 1fc82a1 Mar 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only want to make aware that quite some distributions (CoreOS, openSUSE, ...) are already using tools using /etc/issue.d. We should make sure to not break this. Currently this breaks all existing solutions.

@karelzak
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... they use *.issue files in the directory?

Anyway, we can add ./configure (e.g. --disable-agetty-issued) option to disable this feature.

@thkukuk
Copy link
Contributor

@thkukuk thkukuk commented on 1fc82a1 Mar 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, missed that the files have to end on .issue, so this should not become a problem. Else all implementations I'm aware of are using it in the same way as agetty, except that additional /usr/lib/issue.d and /run/issue.d are used.

@karelzak
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have discussion about this on mailing list right now :-)

It's good idea to add /run and /usr/lib between the directories in the next version, but I'll probably prefer to use "agetty" in the path (/usr/lib/agetty/issued.d/ etc.).

The final goal is to use /run to store output from 3rd party generators, so it should be possible to adopt to the new agetty feature if you already have any solution, ... but it's too late for v2.32 where is support for /etc/issue.d only. Let's wait for the next v2.33.

@thkukuk
Copy link
Contributor

@thkukuk thkukuk commented on 1fc82a1 Mar 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In https://github.com/thkukuk/issue-generator (which uses the same algorithm and semantic as CoreOS) the order is /usr/lib/issue.d (contains distribution stuff), which can be overwritten by /run/issue.d (dynamic generated by e.g. udev), which can be overwritten by the admin in /etc/issue.d.
If agetty could do the same, that would be really great, then we would not need issue-generator and the CoreOS implementation any longer, but have a drop in replacement without problems during update. I don't like the "agetty" in the path, this makes it tool depending.

@karelzak
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. Makes sense.

Please sign in to comment.