summaryrefslogtreecommitdiffstats
path: root/term-utils
diff options
context:
space:
mode:
Diffstat (limited to 'term-utils')
-rw-r--r--term-utils/Makemodule.am117
-rw-r--r--term-utils/agetty.8522
-rw-r--r--term-utils/agetty.c2970
-rw-r--r--term-utils/mesg.1118
-rw-r--r--term-utils/mesg.c184
-rw-r--r--term-utils/script-playutils.c570
-rw-r--r--term-utils/script-playutils.h50
-rw-r--r--term-utils/script.1295
-rw-r--r--term-utils/script.c1068
-rw-r--r--term-utils/scriptlive.1104
-rw-r--r--term-utils/scriptlive.c365
-rw-r--r--term-utils/scriptreplay.1159
-rw-r--r--term-utils/scriptreplay.c313
-rw-r--r--term-utils/setterm.1307
-rw-r--r--term-utils/setterm.c1205
-rw-r--r--term-utils/ttymsg.c190
-rw-r--r--term-utils/ttymsg.h6
-rw-r--r--term-utils/wall.1103
-rw-r--r--term-utils/wall.c450
-rw-r--r--term-utils/write.1101
-rw-r--r--term-utils/write.c367
21 files changed, 9564 insertions, 0 deletions
diff --git a/term-utils/Makemodule.am b/term-utils/Makemodule.am
new file mode 100644
index 0000000..92df7db
--- /dev/null
+++ b/term-utils/Makemodule.am
@@ -0,0 +1,117 @@
+if BUILD_SCRIPT
+usrbin_exec_PROGRAMS += script
+dist_man_MANS += term-utils/script.1
+script_SOURCES = term-utils/script.c \
+ lib/pty-session.c \
+ include/pty-session.h \
+ lib/monotonic.c
+script_CFLAGS = $(AM_CFLAGS) -Wno-format-y2k
+script_LDADD = $(LDADD) libcommon.la $(MATH_LIBS) $(REALTIME_LIBS) -lutil
+if HAVE_UTEMPTER
+script_LDADD += -lutempter
+endif
+
+check_PROGRAMS += test_script
+test_script_SOURCES = $(script_SOURCES)
+test_script_LDADD = $(script_LDADD)
+test_script_CFLAGS = -DTEST_SCRIPT $(logger_CFLAGS)
+endif # BUILD_SCRIPT
+
+if BUILD_SCRIPTREPLAY
+usrbin_exec_PROGRAMS += scriptreplay
+dist_man_MANS += term-utils/scriptreplay.1
+scriptreplay_SOURCES = term-utils/scriptreplay.c \
+ term-utils/script-playutils.c \
+ term-utils/script-playutils.h
+scriptreplay_LDADD = $(LDADD) libcommon.la $(MATH_LIBS)
+endif # BUILD_SCRIPTREPLAY
+
+if BUILD_SCRIPTLIVE
+usrbin_exec_PROGRAMS += scriptlive
+dist_man_MANS += term-utils/scriptlive.1
+scriptlive_SOURCES = term-utils/scriptlive.c \
+ term-utils/script-playutils.c \
+ term-utils/script-playutils.h \
+ lib/pty-session.c \
+ include/pty-session.h \
+ lib/monotonic.c
+scriptlive_LDADD = $(LDADD) libcommon.la $(MATH_LIBS) $(REALTIME_LIBS) -lutil
+endif # BUILD_SCRIPTLIVE
+
+
+if BUILD_AGETTY
+sbin_PROGRAMS += agetty
+dist_man_MANS += term-utils/agetty.8
+agetty_SOURCES = term-utils/agetty.c \
+ lib/plymouth-ctrl.c
+agetty_LDADD = $(LDADD) libcommon.la
+if BSD
+agetty_LDADD += -lutil
+endif
+endif # BUILD_AGETTY
+
+
+if BUILD_SETTERM
+usrbin_exec_PROGRAMS += setterm
+dist_man_MANS += term-utils/setterm.1
+setterm_SOURCES = term-utils/setterm.c
+setterm_CFLAGS = $(AM_CFLAGS)
+setterm_LDADD = $(LDADD) libcommon.la
+if HAVE_TINFO
+setterm_LDADD += $(TINFO_LIBS)
+setterm_CFLAGS += $(TINFO_CFLAGS)
+else
+setterm_LDADD += $(NCURSES_LIBS)
+setterm_CFLAGS += $(NCURSES_CFLAGS)
+endif
+endif
+
+
+if BUILD_MESG
+usrbin_exec_PROGRAMS += mesg
+mesg_LDADD = $(LDADD) libcommon.la
+dist_man_MANS += term-utils/mesg.1
+mesg_SOURCES = term-utils/mesg.c
+endif
+
+
+if BUILD_WALL
+usrbin_exec_PROGRAMS += wall
+wall_SOURCES = \
+ term-utils/wall.c \
+ term-utils/ttymsg.c \
+ term-utils/ttymsg.h
+dist_man_MANS += term-utils/wall.1
+wall_CFLAGS = $(SUID_CFLAGS) $(AM_CFLAGS)
+wall_LDFLAGS = $(SUID_LDFLAGS) $(AM_LDFLAGS)
+wall_LDADD = $(LDADD) libcommon.la
+if USE_TTY_GROUP
+if MAKEINSTALL_DO_CHOWN
+install-exec-hook-wall::
+ chgrp tty $(DESTDIR)$(usrbin_execdir)/wall
+ chmod g+s $(DESTDIR)$(usrbin_execdir)/wall
+
+INSTALL_EXEC_HOOKS += install-exec-hook-wall
+endif
+endif
+endif # BUILD_WALL
+
+
+if BUILD_WRITE
+usrbin_exec_PROGRAMS += write
+dist_man_MANS += term-utils/write.1
+write_SOURCES = term-utils/write.c
+write_CFLAGS = $(SUID_CFLAGS) $(AM_CFLAGS)
+write_LDFLAGS = $(SUID_LDFLAGS) $(AM_LDFLAGS)
+write_LDADD = $(LDADD) libcommon.la
+
+if USE_TTY_GROUP
+if MAKEINSTALL_DO_CHOWN
+install-exec-hook-write::
+ chgrp tty $(DESTDIR)$(usrbin_execdir)/write
+ chmod g+s $(DESTDIR)$(usrbin_execdir)/write
+
+INSTALL_EXEC_HOOKS += install-exec-hook-write
+endif
+endif
+endif # BUILD_WRITE
diff --git a/term-utils/agetty.8 b/term-utils/agetty.8
new file mode 100644
index 0000000..164781e
--- /dev/null
+++ b/term-utils/agetty.8
@@ -0,0 +1,522 @@
+.TH AGETTY 8 "February 2016" "util-linux" "System Administration"
+.SH NAME
+agetty \- alternative Linux getty
+
+.SH SYNOPSIS
+.B agetty
+[options]
+.IR port " [" baud_rate "...] [" term ]
+
+.SH DESCRIPTION
+.ad
+\fBagetty\fP opens a tty port, prompts for a login name and invokes
+the /bin/login command. It is normally invoked by \fBinit\fP(8).
+
+\fBagetty\fP has several \fInon-standard\fP features that are useful
+for hardwired and for dial-in lines:
+.IP \(bu
+Adapts the tty settings to parity bits and to erase, kill,
+end-of-line and uppercase characters when it reads a login name.
+The program can handle 7-bit characters with even, odd, none or space
+parity, and 8-bit characters with no parity. The following special
+characters are recognized: Control-U (kill); DEL and
+backspace (erase); carriage return and line feed (end of line).
+See also the \fB\-\-erase\-chars\fP and \fB\-\-kill\-chars\fP options.
+.IP \(bu
+Optionally deduces the baud rate from the CONNECT messages produced by
+Hayes(tm)-compatible modems.
+.IP \(bu
+Optionally does not hang up when it is given an already opened line
+(useful for call-back applications).
+.IP \(bu
+Optionally does not display the contents of the \fI/etc/issue\fP file.
+.IP \(bu
+Optionally displays an alternative issue files or directories instead of \fI/etc/issue\fP or \fI/etc/issue.d\fP.
+.IP \(bu
+Optionally does not ask for a login name.
+.IP \(bu
+Optionally invokes a non-standard login program instead of
+\fI/bin/login\fP.
+.IP \(bu
+Optionally turns on hardware flow control.
+.IP \(bu
+Optionally forces the line to be local with no need for carrier detect.
+.PP
+This program does not use the \fI/etc/gettydefs\fP (System V) or
+\fI/etc/gettytab\fP (SunOS 4) files.
+.SH ARGUMENTS
+.na
+.nf
+.fi
+.ad
+.TP
+.I port
+A path name relative to the \fI/dev\fP directory. If a "\-" is
+specified, \fBagetty\fP assumes that its standard input is
+already connected to a tty port and that a connection to a
+remote user has already been established.
+.sp
+Under System V, a "\-" \fIport\fP argument should be preceded
+by a "\-\-".
+.TP
+.IR baud_rate ,...
+A comma-separated list of one or more baud rates. Each time
+\fBagetty\fP receives a BREAK character it advances through
+the list, which is treated as if it were circular.
+.sp
+Baud rates should be specified in descending order, so that the
+null character (Ctrl\-@) can also be used for baud-rate switching.
+.sp
+This argument is optional and unnecessary for \fBvirtual terminals\fP.
+.sp
+The default for \fBserial terminals\fP is keep the current baud rate
+(see \fB\-\-keep\-baud\fP) and if unsuccessful then default to '9600'.
+.TP
+.I term
+The value to be used for the TERM environment variable. This overrides
+whatever init(8) may have set, and is inherited by login and the shell.
+.sp
+The default is 'vt100', or 'linux' for Linux on a virtual terminal,
+or 'hurd' for GNU Hurd on a virtual terminal.
+.SH OPTIONS
+.na
+.nf
+.fi
+.ad
+.TP
+\-8, \-\-8bits
+Assume that the tty is 8-bit clean, hence disable parity detection.
+.TP
+\-a, \-\-autologin \fIusername\fP
+Automatically log in the specified user without asking for a username or password.
+Using this option causes an \fB\-f \fIusername\fR option and argument to be
+added to the \fB/bin/login\fP command line. See \fB\-\-login\-options\fR, which
+can be used to modify this option's behavior.
+
+Note that \fB\-\-autologin\fP may affect the way how agetty initializes the
+serial line, because on auto-login agetty does not read from the line and it
+has no opportunity optimize the line setting.
+.TP
+\-c, \-\-noreset
+Do not reset terminal cflags (control modes). See \fBtermios\fP(3) for more
+details.
+.TP
+\-E, \-\-remote
+Typically the \fBlogin\fP(1) command is given a remote hostname when
+called by something such as \fBtelnetd\fP(8). This option allows \fBagetty\fP
+to pass what it is using for a hostname to \fBlogin\fP(1) for use
+in \fButmp\fP(5). See \fB\-\-host\fP, \fBlogin\fP(1), and \fButmp\fP(5).
+.IP
+If the \fB\-\-host\fP \fIfakehost\fP option is given, then an \fB\-h\fP
+\fIfakehost\fP option and argument are added to the \fB/bin/login\fP
+command line.
+.IP
+If the \fB\-\-nohostname\fR option is given, then an \fB\-H\fP option
+is added to the \fB/bin/login\fP command line.
+.IP
+See \fB\-\-login\-options\fR.
+.TP
+\-f, \-\-issue\-file \fIpath\fP
+Specifies a ":" delimited list of files and directories to be displayed instead
+of \fI/etc/issue\fP (or other). All specified files and directories are displayed,
+missing or empty files are silently ignored. If the specified path is a
+directory then display all files with .issue file extension in version-sort
+order from the directory. This allows custom messages to be displayed on
+different terminals. The \fB\-\-noissue\fP option will override this option.
+.TP
+\-\-show\-issue
+Display the current issue file (or other) on the current terminal and exit.
+Use this option to review the current setting, it is not designed for any other
+purpose. Note that output may use some default or incomplete information as
+proper output depends on terminal and agetty command line.
+.TP
+\-h, \-\-flow\-control
+Enable hardware (RTS/CTS) flow control. It is left up to the
+application to disable software (XON/XOFF) flow protocol where
+appropriate.
+.TP
+\-H, \-\-host \fIfakehost\fP
+Write the specified \fIfakehost\fP into the utmp file. Normally,
+no login host is given, since \fBagetty\fP is used for local hardwired
+connections and consoles. However, this option can be useful for
+identifying terminal concentrators and the like.
+.TP
+\-i, \-\-noissue
+Do not display the contents of \fI/etc/issue\fP (or other) before writing the
+login prompt. Terminals or communications hardware may become confused
+when receiving lots of text at the wrong baud rate; dial-up scripts
+may fail if the login prompt is preceded by too much text.
+.TP
+\-I, \-\-init\-string \fIinitstring\fP
+Set an initial string to be sent to the tty or modem before sending
+anything else. This may be used to initialize a modem. Non-printable
+characters may be sent by writing their octal code preceded by a
+backslash (\\). For example, to send a linefeed character (ASCII 10,
+octal 012), write \\012.
+.TP
+\-J, \-\-noclear
+Do not clear the screen before prompting for the login name.
+By default the screen is cleared.
+.TP
+\-l, \-\-login\-program \fIlogin_program\fP
+Invoke the specified \fIlogin_program\fP instead of /bin/login. This allows
+the use of a non-standard login program. Such a program could, for example,
+ask for a dial-up password or use a different password file. See
+\fB\-\-login\-options\fP.
+.TP
+\-L, \-\-local\-line[=\fImode\fP]
+Control the CLOCAL line flag. The optional \fImode\fP argument is 'auto', 'always' or 'never'.
+If the \fImode\fP argument is omitted, then the default is 'always'. If the
+\-\-local\-line option is not given at all, then the default is 'auto'.
+.PP
+.RS
+.PD 1
+.TP
+\fIalways\fR
+Forces the line to be a local line with no need for carrier detect. This
+can be useful when you have a locally attached terminal where the serial
+line does not set the carrier-detect signal.
+.TP
+\fInever\fR
+Explicitly clears the CLOCAL flag from the line setting and the
+carrier-detect signal is expected on the line.
+.TP
+\fIauto\fR
+The \fBagetty\fR default. Does not modify the CLOCAL setting and follows
+the setting enabled by the kernel.
+.PD
+.RE
+.TP
+\-m, \-\-extract\-baud
+Try to extract the baud rate from the CONNECT status message
+produced by Hayes(tm)\-compatible modems. These status
+messages are of the form: "<junk><speed><junk>".
+\fBagetty\fP assumes that the modem emits its status message at
+the same speed as specified with (the first) \fIbaud_rate\fP value
+on the command line.
+.sp
+Since the \fB\-\-extract\-baud\fP feature may fail on heavily-loaded
+systems, you still should enable BREAK processing by enumerating all
+expected baud rates on the command line.
+.TP
+\-\-list\-speeds
+Display supported baud rates. These are determined at compilation time.
+.TP
+\-n, \-\-skip\-login
+Do not prompt the user for a login name. This can be used in connection
+with the \fB\-\-login\-program\fP option to invoke a non-standard login
+process such as a BBS system. Note that with the \fB\-\-skip\-login\fR
+option, \fBagetty\fR gets no input from the user who logs in and therefore
+will not be able to figure out parity, character size, and newline
+processing of the connection. It defaults to space parity, 7 bit
+characters, and ASCII CR (13) end-of-line character. Beware that the
+program that \fBagetty\fR starts (usually /bin/login) is run as root.
+.TP
+\-N, \-\-nonewline
+Do not print a newline before writing out /etc/issue.
+.TP
+\-o, \-\-login\-options "\fIlogin_options\fP"
+Options and arguments that are passed to \fBlogin\fP(1). Where \eu is
+replaced by the login name. For example:
+.RS
+.IP "" 4
+.B "\-\-login\-options '\-h darkstar \-\- \eu'"
+.PP
+See \fB\-\-autologin\fR, \fB\-\-login\-program\fR and \fB\-\-remote\fR.
+.PP
+Please read the SECURITY NOTICE below before using this option.
+.RE
+.TP
+\-p, \-\-login\-pause
+Wait for any key before dropping to the login prompt. Can be combined
+with \fB\-\-autologin\fP to save memory by lazily spawning shells.
+.TP
+\-r, \-\-chroot \fIdirectory\fP
+Change root to the specified directory.
+.TP
+\-R, \-\-hangup
+Call vhangup() to do a virtual hangup of the specified terminal.
+.TP
+\-s, \-\-keep\-baud
+Try to keep the existing baud rate. The baud rates from the command line are
+used when agetty receives a BREAK character. If another baud rates specified
+then the original baud rate is also saved to the end of the wanted baud rates
+list.
+This can be used to return to the original baud rate after unexpected BREAKs.
+.TP
+\-t, \-\-timeout \fItimeout\fP
+Terminate if no user name could be read within \fItimeout\fP seconds.
+Use of this option with hardwired terminal lines is not recommended.
+.TP
+\-U, \-\-detect\-case
+Turn on support for detecting an uppercase-only terminal. This setting
+will detect a login name containing only capitals as indicating an
+uppercase-only terminal and turn on some upper-to-lower case conversions.
+Note that this has no support for any Unicode characters.
+.TP
+\-w, \-\-wait\-cr
+Wait for the user or the modem to send a carriage-return or a
+linefeed character before sending the \fI/etc/issue\fP file (or others)
+and the login prompt. This is useful with the \fB\-\-init\-string\fP
+option.
+.TP
+\-\-nohints
+Do not print hints about Num, Caps and Scroll Locks.
+.TP
+\-\-nohostname
+By default the hostname will be printed. With this option enabled,
+no hostname at all will be shown.
+.TP
+\-\-long\-hostname
+By default the hostname is only printed until the first dot. With
+this option enabled, the fully qualified hostname by \fBgethostname\fR(3P)
+or (if not found) by \fBgetaddrinfo\fR(3) is shown.
+.TP
+\-\-erase\-chars \fIstring\fP
+This option specifies additional characters that should be interpreted as a
+backspace ("ignore the previous character") when the user types the login name.
+The default additional \'erase\' has been \'#\', but since util-linux 2.23
+no additional erase characters are enabled by default.
+.TP
+\-\-kill\-chars \fIstring\fP
+This option specifies additional characters that should be interpreted as a
+kill ("ignore all previous characters") when the user types the login name.
+The default additional \'kill\' has been \'@\', but since util-linux 2.23
+no additional kill characters are enabled by default.
+.TP
+\-\-chdir \fIdirectory\fP
+Change directory before the login.
+.TP
+\-\-delay \fInumber\fP
+Sleep seconds before open tty.
+.TP
+\-\-nice \fInumber\fP
+Run login with this priority.
+.TP
+\-\-reload
+Ask all running agetty instances to reload and update their displayed prompts,
+if the user has not yet commenced logging in. After doing so the command will
+exit. This feature might be unsupported on systems without Linux
+.BR inotify (7).
+.TP
+\-\-version
+Display version information and exit.
+.TP
+\-\-help
+Display help text and exit.
+.SH EXAMPLE
+This section shows examples for the process field of an entry in the
+\fI/etc/inittab\fP file. You'll have to prepend appropriate values
+for the other fields. See \fIinittab(5)\fP for more details.
+
+For a hardwired line or a console tty:
+
+.RS
+.B /sbin/agetty\ 9600\ ttyS1
+.RE
+
+For a directly connected terminal without proper carrier-detect wiring
+(try this if your terminal just sleeps instead of giving you a password:
+prompt):
+
+.RS
+.B /sbin/agetty\ \-\-local\-line\ 9600\ ttyS1\ vt100
+.RE
+
+For an old-style dial-in line with a 9600/2400/1200 baud modem:
+
+.RS
+.B /sbin/agetty\ \-\-extract\-baud\ \-\-timeout\ 60\ ttyS1\ 9600,2400,1200
+.RE
+
+For a Hayes modem with a fixed 115200 bps interface to the machine
+(the example init string turns off modem echo and result codes, makes
+modem/computer DCD track modem/modem DCD, makes a DTR drop cause a
+disconnection, and turns on auto-answer after 1 ring):
+
+.ie n .RS 0
+.el .RS
+.B /sbin/agetty\ \-\-wait\-cr\ \-\-init\-string\ 'ATE0Q1&D2&C1S0=1\\015'\ 115200\ ttyS1
+.RE
+
+.SH SECURITY NOTICE
+If you use the \fB\-\-login\-program\fP and \fB\-\-login\-options\fP options,
+be aware that a malicious user may try to enter lognames with embedded options,
+which then get passed to the used login program. Agetty does check
+for a leading "\-" and makes sure the logname gets passed as one parameter
+(so embedded spaces will not create yet another parameter), but depending
+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 command line should
+not be interpreted as options. Use this feature if available by passing "\-\-"
+before the username gets passed by \\u.
+
+.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 \fBwith .issue extension\fP from the directory are
+printed in version-sort order. The directory can be used 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 all the default issue file and
+directory locations are ignored.
+
+The issue file feature is possible to completely disable by \fB\-\-noissue\fP option.
+
+It is possible to review the current issue file by \fBagetty \-\-show\-issue\fP
+on the current terminal.
+
+The issue files may contain certain escape codes to display the system name, date, time
+et cetera. All escape codes consist of a backslash (\\) immediately
+followed by one of the characters listed below.
+
+.TP
+4 or 4{\fIinterface\fR}
+Insert the IPv4 address of the specified network interface (for example: \\4{eth0}).
+If the \fIinterface\fR argument is not specified, then select the first fully
+configured (UP, non-LOCALBACK, RUNNING) interface. If not any configured
+interface is found, fall back to the IP address of the machine's hostname.
+.TP
+6 or 6{\fIinterface\fR}
+The same as \\4 but for IPv6.
+.TP
+b
+Insert the baudrate of the current line.
+.TP
+d
+Insert the current date.
+.TP
+e or e{\fIname\fR}
+Translate the human-readable \fIname\fP to an escape sequence and insert it
+(for example: \\e{red}Alert text.\\e{reset}). If the \fIname\fR argument is
+not specified, then insert \\033. The currently supported names are: black,
+blink, blue, bold, brown, cyan,
+darkgray, gray, green, halfbright, lightblue, lightcyan, lightgray, lightgreen,
+lightmagenta, lightred, magenta, red, reset, reverse, yellow and white. All unknown
+names are silently ignored.
+.TP
+s
+Insert the system name (the name of the operating system). Same as 'uname \-s'.
+See also the \\S escape code.
+.TP
+S or S{VARIABLE}
+Insert the VARIABLE data from \fI/etc/os-release\fP. If this file does not exist
+then fall back to \fI/usr/lib/os-release\fP. If the VARIABLE argument is not
+specified, then use PRETTY_NAME from the file or the system name (see \\s).
+This escape code can be used to keep \fI/etc/issue\fP distribution and release
+independent. Note that \\S{ANSI_COLOR} is converted to the real terminal
+escape sequence.
+.TP
+l
+Insert the name of the current tty line.
+.TP
+m
+Insert the architecture identifier of the machine. Same as 'uname \-m'.
+.TP
+n
+Insert the nodename of the machine, also known as the hostname. Same as 'uname \-n'.
+.TP
+o
+Insert the NIS domainname of the machine. Same as 'hostname \-d'.
+.TP
+O
+Insert the DNS domainname of the machine.
+.TP
+r
+Insert the release number of the OS. Same as 'uname \-r'.
+.TP
+t
+Insert the current time.
+.TP
+u
+Insert the number of current users logged in.
+.TP
+U
+Insert the string "1 user" or "<n> users" where <n> is the number of current
+users logged in.
+.TP
+v
+Insert the version of the OS, that is, the build-date and such.
+.PP
+An example. On my system, the following \fI/etc/issue\fP file:
+.sp
+.na
+.RS
+.nf
+This is \\n.\\o (\\s \\m \\r) \\t
+.fi
+.RE
+.PP
+displays as:
+.sp
+.RS
+.nf
+This is thingol.orcan.dk (Linux i386 1.1.9) 18:29:30
+.fi
+.RE
+
+.SH FILES
+.na
+.TP
+.I /var/run/utmp
+the system status file.
+.TP
+.I /etc/issue
+printed before the login prompt.
+.TP
+.I /etc/os-release /usr/lib/os-release
+operating system identification data.
+.TP
+.I /dev/console
+problem reports (if syslog(3) is not used).
+.TP
+.I /etc/inittab
+\fIinit\fP(8) configuration file for SysV-style init daemon.
+.SH BUGS
+.ad
+The baud-rate detection feature (the \fB\-\-extract\-baud\fP option) requires that
+\fBagetty\fP be scheduled soon enough after completion of a dial-in
+call (within 30 ms with modems that talk at 2400 baud). For robustness,
+always use the \fB\-\-extract\-baud\fP option in combination with a multiple baud
+rate command-line argument, so that BREAK processing is enabled.
+
+The text in the \fI/etc/issue\fP file (or other) and the login prompt
+are always output with 7-bit characters and space parity.
+
+The baud-rate detection feature (the \fB\-\-extract\-baud\fP option) requires that
+the modem emits its status message \fIafter\fP raising the DCD line.
+.SH DIAGNOSTICS
+.ad
+Depending on how the program was configured, all diagnostics are
+written to the console device or reported via the \fBsyslog\fR(3) facility.
+Error messages are produced if the \fIport\fP argument does not
+specify a terminal device; if there is no utmp entry for the
+current process (System V only); and so on.
+.SH AUTHORS
+.UR werner@suse.de
+Werner Fink
+.UE
+.br
+.UR kzak@redhat.com
+Karel Zak
+.UE
+.sp
+The original
+.B agetty
+for serial terminals was written by W.Z. Venema <wietse@wzv.win.tue.nl>
+and ported to Linux by Peter Orbaek <poe@daimi.aau.dk>.
+
+.SH AVAILABILITY
+The agetty command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util\-linux/.
diff --git a/term-utils/agetty.c b/term-utils/agetty.c
new file mode 100644
index 0000000..191fa29
--- /dev/null
+++ b/term-utils/agetty.c
@@ -0,0 +1,2970 @@
+/*
+ * Alternate Getty (agetty) 'agetty' is a versatile, portable, easy to use
+ * replacement for getty on SunOS 4.1.x or the SAC ttymon/ttyadm/sacadm/pmadm
+ * suite on Solaris and other SVR4 systems. 'agetty' was written by Wietse
+ * Venema, enhanced by John DiMarco, and further enhanced by Dennis Cronin.
+ *
+ * Ported to Linux by Peter Orbaek <poe@daimi.aau.dk>
+ * Adopt the mingetty features for a better support
+ * of virtual consoles by Werner Fink <werner@suse.de>
+ *
+ * This program is freely distributable.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <signal.h>
+#include <errno.h>
+#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 <utmpx.h>
+#include <getopt.h>
+#include <time.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"
+#include "nls.h"
+#include "pathnames.h"
+#include "c.h"
+#include "cctype.h"
+#include "widechar.h"
+#include "ttyutils.h"
+#include "color-names.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>
+# 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
+#elif defined(__GNU__)
+# define USE_SYSLOG
+# ifndef DEFAULT_VCTERM
+# define DEFAULT_VCTERM "hurd"
+# endif
+# ifndef DEFAULT_STERM
+# define DEFAULT_STERM "vt102"
+# endif
+#else
+# ifndef DEFAULT_VCTERM
+# define DEFAULT_VCTERM "vt100"
+# endif
+# ifndef DEFAULT_STERM
+# define DEFAULT_STERM "vt100"
+# 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>
+#endif
+
+/*
+ * Some heuristics to find out what environment we are in: if it is not
+ * System V, assume it is SunOS 4. The LOGIN_PROCESS is defined in System V
+ * utmp.h, which will select System V style getty.
+ */
+#ifdef LOGIN_PROCESS
+# define SYSV_STYLE
+#endif
+
+/*
+ * Things you may want to modify.
+ *
+ * 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
+ * and for line editing at the same time.
+ */
+
+/* Displayed before the login prompt. */
+#ifdef SYSV_STYLE
+# 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. */
+#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;
+static uint32_t netlink_groups;
+#endif
+
+struct issue {
+ FILE *output;
+ char *mem;
+ size_t mem_sz;
+
+#ifdef AGETTY_RELOAD
+ char *mem_old;
+#endif
+ unsigned int do_tcsetattr : 1,
+ do_tcrestore : 1;
+};
+
+/*
+ * When multiple baud rates are specified on the command line, the first one
+ * we will try is the first one specified.
+ */
+#define FIRST_SPEED 0
+
+/* Storage for command-line options. */
+#define MAX_SPEED 10 /* max. nr. of baud rates */
+
+struct options {
+ int flags; /* toggle switches, see below */
+ unsigned int timeout; /* time-out period */
+ char *autolog; /* login the user automatically */
+ char *chdir; /* Chdir before the login */
+ char *chroot; /* Chroot before the login */
+ char *login; /* login program */
+ char *logopt; /* options for login program */
+ char *tty; /* name of tty */
+ char *vcline; /* line of virtual console */
+ char *term; /* terminal type */
+ char *initstring; /* modem init string */
+ 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 */
+ 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_* */
+ int kbmode; /* Keyboard mode if virtual console */
+ speed_t speeds[MAX_SPEED]; /* baud rates to be tried */
+};
+
+enum {
+ CLOCAL_MODE_AUTO = 0,
+ CLOCAL_MODE_ALWAYS,
+ CLOCAL_MODE_NEVER
+};
+
+#define F_PARSE (1<<0) /* process modem status messages */
+#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_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 */
+#define F_KEEPCFLAGS (1<<10) /* reuse c_cflags setup from kernel */
+#define F_EIGHTBITS (1<<11) /* Assume 8bit-clean tty */
+#define F_VCONSOLE (1<<12) /* This is a virtual console */
+#define F_HANGUP (1<<13) /* Do call vhangup(2) */
+#define F_UTF8 (1<<14) /* We can do UTF8 */
+#define F_LOGINPAUSE (1<<15) /* Wait for any key before dropping login prompt */
+#define F_NOCLEAR (1<<16) /* Do not clear the screen before prompting */
+#define F_NONL (1<<17) /* No newline before issue */
+#define F_NOHOSTNAME (1<<18) /* Do not show the hostname */
+#define F_LONGHNAME (1<<19) /* Show Full qualified hostname */
+#define F_NOHINTS (1<<20) /* Don't print hints */
+#define F_REMOTE (1<<21) /* Add '-h fakehost' to login(1) command line */
+
+#define serial_tty_option(opt, flag) \
+ (((opt)->flags & (F_VCONSOLE|(flag))) == (flag))
+
+struct Speedtab {
+ long speed;
+ speed_t code;
+};
+
+static const struct Speedtab speedtab[] = {
+ {50, B50},
+ {75, B75},
+ {110, B110},
+ {134, B134},
+ {150, B150},
+ {200, B200},
+ {300, B300},
+ {600, B600},
+ {1200, B1200},
+ {1800, B1800},
+ {2400, B2400},
+ {4800, B4800},
+ {9600, B9600},
+#ifdef B19200
+ {19200, B19200},
+#elif defined(EXTA)
+ {19200, EXTA},
+#endif
+#ifdef B38400
+ {38400, B38400},
+#elif defined(EXTB)
+ {38400, EXTB},
+#endif
+#ifdef B57600
+ {57600, B57600},
+#endif
+#ifdef B115200
+ {115200, B115200},
+#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},
+};
+
+static void init_special_char(char* arg, struct options *op);
+static void parse_args(int argc, char **argv, struct options *op);
+static void parse_speeds(struct options *op, char *arg);
+static void update_utmp(struct options *op);
+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, int canon);
+static void auto_baud(struct termios *tp);
+static void list_speeds(void);
+static void output_special_char (struct issue *ie, unsigned char c, struct options *op,
+ struct termios *tp, FILE *fp);
+static void do_prompt(struct issue *ie, struct options *op, struct termios *tp);
+static void next_speed(struct options *op, struct termios *tp);
+static char *get_logname(struct issue *ie, struct options *op,
+ struct termios *tp, struct chardata *cp);
+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(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 *, ...)
+ __attribute__((__format__(printf, 1, 2)));
+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 issue *ie, struct options *op, struct termios *tp);
+static void eval_issue_file(struct issue *ie, struct options *op, struct termios *tp);
+static void show_issue(struct options *op);
+
+
+/* Fake hostname for ut_host specified on command line. */
+static char *fakehost;
+
+#ifdef DEBUGGING
+# include "closestream.h"
+# ifndef DEBUG_OUTPUT
+# define DEBUG_OUTPUT "/dev/tty10"
+# endif
+# define debug(s) do { fprintf(dbf,s); fflush(dbf); } while (0)
+FILE *dbf;
+#else
+# define debug(s) do { ; } while (0)
+#endif
+
+int main(int argc, char **argv)
+{
+ char *username = NULL; /* login name, given to /bin/login */
+ struct chardata chardata; /* will be set by get_logname() */
+ struct termios termios; /* terminal mode bits */
+ struct options options = {
+ .flags = F_ISSUE, /* show /etc/issue (SYSV_STYLE) */
+ .login = _PATH_LOGIN, /* default login program */
+ .tty = "tty1" /* default tty line */
+ };
+ struct issue issue = {
+ .mem = NULL,
+ };
+ char *login_argv[LOGIN_ARGV_MAX + 1];
+ int login_argc = 0;
+ struct sigaction sa, sa_hup, sa_quit, sa_int;
+ sigset_t set;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+
+ /* In case vhangup(2) has to called */
+ sa.sa_handler = SIG_IGN;
+ sa.sa_flags = SA_RESTART;
+ sigemptyset (&sa.sa_mask);
+ sigaction(SIGHUP, &sa, &sa_hup);
+ sigaction(SIGQUIT, &sa, &sa_quit);
+ sigaction(SIGINT, &sa, &sa_int);
+
+#ifdef DEBUGGING
+ dbf = fopen(DEBUG_OUTPUT, "w");
+ for (int i = 1; i < argc; i++) {
+ if (i > 1)
+ debug(" ");
+ debug(argv[i]);
+ }
+ debug("\n");
+#endif /* DEBUGGING */
+
+ /* Parse command-line arguments. */
+ parse_args(argc, argv, &options);
+
+ login_argv[login_argc++] = options.login; /* set login program name */
+
+ /* Update the utmp file. */
+#ifdef SYSV_STYLE
+ update_utmp(&options);
+#endif
+ if (options.delay)
+ sleep(options.delay);
+
+ debug("calling open_tty\n");
+
+ /* Open the tty as standard { input, output, error }. */
+ open_tty(options.tty, &termios, &options);
+
+ /* Unmask SIGHUP if inherited */
+ sigemptyset(&set);
+ sigaddset(&set, SIGHUP);
+ sigprocmask(SIG_UNBLOCK, &set, NULL);
+ 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);
+
+ /* Write the modem init string and DO NOT flush the buffers. */
+ if (serial_tty_option(&options, F_INITSTRING) &&
+ options.initstring && *options.initstring != '\0') {
+ debug("writing init string\n");
+ write_all(STDOUT_FILENO, options.initstring,
+ strlen(options.initstring));
+ }
+
+ if (options.flags & F_VCONSOLE || options.clocal != CLOCAL_MODE_ALWAYS)
+ /* Go to blocking mode unless -L is specified, this change
+ * affects stdout, stdin and stderr as all the file descriptors
+ * are created by dup(). */
+ fcntl(STDOUT_FILENO, F_SETFL,
+ fcntl(STDOUT_FILENO, F_GETFL, 0) & ~O_NONBLOCK);
+
+ /* Optionally detect the baud rate from the modem status message. */
+ debug("before autobaud\n");
+ if (serial_tty_option(&options, F_PARSE))
+ auto_baud(&termios);
+
+ /* Set the optional timer. */
+ if (options.timeout)
+ alarm(options.timeout);
+
+ /* Optionally wait for CR or LF before writing /etc/issue */
+ if (serial_tty_option(&options, F_WAITCRLF)) {
+ char ch;
+
+ debug("waiting for cr-lf\n");
+ while (read(STDIN_FILENO, &ch, 1) == 1) {
+ /* Strip "parity bit". */
+ ch &= 0x7f;
+#ifdef DEBUGGING
+ fprintf(dbf, "read %c\n", ch);
+#endif
+ if (ch == '\n' || ch == '\r')
+ break;
+ }
+ }
+
+ INIT_CHARDATA(&chardata);
+
+ if (options.autolog) {
+ debug("doing auto login\n");
+ username = options.autolog;
+ }
+
+ if (options.flags & F_NOPROMPT) { /* --skip-login */
+ eval_issue_file(&issue, &options, &termios);
+ print_issue_file(&issue, &options, &termios);
+ } else { /* regular (auto)login */
+ if (options.autolog) {
+ /* Autologin prompt */
+ eval_issue_file(&issue, &options, &termios);
+ do_prompt(&issue, &options, &termios);
+ printf(_("%s%s (automatic login)\n"), LOGIN, options.autolog);
+ } else {
+ /* Read the login name. */
+ debug("reading login name\n");
+ while ((username =
+ get_logname(&issue, &options, &termios, &chardata)) == NULL)
+ if ((options.flags & F_VCONSOLE) == 0 && options.numspeed)
+ next_speed(&options, &termios);
+ }
+ }
+
+ /* Disable timer. */
+ if (options.timeout)
+ alarm(0);
+
+ /* Finalize the termios settings. */
+ if ((options.flags & F_VCONSOLE) == 0)
+ termio_final(&options, &termios, &chardata);
+ else
+ reset_vc(&options, &termios, 1);
+
+ /* Now the newline character should be properly written. */
+ write_all(STDOUT_FILENO, "\r\n", 2);
+
+ sigaction(SIGQUIT, &sa_quit, NULL);
+ sigaction(SIGINT, &sa_int, NULL);
+
+ if (username)
+ check_username(username);
+
+ if (options.logopt) {
+ /*
+ * The --login-options completely overwrites the default
+ * way how agetty composes login(1) command line.
+ */
+ login_options_to_argv(login_argv, &login_argc,
+ options.logopt, username);
+ } else {
+ 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)
+ login_argv[login_argc++] = "-f";
+ else
+ login_argv[login_argc++] = "--";
+ login_argv[login_argc++] = username;
+ }
+ }
+
+ login_argv[login_argc] = NULL; /* last login argv */
+
+ 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
+ if (close_stream(dbf) != 0)
+ log_err("write failed: %s", DEBUG_OUTPUT);
+#endif
+
+ /* Let the login program take care of password validation. */
+ execv(options.login, login_argv);
+ log_err(_("%s: can't exec %s: %m"), options.tty, login_argv[0]);
+}
+
+/*
+ * Returns : @str if \u not found
+ * : @username if @str equal to "\u"
+ * : newly allocated string if \u mixed with something other
+ */
+static char *replace_u(char *str, char *username)
+{
+ char *entry = NULL, *p = str;
+ size_t usz = username ? strlen(username) : 0;
+
+ while (*p) {
+ size_t sz;
+ char *tp, *old = entry;
+
+ if (memcmp(p, "\\u", 2) != 0) {
+ p++;
+ continue; /* no \u */
+ }
+ sz = strlen(str);
+
+ if (p == str && sz == 2) {
+ /* 'str' contains only '\u' */
+ free(old);
+ return username;
+ }
+
+ tp = entry = malloc(sz + usz);
+ if (!tp)
+ log_err(_("failed to allocate memory: %m"));
+
+ if (p != str) {
+ /* copy chars before \u */
+ memcpy(tp, str, p - str);
+ tp += p - str;
+ }
+ if (usz) {
+ /* copy username */
+ memcpy(tp, username, usz);
+ tp += usz;
+ }
+ if (*(p + 2))
+ /* copy chars after \u + \0 */
+ memcpy(tp, p + 2, sz - (p - str) - 1);
+ else
+ *tp = '\0';
+
+ p = tp;
+ str = entry;
+ free(old);
+ }
+
+ return entry ? entry : str;
+}
+
+static void login_options_to_argv(char *argv[], int *argc,
+ char *str, char *username)
+{
+ char *p;
+ int i = *argc;
+
+ while (str && isspace(*str))
+ str++;
+ p = str;
+
+ while (p && *p && i < LOGIN_ARGV_MAX) {
+ if (isspace(*p)) {
+ *p = '\0';
+ while (isspace(*++p))
+ ;
+ if (*p) {
+ argv[i++] = replace_u(str, username);
+ str = p;
+ }
+ } else
+ p++;
+ }
+ if (str && *str && i < LOGIN_ARGV_MAX)
+ argv[i++] = replace_u(str, username);
+ *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)
+{
+ int c;
+ int opt_show_issue = 0;
+
+ enum {
+ VERSION_OPTION = CHAR_MAX + 1,
+ NOHINTS_OPTION,
+ NOHOSTNAME_OPTION,
+ LONGHOSTNAME_OPTION,
+ HELP_OPTION,
+ ERASE_CHARS_OPTION,
+ KILL_CHARS_OPTION,
+ RELOAD_OPTION,
+ LIST_SPEEDS_OPTION,
+ ISSUE_SHOW_OPTION,
+ };
+ const struct option longopts[] = {
+ { "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' },
+ { "show-issue", no_argument, NULL, ISSUE_SHOW_OPTION },
+ { "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:L::mnNo:pP:r:Rst:Uw", longopts,
+ NULL)) != -1) {
+ switch (c) {
+ case '8':
+ op->flags |= F_EIGHTBITS;
+ break;
+ case 'a':
+ op->autolog = optarg;
+ break;
+ case 'c':
+ op->flags |= F_KEEPCFLAGS;
+ break;
+ case 'C':
+ op->chdir = optarg;
+ break;
+ case 'd':
+ op->delay = strtou32_or_err(optarg, _("invalid delay argument"));
+ break;
+ case 'E':
+ op->flags |= F_REMOTE;
+ break;
+ case 'f':
+ op->issue = optarg;
+ break;
+ case 'h':
+ op->flags |= F_RTSCTS;
+ break;
+ case 'H':
+ fakehost = optarg;
+ break;
+ case 'i':
+ op->flags &= ~F_ISSUE;
+ break;
+ case 'I':
+ init_special_char(optarg, op);
+ op->flags |= F_INITSTRING;
+ break;
+ case 'J':
+ op->flags |= F_NOCLEAR;
+ break;
+ case 'l':
+ op->login = optarg;
+ break;
+ case 'L':
+ /* -L and -L=always have the same meaning */
+ op->clocal = CLOCAL_MODE_ALWAYS;
+ if (optarg) {
+ if (strcmp(optarg, "=always") == 0)
+ op->clocal = CLOCAL_MODE_ALWAYS;
+ else if (strcmp(optarg, "=never") == 0)
+ op->clocal = CLOCAL_MODE_NEVER;
+ else if (strcmp(optarg, "=auto") == 0)
+ op->clocal = CLOCAL_MODE_AUTO;
+ else
+ log_err(_("invalid argument of --local-line"));
+ }
+ break;
+ case 'm':
+ op->flags |= F_PARSE;
+ break;
+ case 'n':
+ op->flags |= F_NOPROMPT;
+ break;
+ case 'N':
+ op->flags |= F_NONL;
+ break;
+ case 'o':
+ op->logopt = optarg;
+ break;
+ case 'p':
+ op->flags |= F_LOGINPAUSE;
+ break;
+ case 'P':
+ op->nice = strtos32_or_err(optarg, _("invalid nice argument"));
+ break;
+ case 'r':
+ op->chroot = optarg;
+ break;
+ case 'R':
+ op->flags |= F_HANGUP;
+ break;
+ case 's':
+ op->flags |= F_KEEPSPEED;
+ break;
+ case 't':
+ op->timeout = strtou32_or_err(optarg, _("invalid timeout argument"));
+ break;
+ case 'U':
+ op->flags |= F_LCUC;
+ break;
+ case 'w':
+ op->flags |= F_WAITCRLF;
+ break;
+ case NOHINTS_OPTION:
+ op->flags |= F_NOHINTS;
+ break;
+ case NOHOSTNAME_OPTION:
+ op->flags |= F_NOHOSTNAME;
+ break;
+ case LONGHOSTNAME_OPTION:
+ op->flags |= F_LONGHNAME;
+ break;
+ case ERASE_CHARS_OPTION:
+ op->erasechars = optarg;
+ break;
+ 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 ISSUE_SHOW_OPTION:
+ opt_show_issue = 1;
+ break;
+ case VERSION_OPTION:
+ output_version();
+ exit(EXIT_SUCCESS);
+ case HELP_OPTION:
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+
+ if (opt_show_issue) {
+ show_issue(op);
+ exit(EXIT_SUCCESS);
+ }
+
+ debug("after getopt loop\n");
+
+ if (argc < optind + 1) {
+ log_warn(_("not enough arguments"));
+ errx(EXIT_FAILURE, _("not enough arguments"));
+ }
+
+ /* Accept "tty", "baudrate tty", and "tty baudrate". */
+ if (is_speed(argv[optind])) {
+ /* Assume BSD style speed. */
+ parse_speeds(op, argv[optind++]);
+ if (argc < optind + 1) {
+ 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 (is_speed(v)) {
+ parse_speeds(op, v);
+ optind++;
+ }
+ }
+ }
+
+ /* On virtual console remember the line which is used for */
+ if (strncmp(op->tty, "tty", 3) == 0 &&
+ strspn(op->tty + 3, "0123456789") == strlen(op->tty+3))
+ op->vcline = op->tty+3;
+
+ if (argc > optind && argv[optind])
+ op->term = argv[optind];
+
+ debug("exiting parseargs\n");
+}
+
+/* Parse alternate baud rates. */
+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(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
+
+/* Update our utmp entry. */
+static void update_utmp(struct options *op)
+{
+ struct utmpx ut;
+ time_t t;
+ pid_t pid = getpid();
+ pid_t sid = getsid(0);
+ char *vcline = op->vcline;
+ char *line = op->tty;
+ struct utmpx *utp;
+
+ /*
+ * The utmp file holds miscellaneous information about things started by
+ * /sbin/init and other system-related events. Our purpose is to update
+ * the utmp entry for the current process, in particular the process type
+ * and the tty line we are listening to. Return successfully only if the
+ * utmp file can be opened for update, and if we are able to find our
+ * entry in the utmp file.
+ */
+ utmpxname(_PATH_UTMP);
+ setutxent();
+
+ /*
+ * Find my pid in utmp.
+ *
+ * FIXME: Earlier (when was that?) code here tested only utp->ut_type !=
+ * INIT_PROCESS, so maybe the >= here should be >.
+ *
+ * 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 = getutxent()))
+ if (utp->ut_pid == pid
+ && utp->ut_type >= INIT_PROCESS
+ && utp->ut_type <= DEAD_PROCESS)
+ break;
+
+ if (utp) {
+ memcpy(&ut, utp, sizeof(ut));
+ } else {
+ /* Some inits do not initialize utmp. */
+ memset(&ut, 0, sizeof(ut));
+ if (vcline && *vcline)
+ /* Standard virtual console devices */
+ str2memcpy(ut.ut_id, vcline, sizeof(ut.ut_id));
+ else {
+ size_t len = strlen(line);
+ char * ptr;
+ if (len >= sizeof(ut.ut_id))
+ ptr = line + len - sizeof(ut.ut_id);
+ else
+ ptr = line;
+ str2memcpy(ut.ut_id, ptr, sizeof(ut.ut_id));
+ }
+ }
+
+ str2memcpy(ut.ut_user, "LOGIN", sizeof(ut.ut_user));
+ str2memcpy(ut.ut_line, line, sizeof(ut.ut_line));
+ if (fakehost)
+ str2memcpy(ut.ut_host, fakehost, sizeof(ut.ut_host));
+ time(&t);
+ ut.ut_tv.tv_sec = t;
+ ut.ut_type = LOGIN_PROCESS;
+ ut.ut_pid = pid;
+ ut.ut_session = sid;
+
+ pututxline(&ut);
+ endutxent();
+
+ updwtmpx(_PATH_WTMP, &ut);
+}
+
+#endif /* SYSV_STYLE */
+
+/* Set up tty as stdin, stdout & stderr. */
+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. */
+
+ if (strcmp(tty, "-") != 0) {
+ char buf[PATH_MAX+1];
+ struct group *gr = NULL;
+ struct stat st;
+ int fd, len;
+ pid_t tid;
+ gid_t gid = 0;
+
+ /* Use tty group if available */
+ if ((gr = getgrnam("tty")))
+ gid = gr->gr_gid;
+
+ 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);
+
+ /*
+ * There is always a race between this reset and the call to
+ * vhangup() that s.o. can use to get access to your tty.
+ * Linux login(1) will change tty permissions. Use root owner and group
+ * with permission -rw------- for the period between getty and login.
+ */
+ if (fchown(fd, 0, gid) || fchmod(fd, (gid ? 0620 : 0600))) {
+ if (errno == EROFS)
+ log_warn("%s: %m", buf);
+ else
+ log_err("%s: %m", buf);
+ }
+
+ /* Sanity checks... */
+ 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)
+ log_warn(_("/dev/%s: cannot get controlling tty: %m"), tty);
+ }
+
+ close(STDIN_FILENO);
+ errno = 0;
+
+ if (op->flags & F_HANGUP) {
+
+ if (ioctl(fd, TIOCNOTTY))
+ debug("TIOCNOTTY ioctl failed\n");
+
+ /*
+ * Let's close all file descriptors before vhangup
+ * https://lkml.org/lkml/2012/6/5/145
+ */
+ close(fd);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+ errno = 0;
+ closed = 1;
+
+ if (vhangup())
+ log_err(_("/dev/%s: vhangup() failed: %m"), tty);
+ } else
+ close(fd);
+
+ debug("open(2)\n");
+ if (open(buf, O_RDWR|O_NOCTTY|O_NONBLOCK, 0) != 0)
+ log_err(_("/dev/%s: cannot open as standard input: %m"), tty);
+
+ if (((tid = tcgetsid(STDIN_FILENO)) < 0) || (pid != tid)) {
+ if (ioctl(STDIN_FILENO, TIOCSCTTY, 1) == -1)
+ log_warn(_("/dev/%s: cannot get controlling tty: %m"), tty);
+ }
+
+ } else {
+
+ /*
+ * Standard input should already be connected to an open port. Make
+ * sure it is open for read/write.
+ */
+
+ if ((fcntl(STDIN_FILENO, F_GETFL, 0) & O_RDWR) != O_RDWR)
+ log_err(_("%s: not open for read/write"), tty);
+
+ }
+
+ if (tcsetpgrp(STDIN_FILENO, pid))
+ log_warn(_("/dev/%s: cannot set process group: %m"), tty);
+
+ /* Get rid of the present outputs. */
+ if (!closed) {
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+ errno = 0;
+ }
+
+ /* Set up standard output and standard error file descriptors. */
+ debug("duping\n");
+
+ /* set up stdout and stderr */
+ if (dup(STDIN_FILENO) != 1 || dup(STDIN_FILENO) != 2)
+ log_err(_("%s: dup problem: %m"), tty);
+
+ /* make stdio unbuffered for slow modem lines */
+ setvbuf(stdout, NULL, _IONBF, 0);
+
+ /*
+ * The following ioctl will fail if stdin is not a tty, but also when
+ * there is noise on the modem control lines. In the latter case, the
+ * common course of action is (1) fix your cables (2) give the modem
+ * more time to properly reset after hanging up.
+ *
+ * SunOS users can achieve (2) by patching the SunOS kernel variable
+ * "zsadtrlow" to a larger value; 5 seconds seems to be a good value.
+ * http://www.sunmanagers.org/archives/1993/0574.html
+ */
+ memset(tp, 0, sizeof(struct termios));
+ 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.
+ */
+#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;
+ }
+
+ 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. */
+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)
+ switch(op->kbmode) {
+ case K_UNICODE:
+ setlocale(LC_CTYPE, "C.UTF-8");
+ op->flags |= F_UTF8;
+ break;
+ case K_RAW:
+ case K_MEDIUMRAW:
+ case K_XLATE:
+ default:
+ setlocale(LC_CTYPE, "POSIX");
+ op->flags &= ~F_UTF8;
+ break;
+ }
+#else
+ setlocale(LC_CTYPE, "POSIX");
+ op->flags &= ~F_UTF8;
+#endif
+ reset_vc(op, tp, 0);
+
+ if ((tp->c_cflag & (CS8|PARODD|PARENB)) == CS8)
+ op->flags |= F_EIGHTBITS;
+
+ if ((op->flags & F_NOCLEAR) == 0)
+ termio_clear(STDOUT_FILENO);
+ return;
+ }
+
+ /*
+ * Serial line
+ */
+
+ if (op->flags & F_KEEPSPEED || !op->numspeed) {
+ /* Save the original setting. */
+ ispeed = cfgetispeed(tp);
+ ospeed = cfgetospeed(tp);
+
+ /* Save also the original speed to array of the speeds to make
+ * it possible to return the original after unexpected BREAKs.
+ */
+ if (op->numspeed)
+ op->speeds[op->numspeed++] = ispeed ? ispeed :
+ ospeed ? ospeed :
+ TTYDEF_SPEED;
+ if (!ispeed)
+ ispeed = TTYDEF_SPEED;
+ if (!ospeed)
+ ospeed = TTYDEF_SPEED;
+ } else {
+ ospeed = ispeed = op->speeds[FIRST_SPEED];
+ }
+
+ /*
+ * Initial termios settings: 8-bit characters, raw-mode, blocking i/o.
+ * Special characters are set after we have read the login name; all
+ * reads will be done in raw mode anyway. Errors will be dealt with
+ * later on.
+ */
+
+ /* The default 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
+ * at all. The best what we can do in this case is to keep c_iflag
+ * unmodified for --autolog.
+ */
+ if (!op->autolog) {
+#ifdef IUTF8
+ tp->c_iflag = tp->c_iflag & IUTF8;
+ if (tp->c_iflag & IUTF8)
+ op->flags |= F_UTF8;
+#else
+ tp->c_iflag = 0;
+#endif
+ }
+
+ tp->c_lflag = 0;
+ tp->c_oflag &= OPOST | ONLCR;
+
+ if ((op->flags & F_KEEPCFLAGS) == 0)
+ tp->c_cflag = CS8 | HUPCL | CREAD | (tp->c_cflag & CLOCAL);
+
+ /*
+ * Note that the speed is stored in the c_cflag termios field, so we have
+ * set the speed always when the cflag is reset.
+ */
+ cfsetispeed(tp, ispeed);
+ cfsetospeed(tp, ospeed);
+
+ /* The default is to follow setting from kernel, but it's possible
+ * to explicitly remove/add CLOCAL flag by -L[=<mode>]*/
+ switch (op->clocal) {
+ case CLOCAL_MODE_ALWAYS:
+ tp->c_cflag |= CLOCAL; /* -L or -L=always */
+ break;
+ case CLOCAL_MODE_NEVER:
+ tp->c_cflag &= ~CLOCAL; /* -L=never */
+ break;
+ case CLOCAL_MODE_AUTO: /* -L=auto */
+ break;
+ }
+
+#ifdef HAVE_STRUCT_TERMIOS_C_LINE
+ tp->c_line = 0;
+#endif
+ tp->c_cc[VMIN] = 1;
+ tp->c_cc[VTIME] = 0;
+
+ /* Check for terminal size and if not found set default */
+ if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == 0) {
+ if (ws.ws_row == 0)
+ ws.ws_row = 24;
+ if (ws.ws_col == 0)
+ ws.ws_col = 80;
+ if (ioctl(STDIN_FILENO, TIOCSWINSZ, &ws))
+ debug("TIOCSWINSZ ioctl failed\n");
+ }
+
+ /* Optionally enable hardware flow control. */
+#ifdef CRTSCTS
+ if (op->flags & F_RTSCTS)
+ tp->c_cflag |= CRTSCTS;
+#endif
+ /* Flush input and output queues, important for modems! */
+ tcflush(STDIN_FILENO, TCIOFLUSH);
+
+ 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,
+ fcntl(STDIN_FILENO, F_GETFL, 0) & ~O_NONBLOCK);
+
+ debug("term_io 2\n");
+}
+
+/* Reset virtual console on stdin to its defaults */
+static void reset_vc(const struct options *op, struct termios *tp, int canon)
+{
+ int fl = 0;
+
+ fl |= (op->flags & F_KEEPCFLAGS) == 0 ? 0 : UL_TTY_KEEPCFLAGS;
+ fl |= (op->flags & F_UTF8) == 0 ? 0 : UL_TTY_UTF8;
+
+ reset_virtual_console(tp, fl);
+
+#ifdef AGETTY_RELOAD
+ /*
+ * Discard all the flags that makes the line go canonical with echoing.
+ * We need to know when the user starts typing.
+ */
+ if (canon == 0)
+ tp->c_lflag = 0;
+#endif
+
+ 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. */
+static void auto_baud(struct termios *tp)
+{
+ speed_t speed;
+ int vmin;
+ unsigned iflag;
+ char buf[BUFSIZ];
+ char *bp;
+ int nread;
+
+ /*
+ * This works only if the modem produces its status code AFTER raising
+ * the DCD line, and if the computer is fast enough to set the proper
+ * baud rate before the message has gone by. We expect a message of the
+ * following format:
+ *
+ * <junk><number><junk>
+ *
+ * The number is interpreted as the baud rate of the incoming call. If the
+ * modem does not tell us the baud rate within one second, we will keep
+ * using the current baud rate. It is advisable to enable BREAK
+ * processing (comma-separated list of baud rates) if the processing of
+ * modem status messages is enabled.
+ */
+
+ /*
+ * Use 7-bit characters, don't block if input queue is empty. Errors will
+ * be dealt with later on.
+ */
+ iflag = tp->c_iflag;
+ /* Enable 8th-bit stripping. */
+ tp->c_iflag |= ISTRIP;
+ vmin = tp->c_cc[VMIN];
+ /* Do not block when queue is empty. */
+ tp->c_cc[VMIN] = 0;
+ tcsetattr(STDIN_FILENO, TCSANOW, tp);
+
+ /*
+ * Wait for a while, then read everything the modem has said so far and
+ * try to extract the speed of the dial-in call.
+ */
+ sleep(1);
+ if ((nread = read(STDIN_FILENO, buf, sizeof(buf) - 1)) > 0) {
+ buf[nread] = '\0';
+ for (bp = buf; bp < buf + nread; bp++)
+ if (c_isascii(*bp) && isdigit(*bp)) {
+ if ((speed = bcode(bp))) {
+ cfsetispeed(tp, speed);
+ cfsetospeed(tp, speed);
+ }
+ break;
+ }
+ }
+
+ /* Restore terminal settings. Errors will be dealt with later on. */
+ tp->c_iflag = iflag;
+ tp->c_cc[VMIN] = vmin;
+ tcsetattr(STDIN_FILENO, TCSANOW, tp);
+}
+
+static char *xgethostname(void)
+{
+ char *name;
+ size_t sz = get_hostname_max() + 1;
+
+ name = malloc(sizeof(char) * sz);
+ if (!name)
+ log_err(_("failed to allocate memory: %m"));
+
+ if (gethostname(name, sz) != 0) {
+ free(name);
+ return NULL;
+ }
+ name[sz - 1] = '\0';
+ return name;
+}
+
+static char *xgetdomainname(void)
+{
+#ifdef HAVE_GETDOMAINNAME
+ char *name;
+ const size_t sz = get_hostname_max() + 1;
+
+ name = malloc(sizeof(char) * sz);
+ if (!name)
+ log_err(_("failed to allocate memory: %m"));
+
+ if (getdomainname(name, sz) != 0) {
+ free(name);
+ return NULL;
+ }
+ name[sz - 1] = '\0';
+ return name;
+#else
+ return NULL;
+#endif
+}
+
+
+static char *read_os_release(struct options *op, const char *varname)
+{
+ int fd = -1;
+ struct stat st;
+ size_t varsz = strlen(varname);
+ char *p, *buf = NULL, *ret = NULL;
+
+ /* read the file only once */
+ if (!op->osrelease) {
+ fd = open(_PATH_OS_RELEASE_ETC, O_RDONLY);
+ if (fd == -1) {
+ 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)
+ goto done;
+
+ op->osrelease = malloc(st.st_size + 1);
+ if (!op->osrelease)
+ log_err(_("failed to allocate memory: %m"));
+ if (read_all(fd, op->osrelease, st.st_size) != (ssize_t) st.st_size) {
+ free(op->osrelease);
+ op->osrelease = NULL;
+ goto done;
+ }
+ op->osrelease[st.st_size] = 0;
+ }
+ buf = strdup(op->osrelease);
+ if (!buf)
+ log_err(_("failed to allocate memory: %m"));
+ p = buf;
+
+ for (;;) {
+ char *eol, *eon;
+
+ p += strspn(p, "\n\r");
+ p += strspn(p, " \t\n\r");
+ if (!*p)
+ break;
+ if (strspn(p, "#;\n") != 0) {
+ p += strcspn(p, "\n\r");
+ continue;
+ }
+ if (strncmp(p, varname, varsz) != 0) {
+ p += strcspn(p, "\n\r");
+ continue;
+ }
+ p += varsz;
+ p += strspn(p, " \t\n\r");
+
+ if (*p != '=')
+ continue;
+
+ p += strspn(p, " \t\n\r=\"");
+ eol = p + strcspn(p, "\n\r");
+ *eol = '\0';
+ eon = eol-1;
+ while (eon > p) {
+ if (*eon == '\t' || *eon == ' ') {
+ eon--;
+ continue;
+ }
+ if (*eon == '"') {
+ *eon = '\0';
+ break;
+ }
+ break;
+ }
+ free(ret);
+ ret = strdup(p);
+ if (!ret)
+ log_err(_("failed to allocate memory: %m"));
+ p = eol + 1;
+ }
+done:
+ free(buf);
+ if (fd >= 0)
+ close(fd);
+ return ret;
+}
+
+#ifdef AGETTY_RELOAD
+static void open_netlink(void)
+{
+ 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 = netlink_groups;
+ if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0)
+ close(sock);
+ else
+ netlink_fd = sock;
+ }
+}
+
+static int process_netlink_msg(int *triggered)
+{
+ 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;
+ }
+
+ *triggered = 1;
+ break;
+ }
+
+ return 1;
+}
+
+static int process_netlink(void)
+{
+ int triggered = 0;
+ while (process_netlink_msg(&triggered));
+ return triggered;
+}
+
+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;
+
+ }
+
+ 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) != 0)
+ return 0;
+
+ /* Accept this */
+ return 1;
+}
+
+
+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)
+{
+ int dd, nfiles, i;
+ struct dirent **namelist = NULL;
+
+ dd = open(dirname, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
+ if (dd < 0)
+ return 1;
+
+ 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) {
+ issuefile_read_stream(ie, f, op, tp);
+ fclose(f);
+ }
+ }
+
+ 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__)))
+{
+ return 1;
+}
+#endif /* ISSUEDIR_SUPPORT */
+
+#ifndef ISSUE_SUPPORT
+static void print_issue_file(struct issue *ie __attribute__((__unused__)),
+ 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);
+ }
+}
+
+static void eval_issue_file(struct issue *ie __attribute__((__unused__)),
+ struct options *op __attribute__((__unused__)),
+ struct termios *tp __attribute__((__unused__)))
+{
+}
+
+static void show_issue(struct options *op __attribute__((__unused__)))
+{
+}
+
+#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) {
+ free(ie->mem);
+ ie->mem_sz = 0;
+ ie->mem = NULL;
+ 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);
+ }
+
+ 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)
+{
+ if (ie->mem_old && ie->mem
+ && strcmp(ie->mem_old, ie->mem) == 0) {
+ free(ie->mem_old);
+ ie->mem_old = ie->mem;
+ ie->mem = NULL;
+ return 0;
+ }
+
+ return 1;
+}
+#endif
+
+static void print_issue_file(struct issue *ie,
+ struct options *op,
+ struct termios *tp)
+{
+ int oflag = tp->c_oflag; /* Save current setting. */
+
+ if ((op->flags & F_NONL) == 0) {
+ /* Issue not in use, start with a new line. */
+ write_all(STDOUT_FILENO, "\r\n", 2);
+ }
+
+ if (ie->do_tcsetattr) {
+ if ((op->flags & F_VCONSOLE) == 0) {
+ /* Map new line in output to carriage return & new line. */
+ tp->c_oflag |= (ONLCR | OPOST);
+ tcsetattr(STDIN_FILENO, TCSADRAIN, tp);
+ }
+ }
+
+ if (ie->mem_sz)
+ write_all(STDOUT_FILENO, ie->mem, ie->mem_sz);
+
+ if (ie->do_tcrestore) {
+ /* Restore settings. */
+ tp->c_oflag = oflag;
+ /* Wait till output is gone. */
+ tcsetattr(STDIN_FILENO, TCSADRAIN, tp);
+ }
+
+#ifdef AGETTY_RELOAD
+ free(ie->mem_old);
+ ie->mem_old = ie->mem;
+ ie->mem = NULL;
+ ie->mem_sz = 0;
+#else
+ free(ie->mem);
+ ie->mem = NULL;
+ ie->mem_sz = 0;
+#endif
+}
+
+static void eval_issue_file(struct issue *ie,
+ struct options *op,
+ struct termios *tp)
+{
+ int has_file = 0;
+
+#ifdef AGETTY_RELOAD
+ netlink_groups = 0;
+#endif
+ if (!(op->flags & F_ISSUE))
+ goto done;
+ /*
+ * The custom issue file or directory list specified by:
+ * agetty --isue-file <path[:path]...>
+ * Note that nothing is printed if the file/dir does not exist.
+ */
+ if (op->issue) {
+ char *list = strdup(op->issue);
+ char *file;
+
+ if (!list)
+ log_err(_("failed to allocate memory: %m"));
+
+ for (file = strtok(list, ":"); file; file = strtok(NULL, ":")) {
+ struct stat st;
+
+ if (stat(file, &st) < 0)
+ continue;
+ if (S_ISDIR(st.st_mode))
+ issuedir_read(ie, file, op, tp);
+ else
+ issuefile_read(ie, file, op, tp);
+ }
+ free(list);
+ goto done;
+ }
+
+ /* 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;
+ }
+
+ /* 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);
+
+done:
+
+#ifdef AGETTY_RELOAD
+ if (netlink_groups != 0)
+ open_netlink();
+#endif
+ if (ie->output) {
+ fclose(ie->output);
+ ie->output = NULL;
+ }
+}
+
+/* This is --show-issue backend, executed by normal user on the current
+ * terminal.
+ */
+static void show_issue(struct options *op)
+{
+ struct issue ie = { .output = NULL };
+ struct termios tp;
+
+ memset(&tp, 0, sizeof(struct termios));
+ if (tcgetattr(STDIN_FILENO, &tp) < 0)
+ err(EXIT_FAILURE, _("failed to get terminal attributes: %m"));
+
+ eval_issue_file(&ie, op, &tp);
+
+ if (ie.mem_sz)
+ write_all(STDOUT_FILENO, ie.mem, ie.mem_sz);
+ if (ie.output)
+ fclose(ie.output);
+ free(ie.mem);
+}
+
+#endif /* ISSUE_SUPPORT */
+
+/* Show login prompt, optionally preceded by /etc/issue contents. */
+static void do_prompt(struct issue *ie, struct options *op, struct termios *tp)
+{
+#ifdef AGETTY_RELOAD
+again:
+#endif
+ print_issue_file(ie, op, tp);
+
+ if (op->flags & F_LOGINPAUSE) {
+ puts(_("[press ENTER to login]"));
+#ifdef AGETTY_RELOAD
+ /* reload issue */
+ if (!wait_for_term_input(STDIN_FILENO)) {
+ eval_issue_file(ie, op, tp);
+ if (issue_is_changed(ie)) {
+ if (op->flags & F_VCONSOLE)
+ termio_clear(STDOUT_FILENO);
+ goto again;
+ }
+ }
+#endif
+ getc(stdin);
+ }
+#ifdef KDGKBLED
+ if (!(op->flags & F_NOHINTS) && !op->autolog &&
+ (op->flags & F_VCONSOLE)) {
+ int kb = 0;
+
+ if (ioctl(STDIN_FILENO, KDGKBLED, &kb) == 0) {
+ char hint[256] = { '\0' };
+ int nl = 0;
+
+ if (access(_PATH_NUMLOCK_ON, F_OK) == 0)
+ nl = 1;
+
+ if (nl && (kb & 0x02) == 0)
+ append(hint, sizeof(hint), NULL, _("Num Lock off"));
+
+ else if (nl == 0 && (kb & 2) && (kb & 0x20) == 0)
+ append(hint, sizeof(hint), NULL, _("Num Lock on"));
+
+ if ((kb & 0x04) && (kb & 0x40) == 0)
+ append(hint, sizeof(hint), ", ", _("Caps Lock on"));
+
+ if ((kb & 0x01) && (kb & 0x10) == 0)
+ append(hint, sizeof(hint), ", ", _("Scroll Lock on"));
+
+ if (*hint)
+ printf(_("Hint: %s\n\n"), hint);
+ }
+ }
+#endif /* KDGKBLED */
+ if ((op->flags & F_NOHOSTNAME) == 0) {
+ char *hn = xgethostname();
+
+ if (hn) {
+ char *dot = strchr(hn, '.');
+ char *cn = hn;
+ struct addrinfo *res = NULL;
+
+ if ((op->flags & F_LONGHNAME) == 0) {
+ if (dot)
+ *dot = '\0';
+
+ } else if (dot == NULL) {
+ struct addrinfo hints;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_CANONNAME;
+
+ if (!getaddrinfo(hn, NULL, &hints, &res)
+ && res && res->ai_canonname)
+ cn = res->ai_canonname;
+ }
+
+ write_all(STDOUT_FILENO, cn, strlen(cn));
+ write_all(STDOUT_FILENO, " ", 1);
+
+ if (res)
+ freeaddrinfo(res);
+ free(hn);
+ }
+ }
+ if (!op->autolog) {
+ /* Always show login prompt. */
+ write_all(STDOUT_FILENO, LOGIN, sizeof(LOGIN) - 1);
+ }
+}
+
+/* Select next baud rate. */
+static void next_speed(struct options *op, struct termios *tp)
+{
+ static int baud_index = -1;
+
+ if (baud_index == -1)
+ /*
+ * If the F_KEEPSPEED flags is set then the FIRST_SPEED is not
+ * tested yet (see termio_init()).
+ */
+ baud_index =
+ (op->flags & F_KEEPSPEED) ? FIRST_SPEED : 1 % op->numspeed;
+ else
+ baud_index = (baud_index + 1) % op->numspeed;
+
+ cfsetispeed(tp, op->speeds[baud_index]);
+ cfsetospeed(tp, op->speeds[baud_index]);
+ tcsetattr(STDIN_FILENO, TCSANOW, tp);
+}
+
+/* Get user name, establish parity, speed, erase, kill & eol. */
+static char *get_logname(struct issue *ie, struct options *op, struct termios *tp, struct chardata *cp)
+{
+ static char logname[BUFSIZ];
+ char *bp;
+ char c; /* input character, full eight bits */
+ char ascval; /* low 7 bits of input character */
+ int eightbit;
+ static char *erase[] = { /* backspace-space-backspace */
+ "\010\040\010", /* space parity */
+ "\010\040\010", /* odd parity */
+ "\210\240\210", /* even parity */
+ "\210\240\210", /* no parity */
+ };
+
+ /* Initialize kill, erase, parity etc. (also after switching speeds). */
+ INIT_CHARDATA(cp);
+
+ /*
+ * Flush pending input (especially important after parsing or switching
+ * the baud rate).
+ */
+ if ((op->flags & F_VCONSOLE) == 0)
+ sleep(1);
+ tcflush(STDIN_FILENO, TCIFLUSH);
+
+ eightbit = (op->flags & (F_EIGHTBITS|F_UTF8));
+ bp = logname;
+ *bp = '\0';
+
+ eval_issue_file(ie, op, tp);
+ while (*logname == '\0') {
+ /* Write issue file and prompt */
+ do_prompt(ie, op, tp);
+
+ no_reload:
+#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);
+ eval_issue_file(ie, op, tp);
+ if (!issue_is_changed(ie))
+ goto no_reload;
+ 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;
+
+ 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... */
+ if (errno == EINTR || errno == EAGAIN) {
+ xusleep(250000);
+ continue;
+ }
+ switch (errno) {
+ case 0:
+ case EIO:
+ case ESRCH:
+ case EINVAL:
+ case ENOENT:
+ 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;
+ else if (c != (ascval = (c & 0177))) {
+ uint32_t bits; /* # of "1" bits per character */
+ uint32_t mask; /* mask with 1 bit up */
+ for (bits = 1, mask = 1; mask & 0177; mask <<= 1) {
+ if (mask & ascval)
+ bits++;
+ }
+ cp->parity |= ((bits & 1) ? 1 : 2);
+ }
+
+ if (op->killchars && strchr(op->killchars, ascval))
+ key = CTL('U');
+ else if (op->erasechars && strchr(op->erasechars, ascval))
+ key = DEL;
+ else
+ key = ascval;
+
+ /* Do erase, kill and end-of-line processing. */
+ switch (key) {
+ case 0:
+ *bp = 0;
+ if (op->numspeed > 1 && !(op->flags & F_VCONSOLE))
+ return NULL;
+ if (readres == 0)
+ exit_slowly(EXIT_SUCCESS);
+ break;
+ case CR:
+ case NL:
+ *bp = 0; /* terminate logname */
+ cp->eol = ascval; /* set end-of-line char */
+ break;
+ case BS:
+ case DEL:
+ cp->erase = ascval; /* set erase character */
+ if (bp > logname) {
+ if ((tp->c_lflag & ECHO) == 0)
+ write_all(1, erase[cp->parity], 3);
+ bp--;
+ }
+ break;
+ case CTL('U'):
+ cp->kill = ascval; /* set kill character */
+ while (bp > logname) {
+ if ((tp->c_lflag & ECHO) == 0)
+ write_all(1, erase[cp->parity], 3);
+ bp--;
+ }
+ break;
+ case CTL('D'):
+ exit(EXIT_SUCCESS);
+ case CTL('C'):
+ /* Ignore */
+ break;
+ default:
+ if ((size_t)(bp - logname) >= sizeof(logname) - 1)
+ log_err(_("%s: input overrun"), op->tty);
+ if ((tp->c_lflag & ECHO) == 0)
+ write_all(1, &c, 1); /* echo the character */
+ *bp++ = ascval; /* and store it */
+ break;
+ }
+ /* Everything was erased. */
+ if (bp == logname && cp->eol == '\0')
+ goto no_reload;
+ }
+ }
+
+#ifdef HAVE_WIDECHAR
+ if ((op->flags & (F_EIGHTBITS|F_UTF8)) == (F_EIGHTBITS|F_UTF8)) {
+ /* Check out UTF-8 multibyte characters */
+ ssize_t len;
+ wchar_t *wcs, *wcp;
+
+ len = mbstowcs((wchar_t *)0, logname, 0);
+ if (len < 0)
+ log_err(_("%s: invalid character conversion for login name"), op->tty);
+
+ wcs = malloc((len + 1) * sizeof(wchar_t));
+ if (!wcs)
+ log_err(_("failed to allocate memory: %m"));
+
+ len = mbstowcs(wcs, logname, len + 1);
+ if (len < 0)
+ log_err(_("%s: invalid character conversion for login name"), op->tty);
+
+ wcp = wcs;
+ while (*wcp) {
+ const wint_t wc = *wcp++;
+ if (!iswprint(wc))
+ log_err(_("%s: invalid character 0x%x in login name"), op->tty, wc);
+ }
+ free(wcs);
+ } else
+#endif
+ if ((op->flags & F_LCUC) && (cp->capslock = caps_lock(logname))) {
+
+ /* Handle names with upper case and no lower case. */
+ for (bp = logname; *bp; bp++)
+ if (isupper(*bp))
+ *bp = tolower(*bp); /* map name to lower case */
+ }
+
+ return logname;
+}
+
+/* Set the final tty mode bits. */
+static void termio_final(struct options *op, struct termios *tp, struct chardata *cp)
+{
+ /* General terminal-independent stuff. */
+
+ /* 2-way flow control */
+ tp->c_iflag |= IXON | IXOFF;
+ tp->c_lflag |= ICANON | ISIG | ECHO | ECHOE | ECHOK | ECHOKE;
+ /* no longer| ECHOCTL | ECHOPRT */
+ tp->c_oflag |= OPOST;
+ /* tp->c_cflag = 0; */
+ tp->c_cc[VINTR] = DEF_INTR;
+ tp->c_cc[VQUIT] = DEF_QUIT;
+ tp->c_cc[VEOF] = DEF_EOF;
+ tp->c_cc[VEOL] = DEF_EOL;
+#ifdef __linux__
+ tp->c_cc[VSWTC] = DEF_SWITCH;
+#elif defined(VSWTCH)
+ tp->c_cc[VSWTCH] = DEF_SWITCH;
+#endif /* __linux__ */
+
+ /* Account for special characters seen in input. */
+ if (cp->eol == CR) {
+ tp->c_iflag |= ICRNL;
+ tp->c_oflag |= ONLCR;
+ }
+ tp->c_cc[VERASE] = cp->erase;
+ tp->c_cc[VKILL] = cp->kill;
+
+ /* Account for the presence or absence of parity bits in input. */
+ switch (cp->parity) {
+ case 0:
+ /* space (always 0) parity */
+ break;
+ case 1:
+ /* odd parity */
+ tp->c_cflag |= PARODD;
+ /* fallthrough */
+ case 2:
+ /* even parity */
+ tp->c_cflag |= PARENB;
+ tp->c_iflag |= INPCK | ISTRIP;
+ /* fallthrough */
+ case (1 | 2):
+ /* no parity bit */
+ tp->c_cflag &= ~CSIZE;
+ tp->c_cflag |= CS7;
+ break;
+ }
+ /* Account for upper case without lower case. */
+ if (cp->capslock) {
+#ifdef IUCLC
+ tp->c_iflag |= IUCLC;
+#endif
+#ifdef XCASE
+ tp->c_lflag |= XCASE;
+#endif
+#ifdef OLCUC
+ tp->c_oflag |= OLCUC;
+#endif
+ }
+ /* Optionally enable hardware flow control. */
+#ifdef CRTSCTS
+ if (op->flags & F_RTSCTS)
+ tp->c_cflag |= CRTSCTS;
+#endif
+
+ /* Finally, make the new settings effective. */
+ if (tcsetattr(STDIN_FILENO, TCSANOW, tp) < 0)
+ log_err(_("%s: failed to set terminal attributes: %m"), op->tty);
+}
+
+/*
+ * String contains upper case without lower case.
+ * http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=52940
+ * http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=156242
+ */
+static int caps_lock(char *s)
+{
+ int capslock;
+
+ for (capslock = 0; *s; s++) {
+ if (islower(*s))
+ return EXIT_SUCCESS;
+ if (capslock == 0)
+ capslock = isupper(*s);
+ }
+ return capslock;
+}
+
+/* Convert speed string to speed code; return 0 on failure. */
+static speed_t bcode(char *s)
+{
+ const struct Speedtab *sp;
+ long speed = atol(s);
+
+ for (sp = speedtab; sp->speed; sp++)
+ if (sp->speed == speed)
+ return sp->code;
+ return 0;
+}
+
+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);
+
+ 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);
+ fputs(_(" -c, --noreset do not reset control mode\n"), out);
+ fputs(_(" -E, --remote use -r <hostname> for login(1)\n"), out);
+ fputs(_(" -f, --issue-file <list> display issue files or directories\n"), out);
+ fputs(_(" --show-issue display issue file and exit\n"), out);
+ fputs(_(" -h, --flow-control enable hardware flow control\n"), 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>] 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);
+ fputs(_(" -R, --hangup do virtually hangup on the tty\n"), out);
+ fputs(_(" -s, --keep-baud try to keep baud rate after break\n"), 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(_(" --nohints do not print hints\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(_(" --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;
+
+ for (sp = speedtab; sp->speed; sp++)
+ printf("%10ld\n", sp->speed);
+}
+
+/*
+ * Helper function reports errors to console or syslog.
+ * Will be used by log_err() and log_warn() therefore
+ * it takes a format as well as va_list.
+ */
+static void dolog(int priority
+#ifndef USE_SYSLOG
+ __attribute__((__unused__))
+#endif
+ , const char *fmt, va_list ap)
+{
+#ifdef USE_SYSLOG
+ /*
+ * If the diagnostic is reported via syslog(3), the process name is
+ * automatically prepended to the message. If we write directly to
+ * /dev/console, we must prepend the process name ourselves.
+ */
+ openlog(program_invocation_short_name, LOG_PID, LOG_AUTHPRIV);
+ vsyslog(priority, fmt, ap);
+ closelog();
+#else
+ /*
+ * Write the diagnostic directly to /dev/console if we do not use
+ * the syslog(3) facility.
+ */
+ char buf[BUFSIZ];
+ char new_fmt[BUFSIZ];
+ int fd;
+
+ snprintf(new_fmt, sizeof(new_fmt), "%s: %s\r\n",
+ program_invocation_short_name, fmt);
+ /* Terminate with CR-LF since the console mode is unknown. */
+ vsnprintf(buf, sizeof(buf), new_fmt, ap);
+
+ if ((fd = open("/dev/console", 1)) >= 0) {
+ write_all(fd, buf, strlen(buf));
+ close(fd);
+ }
+#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;
+
+ va_start(ap, fmt);
+ dolog(LOG_ERR, fmt, ap);
+ va_end(ap);
+
+ exit_slowly(EXIT_FAILURE);
+}
+
+static void log_warn(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ dolog(LOG_WARNING, fmt, ap);
+ va_end(ap);
+}
+
+static void print_addr(struct issue *ie, sa_family_t family, void *addr)
+{
+ char buff[INET6_ADDRSTRLEN + 1];
+
+ inet_ntop(family, addr, buff, sizeof(buff));
+ fprintf(ie->output, "%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 issue *ie,
+ struct ifaddrs *addrs,
+ const char *iface,
+ sa_family_t family)
+{
+ struct ifaddrs *p;
+ struct addrinfo hints, *info = NULL;
+ char *host = NULL;
+ void *addr = NULL;
+
+ if (!addrs)
+ return;
+
+ for (p = addrs; p; p = p->ifa_next) {
+
+ 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;
+ }
+
+ addr = NULL;
+ switch (p->ifa_addr->sa_family) {
+ case AF_INET:
+ addr = &((struct sockaddr_in *) p->ifa_addr)->sin_addr;
+ break;
+ case AF_INET6:
+ addr = &((struct sockaddr_in6 *) p->ifa_addr)->sin6_addr;
+ break;
+ }
+
+ if (addr) {
+ print_addr(ie, family, addr);
+ return;
+ }
+ }
+
+ 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)
+ hints.ai_flags = AI_V4MAPPED;
+
+ host = xgethostname();
+ if (host && getaddrinfo(host, NULL, &hints, &info) == 0 && info) {
+ switch (info->ai_family) {
+ case AF_INET:
+ addr = &((struct sockaddr_in *) info->ai_addr)->sin_addr;
+ break;
+ case AF_INET6:
+ addr = &((struct sockaddr_in6 *) info->ai_addr)->sin6_addr;
+ break;
+ }
+ if (addr)
+ print_addr(ie, family, addr);
+
+ freeaddrinfo(info);
+ }
+ free(host);
+}
+
+/*
+ * parses \x{argument}, if not argument specified then returns NULL, the @fd
+ * has to point to one char after the sequence (it means '{').
+ */
+static char *get_escape_argument(FILE *fd, char *buf, size_t bufsz)
+{
+ size_t i = 0;
+ int c = fgetc(fd);
+
+ if (c == EOF || (unsigned char) c != '{') {
+ ungetc(c, fd);
+ return NULL;
+ }
+
+ do {
+ c = fgetc(fd);
+ if (c == EOF)
+ return NULL;
+ if ((unsigned char) c != '}' && i < bufsz - 1)
+ buf[i++] = (unsigned char) c;
+
+ } while ((unsigned char) c != '}');
+
+ buf[i] = '\0';
+ return buf;
+}
+
+static void output_special_char(struct issue *ie,
+ unsigned char c,
+ struct options *op,
+ struct termios *tp,
+ FILE *fp)
+{
+ struct utsname uts;
+
+ switch (c) {
+ case 'e':
+ {
+ char escname[UL_COLORNAME_MAXSZ];
+
+ if (get_escape_argument(fp, escname, sizeof(escname))) {
+ const char *esc = color_sequence_from_colorname(escname);
+ if (esc)
+ fputs(esc, ie->output);
+ } else
+ fputs("\033", ie->output);
+ break;
+ }
+ case 's':
+ uname(&uts);
+ fprintf(ie->output, "%s", uts.sysname);
+ break;
+ case 'n':
+ uname(&uts);
+ fprintf(ie->output, "%s", uts.nodename);
+ break;
+ case 'r':
+ uname(&uts);
+ fprintf(ie->output, "%s", uts.release);
+ break;
+ case 'v':
+ uname(&uts);
+ fprintf(ie->output, "%s", uts.version);
+ break;
+ case 'm':
+ uname(&uts);
+ fprintf(ie->output, "%s", uts.machine);
+ break;
+ case 'o':
+ {
+ char *dom = xgetdomainname();
+
+ fputs(dom ? dom : "unknown_domain", ie->output);
+ free(dom);
+ break;
+ }
+ case 'O':
+ {
+ char *dom = NULL;
+ char *host = xgethostname();
+ struct addrinfo hints, *info = NULL;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_CANONNAME;
+
+ if (host && getaddrinfo(host, NULL, &hints, &info) == 0 && info) {
+ char *canon;
+
+ if (info->ai_canonname &&
+ (canon = strchr(info->ai_canonname, '.')))
+ dom = canon + 1;
+ }
+ fputs(dom ? dom : "unknown_domain", ie->output);
+ if (info)
+ freeaddrinfo(info);
+ free(host);
+ break;
+ }
+ case 'd':
+ case 't':
+ {
+ time_t now;
+ struct tm tm;
+
+ time(&now);
+ localtime_r(&now, &tm);
+
+ if (c == 'd') /* ISO 8601 */
+ fprintf(ie->output, "%s %s %d %d",
+ nl_langinfo(ABDAY_1 + tm.tm_wday),
+ nl_langinfo(ABMON_1 + tm.tm_mon),
+ tm.tm_mday,
+ tm.tm_year < 70 ? tm.tm_year + 2000 :
+ tm.tm_year + 1900);
+ else
+ fprintf(ie->output, "%02d:%02d:%02d",
+ tm.tm_hour, tm.tm_min, tm.tm_sec);
+ break;
+ }
+ case 'l':
+ fprintf (ie->output, "%s", op->tty);
+ break;
+ case 'b':
+ {
+ const speed_t speed = cfgetispeed(tp);
+ int i;
+
+ for (i = 0; speedtab[i].speed; i++) {
+ if (speedtab[i].code == speed) {
+ fprintf(ie->output, "%ld", speedtab[i].speed);
+ break;
+ }
+ }
+ break;
+ }
+ case 'S':
+ {
+ char *var = NULL, varname[64];
+
+ /* \S{varname} */
+ if (get_escape_argument(fp, varname, sizeof(varname))) {
+ var = read_os_release(op, varname);
+ if (var) {
+ if (strcmp(varname, "ANSI_COLOR") == 0)
+ fprintf(ie->output, "\033[%sm", var);
+ else
+ fputs(var, ie->output);
+ }
+ /* \S */
+ } else if ((var = read_os_release(op, "PRETTY_NAME"))) {
+ fputs(var, ie->output);
+
+ /* \S and PRETTY_NAME not found */
+ } else {
+ uname(&uts);
+ fputs(uts.sysname, ie->output);
+ }
+
+ free(var);
+
+ break;
+ }
+ case 'u':
+ case 'U':
+ {
+ int users = 0;
+ struct utmpx *ut;
+ setutxent();
+ while ((ut = getutxent()))
+ if (ut->ut_type == USER_PROCESS)
+ users++;
+ endutxent();
+ if (c == 'U')
+ fprintf(ie->output, P_("%d user", "%d users", users), users);
+ else
+ fprintf (ie->output, "%d ", users);
+ break;
+ }
+#if defined(RTMGRP_IPV4_IFADDR) && defined(RTMGRP_IPV6_IFADDR)
+ case '4':
+ case '6':
+ {
+ sa_family_t family = c == '4' ? AF_INET : AF_INET6;
+ struct ifaddrs *addrs = NULL;
+ char iface[128];
+
+ if (getifaddrs(&addrs))
+ break;
+
+ if (get_escape_argument(fp, iface, sizeof(iface)))
+ output_iface_ip(ie, addrs, iface, family);
+ else
+ output_iface_ip(ie, addrs, NULL, family);
+
+ freeifaddrs(addrs);
+
+ if (c == '4')
+ netlink_groups |= RTMGRP_IPV4_IFADDR;
+ else
+ netlink_groups |= RTMGRP_IPV6_IFADDR;
+ break;
+ }
+#endif
+ default:
+ putc(c, ie->output);
+ break;
+ }
+}
+
+static void init_special_char(char* arg, struct options *op)
+{
+ char ch, *p, *q;
+ int i;
+
+ op->initstring = malloc(strlen(arg) + 1);
+ if (!op->initstring)
+ log_err(_("failed to allocate memory: %m"));
+
+ /*
+ * Copy optarg into op->initstring decoding \ddd octal
+ * codes into chars.
+ */
+ q = op->initstring;
+ p = arg;
+ while (*p) {
+ /* The \\ is converted to \ */
+ if (*p == '\\') {
+ p++;
+ if (*p == '\\') {
+ ch = '\\';
+ p++;
+ } else {
+ /* Handle \000 - \177. */
+ ch = 0;
+ for (i = 1; i <= 3; i++) {
+ if (*p >= '0' && *p <= '7') {
+ ch <<= 3;
+ ch += *p - '0';
+ p++;
+ } else {
+ break;
+ }
+ }
+ }
+ *q++ = ch;
+ } else
+ *q++ = *p++;
+ }
+ *q = '\0';
+}
+
+/*
+ * 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.
+ */
+static ssize_t append(char *dest, size_t len, const char *sep, const char *src)
+{
+ size_t dsz = 0, ssz = 0, sz;
+ char *p;
+
+ if (!dest || !len || !src)
+ return -1;
+
+ if (*dest)
+ dsz = strlen(dest);
+ if (dsz && sep)
+ ssz = strlen(sep);
+ sz = strlen(src);
+
+ if (dsz + ssz + sz + 1 > len)
+ return -1;
+
+ p = dest + dsz;
+ if (ssz) {
+ memcpy(p, sep, ssz);
+ p += ssz;
+ }
+ memcpy(p, src, sz);
+ *(p + sz) = '\0';
+
+ return dsz + ssz + sz;
+}
+
+/*
+ * Do not allow the user to pass an option as a user name
+ * To be more safe: Use `--' to make sure the rest is
+ * interpreted as non-options by the program, if it supports it.
+ */
+static void check_username(const char* nm)
+{
+ const char *p = nm;
+ if (!nm)
+ goto err;
+ if (strlen(nm) > 42)
+ goto err;
+ while (isspace(*p))
+ p++;
+ if (*p == '-')
+ goto err;
+ return;
+err:
+ errno = EPERM;
+ 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
+}
diff --git a/term-utils/mesg.1 b/term-utils/mesg.1
new file mode 100644
index 0000000..0750971
--- /dev/null
+++ b/term-utils/mesg.1
@@ -0,0 +1,118 @@
+.\" Copyright (c) 1987, 1990, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)mesg.1 8.1 (Berkeley) 6/6/93
+.\"
+.TH MESG 1 "July 2014" "util-linux" "User Commands"
+.SH NAME
+mesg \- display (or do not display) messages from other users
+.SH SYNOPSIS
+.B mesg
+[option]
+.RB [ n | y ]
+.SH DESCRIPTION
+The
+.B mesg
+utility is invoked by a user to control write access others have to the
+terminal device associated with standard error output. If write access
+is allowed, then programs such as
+.BR talk (1)
+and
+.BR write (1)
+may display messages on the terminal.
+.PP
+Traditionally, write access is allowed by default. However, as users
+become more conscious of various security risks, there is a trend to remove
+write access by default, at least for the primary login shell. To make
+sure your ttys are set the way you want them to be set,
+.B mesg
+should be executed in your login scripts.
+.PP
+The
+.B mesg
+utility silently exits with error status 2 if not executed on terminal. In this
+case execute
+.B mesg
+is pointless. The command line option \fB\-\-verbose\fR forces
+mesg to print a warning in this situation. This behaviour has been introduced
+in version 2.33.
+.SH ARGUMENTS
+.TP
+.B n
+Disallow messages.
+.TP
+.B y
+Allow messages to be displayed.
+.PP
+If no arguments are given,
+.B mesg
+shows the current message status on standard error output.
+.SH OPTIONS
+.TP
+.BR \-v , " \-\-verbose"
+Explain what is being done.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH EXIT STATUS
+The
+.B mesg
+utility exits with one of the following values:
+.RS 4
+.TP
+.B "\ 0"
+Messages are allowed.
+.TP
+.B "\ 1"
+Messages are not allowed.
+.TP
+.B ">1"
+An error has occurred.
+.RE
+.SH FILES
+.I /dev/[pt]ty[pq]?
+.SH HISTORY
+A
+.B mesg
+command appeared in Version 6 AT&T UNIX.
+
+.SH SEE ALSO
+.BR login (1),
+.BR talk (1),
+.BR write (1),
+.BR wall (1),
+.BR xterm (1)
+.SH AVAILABILITY
+The mesg command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/term-utils/mesg.c b/term-utils/mesg.c
new file mode 100644
index 0000000..56fb700
--- /dev/null
+++ b/term-utils/mesg.c
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 1987, 1993
+ * The Regents of the University of California. All rights reserved.
+ * (c) UNIX System Laboratories, Inc.
+ * All or some portions of this file are derived from material licensed
+ * to the University of California by American Telephone and Telegraph
+ * Co. or Unix System Laboratories, Inc. and are reproduced herein with
+ * the permission of UNIX System Laboratories, Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Modified Fri Mar 10 20:27:19 1995, faith@cs.unc.edu, for Linux
+ * Modified Mon Jul 1 18:14:10 1996, janl@ifi.uio.no, writing to stdout
+ * as suggested by Michael Meskes <meskes@Informatik.RWTH-Aachen.DE>
+ *
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ *
+ * 2010-12-01 Marek Polacek <mmpolacek@gmail.com>
+ * - cleanups
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <getopt.h>
+
+#include "closestream.h"
+#include "nls.h"
+#include "c.h"
+#include "rpmatch.h"
+#include "ttyutils.h"
+#include "pathnames.h"
+
+/* exit codes */
+
+#define IS_ALLOWED 0 /* Receiving messages is allowed. */
+#define IS_NOT_ALLOWED 1 /* Receiving messages is not allowed. */
+#define MESG_EXIT_FAILURE 2 /* An error occurred. */
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ /* TRANSLATORS: this program uses for y and n rpmatch(3),
+ * which means they can be translated. */
+ fprintf(out,
+ _(" %s [options] [y | n]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Control write access of other users to your terminal.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -v, --verbose explain what is being done\n"), out);
+ printf(USAGE_HELP_OPTIONS(16));
+ printf(USAGE_MAN_TAIL("mesg(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+ struct stat sb;
+ char *tty;
+ char ttybuf[sizeof(_PATH_PROC_FDDIR) + sizeof(stringify_value(INT_MAX))];
+ int ch, fd, verbose = FALSE, ret;
+
+ static const struct option longopts[] = {
+ { "verbose", no_argument, NULL, 'v' },
+ { "version", no_argument, NULL, 'V' },
+ { "help", no_argument, NULL, 'h' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((ch = getopt_long(argc, argv, "vVh", longopts, NULL)) != -1)
+ switch (ch) {
+ case 'v':
+ verbose = TRUE;
+ break;
+
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ fd = get_terminal_stdfd();
+ if (fd < 0) {
+ if (verbose)
+ warnx(_("no tty"));
+ exit(MESG_EXIT_FAILURE);
+ }
+
+ tty = ttyname(fd);
+ if (!tty) {
+ snprintf(ttybuf, sizeof(ttybuf), "%s/%d", _PATH_PROC_FDDIR, fd);
+ tty = ttybuf;
+ if (verbose)
+ warnx(_("ttyname() failed, attempting to go around using: %s"), tty);
+ }
+
+ if ((fd = open(tty, O_RDONLY)) < 0)
+ err(MESG_EXIT_FAILURE, _("cannot open %s"), tty);
+ if (fstat(fd, &sb))
+ err(MESG_EXIT_FAILURE, _("stat of %s failed"), tty);
+
+ if (!*argv) {
+ close(fd);
+ if (sb.st_mode & (S_IWGRP | S_IWOTH)) {
+ puts(_("is y"));
+ return IS_ALLOWED;
+ }
+ puts(_("is n"));
+ return IS_NOT_ALLOWED;
+ }
+
+ switch (rpmatch(argv[0])) {
+ case RPMATCH_YES:
+#ifdef USE_TTY_GROUP
+ if (fchmod(fd, sb.st_mode | S_IWGRP) < 0)
+#else
+ if (fchmod(fd, sb.st_mode | S_IWGRP | S_IWOTH) < 0)
+#endif
+ err(MESG_EXIT_FAILURE, _("change %s mode failed"), tty);
+ if (verbose)
+ puts(_("write access to your terminal is allowed"));
+ ret = IS_ALLOWED;
+ break;
+ case RPMATCH_NO:
+ if (fchmod(fd, sb.st_mode & ~(S_IWGRP|S_IWOTH)) < 0)
+ err(MESG_EXIT_FAILURE, _("change %s mode failed"), tty);
+ if (verbose)
+ puts(_("write access to your terminal is denied"));
+ ret = IS_NOT_ALLOWED;
+ break;
+ case RPMATCH_INVALID:
+ warnx(_("invalid argument: %s"), argv[0]);
+ errtryhelp(EXIT_FAILURE);
+ default:
+ abort();
+ }
+ close(fd);
+ return ret;
+}
diff --git a/term-utils/script-playutils.c b/term-utils/script-playutils.c
new file mode 100644
index 0000000..c589438
--- /dev/null
+++ b/term-utils/script-playutils.c
@@ -0,0 +1,570 @@
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <limits.h>
+#include <math.h>
+#include <unistd.h>
+#include <sys/time.h>
+
+#include "c.h"
+#include "xalloc.h"
+#include "closestream.h"
+#include "nls.h"
+#include "strutils.h"
+#include "script-playutils.h"
+
+UL_DEBUG_DEFINE_MASK(scriptreplay);
+UL_DEBUG_DEFINE_MASKNAMES(scriptreplay) = UL_DEBUG_EMPTY_MASKNAMES;
+
+#define DBG(m, x) __UL_DBG(scriptreplay, SCRIPTREPLAY_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(scriptreplay, SCRIPTREPLAY_DEBUG_, m, x)
+
+/*
+ * The script replay is driven by timing file where each entry describes one
+ * step in the replay. The timing step may refer input or output (or
+ * signal, extra information, etc.)
+ *
+ * The step data are stored in log files, the right log file for the step is
+ * selected from replay_setup.
+ */
+enum {
+ REPLAY_TIMING_SIMPLE, /* timing info in classic "<delta> <offset>" format */
+ REPLAY_TIMING_MULTI /* multiple streams in format "<type> <delta> <offset|etc> */
+};
+
+struct replay_log {
+ const char *streams; /* 'I'nput, 'O'utput or both */
+ const char *filename;
+ FILE *fp;
+
+ unsigned int noseek : 1; /* do not seek in this log */
+};
+
+struct replay_step {
+ char type; /* 'I'nput, 'O'utput, ... */
+ size_t size;
+
+ char *name; /* signals / headers */
+ char *value;
+
+ struct timeval delay;
+ struct replay_log *data;
+};
+
+struct replay_setup {
+ struct replay_log *logs;
+ size_t nlogs;
+
+ struct replay_step step; /* current step */
+
+ FILE *timing_fp;
+ const char *timing_filename;
+ int timing_format;
+ int timing_line;
+
+ struct timeval delay_max;
+ struct timeval delay_min;
+ double delay_div;
+
+ char default_type; /* type for REPLAY_TIMING_SIMPLE */
+ int crmode;
+};
+
+void replay_init_debug(void)
+{
+ __UL_INIT_DEBUG_FROM_ENV(scriptreplay, SCRIPTREPLAY_DEBUG_, 0, SCRIPTREPLAY_DEBUG);
+}
+
+static int ignore_line(FILE *f)
+{
+ int c;
+
+ while((c = fgetc(f)) != EOF && c != '\n');
+ if (ferror(f))
+ return -errno;
+
+ DBG(LOG, ul_debug(" ignore line"));
+ return 0;
+}
+
+/* incretemt @a by @b */
+static inline void timerinc(struct timeval *a, struct timeval *b)
+{
+ struct timeval res;
+
+ timeradd(a, b, &res);
+ a->tv_sec = res.tv_sec;
+ a->tv_usec = res.tv_usec;
+}
+
+struct replay_setup *replay_new_setup(void)
+{
+ return xcalloc(1, sizeof(struct replay_setup));
+}
+
+void replay_free_setup(struct replay_setup *stp)
+{
+ if (!stp)
+ return;
+
+ free(stp->logs);
+ free(stp->step.name);
+ free(stp->step.value);
+ free(stp);
+}
+
+/* if timing file does not contains types of entries (old format) than use this
+ * type as the default */
+int replay_set_default_type(struct replay_setup *stp, char type)
+{
+ assert(stp);
+ stp->default_type = type;
+
+ return 0;
+}
+
+int replay_set_crmode(struct replay_setup *stp, int mode)
+{
+ assert(stp);
+ stp->crmode = mode;
+
+ return 0;
+}
+
+int replay_set_delay_min(struct replay_setup *stp, const struct timeval *tv)
+{
+ stp->delay_min.tv_sec = tv->tv_sec;
+ stp->delay_min.tv_usec = tv->tv_usec;
+ return 0;
+}
+
+int replay_set_delay_max(struct replay_setup *stp, const struct timeval *tv)
+{
+ stp->delay_max.tv_sec = tv->tv_sec;
+ stp->delay_max.tv_usec = tv->tv_usec;
+ return 0;
+}
+
+int replay_set_delay_div(struct replay_setup *stp, const double divi)
+{
+ stp->delay_div = divi;
+ return 0;
+}
+
+static struct replay_log *replay_new_log(struct replay_setup *stp,
+ const char *streams,
+ const char *filename,
+ FILE *f)
+{
+ struct replay_log *log;
+
+ assert(stp);
+ assert(streams);
+ assert(filename);
+
+ stp->logs = xrealloc(stp->logs, (stp->nlogs + 1) * sizeof(*log));
+ log = &stp->logs[stp->nlogs];
+ stp->nlogs++;
+
+ memset(log, 0, sizeof(*log));
+ log->filename = filename;
+ log->streams = streams;
+ log->fp = f;
+
+ return log;
+}
+
+int replay_set_timing_file(struct replay_setup *stp, const char *filename)
+{
+ int c, rc = 0;
+
+ assert(stp);
+ assert(filename);
+
+ stp->timing_filename = filename;
+ stp->timing_line = 0;
+
+ stp->timing_fp = fopen(filename, "r");
+ if (!stp->timing_fp)
+ rc = -errno;
+ else {
+ /* detect timing file format */
+ c = fgetc(stp->timing_fp);
+ if (c != EOF) {
+ if (isdigit((unsigned int) c))
+ stp->timing_format = REPLAY_TIMING_SIMPLE;
+ else
+ stp->timing_format = REPLAY_TIMING_MULTI;
+ ungetc(c, stp->timing_fp);
+ } else if (ferror(stp->timing_fp))
+ rc = -errno;
+ }
+
+ if (rc && stp->timing_fp) {
+ fclose(stp->timing_fp);
+ stp->timing_fp = NULL;
+ }
+
+ /* create quasi-log for signals, headers, etc. */
+ if (rc == 0 && stp->timing_format == REPLAY_TIMING_MULTI) {
+ struct replay_log *log = replay_new_log(stp, "SH",
+ filename, stp->timing_fp);
+ if (!log)
+ rc = -ENOMEM;
+ else {
+ log->noseek = 1;
+ DBG(LOG, ul_debug("associate file '%s' for streams 'SH'", filename));
+ }
+ }
+
+ DBG(TIMING, ul_debug("timing file set to '%s' [rc=%d]", filename, rc));
+ return rc;
+}
+
+const char *replay_get_timing_file(struct replay_setup *setup)
+{
+ assert(setup);
+ return setup->timing_filename;
+}
+
+int replay_get_timing_line(struct replay_setup *setup)
+{
+ assert(setup);
+ return setup->timing_line;
+}
+
+int replay_associate_log(struct replay_setup *stp,
+ const char *streams, const char *filename)
+{
+ FILE *f;
+ int rc;
+
+ assert(stp);
+ assert(streams);
+ assert(filename);
+
+ /* open the file and skip the first line */
+ f = fopen(filename, "r");
+ rc = f == NULL ? -errno : ignore_line(f);
+
+ if (rc == 0)
+ replay_new_log(stp, streams, filename, f);
+
+ DBG(LOG, ul_debug("associate log file '%s', streams '%s' [rc=%d]", filename, streams, rc));
+ return rc;
+}
+
+static int is_wanted_stream(char type, const char *streams)
+{
+ if (streams == NULL)
+ return 1;
+ if (strchr(streams, type))
+ return 1;
+ return 0;
+}
+
+static void replay_reset_step(struct replay_step *step)
+{
+ assert(step);
+
+ step->size = 0;
+ step->data = NULL;
+ step->type = 0;
+ timerclear(&step->delay);
+}
+
+struct timeval *replay_step_get_delay(struct replay_step *step)
+{
+ assert(step);
+ return &step->delay;
+}
+
+/* current data log file */
+const char *replay_step_get_filename(struct replay_step *step)
+{
+ assert(step);
+ return step->data->filename;
+}
+
+int replay_step_is_empty(struct replay_step *step)
+{
+ assert(step);
+ return step->size == 0 && step->type == 0;
+}
+
+
+static int read_multistream_step(struct replay_step *step, FILE *f, char type)
+{
+ int rc = 0;
+ char nl;
+
+
+ switch (type) {
+ case 'O': /* output */
+ case 'I': /* input */
+ rc = fscanf(f, "%ld.%06ld %zu%c\n",
+ &step->delay.tv_sec,
+ &step->delay.tv_usec,
+ &step->size, &nl);
+ if (rc != 4 || nl != '\n')
+ rc = -EINVAL;
+ else
+ rc = 0;
+ break;
+
+ case 'S': /* signal */
+ case 'H': /* header */
+ {
+ char buf[BUFSIZ];
+
+ rc = fscanf(f, "%ld.%06ld ",
+ &step->delay.tv_sec,
+ &step->delay.tv_usec);
+
+ if (rc != 2)
+ break;
+
+ rc = fscanf(f, "%128s", buf); /* name */
+ if (rc != 1)
+ break;
+ step->name = strrealloc(step->name, buf);
+ if (!step->name)
+ err_oom();
+
+ if (!fgets(buf, sizeof(buf), f)) { /* value */
+ rc = -errno;
+ break;
+ }
+ if (*buf) {
+ strrem(buf, '\n');
+ step->value = strrealloc(step->value, buf);
+ if (!step->value)
+ err_oom();
+ }
+ rc = 0;
+ break;
+ }
+ default:
+ break;
+ }
+
+ DBG(TIMING, ul_debug(" read step delay & size [rc=%d]", rc));
+ return rc;
+}
+
+static struct replay_log *replay_get_stream_log(struct replay_setup *stp, char stream)
+{
+ size_t i;
+
+ for (i = 0; i < stp->nlogs; i++) {
+ struct replay_log *log = &stp->logs[i];
+
+ if (is_wanted_stream(stream, log->streams))
+ return log;
+ }
+ return NULL;
+}
+
+static int replay_seek_log(struct replay_log *log, size_t move)
+{
+ if (log->noseek)
+ return 0;
+ DBG(LOG, ul_debug(" %s: seek ++ %zu", log->filename, move));
+ return fseek(log->fp, move, SEEK_CUR) == (off_t) -1 ? -errno : 0;
+}
+
+/* returns next step with pointer to the right log file for specified streams (e.g.
+ * "IOS" for in/out/signals) or all streams if stream is NULL.
+ *
+ * returns: 0 = success, <0 = error, 1 = done (EOF)
+ */
+int replay_get_next_step(struct replay_setup *stp, char *streams, struct replay_step **xstep)
+{
+ struct replay_step *step;
+ int rc;
+ struct timeval ignored_delay;
+
+ assert(stp);
+ assert(stp->timing_fp);
+ assert(xstep);
+
+ step = &stp->step;
+ *xstep = NULL;
+
+ timerclear(&ignored_delay);
+
+ do {
+ struct replay_log *log = NULL;
+
+ rc = 1; /* done */
+ if (feof(stp->timing_fp))
+ break;
+
+ DBG(TIMING, ul_debug("reading next step"));
+
+ replay_reset_step(step);
+ stp->timing_line++;
+
+ switch (stp->timing_format) {
+ case REPLAY_TIMING_SIMPLE:
+ /* old format is the same as new format, but without <type> prefix */
+ rc = read_multistream_step(step, stp->timing_fp, stp->default_type);
+ if (rc == 0)
+ step->type = stp->default_type;
+ break;
+ case REPLAY_TIMING_MULTI:
+ rc = fscanf(stp->timing_fp, "%c ", &step->type);
+ if (rc != 1)
+ rc = -EINVAL;
+ else
+ rc = read_multistream_step(step,
+ stp->timing_fp,
+ step->type);
+ break;
+ }
+
+ if (rc) {
+ if (rc < 0 && feof(stp->timing_fp))
+ rc = 1;
+ break; /* error or EOF */
+ }
+
+ DBG(TIMING, ul_debug(" step entry is '%c'", step->type));
+
+ log = replay_get_stream_log(stp, step->type);
+ if (log) {
+ if (is_wanted_stream(step->type, streams)) {
+ step->data = log;
+ *xstep = step;
+ DBG(LOG, ul_debug(" use %s as data source", log->filename));
+ goto done;
+ }
+ /* The step entry is unwanted, but we keep the right
+ * position in the log file although the data are ignored.
+ */
+ replay_seek_log(log, step->size);
+ } else
+ DBG(TIMING, ul_debug(" not found log for '%c' stream", step->type));
+
+ DBG(TIMING, ul_debug(" ignore step '%c' [delay=%ld.%06ld]",
+ step->type,
+ step->delay.tv_sec,
+ step->delay.tv_usec));
+
+ timerinc(&ignored_delay, &step->delay);
+ } while (rc == 0);
+
+done:
+ if (timerisset(&ignored_delay))
+ timerinc(&step->delay, &ignored_delay);
+
+ DBG(TIMING, ul_debug("reading next step done [rc=%d delay=%ld.%06ld (ignored=%ld.%06ld) size=%zu]",
+ rc,
+ step->delay.tv_sec, step->delay.tv_usec,
+ ignored_delay.tv_sec, ignored_delay.tv_usec,
+ step->size));
+
+ /* normalize delay */
+ if (stp->delay_div) {
+ DBG(TIMING, ul_debug(" normalize delay: divide"));
+ step->delay.tv_sec /= stp->delay_div;
+ step->delay.tv_usec /= stp->delay_div;
+ }
+ if (timerisset(&stp->delay_max) &&
+ timercmp(&step->delay, &stp->delay_max, >)) {
+ DBG(TIMING, ul_debug(" normalize delay: align to max"));
+ step->delay.tv_sec = stp->delay_max.tv_sec;
+ step->delay.tv_usec = stp->delay_max.tv_usec;
+ }
+ if (timerisset(&stp->delay_min) &&
+ timercmp(&step->delay, &stp->delay_min, <)) {
+ DBG(TIMING, ul_debug(" normalize delay: align to min"));
+ timerclear(&step->delay);
+ }
+
+ return rc;
+}
+
+/* return: 0 = success, <0 = error, 1 = done (EOF) */
+int replay_emit_step_data(struct replay_setup *stp, struct replay_step *step, int fd)
+{
+ size_t ct;
+ int rc = 0, cr2nl = 0;
+ char buf[BUFSIZ];
+
+ assert(stp);
+ assert(step);
+ switch (step->type) {
+ case 'S':
+ assert(step->name);
+ assert(step->value);
+ dprintf(fd, "%s %s\n", step->name, step->value);
+ DBG(LOG, ul_debug("log signal emitted"));
+ return 0;
+ case 'H':
+ assert(step->name);
+ assert(step->value);
+ dprintf(fd, "%10s: %s\n", step->name, step->value);
+ DBG(LOG, ul_debug("log header emitted"));
+ return 0;
+ default:
+ break; /* continue with real data */
+ }
+
+ assert(step->size);
+ assert(step->data);
+ assert(step->data->fp);
+
+ switch (stp->crmode) {
+ case REPLAY_CRMODE_AUTO:
+ if (step->type == 'I')
+ cr2nl = 1;
+ break;
+ case REPLAY_CRMODE_NEVER:
+ cr2nl = 0;
+ break;
+ case REPLAY_CRMODE_ALWAYS:
+ cr2nl = 1;
+ break;
+ }
+
+ for (ct = step->size; ct > 0; ) {
+ size_t len, cc;
+
+ cc = ct > sizeof(buf) ? sizeof(buf): ct;
+ len = fread(buf, 1, cc, step->data->fp);
+
+ if (!len) {
+ DBG(LOG, ul_debug("log data emit: failed to read log %m"));
+ break;
+ }
+
+ if (cr2nl) {
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ if (buf[i] == 0x0D)
+ buf[i] = '\n';
+ }
+ }
+
+ ct -= len;
+ cc = write(fd, buf, len);
+ if (cc != len) {
+ rc = -errno;
+ DBG(LOG, ul_debug("log data emit: failed write data %m"));
+ break;
+ }
+ }
+
+ if (ct && ferror(step->data->fp))
+ rc = -errno;
+ if (ct && feof(step->data->fp))
+ rc = 1;
+
+ DBG(LOG, ul_debug("log data emitted [rc=%d size=%zu]", rc, step->size));
+ return rc;
+}
diff --git a/term-utils/script-playutils.h b/term-utils/script-playutils.h
new file mode 100644
index 0000000..53b3fd2
--- /dev/null
+++ b/term-utils/script-playutils.h
@@ -0,0 +1,50 @@
+#ifndef UTIL_LINUX_SCRIPT_PLAYUTILS_H
+#define UTIL_LINUX_SCRIPT_PLAYUTILS_H
+
+#include "c.h"
+#include "debug.h"
+
+#define SCRIPTREPLAY_DEBUG_INIT (1 << 1)
+#define SCRIPTREPLAY_DEBUG_TIMING (1 << 2)
+#define SCRIPTREPLAY_DEBUG_LOG (1 << 3)
+#define SCRIPTREPLAY_DEBUG_MISC (1 << 4)
+#define SCRIPTREPLAY_DEBUG_ALL 0xFFFF
+
+UL_DEBUG_DECLARE_MASK(scriptreplay);
+
+#define DBG(m, x) __UL_DBG(scriptreplay, SCRIPTREPLAY_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(scriptreplay, SCRIPTREPLAY_DEBUG_, m, x)
+
+/* CR to '\n' mode */
+enum {
+ REPLAY_CRMODE_AUTO = 0,
+ REPLAY_CRMODE_NEVER,
+ REPLAY_CRMODE_ALWAYS
+};
+
+struct replay_setup;
+struct replay_step;
+
+void replay_init_debug(void);
+struct replay_setup *replay_new_setup(void);
+void replay_free_setup(struct replay_setup *stp);
+
+int replay_set_default_type(struct replay_setup *stp, char type);
+int replay_set_crmode(struct replay_setup *stp, int mode);
+int replay_set_timing_file(struct replay_setup *stp, const char *filename);
+const char *replay_get_timing_file(struct replay_setup *setup);
+int replay_get_timing_line(struct replay_setup *setup);
+int replay_associate_log(struct replay_setup *stp, const char *streams, const char *filename);
+
+int replay_set_delay_min(struct replay_setup *stp, const struct timeval *tv);
+int replay_set_delay_max(struct replay_setup *stp, const struct timeval *tv);
+int replay_set_delay_div(struct replay_setup *stp, const double divi);
+
+struct timeval *replay_step_get_delay(struct replay_step *step);
+const char *replay_step_get_filename(struct replay_step *step);
+int replay_step_is_empty(struct replay_step *step);
+int replay_get_next_step(struct replay_setup *stp, char *streams, struct replay_step **xstep);
+
+int replay_emit_step_data(struct replay_setup *stp, struct replay_step *step, int fd);
+
+#endif /* UTIL_LINUX_SCRIPT_PLAYUTILS_H */
diff --git a/term-utils/script.1 b/term-utils/script.1
new file mode 100644
index 0000000..49ba582
--- /dev/null
+++ b/term-utils/script.1
@@ -0,0 +1,295 @@
+.\" Copyright (c) 1980, 1990 Regents of the University of California.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)script.1 6.5 (Berkeley) 7/27/91
+.\"
+.TH SCRIPT "1" "October 2019" "util-linux" "User Commands"
+.SH NAME
+script \- make typescript of terminal session
+.SH SYNOPSIS
+.B script
+[options]
+.RI [ file ]
+.SH DESCRIPTION
+.B script
+makes a typescript of everything on your terminal session. The terminal
+data are stored in raw form to the log file and information about timing
+to another (optional) structured log file. The timing log file is necessary to replay
+the session later by
+.BR scriptreplay (1)
+and to store additional information about the session.
+.PP
+Since version 2.35,
+.B script
+supports multiple streams and allows the logging of input and output to separate
+files or all the one file. This version also supports new timing file
+which records additional information. The command
+.B scriptreplay \-\-summary
+then provides all the information.
+
+.PP
+If the argument
+.I file
+or option \fB\-\-log\-out\fR \fIfile\fR is given,
+.B script
+saves the dialogue in this
+.IR file .
+If no filename is given, the dialogue is saved in the file
+.IR typescript .
+.PP
+Note that logging input using \fB\-\-log\-in\fR or \fB\-\-log\-io\fR
+may record security-sensitive information
+as the log file contains all terminal session input
+(e.g., passwords)
+independently of the terminal echo flag setting.
+.SH OPTIONS
+Below, the \fIsize\fR argument may be followed by the multiplicative
+suffixes KiB (=1024), MiB (=1024*1024), and so on for GiB, TiB, PiB, EiB, ZiB and YiB
+(the "iB" is optional, e.g., "K" has the same meaning as "KiB"), or the suffixes
+KB (=1000), MB (=1000*1000), and so on for GB, TB, PB, EB, ZB and YB.
+.TP
+\fB\-a\fR, \fB\-\-append\fR
+Append the output to
+.I file
+or to
+.IR typescript ,
+retaining the prior contents.
+.TP
+\fB\-c\fR, \fB\-\-command\fR \fIcommand\fR
+Run the
+.I command
+rather than an interactive shell. This makes it easy for a script to capture
+the output of a program that behaves differently when its stdout is not a
+tty.
+.TP
+\fB\-E\fR, \fB\-\-echo\fR \fIwhen\fR
+This option controls the ECHO flag for the pseudoterminal within the session.
+The supported modes are
+.IR always ,
+.IR never ,
+or
+.IR auto .
+The default is
+.I auto
+-- in this case, ECHO is disabled if the current standard input is a
+terminal iin order to avoid double-echo,
+and enabled if standard input is not a terminal
+(for example pipe:
+.BR "echo date | script" )
+to avoid missing input in the session log.
+.TP
+\fB\-e\fR, \fB\-\-return\fR
+Return the exit status of the child process. Uses the same format as bash
+termination on signal termination
+(i.e., exit status is 128 + the signal number). The exit status of
+the child process is always stored in the type script file too.
+.TP
+\fB\-f\fR, \fB\-\-flush\fR
+Flush output after each write. This is nice for telecooperation: one person
+does `mkfifo foo; script \-f foo',
+and another can supervise in real-time what is
+being done using `cat foo'. Note that flush has an impact on performance; it's
+possible to use SIGUSR1 to flush logs on demand.
+.TP
+\fB\-\-force\fR
+Allow the default output file
+.I typescript
+to be a hard or symbolic link. The command will follow a symbolic link.
+.TP
+\fB\-B\fR, \fB\-\-log\-io\fR \fIfile\fR
+Log input and output to the same
+\fIfile\fR. Note, this option makes sense only if \fB\-\-log\-timing\fR is
+also specified, otherwise it's impossible to separate output and input streams from
+the log \fIfile\fR.
+.TP
+\fB\-I\fR, \fB\-\-log\-in\fR \fIfile\fR
+Log input to the \fIfile\fR. The log output is disabled if only \fB\-\-log\-in\fR
+specified.
+.sp
+Use this logging functionality carefully as it logs all input, including input
+when terminal has disabled echo flag (for example, password inputs).
+.TP
+\fB\-O\fR, \fB\-\-log\-out\fR \fIfile\fR
+Log output to the \fIfile\fR. The default is to log output to the file with
+name
+.I typescript
+if the option \fB\-\-log\-out\fR or \fB\-\-log\-in\fR is not given. The log
+output is disabled if only \fB\-\-log\-in\fR specified.
+.TP
+\fB\-T\fR, \fB\-\-log\-timing\fR \fIfile\fR
+Log timing information to the \fIfile\fR. Two timing file formats are supported
+now. The classic format is used when only one stream (input or output) logging
+is enabled. The multi-stream format is used on \fB\-\-log\-io\fR or when
+\fB\-\-log\-in\fR and \fB\-\-log\-out\fR are used together.
+See also \fB\-\-logging\-format\fR.
+.TP
+\fB\-m\fR, \fB\-\-logging\-format\fR \fIformat\fR
+Force use of
+.I advanced
+or
+.I classic
+format. The default is the classic format to log only output and the
+advanced format when input as well as output logging is requested.
+.sp
+.RS
+.B Classic format
+.PP
+The log contains two fields, separated by a space. The first
+field indicates how much time elapsed since the previous output. The second
+field indicates how many characters were output this time.
+.sp
+.B Advanced (multi-stream) format
+.PP
+The first field is an entry type identifier
+('I'nput, 'O'utput, 'H'eader, 'S'ignal).
+The socond field is how much time elapsed since the previous entry,
+and the rest of the entry is type-specific data.
+.RE
+.TP
+\fB\-o\fR, \fB\-\-output-limit\fR \fIsize\fR
+Limit the size of the typescript and timing files to
+.I size
+and stop the child process after this size is exceeded. The calculated
+file size does not include the start and done messages that the
+.B script
+command prepends and appends to the child process output.
+Due to buffering, the resulting output file might be larger than the specified value.
+.TP
+\fB\-q\fR, \fB\-\-quiet\fR
+Be quiet (do not write start and done messages to standard output).
+.TP
+\fB\-t\fR[\fIfile\fR], \fB\-\-timing\fR[=\fIfile\fR]
+Output timing data to standard error, or to
+.I file
+when given. This option is deprecated in favour of \fB\-\-log\-timing\fR where
+the \fIfile\fR argument is not optional.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Display version information and exit.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Display help text and exit.
+.SH SIGNALS
+Upon receiving
+.BR SIGUSR1 ,
+.B script
+immediately flushes the output files.
+.SH ENVIRONMENT
+The following environment variable is utilized by
+.BR script :
+.TP
+.B SHELL
+If the variable
+.B SHELL
+exists, the shell forked by
+.B script
+will be that shell. If
+.B SHELL
+is not set, the Bourne shell is assumed. (Most shells set this variable
+automatically).
+.SH NOTES
+The script ends when the forked shell exits (a
+.I control-D
+for the Bourne shell
+.RB ( sh (1p)),
+and
+.IR exit ,
+.I logout
+or
+.I control-d
+(if
+.I ignoreeof
+is not set) for the
+C-shell,
+.BR csh (1)).
+.PP
+Certain interactive commands, such as
+.BR vi (1),
+create garbage in the typescript file.
+.B script
+works best with commands that do not manipulate the screen, the results are
+meant to emulate a hardcopy terminal.
+.PP
+It is not recommended to run
+.B script
+in non-interactive shells. The inner shell of
+.B script
+is always interactive, and this could lead to unexpected results. If you use
+.B script
+in the shell initialization file, you have to avoid entering an infinite
+loop. You can use for example the \fB\%.profile\fR file, which is read
+by login shells only:
+.sp
+.na
+.RS
+.nf
+if test \-t 0 ; then
+ script
+ exit
+fi
+.fi
+.RE
+.ad
+.PP
+You should also avoid use of
+.B script
+in command pipes, as
+.B script
+can read more input than you would expect.
+.SH HISTORY
+The
+.B script
+command appeared in 3.0BSD.
+.SH BUGS
+.B script
+places
+.I everything
+in the log file, including linefeeds and backspaces. This is not what the
+naive user expects.
+.PP
+.B script
+is primarily designed for interactive terminal sessions. When stdin
+is not a terminal (for example: \fBecho foo | script\fR), then the session
+can hang, because the interactive shell within the script session misses EOF and
+.B script
+has no clue when to close the session. See the \fBNOTES\fR section for more information.
+.SH SEE ALSO
+.BR csh (1)
+(for the
+.I history
+mechanism),
+.BR scriptreplay (1),
+.BR scriptlive (1),
+.SH AVAILABILITY
+The script command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/term-utils/script.c b/term-utils/script.c
new file mode 100644
index 0000000..561e291
--- /dev/null
+++ b/term-utils/script.c
@@ -0,0 +1,1068 @@
+/*
+ * Copyright (C) 1980 Regents of the University of California.
+ * Copyright (C) 2013-2019 Karel Zak <kzak@redhat.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <paths.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <termios.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <signal.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <locale.h>
+#include <stddef.h>
+#include <sys/wait.h>
+#include <poll.h>
+#include <sys/signalfd.h>
+#include <assert.h>
+#include <inttypes.h>
+
+#include "closestream.h"
+#include "nls.h"
+#include "c.h"
+#include "ttyutils.h"
+#include "all-io.h"
+#include "monotonic.h"
+#include "timeutils.h"
+#include "strutils.h"
+#include "xalloc.h"
+#include "optutils.h"
+#include "signames.h"
+#include "pty-session.h"
+#include "debug.h"
+
+static UL_DEBUG_DEFINE_MASK(script);
+UL_DEBUG_DEFINE_MASKNAMES(script) = UL_DEBUG_EMPTY_MASKNAMES;
+
+#define SCRIPT_DEBUG_INIT (1 << 1)
+#define SCRIPT_DEBUG_PTY (1 << 2)
+#define SCRIPT_DEBUG_IO (1 << 3)
+#define SCRIPT_DEBUG_SIGNAL (1 << 4)
+#define SCRIPT_DEBUG_MISC (1 << 5)
+#define SCRIPT_DEBUG_ALL 0xFFFF
+
+#define DBG(m, x) __UL_DBG(script, SCRIPT_DEBUG_, m, x)
+#define ON_DBG(m, x) __UL_DBG_CALL(script, SCRIPT_DEBUG_, m, x)
+
+#ifdef HAVE_LIBUTEMPTER
+# include <utempter.h>
+#endif
+
+#define DEFAULT_TYPESCRIPT_FILENAME "typescript"
+
+/*
+ * Script is driven by stream (stdout/stdin) activity. It's possible to
+ * associate arbitrary number of log files with the stream. We have two basic
+ * types of log files: "timing file" (simple or multistream) and "data file"
+ * (raw).
+ *
+ * The same log file maybe be shared between both streams. For example
+ * multi-stream timing file is possible to use for stdin as well as for stdout.
+ */
+enum {
+ SCRIPT_FMT_RAW = 1, /* raw slave/master data */
+ SCRIPT_FMT_TIMING_SIMPLE, /* (classic) in format "<delta> <offset>" */
+ SCRIPT_FMT_TIMING_MULTI, /* (advanced) multiple streams in format "<type> <delta> <offset|etc> */
+};
+
+struct script_log {
+ FILE *fp; /* file pointer (handler) */
+ int format; /* SCRIPT_FMT_* */
+ char *filename; /* on command line specified name */
+ struct timeval oldtime; /* previous entry log time (SCRIPT_FMT_TIMING_* only) */
+ struct timeval starttime;
+
+ unsigned int initialized : 1;
+};
+
+struct script_stream {
+ struct script_log **logs; /* logs where to write data from stream */
+ size_t nlogs; /* number of logs */
+ char ident; /* stream identifier */
+};
+
+struct script_control {
+ uint64_t outsz; /* current output files size */
+ uint64_t maxsz; /* maximum output files size */
+
+ struct script_stream out; /* output */
+ struct script_stream in; /* input */
+
+ struct script_log *siglog; /* log for signal entries */
+ struct script_log *infolog; /* log for info entries */
+
+ const char *ttyname;
+ const char *ttytype;
+ int ttycols;
+ int ttylines;
+
+ struct ul_pty *pty; /* pseudo-terminal */
+ pid_t child; /* child pid */
+ int childstatus; /* child process exit value */
+
+ unsigned int
+ append:1, /* append output */
+ rc_wanted:1, /* return child exit value */
+ flush:1, /* flush after each write */
+ quiet:1, /* suppress most output */
+ force:1, /* write output to links */
+ isterm:1; /* is child process running as terminal */
+};
+
+static ssize_t log_info(struct script_control *ctl, const char *name, const char *msgfmt, ...);
+
+static void script_init_debug(void)
+{
+ __UL_INIT_DEBUG_FROM_ENV(script, SCRIPT_DEBUG_, 0, SCRIPT_DEBUG);
+}
+
+static void init_terminal_info(struct script_control *ctl)
+{
+ if (ctl->ttyname || !ctl->isterm)
+ return; /* already initialized */
+
+ get_terminal_dimension(&ctl->ttycols, &ctl->ttylines);
+ get_terminal_name(&ctl->ttyname, NULL, NULL);
+ get_terminal_type(&ctl->ttytype);
+}
+
+/*
+ * For tests we want to be able to control time output
+ */
+#ifdef TEST_SCRIPT
+static inline time_t script_time(time_t *t)
+{
+ const char *str = getenv("SCRIPT_TEST_SECOND_SINCE_EPOCH");
+ int64_t sec;
+
+ if (!str || sscanf(str, "%"SCNi64, &sec) != 1)
+ return time(t);
+ if (t)
+ *t = (time_t)sec;
+ return (time_t)sec;
+}
+#else /* !TEST_SCRIPT */
+# define script_time(x) time(x)
+#endif
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out, _(" %s [options] [file]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Make a typescript of a terminal session.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -I, --log-in <file> log stdin to file\n"), out);
+ fputs(_(" -O, --log-out <file> log stdout to file (default)\n"), out);
+ fputs(_(" -B, --log-io <file> log stdin and stdout to file\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+
+ fputs(_(" -T, --log-timing <file> log timing information to file\n"), out);
+ fputs(_(" -t[<file>], --timing[=<file>] deprecated alias to -T (default file is stderr)\n"), out);
+ fputs(_(" -m, --logging-format <name> force to 'classic' or 'advanced' format\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+
+ fputs(_(" -a, --append append to the log file\n"), out);
+ fputs(_(" -c, --command <command> run command rather than interactive shell\n"), out);
+ fputs(_(" -e, --return return exit code of the child process\n"), out);
+ fputs(_(" -f, --flush run flush after each write\n"), out);
+ fputs(_(" --force use output file even when it is a link\n"), out);
+ fputs(_(" -E, --echo <when> echo input (auto, always or never)\n"), out);
+ fputs(_(" -o, --output-limit <size> terminate if output files exceed size\n"), out);
+ fputs(_(" -q, --quiet be quiet\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(31));
+ printf(USAGE_MAN_TAIL("script(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+static struct script_log *get_log_by_name(struct script_stream *stream,
+ const char *name)
+{
+ size_t i;
+
+ for (i = 0; i < stream->nlogs; i++) {
+ struct script_log *log = stream->logs[i];
+ if (strcmp(log->filename, name) == 0)
+ return log;
+ }
+ return NULL;
+}
+
+static struct script_log *log_associate(struct script_control *ctl,
+ struct script_stream *stream,
+ const char *filename, int format)
+{
+ struct script_log *log;
+
+ DBG(MISC, ul_debug("associate %s with stream", filename));
+
+ assert(ctl);
+ assert(filename);
+ assert(stream);
+
+ log = get_log_by_name(stream, filename);
+ if (log)
+ return log; /* already defined */
+
+ log = get_log_by_name(stream == &ctl->out ? &ctl->in : &ctl->out, filename);
+ if (!log) {
+ /* create a new log */
+ log = xcalloc(1, sizeof(*log));
+ log->filename = xstrdup(filename);
+ log->format = format;
+ }
+
+ /* add log to the stream */
+ stream->logs = xrealloc(stream->logs,
+ (stream->nlogs + 1) * sizeof(log));
+ stream->logs[stream->nlogs] = log;
+ stream->nlogs++;
+
+ /* remember where to write info about signals */
+ if (format == SCRIPT_FMT_TIMING_MULTI) {
+ if (!ctl->siglog)
+ ctl->siglog = log;
+ if (!ctl->infolog)
+ ctl->infolog = log;
+ }
+
+ return log;
+}
+
+static int log_close(struct script_control *ctl,
+ struct script_log *log,
+ const char *msg,
+ int status)
+{
+ int rc = 0;
+
+ if (!log || !log->initialized)
+ return 0;
+
+ DBG(MISC, ul_debug("closing %s", log->filename));
+
+ switch (log->format) {
+ case SCRIPT_FMT_RAW:
+ {
+ char buf[FORMAT_TIMESTAMP_MAX];
+ time_t tvec = script_time((time_t *)NULL);
+
+ strtime_iso(&tvec, ISO_TIMESTAMP, buf, sizeof(buf));
+ if (msg)
+ fprintf(log->fp, _("\nScript done on %s [<%s>]\n"), buf, msg);
+ else
+ fprintf(log->fp, _("\nScript done on %s [COMMAND_EXIT_CODE=\"%d\"]\n"), buf, status);
+ break;
+ }
+ case SCRIPT_FMT_TIMING_MULTI:
+ {
+ struct timeval now, delta;
+
+ gettime_monotonic(&now);
+ timersub(&now, &log->starttime, &delta);
+
+ log_info(ctl, "DURATION", "%ld.%06ld",
+ (long)delta.tv_sec, (long)delta.tv_usec);
+ log_info(ctl, "EXIT_CODE", "%d", status);
+ break;
+ }
+ case SCRIPT_FMT_TIMING_SIMPLE:
+ break;
+ }
+
+ if (close_stream(log->fp) != 0) {
+ warn(_("write failed: %s"), log->filename);
+ rc = -errno;
+ }
+
+ free(log->filename);
+ memset(log, 0, sizeof(*log));
+
+ return rc;
+}
+
+static int log_flush(struct script_control *ctl __attribute__((__unused__)), struct script_log *log)
+{
+
+ if (!log || !log->initialized)
+ return 0;
+
+ DBG(MISC, ul_debug("flushing %s", log->filename));
+
+ fflush(log->fp);
+ return 0;
+}
+
+static void log_free(struct script_control *ctl, struct script_log *log)
+{
+ size_t i;
+
+ if (!log)
+ return;
+
+ /* the same log is possible to reference from more places, remove all
+ * (TODO: maybe use include/list.h to make it more elegant)
+ */
+ if (ctl->siglog == log)
+ ctl->siglog = NULL;
+ else if (ctl->infolog == log)
+ ctl->infolog = NULL;
+
+ for (i = 0; i < ctl->out.nlogs; i++) {
+ if (ctl->out.logs[i] == log)
+ ctl->out.logs[i] = NULL;
+ }
+ for (i = 0; i < ctl->in.nlogs; i++) {
+ if (ctl->in.logs[i] == log)
+ ctl->in.logs[i] = NULL;
+ }
+ free(log);
+}
+
+static int log_start(struct script_control *ctl,
+ struct script_log *log)
+{
+ if (log->initialized)
+ return 0;
+
+ DBG(MISC, ul_debug("opening %s", log->filename));
+
+ assert(log->fp == NULL);
+
+ /* open the log */
+ log->fp = fopen(log->filename,
+ ctl->append && log->format == SCRIPT_FMT_RAW ?
+ "a" UL_CLOEXECSTR :
+ "w" UL_CLOEXECSTR);
+ if (!log->fp) {
+ warn(_("cannot open %s"), log->filename);
+ return -errno;
+ }
+
+ /* write header, etc. */
+ switch (log->format) {
+ case SCRIPT_FMT_RAW:
+ {
+ char buf[FORMAT_TIMESTAMP_MAX];
+ time_t tvec = script_time((time_t *)NULL);
+
+ strtime_iso(&tvec, ISO_TIMESTAMP, buf, sizeof(buf));
+ fprintf(log->fp, _("Script started on %s ["), buf);
+
+ if (ctl->isterm) {
+ init_terminal_info(ctl);
+
+ if (ctl->ttytype)
+ fprintf(log->fp, "TERM=\"%s\" ", ctl->ttytype);
+ if (ctl->ttyname)
+ fprintf(log->fp, "TTY=\"%s\" ", ctl->ttyname);
+
+ fprintf(log->fp, "COLUMNS=\"%d\" LINES=\"%d\"", ctl->ttycols, ctl->ttylines);
+ } else
+ fprintf(log->fp, _("<not executed on terminal>"));
+
+ fputs("]\n", log->fp);
+ break;
+ }
+ case SCRIPT_FMT_TIMING_SIMPLE:
+ case SCRIPT_FMT_TIMING_MULTI:
+ gettime_monotonic(&log->oldtime);
+ gettime_monotonic(&log->starttime);
+ break;
+ }
+
+ log->initialized = 1;
+ return 0;
+}
+
+static int logging_start(struct script_control *ctl)
+{
+ size_t i;
+
+ /* start all output logs */
+ for (i = 0; i < ctl->out.nlogs; i++) {
+ int rc = log_start(ctl, ctl->out.logs[i]);
+ if (rc)
+ return rc;
+ }
+
+ /* start all input logs */
+ for (i = 0; i < ctl->in.nlogs; i++) {
+ int rc = log_start(ctl, ctl->in.logs[i]);
+ if (rc)
+ return rc;
+ }
+ return 0;
+}
+
+static ssize_t log_write(struct script_control *ctl,
+ struct script_stream *stream,
+ struct script_log *log,
+ char *obuf, size_t bytes)
+{
+ int rc;
+ ssize_t ssz = 0;
+ struct timeval now, delta;
+
+ if (!log->fp)
+ return 0;
+
+ DBG(IO, ul_debug(" writing [file=%s]", log->filename));
+
+ switch (log->format) {
+ case SCRIPT_FMT_RAW:
+ DBG(IO, ul_debug(" log raw data"));
+ rc = fwrite_all(obuf, 1, bytes, log->fp);
+ if (rc) {
+ warn(_("cannot write %s"), log->filename);
+ return rc;
+ }
+ ssz = bytes;
+ break;
+
+ case SCRIPT_FMT_TIMING_SIMPLE:
+ DBG(IO, ul_debug(" log timing info"));
+
+ gettime_monotonic(&now);
+ timersub(&now, &log->oldtime, &delta);
+ ssz = fprintf(log->fp, "%ld.%06ld %zd\n",
+ (long)delta.tv_sec, (long)delta.tv_usec, bytes);
+ if (ssz < 0)
+ return -errno;
+
+ log->oldtime = now;
+ break;
+
+ case SCRIPT_FMT_TIMING_MULTI:
+ DBG(IO, ul_debug(" log multi-stream timing info"));
+
+ gettime_monotonic(&now);
+ timersub(&now, &log->oldtime, &delta);
+ ssz = fprintf(log->fp, "%c %ld.%06ld %zd\n",
+ stream->ident,
+ (long)delta.tv_sec, (long)delta.tv_usec, bytes);
+ if (ssz < 0)
+ return -errno;
+
+ log->oldtime = now;
+ break;
+ default:
+ break;
+ }
+
+ if (ctl->flush)
+ fflush(log->fp);
+ return ssz;
+}
+
+static ssize_t log_stream_activity(
+ struct script_control *ctl,
+ struct script_stream *stream,
+ char *buf, size_t bytes)
+{
+ size_t i;
+ ssize_t outsz = 0;
+
+ for (i = 0; i < stream->nlogs; i++) {
+ ssize_t ssz = log_write(ctl, stream, stream->logs[i], buf, bytes);
+
+ if (ssz < 0)
+ return ssz;
+ outsz += ssz;
+ }
+
+ return outsz;
+}
+
+static ssize_t log_signal(struct script_control *ctl, int signum, char *msgfmt, ...)
+{
+ struct script_log *log;
+ struct timeval now, delta;
+ char msg[BUFSIZ] = {0};
+ va_list ap;
+ ssize_t sz;
+
+ assert(ctl);
+
+ log = ctl->siglog;
+ if (!log)
+ return 0;
+
+ assert(log->format == SCRIPT_FMT_TIMING_MULTI);
+ DBG(IO, ul_debug(" writing signal to multi-stream timing"));
+
+ gettime_monotonic(&now);
+ timersub(&now, &log->oldtime, &delta);
+
+ if (msgfmt) {
+ int rc;
+ va_start(ap, msgfmt);
+ rc = vsnprintf(msg, sizeof(msg), msgfmt, ap);
+ va_end(ap);
+ if (rc < 0)
+ *msg = '\0';;
+ }
+
+ if (*msg)
+ sz = fprintf(log->fp, "S %ld.%06ld SIG%s %s\n",
+ (long)delta.tv_sec, (long)delta.tv_usec,
+ signum_to_signame(signum), msg);
+ else
+ sz = fprintf(log->fp, "S %ld.%06ld SIG%s\n",
+ (long)delta.tv_sec, (long)delta.tv_usec,
+ signum_to_signame(signum));
+
+ log->oldtime = now;
+ return sz;
+}
+
+static ssize_t log_info(struct script_control *ctl, const char *name, const char *msgfmt, ...)
+{
+ struct script_log *log;
+ char msg[BUFSIZ] = {0};
+ va_list ap;
+ ssize_t sz;
+
+ assert(ctl);
+
+ log = ctl->infolog;
+ if (!log)
+ return 0;
+
+ assert(log->format == SCRIPT_FMT_TIMING_MULTI);
+ DBG(IO, ul_debug(" writing info to multi-stream log"));
+
+ if (msgfmt) {
+ int rc;
+ va_start(ap, msgfmt);
+ rc = vsnprintf(msg, sizeof(msg), msgfmt, ap);
+ va_end(ap);
+ if (rc < 0)
+ *msg = '\0';;
+ }
+
+ if (*msg)
+ sz = fprintf(log->fp, "H %f %s %s\n", 0.0, name, msg);
+ else
+ sz = fprintf(log->fp, "H %f %s\n", 0.0, name);
+
+ return sz;
+}
+
+
+static void logging_done(struct script_control *ctl, const char *msg)
+{
+ int status;
+ size_t i;
+
+ DBG(MISC, ul_debug("stop logging"));
+
+ if (WIFSIGNALED(ctl->childstatus))
+ status = WTERMSIG(ctl->childstatus) + 0x80;
+ else
+ status = WEXITSTATUS(ctl->childstatus);
+
+ DBG(MISC, ul_debug(" status=%d", status));
+
+ /* close all output logs */
+ for (i = 0; i < ctl->out.nlogs; i++) {
+ struct script_log *log = ctl->out.logs[i];
+ log_close(ctl, log, msg, status);
+ log_free(ctl, log);
+ }
+ free(ctl->out.logs);
+ ctl->out.logs = NULL;
+ ctl->out.nlogs = 0;
+
+ /* close all input logs */
+ for (i = 0; i < ctl->in.nlogs; i++) {
+ struct script_log *log = ctl->in.logs[i];
+ log_close(ctl, log, msg, status);
+ log_free(ctl, log);
+ }
+ free(ctl->in.logs);
+ ctl->in.logs = NULL;
+ ctl->in.nlogs = 0;
+}
+
+static void callback_child_die(
+ void *data,
+ pid_t child __attribute__((__unused__)),
+ int status)
+{
+ struct script_control *ctl = (struct script_control *) data;
+
+ ctl->child = (pid_t) -1;
+ ctl->childstatus = status;
+}
+
+static void callback_child_sigstop(
+ void *data __attribute__((__unused__)),
+ pid_t child)
+{
+ DBG(SIGNAL, ul_debug(" child stop by SIGSTOP -- stop parent too"));
+ kill(getpid(), SIGSTOP);
+ DBG(SIGNAL, ul_debug(" resume"));
+ kill(child, SIGCONT);
+}
+
+static int callback_log_stream_activity(void *data, int fd, char *buf, size_t bufsz)
+{
+ struct script_control *ctl = (struct script_control *) data;
+ ssize_t ssz = 0;
+
+ DBG(IO, ul_debug("stream activity callback"));
+
+ /* from stdin (user) to command */
+ if (fd == STDIN_FILENO)
+ ssz = log_stream_activity(ctl, &ctl->in, buf, (size_t) bufsz);
+
+ /* from command (master) to stdout and log */
+ else if (fd == ul_pty_get_childfd(ctl->pty))
+ ssz = log_stream_activity(ctl, &ctl->out, buf, (size_t) bufsz);
+
+ if (ssz < 0)
+ return (int) ssz;
+
+ DBG(IO, ul_debug(" append %ld bytes [summary=%zu, max=%zu]", ssz,
+ ctl->outsz, ctl->maxsz));
+
+ ctl->outsz += ssz;
+
+
+ /* check output limit */
+ if (ctl->maxsz != 0 && ctl->outsz >= ctl->maxsz) {
+ if (!ctl->quiet)
+ printf(_("Script terminated, max output files size %"PRIu64" exceeded.\n"), ctl->maxsz);
+ DBG(IO, ul_debug("output size %"PRIu64", exceeded limit %"PRIu64, ctl->outsz, ctl->maxsz));
+ logging_done(ctl, _("max output size exceeded"));
+ return 1;
+ }
+ return 0;
+}
+
+static int callback_log_signal(void *data, struct signalfd_siginfo *info, void *sigdata)
+{
+ struct script_control *ctl = (struct script_control *) data;
+ ssize_t ssz = 0;
+
+ switch (info->ssi_signo) {
+ case SIGWINCH:
+ {
+ struct winsize *win = (struct winsize *) sigdata;
+ ssz = log_signal(ctl, info->ssi_signo, "ROWS=%d COLS=%d",
+ win->ws_row, win->ws_col);
+ break;
+ }
+ case SIGTERM:
+ /* fallthrough */
+ case SIGINT:
+ /* fallthrough */
+ case SIGQUIT:
+ ssz = log_signal(ctl, info->ssi_signo, NULL);
+ break;
+ default:
+ /* no log */
+ break;
+ }
+
+ return ssz < 0 ? ssz : 0;
+}
+
+static int callback_flush_logs(void *data)
+{
+ struct script_control *ctl = (struct script_control *) data;
+ size_t i;
+
+ for (i = 0; i < ctl->out.nlogs; i++) {
+ int rc = log_flush(ctl, ctl->out.logs[i]);
+ if (rc)
+ return rc;
+ }
+
+ for (i = 0; i < ctl->in.nlogs; i++) {
+ int rc = log_flush(ctl, ctl->in.logs[i]);
+ if (rc)
+ return rc;
+ }
+ return 0;
+}
+
+static void die_if_link(struct script_control *ctl, const char *filename)
+{
+ struct stat s;
+
+ if (ctl->force)
+ return;
+ if (lstat(filename, &s) == 0 && (S_ISLNK(s.st_mode) || s.st_nlink > 1))
+ errx(EXIT_FAILURE,
+ _("output file `%s' is a link\n"
+ "Use --force if you really want to use it.\n"
+ "Program not started."), filename);
+}
+
+int main(int argc, char **argv)
+{
+ struct script_control ctl = {
+ .out = { .ident = 'O' },
+ .in = { .ident = 'I' },
+ };
+ struct ul_pty_callbacks *cb;
+ int ch, format = 0, caught_signal = 0, rc = 0, echo = 0;
+ const char *outfile = NULL, *infile = NULL;
+ const char *timingfile = NULL, *shell = NULL, *command = NULL;
+
+ enum { FORCE_OPTION = CHAR_MAX + 1 };
+
+ static const struct option longopts[] = {
+ {"append", no_argument, NULL, 'a'},
+ {"command", required_argument, NULL, 'c'},
+ {"echo", required_argument, NULL, 'E'},
+ {"return", no_argument, NULL, 'e'},
+ {"flush", no_argument, NULL, 'f'},
+ {"force", no_argument, NULL, FORCE_OPTION,},
+ {"log-in", required_argument, NULL, 'I'},
+ {"log-out", required_argument, NULL, 'O'},
+ {"log-io", required_argument, NULL, 'B'},
+ {"log-timing", required_argument, NULL, 'T'},
+ {"logging-format", required_argument, NULL, 'm'},
+ {"output-limit", required_argument, NULL, 'o'},
+ {"quiet", no_argument, NULL, 'q'},
+ {"timing", optional_argument, NULL, 't'},
+ {"version", no_argument, NULL, 'V'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'T', 't' },
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+ setlocale(LC_ALL, "");
+ /*
+ * script -t prints time delays as floating point numbers. The example
+ * program (scriptreplay) that we provide to handle this timing output
+ * is a perl script, and does not handle numbers in locale format (not
+ * even when "use locale;" is added). So, since these numbers are not
+ * for human consumption, it seems easiest to set LC_NUMERIC here.
+ */
+ setlocale(LC_NUMERIC, "C");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ script_init_debug();
+ ON_DBG(PTY, ul_pty_init_debug(0xFFFF));
+
+ /* The default is to keep ECHO flag when stdin is not terminal. We need
+ * it to make stdin (in case of "echo foo | script") log-able and
+ * visible on terminal, and for backward compatibility.
+ */
+ ctl.isterm = isatty(STDIN_FILENO);
+ echo = ctl.isterm ? 0 : 1;
+
+ while ((ch = getopt_long(argc, argv, "aB:c:eE:fI:O:o:qm:T:t::Vh", longopts, NULL)) != -1) {
+
+ err_exclusive_options(ch, longopts, excl, excl_st);
+
+ switch (ch) {
+ case 'a':
+ ctl.append = 1;
+ break;
+ case 'c':
+ command = optarg;
+ break;
+ case 'E':
+ if (strcmp(optarg, "auto") == 0)
+ ; /* keep default */
+ else if (strcmp(optarg, "never") == 0)
+ echo = 0;
+ else if (strcmp(optarg, "always") == 0)
+ echo = 1;
+ else
+ errx(EXIT_FAILURE, _("unssuported echo mode: '%s'"), optarg);
+ break;
+ case 'e':
+ ctl.rc_wanted = 1;
+ break;
+ case 'f':
+ ctl.flush = 1;
+ break;
+ case FORCE_OPTION:
+ ctl.force = 1;
+ break;
+ case 'B':
+ log_associate(&ctl, &ctl.in, optarg, SCRIPT_FMT_RAW);
+ log_associate(&ctl, &ctl.out, optarg, SCRIPT_FMT_RAW);
+ infile = outfile = optarg;
+ break;
+ case 'I':
+ log_associate(&ctl, &ctl.in, optarg, SCRIPT_FMT_RAW);
+ infile = optarg;
+ break;
+ case 'O':
+ log_associate(&ctl, &ctl.out, optarg, SCRIPT_FMT_RAW);
+ outfile = optarg;
+ break;
+ case 'o':
+ ctl.maxsz = strtosize_or_err(optarg, _("failed to parse output limit size"));
+ break;
+ case 'q':
+ ctl.quiet = 1;
+ break;
+ case 'm':
+ if (strcasecmp(optarg, "classic") == 0)
+ format = SCRIPT_FMT_TIMING_SIMPLE;
+ else if (strcasecmp(optarg, "advanced") == 0)
+ format = SCRIPT_FMT_TIMING_MULTI;
+ else
+ errx(EXIT_FAILURE, _("unsupported logging format: '%s'"), optarg);
+ break;
+ case 't':
+ if (optarg && *optarg == '=')
+ optarg++;
+ timingfile = optarg ? optarg : "/dev/stderr";
+ break;
+ case 'T' :
+ timingfile = optarg;
+ break;
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ /* default if no --log-* specified */
+ if (!outfile && !infile) {
+ if (argc > 0)
+ outfile = argv[0];
+ else {
+ die_if_link(&ctl, DEFAULT_TYPESCRIPT_FILENAME);
+ outfile = DEFAULT_TYPESCRIPT_FILENAME;
+ }
+
+ /* associate stdout with typescript file */
+ log_associate(&ctl, &ctl.out, outfile, SCRIPT_FMT_RAW);
+ }
+
+ if (timingfile) {
+ /* the old SCRIPT_FMT_TIMING_SIMPLE should be used when
+ * recoding output only (just for backward compatibility),
+ * otherwise switch to new format. */
+ if (!format)
+ format = infile || (outfile && infile) ?
+ SCRIPT_FMT_TIMING_MULTI :
+ SCRIPT_FMT_TIMING_SIMPLE;
+
+ else if (format == SCRIPT_FMT_TIMING_SIMPLE && outfile && infile)
+ errx(EXIT_FAILURE, _("log multiple streams is mutually "
+ "exclusive with 'classic' format"));
+ if (outfile)
+ log_associate(&ctl, &ctl.out, timingfile, format);
+ if (infile)
+ log_associate(&ctl, &ctl.in, timingfile, format);
+ }
+
+ shell = getenv("SHELL");
+ if (!shell)
+ shell = _PATH_BSHELL;
+
+ ctl.pty = ul_new_pty(ctl.isterm);
+ if (!ctl.pty)
+ err(EXIT_FAILURE, "failed to allocate PTY handler");
+
+ ul_pty_slave_echo(ctl.pty, echo);
+
+ ul_pty_set_callback_data(ctl.pty, (void *) &ctl);
+ cb = ul_pty_get_callbacks(ctl.pty);
+ cb->child_die = callback_child_die;
+ cb->child_sigstop = callback_child_sigstop;
+ cb->log_stream_activity = callback_log_stream_activity;
+ cb->log_signal = callback_log_signal;
+ cb->flush_logs = callback_flush_logs;
+
+ if (!ctl.quiet) {
+ printf(_("Script started"));
+ if (outfile)
+ printf(_(", output log file is '%s'"), outfile);
+ if (infile)
+ printf(_(", input log file is '%s'"), infile);
+ if (timingfile)
+ printf(_(", timing file is '%s'"), timingfile);
+ printf(_(".\n"));
+ }
+
+#ifdef HAVE_LIBUTEMPTER
+ utempter_add_record(ul_pty_get_childfd(ctl.pty), NULL);
+#endif
+
+ if (ul_pty_setup(ctl.pty))
+ err(EXIT_FAILURE, _("failed to create pseudo-terminal"));
+
+ fflush(stdout);
+
+ /*
+ * We have terminal, do not use err() from now, use "goto done"
+ */
+
+ switch ((int) (ctl.child = fork())) {
+ case -1: /* error */
+ warn(_("cannot create child process"));
+ rc = -errno;
+ goto done;
+
+ case 0: /* child */
+ {
+ const char *shname;
+
+ ul_pty_init_slave(ctl.pty);
+
+ signal(SIGTERM, SIG_DFL); /* because /etc/csh.login */
+
+ shname = strrchr(shell, '/');
+ shname = shname ? shname + 1 : shell;
+
+ if (access(shell, X_OK) == 0) {
+ if (command)
+ execl(shell, shname, "-c", command, (char *)NULL);
+ else
+ execl(shell, shname, "-i", (char *)NULL);
+ } else {
+ if (command)
+ execlp(shname, "-c", command, (char *)NULL);
+ else
+ execlp(shname, "-i", (char *)NULL);
+ }
+
+ err(EXIT_FAILURE, "failed to execute %s", shell);
+ break;
+ }
+ default:
+ break;
+ }
+
+ /* parent */
+ ul_pty_set_child(ctl.pty, ctl.child);
+
+ rc = logging_start(&ctl);
+ if (rc)
+ goto done;
+
+ /* add extra info to advanced timing file */
+ if (timingfile && format == SCRIPT_FMT_TIMING_MULTI) {
+ char buf[FORMAT_TIMESTAMP_MAX];
+ time_t tvec = script_time((time_t *)NULL);
+
+ strtime_iso(&tvec, ISO_TIMESTAMP, buf, sizeof(buf));
+ log_info(&ctl, "START_TIME", buf);
+
+ if (ctl.isterm) {
+ init_terminal_info(&ctl);
+ log_info(&ctl, "TERM", ctl.ttytype);
+ log_info(&ctl, "TTY", ctl.ttyname);
+ log_info(&ctl, "COLUMNS", "%d", ctl.ttycols);
+ log_info(&ctl, "LINES", "%d", ctl.ttylines);
+ }
+ log_info(&ctl, "SHELL", shell);
+ if (command)
+ log_info(&ctl, "COMMAND", command);
+ log_info(&ctl, "TIMING_LOG", timingfile);
+ if (outfile)
+ log_info(&ctl, "OUTPUT_LOG", outfile);
+ if (infile)
+ log_info(&ctl, "INPUT_LOG", infile);
+ }
+
+ /* this is the main loop */
+ rc = ul_pty_proxy_master(ctl.pty);
+
+ /* all done; cleanup and kill */
+ caught_signal = ul_pty_get_delivered_signal(ctl.pty);
+
+ if (!caught_signal && ctl.child != (pid_t)-1)
+ ul_pty_wait_for_child(ctl.pty); /* final wait */
+
+ if (caught_signal && ctl.child != (pid_t)-1) {
+ fprintf(stderr, "\nSession terminated, killing shell...");
+ kill(ctl.child, SIGTERM);
+ sleep(2);
+ kill(ctl.child, SIGKILL);
+ fprintf(stderr, " ...killed.\n");
+ }
+
+done:
+ ul_pty_cleanup(ctl.pty);
+ logging_done(&ctl, NULL);
+
+ if (!ctl.quiet)
+ printf(_("Script done.\n"));
+
+#ifdef HAVE_LIBUTEMPTER
+ if (ul_pty_get_childfd(ctl.pty) >= 0)
+ utempter_remove_record(ul_pty_get_childfd(ctl.pty));
+#endif
+ ul_free_pty(ctl.pty);
+
+ /* default exit code */
+ rc = rc ? EXIT_FAILURE : EXIT_SUCCESS;
+
+ /* exit code based on child status */
+ if (ctl.rc_wanted && rc == EXIT_SUCCESS) {
+ if (WIFSIGNALED(ctl.childstatus))
+ rc = WTERMSIG(ctl.childstatus) + 0x80;
+ else
+ rc = WEXITSTATUS(ctl.childstatus);
+ }
+
+ DBG(MISC, ul_debug("done [rc=%d]", rc));
+ return rc;
+}
diff --git a/term-utils/scriptlive.1 b/term-utils/scriptlive.1
new file mode 100644
index 0000000..fd553ad
--- /dev/null
+++ b/term-utils/scriptlive.1
@@ -0,0 +1,104 @@
+.TH SCRIPTLIVE 1 "October 2019" "util-linux" "User Commands"
+.SH NAME
+scriptlive \- re-run session typescripts, using timing information
+.SH SYNOPSIS
+.B scriptlive
+[options]
+.RB [ \-t ]
+.I timingfile
+.RB [ \-I|\-B ]
+.I typescript
+.SH DESCRIPTION
+This program re-runs a typescript,
+using stdin typescript and timing information to ensure that
+input happens in the same rhythm as it originally appeared when the script
+was recorded.
+.PP
+The \fBsession is executed\fR in a newly created pseudoterminal with
+the user's $SHELL
+(or defaults to /bin/bash).
+.PP
+.B Be careful!
+Do not forget that the typescript may contains arbitrary commands.
+It is recommended to use \fB"scriptreplay \-\-stream in \-\-log\-in typescript"\fR
+(or with
+.B \-\-log\-io
+instead of
+.BR \-\-log\-in\)
+to verify the typescript before it is executed by
+.BR scriptlive (1).
+.PP
+The timing information is what
+.BR script (1)
+outputs to file specified by
+.BR \-\-log\-timing .
+The typescript has to contain stdin information and it is what
+.BR script (1)
+outputs to file specified by
+.B \-\-log-in
+or
+.BR \-\-log\-io .
+
+.SH OPTIONS
+.TP
+.BR \-I , " \-\-log-in " \fIfile\fR
+File containing \fBscript\fR's terminal input.
+.TP
+.BR \-B , " \-\-log-io " \fIfile\fR
+File containing \fBscript\fR's terminal output and input.
+.TP
+.BR \-t , " \-\-timing " \fIfile\fR
+File containing \fBscript\fR's timing output. This option overrides old-style arguments.
+.TP
+.BR \-T , " \-\-log\-timing " \fIfile\fR
+Aliased to \fB\-t\fR, maintained for compatibility with
+.BR script (1)
+command-line options.
+.TP
+.BR \-d , " \-\-divisor " \fInumber\fR
+Speed up the replay displaying this
+.I number
+of times. The argument is a floating-point number. It's called divisor
+because it divides the timings by this factor. This option overrides old-style arguments.
+.TP
+.BR \-m , " \-\-maxdelay " \fInumber\fR
+Set the maximum delay between updates to
+.I number
+of seconds. The argument is a floating-point number. This can be used to
+avoid long pauses in the typescript replay.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH EXAMPLES
+.nf
+% script --log-timing file.tm --log-in script.in
+Script started, file is script.out
+% date
+<etc, etc>
+% exit
+Script done, file is script.out
+% scriptlive --log-timing file.tm --log-in script.in
+.fi
+.SH AUTHORS
+.MT kzak@\:redhat.com
+Karel Zak
+.ME .
+.SH COPYRIGHT
+Copyright \(co 2019 Karel Zak
+.PP
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR
+PURPOSE.
+.PP
+Released under the GNU General Public License version 2 or later.
+.SH SEE ALSO
+.BR script (1),
+.BR scriptreplay (1)
+.SH AVAILABILITY
+The scriptlive command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/term-utils/scriptlive.c b/term-utils/scriptlive.c
new file mode 100644
index 0000000..ac75588
--- /dev/null
+++ b/term-utils/scriptlive.c
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2019, Karel Zak <kzak@redhat.com>
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <limits.h>
+#include <math.h>
+#include <sys/select.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <paths.h>
+
+#include "c.h"
+#include "xalloc.h"
+#include "closestream.h"
+#include "nls.h"
+#include "strutils.h"
+#include "optutils.h"
+#include "pty-session.h"
+#include "script-playutils.h"
+#include "monotonic.h"
+
+
+#define SCRIPT_MIN_DELAY 0.0001 /* from original sripreplay.pl */
+
+struct scriptlive {
+ struct ul_pty *pty;
+ struct replay_setup *setup;
+ struct replay_step *step;
+};
+
+static void __attribute__((__noreturn__))
+usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out,
+ _(" %s [options]\n"),
+ program_invocation_short_name);
+ fprintf(out,
+ _(" %s [-t] timingfile [-I|-B] typescript\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Execute terminal typescript.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -t, --timing <file> script timing log file\n"), out);
+ fputs(_(" -T, --log-timing <file> alias to -t\n"), out);
+ fputs(_(" -I, --log-in <file> script stdin log file\n"), out);
+ fputs(_(" -B, --log-io <file> script stdin and stdout log file\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_(" -c, --command <command> run command rather than interactive shell\n"), out);
+ fputs(_(" -d, --divisor <num> speed up or slow down execution with time divisor\n"), out);
+ fputs(_(" -m, --maxdelay <num> wait at most this many seconds between updates\n"), out);
+ printf(USAGE_HELP_OPTIONS(25));
+
+ printf(USAGE_MAN_TAIL("scriptlive(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+static double
+getnum(const char *s)
+{
+ const double d = strtod_or_err(s, _("failed to parse number"));
+
+ if (isnan(d)) {
+ errno = EINVAL;
+ err(EXIT_FAILURE, "%s: %s", _("failed to parse number"), s);
+ }
+ return d;
+}
+
+static void callback_child_sigstop(
+ void *data __attribute__((__unused__)),
+ pid_t child)
+{
+ kill(getpid(), SIGSTOP);
+ kill(child, SIGCONT);
+}
+
+static int process_next_step(struct scriptlive *ss)
+{
+ int rc = 0, fd = ul_pty_get_childfd(ss->pty);
+
+ /* read next step(s) */
+ do {
+ struct timeval *delay;
+
+ rc = replay_get_next_step(ss->setup, "I", &ss->step);
+ if (rc == 1) {
+ ul_pty_write_eof_to_child(ss->pty);
+ rc = 0;
+ break;
+ }
+ if (rc)
+ break;
+
+ delay = replay_step_get_delay(ss->step);
+ if (timerisset(delay)) {
+ /* wait until now+delay in mainloop */
+ struct timeval now, target;
+
+ gettime_monotonic(&now);
+ timeradd(&now, delay, &target);
+
+ ul_pty_set_mainloop_time(ss->pty, &target);
+ break;
+ }
+
+ /* no delay -- immediately write */
+ rc = replay_emit_step_data(ss->setup, ss->step, fd);
+ fdatasync(fd);
+ } while (rc == 0);
+
+ return rc;
+}
+
+static int mainloop_cb(void *data)
+{
+ struct scriptlive *ss = (struct scriptlive *) data;
+ int rc = 0;
+
+ /* emit previous waiting step */
+ if (ss->step && !replay_step_is_empty(ss->step)) {
+ int fd = ul_pty_get_childfd(ss->pty);;
+
+ rc = replay_emit_step_data(ss->setup, ss->step, fd);
+ fdatasync(fd);
+ if (rc)
+ return rc;
+ }
+
+ return process_next_step(ss);
+}
+
+int
+main(int argc, char *argv[])
+{
+ static const struct timeval mindelay = { .tv_sec = 0, .tv_usec = 100 };
+ struct timeval maxdelay;
+
+ const char *log_in = NULL, *log_io = NULL, *log_tm = NULL,
+ *shell = NULL, *command = NULL;
+ double divi = 1;
+ int diviopt = FALSE, idx;
+ int ch, caught_signal = 0;
+ struct ul_pty_callbacks *cb;
+ struct scriptlive ss = { .pty = NULL };
+ pid_t child;
+
+ static const struct option longopts[] = {
+ { "command", required_argument, 0, 'c' },
+ { "timing", required_argument, 0, 't' },
+ { "log-timing", required_argument, 0, 'T' },
+ { "log-in", required_argument, 0, 'I'},
+ { "log-io", required_argument, 0, 'B'},
+ { "divisor", required_argument, 0, 'd' },
+ { "maxdelay", required_argument, 0, 'm' },
+ { "version", no_argument, 0, 'V' },
+ { "help", no_argument, 0, 'h' },
+ { NULL, 0, 0, 0 }
+ };
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'B', 'I' },
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+ /* Because we use space as a separator, we can't afford to use any
+ * locale which tolerates a space in a number. In any case, script.c
+ * sets the LC_NUMERIC locale to C, anyway.
+ */
+ setlocale(LC_ALL, "");
+ setlocale(LC_NUMERIC, "C");
+
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ replay_init_debug();
+ timerclear(&maxdelay);
+
+ while ((ch = getopt_long(argc, argv, "c:B:I:T:t:d:m:Vh", longopts, NULL)) != -1) {
+
+ err_exclusive_options(ch, longopts, excl, excl_st);
+
+ switch(ch) {
+ case 'c':
+ command = optarg;
+ break;
+ case 't':
+ case 'T':
+ log_tm = optarg;
+ break;
+ case 'I':
+ log_in = optarg;
+ break;
+ case 'B':
+ log_io = optarg;
+ break;
+ case 'd':
+ diviopt = TRUE;
+ divi = getnum(optarg);
+ break;
+ case 'm':
+ strtotimeval_or_err(optarg, &maxdelay, _("failed to parse maximal delay argument"));
+ break;
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ idx = 0;
+
+ if (!log_tm && idx < argc)
+ log_tm = argv[idx++];
+ if (!log_in && !log_io && idx < argc)
+ log_in = argv[idx++];
+
+ if (!diviopt)
+ divi = idx < argc ? getnum(argv[idx]) : 1;
+
+ if (!log_tm)
+ errx(EXIT_FAILURE, _("timing file not specified"));
+ if (!(log_in || log_io))
+ errx(EXIT_FAILURE, _("stdin typescript file not specified"));
+
+ ss.setup = replay_new_setup();
+
+ if (replay_set_timing_file(ss.setup, log_tm) != 0)
+ err(EXIT_FAILURE, _("cannot open %s"), log_tm);
+
+ if (log_in && replay_associate_log(ss.setup, "I", log_in) != 0)
+ err(EXIT_FAILURE, _("cannot open %s"), log_in);
+
+ if (log_io && replay_associate_log(ss.setup, "IO", log_io) != 0)
+ err(EXIT_FAILURE, _("cannot open %s"), log_io);
+
+ replay_set_default_type(ss.setup, 'I');
+ replay_set_crmode(ss.setup, REPLAY_CRMODE_NEVER);
+
+ if (divi != 1)
+ replay_set_delay_div(ss.setup, divi);
+ if (timerisset(&maxdelay))
+ replay_set_delay_max(ss.setup, &maxdelay);
+ replay_set_delay_min(ss.setup, &mindelay);
+
+ shell = getenv("SHELL");
+ if (shell == NULL)
+ shell = _PATH_BSHELL;
+
+ fprintf(stdout, _(">>> scriptlive: Starting your typescript execution by %s.\n"),
+ command ? command : shell);
+
+ ul_pty_init_debug(0);
+
+ ss.pty = ul_new_pty(isatty(STDIN_FILENO));
+ if (!ss.pty)
+ err(EXIT_FAILURE, _("failed to allocate PTY handler"));
+
+ ul_pty_set_callback_data(ss.pty, (void *) &ss);
+ cb = ul_pty_get_callbacks(ss.pty);
+ cb->child_sigstop = callback_child_sigstop;
+ cb->mainloop = mainloop_cb;
+
+ if (!isatty(STDIN_FILENO))
+ /* We keep ECHO flag for compatibility with script(1) */
+ ul_pty_slave_echo(ss.pty, 1);
+
+ if (ul_pty_setup(ss.pty))
+ err(EXIT_FAILURE, _("failed to create pseudo-terminal"));
+
+ fflush(stdout); /* ??? */
+
+ switch ((int) (child = fork())) {
+ case -1: /* error */
+ ul_pty_cleanup(ss.pty);
+ err(EXIT_FAILURE, _("cannot create child process"));
+ break;
+
+ case 0: /* child */
+ {
+ const char *shname;
+
+ ul_pty_init_slave(ss.pty);
+
+ signal(SIGTERM, SIG_DFL); /* because /etc/csh.login */
+
+ shname = strrchr(shell, '/');
+ shname = shname ? shname + 1 : shell;
+
+ if (access(shell, X_OK) == 0) {
+ if (command)
+ execl(shell, shname, "-c", command, (char *)NULL);
+ else
+ execl(shell, shname, "-i", (char *)NULL);
+ } else {
+ if (command)
+ execlp(shname, "-c", command, (char *)NULL);
+ else
+ execlp(shname, "-i", (char *)NULL);
+ }
+ err(EXIT_FAILURE, "failed to execute %s", shell);
+ break;
+ }
+ default:
+ break;
+ }
+
+ /* parent */
+ ul_pty_set_child(ss.pty, child);
+
+ /* read the first step and set initial delay for pty main loop; the
+ * next steps will be processed by mainloop_cb() */
+ process_next_step(&ss);
+
+ /* this is the main loop */
+ ul_pty_proxy_master(ss.pty);
+
+ /* all done; cleanup and kill */
+ caught_signal = ul_pty_get_delivered_signal(ss.pty);
+
+ if (!caught_signal && ul_pty_get_child(ss.pty) != (pid_t)-1)
+ ul_pty_wait_for_child(ss.pty); /* final wait */
+
+ if (caught_signal && ul_pty_get_child(ss.pty) != (pid_t)-1) {
+ fprintf(stderr, _("\nSession terminated, killing shell..."));
+ kill(child, SIGTERM);
+ sleep(2);
+ kill(child, SIGKILL);
+ fprintf(stderr, " ...killed.\n");
+ }
+
+ ul_pty_cleanup(ss.pty);
+ ul_free_pty(ss.pty);
+ replay_free_setup(ss.setup);
+
+ fprintf(stdout, _("\n>>> scriptlive: done.\n"));
+ return EXIT_SUCCESS;
+}
diff --git a/term-utils/scriptreplay.1 b/term-utils/scriptreplay.1
new file mode 100644
index 0000000..18aadd5
--- /dev/null
+++ b/term-utils/scriptreplay.1
@@ -0,0 +1,159 @@
+.TH SCRIPTREPLAY 1 "October 2019" "util-linux" "User Commands"
+.SH NAME
+scriptreplay \- play back typescripts, using timing information
+.SH SYNOPSIS
+.B scriptreplay
+[options]
+.RB [ \-t ]
+.I timingfile
+.RI [ typescript
+.RI [ divisor ]]
+.SH DESCRIPTION
+This program replays a typescript, using timing information to ensure that
+output happens in the same rhythm as it originally appeared when the script
+was recorded.
+.PP
+The replay simply displays the information again; the programs
+that were run when the typescript was being recorded are \fBnot run again\fR.
+Since the same information is simply being displayed,
+.B scriptreplay
+is only guaranteed to work properly if run on the same type of
+terminal the typescript was recorded on. Otherwise, any escape characters
+in the typescript may be interpreted differently by the terminal to
+which
+.B scriptreplay
+is sending its output.
+.PP
+The timing information is what
+.BR script (1)
+outputs to file specified by
+.BR \-\-log-timing .
+.PP
+By default, the typescript to display is assumed to be named
+.IR typescript ,
+but other filenames may be specified, as the second parameter or with option
+.BR \-\-log\-out .
+.PP
+If the third parameter or
+.B \-\-divisor
+is specified, it is used as a speed-up multiplier.
+For example, a speed-up of 2 makes
+.B scriptreplay
+go twice as fast, and a speed-up of 0.1 makes it go ten times slower
+than the original session.
+.SH OPTIONS
+.TP
+.BR \-I , " \-\-log-in " \fIfile\fR
+File containing \fBscript\fR's terminal input.
+.TP
+.BR \-O , " \-\-log-out " \fIfile\fR
+File containing \fBscript\fR's terminal output.
+.TP
+.BR \-B , " \-\-log-io " \fIfile\fR
+File containing \fBscript\fR's terminal output and input.
+.TP
+.BR \-t , " \-\-timing " \fIfile\fR
+File containing \fBscript\fR's timing output. This option overrides old-style arguments.
+.TP
+.BR \-T , " \-\-log\-timing " \fIfile\fR
+This is an alias for \fB\-t\fR, maintained for compatibility with
+.BR script (1)
+command-line options.
+.TP
+.BR \-s , " \-\-typescript " \fIfile\fR
+File containing \fBscript\fR's terminal output. Deprecated alias to \fB\-\-log-out\fR.
+This option overrides old-style arguments.
+.TP
+.BR \-c , " \-\-cr\-mode " \fImode\fR
+Specifies how to use the CR (0x0D, carriage return) character from log files.
+The default mode is
+.IR auto ,
+in this case CR is replaced with line break for stdin log, because otherwise
+.B scriptreplay
+would overwrite the same line. The other modes are
+.I never
+and
+.IR always .
+.TP
+.BR \-d , " \-\-divisor " \fInumber\fR
+Speed up the replay displaying this
+.I number
+of times. The argument is a floating-point number. It's called divisor
+because it divides the timings by this factor. This option overrides old-style arguments.
+.TP
+.BR \-m , " \-\-maxdelay " \fInumber\fR
+Set the maximum delay between updates to
+.I number
+of seconds. The argument is a floating-point number. This can be used to
+avoid long pauses in the typescript replay.
+.TP
+.B \-\-summary
+Display details about the session recorded in the specified timing file
+and exit. The session has to be recorded using
+.I advanced
+format (see
+.BR script (1))
+option \fB\-\-logging\-format\fR for more details).
+.TP
+.BR \-x , " \-\-stream " \fItype\fR
+Forces
+.B scriptreplay
+to print only the specified stream. The supported stream types
+are
+.IR in ,
+.IR out ,
+.IR signal ,
+or
+.IR info .
+This option is recommended for multi-stream logs (e.g.,
+.BR \-\-log-io )
+in order to print only specified data.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH EXAMPLES
+.nf
+% script --log-timing file.tm --log-out script.out
+Script started, file is script.out
+% ls
+<etc, etc>
+% exit
+Script done, file is script.out
+% scriptreplay --log-timing file.tm --log-out script.out
+.fi
+.SH AUTHORS
+The original
+.B scriptreplay
+program was written by
+.MT joey@\:kitenet.net
+Joey Hess
+.ME .
+The program was re-written in C by
+.MT jay@\:gnu.org
+James Youngman
+.ME
+and
+.MT kzak@\:redhat.com
+Karel Zak
+.ME .
+.SH COPYRIGHT
+Copyright \(co 2008 James Youngman
+.br
+Copyright \(co 2008-2019 Karel Zak
+.PP
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR
+PURPOSE.
+.PP
+Released under the GNU General Public License version 2 or later.
+.SH SEE ALSO
+.BR script (1),
+.BR scriptlive (1)
+.SH AVAILABILITY
+The scriptreplay command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/term-utils/scriptreplay.c b/term-utils/scriptreplay.c
new file mode 100644
index 0000000..bca1d21
--- /dev/null
+++ b/term-utils/scriptreplay.c
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2008-2019, Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2008, James Youngman <jay@gnu.org>
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ *
+ * Based on scriptreplay.pl by Joey Hess <joey@kitenet.net>
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <limits.h>
+#include <math.h>
+#include <sys/select.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <sys/time.h>
+
+#include "c.h"
+#include "xalloc.h"
+#include "closestream.h"
+#include "nls.h"
+#include "strutils.h"
+#include "optutils.h"
+#include "script-playutils.h"
+
+static void __attribute__((__noreturn__))
+usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out,
+ _(" %s [options]\n"),
+ program_invocation_short_name);
+ fprintf(out,
+ _(" %s [-t] timingfile [typescript] [divisor]\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Play back terminal typescripts, using timing information.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -t, --timing <file> script timing log file\n"), out);
+ fputs(_(" -T, --log-timing <file> alias to -t\n"), out);
+ fputs(_(" -I, --log-in <file> script stdin log file\n"), out);
+ fputs(_(" -O, --log-out <file> script stdout log file (default)\n"), out);
+ fputs(_(" -B, --log-io <file> script stdin and stdout log file\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_(" -s, --typescript <file> deprecated alias to -O\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_(" --summary display overview about recorded session and exit\n"), out);
+ fputs(_(" -d, --divisor <num> speed up or slow down execution with time divisor\n"), out);
+ fputs(_(" -m, --maxdelay <num> wait at most this many seconds between updates\n"), out);
+ fputs(_(" -x, --stream <name> stream type (out, in, signal or info)\n"), out);
+ fputs(_(" -c, --cr-mode <type> CR char mode (auto, never, always)\n"), out);
+ printf(USAGE_HELP_OPTIONS(25));
+
+ printf(USAGE_MAN_TAIL("scriptreplay(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+static double
+getnum(const char *s)
+{
+ const double d = strtod_or_err(s, _("failed to parse number"));
+
+ if (isnan(d)) {
+ errno = EINVAL;
+ err(EXIT_FAILURE, "%s: %s", _("failed to parse number"), s);
+ }
+ return d;
+}
+
+static void
+delay_for(struct timeval *delay)
+{
+#ifdef HAVE_NANOSLEEP
+ struct timespec ts, remainder;
+ ts.tv_sec = (time_t) delay->tv_sec;
+ ts.tv_nsec = delay->tv_usec * 1000;
+
+ DBG(TIMING, ul_debug("going to sleep for %ld.%06ld",
+ delay->tv_sec,
+ delay->tv_usec));
+
+ while (-1 == nanosleep(&ts, &remainder)) {
+ if (EINTR == errno)
+ ts = remainder;
+ else
+ break;
+ }
+#else
+ select(0, NULL, NULL, NULL, delay);
+#endif
+}
+
+static void appendchr(char *buf, size_t bufsz, int c)
+{
+ size_t sz;
+
+ if (strchr(buf, c))
+ return; /* already in */
+
+ sz = strlen(buf);
+ if (sz + 1 < bufsz)
+ buf[sz] = c;
+}
+
+int
+main(int argc, char *argv[])
+{
+ static const struct timeval mindelay = { .tv_sec = 0, .tv_usec = 100 };
+ struct timeval maxdelay;
+
+ struct replay_setup *setup = NULL;
+ struct replay_step *step = NULL;
+ char streams[6] = {0}; /* IOSI - in, out, signal,info */
+ const char *log_out = NULL,
+ *log_in = NULL,
+ *log_io = NULL,
+ *log_tm = NULL;
+ double divi = 1;
+ int diviopt = FALSE, idx;
+ int ch, rc, crmode = REPLAY_CRMODE_AUTO, summary = 0;
+ enum {
+ OPT_SUMMARY = CHAR_MAX + 1
+ };
+
+ static const struct option longopts[] = {
+ { "cr-mode", required_argument, 0, 'c' },
+ { "timing", required_argument, 0, 't' },
+ { "log-timing", required_argument, 0, 'T' },
+ { "log-in", required_argument, 0, 'I' },
+ { "log-out", required_argument, 0, 'O' },
+ { "log-io", required_argument, 0, 'B' },
+ { "typescript", required_argument, 0, 's' },
+ { "divisor", required_argument, 0, 'd' },
+ { "maxdelay", required_argument, 0, 'm' },
+ { "stream", required_argument, 0, 'x' },
+ { "summary", no_argument, 0, OPT_SUMMARY },
+ { "version", no_argument, 0, 'V' },
+ { "help", no_argument, 0, 'h' },
+ { NULL, 0, 0, 0 }
+ };
+ static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
+ { 'O', 's' },
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+ /* Because we use space as a separator, we can't afford to use any
+ * locale which tolerates a space in a number. In any case, script.c
+ * sets the LC_NUMERIC locale to C, anyway.
+ */
+ setlocale(LC_ALL, "");
+ setlocale(LC_NUMERIC, "C");
+
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ replay_init_debug();
+ timerclear(&maxdelay);
+
+ while ((ch = getopt_long(argc, argv, "B:c:I:O:T:t:s:d:m:x:Vh", longopts, NULL)) != -1) {
+
+ err_exclusive_options(ch, longopts, excl, excl_st);
+
+ switch(ch) {
+ case 'c':
+ if (strcmp("auto", optarg) == 0)
+ crmode = REPLAY_CRMODE_AUTO;
+ else if (strcmp("never", optarg) == 0)
+ crmode = REPLAY_CRMODE_NEVER;
+ else if (strcmp("always", optarg) == 0)
+ crmode = REPLAY_CRMODE_ALWAYS;
+ else
+ errx(EXIT_FAILURE, _("unsupported mode name: '%s'"), optarg);
+ break;
+ case 't':
+ case 'T':
+ log_tm = optarg;
+ break;
+ case 'O':
+ case 's':
+ log_out = optarg;
+ break;
+ case 'I':
+ log_in = optarg;
+ break;
+ case 'B':
+ log_io = optarg;
+ break;
+ case 'd':
+ diviopt = TRUE;
+ divi = getnum(optarg);
+ break;
+ case 'm':
+ strtotimeval_or_err(optarg, &maxdelay, _("failed to parse maximal delay argument"));
+ break;
+ case 'x':
+ if (strcmp("in", optarg) == 0)
+ appendchr(streams, sizeof(streams), 'I');
+ else if (strcmp("out", optarg) == 0)
+ appendchr(streams, sizeof(streams), 'O');
+ else if (strcmp("signal", optarg) == 0)
+ appendchr(streams, sizeof(streams), 'S');
+ else if (strcmp("info", optarg) == 0)
+ appendchr(streams, sizeof(streams), 'H');
+ else
+ errx(EXIT_FAILURE, _("unsupported stream name: '%s'"), optarg);
+ break;
+ case OPT_SUMMARY:
+ summary = 1;
+ break;
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ idx = 0;
+
+ if (summary)
+ streams[0] = 'H', streams[1] = '\0';
+
+ if (!log_tm && idx < argc)
+ log_tm = argv[idx++];
+ if (!log_out && !summary && !log_in && !log_io)
+ log_out = idx < argc ? argv[idx++] : "typescript";
+
+ if (!diviopt)
+ divi = idx < argc ? getnum(argv[idx]) : 1;
+
+ if (!log_tm)
+ errx(EXIT_FAILURE, _("timing file not specified"));
+ if (!(log_out || log_in || log_io) && !summary)
+ errx(EXIT_FAILURE, _("data log file not specified"));
+
+ setup = replay_new_setup();
+
+ if (replay_set_timing_file(setup, log_tm) != 0)
+ err(EXIT_FAILURE, _("cannot open %s"), log_tm);
+
+ if (log_out && replay_associate_log(setup, "O", log_out) != 0)
+ err(EXIT_FAILURE, _("cannot open %s"), log_out);
+
+ if (log_in && replay_associate_log(setup, "I", log_in) != 0)
+ err(EXIT_FAILURE, _("cannot open %s"), log_in);
+
+ if (log_io && replay_associate_log(setup, "IO", log_io) != 0)
+ err(EXIT_FAILURE, _("cannot open %s"), log_io);
+
+ if (!*streams) {
+ /* output is preferred default */
+ if (log_out || log_io)
+ appendchr(streams, sizeof(streams), 'O');
+ else if (log_in)
+ appendchr(streams, sizeof(streams), 'I');
+ }
+
+ replay_set_default_type(setup,
+ *streams && streams[1] == '\0' ? *streams : 'O');
+ replay_set_crmode(setup, crmode);
+
+ if (divi != 1)
+ replay_set_delay_div(setup, divi);
+ if (timerisset(&maxdelay))
+ replay_set_delay_max(setup, &maxdelay);
+ replay_set_delay_min(setup, &mindelay);
+
+ do {
+ rc = replay_get_next_step(setup, streams, &step);
+ if (rc)
+ break;
+
+ if (!summary) {
+ struct timeval *delay = replay_step_get_delay(step);
+
+ if (delay && timerisset(delay))
+ delay_for(delay);
+ }
+ rc = replay_emit_step_data(setup, step, STDOUT_FILENO);
+ } while (rc == 0);
+
+ if (step && rc < 0)
+ err(EXIT_FAILURE, _("%s: log file error"), replay_step_get_filename(step));
+ else if (rc < 0)
+ err(EXIT_FAILURE, _("%s: line %d: timing file error"),
+ replay_get_timing_file(setup),
+ replay_get_timing_line(setup));
+ printf("\n");
+ replay_free_setup(setup);
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/term-utils/setterm.1 b/term-utils/setterm.1
new file mode 100644
index 0000000..42bad04
--- /dev/null
+++ b/term-utils/setterm.1
@@ -0,0 +1,307 @@
+.\" Copyright 1990 Gordon Irlam (gordoni@cs.ua.oz.au)
+.\" Copyright 1992 Rickard E. Faith (faith@cs.unc.edu)
+.\" Copyright 2000 Colin Watson (cjw44@cam.ac.uk)
+.\" Do not restrict distribution.
+.\" May be distributed under the GNU General Public License
+.\"
+.TH SETTERM 1 "May 2014" "util-linux" "User Commands"
+.SH NAME
+setterm \- set terminal attributes
+.SH SYNOPSIS
+.B setterm
+[options]
+.SH DESCRIPTION
+.B setterm
+writes to standard output a character string that will invoke the specified
+terminal capabilities. Where possible
+.I terminfo
+is consulted to find the string to use. Some options however (marked "virtual
+consoles only" below) do not correspond to a
+.BR terminfo (5)
+capability. In this case, if the terminal type is "con" or "linux" the string
+that invokes the specified capabilities on the PC Minix virtual console driver
+is output. Options that are not implemented by the terminal are ignored.
+.SH OPTIONS
+For boolean options
+.RB ( on " or " off ),
+the default is
+.BR on .
+.P
+Below, an
+.I 8-color
+can be
+.BR black ,
+.BR red ,
+.BR green ,
+.BR yellow ,
+.BR blue ,
+.BR magenta ,
+.BR cyan ,
+or
+.BR white .
+.P
+A
+.I 16-color
+can be an
+.IR 8-color ,
+or
+.BR grey ,
+or
+.B bright
+followed
+by
+.BR red ,
+.BR green ,
+.BR yellow ,
+.BR blue ,
+.BR magenta ,
+.BR cyan ,
+or
+.BR white .
+.P
+The various color options may be set independently, at least on virtual
+consoles, though the results of setting multiple modes (for example,
+.B \-\-underline
+and
+.BR \-\-half\-bright )
+are hardware-dependent.
+.PP
+The optional arguments require '=' (equals sign) and not space between the
+option and the argument. For example --option=argument.
+.TP
+\fB\-\-appcursorkeys\fP on|off
+Sets Cursor Key Application Mode on or off. When on, ESC O A, ESC O B, etc.
+will be sent for the cursor keys instead of ESC [ A, ESC [ B, etc. See the
+.I vi and Cursor-Keys
+section of the
+.I Text-Terminal-HOWTO
+for how this can cause problems for \fBvi\fR users.
+Virtual consoles only.
+.TP
+\fB\-\-append\fP \fIconsole_number\fP
+Like
+.BR \-\-dump ,
+but appends to the snapshot file instead of overwriting it. Only works if no
+.B \-\-dump
+options are given.
+.TP
+\fB\-\-background\fP \fI8-color\fP|default
+Sets the background text color.
+.TP
+\fB\-\-blank\fP[=0-60|force|poke]
+Sets the interval of inactivity, in minutes, after which the screen will be
+automatically blanked (using APM if available). Without an argument, it gets
+the blank status (returns which vt was blanked, or zero for an unblanked vt).
+Virtual consoles only.
+.IP
+The
+.B force
+argument keeps the screen blank even if a key is pressed.
+.IP
+The
+.B poke
+argument unblanks the screen.
+.TP
+\fB\-\-bfreq\fP[=\fInumber\fP]
+Sets the bell frequency in Hertz. Without an argument, it defaults to
+.BR 0 .
+Virtual consoles only.
+.TP
+\fB\-\-blength\fP[=0-2000]
+Sets the bell duration in milliseconds. Without an argument, it defaults to
+.BR 0 .
+Virtual consoles only.
+.TP
+\fB\-\-blink\fP on|off
+Turns blink mode on or off. Except on a virtual console,
+.B \-\-blink off
+turns off all attributes (bold, half-brightness, blink, reverse).
+.TP
+\fB\-\-bold\fP on|off
+urns bold (extra bright) mode on or off. Except on a virtual console,
+.B \-\-bold off
+turns off all attributes (bold, half-brightness, blink, reverse).
+.TP
+\fB\-\-clear\fP[=all|rest]
+Without an argument or with the argument
+.BR all ,
+the entire screen is cleared and the cursor is set to the home position,
+just like
+.BR clear (1)
+does. With the argument
+.BR rest ,
+the screen is cleared from the current cursor position to the end.
+.TP
+\fB\-\-clrtabs\fP[=\fItab1 tab2 tab3\fP ...]
+Clears tab stops from the given horizontal cursor positions, in the range
+.BR 1-160 .
+Without arguments, it clears all tab stops.
+Virtual consoles only.
+.TP
+\fB\-\-cursor\fP on|off
+Turns the terminal's cursor on or off.
+.TP
+\fB\-\-default\fP
+Sets the terminal's rendering options to the default values.
+.TP
+\fB\-\-dump\fP[=\fIconsole_number\fP]
+Writes a snapshot of the virtual console with the given number
+to the file specified with the
+.B \-\-file
+option, overwriting its contents; the default is
+.IR screen.dump .
+Without an argument, it dumps the current virtual console. This overrides
+.BR \-\-append .
+.TP
+\fB\-\-file\fP \fIfilename\fP
+Sets the snapshot file name for any
+.B \-\-dump
+or
+.B \-\-append
+options on the same command line. If this option is not present, the default
+is
+.I screen.dump
+in the current directory. A path name that exceeds the system maximum will be
+truncated, see PATH_MAX from linux/limits.h for the value.
+.TP
+\fB\-\-foreground\fP \fI8-color\fP|default
+Sets the foreground text color.
+.TP
+\fB\-\-half\-bright\fP on|off
+Turns dim (half-brightness) mode on or off. Except on a virtual console,
+.B \-\-half\-bright off
+turns off all attributes (bold, half-brightness, blink, reverse).
+.TP
+\fB\-\-hbcolor\fP [bright] \fI16-color\fP
+Sets the color for half-bright characters.
+.TP
+\fB\-\-initialize\fP
+Displays the terminal initialization string, which typically sets the
+terminal's rendering options, and other attributes to the default values.
+.TP
+\fB\-\-inversescreen\fP on|off
+Swaps foreground and background colors for the whole screen.
+.TP
+\fB\-\-linewrap\fP on|off
+Makes the terminal continue on a new line when a line is full.
+.TP
+\fB\-\-msg\fP on|off
+Enables or disables the sending of kernel
+.BR printk ()
+messages to the console.
+Virtual consoles only.
+.TP
+\fB\-\-msglevel\fP 0-8
+Sets the console logging level for kernel
+.B printk()
+messages. All messages strictly more important than this will be printed, so a
+logging level of
+.B 0
+has the same effect as
+.B \-\-msg on
+and a logging level of
+.B 8
+will print all kernel messages.
+.BR klogd (8)
+may be a more convenient interface to the logging of kernel messages.
+.sp
+Virtual consoles only.
+.TP
+\fB\-\-powerdown\fP[=0-60]
+Sets the VESA powerdown interval in minutes. Without an argument, it defaults
+to
+.B 0
+(disable powerdown). If the console is blanked or the monitor is in suspend
+mode, then the monitor will go into vsync suspend mode or powerdown mode
+respectively after this period of time has elapsed.
+.TP
+\fB\-\-powersave\fP \fImode\fP
+Valid values for \fImode\fP are:
+.RS
+.TP
+.B vsync|on
+Puts the monitor into VESA vsync suspend mode.
+.TP
+.B hsync
+Puts the monitor into VESA hsync suspend mode.
+.TP
+.B powerdown
+Puts the monitor into VESA powerdown mode.
+.TP
+.B off
+Turns monitor VESA powersaving features.
+.RE
+.TP
+\fB\-\-regtabs\fP[=1-160]
+Clears all tab stops, then sets a regular tab stop pattern, with one tab every
+specified number of positions. Without an argument, it defaults to
+.BR 8 .
+Virtual consoles only.
+.TP
+\fB\-\-repeat\fP on|off
+Turns keyboard repeat on or off.
+Virtual consoles only.
+.TP
+\fB\-\-reset\fP
+Displays the terminal reset string, which typically resets the terminal to
+its power-on state.
+.TP
+\fB\-\-resize\fP
+Reset terminal size by assessing maximum row and column. This is useful
+when actual geometry and kernel terminal driver are not in sync. Most
+notable use case is with serial consoles, that do not use
+.BR ioctl (3p)
+but just byte streams and breaks.
+.TP
+\fB\-\-reverse\fP on|off
+Turns reverse video mode on or off. Except on a virtual console,
+.B \-\-reverse off
+turns off all attributes (bold, half-brightness, blink, reverse).
+.TP
+\fB\-\-store\fP
+Stores the terminal's current rendering options (foreground and background
+colors) as the values to be used at reset-to-default.
+Virtual consoles only.
+.TP
+\fB\-\-tabs\fP[=\fItab1 tab2 tab3\fP ...]
+Sets tab stops at the given horizontal cursor positions, in the range
+.BR 1-160 .
+Without arguments, it shows the current tab stop settings.
+.TP
+\fB\-\-term\fP \fIterminal_name\fP
+Overrides the TERM environment variable.
+.TP
+\fB\-\-ulcolor\fP [bright] \fI16-color\fP
+Sets the color for underlined characters.
+Virtual consoles only.
+.TP
+\fB\-\-underline\fP on|off
+Turns underline mode on or off.
+.TP
+\fB\-\-version\fP
+Displays version information and exits.
+.TP
+\fB\-\-help\fP
+Displays a help text and exits.
+.SH COMPATIBILITY
+Since version 2.25
+.B setterm
+has support for long options with two hyphens, for example
+.BR \-\-help ,
+beside the historical long options with a single hyphen, for example
+.BR \-help .
+In scripts it is better to use the backward-compatible single hyphen
+rather than the double hyphen. Currently there are no plans nor good
+reasons to discontinue single-hyphen compatibility.
+.SH BUGS
+Differences between the Minix and Linux versions are not documented.
+.SH SEE ALSO
+.BR stty (1),
+.BR tput (1),
+.BR tty (4),
+.BR terminfo (5)
+.SH AVAILABILITY
+The setterm command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/term-utils/setterm.c b/term-utils/setterm.c
new file mode 100644
index 0000000..22afc76
--- /dev/null
+++ b/term-utils/setterm.c
@@ -0,0 +1,1205 @@
+/* setterm.c, set terminal attributes.
+ *
+ * Copyright (C) 1990 Gordon Irlam (gordoni@cs.ua.oz.au). Conditions of use,
+ * modification, and redistribution are contained in the file COPYRIGHT that
+ * forms part of this distribution.
+ *
+ * Adaption to Linux by Peter MacDonald.
+ *
+ * Enhancements by Mika Liljeberg (liljeber@cs.Helsinki.FI)
+ *
+ * Beep modifications by Christophe Jolif (cjolif@storm.gatelink.fr.net)
+ *
+ * Sanity increases by Cafeine Addict [sic].
+ *
+ * Powersave features by todd j. derr <tjd@wordsmith.org>
+ *
+ * Converted to terminfo by Kars de Jong (jongk@cs.utwente.nl)
+ *
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ *
+ * Semantics:
+ *
+ * Setterm writes to standard output a character string that will
+ * invoke the specified terminal capabilities. Where possible
+ * terminfo is consulted to find the string to use. Some options
+ * however do not correspond to a terminfo capability. In this case if
+ * the terminal type is "con*", or "linux*" the string that invokes
+ * the specified capabilities on the PC Linux virtual console driver
+ * is output. Options that are not implemented by the terminal are
+ * ignored.
+ *
+ * The following options are non-obvious.
+ *
+ * -term can be used to override the TERM environment variable.
+ *
+ * -reset displays the terminal reset string, which typically resets the
+ * terminal to its power on state.
+ *
+ * -initialize displays the terminal initialization string, which typically
+ * sets the terminal's rendering options, and other attributes to the
+ * default values.
+ *
+ * -default sets the terminal's rendering options to the default values.
+ *
+ * -store stores the terminal's current rendering options as the default
+ * values. */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/klog.h>
+#include <sys/param.h> /* for MAXPATHLEN */
+#include <sys/time.h>
+#include <termios.h>
+#include <unistd.h>
+
+#if defined(HAVE_NCURSESW_TERM_H)
+# include <ncursesw/term.h>
+#elif defined(HAVE_NCURSES_TERM_H)
+# include <ncurses/term.h>
+#elif defined(HAVE_TERM_H)
+# include <term.h>
+#endif
+
+#ifdef HAVE_LINUX_TIOCL_H
+# include <linux/tiocl.h>
+#endif
+
+#include "all-io.h"
+#include "c.h"
+#include "closestream.h"
+#include "nls.h"
+#include "optutils.h"
+#include "strutils.h"
+#include "xalloc.h"
+
+/* Constants. */
+
+/* Non-standard return values. */
+#define EXIT_DUMPFILE -1
+
+/* Colors. */
+enum {
+ BLACK = 0,
+ RED,
+ GREEN,
+ YELLOW,
+ BLUE,
+ MAGENTA,
+ CYAN,
+ WHITE,
+ GREY,
+ DEFAULT
+};
+
+static const char *colornames[] = {
+ [BLACK] = "black",
+ [RED] = "red",
+ [GREEN] = "green",
+ [YELLOW]= "yellow",
+ [BLUE] = "blue",
+ [MAGENTA]="magenta",
+ [CYAN] = "cyan",
+ [WHITE] = "white",
+ [GREY] = "grey",
+ [DEFAULT] = "default"
+};
+
+#define is_valid_color(x) (x >= 0 && (size_t) x < ARRAY_SIZE(colornames))
+
+/* Blank commands */
+enum {
+ BLANKSCREEN = -1,
+ UNBLANKSCREEN = -2,
+ BLANKEDSCREEN = -3
+};
+
+/* <linux/tiocl.h> fallback */
+#ifndef TIOCL_BLANKSCREEN
+enum {
+ TIOCL_UNBLANKSCREEN = 4, /* unblank screen */
+ TIOCL_SETVESABLANK = 10, /* set vesa blanking mode */
+ TIOCL_BLANKSCREEN = 14, /* keep screen blank even if a key is pressed */
+ TIOCL_BLANKEDSCREEN = 15 /* return which vt was blanked */
+};
+#endif
+
+/* Powersave modes */
+enum {
+ VESA_BLANK_MODE_OFF = 0,
+ VESA_BLANK_MODE_SUSPENDV,
+ VESA_BLANK_MODE_SUSPENDH,
+ VESA_BLANK_MODE_POWERDOWN
+};
+
+/* klogctl() actions */
+enum {
+ SYSLOG_ACTION_CONSOLE_OFF = 6,
+ SYSLOG_ACTION_CONSOLE_ON = 7,
+ SYSLOG_ACTION_CONSOLE_LEVEL = 8
+};
+
+/* Console log levels */
+enum {
+ CONSOLE_LEVEL_MIN = 0,
+ CONSOLE_LEVEL_MAX = 8
+};
+
+/* Various numbers */
+#define DEFAULT_TAB_LEN 8
+#define BLANK_MAX 60
+#define TABS_MAX 160
+#define BLENGTH_MAX 2000
+
+/* Command controls. */
+struct setterm_control {
+ char *opt_te_terminal_name; /* terminal name */
+ int opt_bl_min; /* blank screen */
+ int opt_blength_l; /* bell duration in milliseconds */
+ int opt_bfreq_f; /* bell frequency in Hz */
+ int opt_sn_num; /* console number to be snapshot */
+ char *opt_sn_name; /* path to write snap */
+ char *in_device; /* device to snapshot */
+ int opt_msglevel_num; /* printk() logging level */
+ int opt_ps_mode; /* powersave mode */
+ int opt_pd_min; /* powerdown time */
+ int opt_rt_len; /* regular tab length */
+ int opt_tb_array[TABS_MAX + 1]; /* array for tab list */
+ /* colors */
+ unsigned int opt_fo_color:4, opt_ba_color:4, opt_ul_color:4, opt_hb_color:4;
+ /* boolean options */
+ unsigned int opt_cu_on:1, opt_li_on:1, opt_bo_on:1, opt_hb_on:1,
+ opt_bl_on:1, opt_re_on:1, opt_un_on:1, opt_rep_on:1,
+ opt_appck_on:1, opt_invsc_on:1, opt_msg_on:1, opt_cl_all:1,
+ vcterm:1;
+ /* Option flags. Set when an option is invoked. */
+ uint64_t opt_term:1, opt_reset:1, opt_resize:1, opt_initialize:1, opt_cursor:1,
+ opt_linewrap:1, opt_default:1, opt_foreground:1,
+ opt_background:1, opt_bold:1, opt_blink:1, opt_reverse:1,
+ opt_underline:1, opt_store:1, opt_clear:1, opt_blank:1,
+ opt_snap:1, opt_snapfile:1, opt_append:1, opt_ulcolor:1,
+ opt_hbcolor:1, opt_halfbright:1, opt_repeat:1, opt_tabs:1,
+ opt_clrtabs:1, opt_regtabs:1, opt_appcursorkeys:1,
+ opt_inversescreen:1, opt_msg:1, opt_msglevel:1, opt_powersave:1,
+ opt_powerdown:1, opt_blength:1, opt_bfreq:1;
+};
+
+static int parse_color(const char *arg)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(colornames); i++) {
+ if (strcmp(colornames[i], arg) == 0)
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+static int parse_febg_color(const char *arg)
+{
+ int color = parse_color(arg);
+
+ if (color < 0)
+ color = strtos32_or_err(arg, _("argument error"));
+
+ if (!is_valid_color(color) || color == GREY)
+ errx(EXIT_FAILURE, "%s: %s", _("argument error"), arg);
+ return color;
+}
+
+static int parse_ulhb_color(char **av, int *oi)
+{
+ char *color_name;
+ int bright = 0;
+ int color = -1;
+
+ if (av[*oi] && strcmp(av[*oi - 1], "bright") == 0) {
+ bright = 1;
+ color_name = av[*oi];
+ (*oi)++;
+ } else
+ color_name = av[*oi - 1];
+
+ color = parse_color(color_name);
+ if (color < 0)
+ color = strtos32_or_err(color_name, _("argument error"));
+ if (!is_valid_color(color) || color == DEFAULT)
+ errx(EXIT_FAILURE, "%s: %s", _("argument error"), color_name);
+ if (bright && (color == BLACK || color == GREY))
+ errx(EXIT_FAILURE, _("argument error: bright %s is not supported"), color_name);
+
+ if (bright)
+ color |= 8;
+
+ return color;
+}
+
+static char *find_optional_arg(char **av, char *oa, int *oi)
+{
+ char *arg;
+ if (oa)
+ return oa;
+
+ arg = av[*oi];
+ if (!arg || arg[0] == '-')
+ return NULL;
+
+ (*oi)++;
+ return arg;
+}
+
+static int parse_blank(char **av, char *oa, int *oi)
+{
+ char *arg;
+
+ arg = find_optional_arg(av, oa, oi);
+ if (!arg)
+ return BLANKEDSCREEN;
+ if (!strcmp(arg, "force"))
+ return BLANKSCREEN;
+ if (!strcmp(arg, "poke"))
+ return UNBLANKSCREEN;
+
+ int ret;
+
+ ret = strtos32_or_err(arg, _("argument error"));
+ if (ret < 0 || BLANK_MAX < ret)
+ errx(EXIT_FAILURE, "%s: %s", _("argument error"), arg);
+ return ret;
+}
+
+static int parse_powersave(const char *arg)
+{
+ if (strcmp(arg, "on") == 0)
+ return VESA_BLANK_MODE_SUSPENDV;
+ if (strcmp(arg, "vsync") == 0)
+ return VESA_BLANK_MODE_SUSPENDV;
+ if (strcmp(arg, "hsync") == 0)
+ return VESA_BLANK_MODE_SUSPENDH;
+ if (strcmp(arg, "powerdown") == 0)
+ return VESA_BLANK_MODE_POWERDOWN;
+ if (strcmp(arg, "off") == 0)
+ return VESA_BLANK_MODE_OFF;
+ errx(EXIT_FAILURE, "%s: %s", _("argument error"), arg);
+}
+
+static int parse_msglevel(const char *arg)
+{
+ int ret;
+
+ ret = strtos32_or_err(arg, _("argument error"));
+ if (ret < CONSOLE_LEVEL_MIN || CONSOLE_LEVEL_MAX < ret)
+ errx(EXIT_FAILURE, "%s: %s", _("argument error"), arg);
+ return ret;
+}
+
+static int parse_snap(char **av, char *oa, int *oi)
+{
+ int ret;
+ char *arg;
+
+ arg = find_optional_arg(av, oa, oi);
+ if (!arg)
+ return 0;
+ ret = strtos32_or_err(arg, _("argument error"));
+ if (ret < 1)
+ errx(EXIT_FAILURE, "%s: %s", _("argument error"), arg);
+ return ret;
+}
+
+static void parse_tabs(char **av, char *oa, int *oi, int *tab_array)
+{
+ int i = 0;
+
+ if (oa) {
+ tab_array[i] = strtos32_or_err(oa, _("argument error"));
+ i++;
+ }
+ while (av[*oi]) {
+ if (TABS_MAX < i)
+ errx(EXIT_FAILURE, _("too many tabs"));
+ if (av[*oi][0] == '-')
+ break;
+ tab_array[i] = strtos32_or_err(av[*oi], _("argument error"));
+ (*oi)++;
+ i++;
+ }
+ tab_array[i] = -1;
+}
+
+static int parse_regtabs(char **av, char *oa, int *oi)
+{
+ int ret;
+ char *arg;
+
+ arg = find_optional_arg(av, oa, oi);
+ if (!arg)
+ return DEFAULT_TAB_LEN;
+ ret = strtos32_or_err(arg, _("argument error"));
+ if (ret < 1 || TABS_MAX < ret)
+ errx(EXIT_FAILURE, "%s: %s", _("argument error"), arg);
+ return ret;
+}
+
+static int parse_blength(char **av, char *oa, int *oi)
+{
+ int ret = -1;
+ char *arg;
+
+ arg = find_optional_arg(av, oa, oi);
+ if (!arg)
+ return 0;
+ ret = strtos32_or_err(arg, _("argument error"));
+ if (ret < 0 || BLENGTH_MAX < ret)
+ errx(EXIT_FAILURE, "%s: %s", _("argument error"), arg);
+ return ret;
+}
+
+static int parse_bfreq(char **av, char *oa, int *oi)
+{
+ char *arg;
+
+ arg = find_optional_arg(av, oa, oi);
+ if (!arg)
+ return 0;
+ return strtos32_or_err(arg, _("argument error"));
+}
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out,
+ _(" %s [options]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Set the attributes of a terminal.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" --term <terminal_name> override TERM environment variable\n"), out);
+ fputs(_(" --reset reset terminal to power-on state\n"), out);
+ fputs(_(" --resize reset terminal rows and columns\n"), out);
+ fputs(_(" --initialize display init string, and use default settings\n"), out);
+ fputs(_(" --default use default terminal settings\n"), out);
+ fputs(_(" --store save current terminal settings as default\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+
+ fputs(_(" --cursor on|off display cursor\n"), out);
+ fputs(_(" --repeat on|off keyboard repeat\n"), out);
+ fputs(_(" --appcursorkeys on|off cursor key application mode\n"), out);
+ fputs(_(" --linewrap on|off continue on a new line when a line is full\n"), out);
+ fputs(_(" --inversescreen on|off swap colors for the whole screen\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+
+ fputs(_(" --msg on|off send kernel messages to console\n"), out);
+ fputs(_(" --msglevel <0-8> kernel console log level\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+
+ fputs(_(" --foreground default|<color> set foreground color\n"), out);
+ fputs(_(" --background default|<color> set background color\n"), out);
+ fputs(_(" --ulcolor [bright] <color> set underlined text color\n"), out);
+ fputs(_(" --hbcolor [bright] <color> set half-bright text color\n"), out);
+ fputs(_(" <color>: black blue cyan green grey magenta red white yellow\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+
+ fputs(_(" --bold on|off bold\n"), out);
+ fputs(_(" --half-bright on|off dim\n"), out);
+ fputs(_(" --blink on|off blink\n"), out);
+ fputs(_(" --underline on|off underline\n"), out);
+ fputs(_(" --reverse on|off swap foreground and background colors\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+
+ fputs(_(" --clear[=<all|rest>] clear screen and set cursor position\n"), out);
+ fputs(_(" --tabs[=<number>...] set these tab stop positions, or show them\n"), out);
+ fputs(_(" --clrtabs[=<number>...] clear these tab stop positions, or all\n"), out);
+ fputs(_(" --regtabs[=1-160] set a regular tab stop interval\n"), out);
+ fputs(_(" --blank[=0-60|force|poke] set time of inactivity before screen blanks\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+
+ fputs(_(" --dump[=<number>] write vcsa<number> console dump to file\n"), out);
+ fputs(_(" --append <number> append vcsa<number> console dump to file\n"), out);
+ fputs(_(" --file <filename> name of the dump file\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+
+ fputs(_(" --powersave on|vsync|hsync|powerdown|off\n"), out);
+ fputs(_(" set vesa powersaving features\n"), out);
+ fputs(_(" --powerdown[=<0-60>] set vesa powerdown interval in minutes\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+
+ fputs(_(" --blength[=<0-2000>] duration of the bell in milliseconds\n"), out);
+ fputs(_(" --bfreq[=<number>] bell frequency in Hertz\n"), out);
+
+ fputs(USAGE_SEPARATOR, out);
+ printf( " --help %s\n", USAGE_OPTSTR_HELP);
+ printf( " --version %s\n", USAGE_OPTSTR_VERSION);
+
+ printf(USAGE_MAN_TAIL("setterm(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+static int __attribute__((__pure__)) set_opt_flag(int opt)
+{
+ if (opt)
+ errx(EXIT_FAILURE, _("duplicate use of an option"));
+ return 1;
+}
+
+static void parse_option(struct setterm_control *ctl, int ac, char **av)
+{
+ int c;
+ enum {
+ OPT_TERM = CHAR_MAX + 1,
+ OPT_RESET,
+ OPT_RESIZE,
+ OPT_INITIALIZE,
+ OPT_CURSOR,
+ OPT_REPEAT,
+ OPT_APPCURSORKEYS,
+ OPT_LINEWRAP,
+ OPT_DEFAULT,
+ OPT_FOREGROUND,
+ OPT_BACKGROUND,
+ OPT_ULCOLOR,
+ OPT_HBCOLOR,
+ OPT_INVERSESCREEN,
+ OPT_BOLD,
+ OPT_HALF_BRIGHT,
+ OPT_BLINK,
+ OPT_REVERSE,
+ OPT_UNDERLINE,
+ OPT_STORE,
+ OPT_CLEAR,
+ OPT_TABS,
+ OPT_CLRTABS,
+ OPT_REGTABS,
+ OPT_BLANK,
+ OPT_DUMP,
+ OPT_APPEND,
+ OPT_FILE,
+ OPT_MSG,
+ OPT_MSGLEVEL,
+ OPT_POWERSAVE,
+ OPT_POWERDOWN,
+ OPT_BLENGTH,
+ OPT_BFREQ,
+ OPT_VERSION,
+ OPT_HELP
+ };
+ static const struct option longopts[] = {
+ {"term", required_argument, NULL, OPT_TERM},
+ {"reset", no_argument, NULL, OPT_RESET},
+ {"resize", no_argument, NULL, OPT_RESIZE},
+ {"initialize", no_argument, NULL, OPT_INITIALIZE},
+ {"cursor", required_argument, NULL, OPT_CURSOR},
+ {"repeat", required_argument, NULL, OPT_REPEAT},
+ {"appcursorkeys", required_argument, NULL, OPT_APPCURSORKEYS},
+ {"linewrap", required_argument, NULL, OPT_LINEWRAP},
+ {"default", no_argument, NULL, OPT_DEFAULT},
+ {"foreground", required_argument, NULL, OPT_FOREGROUND},
+ {"background", required_argument, NULL, OPT_BACKGROUND},
+ {"ulcolor", required_argument, NULL, OPT_ULCOLOR},
+ {"hbcolor", required_argument, NULL, OPT_HBCOLOR},
+ {"inversescreen", required_argument, NULL, OPT_INVERSESCREEN},
+ {"bold", required_argument, NULL, OPT_BOLD},
+ {"half-bright", required_argument, NULL, OPT_HALF_BRIGHT},
+ {"blink", required_argument, NULL, OPT_BLINK},
+ {"reverse", required_argument, NULL, OPT_REVERSE},
+ {"underline", required_argument, NULL, OPT_UNDERLINE},
+ {"store", no_argument, NULL, OPT_STORE},
+ {"clear", optional_argument, NULL, OPT_CLEAR},
+ {"tabs", optional_argument, NULL, OPT_TABS},
+ {"clrtabs", optional_argument, NULL, OPT_CLRTABS},
+ {"regtabs", optional_argument, NULL, OPT_REGTABS},
+ {"blank", optional_argument, NULL, OPT_BLANK},
+ {"dump", optional_argument, NULL, OPT_DUMP},
+ {"append", required_argument, NULL, OPT_APPEND},
+ {"file", required_argument, NULL, OPT_FILE},
+ {"msg", required_argument, NULL, OPT_MSG},
+ {"msglevel", required_argument, NULL, OPT_MSGLEVEL},
+ {"powersave", required_argument, NULL, OPT_POWERSAVE},
+ {"powerdown", optional_argument, NULL, OPT_POWERDOWN},
+ {"blength", optional_argument, NULL, OPT_BLENGTH},
+ {"bfreq", optional_argument, NULL, OPT_BFREQ},
+ {"version", no_argument, NULL, OPT_VERSION},
+ {"help", no_argument, NULL, OPT_HELP},
+ {NULL, 0, NULL, 0}
+ };
+ static const ul_excl_t excl[] = {
+ { OPT_DEFAULT, OPT_STORE },
+ { OPT_TABS, OPT_CLRTABS, OPT_REGTABS },
+ { OPT_MSG, OPT_MSGLEVEL },
+ { 0 }
+ };
+ int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
+
+ while ((c = getopt_long_only(ac, av, "", longopts, NULL)) != -1) {
+ err_exclusive_options(c, longopts, excl, excl_st);
+ switch (c) {
+ case OPT_TERM:
+ ctl->opt_term = set_opt_flag(ctl->opt_term);
+ ctl->opt_te_terminal_name = optarg;
+ break;
+ case OPT_RESET:
+ ctl->opt_reset = set_opt_flag(ctl->opt_reset);
+ break;
+ case OPT_RESIZE:
+ ctl->opt_resize = set_opt_flag(ctl->opt_resize);
+ break;
+ case OPT_INITIALIZE:
+ ctl->opt_initialize = set_opt_flag(ctl->opt_initialize);
+ break;
+ case OPT_CURSOR:
+ ctl->opt_cursor = set_opt_flag(ctl->opt_cursor);
+ ctl->opt_cu_on = parse_switch(optarg, _("argument error"),
+ "on", "off", NULL);
+ break;
+ case OPT_REPEAT:
+ ctl->opt_repeat = set_opt_flag(ctl->opt_repeat);
+ ctl->opt_rep_on = parse_switch(optarg, _("argument error"),
+ "on", "off", NULL);
+ break;
+ case OPT_APPCURSORKEYS:
+ ctl->opt_appcursorkeys = set_opt_flag(ctl->opt_appcursorkeys);
+ ctl->opt_appck_on = parse_switch(optarg, _("argument error"),
+ "on", "off", NULL);
+ break;
+ case OPT_LINEWRAP:
+ ctl->opt_linewrap = set_opt_flag(ctl->opt_linewrap);
+ ctl->opt_li_on = parse_switch(optarg, _("argument error"),
+ "on", "off", NULL);
+ break;
+ case OPT_DEFAULT:
+ ctl->opt_default = set_opt_flag(ctl->opt_default);
+ break;
+ case OPT_FOREGROUND:
+ ctl->opt_foreground = set_opt_flag(ctl->opt_foreground);
+ ctl->opt_fo_color = parse_febg_color(optarg);
+ break;
+ case OPT_BACKGROUND:
+ ctl->opt_background = set_opt_flag(ctl->opt_background);
+ ctl->opt_ba_color = parse_febg_color(optarg);
+ break;
+ case OPT_ULCOLOR:
+ ctl->opt_ulcolor = set_opt_flag(ctl->opt_ulcolor);
+ ctl->opt_ul_color = parse_ulhb_color(av, &optind);
+ break;
+ case OPT_HBCOLOR:
+ ctl->opt_hbcolor = set_opt_flag(ctl->opt_hbcolor);
+ ctl->opt_hb_color = parse_ulhb_color(av, &optind);
+ break;
+ case OPT_INVERSESCREEN:
+ ctl->opt_inversescreen = set_opt_flag(ctl->opt_inversescreen);
+ ctl->opt_invsc_on = parse_switch(optarg, _("argument error"),
+ "on", "off", NULL);
+ break;
+ case OPT_BOLD:
+ ctl->opt_bold = set_opt_flag(ctl->opt_bold);
+ ctl->opt_bo_on = parse_switch(optarg, _("argument error"),
+ "on", "off", NULL);
+ break;
+ case OPT_HALF_BRIGHT:
+ ctl->opt_halfbright = set_opt_flag(ctl->opt_halfbright);
+ ctl->opt_hb_on = parse_switch(optarg, _("argument error"),
+ "on", "off", NULL);
+ break;
+ case OPT_BLINK:
+ ctl->opt_blink = set_opt_flag(ctl->opt_blink);
+ ctl->opt_bl_on = parse_switch(optarg, _("argument error"),
+ "on", "off", NULL);
+ break;
+ case OPT_REVERSE:
+ ctl->opt_reverse = set_opt_flag(ctl->opt_reverse);
+ ctl->opt_re_on = parse_switch(optarg, _("argument error"),
+ "on", "off", NULL);
+ break;
+ case OPT_UNDERLINE:
+ ctl->opt_underline = set_opt_flag(ctl->opt_underline);
+ ctl->opt_un_on = parse_switch(optarg, _("argument error"),
+ "on", "off", NULL);
+ break;
+ case OPT_STORE:
+ ctl->opt_store = set_opt_flag(ctl->opt_store);
+ break;
+ case OPT_CLEAR:
+ ctl->opt_clear = set_opt_flag(ctl->opt_clear);
+ if (optarg)
+ ctl->opt_cl_all = parse_switch(optarg, _("argument error"),
+ "all", "rest", NULL);
+ else
+ ctl->opt_cl_all = 1;
+ break;
+ case OPT_TABS:
+ ctl->opt_tabs = set_opt_flag(ctl->opt_tabs);
+ parse_tabs(av, optarg, &optind, ctl->opt_tb_array);
+ break;
+ case OPT_CLRTABS:
+ ctl->opt_clrtabs = set_opt_flag(ctl->opt_clrtabs);
+ parse_tabs(av, optarg, &optind, ctl->opt_tb_array);
+ break;
+ case OPT_REGTABS:
+ ctl->opt_regtabs = set_opt_flag(ctl->opt_regtabs);
+ ctl->opt_rt_len = parse_regtabs(av, optarg, &optind);
+ break;
+ case OPT_BLANK:
+ ctl->opt_blank = set_opt_flag(ctl->opt_blank);
+ ctl->opt_bl_min = parse_blank(av, optarg, &optind);
+ break;
+ case OPT_DUMP:
+ ctl->opt_snap = set_opt_flag(ctl->opt_snap);
+ ctl->opt_sn_num = parse_snap(av, optarg, &optind);
+ break;
+ case OPT_APPEND:
+ ctl->opt_append = set_opt_flag(ctl->opt_append);
+ ctl->opt_sn_num = parse_snap(av, optarg, &optind);
+ break;
+ case OPT_FILE:
+ ctl->opt_snapfile = set_opt_flag(ctl->opt_snapfile);
+ ctl->opt_sn_name = optarg;
+ break;
+ case OPT_MSG:
+ ctl->opt_msg = set_opt_flag(ctl->opt_msg);
+ ctl->opt_msg_on = parse_switch(optarg, _("argument error"),
+ "on", "off", NULL);
+ break;
+ case OPT_MSGLEVEL:
+ ctl->opt_msglevel = set_opt_flag(ctl->opt_msglevel);
+ ctl->opt_msglevel_num = parse_msglevel(optarg);
+ if (ctl->opt_msglevel_num == 0) {
+ ctl->opt_msg = set_opt_flag(ctl->opt_msg);
+ ctl->opt_msg_on |= 1;
+ }
+ break;
+ case OPT_POWERSAVE:
+ ctl->opt_powersave = set_opt_flag(ctl->opt_powersave);
+ ctl->opt_ps_mode = parse_powersave(optarg);
+ break;
+ case OPT_POWERDOWN:
+ ctl->opt_powerdown = set_opt_flag(ctl->opt_powerdown);
+ ctl->opt_pd_min = parse_blank(av, optarg, &optind);
+ break;
+ case OPT_BLENGTH:
+ ctl->opt_blength = set_opt_flag(ctl->opt_blength);
+ ctl->opt_blength_l = parse_blength(av, optarg, &optind);
+ break;
+ case OPT_BFREQ:
+ ctl->opt_bfreq = set_opt_flag(ctl->opt_bfreq);
+ ctl->opt_bfreq_f = parse_bfreq(av, optarg, &optind);
+ break;
+
+ case OPT_VERSION:
+ print_version(EXIT_SUCCESS);
+ case OPT_HELP:
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+}
+
+/* Return the specified terminfo string, or an empty string if no such
+ * terminfo capability exists. */
+static char *ti_entry(const char *name)
+{
+ char *buf_ptr;
+
+ if ((buf_ptr = tigetstr(name)) == (char *)-1)
+ buf_ptr = NULL;
+ return buf_ptr;
+}
+
+static void show_tabs(void)
+{
+ int i, co = tigetnum("cols");
+
+ if (co > 0) {
+ printf("\r ");
+ for (i = 10; i < co - 2; i += 10)
+ printf("%-10d", i);
+ putchar('\n');
+ for (i = 1; i <= co; i++)
+ putchar(i % 10 + '0');
+ putchar('\n');
+ for (i = 1; i < co; i++)
+ printf("\tT\b");
+ putchar('\n');
+ }
+}
+
+static int open_snapshot_device(struct setterm_control *ctl)
+{
+ int fd;
+
+ if (ctl->opt_sn_num)
+ xasprintf(&ctl->in_device, "/dev/vcsa%d", ctl->opt_sn_num);
+ else
+ xasprintf(&ctl->in_device, "/dev/vcsa");
+ fd = open(ctl->in_device, O_RDONLY);
+ if (fd < 0)
+ err(EXIT_DUMPFILE, _("cannot read %s"), ctl->in_device);
+ return fd;
+}
+
+static void set_blanking(struct setterm_control *ctl)
+{
+ char ioctlarg;
+ int ret;
+
+ if (0 <= ctl->opt_bl_min) {
+ printf("\033[9;%d]", ctl->opt_bl_min);
+ return;
+ }
+ switch (ctl->opt_bl_min) {
+ case BLANKSCREEN:
+ ioctlarg = TIOCL_BLANKSCREEN;
+ if (ioctl(STDIN_FILENO, TIOCLINUX, &ioctlarg))
+ warn(_("cannot force blank"));
+ break;
+ case UNBLANKSCREEN:
+ ioctlarg = TIOCL_UNBLANKSCREEN;
+ if (ioctl(STDIN_FILENO, TIOCLINUX, &ioctlarg))
+ warn(_("cannot force unblank"));
+ break;
+ case BLANKEDSCREEN:
+ ioctlarg = TIOCL_BLANKEDSCREEN;
+ ret = ioctl(STDIN_FILENO, TIOCLINUX, &ioctlarg);
+ if (ret < 0)
+ warn(_("cannot get blank status"));
+ else
+ printf("%d\n", ret);
+ break;
+ default: /* should be impossible to reach */
+ abort();
+ }
+}
+
+static void screendump(struct setterm_control *ctl)
+{
+ unsigned char header[4];
+ unsigned int rows, cols;
+ int fd;
+ FILE *out;
+ size_t i, j;
+ ssize_t rc;
+ char *inbuf, *outbuf, *p, *q;
+
+ /* open source and destination files */
+ fd = open_snapshot_device(ctl);
+ if (!ctl->opt_sn_name)
+ ctl->opt_sn_name = "screen.dump";
+ out = fopen(ctl->opt_sn_name, ctl->opt_snap ? "w" : "a");
+ if (!out)
+ err(EXIT_DUMPFILE, _("cannot open dump file %s for output"), ctl->opt_sn_name);
+ /* determine snapshot size */
+ if (read(fd, header, 4) != 4)
+ err(EXIT_DUMPFILE, _("cannot read %s"), ctl->in_device);
+ rows = header[0];
+ cols = header[1];
+ if (rows * cols == 0)
+ err(EXIT_DUMPFILE, _("cannot read %s"), ctl->in_device);
+ /* allocate buffers */
+ inbuf = xmalloc(rows * cols * 2);
+ outbuf = xmalloc(rows * (cols + 1));
+ /* read input */
+ rc = read(fd, inbuf, rows * cols * 2);
+ if (rc < 0 || (size_t)rc != rows * cols * 2)
+ err(EXIT_DUMPFILE, _("cannot read %s"), ctl->in_device);
+ p = inbuf;
+ q = outbuf;
+ /* copy inbuf to outbuf */
+ for (i = 0; i < rows; i++) {
+ for (j = 0; j < cols; j++) {
+ *q++ = *p;
+ p += 2;
+ }
+ while (j-- > 0 && q[-1] == ' ')
+ q--;
+ *q++ = '\n';
+ }
+ fwrite(outbuf, 1, q - outbuf, out);
+ /* clean up allocations */
+ close(fd);
+ free(inbuf);
+ free(outbuf);
+ free(ctl->in_device);
+ if (close_stream(out) != 0)
+ errx(EXIT_FAILURE, _("write error"));
+}
+
+/* Some options are applicable when terminal is virtual console. */
+static int vc_only(struct setterm_control *ctl, const char *err)
+{
+ if (!ctl->vcterm && err)
+ warnx(_("terminal %s does not support %s"),
+ ctl->opt_te_terminal_name, err);
+ return ctl->vcterm;
+}
+
+static void tty_raw(struct termios *saved_attributes, int *saved_fl)
+{
+ struct termios tattr;
+
+ fcntl(STDIN_FILENO, F_GETFL, saved_fl);
+ tcgetattr(STDIN_FILENO, saved_attributes);
+ fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);
+ memcpy(&tattr, saved_attributes, sizeof(struct termios));
+ tattr.c_lflag &= ~(ICANON | ECHO);
+ tattr.c_cc[VMIN] = 1;
+ tattr.c_cc[VTIME] = 0;
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &tattr);
+}
+
+static void tty_restore(struct termios *saved_attributes, int *saved_fl)
+{
+ fcntl(STDIN_FILENO, F_SETFL, *saved_fl);
+ tcsetattr(STDIN_FILENO, TCSANOW, saved_attributes);
+}
+
+static int select_wait(void)
+{
+ struct timeval tv;
+ fd_set set;
+ int ret;
+
+ FD_ZERO(&set);
+ FD_SET(STDIN_FILENO, &set);
+ tv.tv_sec = 10;
+ tv.tv_usec = 0;
+ while ((ret = select(1, &set, NULL, NULL, &tv)) < 0) {
+ if (errno == EINTR)
+ continue;
+ err(EXIT_FAILURE, _("select failed"));
+ }
+ return ret;
+}
+
+static int resizetty(void)
+{
+ /*
+ * \e7 Save current state (cursor coordinates, attributes,
+ * character sets pointed at by G0, G1).
+ * \e[r Set scrolling region; parameters are top and bottom row.
+ * \e[32766E Move cursor down 32766 (INT16_MAX - 1) rows.
+ * \e[32766C Move cursor right 32766 columns.
+ * \e[6n Report cursor position.
+ * \e8 Restore state most recently saved by \e7.
+ */
+ static const char *getpos = "\e7\e[r\e[32766E\e[32766C\e[6n\e8";
+ char retstr[32];
+ int row, col;
+ size_t pos;
+ ssize_t rc;
+ struct winsize ws;
+ struct termios saved_attributes;
+ int saved_fl;
+
+ if (!isatty(STDIN_FILENO))
+ errx(EXIT_FAILURE, _("stdin does not refer to a terminal"));
+
+ tty_raw(&saved_attributes, &saved_fl);
+ if (write_all(STDIN_FILENO, getpos, strlen(getpos)) < 0) {
+ warn(_("write failed"));
+ tty_restore(&saved_attributes, &saved_fl);
+ return 1;
+ }
+ for (pos = 0; pos < sizeof(retstr) - 1;) {
+ if (0 == select_wait())
+ break;
+ if ((rc =
+ read(STDIN_FILENO, retstr + pos,
+ sizeof(retstr) - 1 - pos)) < 0) {
+ if (errno == EINTR)
+ continue;
+ warn(_("read failed"));
+ tty_restore(&saved_attributes, &saved_fl);
+ return 1;
+ }
+ pos += rc;
+ if (retstr[pos - 1] == 'R')
+ break;
+ }
+ retstr[pos] = 0;
+ tty_restore(&saved_attributes, &saved_fl);
+ rc = sscanf(retstr, "\033[%d;%dR", &row, &col);
+ if (rc != 2) {
+ warnx(_("invalid cursor position: %s"), retstr);
+ return 1;
+ }
+ memset(&ws, 0, sizeof(struct winsize));
+ ioctl(STDIN_FILENO, TIOCGWINSZ, &ws);
+ ws.ws_row = row;
+ ws.ws_col = col;
+ ioctl(STDIN_FILENO, TIOCSWINSZ, &ws);
+ return 0;
+}
+
+static void perform_sequence(struct setterm_control *ctl)
+{
+ int result;
+
+ /* -reset. */
+ if (ctl->opt_reset)
+ putp(ti_entry("rs1"));
+
+ /* -resize. */
+ if (ctl->opt_resize)
+ if (resizetty())
+ warnx(_("reset failed"));
+
+ /* -initialize. */
+ if (ctl->opt_initialize)
+ putp(ti_entry("is2"));
+
+ /* -cursor [on|off]. */
+ if (ctl->opt_cursor) {
+ if (ctl->opt_cu_on)
+ putp(ti_entry("cnorm"));
+ else
+ putp(ti_entry("civis"));
+ }
+
+ /* -linewrap [on|off]. */
+ if (ctl->opt_linewrap)
+ fputs(ctl->opt_li_on ? "\033[?7h" : "\033[?7l", stdout);
+
+ /* -repeat [on|off]. */
+ if (ctl->opt_repeat && vc_only(ctl, "--repeat"))
+ fputs(ctl->opt_rep_on ? "\033[?8h" : "\033[?8l", stdout);
+
+ /* -appcursorkeys [on|off]. */
+ if (ctl->opt_appcursorkeys && vc_only(ctl, "--appcursorkeys"))
+ fputs(ctl->opt_appck_on ? "\033[?1h" : "\033[?1l", stdout);
+
+ /* -default. Vc sets default rendition, otherwise clears all
+ * attributes. */
+ if (ctl->opt_default) {
+ if (vc_only(ctl, NULL))
+ printf("\033[0m");
+ else
+ putp(ti_entry("sgr0"));
+ }
+
+ /* -foreground black|red|green|yellow|blue|magenta|cyan|white|default. */
+ if (ctl->opt_foreground)
+ printf("\033[3%c%s", '0' + ctl->opt_fo_color, "m");
+
+ /* -background black|red|green|yellow|blue|magenta|cyan|white|default. */
+ if (ctl->opt_background)
+ printf("\033[4%c%s", '0' + ctl->opt_ba_color, "m");
+
+ /* -ulcolor [bright] black|red|green|yellow|blue|magenta|cyan|white. */
+ if (ctl->opt_ulcolor && vc_only(ctl, "--ulcolor"))
+ printf("\033[1;%d]", ctl->opt_ul_color);
+
+ /* -hbcolor [bright] black|red|green|yellow|blue|magenta|cyan|white. */
+ if (ctl->opt_hbcolor)
+ printf("\033[2;%d]", ctl->opt_hb_color);
+
+ /* -inversescreen [on|off]. */
+ if (ctl->opt_inversescreen)
+ fputs(ctl->opt_invsc_on ? "\033[?5h" : "\033[?5l", stdout);
+
+ /* -bold [on|off]. Vc behaves as expected, otherwise off turns off
+ * all attributes. */
+ if (ctl->opt_bold) {
+ if (ctl->opt_bo_on)
+ putp(ti_entry("bold"));
+ else {
+ if (vc_only(ctl, NULL))
+ fputs("\033[22m", stdout);
+ else
+ putp(ti_entry("sgr0"));
+ }
+ }
+
+ /* -half-bright [on|off]. Vc behaves as expected, otherwise off
+ * turns off all attributes. */
+ if (ctl->opt_halfbright) {
+ if (ctl->opt_hb_on)
+ putp(ti_entry("dim"));
+ else {
+ if (vc_only(ctl, NULL))
+ fputs("\033[22m", stdout);
+ else
+ putp(ti_entry("sgr0"));
+ }
+ }
+
+ /* -blink [on|off]. Vc behaves as expected, otherwise off turns off
+ * all attributes. */
+ if (ctl->opt_blink) {
+ if (ctl->opt_bl_on)
+ putp(ti_entry("blink"));
+ else {
+ if (vc_only(ctl, NULL))
+ fputs("\033[25m", stdout);
+ else
+ putp(ti_entry("sgr0"));
+ }
+ }
+
+ /* -reverse [on|off]. Vc behaves as expected, otherwise off turns
+ * off all attributes. */
+ if (ctl->opt_reverse) {
+ if (ctl->opt_re_on)
+ putp(ti_entry("rev"));
+ else {
+ if (vc_only(ctl, NULL))
+ fputs("\033[27m", stdout);
+ else
+ putp(ti_entry("sgr0"));
+ }
+ }
+
+ /* -underline [on|off]. */
+ if (ctl->opt_underline)
+ putp(ti_entry(ctl->opt_un_on ? "smul" : "rmul"));
+
+ /* -store. */
+ if (ctl->opt_store && vc_only(ctl, "--store"))
+ fputs("\033[8]", stdout);
+
+ /* -clear [all|rest]. */
+ if (ctl->opt_clear)
+ putp(ti_entry(ctl->opt_cl_all ? "clear" : "ed"));
+
+ /* -tabs. */
+ if (ctl->opt_tabs) {
+ if (ctl->opt_tb_array[0] == -1)
+ show_tabs();
+ else {
+ int i;
+
+ for (i = 0; ctl->opt_tb_array[i] > 0; i++)
+ printf("\033[%dG\033H", ctl->opt_tb_array[i]);
+ putchar('\r');
+ }
+ }
+
+ /* -clrtabs. */
+ if (ctl->opt_clrtabs && vc_only(ctl, "--clrtabs")) {
+ int i;
+
+ if (ctl->opt_tb_array[0] == -1)
+ fputs("\033[3g", stdout);
+ else
+ for (i = 0; ctl->opt_tb_array[i] > 0; i++)
+ printf("\033[%dG\033[g", ctl->opt_tb_array[i]);
+ putchar('\r');
+ }
+
+ /* -regtabs. */
+ if (ctl->opt_regtabs && vc_only(ctl, "--regtabs")) {
+ int i;
+
+ fputs("\033[3g\r", stdout);
+ for (i = ctl->opt_rt_len + 1; i <= TABS_MAX; i += ctl->opt_rt_len)
+ printf("\033[%dC\033H", ctl->opt_rt_len);
+ putchar('\r');
+ }
+
+ /* -blank [0-60]. */
+ if (ctl->opt_blank && vc_only(ctl, "--blank"))
+ set_blanking(ctl);
+
+ /* -powersave [on|vsync|hsync|powerdown|off] (console) */
+ if (ctl->opt_powersave) {
+ char ioctlarg[2];
+ ioctlarg[0] = TIOCL_SETVESABLANK;
+ ioctlarg[1] = ctl->opt_ps_mode;
+ if (ioctl(STDIN_FILENO, TIOCLINUX, ioctlarg))
+ warn(_("cannot (un)set powersave mode"));
+ }
+
+ /* -powerdown [0-60]. */
+ if (ctl->opt_powerdown)
+ printf("\033[14;%d]", ctl->opt_pd_min);
+
+ /* -snap [1-NR_CONS]. */
+ if (ctl->opt_snap || ctl->opt_append)
+ screendump(ctl);
+
+ /* -msg [on|off]. Controls printk's to console. */
+ if (ctl->opt_msg && vc_only(ctl, "--msg")) {
+ if (ctl->opt_msg_on)
+ result = klogctl(SYSLOG_ACTION_CONSOLE_ON, NULL, 0);
+ else
+ result = klogctl(SYSLOG_ACTION_CONSOLE_OFF, NULL, 0);
+
+ if (result != 0)
+ warn(_("klogctl error"));
+ }
+
+ /* -msglevel [0-8]. Console printk message level. */
+ if (ctl->opt_msglevel_num && vc_only(ctl, "--msglevel")) {
+ result =
+ klogctl(SYSLOG_ACTION_CONSOLE_LEVEL, NULL,
+ ctl->opt_msglevel_num);
+ if (result != 0)
+ warn(_("klogctl error"));
+ }
+
+ /* -blength [0-2000] */
+ if (ctl->opt_blength && vc_only(ctl, "--blength")) {
+ printf("\033[11;%d]", ctl->opt_blength_l);
+ }
+
+ /* -bfreq freqnumber */
+ if (ctl->opt_bfreq && vc_only(ctl, "--bfreq")) {
+ printf("\033[10;%d]", ctl->opt_bfreq_f);
+ }
+}
+
+static void init_terminal(struct setterm_control *ctl)
+{
+ int term_errno;
+
+ if (!ctl->opt_te_terminal_name) {
+ ctl->opt_te_terminal_name = getenv("TERM");
+ if (ctl->opt_te_terminal_name == NULL)
+ errx(EXIT_FAILURE, _("$TERM is not defined."));
+ }
+
+ /* Find terminfo entry. */
+ if (setupterm(ctl->opt_te_terminal_name, STDOUT_FILENO, &term_errno))
+ switch (term_errno) {
+ case -1:
+ errx(EXIT_FAILURE, _("terminfo database cannot be found"));
+ case 0:
+ errx(EXIT_FAILURE, _("%s: unknown terminal type"), ctl->opt_te_terminal_name);
+ case 1:
+ errx(EXIT_FAILURE, _("terminal is hardcopy"));
+ }
+
+ /* See if the terminal is a virtual console terminal. */
+ ctl->vcterm = (!strncmp(ctl->opt_te_terminal_name, "con", 3) ||
+ !strncmp(ctl->opt_te_terminal_name, "linux", 5));
+}
+
+
+int main(int argc, char **argv)
+{
+ struct setterm_control ctl = { NULL };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ if (argc < 2) {
+ warnx(_("bad usage"));
+ errtryhelp(EXIT_FAILURE);
+ }
+ parse_option(&ctl, argc, argv);
+ init_terminal(&ctl);
+ perform_sequence(&ctl);
+
+ return EXIT_SUCCESS;
+}
diff --git a/term-utils/ttymsg.c b/term-utils/ttymsg.c
new file mode 100644
index 0000000..2aab69f
--- /dev/null
+++ b/term-utils/ttymsg.c
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Modified Sun Mar 12 10:39:22 1995, faith@cs.unc.edu for Linux
+ *
+ */
+
+ /* 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ * Sun Mar 21 1999 - Arnaldo Carvalho de Melo <acme@conectiva.com.br>
+ * - fixed strerr(errno) in gettext calls
+ */
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/uio.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <errno.h>
+#include <paths.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "nls.h"
+#include "closestream.h"
+#include "pathnames.h"
+#include "ttymsg.h"
+
+#define ERR_BUFLEN (MAXNAMLEN + 1024)
+
+/*
+ * Display the contents of a uio structure on a terminal. Used by wall(1),
+ * syslogd(8), and talkd(8). Forks and finishes in child if write would block,
+ * waiting up to tmout seconds. Returns pointer to error string on unexpected
+ * error; string is not newline-terminated. Various "normal" errors are
+ * ignored (exclusive-use, lack of permission, etc.).
+ */
+char *
+ttymsg(struct iovec *iov, size_t iovcnt, char *line, int tmout) {
+ static char device[MAXNAMLEN];
+ static char errbuf[ERR_BUFLEN];
+ size_t cnt, left;
+ ssize_t wret;
+ struct iovec localiov[6];
+ int fd, forked = 0;
+ ssize_t len = 0;
+
+ if (iovcnt > ARRAY_SIZE(localiov)) {
+ snprintf(errbuf, sizeof(errbuf), _("internal error: too many iov's"));
+ return errbuf;
+ }
+
+ /* The old code here rejected the line argument when it contained a '/',
+ saying: "A slash may be an attempt to break security...".
+ However, if a user can control the line argument here
+ then he can make this routine write to /dev/hda or /dev/sda
+ already. So, this test was worthless, and these days it is
+ also wrong since people use /dev/pts/xxx. */
+
+ len = snprintf(device, sizeof(device), "%s%s", _PATH_DEV, line);
+ if (len < 0 || (size_t)len >= sizeof(device)) {
+ snprintf(errbuf, sizeof(errbuf), _("excessively long line arg"));
+ return errbuf;
+ }
+
+ /*
+ * open will fail on slip lines or exclusive-use lines
+ * if not running as root; not an error.
+ */
+ if ((fd = open(device, O_WRONLY|O_NONBLOCK, 0)) < 0) {
+ if (errno == EBUSY || errno == EACCES)
+ return NULL;
+
+ len = snprintf(errbuf, sizeof(errbuf), "%s: %m", device);
+ if (len < 0 || (size_t)len >= sizeof(errbuf))
+ snprintf(errbuf, sizeof(errbuf), _("open failed"));
+ return errbuf;
+ }
+
+ for (cnt = left = 0; cnt < iovcnt; ++cnt)
+ left += iov[cnt].iov_len;
+
+ for (;;) {
+ wret = writev(fd, iov, iovcnt);
+ if (wret >= (ssize_t) left)
+ break;
+ if (wret >= 0) {
+ left -= wret;
+ if (iov != localiov) {
+ memmove(localiov, iov,
+ iovcnt * sizeof(struct iovec));
+ iov = localiov;
+ }
+ for (cnt = 0; wret >= (ssize_t) iov->iov_len; ++cnt) {
+ wret -= iov->iov_len;
+ ++iov;
+ --iovcnt;
+ }
+ if (wret) {
+ iov->iov_base = (char *) iov->iov_base + wret;
+ iov->iov_len -= wret;
+ }
+ continue;
+ }
+ if (errno == EWOULDBLOCK) {
+ int cpid, flags;
+ sigset_t sigmask;
+
+ if (forked) {
+ close(fd);
+ _exit(EXIT_FAILURE);
+ }
+ cpid = fork();
+ if (cpid < 0) {
+ len = snprintf(errbuf, sizeof(errbuf), _("fork: %m"));
+ if (len < 0 || (size_t)len >= sizeof(errbuf))
+ snprintf(errbuf, sizeof(errbuf), _("cannot fork"));
+ close(fd);
+ return errbuf;
+ }
+ if (cpid) { /* parent */
+ close(fd);
+ return NULL;
+ }
+ forked++;
+ /* wait at most tmout seconds */
+ signal(SIGALRM, SIG_DFL);
+ signal(SIGTERM, SIG_DFL); /* XXX */
+ sigemptyset(&sigmask);
+ sigprocmask (SIG_SETMASK, &sigmask, NULL);
+ alarm((u_int)tmout);
+ flags = fcntl(fd, F_GETFL);
+ fcntl(flags, F_SETFL, (long) (flags & ~O_NONBLOCK));
+ continue;
+ }
+ /*
+ * We get ENODEV on a slip line if we're running as root,
+ * and EIO if the line just went away.
+ */
+ if (errno == ENODEV || errno == EIO)
+ break;
+ if (close_fd(fd) != 0)
+ warn(_("write failed: %s"), device);
+ if (forked)
+ _exit(EXIT_FAILURE);
+
+ len = snprintf(errbuf, sizeof(errbuf), "%s: %m", device);
+ if (len < 0 || (size_t)len >= sizeof(errbuf))
+ snprintf(errbuf, sizeof(errbuf),
+ _("%s: BAD ERROR, message is "
+ "far too long"), device);
+ return errbuf;
+ }
+
+ if (forked)
+ _exit(EXIT_SUCCESS);
+ return NULL;
+}
diff --git a/term-utils/ttymsg.h b/term-utils/ttymsg.h
new file mode 100644
index 0000000..8424ac4
--- /dev/null
+++ b/term-utils/ttymsg.h
@@ -0,0 +1,6 @@
+#ifndef UTIL_LINUX_TERM_TTYMSG_H
+#define UTIL_LINUX_TERM_TTYMSG_H
+
+char *ttymsg(struct iovec *iov, size_t iovcnt, char *line, int tmout);
+
+#endif /* UTIL_LINUX_TERM_TTYMSG_H */
diff --git a/term-utils/wall.1 b/term-utils/wall.1
new file mode 100644
index 0000000..aada25c
--- /dev/null
+++ b/term-utils/wall.1
@@ -0,0 +1,103 @@
+.\" Copyright (c) 1989, 1990 The Regents of the University of California.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)wall.1 6.5 (Berkeley) 4/23/91
+.\"
+.TH WALL "1" "August 2013" "util-linux" "User Commands"
+.SH NAME
+wall \- write a message to all users
+.SH SYNOPSIS
+.B wall
+.RB [ \-n ]
+.RB [ \-t
+.IR timeout ]
+.RB [ \-g
+.IR group ]
+.RI [ message " | " file ]
+.SH DESCRIPTION
+.B wall
+displays a
+.IR message ,
+or the contents of a
+.IR file ,
+or otherwise its standard input, on the terminals of all currently logged
+in users. The command will wrap lines that are longer than 79 characters.
+Short lines are whitespace padded to have 79 characters. The command will
+always put a carriage return and new line at the end of each line.
+.PP
+Only the superuser can write on the terminals of users who have chosen to
+deny messages or are using a program which automatically denies messages.
+.PP
+Reading from a
+.I file
+is refused when the invoker is not superuser and the program is
+set-user-ID or set-group-ID.
+.SH OPTIONS
+.TP
+.BR \-n , " \-\-nobanner"
+Suppress the banner.
+.TP
+.BR \-t , " \-\-timeout " \fItimeout\fR
+Abandon the write attempt to the terminals after \fItimeout\fR seconds.
+This \fItimeout\fR must be a positive integer. The default value
+is 300 seconds, which is a legacy from the time when people ran terminals over
+modem lines.
+.TP
+.BR \-g , " \-\-group " \fIgroup\fR
+Limit printing message to members of group defined as a
+.I group
+argument. The argument can be group name or GID.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display help text and exit.
+.SH NOTES
+Some sessions, such as wdm, that have in the beginning of
+.BR utmp (5)
+ut_type data a ':' character will not get the message from
+.BR wall .
+This is done to avoid write errors.
+.SH HISTORY
+A
+.B wall
+command appeared in Version 7 AT&T UNIX.
+.SH SEE ALSO
+.BR mesg (1),
+.BR talk (1),
+.BR write (1),
+.BR shutdown (8)
+.SH AVAILABILITY
+The wall command is part of the util-linux package and is available from
+.UR https://\:www.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/term-utils/wall.c b/term-utils/wall.c
new file mode 100644
index 0000000..97d9a56
--- /dev/null
+++ b/term-utils/wall.c
@@ -0,0 +1,450 @@
+/*
+ * Copyright (c) 1988, 1990, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Modified Sun Mar 12 10:34:34 1995, faith@cs.unc.edu, for Linux
+ */
+
+/*
+ * This program is not related to David Wall, whose Stanford Ph.D. thesis
+ * is entitled "Mechanisms for Broadcast and Selective Broadcast".
+ *
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ *
+ */
+
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/uio.h>
+
+#include <errno.h>
+#include <paths.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <utmpx.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <grp.h>
+
+#include "nls.h"
+#include "xalloc.h"
+#include "strutils.h"
+#include "ttymsg.h"
+#include "pathnames.h"
+#include "carefulputc.h"
+#include "c.h"
+#include "cctype.h"
+#include "fileutils.h"
+#include "closestream.h"
+#include "timeutils.h"
+
+#define TERM_WIDTH 79
+#define WRITE_TIME_OUT 300 /* in seconds */
+
+/* Function prototypes */
+static char *makemsg(char *fname, char **mvec, int mvecsz,
+ size_t *mbufsize, int print_banner);
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out,
+ _(" %s [options] [<file> | <message>]\n"), program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Write a message to all users.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ fputs(_(" -g, --group <group> only send message to group\n"), out);
+ fputs(_(" -n, --nobanner do not print banner, works only for root\n"), out);
+ fputs(_(" -t, --timeout <timeout> write timeout in seconds\n"), out);
+ fputs(USAGE_SEPARATOR, out);
+ printf(USAGE_HELP_OPTIONS(25));
+ printf(USAGE_MAN_TAIL("wall(1)"));
+
+ exit(EXIT_SUCCESS);
+}
+
+struct group_workspace {
+ gid_t requested_group;
+ int ngroups;
+
+/* getgrouplist() on OSX takes int* not gid_t* */
+#ifdef __APPLE__
+ int *groups;
+#else
+ gid_t *groups;
+#endif
+};
+
+static gid_t get_group_gid(const char *group)
+{
+ struct group *gr;
+ gid_t gid;
+
+ if ((gr = getgrnam(group)))
+ return gr->gr_gid;
+
+ gid = strtou32_or_err(group, _("invalid group argument"));
+ if (!getgrgid(gid))
+ errx(EXIT_FAILURE, _("%s: unknown gid"), group);
+
+ return gid;
+}
+
+static struct group_workspace *init_group_workspace(const char *group)
+{
+ struct group_workspace *buf = xmalloc(sizeof(struct group_workspace));
+
+ buf->requested_group = get_group_gid(group);
+ buf->ngroups = sysconf(_SC_NGROUPS_MAX) + 1; /* room for the primary gid */
+ buf->groups = xcalloc(sizeof(*buf->groups), buf->ngroups);
+
+ return buf;
+}
+
+static void free_group_workspace(struct group_workspace *buf)
+{
+ if (!buf)
+ return;
+
+ free(buf->groups);
+ free(buf);
+}
+
+static int is_gr_member(const char *login, const struct group_workspace *buf)
+{
+ struct passwd *pw;
+ int ngroups = buf->ngroups;
+ int rc;
+
+ pw = getpwnam(login);
+ if (!pw)
+ return 0;
+
+ if (buf->requested_group == pw->pw_gid)
+ return 1;
+
+ rc = getgrouplist(login, pw->pw_gid, buf->groups, &ngroups);
+ if (rc < 0) {
+ /* buffer too small, not sure how this can happen, since
+ we used sysconf to get the size... */
+ errx(EXIT_FAILURE,
+ _("getgrouplist found more groups than sysconf allows"));
+ }
+
+ for (; ngroups >= 0; --ngroups) {
+ if (buf->requested_group == (gid_t) buf->groups[ngroups])
+ return 1;
+ }
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int ch;
+ struct iovec iov;
+ struct utmpx *utmpptr;
+ char *p;
+ char line[sizeof(utmpptr->ut_line) + 1];
+ int print_banner = TRUE;
+ struct group_workspace *group_buf = NULL;
+ char *mbuf, *fname = NULL;
+ size_t mbufsize;
+ unsigned timeout = WRITE_TIME_OUT;
+ char **mvec = NULL;
+ int mvecsz = 0;
+
+ static const struct option longopts[] = {
+ { "nobanner", no_argument, NULL, 'n' },
+ { "timeout", required_argument, NULL, 't' },
+ { "group", required_argument, NULL, 'g' },
+ { "version", no_argument, NULL, 'V' },
+ { "help", no_argument, NULL, 'h' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((ch = getopt_long(argc, argv, "nt:g:Vh", longopts, NULL)) != -1) {
+ switch (ch) {
+ case 'n':
+ if (geteuid() == 0)
+ print_banner = FALSE;
+ else
+ warnx(_("--nobanner is available only for root"));
+ break;
+ case 't':
+ timeout = strtou32_or_err(optarg, _("invalid timeout argument"));
+ if (timeout < 1)
+ errx(EXIT_FAILURE, _("invalid timeout argument: %s"), optarg);
+ break;
+ case 'g':
+ group_buf = init_group_workspace(optarg);
+ break;
+
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc == 1 && access(argv[0], F_OK) == 0)
+ fname = argv[0];
+ else if (argc >= 1) {
+ mvec = argv;
+ mvecsz = argc;
+ }
+
+ mbuf = makemsg(fname, mvec, mvecsz, &mbufsize, print_banner);
+
+ iov.iov_base = mbuf;
+ iov.iov_len = mbufsize;
+ while((utmpptr = getutxent())) {
+ if (!utmpptr->ut_user[0])
+ continue;
+#ifdef USER_PROCESS
+ if (utmpptr->ut_type != USER_PROCESS)
+ continue;
+#endif
+ /* Joey Hess reports that use-sessreg in /etc/X11/wdm/ produces
+ * ut_line entries like :0, and a write to /dev/:0 fails.
+ *
+ * It also seems that some login manager may produce empty ut_line.
+ */
+ if (!*utmpptr->ut_line || *utmpptr->ut_line == ':')
+ continue;
+
+ if (group_buf && !is_gr_member(utmpptr->ut_user, group_buf))
+ continue;
+
+ mem2strcpy(line, utmpptr->ut_line, sizeof(utmpptr->ut_line), sizeof(line));
+ if ((p = ttymsg(&iov, 1, line, timeout)) != NULL)
+ warnx("%s", p);
+ }
+ endutxent();
+ free(mbuf);
+ free_group_workspace(group_buf);
+ exit(EXIT_SUCCESS);
+}
+
+struct buffer {
+ size_t sz;
+ size_t used;
+ char *data;
+};
+
+static void buf_enlarge(struct buffer *bs, size_t len)
+{
+ if (bs->sz == 0 || len > bs->sz - bs->used) {
+ bs->sz += len < 128 ? 128 : len;
+ bs->data = xrealloc(bs->data, bs->sz);
+ }
+}
+
+static void buf_puts(struct buffer *bs, const char *s)
+{
+ size_t len = strlen(s);
+
+ buf_enlarge(bs, len + 1);
+ memcpy(bs->data + bs->used, s, len + 1);
+ bs->used += len;
+}
+
+static void buf_printf(struct buffer *bs, const char *fmt, ...)
+{
+ int rc;
+ va_list ap;
+ size_t limit;
+
+ buf_enlarge(bs, 0); /* default size */
+ limit = bs->sz - bs->used;
+
+ va_start(ap, fmt);
+ rc = vsnprintf(bs->data + bs->used, limit, fmt, ap);
+ va_end(ap);
+
+ if (rc >= 0 && (size_t) rc >= limit) { /* not enough, enlarge */
+ buf_enlarge(bs, (size_t)rc + 1);
+ limit = bs->sz - bs->used;
+ va_start(ap, fmt);
+ rc = vsnprintf(bs->data + bs->used, limit, fmt, ap);
+ va_end(ap);
+ }
+
+ if (rc > 0)
+ bs->used += rc;
+}
+
+static void buf_putc_careful(struct buffer *bs, int c)
+{
+ if (isprint(c) || c == '\a' || c == '\t' || c == '\r' || c == '\n') {
+ buf_enlarge(bs, 1);
+ bs->data[bs->used++] = c;
+ } else if (!c_isascii(c))
+ buf_printf(bs, "\\%3o", (unsigned char)c);
+ else {
+ char tmp[] = { '^', c ^ 0x40, '\0' };
+ buf_puts(bs, tmp);
+ }
+}
+
+static char *makemsg(char *fname, char **mvec, int mvecsz,
+ size_t *mbufsize, int print_banner)
+{
+ struct buffer _bs = {.used = 0}, *bs = &_bs;
+ register int ch, cnt;
+ char *p, *lbuf;
+ long line_max;
+
+ line_max = sysconf(_SC_LINE_MAX);
+ if (line_max <= 0)
+ line_max = 512;
+
+ lbuf = xmalloc(line_max);
+
+ if (print_banner == TRUE) {
+ char *hostname = xgethostname();
+ char *whom, *where, date[CTIME_BUFSIZ];
+ struct passwd *pw;
+ time_t now;
+
+ if (!(whom = getlogin()) || !*whom)
+ whom = (pw = getpwuid(getuid())) ? pw->pw_name : "???";
+ if (!whom) {
+ whom = "someone";
+ warn(_("cannot get passwd uid"));
+ }
+ where = ttyname(STDOUT_FILENO);
+ if (!where) {
+ where = "somewhere";
+ } else if (strncmp(where, "/dev/", 5) == 0)
+ where += 5;
+
+ time(&now);
+ ctime_r(&now, date);
+ date[strlen(date) - 1] = '\0';
+
+ /*
+ * all this stuff is to blank out a square for the message;
+ * we wrap message lines at column 79, not 80, because some
+ * terminals wrap after 79, some do not, and we can't tell.
+ * Which means that we may leave a non-blank character
+ * in column 80, but that can't be helped.
+ */
+ /* snprintf is not always available, but the sprintf's here
+ will not overflow as long as %d takes at most 100 chars */
+ buf_printf(bs, "\r%*s\r\n", TERM_WIDTH, " ");
+
+ snprintf(lbuf, line_max,
+ _("Broadcast message from %s@%s (%s) (%s):"),
+ whom, hostname, where, date);
+ buf_printf(bs, "%-*.*s\007\007\r\n", TERM_WIDTH, TERM_WIDTH, lbuf);
+ free(hostname);
+ }
+ buf_printf(bs, "%*s\r\n", TERM_WIDTH, " ");
+
+ if (mvec) {
+ /*
+ * Read message from argv[]
+ */
+ int i;
+
+ for (i = 0; i < mvecsz; i++) {
+ buf_puts(bs, mvec[i]);
+ if (i < mvecsz - 1)
+ buf_puts(bs, " ");
+ }
+ buf_puts(bs, "\r\n");
+ } else {
+ /*
+ * read message from <file>
+ */
+ if (fname) {
+ /*
+ * When we are not root, but suid or sgid, refuse to read files
+ * (e.g. device files) that the user may not have access to.
+ * After all, our invoker can easily do "wall < file"
+ * instead of "wall file".
+ */
+ uid_t uid = getuid();
+ if (uid && (uid != geteuid() || getgid() != getegid()))
+ errx(EXIT_FAILURE, _("will not read %s - use stdin."),
+ fname);
+
+ if (!freopen(fname, "r", stdin))
+ err(EXIT_FAILURE, _("cannot open %s"), fname);
+
+ }
+
+ /*
+ * Read message from stdin.
+ */
+ while (fgets(lbuf, line_max, stdin)) {
+ for (cnt = 0, p = lbuf; (ch = *p) != '\0'; ++p, ++cnt) {
+ if (cnt == TERM_WIDTH || ch == '\n') {
+ for (; cnt < TERM_WIDTH; ++cnt)
+ buf_puts(bs, " ");
+ buf_puts(bs, "\r\n");
+ cnt = 0;
+ }
+ if (ch == '\t')
+ cnt += (7 - (cnt % 8));
+ if (ch != '\n')
+ buf_putc_careful(bs, ch);
+ }
+ }
+ }
+ buf_printf(bs, "%*s\r\n", TERM_WIDTH, " ");
+
+ free(lbuf);
+
+ bs->data[bs->used] = '\0'; /* be paranoid */
+ *mbufsize = bs->used;
+ return bs->data;
+}
diff --git a/term-utils/write.1 b/term-utils/write.1
new file mode 100644
index 0000000..caadbcb
--- /dev/null
+++ b/term-utils/write.1
@@ -0,0 +1,101 @@
+.\" Copyright (c) 1989, 1993
+.\" The Regents of the University of California. All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)write.1 8.1 (Berkeley) 6/6/93
+.\"
+.TH WRITE 1 "March 1995" "util-linux" "User Commands"
+.SH NAME
+write \- send a message to another user
+.SH SYNOPSIS
+.B write
+.I user
+.RI [ ttyname ]
+.SH DESCRIPTION
+.B write
+allows you to communicate with other users, by copying lines from
+your terminal to theirs.
+.PP
+When you run the
+.B write
+command, the user you are writing to gets a message of the form:
+.PP
+.RS
+Message from yourname@yourhost on yourtty at hh:mm ...
+.RE
+.PP
+Any further lines you enter will be copied to the specified user's
+terminal. If the other user wants to reply, they must run
+.B write
+as well.
+.PP
+When you are done, type an end-of-file or interrupt character. The other
+user will see the message
+.B EOF
+indicating that the conversation is over.
+.PP
+You can prevent people (other than the superuser) from writing to you with
+the
+.BR mesg (1)
+command. Some commands, for example
+.BR nroff (1)
+and
+.BR pr (1),
+may automatically disallow writing, so that the output they produce
+isn't overwritten.
+.PP
+If the user you want to write to is logged in on more than one terminal,
+you can specify which terminal to write to by giving the terminal
+name as the second operand to the
+.B write
+command. Alternatively, you can let
+.B write
+select one of the terminals \- it will pick the one with the shortest idle
+time. This is so that if the user is logged in at work and also dialed up
+from home, the message will go to the right place.
+.PP
+The traditional protocol for writing to someone is that the string `\-o',
+either at the end of a line or on a line by itself, means that it's the
+other person's turn to talk. The string `oo' means that the person
+believes the conversation to be over.
+.SH HISTORY
+A
+.B write
+command appeared in Version 6 AT&T UNIX.
+.SH SEE ALSO
+.BR mesg (1),
+.BR talk (1),
+.BR who (1)
+.SH AVAILABILITY
+The write command is part of the util-linux package and is available from
+https://www.kernel.org/pub/linux/utils/util-linux/.
diff --git a/term-utils/write.c b/term-utils/write.c
new file mode 100644
index 0000000..50f18dc
--- /dev/null
+++ b/term-utils/write.c
@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 1989, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * Modified for Linux, Mon Mar 8 18:16:24 1993, faith@cs.unc.edu
+ * Wed Jun 22 21:41:56 1994, faith@cs.unc.edu:
+ * Added fix from Mike Grupenhoff (kashmir@umiacs.umd.edu)
+ * Mon Jul 1 17:01:39 MET DST 1996, janl@math.uio.no:
+ * - Added fix from David.Chapell@mail.trincoll.edu enabling daemons
+ * to use write.
+ * - ANSIed it since I was working on it anyway.
+ * 1999-02-22 Arkadiusz Miśkiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ *
+ */
+
+#include <errno.h>
+#include <getopt.h>
+#include <paths.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+#include <utmpx.h>
+
+#include "c.h"
+#include "carefulputc.h"
+#include "closestream.h"
+#include "nls.h"
+#include "strutils.h"
+#include "ttyutils.h"
+#include "xalloc.h"
+
+static sig_atomic_t signal_received = 0;
+
+struct write_control {
+ uid_t src_uid;
+ const char *src_login;
+ const char *src_tty_path;
+ const char *src_tty_name;
+ const char *dst_login;
+ char *dst_tty_path;
+ const char *dst_tty_name;
+};
+
+static void __attribute__((__noreturn__)) usage(void)
+{
+ FILE *out = stdout;
+ fputs(USAGE_HEADER, out);
+ fprintf(out,
+ _(" %s [options] <user> [<ttyname>]\n"),
+ program_invocation_short_name);
+
+ fputs(USAGE_SEPARATOR, out);
+ fputs(_("Send a message to another user.\n"), out);
+
+ fputs(USAGE_OPTIONS, out);
+ printf(USAGE_HELP_OPTIONS(16));
+ printf(USAGE_MAN_TAIL("write(1)"));
+ exit(EXIT_SUCCESS);
+}
+
+/*
+ * check_tty - check that a terminal exists, and get the message bit
+ * and the access time
+ */
+static int check_tty(const char *tty, int *tty_writeable, time_t *tty_atime, int showerror)
+{
+ struct stat s;
+
+ if (stat(tty, &s) < 0) {
+ if (showerror)
+ warn("%s", tty);
+ return 1;
+ }
+ if (getuid() == 0) /* root can always write */
+ *tty_writeable = 1;
+ else {
+ if (getegid() != s.st_gid) {
+ warnx(_("effective gid does not match group of %s"), tty);
+ return 1;
+ }
+ *tty_writeable = s.st_mode & S_IWGRP;
+ }
+ if (tty_atime)
+ *tty_atime = s.st_atime;
+ return 0;
+}
+
+/*
+ * check_utmp - checks that the given user is actually logged in on
+ * the given tty
+ */
+static int check_utmp(const struct write_control *ctl)
+{
+ struct utmpx *u;
+ int res = 1;
+
+ utmpxname(_PATH_UTMP);
+ setutxent();
+
+ while ((u = getutxent())) {
+ if (strncmp(ctl->dst_login, u->ut_user, sizeof(u->ut_user)) == 0 &&
+ strncmp(ctl->dst_tty_name, u->ut_line, sizeof(u->ut_line)) == 0) {
+ res = 0;
+ break;
+ }
+ }
+
+ endutxent();
+ return res;
+}
+
+/*
+ * search_utmp - search utmp for the "best" terminal to write to
+ *
+ * Ignores terminals with messages disabled, and of the rest, returns
+ * the one with the most recent access time. Returns as value the number
+ * of the user's terminals with messages enabled, or -1 if the user is
+ * not logged in at all.
+ *
+ * Special case for writing to yourself - ignore the terminal you're
+ * writing from, unless that's the only terminal with messages enabled.
+ */
+static void search_utmp(struct write_control *ctl)
+{
+ struct utmpx *u;
+ time_t best_atime = 0, tty_atime;
+ int num_ttys = 0, valid_ttys = 0, tty_writeable = 0, user_is_me = 0;
+ char path[sizeof(u->ut_line) + 6];
+
+ utmpxname(_PATH_UTMP);
+ setutxent();
+
+ while ((u = getutxent())) {
+ if (strncmp(ctl->dst_login, u->ut_user, sizeof(u->ut_user)) != 0)
+ continue;
+ num_ttys++;
+ sprintf(path, "/dev/%s", u->ut_line);
+ if (check_tty(path, &tty_writeable, &tty_atime, 0))
+ /* bad term? skip */
+ continue;
+ if (ctl->src_uid && !tty_writeable)
+ /* skip ttys with msgs off */
+ continue;
+ if (memcmp(u->ut_line, ctl->src_tty_name, strlen(ctl->src_tty_name) + 1) == 0) {
+ user_is_me = 1;
+ /* don't write to yourself */
+ continue;
+ }
+ if (u->ut_type != USER_PROCESS)
+ /* it's not a valid entry */
+ continue;
+ valid_ttys++;
+ if (best_atime < tty_atime) {
+ best_atime = tty_atime;
+ free(ctl->dst_tty_path);
+ ctl->dst_tty_path = xstrdup(path);
+ ctl->dst_tty_name = ctl->dst_tty_path + 5;
+ }
+ }
+
+ endutxent();
+ if (num_ttys == 0)
+ errx(EXIT_FAILURE, _("%s is not logged in"), ctl->dst_login);
+ if (valid_ttys == 0) {
+ if (user_is_me) {
+ /* ok, so write to yourself! */
+ if (!ctl->src_tty_path)
+ errx(EXIT_FAILURE, _("can't find your tty's name"));
+ ctl->dst_tty_path = xstrdup(ctl->src_tty_path);
+ ctl->dst_tty_name = ctl->dst_tty_path + 5;
+ return;
+ }
+ errx(EXIT_FAILURE, _("%s has messages disabled"), ctl->dst_login);
+ }
+ if (1 < valid_ttys)
+ warnx(_("%s is logged in more than once; writing to %s"),
+ ctl->dst_login, ctl->dst_tty_name);
+}
+
+/*
+ * signal_handler - cause write loop to exit
+ */
+static void signal_handler(int signo)
+{
+ signal_received = signo;
+}
+
+/*
+ * write_line - like fputs(), but makes control characters visible and
+ * turns \n into \r\n.
+ */
+static void write_line(char *s)
+{
+ while (*s) {
+ const int c = *s++;
+
+ if ((c == '\n' && fputc_careful('\r', stdout, '^') == EOF)
+ || fputc_careful(c, stdout, '^') == EOF)
+ err(EXIT_FAILURE, _("carefulputc failed"));
+ }
+}
+
+/*
+ * do_write - actually make the connection
+ */
+static void do_write(const struct write_control *ctl)
+{
+ char *login, *pwuid;
+ struct passwd *pwd;
+ time_t now;
+ struct tm *tm;
+ char *host, line[512];
+ struct sigaction sigact;
+
+ /* Determine our login name(s) before the we reopen() stdout */
+ if ((pwd = getpwuid(ctl->src_uid)) != NULL)
+ pwuid = pwd->pw_name;
+ else
+ pwuid = "???";
+ if ((login = getlogin()) == NULL)
+ login = pwuid;
+
+ if ((freopen(ctl->dst_tty_path, "w", stdout)) == NULL)
+ err(EXIT_FAILURE, "%s", ctl->dst_tty_path);
+
+ sigact.sa_handler = signal_handler;
+ sigemptyset(&sigact.sa_mask);
+ sigact.sa_flags = 0;
+ sigaction(SIGINT, &sigact, NULL);
+ sigaction(SIGHUP, &sigact, NULL);
+
+ host = xgethostname();
+ if (!host)
+ host = xstrdup("???");
+
+ now = time((time_t *)NULL);
+ tm = localtime(&now);
+ /* print greeting */
+ printf("\r\n\a\a\a");
+ if (strcmp(login, pwuid) != 0)
+ printf(_("Message from %s@%s (as %s) on %s at %02d:%02d ..."),
+ login, host, pwuid, ctl->src_tty_name,
+ tm->tm_hour, tm->tm_min);
+ else
+ printf(_("Message from %s@%s on %s at %02d:%02d ..."),
+ login, host, ctl->src_tty_name,
+ tm->tm_hour, tm->tm_min);
+ free(host);
+ printf("\r\n");
+
+ while (fgets(line, sizeof(line), stdin) != NULL) {
+ if (signal_received)
+ break;
+ write_line(line);
+ }
+ printf("EOF\r\n");
+}
+
+int main(int argc, char **argv)
+{
+ int tty_writeable = 0, c;
+ struct write_control ctl = { 0 };
+
+ static const struct option longopts[] = {
+ {"version", no_argument, NULL, 'V'},
+ {"help", no_argument, NULL, 'h'},
+ {NULL, 0, NULL, 0}
+ };
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ close_stdout_atexit();
+
+ while ((c = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1)
+ switch (c) {
+ case 'V':
+ print_version(EXIT_SUCCESS);
+ case 'h':
+ usage();
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+
+ if (get_terminal_name(&ctl.src_tty_path, &ctl.src_tty_name, NULL) == 0) {
+ /* check that sender has write enabled */
+ if (check_tty(ctl.src_tty_path, &tty_writeable, NULL, 1))
+ exit(EXIT_FAILURE);
+ if (!tty_writeable)
+ errx(EXIT_FAILURE,
+ _("you have write permission turned off"));
+ tty_writeable = 0;
+ } else
+ ctl.src_tty_name = "<no tty>";
+
+ ctl.src_uid = getuid();
+
+ /* check args */
+ switch (argc) {
+ case 2:
+ ctl.dst_login = argv[1];
+ search_utmp(&ctl);
+ do_write(&ctl);
+ break;
+ case 3:
+ ctl.dst_login = argv[1];
+ if (!strncmp(argv[2], "/dev/", 5))
+ ctl.dst_tty_path = xstrdup(argv[2]);
+ else
+ xasprintf(&ctl.dst_tty_path, "/dev/%s", argv[2]);
+ ctl.dst_tty_name = ctl.dst_tty_path + 5;
+ if (check_utmp(&ctl))
+ errx(EXIT_FAILURE,
+ _("%s is not logged in on %s"),
+ ctl.dst_login, ctl.dst_tty_name);
+ if (check_tty(ctl.dst_tty_path, &tty_writeable, NULL, 1))
+ exit(EXIT_FAILURE);
+ if (ctl.src_uid && !tty_writeable)
+ errx(EXIT_FAILURE,
+ _("%s has messages disabled on %s"),
+ ctl.dst_login, ctl.dst_tty_name);
+ do_write(&ctl);
+ break;
+ default:
+ errtryhelp(EXIT_FAILURE);
+ }
+ free(ctl.dst_tty_path);
+ return EXIT_SUCCESS;
+}