See \fB\-\-login\-options\fR.
.TP
\-f, \-\-issue\-file \fIfile|directory\fP
-Display the contents of \fIfile\fP instead of \fI/etc/issue\fP. If the
+Display the contents of \fIfile\fP instead of \fI/etc/issue\fP (or other). 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
on how the login binary parses the command line that might not be sufficient.
Check that the used login program cannot be abused this way.
.PP
-Some programs use "\-\-" to indicate that the rest of the commandline should
+Some programs use "\-\-" to indicate that the rest of the command line should
not be interpreted as options. Use this feature if available by passing "\-\-"
before the username gets passed by \\u.
printed in version-sort order. The directory allow to maintain 3rd-party
messages independently on the primary system \fI/etc/issue\fP file.
+Since version 2.35 additional locations for issue file and directory are
+supported. If the default \fI/etc/issue\fP does not exist than agetty checks
+for \fI/run/issue\fP and \fI/run/issue.d\fP, thereafter for
+\fI/usr/lib/issue\fP and \fI/usr/lib/issue.d\fP. The directory /etc is
+expected for host specific configuration, /run is expected for generated stuff
+and /usr/lib for static distribution maintained configuration.
+
The default path maybe overridden 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.
+specified path has to be file or directory and all the default issue file and
+directory locations are ignored.
+
+The issue file feature is possible to completely disable by \fB\-\-noissue\fP option.
The issue files may contain certain escape codes to display the system name, date, time
etcetera. All escape codes consist of a backslash (\\) immediately
return 1;
}
-static FILE *issuedir_next_file(int dd, struct dirent **namelist, int nfiles, int *n)
+
+static int issuefile_read_stream(struct issue *ie, FILE *f, struct options *op, struct termios *tp);
+
+/* returns: 0 on success, 1 cannot open, <0 on error
+ */
+static int issuedir_read(struct issue *ie, const char *dirname,
+ struct options *op, struct termios *tp)
{
- while (*n < nfiles) {
- struct dirent *d = namelist[*n];
- struct stat st;
- FILE *f;
+ int dd, nfiles, i;
+ struct dirent **namelist = NULL;
- (*n)++;
+ dd = open(dirname, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
+ if (dd < 0)
+ return 1;
- if (fstatat(dd, d->d_name, &st, 0) ||
- !S_ISREG(st.st_mode))
- continue;
+ nfiles = scandirat(dd, ".", &namelist, issuedir_filter, versionsort);
+ if (nfiles <= 0)
+ goto done;
+
+ ie->do_tcsetattr = 1;
+
+ for (i = 0; i < nfiles; i++) {
+ struct dirent *d = namelist[i];
+ FILE *f;
f = fopen_at(dd, d->d_name, O_RDONLY|O_CLOEXEC, "r" UL_CLOEXECSTR);
- if (f)
- return f;
+ if (f) {
+ issuefile_read_stream(ie, f, op, tp);
+ fclose(f);
+ }
}
- return NULL;
+
+ for (i = 0; i < nfiles; i++)
+ free(namelist[i]);
+ free(namelist);
+done:
+ close(dd);
+ return 0;
}
+#else /* !ISSUEDIR_SUPPORT */
+static int issuedir_read(struct issue *ie __attribute__((__unused__)),
+ const char *dirname __attribute__((__unused__)),
+ struct options *op __attribute__((__unused__)),
+ struct termios *tp __attribute__((__unused__)))
+{
+}
#endif /* ISSUEDIR_SUPPORT */
#ifndef ISSUE_SUPPORT
}
#else /* ISSUE_SUPPORT */
+static int issuefile_read_stream(
+ struct issue *ie, FILE *f,
+ struct options *op, struct termios *tp)
+{
+ struct stat st;
+ int c;
+
+ if (fstat(fileno(f), &st) || !S_ISREG(st.st_mode))
+ return 1;
+
+ if (!ie->output)
+ ie->output = open_memstream(&ie->mem, &ie->mem_sz);
+
+ while ((c = getc(f)) != EOF) {
+ if (c == '\\')
+ output_special_char(ie, getc(f), op, tp, f);
+ else
+ putc(c, ie->output);
+ }
+
+ fclose(f);
+ return 0;
+}
+
+static int issuefile_read(
+ struct issue *ie, const char *filename,
+ struct options *op, struct termios *tp)
+{
+ FILE *f = fopen(filename, "r" UL_CLOEXECSTR);
+ int rc = 1;
+
+ if (f)
+ rc = issuefile_read_stream(ie, f, op, tp);
+ fclose(f);
+ return rc;
+}
+
+
#ifdef AGETTY_RELOAD
static int issue_is_changed(struct issue *ie)
{
struct options *op,
struct termios *tp)
{
- const char *filename, *dirname = NULL;
- FILE *f = NULL;
-#ifdef ISSUEDIR_SUPPORT
- int dd = -1, nfiles = 0, i;
- struct dirent **namelist = NULL;
-#endif
+ int has_file = 0;
+
#ifdef AGETTY_RELOAD
netlink_groups = 0;
#endif
-
if (!(op->flags & F_ISSUE))
- return;
+ goto done;
/*
* 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) {
+ if (op->issue) {
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;
- }
-
- ie->output = open_memstream(&ie->mem, &ie->mem_sz);
-#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;
+ if (stat(op->issue, &st) < 0)
+ goto done;
+ if (S_ISDIR(st.st_mode))
+ issuedir_read(ie, op->issue, op, tp);
+ else
+ issuefile_read(ie, op->issue, op, tp);
+ goto done;
}
- i = 0;
-#endif
- if (filename)
- f = fopen(filename, "r");
- if (f || dirname) {
- int c;
- ie->do_tcsetattr = 1;
+ /* 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) {
+ issuefile_read(ie, _PATH_ISSUE, op, tp);
+ issuedir_read(ie, _PATH_ISSUEDIR, op, tp);
+ goto done;
+ }
- 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(ie, getc(f), op, tp, f);
- else
- putc(c, ie->output);
- }
- fclose(f);
- f = NULL;
- } while (dirname);
+ /* Fallback @runstatedir (usually /run) -- the file is not required to
+ * read the dir.
+ */
+ if (issuefile_read(ie, _PATH_RUNSTATEDIR "/" _PATH_ISSUE_FILENAME, op, tp) == 0)
+ has_file++;
+ if (issuedir_read(ie, _PATH_RUNSTATEDIR "/" _PATH_ISSUE_DIRNAME, op, tp) == 0)
+ has_file++;
+ if (has_file)
+ goto done;
+
+ /* Fallback @sysconfstaticdir (usually /usr/lib) -- the file is not
+ * required to read the dir
+ */
+ issuefile_read(ie, _PATH_SYSCONFSTATICDIR "/" _PATH_ISSUE_FILENAME, op, tp);
+ issuedir_read(ie, _PATH_SYSCONFSTATICDIR "/" _PATH_ISSUE_DIRNAME, op, tp);
- if ((op->flags & F_VCONSOLE) == 0)
- ie->do_tcrestore = 1;
- }
+done:
-#ifdef ISSUEDIR_SUPPORT
- for (i = 0; i < nfiles; i++)
- free(namelist[i]);
- free(namelist);
- if (dd >= 0)
- close(dd);
-#endif
#ifdef AGETTY_RELOAD
if (netlink_groups != 0)
open_netlink();
#endif
- fclose(ie->output);
+ if (ie->output)
+ fclose(ie->output);
}
#endif /* ISSUE_SUPPORT */