Blob Blame History Raw
From ea152b4e9656945f17d18bf4e5ece91e3e57ebb1 Mon Sep 17 00:00:00 2001
From: Karel Zak <kzak@redhat.com>
Date: Tue, 29 Jan 2019 11:57:55 +0100
Subject: [PATCH] agetty: backport RHEL-8 version

The code is identical to RHEL-8.0, except:

 * disabled ISSUEDIR_SUPPORT
 * disabled AGETTY_RELOAD
 * removed \e and \e{name} feature

Addresses: https://bugzilla.redhat.com/show_bug.cgi?id=1664752
Signed-off-by: Karel Zak <kzak@redhat.com>
---
 include/pathnames.h |    5 +-
 term-utils/agetty.c | 1141 +++++++++++++++++++++++++++++++------------
 2 files changed, 839 insertions(+), 307 deletions(-)

diff --git a/include/pathnames.h b/include/pathnames.h
index fa4bddbad..ec5eef918 100644
--- a/include/pathnames.h
+++ b/include/pathnames.h
@@ -64,9 +64,10 @@
 
 /* used in term-utils/agetty.c */
 #define _PATH_ISSUE		"/etc/issue"
-#define _PATH_OS_RELEASE	"/etc/os-release"
+#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_LOCALSTATEDIR "/numlock-on"
-
 #define _PATH_LOGINDEFS		"/etc/login.defs"
 
 /* used in misc-utils/look.c */
diff --git a/term-utils/agetty.c b/term-utils/agetty.c
index 948d77246..c3c7ab504 100644
--- a/term-utils/agetty.c
+++ b/term-utils/agetty.c
@@ -10,6 +10,7 @@
  *
  * This program is freely distributable.
  */
+
 #include <stdio.h>
 #include <unistd.h>
 #include <stdlib.h>
@@ -20,19 +21,21 @@
 #include <sys/ioctl.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/wait.h>
 #include <fcntl.h>
 #include <stdarg.h>
 #include <ctype.h>
-#include <utmp.h>
+#include <utmpx.h>
 #include <getopt.h>
 #include <time.h>
-#include <sys/file.h>
 #include <sys/socket.h>
 #include <langinfo.h>
 #include <grp.h>
 #include <arpa/inet.h>
 #include <netdb.h>
 #include <ifaddrs.h>
+#include <net/if.h>
+#include <sys/utsname.h>
 
 #include "strutils.h"
 #include "all-io.h"
@@ -41,14 +44,37 @@
 #include "c.h"
 #include "widechar.h"
 #include "ttyutils.h"
+#include "env.h"
+
+#ifdef USE_PLYMOUTH_SUPPORT
+# include "plymouth-ctrl.h"
+#endif
+
+#ifdef HAVE_SYS_PARAM_H
+# include <sys/param.h>
+#endif
+
+#if defined(__FreeBSD_kernel__)
+# include <pty.h>
+# ifdef HAVE_UTMP_H
+#  include <utmp.h>
+# endif
+# ifdef HAVE_LIBUTIL_H
+#  include <libutil.h>
+# endif
+#endif
 
 #ifdef __linux__
 #  include <sys/kd.h>
-#  include <sys/param.h>
 #  define USE_SYSLOG
 #  ifndef DEFAULT_VCTERM
 #    define DEFAULT_VCTERM "linux"
 #  endif
+#  if defined (__s390__) || defined (__s390x__)
+#    define DEFAULT_TTYS0  "dumb"
+#    define DEFAULT_TTY32  "ibm327x"
+#    define DEFAULT_TTYS1  "vt220"
+#  endif
 #  ifndef DEFAULT_STERM
 #    define DEFAULT_STERM  "vt102"
 #  endif
@@ -69,6 +95,10 @@
 #  endif
 #endif
 
+#ifdef __FreeBSD_kernel__
+#define USE_SYSLOG
+#endif
+
 /* If USE_SYSLOG is undefined all diagnostics go to /dev/console. */
 #ifdef	USE_SYSLOG
 #  include <syslog.h>
@@ -86,9 +116,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
@@ -97,14 +127,26 @@
 
 /* Displayed before the login prompt. */
 #ifdef	SYSV_STYLE
-#  define ISSUE _PATH_ISSUE
-#  include <sys/utsname.h>
+#  define ISSUE_SUPPORT
 #endif
 
 /* Login prompt. */
 #define LOGIN		"login: "
 #define LOGIN_ARGV_MAX	16		/* Numbers of args for login */
 
+/*
+ * agetty --reload
+ */
+#ifdef AGETTY_RELOAD
+# include <sys/inotify.h>
+# include <linux/netlink.h>
+# include <linux/rtnetlink.h>
+# define AGETTY_RELOAD_FILENAME "/run/agetty.reload"	/* trigger file */
+# define AGETTY_RELOAD_FDNONE	-2			/* uninitialized fd */
+static int inotify_fd = AGETTY_RELOAD_FDNONE;
+static int netlink_fd = AGETTY_RELOAD_FDNONE;
+#endif
+
 /*
  * When multiple baud rates are specified on the command line, the first one
  * we will try is the first one specified.
@@ -116,7 +158,7 @@
 
 struct options {
 	int flags;			/* toggle switches, see below */
-	int timeout;			/* time-out period */
+	unsigned int timeout;			/* time-out period */
 	char *autolog;			/* login the user automatically */
 	char *chdir;			/* Chdir before the login */
 	char *chroot;			/* Chroot before the login */
@@ -126,11 +168,11 @@ 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 */
-	int delay;			/* Sleep seconds before prompt */
+	unsigned int delay;			/* Sleep seconds before prompt */
 	int nice;			/* Run login with this priority */
 	int numspeed;			/* number of baud rates to try */
 	int clocal;			/* CLOCAL_MODE_* */
@@ -145,12 +187,12 @@ 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 */
 #define F_WAITCRLF	(1<<5)	/* wait for CR or LF */
-#define F_CUSTISSUE	(1<<6)	/* give alternative issue file */
+
 #define F_NOPROMPT	(1<<7)	/* do not ask for login name! */
 #define F_LCUC		(1<<8)	/* support for *LCUC stty modes */
 #define F_KEEPSPEED	(1<<9)	/* follow baud rate from kernel */
@@ -189,16 +231,14 @@ static const struct Speedtab speedtab[] = {
 	{2400, B2400},
 	{4800, B4800},
 	{9600, B9600},
-#ifdef	B19200
+#ifdef B19200
 	{19200, B19200},
-#endif
-#ifdef	B38400
-	{38400, B38400},
-#endif
-#ifdef	EXTA
+#elif defined(EXTA)
 	{19200, EXTA},
 #endif
-#ifdef	EXTB
+#ifdef B38400
+	{38400, B38400},
+#elif defined(EXTB)
 	{38400, EXTB},
 #endif
 #ifdef B57600
@@ -209,6 +249,42 @@ static const struct Speedtab speedtab[] = {
 #endif
 #ifdef B230400
 	{230400, B230400},
+#endif
+#ifdef B460800
+	{460800, B460800},
+#endif
+#ifdef B500000
+	{500000, B500000},
+#endif
+#ifdef B576000
+	{576000, B576000},
+#endif
+#ifdef B921600
+	{921600, B921600},
+#endif
+#ifdef B1000000
+	{1000000, B1000000},
+#endif
+#ifdef B1152000
+	{1152000, B1152000},
+#endif
+#ifdef B1500000
+	{1500000, B1500000},
+#endif
+#ifdef B2000000
+	{2000000, B2000000},
+#endif
+#ifdef B2500000
+	{2500000, B2500000},
+#endif
+#ifdef B3000000
+	{3000000, B3000000},
+#endif
+#ifdef B3500000
+	{3500000, B3500000},
+#endif
+#ifdef B4000000
+	{4000000, B4000000},
 #endif
 	{0, 0},
 };
@@ -221,6 +297,7 @@ static void open_tty(char *tty, struct termios *tp, struct options *op);
 static void termio_init(struct options *op, struct termios *tp);
 static void reset_vc (const struct options *op, struct termios *tp);
 static void auto_baud(struct termios *tp);
+static void list_speeds(void);
 static void output_special_char (unsigned char c, struct options *op,
 		struct termios *tp, FILE *fp);
 static void do_prompt(struct options *op, struct termios *tp);
@@ -231,7 +308,8 @@ static void termio_final(struct options *op,
 			 struct termios *tp, struct chardata *cp);
 static int caps_lock(char *s);
 static speed_t bcode(char *s);
-static void usage(FILE * out) __attribute__((__noreturn__));
+static void usage(void) __attribute__((__noreturn__));
+static void exit_slowly(int code) __attribute__((__noreturn__));
 static void log_err(const char *, ...) __attribute__((__noreturn__))
 			       __attribute__((__format__(printf, 1, 2)));
 static void log_warn (const char *, ...)
@@ -239,13 +317,16 @@ static void log_warn (const char *, ...)
 static ssize_t append(char *dest, size_t len, const char  *sep, const char *src);
 static void check_username (const char* nm);
 static void login_options_to_argv(char *argv[], int *argc, char *str, char *username);
+static void reload_agettys(void);
+static void print_issue_file(struct options *op, struct termios *tp);
 
 /* Fake hostname for ut_host specified on command line. */
 static char *fakehost;
 
 #ifdef DEBUGGING
+# include "closestream.h"
 # ifndef DEBUG_OUTPUT
-#  define DEBUG_OUTPUT "/dev/ttyp0"
+#  define DEBUG_OUTPUT "/dev/tty10"
 # endif
 # define debug(s) do { fprintf(dbf,s); fflush(dbf); } while (0)
 FILE *dbf;
@@ -261,8 +342,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;
@@ -283,8 +363,12 @@ int main(int argc, char **argv)
 
 #ifdef DEBUGGING
 	dbf = fopen(DEBUG_OUTPUT, "w");
-	for (int i = 1; i < argc; i++)
+	for (int i = 1; i < argc; i++) {
+		if (i > 1)
+			debug(" ");
 		debug(argv[i]);
+	}
+	debug("\n");
 #endif				/* DEBUGGING */
 
 	/* Parse command-line arguments. */
@@ -311,6 +395,13 @@ int main(int argc, char **argv)
 	sigaction(SIGHUP, &sa_hup, NULL);
 
 	tcsetpgrp(STDIN_FILENO, getpid());
+
+	/* Default is to follow the current line speed and then default to 9600 */
+	if ((options.flags & F_VCONSOLE) == 0 && options.numspeed == 0) {
+		options.speeds[options.numspeed++] = bcode("9600");
+		options.flags |= F_KEEPSPEED;
+	}
+
 	/* Initialize the termios settings (raw mode, eight-bit, blocking i/o). */
 	debug("calling termio_init\n");
 	termio_init(&options, &termios);
@@ -337,7 +428,7 @@ int main(int argc, char **argv)
 
 	/* Set the optional timer. */
 	if (options.timeout)
-		alarm((unsigned) options.timeout);
+		alarm(options.timeout);
 
 	/* Optionally wait for CR or LF before writing /etc/issue */
 	if (serial_tty_option(&options, F_WAITCRLF)) {
@@ -362,7 +453,9 @@ int main(int argc, char **argv)
 		username = options.autolog;
 	}
 
-	if ((options.flags & F_NOPROMPT) == 0) {
+	if (options.flags & F_NOPROMPT) {	/* --skip-login */
+		print_issue_file(&options, &termios);
+	} else {				/* regular (auto)login */
 		if (options.autolog) {
 			/* Autologin prompt */
 			do_prompt(&options, &termios);
@@ -372,7 +465,7 @@ int main(int argc, char **argv)
 			debug("reading login name\n");
 			while ((username =
 				get_logname(&options, &termios, &chardata)) == NULL)
-				if ((options.flags & F_VCONSOLE) == 0)
+				if ((options.flags & F_VCONSOLE) == 0 && options.numspeed)
 					next_speed(&options, &termios);
 		}
 	}
@@ -403,9 +496,12 @@ int main(int argc, char **argv)
 		login_options_to_argv(login_argv, &login_argc,
 				      options.logopt, username);
 	} else {
-		if (fakehost && (options.flags & F_REMOTE)) {
-			login_argv[login_argc++] = "-h";
-			login_argv[login_argc++] = fakehost;
+		if (options.flags & F_REMOTE) {
+			if (fakehost) {
+				login_argv[login_argc++] = "-h";
+				login_argv[login_argc++] = fakehost;
+			} else if (options.flags & F_NOHOSTNAME)
+				login_argv[login_argc++] = "-H";
 		}
 		if (username) {
 			if (options.autolog)
@@ -418,25 +514,18 @@ int main(int argc, char **argv)
 
 	login_argv[login_argc] = NULL;	/* last login argv */
 
-	if (options.chroot) {
-		if (chroot(options.chroot) < 0)
-			log_err(_("%s: can't change root directory %s: %m"),
-				options.tty, options.chroot);
-	}
-	if (options.chdir) {
-		if (chdir(options.chdir) < 0)
-			log_err(_("%s: can't change working directory %s: %m"),
-				options.tty, options.chdir);
-	}
-	if (options.nice) {
-		if (nice(options.nice) < 0)
-			log_warn(_("%s: can't change process priority: %m"),
-				options.tty);
-	}
-	if (options.osrelease)
-		free(options.osrelease);
+	if (options.chroot && chroot(options.chroot) < 0)
+		log_err(_("%s: can't change root directory %s: %m"),
+			options.tty, options.chroot);
+	if (options.chdir && chdir(options.chdir) < 0)
+		log_err(_("%s: can't change working directory %s: %m"),
+			options.tty, options.chdir);
+	if (options.nice && nice(options.nice) < 0)
+		log_warn(_("%s: can't change process priority: %m"),
+			 options.tty);
+
+	free(options.osrelease);
 #ifdef DEBUGGING
-	fprintf(dbf, "read %c\n", ch);
 	if (close_stream(dbf) != 0)
 		log_err("write failed: %s", DEBUG_OUTPUT);
 #endif
@@ -477,7 +566,7 @@ static char *replace_u(char *str, char *username)
 			log_err(_("failed to allocate memory: %m"));
 
 		if (p != str) {
-			/* copy chars befor \u */
+			/* copy chars before \u */
 			memcpy(tp, str, p - str);
 			tp += p - str;
 		}
@@ -527,6 +616,55 @@ static void login_options_to_argv(char *argv[], int *argc,
 	*argc = i;
 }
 
+static void output_version(void)
+{
+	static const char *features[] = {
+#ifdef DEBUGGING
+		"debug",
+#endif
+#ifdef CRTSCTS
+		"flow control",
+#endif
+#ifdef KDGKBLED
+		"hints",
+#endif
+#ifdef ISSUE_SUPPORT
+		"issue",
+#endif
+#ifdef ISSUEDIR_SUPPORT
+		"issue.d",
+#endif
+#ifdef KDGKBMODE
+		"keyboard mode",
+#endif
+#ifdef USE_PLYMOUTH_SUPPORT
+		"plymouth",
+#endif
+#ifdef AGETTY_RELOAD
+		"reload",
+#endif
+#ifdef USE_SYSLOG
+		"syslog",
+#endif
+#ifdef HAVE_WIDECHAR
+		"widechar",
+#endif
+		NULL
+	};
+	unsigned int i;
+
+	printf( _("%s from %s"), program_invocation_short_name, PACKAGE_STRING);
+	fputs(" (", stdout);
+	for (i = 0; features[i]; i++) {
+		if (0 < i)
+			fputs(", ", stdout);
+		printf("%s", features[i]);
+	}
+	fputs(")\n", stdout);
+}
+
+#define is_speed(str) (strlen((str)) == strspn((str), "0123456789,"))
+
 /* Parse command-line arguments. */
 static void parse_args(int argc, char **argv, struct options *op)
 {
@@ -540,46 +678,50 @@ static void parse_args(int argc, char **argv, struct options *op)
 		HELP_OPTION,
 		ERASE_CHARS_OPTION,
 		KILL_CHARS_OPTION,
+		RELOAD_OPTION,
+		LIST_SPEEDS_OPTION,
 	};
 	const struct option longopts[] = {
-		{  "8bits",	     no_argument,	 0,  '8'  },
-		{  "autologin",	     required_argument,	 0,  'a'  },
-		{  "noreset",	     no_argument,	 0,  'c'  },
-		{  "chdir",	     required_argument,	 0,  'C'  },
-		{  "delay",	     required_argument,	 0,  'd'  },
-		{  "remote",         no_argument,        0,  'E'  },
-		{  "issue-file",     required_argument,  0,  'f'  },
-		{  "flow-control",   no_argument,	 0,  'h'  },
-		{  "host",	     required_argument,  0,  'H'  },
-		{  "noissue",	     no_argument,	 0,  'i'  },
-		{  "init-string",    required_argument,  0,  'I'  },
-		{  "noclear",	     no_argument,	 0,  'J'  },
-		{  "login-program",  required_argument,  0,  'l'  },
-		{  "local-line",     optional_argument,	 0,  'L'  },
-		{  "extract-baud",   no_argument,	 0,  'm'  },
-		{  "skip-login",     no_argument,	 0,  'n'  },
-		{  "nonewline",	     no_argument,	 0,  'N'  },
-		{  "login-options",  required_argument,  0,  'o'  },
-		{  "login-pause",    no_argument,        0,  'p'  },
-		{  "nice",	     required_argument,  0,  'P'  },
-		{  "chroot",	     required_argument,	 0,  'r'  },
-		{  "hangup",	     no_argument,	 0,  'R'  },
-		{  "keep-baud",      no_argument,	 0,  's'  },
-		{  "timeout",	     required_argument,  0,  't'  },
-		{  "detect-case",    no_argument,	 0,  'U'  },
-		{  "wait-cr",	     no_argument,	 0,  'w'  },
-		{  "nohints",        no_argument,        0,  NOHINTS_OPTION },
-		{  "nohostname",     no_argument,	 0,  NOHOSTNAME_OPTION },
-		{  "long-hostname",  no_argument,	 0,  LONGHOSTNAME_OPTION },
-		{  "version",	     no_argument,	 0,  VERSION_OPTION  },
-		{  "help",	     no_argument,	 0,  HELP_OPTION     },
-		{  "erase-chars",    required_argument,  0,  ERASE_CHARS_OPTION },
-		{  "kill-chars",     required_argument,  0,  KILL_CHARS_OPTION },
-		{ NULL, 0, 0, 0 }
+		{  "8bits",	     no_argument,	 NULL,  '8'  },
+		{  "autologin",	     required_argument,	 NULL,  'a'  },
+		{  "noreset",	     no_argument,	 NULL,  'c'  },
+		{  "chdir",	     required_argument,	 NULL,  'C'  },
+		{  "delay",	     required_argument,	 NULL,  'd'  },
+		{  "remote",         no_argument,        NULL,  'E'  },
+		{  "issue-file",     required_argument,  NULL,  'f'  },
+		{  "flow-control",   no_argument,	 NULL,  'h'  },
+		{  "host",	     required_argument,  NULL,  'H'  },
+		{  "noissue",	     no_argument,	 NULL,  'i'  },
+		{  "init-string",    required_argument,  NULL,  'I'  },
+		{  "noclear",	     no_argument,	 NULL,  'J'  },
+		{  "login-program",  required_argument,  NULL,  'l'  },
+		{  "local-line",     optional_argument,	 NULL,  'L'  },
+		{  "extract-baud",   no_argument,	 NULL,  'm'  },
+		{  "list-speeds",    no_argument,	 NULL,	LIST_SPEEDS_OPTION },
+		{  "skip-login",     no_argument,	 NULL,  'n'  },
+		{  "nonewline",	     no_argument,	 NULL,  'N'  },
+		{  "login-options",  required_argument,  NULL,  'o'  },
+		{  "login-pause",    no_argument,        NULL,  'p'  },
+		{  "nice",	     required_argument,  NULL,  'P'  },
+		{  "chroot",	     required_argument,	 NULL,  'r'  },
+		{  "hangup",	     no_argument,	 NULL,  'R'  },
+		{  "keep-baud",      no_argument,	 NULL,  's'  },
+		{  "timeout",	     required_argument,  NULL,  't'  },
+		{  "detect-case",    no_argument,	 NULL,  'U'  },
+		{  "wait-cr",	     no_argument,	 NULL,  'w'  },
+		{  "nohints",        no_argument,        NULL,  NOHINTS_OPTION },
+		{  "nohostname",     no_argument,	 NULL,  NOHOSTNAME_OPTION },
+		{  "long-hostname",  no_argument,	 NULL,  LONGHOSTNAME_OPTION },
+		{  "reload",         no_argument,        NULL,  RELOAD_OPTION },
+		{  "version",	     no_argument,	 NULL,  VERSION_OPTION  },
+		{  "help",	     no_argument,	 NULL,  HELP_OPTION     },
+		{  "erase-chars",    required_argument,  NULL,  ERASE_CHARS_OPTION },
+		{  "kill-chars",     required_argument,  NULL,  KILL_CHARS_OPTION },
+		{ NULL, 0, NULL, 0 }
 	};
 
 	while ((c = getopt_long(argc, argv,
-			   "8a:cC:d:Ef:hH:iI:Jl:LmnNo:pP:r:Rst:Uw", longopts,
+			   "8a:cC:d:Ef:hH:iI:Jl:L::mnNo:pP:r:Rst:Uw", longopts,
 			    NULL)) != -1) {
 		switch (c) {
 		case '8':
@@ -595,13 +737,12 @@ static void parse_args(int argc, char **argv, struct options *op)
 			op->chdir = optarg;
 			break;
 		case 'd':
-			op->delay = atoi(optarg);
+			op->delay = strtou32_or_err(optarg,  _("invalid delay argument"));
 			break;
 		case 'E':
 			op->flags |= F_REMOTE;
 			break;
 		case 'f':
-			op->flags |= F_CUSTISSUE;
 			op->issue = optarg;
 			break;
 		case 'h':
@@ -634,7 +775,7 @@ static void parse_args(int argc, char **argv, struct options *op)
 				else if (strcmp(optarg, "=auto") == 0)
 					op->clocal = CLOCAL_MODE_AUTO;
 				else
-					log_err(_("unssuported --local-line mode argument"));
+					log_err(_("invalid argument of --local-line"));
 			}
 			break;
 		case 'm':
@@ -643,6 +784,9 @@ static void parse_args(int argc, char **argv, struct options *op)
 		case 'n':
 			op->flags |= F_NOPROMPT;
 			break;
+		case 'N':
+			op->flags |= F_NONL;
+			break;
 		case 'o':
 			op->logopt = optarg;
 			break;
@@ -650,7 +794,7 @@ static void parse_args(int argc, char **argv, struct options *op)
 			op->flags |= F_LOGINPAUSE;
 			break;
 		case 'P':
-			op->nice = atoi(optarg);
+			op->nice = strtos32_or_err(optarg,  _("invalid nice argument"));
 			break;
 		case 'r':
 			op->chroot = optarg;
@@ -662,8 +806,7 @@ static void parse_args(int argc, char **argv, struct options *op)
 			op->flags |= F_KEEPSPEED;
 			break;
 		case 't':
-			if ((op->timeout = atoi(optarg)) <= 0)
-				log_err(_("bad timeout value: %s"), optarg);
+			op->timeout = strtou32_or_err(optarg,  _("invalid timeout argument"));
 			break;
 		case 'U':
 			op->flags |= F_LCUC;
@@ -686,14 +829,19 @@ static void parse_args(int argc, char **argv, struct options *op)
 		case KILL_CHARS_OPTION:
 			op->killchars = optarg;
 			break;
+		case RELOAD_OPTION:
+			reload_agettys();
+			exit(EXIT_SUCCESS);
+		case LIST_SPEEDS_OPTION:
+			list_speeds();
+			exit(EXIT_SUCCESS);
 		case VERSION_OPTION:
-			printf(_("%s from %s\n"), program_invocation_short_name,
-			       PACKAGE_STRING);
+			output_version();
 			exit(EXIT_SUCCESS);
 		case HELP_OPTION:
-			usage(stdout);
+			usage();
 		default:
-			usage(stderr);
+			errtryhelp(EXIT_FAILURE);
 		}
 	}
 
@@ -701,26 +849,26 @@ static void parse_args(int argc, char **argv, struct options *op)
 
 	if (argc < optind + 1) {
 		log_warn(_("not enough arguments"));
-		usage(stderr);
+		errx(EXIT_FAILURE, _("not enough arguments"));
 	}
 
 	/* Accept "tty", "baudrate tty", and "tty baudrate". */
-	if ('0' <= argv[optind][0] && argv[optind][0] <= '9') {
+	if (is_speed(argv[optind])) {
 		/* Assume BSD style speed. */
 		parse_speeds(op, argv[optind++]);
 		if (argc < optind + 1) {
-			warn(_("not enough arguments"));
-			usage(stderr);
+			log_warn(_("not enough arguments"));
+			errx(EXIT_FAILURE, _("not enough arguments"));
 		}
 		op->tty = argv[optind++];
 	} else {
 		op->tty = argv[optind++];
 		if (argc > optind) {
-			char *v = argv[optind++];
-			if ('0' <= *v && *v <= '9')
+			char *v = argv[optind];
+			if (is_speed(v)) {
 				parse_speeds(op, v);
-			else
-				op->speeds[op->numspeed++] = bcode("9600");
+				optind++;
+			}
 		}
 	}
 
@@ -732,45 +880,6 @@ static void parse_args(int argc, char **argv, struct options *op)
 	if (argc > optind && argv[optind])
 		op->term = argv[optind];
 
-#ifdef DO_DEVFS_FIDDLING
-	/*
-	 * Some devfs junk, following Goswin Brederlow:
-	 *   turn ttyS<n> into tts/<n>
-	 *   turn tty<n> into vc/<n>
-	 * http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=72241
-	 */
-	if (op->tty && strlen(op->tty) < 90) {
-		char dev_name[100];
-		struct stat st;
-
-		if (strncmp(op->tty, "ttyS", 4) == 0) {
-			strcpy(dev_name, "/dev/");
-			strcat(dev_name, op->tty);
-			if (stat(dev_name, &st) < 0) {
-				strcpy(dev_name, "/dev/tts/");
-				strcat(dev_name, op->tty + 4);
-				if (stat(dev_name, &st) == 0) {
-					op->tty = strdup(dev_name + 5);
-					if (!op->tty)
-						log_err(_("failed to allocate memory: %m"));
-				}
-			}
-		} else if (strncmp(op->tty, "tty", 3) == 0) {
-			strcpy(dev_name, "/dev/");
-			strncat(dev_name, op->tty, 90);
-			if (stat(dev_name, &st) < 0) {
-				strcpy(dev_name, "/dev/vc/");
-				strcat(dev_name, op->tty + 3);
-				if (stat(dev_name, &st) == 0) {
-					op->tty = strdup(dev_name + 5);
-					if (!op->tty)
-						log_err(_("failed to allocate memory: %m"));
-				}
-			}
-		}
-	}
-#endif				/* DO_DEVFS_FIDDLING */
-
 	debug("exiting parseargs\n");
 }
 
@@ -778,15 +887,20 @@ static void parse_args(int argc, char **argv, struct options *op)
 static void parse_speeds(struct options *op, char *arg)
 {
 	char *cp;
+	char *str = strdup(arg);
+
+	if (!str)
+		log_err(_("failed to allocate memory: %m"));
 
-	debug("entered parse_speeds\n");
-	for (cp = strtok(arg, ","); cp != NULL; cp = strtok((char *)0, ",")) {
+	debug("entered parse_speeds:\n");
+	for (cp = strtok(str, ","); cp != NULL; cp = strtok((char *)0, ",")) {
 		if ((op->speeds[op->numspeed++] = bcode(cp)) <= 0)
 			log_err(_("bad speed: %s"), cp);
 		if (op->numspeed >= MAX_SPEED)
 			log_err(_("too many alternate speeds"));
 	}
 	debug("exiting parsespeeds\n");
+	free(str);
 }
 
 #ifdef	SYSV_STYLE
@@ -794,13 +908,13 @@ static void parse_speeds(struct options *op, char *arg)
 /* Update our utmp entry. */
 static void update_utmp(struct options *op)
 {
-	struct utmp ut;
+	struct utmpx ut;
 	time_t t;
 	pid_t pid = getpid();
 	pid_t sid = getsid(0);
 	char *vcline = op->vcline;
 	char *line   = op->tty;
-	struct utmp *utp;
+	struct utmpx *utp;
 
 	/*
 	 * The utmp file holds miscellaneous information about things started by
@@ -810,8 +924,8 @@ static void update_utmp(struct options *op)
 	 * utmp file can be opened for update, and if we are able to find our
 	 * entry in the utmp file.
 	 */
-	utmpname(_PATH_UTMP);
-	setutent();
+	utmpxname(_PATH_UTMP);
+	setutxent();
 
 	/*
 	 * Find my pid in utmp.
@@ -822,7 +936,7 @@ static void update_utmp(struct options *op)
 	 * FIXME: The present code is taken from login.c, so if this is changed,
 	 * maybe login has to be changed as well (is this true?).
 	 */
-	while ((utp = getutent()))
+	while ((utp = getutxent()))
 		if (utp->ut_pid == pid
 				&& utp->ut_type >= INIT_PROCESS
 				&& utp->ut_type <= DEAD_PROCESS)
@@ -852,33 +966,15 @@ static void update_utmp(struct options *op)
 	if (fakehost)
 		strncpy(ut.ut_host, fakehost, sizeof(ut.ut_host));
 	time(&t);
-	ut.ut_time = t;
+	ut.ut_tv.tv_sec = t;
 	ut.ut_type = LOGIN_PROCESS;
 	ut.ut_pid = pid;
 	ut.ut_session = sid;
 
-	pututline(&ut);
-	endutent();
+	pututxline(&ut);
+	endutxent();
 
-	{
-#ifdef HAVE_UPDWTMP
-		updwtmp(_PATH_WTMP, &ut);
-#else
-		int ut_fd;
-		int lf;
-
-		if ((lf = open(_PATH_WTMPLOCK, O_CREAT | O_WRONLY, 0660)) >= 0) {
-			flock(lf, LOCK_EX);
-			if ((ut_fd =
-			     open(_PATH_WTMP, O_APPEND | O_WRONLY)) >= 0) {
-				write_all(ut_fd, &ut, sizeof(ut));
-				close(ut_fd);
-			}
-			flock(lf, LOCK_UN);
-			close(lf);
-		}
-#endif				/* HAVE_UPDWTMP */
-	}
+	updwtmpx(_PATH_WTMP, &ut);
 }
 
 #endif				/* SYSV_STYLE */
@@ -888,6 +984,9 @@ static void open_tty(char *tty, struct termios *tp, struct options *op)
 {
 	const pid_t pid = getpid();
 	int closed = 0;
+#ifndef KDGKBMODE
+	int serial;
+#endif
 
 	/* Set up new standard input, unless we are given an already opened port. */
 
@@ -903,8 +1002,12 @@ static void open_tty(char *tty, struct termios *tp, struct options *op)
 		if ((gr = getgrnam("tty")))
 			gid = gr->gr_gid;
 
-		if (((len = snprintf(buf, sizeof(buf), "/dev/%s", tty)) >=
-		     (int)sizeof(buf)) || (len < 0))
+		len = snprintf(buf, sizeof(buf), "/dev/%s", tty);
+		if (len < 0 || (size_t)len >= sizeof(buf))
+			log_err(_("/dev/%s: cannot open as standard input: %m"), tty);
+
+		/* Open the tty as standard input. */
+		if ((fd = open(buf, O_RDWR|O_NOCTTY|O_NONBLOCK, 0)) < 0)
 			log_err(_("/dev/%s: cannot open as standard input: %m"), tty);
 
 		/*
@@ -913,24 +1016,20 @@ static void open_tty(char *tty, struct termios *tp, struct options *op)
 		 * Linux login(1) will change tty permissions. Use root owner and group
 		 * with permission -rw------- for the period between getty and login.
 		 */
-		if (chown(buf, 0, gid) || chmod(buf, (gid ? 0620 : 0600))) {
+		if (fchown(fd, 0, gid) || fchmod(fd, (gid ? 0620 : 0600))) {
 			if (errno == EROFS)
 				log_warn("%s: %m", buf);
 			else
 				log_err("%s: %m", buf);
 		}
 
-		/* Open the tty as standard input. */
-		if ((fd = open(buf, O_RDWR|O_NOCTTY|O_NONBLOCK, 0)) < 0)
-			log_err(_("/dev/%s: cannot open as standard input: %m"), tty);
-
 		/* Sanity checks... */
-		if (!isatty(fd))
-			log_err(_("/dev/%s: not a character device"), tty);
 		if (fstat(fd, &st) < 0)
 			log_err("%s: %m", buf);
 		if ((st.st_mode & S_IFMT) != S_IFCHR)
 			log_err(_("/dev/%s: not a character device"), tty);
+		if (!isatty(fd))
+			log_err(_("/dev/%s: not a tty"), tty);
 
 		if (((tid = tcgetsid(fd)) < 0) || (pid != tid)) {
 			if (ioctl(fd, TIOCSCTTY, 1) == -1)
@@ -946,7 +1045,7 @@ static void open_tty(char *tty, struct termios *tp, struct options *op)
 				debug("TIOCNOTTY ioctl failed\n");
 
 			/*
-			 * Let's close all file decriptors before vhangup
+			 * Let's close all file descriptors before vhangup
 			 * https://lkml.org/lkml/2012/6/5/145
 			 */
 			close(fd);
@@ -1015,22 +1114,66 @@ static void open_tty(char *tty, struct termios *tp, struct options *op)
 	if (tcgetattr(STDIN_FILENO, tp) < 0)
 		log_err(_("%s: failed to get terminal attributes: %m"), tty);
 
+#if defined (__s390__) || defined (__s390x__)
+	if (!op->term) {
+	        /*
+		 * Special terminal on first serial line on a S/390(x) which
+		 * is due legacy reasons a block terminal of type 3270 or
+		 * higher.  Whereas the second serial line on a S/390(x) is
+		 * a real character terminal which is compatible with VT220.
+		 */
+		if (strcmp(op->tty, "ttyS0") == 0)		/* linux/drivers/s390/char/con3215.c */
+			op->term = DEFAULT_TTYS0;
+		else if (strncmp(op->tty, "3270/tty", 8) == 0)	/* linux/drivers/s390/char/con3270.c */
+			op->term = DEFAULT_TTY32;
+		else if (strcmp(op->tty, "ttyS1") == 0)		/* linux/drivers/s390/char/sclp_vt220.c */
+			op->term = DEFAULT_TTYS1;
+	}
+#endif
+
+#if defined(__FreeBSD_kernel__)
+	login_tty (0);
+#endif
+
 	/*
 	 * Detect if this is a virtual console or serial/modem line.
 	 * In case of a virtual console the ioctl KDGKBMODE succeeds
 	 * whereas on other lines it will fails.
 	 */
-	if (ioctl(STDIN_FILENO, KDGKBMODE, &op->kbmode) == 0) {
+#ifdef KDGKBMODE
+	if (ioctl(STDIN_FILENO, KDGKBMODE, &op->kbmode) == 0)
+#else
+	if (ioctl(STDIN_FILENO, TIOCMGET, &serial) < 0 && (errno == EINVAL))
+#endif
+	{
 		op->flags |= F_VCONSOLE;
 		if (!op->term)
 			op->term = DEFAULT_VCTERM;
 	} else {
+#ifdef K_RAW
 		op->kbmode = K_RAW;
+#endif
 		if (!op->term)
 			op->term = DEFAULT_STERM;
 	}
 
-	setenv("TERM", op->term, 1);
+	if (setenv("TERM", op->term, 1) != 0)
+		log_err(_("failed to set the %s environment variable"), "TERM");
+}
+
+/* Initialize termios settings. */
+static void termio_clear(int fd)
+{
+	/*
+	 * Do not write a full reset (ESC c) because this destroys
+	 * the unicode mode again if the terminal was in unicode
+	 * mode.  Also it clears the CONSOLE_MAGIC features which
+	 * are required for some languages/console-fonts.
+	 * Just put the cursor to the home position (ESC [ H),
+	 * erase everything below the cursor (ESC [ J), and set the
+	 * scrolling region to the full window (ESC [ r)
+	 */
+	write_all(fd, "\033[r\033[H\033[J", 9);
 }
 
 /* Initialize termios settings. */
@@ -1038,6 +1181,28 @@ static void termio_init(struct options *op, struct termios *tp)
 {
 	speed_t ispeed, ospeed;
 	struct winsize ws;
+#ifdef USE_PLYMOUTH_SUPPORT
+	struct termios lock;
+	int i =  (plymouth_command(MAGIC_PING) == 0) ? PLYMOUTH_TERMIOS_FLAGS_DELAY : 0;
+	if (i)
+		plymouth_command(MAGIC_QUIT);
+	while (i-- > 0) {
+		/*
+		 * Even with TTYReset=no it seems with systemd or plymouth
+		 * the termios flags become changed from under the first
+		 * agetty on a serial system console as the flags are locked.
+		 */
+		memset(&lock, 0, sizeof(struct termios));
+		if (ioctl(STDIN_FILENO, TIOCGLCKTRMIOS, &lock) < 0)
+			break;
+		if (!lock.c_iflag && !lock.c_oflag && !lock.c_cflag && !lock.c_lflag)
+			break;
+		debug("termios locked\n");
+		sleep(1);
+	}
+	memset(&lock, 0, sizeof(struct termios));
+	ioctl(STDIN_FILENO, TIOCSLCKTRMIOS, &lock);
+#endif
 
 	if (op->flags & F_VCONSOLE) {
 #if defined(IUTF8) && defined(KDGKBMODE)
@@ -1063,22 +1228,16 @@ static void termio_init(struct options *op, struct termios *tp)
 		if ((tp->c_cflag & (CS8|PARODD|PARENB)) == CS8)
 			op->flags |= F_EIGHTBITS;
 
-		if ((op->flags & F_NOCLEAR) == 0) {
-			/*
-			 * Do not write a full reset (ESC c) because this destroys
-			 * the unicode mode again if the terminal was in unicode
-			 * mode.  Also it clears the CONSOLE_MAGIC features which
-			 * are required for some languages/console-fonts.
-			 * Just put the cursor to the home position (ESC [ H),
-			 * erase everything below the cursor (ESC [ J), and set the
-			 * scrolling region to the full window (ESC [ r)
-			 */
-			write_all(STDOUT_FILENO, "\033[r\033[H\033[J", 9);
-		}
+		if ((op->flags & F_NOCLEAR) == 0)
+			termio_clear(STDOUT_FILENO);
 		return;
 	}
 
-	if (op->flags & F_KEEPSPEED) {
+	/*
+	 * Serial line
+	 */
+
+	if (op->flags & F_KEEPSPEED || !op->numspeed) {
 		/* Save the original setting. */
 		ispeed = cfgetispeed(tp);
 		ospeed = cfgetospeed(tp);
@@ -1097,9 +1256,6 @@ static void termio_init(struct options *op, struct termios *tp)
 	 * later on.
 	 */
 
-	 /* Flush input and output queues, important for modems! */
-	tcflush(STDIN_FILENO, TCIOFLUSH);
-
 	/* The defaul is set c_iflag in termio_final() according to chardata.
 	 * Unfortunately, the chardata are not set according to the serial line
 	 * if --autolog is enabled. In this case we do not read from the line
@@ -1124,7 +1280,7 @@ static void termio_init(struct options *op, struct termios *tp)
 
 	/*
 	 * Note that the speed is stored in the c_cflag termios field, so we have
-	 * set the speed always when the cflag se reseted.
+	 * set the speed always when the cflag is reset.
 	 */
 	cfsetispeed(tp, ispeed);
 	cfsetospeed(tp, ospeed);
@@ -1150,15 +1306,10 @@ static void termio_init(struct options *op, struct termios *tp)
 
 	/* Check for terminal size and if not found set default */
 	if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == 0) {
-		int set = 0;
-		if (ws.ws_row == 0) {
+		if (ws.ws_row == 0)
 			ws.ws_row = 24;
-			set++;
-		}
-		if (ws.ws_col == 0) {
+		if (ws.ws_col == 0)
 			ws.ws_col = 80;
-			set++;
-		}
 		if (ioctl(STDIN_FILENO, TIOCSWINSZ, &ws))
 			debug("TIOCSWINSZ ioctl failed\n");
 	}
@@ -1168,8 +1319,11 @@ static void termio_init(struct options *op, struct termios *tp)
 	if (op->flags & F_RTSCTS)
 		tp->c_cflag |= CRTSCTS;
 #endif
+	 /* Flush input and output queues, important for modems! */
+	tcflush(STDIN_FILENO, TCIOFLUSH);
 
-	tcsetattr(STDIN_FILENO, TCSANOW, tp);
+	if (tcsetattr(STDIN_FILENO, TCSANOW, tp))
+		log_warn(_("setting terminal attributes failed: %m"));
 
 	/* Go to blocking input even in local mode. */
 	fcntl(STDIN_FILENO, F_SETFL,
@@ -1190,6 +1344,10 @@ static void reset_vc(const struct options *op, struct termios *tp)
 
 	if (tcsetattr(STDIN_FILENO, TCSADRAIN, tp))
 		log_warn(_("setting terminal attributes failed: %m"));
+
+	/* Go to blocking input even in local mode. */
+	fcntl(STDIN_FILENO, F_SETFL,
+	      fcntl(STDIN_FILENO, F_GETFL, 0) & ~O_NONBLOCK);
 }
 
 /* Extract baud rate from modem status message. */
@@ -1273,7 +1431,7 @@ static char *xgetdomainname(void)
 {
 #ifdef HAVE_GETDOMAINNAME
 	char *name;
-	size_t sz = get_hostname_max() + 1;
+	const size_t sz = get_hostname_max() + 1;
 
 	name = malloc(sizeof(char) * sz);
 	if (!name)
@@ -1285,8 +1443,9 @@ static char *xgetdomainname(void)
 	}
 	name[sz - 1] = '\0';
 	return name;
-#endif
+#else
 	return NULL;
+#endif
 }
 
 
@@ -1299,10 +1458,13 @@ static char *read_os_release(struct options *op, const char *varname)
 
 	/* read the file only once */
 	if (!op->osrelease) {
-		fd = open(_PATH_OS_RELEASE, O_RDONLY);
+		fd = open(_PATH_OS_RELEASE_ETC, O_RDONLY);
 		if (fd == -1) {
-			log_warn(_("cannot open: %s: %m"), _PATH_OS_RELEASE);
-			return NULL;
+			fd = open(_PATH_OS_RELEASE_USR, O_RDONLY);
+			if (fd == -1) {
+				log_warn(_("cannot open os-release file"));
+				return NULL;
+			}
 		}
 
 		if (fstat(fd, &st) < 0 || st.st_size > 4 * 1024 * 1024)
@@ -1359,8 +1521,7 @@ static char *read_os_release(struct options *op, const char *varname)
 			}
 			break;
 		}
-		if (ret)
-			free(ret);
+		free(ret);
 		ret = strdup(p);
 		if (!ret)
 			log_err(_("failed to allocate memory: %m"));
@@ -1373,20 +1534,251 @@ done:
 	return ret;
 }
 
-/* Show login prompt, optionally preceded by /etc/issue contents. */
-static void do_prompt(struct options *op, struct termios *tp)
+#ifdef AGETTY_RELOAD
+static void open_netlink(void)
 {
-#ifdef	ISSUE
-	FILE *fd;
-#endif				/* ISSUE */
+	struct sockaddr_nl addr = { 0, };
+	int sock;
 
+	if (netlink_fd != AGETTY_RELOAD_FDNONE)
+		return;
+
+	sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+	if (sock >= 0) {
+		addr.nl_family = AF_NETLINK;
+		addr.nl_pid = getpid();
+		addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;
+		if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
+			close(sock);
+		else
+			netlink_fd = sock;
+	}
+}
+
+static int process_netlink_msg(int *changed)
+{
+	char buf[4096];
+	struct sockaddr_nl snl;
+	struct nlmsghdr *h;
+	int rc;
+
+	struct iovec iov = {
+		.iov_base = buf,
+		.iov_len = sizeof(buf)
+	};
+	struct msghdr msg = {
+		.msg_name = &snl,
+		.msg_namelen = sizeof(snl),
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+		.msg_control = NULL,
+		.msg_controllen = 0,
+		.msg_flags = 0
+	};
+
+	rc = recvmsg(netlink_fd, &msg, MSG_DONTWAIT);
+	if (rc < 0) {
+		if (errno == EWOULDBLOCK || errno == EAGAIN)
+			return 0;
+
+		/* Failure, just stop listening for changes */
+		close(netlink_fd);
+		netlink_fd = AGETTY_RELOAD_FDNONE;
+		return 0;
+	}
+
+	for (h = (struct nlmsghdr *)buf; NLMSG_OK(h, (unsigned int)rc); h = NLMSG_NEXT(h, rc)) {
+		if (h->nlmsg_type == NLMSG_DONE ||
+		    h->nlmsg_type == NLMSG_ERROR) {
+			close(netlink_fd);
+			netlink_fd = AGETTY_RELOAD_FDNONE;
+			return 0;
+		}
+
+		*changed = 1;
+		break;
+	}
+
+	return 1;
+}
+
+static int process_netlink(void)
+{
+	int changed = 0;
+	while (process_netlink_msg(&changed));
+	return changed;
+}
+
+static int wait_for_term_input(int fd)
+{
+	char buffer[sizeof(struct inotify_event) + NAME_MAX + 1];
+	fd_set rfds;
+
+	if (inotify_fd == AGETTY_RELOAD_FDNONE) {
+		/* make sure the reload trigger file exists */
+		int reload_fd = open(AGETTY_RELOAD_FILENAME,
+					O_CREAT|O_CLOEXEC|O_RDONLY,
+					S_IRUSR|S_IWUSR);
+
+		/* initialize reload trigger inotify stuff */
+		if (reload_fd >= 0) {
+			inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
+			if (inotify_fd > 0)
+				inotify_add_watch(inotify_fd, AGETTY_RELOAD_FILENAME,
+					  IN_ATTRIB | IN_MODIFY);
+
+			close(reload_fd);
+		} else
+			log_warn(_("failed to create reload file: %s: %m"),
+					AGETTY_RELOAD_FILENAME);
+	}
+
+	while (1) {
+		int nfds = fd;
+
+		FD_ZERO(&rfds);
+		FD_SET(fd, &rfds);
+
+		if (inotify_fd >= 0) {
+			FD_SET(inotify_fd, &rfds);
+			nfds = max(nfds, inotify_fd);
+		}
+		if (netlink_fd >= 0) {
+			FD_SET(netlink_fd, &rfds);
+			nfds = max(nfds, netlink_fd);
+		}
+
+		/* If waiting fails, just fall through, presumably reading input will fail */
+		if (select(nfds + 1, &rfds, NULL, NULL, NULL) < 0)
+			return 1;
+
+		if (FD_ISSET(fd, &rfds)) {
+			return 1;
+
+		} else if (netlink_fd >= 0 && FD_ISSET(netlink_fd, &rfds)) {
+			if (!process_netlink())
+				continue;
+
+		/* Just drain the inotify buffer */
+		} else if (inotify_fd >= 0 && FD_ISSET(inotify_fd, &rfds)) {
+			while (read(inotify_fd, buffer, sizeof (buffer)) > 0);
+		}
+
+		return 0;
+	}
+}
+#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)
+{
+	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) {
@@ -1395,12 +1787,23 @@ static void do_prompt(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) {
@@ -1409,11 +1812,36 @@ static void do_prompt(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)
+{
+#ifdef AGETTY_RELOAD
+again:
+#endif
+	print_issue_file(op, tp);
+
 	if (op->flags & F_LOGINPAUSE) {
 		puts(_("[press ENTER to login]"));
+#ifdef AGETTY_RELOAD
+		if (!wait_for_term_input(STDIN_FILENO)) {
+			/* reload issue */
+			if (op->flags & F_VCONSOLE)
+				termio_clear(STDOUT_FILENO);
+			goto again;
+		}
+#endif
 		getc(stdin);
 	}
 #ifdef KDGKBLED
@@ -1533,18 +1961,36 @@ static char *get_logname(struct options *op, struct termios *tp, struct chardata
 	*bp = '\0';
 
 	while (*logname == '\0') {
-
 		/* Write issue file and prompt */
 		do_prompt(op, tp);
 
+#ifdef AGETTY_RELOAD
+		if (!wait_for_term_input(STDIN_FILENO)) {
+			/* refresh prompt -- discard input data, clear terminal
+			 * and call do_prompt() again
+			 */
+			if ((op->flags & F_VCONSOLE) == 0)
+				sleep(1);
+			tcflush(STDIN_FILENO, TCIFLUSH);
+			if (op->flags & F_VCONSOLE)
+				termio_clear(STDOUT_FILENO);
+			bp = logname;
+			*bp = '\0';
+			continue;
+		}
+#endif
 		cp->eol = '\0';
 
 		/* Read name, watch for break and end-of-line. */
 		while (cp->eol == '\0') {
 
 			char key;
+			ssize_t readres;
 
-			if (read(STDIN_FILENO, &c, 1) < 1) {
+			debug("read from FD\n");
+			readres = read(STDIN_FILENO, &c, 1);
+			if (readres < 0) {
+				debug("read failed\n");
 
 				/* The terminal could be open with O_NONBLOCK when
 				 * -L (force CLOCAL) is specified...  */
@@ -1558,12 +2004,15 @@ static char *get_logname(struct options *op, struct termios *tp, struct chardata
 				case ESRCH:
 				case EINVAL:
 				case ENOENT:
-					break;
+					exit_slowly(EXIT_SUCCESS);
 				default:
 					log_err(_("%s: read: %m"), op->tty);
 				}
 			}
 
+			if (readres == 0)
+				c = 0;
+
 			/* Do parity bit handling. */
 			if (eightbit)
 				ascval = c;
@@ -1588,8 +2037,10 @@ static char *get_logname(struct options *op, struct termios *tp, struct chardata
 			switch (key) {
 			case 0:
 				*bp = 0;
-				if (op->numspeed > 1)
+				if (op->numspeed > 1 && !(op->flags & F_VCONSOLE))
 					return NULL;
+				if (readres == 0)
+					exit_slowly(EXIT_SUCCESS);
 				break;
 			case CR:
 			case NL:
@@ -1627,6 +2078,7 @@ static char *get_logname(struct options *op, struct termios *tp, struct chardata
 			}
 		}
 	}
+
 #ifdef HAVE_WIDECHAR
 	if ((op->flags & (F_EIGHTBITS|F_UTF8)) == (F_EIGHTBITS|F_UTF8)) {
 		/* Check out UTF-8 multibyte characters */
@@ -1637,7 +2089,7 @@ static char *get_logname(struct options *op, struct termios *tp, struct chardata
 		if (len < 0)
 			log_err(_("%s: invalid character conversion for login name"), op->tty);
 
-		wcs = (wchar_t *) malloc((len + 1) * sizeof(wchar_t));
+		wcs = malloc((len + 1) * sizeof(wchar_t));
 		if (!wcs)
 			log_err(_("failed to allocate memory: %m"));
 
@@ -1702,12 +2154,12 @@ static void termio_final(struct options *op, struct termios *tp, struct chardata
 	case 1:
 		/* odd parity */
 		tp->c_cflag |= PARODD;
-		/* do not break */
+		/* fallthrough */
 	case 2:
 		/* even parity */
 		tp->c_cflag |= PARENB;
 		tp->c_iflag |= INPCK | ISTRIP;
-		/* do not break */
+		/* fallthrough */
 	case (1 | 2):
 		/* no parity bit */
 		tp->c_cflag &= ~CSIZE;
@@ -1767,11 +2219,17 @@ static speed_t bcode(char *s)
 	return 0;
 }
 
-static void __attribute__ ((__noreturn__)) usage(FILE *out)
+static void __attribute__((__noreturn__)) usage(void)
 {
+	FILE *out = stdout;
+
 	fputs(USAGE_HEADER, out);
-	fprintf(out, _(" %1$s [options] line [baud_rate,...] [termtype]\n"
-		       " %1$s [options] baud_rate,... line [termtype]\n"), program_invocation_short_name);
+	fprintf(out, _(" %1$s [options] <line> [<baud_rate>,...] [<termtype>]\n"
+		       " %1$s [options] <baud_rate>,... <line> [<termtype>]\n"), program_invocation_short_name);
+
+	fputs(USAGE_SEPARATOR, out);
+	fputs(_("Open a terminal and set its mode.\n"), out);
+
 	fputs(USAGE_OPTIONS, out);
 	fputs(_(" -8, --8bits                assume 8-bit tty\n"), out);
 	fputs(_(" -a, --autologin <user>     login the specified user automatically\n"), out);
@@ -1782,10 +2240,12 @@ static void __attribute__ ((__noreturn__)) usage(FILE *out)
 	fputs(_(" -H, --host <hostname>      specify login host\n"), out);
 	fputs(_(" -i, --noissue              do not display issue file\n"), out);
 	fputs(_(" -I, --init-string <string> set init string\n"), out);
+	fputs(_(" -J  --noclear              do not clear the screen before prompt\n"), out);
 	fputs(_(" -l, --login-program <file> specify login program\n"), out);
-	fputs(_(" -L, --local-line[=<mode>]  cotrol local line flag\n"), out);
+	fputs(_(" -L, --local-line[=<mode>]  control the local line flag\n"), out);
 	fputs(_(" -m, --extract-baud         extract baud rate during connect\n"), out);
 	fputs(_(" -n, --skip-login           do not prompt for login\n"), out);
+	fputs(_(" -N  --nonewline            do not print a newline before issue\n"), out);
 	fputs(_(" -o, --login-options <opts> options that are passed to login\n"), out);
 	fputs(_(" -p, --login-pause          wait for any key before the login\n"), out);
 	fputs(_(" -r, --chroot <dir>         change root to the directory\n"), out);
@@ -1794,18 +2254,29 @@ static void __attribute__ ((__noreturn__)) usage(FILE *out)
 	fputs(_(" -t, --timeout <number>     login process timeout\n"), out);
 	fputs(_(" -U, --detect-case          detect uppercase terminal\n"), out);
 	fputs(_(" -w, --wait-cr              wait carriage-return\n"), out);
-	fputs(_("     --noclear              do not clear the screen before prompt\n"), out);
 	fputs(_("     --nohints              do not print hints\n"), out);
-	fputs(_("     --nonewline            do not print a newline before issue\n"), out);
 	fputs(_("     --nohostname           no hostname at all will be shown\n"), out);
 	fputs(_("     --long-hostname        show full qualified hostname\n"), out);
 	fputs(_("     --erase-chars <string> additional backspace chars\n"), out);
 	fputs(_("     --kill-chars <string>  additional kill chars\n"), out);
-	fputs(_("     --help                 display this help and exit\n"), out);
-	fputs(_("     --version              output version information and exit\n"), out);
-	fprintf(out, USAGE_MAN_TAIL("agetty(8)"));
+	fputs(_("     --chdir <directory>    chdir before the login\n"), out);
+	fputs(_("     --delay <number>       sleep seconds before prompt\n"), out);
+	fputs(_("     --nice <number>        run login with this priority\n"), out);
+	fputs(_("     --reload               reload prompts on running agetty instances\n"), out);
+	fputs(_("     --list-speeds          display supported baud rates\n"), out);
+	printf( "     --help                 %s\n", USAGE_OPTSTR_HELP);
+	printf( "     --version              %s\n", USAGE_OPTSTR_VERSION);
+	printf(USAGE_MAN_TAIL("agetty(8)"));
+
+	exit(EXIT_SUCCESS);
+}
+
+static void list_speeds(void)
+{
+	const struct Speedtab *sp;
 
-	exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+	for (sp = speedtab; sp->speed; sp++)
+		printf("%10ld\n", sp->speed);
 }
 
 /*
@@ -1855,6 +2326,13 @@ static void dolog(int priority, const char *fmt, va_list ap)
 #endif				/* USE_SYSLOG */
 }
 
+static void exit_slowly(int code)
+{
+	/* Be kind to init(8). */
+	sleep(10);
+	exit(code);
+}
+
 static void log_err(const char *fmt, ...)
 {
 	va_list ap;
@@ -1863,9 +2341,7 @@ static void log_err(const char *fmt, ...)
 	dolog(LOG_ERR, fmt, ap);
 	va_end(ap);
 
-	/* Be kind to init(8). */
-	sleep(10);
-	exit(EXIT_FAILURE);
+	exit_slowly(EXIT_FAILURE);
 }
 
 static void log_warn(const char *fmt, ...)
@@ -1877,41 +2353,70 @@ static void log_warn(const char *fmt, ...)
 	va_end(ap);
 }
 
-static void output_iface_ip(struct ifaddrs *addrs, const char *iface, sa_family_t family)
+static void print_addr(sa_family_t family, void *addr)
+{
+	char buff[INET6_ADDRSTRLEN + 1];
+
+	inet_ntop(family, addr, buff, sizeof(buff));
+	printf("%s", buff);
+}
+
+/*
+ * Prints IP for the specified interface (@iface), if the interface is not
+ * specified then prints the "best" one (UP, RUNNING, non-LOOPBACK). If not
+ * found the "best" interface then prints at least host IP.
+ */
+static void output_iface_ip(struct ifaddrs *addrs,
+			    const char *iface,
+			    sa_family_t family)
 {
-	if (!iface)
+	struct ifaddrs *p;
+	struct addrinfo hints, *info = NULL;
+	char *host = NULL;
+	void *addr = NULL;
+
+	if (!addrs)
 		return;
 
-	if (addrs->ifa_name
-	    && strcmp(addrs->ifa_name, iface) == 0
-	    && addrs->ifa_addr
-	    && addrs->ifa_addr->sa_family == family) {
+	for (p = addrs; p; p = p->ifa_next) {
 
-		void *addr = NULL;
-		char buff[INET6_ADDRSTRLEN + 1];
+		if (!p->ifa_name ||
+		    !p->ifa_addr ||
+		    p->ifa_addr->sa_family != family)
+			continue;
+
+		if (iface) {
+			/* Filter out by interface name */
+		       if (strcmp(p->ifa_name, iface) != 0)
+				continue;
+		} else {
+			/* Select the "best" interface */
+			if ((p->ifa_flags & IFF_LOOPBACK) ||
+			    !(p->ifa_flags & IFF_UP) ||
+			    !(p->ifa_flags & IFF_RUNNING))
+				continue;
+		}
 
-		switch (addrs->ifa_addr->sa_family) {
+		addr = NULL;
+		switch (p->ifa_addr->sa_family) {
 		case AF_INET:
-			addr = &((struct sockaddr_in *)	addrs->ifa_addr)->sin_addr;
+			addr = &((struct sockaddr_in *)	p->ifa_addr)->sin_addr;
 			break;
 		case AF_INET6:
-			addr = &((struct sockaddr_in6 *) addrs->ifa_addr)->sin6_addr;
+			addr = &((struct sockaddr_in6 *) p->ifa_addr)->sin6_addr;
 			break;
 		}
+
 		if (addr) {
-			inet_ntop(addrs->ifa_addr->sa_family, addr, buff, sizeof(buff));
-			printf("%s", buff);
+			print_addr(family, addr);
+			return;
 		}
+	}
 
-	} else if (addrs->ifa_next)
-		output_iface_ip(addrs->ifa_next, iface, family);
-}
-
-static void output_ip(sa_family_t family)
-{
-	char *host;
-	struct addrinfo hints, *info = NULL;
+	if (iface)
+		return;
 
+	/* Hmm.. not found the best interface, print host IP at least */
 	memset(&hints, 0, sizeof(hints));
 	hints.ai_family = family;
 	if (family == AF_INET6)
@@ -1919,9 +2424,6 @@ static void output_ip(sa_family_t family)
 
 	host = xgethostname();
 	if (host && getaddrinfo(host, NULL, &hints, &info) == 0 && info) {
-
-		void *addr = NULL;
-
 		switch (info->ai_family) {
 		case AF_INET:
 			addr = &((struct sockaddr_in *) info->ai_addr)->sin_addr;
@@ -1930,12 +2432,8 @@ static void output_ip(sa_family_t family)
 			addr = &((struct sockaddr_in6 *) info->ai_addr)->sin6_addr;
 			break;
 		}
-		if (addr) {
-			char buff[INET6_ADDRSTRLEN + 1];
-
-			inet_ntop(info->ai_family, (void *) addr, buff, sizeof(buff));
-			printf("%s", buff);
-		}
+		if (addr)
+			print_addr(family, addr);
 
 		freeaddrinfo(info);
 	}
@@ -1974,22 +2472,25 @@ static void output_special_char(unsigned char c, struct options *op,
 {
 	struct utsname uts;
 
-	uname(&uts);
-
 	switch (c) {
 	case 's':
+		uname(&uts);
 		printf("%s", uts.sysname);
 		break;
 	case 'n':
+		uname(&uts);
 		printf("%s", uts.nodename);
 		break;
 	case 'r':
+		uname(&uts);
 		printf("%s", uts.release);
 		break;
 	case 'v':
+		uname(&uts);
 		printf("%s", uts.version);
 		break;
 	case 'm':
+		uname(&uts);
 		printf("%s", uts.machine);
 		break;
 	case 'o':
@@ -2066,30 +2567,39 @@ static void output_special_char(unsigned char c, struct options *op,
 	{
 		char *var = NULL, varname[64];
 
-		if (get_escape_argument(fp, varname, sizeof(varname)))
+		/* \S{varname} */
+		if (get_escape_argument(fp, varname, sizeof(varname))) {
 			var = read_os_release(op, varname);
-		else if (!(var = read_os_release(op, "PRETTY_NAME")))
-			var = uts.sysname;
-		if (var) {
-			if (strcmp(varname, "ANSI_COLOR") == 0)
-				printf("\033[%sm", var);
-			else
-				printf("%s", var);
-			if (var != uts.sysname)
-				free(var);
+			if (var) {
+				if (strcmp(varname, "ANSI_COLOR") == 0)
+					printf("\033[%sm", var);
+				else
+					fputs(var, stdout);
+			}
+		/* \S */
+		} else if ((var = read_os_release(op, "PRETTY_NAME"))) {
+			fputs(var, stdout);
+
+		/* \S and PRETTY_NAME not found */
+		} else {
+			uname(&uts);
+			fputs(uts.sysname, stdout);
 		}
+
+		free(var);
+
 		break;
 	}
 	case 'u':
 	case 'U':
 	{
 		int users = 0;
-		struct utmp *ut;
-		setutent();
-		while ((ut = getutent()))
+		struct utmpx *ut;
+		setutxent();
+		while ((ut = getutxent()))
 			if (ut->ut_type == USER_PROCESS)
 				users++;
-		endutent();
+		endutxent();
 		if (c == 'U')
 			printf(P_("%d user", "%d users", users), users);
 		else
@@ -2100,17 +2610,22 @@ static void output_special_char(unsigned char c, struct options *op,
 	case '6':
 	{
 		sa_family_t family = c == '4' ? AF_INET : AF_INET6;
+		struct ifaddrs *addrs = NULL;
 		char iface[128];
 
-		if (get_escape_argument(fp, iface, sizeof(iface))) {	/* interface IP */
-			struct ifaddrs *addrs;
-			int status = getifaddrs(&addrs);
-			if (status != 0)
-				break;
+#ifdef AGETTY_RELOAD
+		open_netlink();
+#endif
+
+		if (getifaddrs(&addrs))
+			break;
+
+		if (get_escape_argument(fp, iface, sizeof(iface)))
 			output_iface_ip(addrs, iface, family);
-			freeifaddrs(addrs);
-		} else							/* host IP */
-			output_ip(family);
+		else
+			output_iface_ip(addrs, NULL, family);
+
+		freeifaddrs(addrs);
 		break;
 	}
 	default:
@@ -2162,7 +2677,7 @@ static void init_special_char(char* arg, struct options *op)
 }
 
 /*
- * Appends @str to @dest and if @dest is not empty then use use @sep as a
+ * Appends @str to @dest and if @dest is not empty then use @sep as a
  * separator. The maximal final length of the @dest is @len.
  *
  * Returns the final @dest length or -1 in case of error.
@@ -2217,3 +2732,19 @@ err:
 	log_err(_("checkname failed: %m"));
 }
 
+static void reload_agettys(void)
+{
+#ifdef AGETTY_RELOAD
+	int fd = open(AGETTY_RELOAD_FILENAME, O_CREAT|O_CLOEXEC|O_WRONLY,
+					      S_IRUSR|S_IWUSR);
+	if (fd < 0)
+		err(EXIT_FAILURE, _("cannot open %s"), AGETTY_RELOAD_FILENAME);
+
+	if (futimens(fd, NULL) < 0 || close(fd) < 0)
+		err(EXIT_FAILURE, _("cannot touch file %s"),
+		    AGETTY_RELOAD_FILENAME);
+#else
+	/* very unusual */
+	errx(EXIT_FAILURE, _("--reload is unsupported on your system"));
+#endif
+}
-- 
2.20.1