diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 14:30:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 14:30:35 +0000 |
commit | 378c18e5f024ac5a8aef4cb40d7c9aa9633d144c (patch) | |
tree | 44dfb6ca500d32cabd450649b322a42e70a30683 /term-utils | |
parent | Initial commit. (diff) | |
download | util-linux-upstream.tar.xz util-linux-upstream.zip |
Adding upstream version 2.38.1.upstream/2.38.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
30 files changed, 10777 insertions, 0 deletions
diff --git a/term-utils/Makemodule.am b/term-utils/Makemodule.am new file mode 100644 index 0000000..119324f --- /dev/null +++ b/term-utils/Makemodule.am @@ -0,0 +1,127 @@ +if BUILD_SCRIPT +usrbin_exec_PROGRAMS += script +MANPAGES += term-utils/script.1 +dist_noinst_DATA += term-utils/script.1.adoc +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 +MANPAGES += term-utils/scriptreplay.1 +dist_noinst_DATA += term-utils/scriptreplay.1.adoc +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 +MANPAGES += term-utils/scriptlive.1 +dist_noinst_DATA += term-utils/scriptlive.1.adoc +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 +MANPAGES += term-utils/agetty.8 +dist_noinst_DATA += term-utils/agetty.8.adoc +agetty_SOURCES = term-utils/agetty.c +if USE_PLYMOUTH_SUPPORT +agetty_SOURCES += lib/plymouth-ctrl.c +endif +agetty_LDADD = $(LDADD) libcommon.la +if BSD +agetty_LDADD += -lutil +endif +endif # BUILD_AGETTY + + +if BUILD_SETTERM +usrbin_exec_PROGRAMS += setterm +MANPAGES += term-utils/setterm.1 +dist_noinst_DATA += term-utils/setterm.1.adoc +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 +MANPAGES += term-utils/mesg.1 +dist_noinst_DATA += term-utils/mesg.1.adoc +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 +MANPAGES += term-utils/wall.1 +dist_noinst_DATA += term-utils/wall.1.adoc +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 +MANPAGES += term-utils/write.1 +dist_noinst_DATA += term-utils/write.1.adoc +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..df75a17 --- /dev/null +++ b/term-utils/agetty.8 @@ -0,0 +1,593 @@ +'\" t +.\" Title: agetty +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.15 +.\" Date: 2022-08-04 +.\" Manual: System Administration +.\" Source: util-linux 2.38.1 +.\" Language: English +.\" +.TH "AGETTY" "8" "2022-08-04" "util\-linux 2.38.1" "System Administration" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +agetty \- alternative Linux getty +.SH "SYNOPSIS" +.sp +\fBagetty\fP [options] \fIport\fP [\fIbaud_rate\fP...] [\fIterm\fP] +.SH "DESCRIPTION" +.sp +\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). +.sp +\fBagetty\fP has several \fInon\-standard\fP features that are useful for hardwired and for dial\-in lines: +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +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. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +Optionally deduces the baud rate from the CONNECT messages produced by Hayes(tm)\-compatible modems. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +Optionally does not hang up when it is given an already opened line (useful for call\-back applications). +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +Optionally does not display the contents of the \fI/etc/issue\fP file. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +Optionally displays an alternative issue files or directories instead of \fI/etc/issue\fP or \fI/etc/issue.d\fP. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +Optionally does not ask for a login name. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +Optionally invokes a non\-standard login program instead of \fI/bin/login\fP. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +Optionally turns on hardware flow control. +.RE +.sp +.RS 4 +.ie n \{\ +\h'-04'\(bu\h'+03'\c +.\} +.el \{\ +. sp -1 +. IP \(bu 2.3 +.\} +Optionally forces the line to be local with no need for carrier detect. +.RE +.sp +This program does not use the \fI/etc/gettydefs\fP (System V) or \fI/etc/gettytab\fP (SunOS 4) files. +.SH "ARGUMENTS" +.sp +\fIport\fP +.RS 4 +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 "\-\-". +.RE +.sp +\fIbaud_rate\fP,... +.RS 4 +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 \(aq9600\(aq. +.RE +.sp +\fIterm\fP +.RS 4 +The value to be used for the \fBTERM\fP environment variable. This overrides whatever \fBinit\fP(1) may have set, and is inherited by login and the shell. +.sp +The default is \(aqvt100\(aq, or \(aqlinux\(aq for Linux on a virtual terminal, or \(aqhurd\(aq for GNU Hurd on a virtual terminal. +.RE +.SH "OPTIONS" +.sp +\fB\-8\fP, \fB\-\-8bits\fP +.RS 4 +Assume that the tty is 8\-bit clean, hence disable parity detection. +.RE +.sp +\fB\-a\fP, \fB\-\-autologin\fP \fIusername\fP +.RS 4 +Automatically log in the specified user without asking for a username or password. Using this option causes an \fB\-f\fP \fIusername\fP option and argument to be added to the \fB/bin/login\fP command line. See \fB\-\-login\-options\fP, which can be used to modify this option\(cqs behavior. +.sp +Note that \fB\-\-autologin\fP may affect the way in which \fBgetty\fP initializes the serial line, because on auto\-login \fBagetty\fP does not read from the line and it has no opportunity optimize the line setting. +.RE +.sp +\fB\-c\fP, \fB\-\-noreset\fP +.RS 4 +Do not reset terminal cflags (control modes). See \fBtermios\fP(3) for more details. +.RE +.sp +\fB\-E\fP, \fB\-\-remote\fP +.RS 4 +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). +.sp +If the \fB\-\-host\fP \fIfakehost\fP option is given, then an \fB\-h\fP \fIfakehost\fP option and argument are added to the \fI/bin/login\fP command line. +.sp +If the \fB\-\-nohostname\fP option is given, then an \fB\-H\fP option is added to the \fB/bin/login\fP command line. +.sp +See \fB\-\-login\-options\fP. +.RE +.sp +\fB\-f\fP, \fB\-\-issue\-file\fP \fIpath\fP +.RS 4 +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 \fI.issue\fP 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. +.RE +.sp +\fB\-\-show\-issue\fP +.RS 4 +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 \fBagetty\fP command line. +.RE +.sp +\fB\-h, \-\-flow\-control\fP +.RS 4 +Enable hardware (RTS/CTS) flow control. It is left up to the application to disable software (XON/XOFF) flow protocol where appropriate. +.RE +.sp +\fB\-H\fP, \fB\-\-host\fP \fIfakehost\fP +.RS 4 +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. +.RE +.sp +\fB\-i\fP, \fB\-\-noissue\fP +.RS 4 +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. +.RE +.sp +\fB\-I\fP, \fB\-\-init\-string\fP \fIinitstring\fP +.RS 4 +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 (\(rs). For example, to send a linefeed character (ASCII 10, octal 012), write \(rs12. +.RE +.sp +\fB\-J\fP, \fB\-\-noclear\fP +.RS 4 +Do not clear the screen before prompting for the login name. By default the screen is cleared. +.RE +.sp +\fB\-l\fP, \fB\-\-login\-program\fP \fIlogin_program\fP +.RS 4 +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. +.RE +.sp +\fB\-L\fP, \fB\-\-local\-line\fP[=\fImode\fP] +.RS 4 +Control the CLOCAL line flag. The optional \fImode\fP argument is \fBauto\fP, \fBalways\fP or \fBnever\fP. If the \fImode\fP argument is omitted, then the default is \fBalways\fP. If the \fB\-\-local\-line\fP option is not given at all, then the default is \fBauto\fP. +.sp +\fIalways\fP +.RS 4 +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. +.RE +.sp +\fInever\fP +.RS 4 +Explicitly clears the CLOCAL flag from the line setting and the carrier\-detect signal is expected on the line. +.RE +.sp +\fIauto\fP +.RS 4 +The \fBagetty\fP default. Does not modify the CLOCAL setting and follows the setting enabled by the kernel. +.RE +.RE +.sp +\fB\-m\fP, \fB\-\-extract\-baud\fP +.RS 4 +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. +.RE +.sp +\fB\-\-list\-speeds\fP +.RS 4 +Display supported baud rates. These are determined at compilation time. +.RE +.sp +\fB\-n\fP, \fB\-\-skip\-login\fP +.RS 4 +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\fP option, \fBagetty\fP 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\fP starts (usually /bin/login) is run as root. +.RE +.sp +\fB\-N\fP, \fB\-\-nonewline\fP +.RS 4 +Do not print a newline before writing out \fI/etc/issue\fP. +.RE +.sp +\fB\-o\fP, \fB\-\-login\-options\fP \fIlogin_options\fP +.RS 4 +Options and arguments that are passed to \fBlogin\fP(1). Where \(rsu is replaced by the login name. For example: +.sp +\fB\-\-login\-options \(aq\-h darkstar \-\- \(rsu\(aq\fP +.sp +See \fB\-\-autologin\fP, \fB\-\-login\-program\fP and \fB\-\-remote\fP. +.sp +Please read the \fBSECURITY NOTICE\fP below before using this option. +.RE +.sp +\fB\-p\fP, \fB\-\-login\-pause\fP +.RS 4 +Wait for any key before dropping to the login prompt. Can be combined with \fB\-\-autologin\fP to save memory by lazily spawning shells. +.RE +.sp +\fB\-r\fP, \fB\-\-chroot\fP \fIdirectory\fP +.RS 4 +Change root to the specified directory. +.RE +.sp +\fB\-R\fP, \fB\-\-hangup\fP +.RS 4 +Call \fBvhangup\fP(2) to do a virtual hangup of the specified terminal. +.RE +.sp +\fB\-s\fP, \fB\-\-keep\-baud\fP +.RS 4 +Try to keep the existing baud rate. The baud rates from the command line are used when \fBagetty\fP 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. +.RE +.sp +\fB\-t\fP, \fB\-\-timeout\fP \fItimeout\fP +.RS 4 +Terminate if no user name could be read within \fItimeout\fP seconds. Use of this option with hardwired terminal lines is not recommended. +.RE +.sp +\fB\-U\fP, \fB\-\-detect\-case\fP +.RS 4 +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. +.RE +.sp +\fB\-w\fP, \fB\-\-wait\-cr\fP +.RS 4 +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. +.RE +.sp +\fB\-\-nohints\fP +.RS 4 +Do not print hints about Num, Caps and Scroll Locks. +.RE +.sp +\fB\-\-nohostname\fP +.RS 4 +By default the hostname will be printed. With this option enabled, no hostname at all will be shown. +.RE +.sp +\fB\-\-long\-hostname\fP +.RS 4 +By default the hostname is only printed until the first dot. With this option enabled, the fully qualified hostname by \fBgethostname\fP(3P) or (if not found) by \fBgetaddrinfo\fP(3) is shown. +.RE +.sp +\fB\-\-erase\-chars\fP \fIstring\fP +.RS 4 +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 \(aqerase\(aq has been \(aq#\(aq, but since util\-linux 2.23 no additional erase characters are enabled by default. +.RE +.sp +\fB\-\-kill\-chars\fP \fIstring\fP +.RS 4 +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 \(aqkill\(aq has been \(aq@\(aq, but since util\-linux 2.23 no additional kill characters are enabled by default. +.RE +.sp +\fB\-\-chdir\fP \fIdirectory\fP +.RS 4 +Change directory before the login. +.RE +.sp +\fB\-\-delay\fP \fInumber\fP +.RS 4 +Sleep seconds before open tty. +.RE +.sp +\fB\-\-nice\fP \fInumber\fP +.RS 4 +Run login with this priority. +.RE +.sp +\fB\-\-reload\fP +.RS 4 +Ask all running \fBagetty\fP 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 \fBinotify\fP(7). +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "EXAMPLE" +.sp +This section shows examples for the process field of an entry in the \fI/etc/inittab\fP file. You\(cqll have to prepend appropriate values for the other fields. See \fBinittab\fP(5) for more details. +.sp +For a hardwired line or a console tty: +.RS 3 +.ll -.6i +.sp +\fB/sbin/agetty 9600 ttyS1\fP +.br +.RE +.ll +.sp +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 3 +.ll -.6i +.sp +\fB/sbin/agetty \-\-local\-line 9600 ttyS1 vt100\fP +.br +.RE +.ll +.sp +For an old\-style dial\-in line with a 9600/2400/1200 baud modem: +.RS 3 +.ll -.6i +.sp +\fB/sbin/agetty \-\-extract\-baud \-\-timeout 60 ttyS1 9600,2400,1200\fP +.br +.RE +.ll +.sp +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): +.RS 3 +.ll -.6i +.sp +\fB/sbin/agetty \-\-wait\-cr \-\-init\-string \(aqATE0Q1&D2&C1S0=1\(rs015\(aq 115200 ttyS1\fP +.br +.RE +.ll +.SH "SECURITY NOTICE" +.sp +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. \fBagetty\fP 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. +.sp +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 \(rsu. +.SH "ISSUE FILES" +.sp +The default issue file is \fI/etc/issue\fP. If the file exists, then \fBagetty\fP 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, then 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. +.sp +Since version 2.35 additional locations for issue file and directory are supported. If the default \fI/etc/issue\fP does not exist, then \fBagetty\fP 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 \fI/etc\fP is expected for host specific configuration, \fI/run\fP is expected for generated stuff and \fI/usr/lib\fP for static distribution maintained configuration. +.sp +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. +.sp +The issue file feature can be completely disabled by \fB\-\-noissue\fP option. +.sp +It is possible to review the current issue file by \fBagetty \-\-show\-issue\fP on the current terminal. +.sp +The issue files may contain certain escape codes to display the system name, date, time et cetera. All escape codes consist of a backslash (\(rs) immediately followed by one of the characters listed below. +.sp +4 or 4{\fIinterface\fP} +.RS 4 +Insert the IPv4 address of the specified network interface (for example: \(rs4{eth0}). If the \fIinterface\fP argument is not specified, then select the first fully configured (UP, non\-LOCALBACK, RUNNING) interface. If no configured interface is found, fall back to the IP address of the machine\(cqs hostname. +.RE +.sp +6 or 6{\fIinterface\fP} +.RS 4 +The same as \(rs4 but for IPv6. +.RE +.sp +b +.RS 4 +Insert the baudrate of the current line. +.RE +.sp +d +.RS 4 +Insert the current date. +.RE +.sp +e or e{\fIname\fP} +.RS 4 +Translate the human\-readable \fIname\fP to an escape sequence and insert it (for example: \(rse{red}Alert text.\(rse{reset}). If the \fIname\fP argument is not specified, then insert \(rs033. 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. +.RE +.sp +s +.RS 4 +Insert the system name (the name of the operating system). Same as \(aquname \-s\(aq. See also the \(rsS escape code. +.RE +.sp +S or S{VARIABLE} +.RS 4 +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 \(rss). This escape code can be used to keep \fI/etc/issue\fP distribution and release independent. Note that \(rsS{ANSI_COLOR} is converted to the real terminal escape sequence. +.RE +.sp +l +.RS 4 +Insert the name of the current tty line. +.RE +.sp +m +.RS 4 +Insert the architecture identifier of the machine. Same as \fBuname \-m\fP. +.RE +.sp +n +.RS 4 +Insert the nodename of the machine, also known as the hostname. Same as \fBuname \-n\fP. +.RE +.sp +o +.RS 4 +Insert the NIS domainname of the machine. Same as \fBhostname \-d\fP. +.RE +.sp +O +.RS 4 +Insert the DNS domainname of the machine. +.RE +.sp +r +.RS 4 +Insert the release number of the OS. Same as \fBuname \-r\fP. +.RE +.sp +t +.RS 4 +Insert the current time. +.RE +.sp +u +.RS 4 +Insert the number of current users logged in. +.RE +.sp +U +.RS 4 +Insert the string "1 user" or "<n> users" where <n> is the number of current users logged in. +.RE +.sp +v +.RS 4 +Insert the version of the OS, that is, the build\-date and such. +.RE +.sp +An example. On my system, the following \fI/etc/issue\fP file: +.sp +.if n .RS 4 +.nf +.fam C +This is \(rsn.\(rso (\(rss \(rsm \(rsr) \(rst +.fam +.fi +.if n .RE +.sp +displays as: +.sp +.if n .RS 4 +.nf +.fam C +This is thingol.orcan.dk (Linux i386 1.1.9) 18:29:30 +.fam +.fi +.if n .RE +.SH "FILES" +.sp +\fI/var/run/utmp\fP +.RS 4 +the system status file. +.RE +.sp +\fI/etc/issue\fP +.RS 4 +printed before the login prompt. +.RE +.sp +\fI/etc/os\-release /usr/lib/os\-release\fP +.RS 4 +operating system identification data. +.RE +.sp +\fI/dev/console\fP +.RS 4 +problem reports (if \fBsyslog\fP(3) is not used). +.RE +.sp +\fI/etc/inittab\fP +.RS 4 +\fBinit\fP(8) configuration file for SysV\-style init daemon. +.RE +.SH "BUGS" +.sp +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. +.sp +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. +.sp +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" +.sp +Depending on how the program was configured, all diagnostics are written to the console device or reported via the \fBsyslog\fP(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" +.sp +.MTO "werner\(atsuse.de" "Werner Fink" "," +.MTO "kzak\(atredhat.com" "Karel Zak" "" +.sp +The original \fBagetty\fP for serial terminals was written by \c +.MTO "wietse\(atwzv.win.tue.nl" "W.Z. Venema" "" +and ported to Linux by +.MTO "poe\(atdaimi.aau.dk" "Peter Orbaek" "." +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBagetty\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/term-utils/agetty.8.adoc b/term-utils/agetty.8.adoc new file mode 100644 index 0000000..123134e --- /dev/null +++ b/term-utils/agetty.8.adoc @@ -0,0 +1,338 @@ +//po4a: entry man manual += agetty(8) +:doctype: manpage +:man manual: System Administration +:man source: util-linux {release-version} +:page-layout: base +:command: agetty + +== NAME + +agetty - alternative Linux getty + +== SYNOPSIS + +*agetty* [options] _port_ [_baud_rate_...] [_term_] + +== DESCRIPTION + +*agetty* opens a tty port, prompts for a login name and invokes the /bin/login command. It is normally invoked by *init*(8). + +*agetty* has several _non-standard_ features that are useful for hardwired and for dial-in lines: + +* 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 *--erase-chars* and *--kill-chars* options. +* Optionally deduces the baud rate from the CONNECT messages produced by Hayes(tm)-compatible modems. +* Optionally does not hang up when it is given an already opened line (useful for call-back applications). +* Optionally does not display the contents of the _/etc/issue_ file. +* Optionally displays an alternative issue files or directories instead of _/etc/issue_ or _/etc/issue.d_. +* Optionally does not ask for a login name. +* Optionally invokes a non-standard login program instead of _/bin/login_. +* Optionally turns on hardware flow control. +* Optionally forces the line to be local with no need for carrier detect. + +This program does not use the _/etc/gettydefs_ (System V) or _/etc/gettytab_ (SunOS 4) files. + +== ARGUMENTS + +_port_:: +A path name relative to the _/dev_ directory. If a "-" is specified, *agetty* assumes that its standard input is already connected to a tty port and that a connection to a remote user has already been established. ++ +Under System V, a "-" _port_ argument should be preceded by a "--". + +_baud_rate_,...:: +A comma-separated list of one or more baud rates. Each time *agetty* receives a BREAK character it advances through the list, which is treated as if it were circular. ++ +Baud rates should be specified in descending order, so that the null character (Ctrl-@) can also be used for baud-rate switching. ++ +This argument is optional and unnecessary for *virtual terminals*. ++ +The default for *serial terminals* is keep the current baud rate (see *--keep-baud*) and if unsuccessful then default to '9600'. + +_term_:: +The value to be used for the *TERM* environment variable. This overrides whatever *init*(1) may have set, and is inherited by login and the shell. ++ +The default is 'vt100', or 'linux' for Linux on a virtual terminal, or 'hurd' for GNU Hurd on a virtual terminal. + +== OPTIONS + +*-8*, *--8bits*:: +Assume that the tty is 8-bit clean, hence disable parity detection. + +*-a*, *--autologin* _username_:: +Automatically log in the specified user without asking for a username or password. Using this option causes an *-f* _username_ option and argument to be added to the */bin/login* command line. See *--login-options*, which can be used to modify this option's behavior. ++ +Note that *--autologin* may affect the way in which *getty* initializes the serial line, because on auto-login *agetty* does not read from the line and it has no opportunity optimize the line setting. + +*-c*, *--noreset*:: +Do not reset terminal cflags (control modes). See *termios*(3) for more details. + +*-E*, *--remote*:: +Typically the *login*(1) command is given a remote hostname when called by something such as *telnetd*(8). This option allows *agetty* to pass what it is using for a hostname to *login*(1) for use in *utmp*(5). See *--host*, *login*(1), and *utmp*(5). ++ +If the *--host* _fakehost_ option is given, then an *-h* _fakehost_ option and argument are added to the _/bin/login_ command line. ++ +If the *--nohostname* option is given, then an *-H* option is added to the */bin/login* command line. ++ +See *--login-options*. + +*-f*, *--issue-file* _path_:: +Specifies a ":" delimited list of files and directories to be displayed instead of _/etc/issue_ (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 *--noissue* option will override this option. + +*--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. + +*-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. + +*-H*, *--host* _fakehost_:: +Write the specified _fakehost_ into the utmp file. Normally, no login host is given, since *agetty* is used for local hardwired connections and consoles. However, this option can be useful for identifying terminal concentrators and the like. + +*-i*, *--noissue*:: +Do not display the contents of _/etc/issue_ (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. + +*-I*, *--init-string* _initstring_:: +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 \12. + +*-J*, *--noclear*:: +Do not clear the screen before prompting for the login name. By default the screen is cleared. + +*-l*, *--login-program* _login_program_:: +Invoke the specified _login_program_ 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 *--login-options*. + +*-L*, *--local-line*[=__mode__]:: +Control the CLOCAL line flag. The optional _mode_ argument is *auto*, *always* or *never*. If the _mode_ argument is omitted, then the default is *always*. If the *--local-line* option is not given at all, then the default is *auto*. + +_always_;; +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. +_never_;; +Explicitly clears the CLOCAL flag from the line setting and the carrier-detect signal is expected on the line. +_auto_;; +The *agetty* default. Does not modify the CLOCAL setting and follows the setting enabled by the kernel. + +*-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>". *agetty* assumes that the modem emits its status message at the same speed as specified with (the first) _baud_rate_ value on the command line. ++ +Since the *--extract-baud* feature may fail on heavily-loaded systems, you still should enable BREAK processing by enumerating all expected baud rates on the command line. + +*--list-speeds*:: +Display supported baud rates. These are determined at compilation time. + +*-n*, *--skip-login*:: +Do not prompt the user for a login name. This can be used in connection with the *--login-program* option to invoke a non-standard login process such as a BBS system. Note that with the *--skip-login* option, *agetty* 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 *agetty* starts (usually /bin/login) is run as root. + +*-N*, *--nonewline*:: +Do not print a newline before writing out _/etc/issue_. + +*-o*, *--login-options* _login_options_:: +Options and arguments that are passed to *login*(1). Where \u is replaced by the login name. For example: ++ +*--login-options '-h darkstar \-- \u'* ++ +See *--autologin*, *--login-program* and *--remote*. ++ +Please read the *SECURITY NOTICE* below before using this option. + +*-p*, *--login-pause*:: +Wait for any key before dropping to the login prompt. Can be combined with *--autologin* to save memory by lazily spawning shells. + +*-r*, *--chroot* _directory_:: +Change root to the specified directory. + +*-R*, *--hangup*:: +Call *vhangup*(2) to do a virtual hangup of the specified terminal. + +*-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. + +*-t*, *--timeout* _timeout_:: +Terminate if no user name could be read within _timeout_ seconds. Use of this option with hardwired terminal lines is not recommended. + +*-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. + +*-w*, *--wait-cr*:: +Wait for the user or the modem to send a carriage-return or a linefeed character before sending the _/etc/issue_ file (or others) and the login prompt. This is useful with the *--init-string* option. + +*--nohints*:: +Do not print hints about Num, Caps and Scroll Locks. + +*--nohostname*:: +By default the hostname will be printed. With this option enabled, no hostname at all will be shown. + +*--long-hostname*:: +By default the hostname is only printed until the first dot. With this option enabled, the fully qualified hostname by *gethostname*(3P) or (if not found) by *getaddrinfo*(3) is shown. + +*--erase-chars* _string_:: +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. + +*--kill-chars* _string_:: +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. + +*--chdir* _directory_:: +Change directory before the login. + +*--delay* _number_:: +Sleep seconds before open tty. + +*--nice* _number_:: +Run login with this priority. + +*--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 *inotify*(7). + +include::man-common/help-version.adoc[] + +== EXAMPLE + +This section shows examples for the process field of an entry in the _/etc/inittab_ file. You'll have to prepend appropriate values for the other fields. See *inittab*(5) for more details. + +For a hardwired line or a console tty: + +____ +*/sbin/agetty 9600 ttyS1* +____ + +For a directly connected terminal without proper carrier-detect wiring (try this if your terminal just sleeps instead of giving you a password: prompt): + +____ +*/sbin/agetty --local-line 9600 ttyS1 vt100* +____ + +For an old-style dial-in line with a 9600/2400/1200 baud modem: + +____ +*/sbin/agetty --extract-baud --timeout 60 ttyS1 9600,2400,1200* +____ + +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): + +____ +*/sbin/agetty --wait-cr --init-string 'ATE0Q1&D2&C1S0=1\015' 115200 ttyS1* +____ + +== SECURITY NOTICE + +If you use the *--login-program* and *--login-options* 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. + +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. + +== ISSUE FILES + +The default issue file is _/etc/issue_. If the file exists, then *agetty* also checks for _/etc/issue.d_ directory. The directory is optional extension to the default issue file and content of the directory is printed after _/etc/issue_ content. If the _/etc/issue_ does not exist, then the directory is ignored. All files *with .issue extension* from the directory are printed in version-sort order. The directory can be used to maintain 3rd-party messages independently on the primary system _/etc/issue_ file. + +Since version 2.35 additional locations for issue file and directory are supported. If the default _/etc/issue_ does not exist, then *agetty* checks for _/run/issue_ and _/run/issue.d_, thereafter for _/usr/lib/issue_ and _/usr/lib/issue.d_. 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 *--issue-file* 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 can be completely disabled by *--noissue* option. + +It is possible to review the current issue file by *agetty --show-issue* 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. + +4 or 4{_interface_}:: +Insert the IPv4 address of the specified network interface (for example: \4\{eth0}). If the _interface_ argument is not specified, then select the first fully configured (UP, non-LOCALBACK, RUNNING) interface. If no configured interface is found, fall back to the IP address of the machine's hostname. + +6 or 6{_interface_}:: +The same as \4 but for IPv6. + +b:: +Insert the baudrate of the current line. + +d:: +Insert the current date. + +e or e{_name_}:: +Translate the human-readable _name_ to an escape sequence and insert it (for example: \e{red}Alert text.\e{reset}). If the _name_ 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. + +s:: +Insert the system name (the name of the operating system). Same as 'uname -s'. See also the \S escape code. + +S or S{VARIABLE}:: +Insert the VARIABLE data from _/etc/os-release_. If this file does not exist then fall back to _/usr/lib/os-release_. 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 _/etc/issue_ distribution and release independent. Note that \S{ANSI_COLOR} is converted to the real terminal escape sequence. + +l:: +Insert the name of the current tty line. + +m:: +Insert the architecture identifier of the machine. Same as *uname -m*. + +n:: +Insert the nodename of the machine, also known as the hostname. Same as *uname -n*. + +o:: +Insert the NIS domainname of the machine. Same as *hostname -d*. + +O:: +Insert the DNS domainname of the machine. + +r:: +Insert the release number of the OS. Same as *uname -r*. + +t:: +Insert the current time. + +u:: +Insert the number of current users logged in. + +U:: +Insert the string "1 user" or "<n> users" where <n> is the number of current users logged in. + +v:: +Insert the version of the OS, that is, the build-date and such. + +An example. On my system, the following _/etc/issue_ file: + +.... +This is \n.\o (\s \m \r) \t +.... + +displays as: + +.... +This is thingol.orcan.dk (Linux i386 1.1.9) 18:29:30 +.... + +== FILES + +_/var/run/utmp_:: +the system status file. + +_/etc/issue_:: +printed before the login prompt. + +_/etc/os-release /usr/lib/os-release_:: +operating system identification data. + +_/dev/console_:: +problem reports (if *syslog*(3) is not used). + +_/etc/inittab_:: +*init*(8) configuration file for SysV-style init daemon. + +== BUGS + +The baud-rate detection feature (the *--extract-baud* option) requires that *agetty* 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 *--extract-baud* option in combination with a multiple baud rate command-line argument, so that BREAK processing is enabled. + +The text in the _/etc/issue_ file (or other) and the login prompt are always output with 7-bit characters and space parity. + +The baud-rate detection feature (the *--extract-baud* option) requires that the modem emits its status message _after_ raising the DCD line. + +== DIAGNOSTICS + +Depending on how the program was configured, all diagnostics are written to the console device or reported via the *syslog*(3) facility. Error messages are produced if the _port_ argument does not specify a terminal device; if there is no utmp entry for the current process (System V only); and so on. + +== AUTHORS + +mailto:werner@suse.de[Werner Fink], +mailto:kzak@redhat.com[Karel Zak] + +The original *agetty* for serial terminals was written by mailto:wietse@wzv.win.tue.nl[W.Z. Venema] and ported to Linux by mailto:poe@daimi.aau.dk[Peter Orbaek]. + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/term-utils/agetty.c b/term-utils/agetty.c new file mode 100644 index 0000000..cecde2c --- /dev/null +++ b/term-utils/agetty.c @@ -0,0 +1,3009 @@ +/* + * 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 + +#ifdef HAVE_GETTTYNAM +# include <ttyent.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 */ + const char *tty; /* name of tty */ + const 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 */ + int tty_is_stdin; /* is the tty the standard input stream */ + 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(const 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 + { + int i; + + dbf = fopen(DEBUG_OUTPUT, "w"); + for (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 (options.flags & 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++; + } + } + } + + /* resolve the tty path in case it was provided as stdin */ + if (strcmp(op->tty, "-") == 0) { + op->tty_is_stdin = 1; + int fd = get_terminal_name(NULL, &op->tty, NULL); + if (fd < 0) { + log_warn(_("could not get terminal name: %d"), fd); + } + } + + /* 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); + const char *vcline = op->vcline; + const 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); + const 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(const 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 (!op->tty_is_stdin) { + 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); + +#ifdef HAVE_GETTTYNAM + if (!op->term) { + struct ttyent *ent = getttynam(tty); + /* a bit nasty as it's never freed */ + if (ent && ent->ty_type) { + op->term = strdup(ent->ty_type); + if (!op->term) + log_err(_("failed to allocate memory: %m")); + } + } +#endif + +#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 */ + /* fallthrough */ + case CTL('C'): + if (key == CTL('C') && !(op->flags & F_VCONSOLE)) + /* Ignore CTRL+C on serial line */ + break; + while (bp > logname) { + if ((tp->c_lflag & ECHO) == 0) + write_all(1, erase[cp->parity], 3); + bp--; + } + break; + case CTL('D'): + exit(EXIT_SUCCESS); + 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; + char *end = NULL; + long speed; + + errno = 0; + speed = strtol(s, &end, 10); + + if (errno || !end || end == s) + return 0; + + 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 %2d %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..937b360 --- /dev/null +++ b/term-utils/mesg.1 @@ -0,0 +1,109 @@ +'\" t +.\" Title: mesg +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.15 +.\" Date: 2022-05-11 +.\" Manual: User Commands +.\" Source: util-linux 2.38.1 +.\" Language: English +.\" +.TH "MESG" "1" "2022-05-11" "util\-linux 2.38.1" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +mesg \- display (or do not display) messages from other users +.SH "SYNOPSIS" +.sp +\fBmesg\fP [\fIoption\fP] [\fBn\fP|\fBy\fP] +.SH "DESCRIPTION" +.sp +The \fBmesg\fP 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 \fBtalk\fP(1) and \fBwrite\fP(1) may display messages on the terminal. +.sp +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, \fBmesg\fP should be executed in your login scripts. +.sp +The \fBmesg\fP utility silently exits with error status 2 if not executed on terminal. In this case execute \fBmesg\fP is pointless. The command line option \fB\-\-verbose\fP forces mesg to print a warning in this situation. This behaviour has been introduced in version 2.33. +.SH "ARGUMENTS" +.sp +\fBn\fP +.RS 4 +Disallow messages. +.RE +.sp +\fBy\fP +.RS 4 +Allow messages to be displayed. +.RE +.sp +If no arguments are given, \fBmesg\fP shows the current message status on standard error output. +.SH "OPTIONS" +.sp +\fB\-v\fP, \fB\-\-verbose\fP +.RS 4 +Explain what is being done. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "EXIT STATUS" +.sp +The \fBmesg\fP utility exits with one of the following values: +.sp +\fB0\fP +.RS 4 +Messages are allowed. +.RE +.sp +\fB1\fP +.RS 4 +Messages are not allowed. +.RE +.sp +\fB>1\fP +.RS 4 +An error has occurred. +.RE +.SH "FILES" +.sp +\fI/dev/[pt]ty[pq]?\fP +.SH "HISTORY" +.sp +A \fBmesg\fP command appeared in Version 6 AT&T UNIX. +.SH "SEE ALSO" +.sp +\fBlogin\fP(1), +\fBtalk\fP(1), +\fBwrite\fP(1), +\fBwall\fP(1), +\fBxterm\fP(1) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBmesg\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/term-utils/mesg.1.adoc b/term-utils/mesg.1.adoc new file mode 100644 index 0000000..5ccef72 --- /dev/null +++ b/term-utils/mesg.1.adoc @@ -0,0 +1,109 @@ +//po4a: entry man manual +//// +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 +//// += mesg(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: mesg + +== NAME + +mesg - display (or do not display) messages from other users + +== SYNOPSIS + +*mesg* [_option_] [*n*|*y*] + +== DESCRIPTION + +The *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 *talk*(1) and *write*(1) may display messages on the terminal. + +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, *mesg* should be executed in your login scripts. + +The *mesg* utility silently exits with error status 2 if not executed on terminal. In this case execute *mesg* is pointless. The command line option *--verbose* forces mesg to print a warning in this situation. This behaviour has been introduced in version 2.33. + +== ARGUMENTS + +*n*:: +Disallow messages. + +*y*:: +Allow messages to be displayed. + +If no arguments are given, *mesg* shows the current message status on standard error output. + +== OPTIONS + +*-v*, *--verbose*:: +Explain what is being done. + +include::man-common/help-version.adoc[] + +== EXIT STATUS + +The *mesg* utility exits with one of the following values: + +*0*:: +Messages are allowed. +*1*:: +Messages are not allowed. +*>1*:: +An error has occurred. + +== FILES + +_/dev/[pt]ty[pq]?_ + +== HISTORY + +A *mesg* command appeared in Version 6 AT&T UNIX. + +== SEE ALSO + +*login*(1), +*talk*(1), +*write*(1), +*wall*(1), +*xterm*(1) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/term-utils/mesg.c b/term-utils/mesg.c new file mode 100644 index 0000000..cb0b493 --- /dev/null +++ b/term-utils/mesg.c @@ -0,0 +1,185 @@ +/* + * 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 (!*argv) { + if (stat(tty, &sb)) + err(MESG_EXIT_FAILURE, _("stat of %s failed"), tty); + if (sb.st_mode & (S_IWGRP | S_IWOTH)) { + puts(_("is y")); + return IS_ALLOWED; + } + puts(_("is n")); + return IS_NOT_ALLOWED; + } + + 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); + + 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/meson.build b/term-utils/meson.build new file mode 100644 index 0000000..f93ee94 --- /dev/null +++ b/term-utils/meson.build @@ -0,0 +1,41 @@ +script_sources = files( + 'script.c', +) + \ + pty_session_c + \ + monotonic_c + +scriptlive_sources = files( + 'scriptlive.c', + 'script-playutils.c', + 'script-playutils.h', +) + \ + pty_session_c + \ + monotonic_c + +scriptreplay_sources = files( + 'scriptreplay.c', + 'script-playutils.c', + 'script-playutils.h', +) + +agetty_sources = files( + 'agetty.c', +) + +setterm_sources = files( + 'setterm.c', +) + +mesg_sources = files( + 'mesg.c', +) + +wall_sources = files( + 'wall.c', + 'ttymsg.c', + 'ttymsg.h', +) + +write_sources = files( + 'write.c', +) diff --git a/term-utils/script-playutils.c b/term-utils/script-playutils.c new file mode 100644 index 0000000..cd598d2 --- /dev/null +++ b/term-utils/script-playutils.c @@ -0,0 +1,573 @@ +#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; + int64_t sec = 0, usec = 0; + + switch (type) { + case 'O': /* output */ + case 'I': /* input */ + rc = fscanf(f, "%"SCNd64".%06"SCNd64" %zu%c\n", + &sec, &usec, &step->size, &nl); + if (rc != 4 || nl != '\n') + rc = -EINVAL; + else + rc = 0; + + step->delay.tv_sec = (time_t) sec; + step->delay.tv_usec = (suseconds_t) usec; + break; + + case 'S': /* signal */ + case 'H': /* header */ + { + char buf[BUFSIZ]; + + rc = fscanf(f, "%"SCNd64".%06"SCNd64" ", + &sec, &usec); + if (rc != 2) + break; + + step->delay.tv_sec = (time_t) sec; + step->delay.tv_usec = (suseconds_t) usec; + + 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=%"PRId64".%06"PRId64"]", + step->type, + (int64_t) step->delay.tv_sec, + (int64_t) 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=%"PRId64".%06"PRId64 + "(ignored=%"PRId64".%06"PRId64") size=%zu]", + rc, + (int64_t) step->delay.tv_sec, (int64_t) step->delay.tv_usec, + (int64_t) ignored_delay.tv_sec, (int64_t) 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..8ca2267 --- /dev/null +++ b/term-utils/script.1 @@ -0,0 +1,194 @@ +'\" t +.\" Title: script +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.15 +.\" Date: 2022-08-04 +.\" Manual: User Commands +.\" Source: util-linux 2.38.1 +.\" Language: English +.\" +.TH "SCRIPT" "1" "2022-08-04" "util\-linux 2.38.1" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +script \- make typescript of terminal session +.SH "SYNOPSIS" +.sp +\fBscript\fP [options] [\fIfile\fP] +.SH "DESCRIPTION" +.sp +\fBscript\fP 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 \fBscriptreplay\fP(1) and to store additional information about the session. +.sp +Since version 2.35, \fBscript\fP supports multiple streams and allows the logging of input and output to separate files or all the one file. This version also supports a new timing file which records additional information. The command \fBscriptreplay \-\-summary\fP then provides all the information. +.sp +If the argument \fIfile\fP or option \fB\-\-log\-out\fP \fIfile\fP is given, \fBscript\fP saves the dialogue in this \fIfile\fP. If no filename is given, the dialogue is saved in the file \fItypescript\fP. +.sp +Note that logging input using \fB\-\-log\-in\fP or \fB\-\-log\-io\fP 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" +.sp +Below, the \fIsize\fP 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. +.sp +\fB\-a\fP, \fB\-\-append\fP +.RS 4 +Append the output to \fIfile\fP or to \fItypescript\fP, retaining the prior contents. +.RE +.sp +\fB\-c\fP, \fB\-\-command\fP \fIcommand\fP +.RS 4 +Run the \fIcommand\fP 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. +.RE +.sp +\fB\-E\fP, \fB\-\-echo\fP \fIwhen\fP +.RS 4 +This option controls the \fBECHO\fP flag for the slave end of the session\(cqs pseudoterminal. The supported modes are \fIalways\fP, \fInever\fP, or \fIauto\fP. +.sp +The default is \fIauto\fP \(em in this case, \fBECHO\fP enabled for the pseudoterminal slave; if the current standard input is a terminal, \fBECHO\fP is disabled for it to prevent double echo; if the current standard input is not a terminal (for example pipe: \fBecho date | script\fP) then keeping \fBECHO\fP enabled for the pseudoterminal slave enables the standard input data to be viewed on screen while being recorded to session log simultaneously. +.sp +Note that \(aqnever\(aq mode affects content of the session output log, because users input is not repeated on output. +.RE +.sp +\fB\-e\fP, \fB\-\-return\fP +.RS 4 +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. +.RE +.sp +\fB\-f\fP, \fB\-\-flush\fP +.RS 4 +Flush output after each write. This is nice for telecooperation: one person does \fBmkfifo\fP \fIfoo\fP; \fBscript \-f\fP \fIfoo\fP, and another can supervise in real\-time what is being done using \fBcat\fP \fIfoo\fP. Note that flush has an impact on performance; it\(cqs possible to use \fBSIGUSR1\fP to flush logs on demand. +.RE +.sp +\fB\-\-force\fP +.RS 4 +Allow the default output file \fItypescript\fP to be a hard or symbolic link. The command will follow a symbolic link. +.RE +.sp +\fB\-B\fP, \fB\-\-log\-io\fP \fIfile\fP +.RS 4 +Log input and output to the same \fIfile\fP. Note, this option makes sense only if \fB\-\-log\-timing\fP is also specified, otherwise it\(cqs impossible to separate output and input streams from the log \fIfile\fP. +.RE +.sp +\fB\-I\fP, \fB\-\-log\-in\fP \fIfile\fP +.RS 4 +Log input to the \fIfile\fP. The log output is disabled if only \fB\-\-log\-in\fP specified. +.sp +Use this logging functionality carefully as it logs all input, including input when terminal has disabled echo flag (for example, password inputs). +.RE +.sp +\fB\-O\fP, \fB\-\-log\-out\fP \fIfile\fP +.RS 4 +Log output to the \fIfile\fP. The default is to log output to the file with name \fItypescript\fP if the option \fB\-\-log\-out\fP or \fB\-\-log\-in\fP is not given. The log output is disabled if only \fB\-\-log\-in\fP specified. +.RE +.sp +\fB\-T\fP, \fB\-\-log\-timing\fP \fIfile\fP +.RS 4 +Log timing information to the \fIfile\fP. 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\fP or when \fB\-\-log\-in\fP and \fB\-\-log\-out\fP are used together. See also \fB\-\-logging\-format\fP. +.RE +.sp +\fB\-m\fP, \fB\-\-logging\-format\fP \fIformat\fP +.RS 4 +Force use of \fIadvanced\fP or \fIclassic\fP 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 +\fBClassic format\fP +.RS 4 +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. +.RE +.sp +\fBAdvanced (multi\-stream) format\fP +.RS 4 +The first field is an entry type identifier (\(aqI\(cqnput, \(aqO\(cqutput, \(aqH\(cqeader, \(aqS\(cqignal). The second field is how much time elapsed since the previous entry, and the rest of the entry is type\-specific data. +.RE +.RE +.sp +\fB\-o\fP, \fB\-\-output\-limit\fP \fIsize\fP +.RS 4 +Limit the size of the typescript and timing files to \fIsize\fP and stop the child process after this size is exceeded. The calculated file size does not include the start and done messages that the \fBscript\fP command prepends and appends to the child process output. Due to buffering, the resulting output file might be larger than the specified value. +.RE +.sp +\fB\-q\fP, \fB\-\-quiet\fP +.RS 4 +Be quiet (do not write start and done messages to standard output). +.RE +.sp +\fB\-t\fP[\fIfile\fP], \fB\-\-timing\fP[=\fIfile\fP] +.RS 4 +Output timing data to standard error, or to \fIfile\fP when given. This option is deprecated in favour of \fB\-\-log\-timing\fP where the \fIfile\fP argument is not optional. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "SIGNALS" +.sp +Upon receiving \fBSIGUSR1\fP, \fBscript\fP immediately flushes the output files. +.SH "ENVIRONMENT" +.sp +The following environment variable is utilized by \fBscript\fP: +.sp +\fBSHELL\fP +.RS 4 +If the variable \fBSHELL\fP exists, the shell forked by \fBscript\fP will be that shell. If \fBSHELL\fP is not set, the Bourne shell is assumed. (Most shells set this variable automatically). +.RE +.SH "NOTES" +.sp +The script ends when the forked shell exits (a \fIcontrol\-D\fP for the Bourne shell (\fBsh\fP(1p)), and \fIexit\fP, \fIlogout\fP or \fIcontrol\-d\fP (if \fIignoreeof\fP is not set) for the C\-shell, \fBcsh\fP(1)). +.sp +Certain interactive commands, such as \fBvi\fP(1), create garbage in the typescript file. \fBscript\fP works best with commands that do not manipulate the screen, the results are meant to emulate a hardcopy terminal. +.sp +It is not recommended to run \fBscript\fP in non\-interactive shells. The inner shell of \fBscript\fP is always interactive, and this could lead to unexpected results. If you use \fBscript\fP in the shell initialization file, you have to avoid entering an infinite loop. You can use for example the \fB.profile\fP file, which is read by login shells only: +.sp +.if n .RS 4 +.nf +.fam C +if test \-t 0 ; then + script + exit +fi +.fam +.fi +.if n .RE +.sp +You should also avoid use of \fBscript\fP in command pipes, as \fBscript\fP can read more input than you would expect. +.SH "HISTORY" +.sp +The \fBscript\fP command appeared in 3.0BSD. +.SH "BUGS" +.sp +\fBscript\fP places \fIeverything\fP in the log file, including linefeeds and backspaces. This is not what the naive user expects. +.sp +\fBscript\fP is primarily designed for interactive terminal sessions. When stdin is not a terminal (for example: \fBecho foo | script\fP), then the session can hang, because the interactive shell within the script session misses EOF and \fBscript\fP has no clue when to close the session. See the \fBNOTES\fP section for more information. +.SH "SEE ALSO" +.sp +\fBcsh\fP(1) (for the \fIhistory\fP mechanism), +\fBscriptreplay\fP(1), +\fBscriptlive\fP(1) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBscript\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/term-utils/script.1.adoc b/term-utils/script.1.adoc new file mode 100644 index 0000000..7921361 --- /dev/null +++ b/term-utils/script.1.adoc @@ -0,0 +1,173 @@ +//po4a: entry man manual +//// +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 +//// += script(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: script +:plus: + + +== NAME + +script - make typescript of terminal session + +== SYNOPSIS + +*script* [options] [_file_] + +== DESCRIPTION + +*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 *scriptreplay*(1) and to store additional information about the session. + +Since version 2.35, *script* supports multiple streams and allows the logging of input and output to separate files or all the one file. This version also supports a new timing file which records additional information. The command *scriptreplay --summary* then provides all the information. + +If the argument _file_ or option *--log-out* _file_ is given, *script* saves the dialogue in this _file_. If no filename is given, the dialogue is saved in the file _typescript_. + +Note that logging input using *--log-in* or *--log-io* may record security-sensitive information as the log file contains all terminal session input (e.g., passwords) independently of the terminal echo flag setting. + +== OPTIONS + +Below, the _size_ 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. + +*-a*, *--append*:: +Append the output to _file_ or to _typescript_, retaining the prior contents. + +*-c*, *--command* _command_:: +Run the _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. + +*-E*, *--echo* _when_:: +This option controls the *ECHO* flag for the slave end of the session's pseudoterminal. The supported modes are _always_, _never_, or _auto_. ++ +The default is _auto_ -- in this case, *ECHO* enabled for the pseudoterminal slave; if the current standard input is a terminal, *ECHO* is disabled for it to prevent double echo; if the current standard input is not a terminal (for example pipe: *echo date | script*) then keeping *ECHO* enabled for the pseudoterminal slave enables the standard input data to be viewed on screen while being recorded to session log simultaneously. ++ +Note that 'never' mode affects content of the session output log, because users input is not repeated on output. + +*-e*, *--return*:: +Return the exit status of the child process. Uses the same format as bash termination on signal termination (i.e., exit status is 128 {plus} the signal number). The exit status of the child process is always stored in the type script file too. +//TRANSLATORS: Keep {plus} untranslated. + +*-f*, *--flush*:: +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. + +*--force*:: +Allow the default output file _typescript_ to be a hard or symbolic link. The command will follow a symbolic link. + +*-B*, *--log-io* _file_:: +Log input and output to the same _file_. Note, this option makes sense only if *--log-timing* is also specified, otherwise it's impossible to separate output and input streams from the log _file_. + +*-I*, *--log-in* _file_:: +Log input to the _file_. The log output is disabled if only *--log-in* specified. ++ +Use this logging functionality carefully as it logs all input, including input when terminal has disabled echo flag (for example, password inputs). + +*-O*, *--log-out* _file_:: +Log output to the _file_. The default is to log output to the file with name _typescript_ if the option *--log-out* or *--log-in* is not given. The log output is disabled if only *--log-in* specified. + +*-T*, *--log-timing* _file_:: +Log timing information to the _file_. 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 *--log-io* or when *--log-in* and *--log-out* are used together. See also *--logging-format*. + +*-m*, *--logging-format* _format_:: +Force use of _advanced_ or _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. ++ +*Classic format*;; +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. ++ +*Advanced (multi-stream) format*;; +The first field is an entry type identifier ('I'nput, 'O'utput, 'H'eader, 'S'ignal). The second field is how much time elapsed since the previous entry, and the rest of the entry is type-specific data. + +*-o*, *--output-limit* _size_:: +Limit the size of the typescript and timing files to _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 *script* command prepends and appends to the child process output. Due to buffering, the resulting output file might be larger than the specified value. + +*-q*, *--quiet*:: +Be quiet (do not write start and done messages to standard output). + +*-t*[_file_], *--timing*[=_file_]:: +Output timing data to standard error, or to _file_ when given. This option is deprecated in favour of *--log-timing* where the _file_ argument is not optional. + +include::man-common/help-version.adoc[] + +== SIGNALS + +Upon receiving *SIGUSR1*, *script* immediately flushes the output files. + +== ENVIRONMENT + +The following environment variable is utilized by *script*: + +*SHELL*:: +If the variable *SHELL* exists, the shell forked by *script* will be that shell. If *SHELL* is not set, the Bourne shell is assumed. (Most shells set this variable automatically). + +== NOTES + +The script ends when the forked shell exits (a _control-D_ for the Bourne shell (*sh*(1p)), and _exit_, _logout_ or _control-d_ (if _ignoreeof_ is not set) for the C-shell, *csh*(1)). + +Certain interactive commands, such as *vi*(1), create garbage in the typescript file. *script* works best with commands that do not manipulate the screen, the results are meant to emulate a hardcopy terminal. + +It is not recommended to run *script* in non-interactive shells. The inner shell of *script* is always interactive, and this could lead to unexpected results. If you use *script* in the shell initialization file, you have to avoid entering an infinite loop. You can use for example the *.profile* file, which is read by login shells only: + +.... +if test -t 0 ; then + script + exit +fi +.... + +You should also avoid use of *script* in command pipes, as *script* can read more input than you would expect. + +== HISTORY + +The *script* command appeared in 3.0BSD. + +== BUGS + +*script* places _everything_ in the log file, including linefeeds and backspaces. This is not what the naive user expects. + +*script* is primarily designed for interactive terminal sessions. When stdin is not a terminal (for example: *echo foo | script*), then the session can hang, because the interactive shell within the script session misses EOF and *script* has no clue when to close the session. See the *NOTES* section for more information. + +== SEE ALSO + +*csh*(1) (for the _history_ mechanism), +*scriptreplay*(1), +*scriptlive*(1) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/term-utils/script.c b/term-utils/script.c new file mode 100644 index 0000000..c918ecd --- /dev/null +++ b/term-utils/script.c @@ -0,0 +1,1074 @@ +/* + * 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; + const char *command; + char *command_norm; /* normalized (without \n) */ + 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, ...) + __attribute__((__format__ (__printf__, 3, 4))); + +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 in session (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 = { 0 }, delta = { 0 }; + + gettime_monotonic(&now); + timersub(&now, &log->starttime, &delta); + + log_info(ctl, "DURATION", "%"PRId64".%06"PRId64, + (int64_t)delta.tv_sec, + (int64_t)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: + { + int x = 0; + 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->command) + x += fprintf(log->fp, "COMMAND=\"%s\"", ctl->command_norm); + + if (ctl->isterm) { + init_terminal_info(ctl); + + if (ctl->ttytype) + x += fprintf(log->fp, "%*sTERM=\"%s\"", !!x, "", ctl->ttytype); + if (ctl->ttyname) + x += fprintf(log->fp, "%*sTTY=\"%s\"", !!x, "", ctl->ttyname); + + x += fprintf(log->fp, "%*sCOLUMNS=\"%d\" LINES=\"%d\"", !!x, "", + ctl->ttycols, ctl->ttylines); + } else + fprintf(log->fp, _("%*s<not executed on terminal>"), !!x, ""); + + 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, "%"PRId64".%06"PRId64" %zd\n", + (int64_t)delta.tv_sec, (int64_t)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 %"PRId64".%06"PRId64" %zd\n", + stream->ident, + (int64_t)delta.tv_sec, (int64_t)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 __attribute__ ((__format__ (__printf__, 3, 4))) + log_signal(struct script_control *ctl, int signum, const 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 %"PRId64".%06"PRId64" SIG%s %s\n", + (int64_t)delta.tv_sec, (int64_t)delta.tv_usec, + signum_to_signame(signum), msg); + else + sz = fprintf(log->fp, "S %"PRId64".%06"PRId64" SIG%s\n", + (int64_t)delta.tv_sec, (int64_t)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 %zd bytes [summary=%" PRIu64 ", max=%" PRIu64 "]", 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 = 1; + const char *outfile = NULL, *infile = NULL; + const char *timingfile = NULL, *shell = 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)); + + ctl.isterm = isatty(STDIN_FILENO); + + 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': + ctl.command = optarg; + ctl.command_norm = xstrdup(ctl.command); + strrep(ctl.command_norm, '\n', ' '); + 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 (ctl.command) + execl(shell, shname, "-c", ctl.command, (char *)NULL); + else + execl(shell, shname, "-i", (char *)NULL); + } else { + if (ctl.command) + execlp(shname, shname, "-c", ctl.command, (char *)NULL); + else + execlp(shname, 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", "%s", buf); + + if (ctl.isterm) { + init_terminal_info(&ctl); + log_info(&ctl, "TERM", "%s", ctl.ttytype); + log_info(&ctl, "TTY", "%s", ctl.ttyname); + log_info(&ctl, "COLUMNS", "%d", ctl.ttycols); + log_info(&ctl, "LINES", "%d", ctl.ttylines); + } + log_info(&ctl, "SHELL", "%s", shell); + if (ctl.command) + log_info(&ctl, "COMMAND", "%s", ctl.command_norm); + log_info(&ctl, "TIMING_LOG", "%s", timingfile); + if (outfile) + log_info(&ctl, "OUTPUT_LOG", "%s", outfile); + if (infile) + log_info(&ctl, "INPUT_LOG", "%s", 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..74f2829 --- /dev/null +++ b/term-utils/scriptlive.1 @@ -0,0 +1,121 @@ +'\" t +.\" Title: scriptlive +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.15 +.\" Date: 2022-05-11 +.\" Manual: User Commands +.\" Source: util-linux 2.38.1 +.\" Language: English +.\" +.TH "SCRIPTLIVE" "1" "2022-05-11" "util\-linux 2.38.1" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +scriptlive \- re\-run session typescripts, using timing information +.SH "SYNOPSIS" +.sp +\fBscriptlive\fP [options] [\fB\-t\fP] \fItimingfile\fP [\fB\-I\fP|\fB\-B\fP] \fItypescript\fP +.SH "DESCRIPTION" +.sp +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. +.sp +The \fBsession is executed\fP in a newly created pseudoterminal with the user\(cqs $SHELL (or defaults to \fI/bin/bash\fP). +.sp +\fBBe careful!\fP Do not forget that the typescript may contains arbitrary commands. It is recommended to use \fB"scriptreplay \-\-stream in \-\-log\-in typescript"\fP (or with \fB\-\-log\-io\fP instead of \fB\-\-log\-in\fP) to verify the typescript before it is executed by \fBscriptlive\fP. +.sp +The timing information is what \fBscript\fP(1) outputs to file specified by \fB\-\-log\-timing\fP. The typescript has to contain stdin information and it is what script1 outputs to file specified by \fB\-\-log\-in\fP or \fB\-\-log\-io\fP. +.SH "OPTIONS" +.sp +\fB\-I\fP, \fB\-\-log\-in\fP \fIfile\fP +.RS 4 +File containing \fBscript\fP\(aqs terminal input. +.RE +.sp +\fB\-B\fP, \fB\-\-log\-io\fP \fIfile\fP +.RS 4 +File containing \fBscript\fP\(aqs terminal output and input. +.RE +.sp +\fB\-t\fP, \fB\-\-timing\fP \fIfile\fP +.RS 4 +File containing \fBscript\fP\(aqs timing output. This option overrides old\-style arguments. +.RE +.sp +\fB\-T\fP, \fB\-\-log\-timing\fP \fIfile\fP +.RS 4 +Aliased to \fB\-t\fP, maintained for compatibility with \fBscript\fP(1) command\-line options. +.RE +.sp +\fB\-d\fP, \fB\-\-divisor\fP \fInumber\fP +.RS 4 +Speed up the replay displaying this \fInumber\fP of times. The argument is a floating\-point number. It\(cqs called divisor because it divides the timings by this factor. This option overrides old\-style arguments. +.RE +.sp +\fB\-m\fP, \fB\-\-maxdelay\fP \fInumber\fP +.RS 4 +Set the maximum delay between updates to \fInumber\fP of seconds. The argument is a floating\-point number. This can be used to avoid long pauses in the typescript replay. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "EXAMPLES" +.sp +.if n .RS 4 +.nf +.fam C +% 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 +.fam +.fi +.if n .RE +.SH "AUTHORS" +.sp +.MTO "kzak\(atredhat.com" "Karel Zak" "" +.SH "COPYRIGHT" +.sp +Copyright © 2019 Karel Zak +.sp +This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.sp +Released under the GNU General Public License version 2 or later. +.SH "SEE ALSO" +.sp +\fBscript\fP(1), +\fBscriptreplay\fP(1) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBscriptlive\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/term-utils/scriptlive.1.adoc b/term-utils/scriptlive.1.adoc new file mode 100644 index 0000000..73e8faf --- /dev/null +++ b/term-utils/scriptlive.1.adoc @@ -0,0 +1,86 @@ +//po4a: entry man manual += scriptlive(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: scriptlive +:copyright: © + +== NAME + +scriptlive - re-run session typescripts, using timing information + +== SYNOPSIS + +*scriptlive* [options] [*-t*] _timingfile_ [*-I*|*-B*] _typescript_ + +== 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. + +The *session is executed* in a newly created pseudoterminal with the user's $SHELL (or defaults to _/bin/bash_). + +*Be careful!* Do not forget that the typescript may contains arbitrary commands. It is recommended to use *"scriptreplay --stream in --log-in typescript"* (or with *--log-io* instead of *--log-in*) to verify the typescript before it is executed by *scriptlive*. + +The timing information is what *script*(1) outputs to file specified by *--log-timing*. The typescript has to contain stdin information and it is what script1 outputs to file specified by *--log-in* or *--log-io*. + +== OPTIONS + +*-I*, *--log-in* _file_:: +File containing *script*'s terminal input. + +*-B*, *--log-io* _file_:: +File containing *script*'s terminal output and input. + +*-t*, *--timing* _file_:: +File containing *script*'s timing output. This option overrides old-style arguments. + +*-T*, *--log-timing* _file_:: +Aliased to *-t*, maintained for compatibility with *script*(1) command-line options. + +*-d*, *--divisor* _number_:: +Speed up the replay displaying this _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. + +*-m*, *--maxdelay* _number_:: +Set the maximum delay between updates to _number_ of seconds. The argument is a floating-point number. This can be used to avoid long pauses in the typescript replay. + +include::man-common/help-version.adoc[] + +== EXAMPLES + +.... +% 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 +.... + +== AUTHORS + +mailto:kzak@redhat.com[Karel Zak] + +== COPYRIGHT + +//TRANSLATORS: Keep {copyright} untranslated. +Copyright {copyright} 2019 Karel Zak + +This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +Released under the GNU General Public License version 2 or later. + +== SEE ALSO + +*script*(1), +*scriptreplay*(1) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/term-utils/scriptlive.c b/term-utils/scriptlive.c new file mode 100644 index 0000000..d81712d --- /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 = { 0 }, target = { 0 }; + + 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, shname, "-c", command, (char *)NULL); + else + execlp(shname, 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..f98159f --- /dev/null +++ b/term-utils/scriptreplay.1 @@ -0,0 +1,155 @@ +'\" t +.\" Title: scriptreplay +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.15 +.\" Date: 2022-05-11 +.\" Manual: User Commands +.\" Source: util-linux 2.38.1 +.\" Language: English +.\" +.TH "SCRIPTREPLAY" "1" "2022-05-11" "util\-linux 2.38.1" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +scriptreplay \- play back typescripts, using timing information +.SH "SYNOPSIS" +.sp +\fBscriptreplay\fP [options] [\fB\-t\fP] \fItimingfile\fP [\fItypescript\fP [\fIdivisor\fP]] +.SH "DESCRIPTION" +.sp +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. +.sp +The replay simply displays the information again; the programs that were run when the typescript was being recorded are \fBnot run again\fP. Since the same information is simply being displayed, \fBscriptreplay\fP 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 \fBscriptreplay\fP is sending its output. +.sp +The timing information is what \fBscript\fP(1) outputs to file specified by \fB\-\-log\-timing\fP. +.sp +By default, the typescript to display is assumed to be named \fItypescript\fP, but other filenames may be specified, as the second parameter or with option \fB\-\-log\-out\fP. +.sp +If the third parameter or \fB\-\-divisor\fP is specified, it is used as a speed\-up multiplier. For example, a speed\-up of 2 makes \fBscriptreplay\fP go twice as fast, and a speed\-down of 0.1 makes it go ten times slower than the original session. +.SH "OPTIONS" +.sp +\fB\-I\fP, \fB\-\-log\-in\fP \fIfile\fP +.RS 4 +File containing \fBscript\fP\(aqs terminal input. +.RE +.sp +\fB\-O\fP, \fB\-\-log\-out\fP \fIfile\fP +.RS 4 +File containing \fBscript\fP\(aqs terminal output. +.RE +.sp +\fB\-B\fP, \fB\-\-log\-io\fP \fIfile\fP +.RS 4 +File containing \fBscript\fP\(aqs terminal output and input. +.RE +.sp +\fB\-t\fP, \fB\-\-timing\fP \fIfile\fP +.RS 4 +File containing \fBscript\fP\(aqs timing output. This option overrides old\-style arguments. +.RE +.sp +\fB\-T\fP, \fB\-\-log\-timing\fP \fIfile\fP +.RS 4 +This is an alias for \fB\-t\fP, maintained for compatibility with \fBscript\fP(1) command\-line options. +.RE +.sp +\fB\-s\fP, \fB\-\-typescript\fP \fIfile\fP +.RS 4 +File containing \fBscript\fP\(aqs terminal output. Deprecated alias to \fB\-\-log\-out\fP. This option overrides old\-style arguments. +.RE +.sp +\fB\-c\fP, \fB\-\-cr\-mode\fP \fImode\fP +.RS 4 +Specifies how to use the CR (0x0D, carriage return) character from log files. The default mode is \fIauto\fP, in this case CR is replaced with line break for stdin log, because otherwise \fBscriptreplay\fP would overwrite the same line. The other modes are \fInever\fP and \fIalways\fP. +.RE +.sp +\fB\-d\fP, \fB\-\-divisor\fP \fInumber\fP +.RS 4 +Speed up the replay displaying this \fInumber\fP of times. The argument is a floating\-point number. It\(cqs called divisor because it divides the timings by this factor. This option overrides old\-style arguments. +.RE +.sp +\fB\-m\fP, \fB\-\-maxdelay\fP \fInumber\fP +.RS 4 +Set the maximum delay between updates to \fInumber\fP of seconds. The argument is a floating\-point number. This can be used to avoid long pauses in the typescript replay. +.RE +.sp +\fB\-\-summary\fP +.RS 4 +Display details about the session recorded in the specified timing file and exit. The session has to be recorded using \fIadvanced\fP format (see \fBscript\fP(1) option \fB\-\-logging\-format\fP for more details). +.RE +.sp +\fB\-x\fP, \fB\-\-stream\fP \fItype\fP +.RS 4 +Forces \fBscriptreplay\fP to print only the specified stream. The supported stream types are \fIin\fP, \fIout\fP, \fIsignal\fP, or \fIinfo\fP. This option is recommended for multi\-stream logs (e.g., \fB\-\-log\-io\fP) in order to print only specified data. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "EXAMPLES" +.sp +.if n .RS 4 +.nf +.fam C +% 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 +.fam +.fi +.if n .RE +.SH "AUTHORS" +.sp +The original \fBscriptreplay\fP program was written by \c +.MTO "joey\(atkitenet.net" "Joey Hess" "." +The program was re\-written in C by +.MTO "jay\(atgnu.org" "James Youngman" "" +and +.MTO "kzak\(atredhat.com" "Karel Zak" "" +.SH "COPYRIGHT" +.sp +Copyright © 2008 James Youngman +.sp +Copyright © 2008\-2019 Karel Zak +.sp +This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +.sp +Released under the GNU General Public License version 2 or later. +.SH "SEE ALSO" +.sp +\fBscript\fP(1), +\fBscriptlive\fP(1) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBscriptreplay\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/term-utils/scriptreplay.1.adoc b/term-utils/scriptreplay.1.adoc new file mode 100644 index 0000000..2f4df0c --- /dev/null +++ b/term-utils/scriptreplay.1.adoc @@ -0,0 +1,106 @@ +//po4a: entry man manual += scriptreplay(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: scriptreplay +:copyright: © + +== NAME + +scriptreplay - play back typescripts, using timing information + +== SYNOPSIS + +*scriptreplay* [options] [*-t*] _timingfile_ [_typescript_ [_divisor_]] + +== 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. + +The replay simply displays the information again; the programs that were run when the typescript was being recorded are *not run again*. Since the same information is simply being displayed, *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 *scriptreplay* is sending its output. + +The timing information is what *script*(1) outputs to file specified by *--log-timing*. + +By default, the typescript to display is assumed to be named _typescript_, but other filenames may be specified, as the second parameter or with option *--log-out*. + +If the third parameter or *--divisor* is specified, it is used as a speed-up multiplier. For example, a speed-up of 2 makes *scriptreplay* go twice as fast, and a speed-down of 0.1 makes it go ten times slower than the original session. + +== OPTIONS + +*-I*, *--log-in* _file_:: +File containing *script*'s terminal input. + +*-O*, *--log-out* _file_:: +File containing *script*'s terminal output. + +*-B*, *--log-io* _file_:: +File containing *script*'s terminal output and input. + +*-t*, *--timing* _file_:: +File containing *script*'s timing output. This option overrides old-style arguments. + +*-T*, *--log-timing* _file_:: +This is an alias for *-t*, maintained for compatibility with *script*(1) command-line options. + +*-s*, *--typescript* _file_:: +File containing *script*'s terminal output. Deprecated alias to *--log-out*. This option overrides old-style arguments. + +*-c*, *--cr-mode* _mode_:: +Specifies how to use the CR (0x0D, carriage return) character from log files. The default mode is _auto_, in this case CR is replaced with line break for stdin log, because otherwise *scriptreplay* would overwrite the same line. The other modes are _never_ and _always_. + +*-d*, *--divisor* _number_:: +Speed up the replay displaying this _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. + +*-m*, *--maxdelay* _number_:: +Set the maximum delay between updates to _number_ of seconds. The argument is a floating-point number. This can be used to avoid long pauses in the typescript replay. + +*--summary*:: +Display details about the session recorded in the specified timing file and exit. The session has to be recorded using _advanced_ format (see *script*(1) option *--logging-format* for more details). + +*-x*, *--stream* _type_:: +Forces *scriptreplay* to print only the specified stream. The supported stream types are _in_, _out_, _signal_, or _info_. This option is recommended for multi-stream logs (e.g., *--log-io*) in order to print only specified data. + +include::man-common/help-version.adoc[] + +== EXAMPLES + +.... +% 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 +.... + +== AUTHORS + +The original *scriptreplay* program was written by mailto:joey@kitenet.net[Joey Hess]. The program was re-written in C by mailto:jay@gnu.org[James Youngman] and mailto:kzak@redhat.com[Karel Zak] + +== COPYRIGHT + +//TRANSLATORS: Keep {copyright} untranslated. +Copyright {copyright} 2008 James Youngman + +//TRANSLATORS: Keep {copyright} untranslated. +Copyright {copyright} 2008-2019 Karel Zak + +This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +Released under the GNU General Public License version 2 or later. + +== SEE ALSO + +*script*(1), +*scriptlive*(1) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/term-utils/scriptreplay.c b/term-utils/scriptreplay.c new file mode 100644 index 0000000..fb68499 --- /dev/null +++ b/term-utils/scriptreplay.c @@ -0,0 +1,339 @@ +/* + * 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 <termios.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 %"PRId64".%06"PRId64, + (int64_t) delay->tv_sec, (int64_t) 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; +} + +static int +setterm(struct termios *backup) +{ + struct termios tattr; + + if (tcgetattr(STDOUT_FILENO, backup) != 0) { + if (errno != ENOTTY) /* For debugger. */ + err(EXIT_FAILURE, _("unexpected tcgetattr failure")); + return 0; + } + tattr = *backup; + cfmakeraw(&tattr); + tattr.c_lflag |= ISIG; + tcsetattr(STDOUT_FILENO, TCSANOW, &tattr); + return 1; +} + +int +main(int argc, char *argv[]) +{ + static const struct timeval mindelay = { .tv_sec = 0, .tv_usec = 100 }; + struct timeval maxdelay; + + int isterm; + struct termios saved; + + 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); + + isterm = setterm(&saved); + + 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 (isterm) + tcsetattr(STDOUT_FILENO, TCSADRAIN, &saved); + + 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..5daa484 --- /dev/null +++ b/term-utils/setterm.1 @@ -0,0 +1,274 @@ +'\" t +.\" Title: setterm +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.15 +.\" Date: 2022-05-11 +.\" Manual: User Commands +.\" Source: util-linux 2.38.1 +.\" Language: English +.\" +.TH "SETTERM" "1" "2022-05-11" "util\-linux 2.38.1" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +setterm \- set terminal attributes +.SH "SYNOPSIS" +.sp +\fBsetterm\fP [options] +.SH "DESCRIPTION" +.sp +\fBsetterm\fP writes to standard output a character string that will invoke the specified terminal capabilities. Where possible \fIterminfo\fP is consulted to find the string to use. Some options however (marked "virtual consoles only" below) do not correspond to a \fBterminfo\fP(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" +.sp +For boolean options (\fBon\fP or \fBoff\fP), the default is \fBon\fP. +.sp +Below, an \fI8\-color\fP can be \fBblack\fP, \fBred\fP, \fBgreen\fP, \fByellow\fP, \fBblue\fP, \fBmagenta\fP, \fBcyan\fP, or \fBwhite\fP. +.sp +A \fI16\-color\fP can be an \fI8\-color\fP, or \fBgrey\fP, or \fBbright\fP followed by \fBred\fP, \fBgreen\fP, \fByellow\fP, \fBblue\fP, \fBmagenta\fP, \fBcyan\fP, or \fBwhite\fP. +.sp +The various color options may be set independently, at least on virtual consoles, though the results of setting multiple modes (for example, \fB\-\-underline\fP and \fB\-\-half\-bright\fP) are hardware\-dependent. +.sp +The optional arguments are recommended with \(aq=\(aq (equals sign) and not space between the option and the argument. For example \-\-option=argument. \fBsetterm\fP can interpret the next non\-option argument as an optional argument too. +.sp +\fB\-\-appcursorkeys\fP on|off +.RS 4 +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 \fIvi and Cursor\-Keys\fP section of the \fIText\-Terminal\-HOWTO\fP for how this can cause problems for \fBvi\fP users. Virtual consoles only. +.RE +.sp +\fB\-\-append\fP \fIconsole_number\fP +.RS 4 +Like \fB\-\-dump\fP, but appends to the snapshot file instead of overwriting it. Only works if no \fB\-\-dump\fP options are given. +.RE +.sp +\fB\-\-background\fP \fI8\-color\fP|default +.RS 4 +Sets the background text color. +.RE +.sp +\fB\-\-blank\fP[=0\-60|force|poke] +.RS 4 +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. +.sp +The \fBforce\fP argument keeps the screen blank even if a key is pressed. +.sp +The \fBpoke\fP argument unblanks the screen. +.RE +.sp +\fB\-\-bfreq\fP[=\fInumber\fP] +.RS 4 +Sets the bell frequency in Hertz. Without an argument, it defaults to \fB0\fP. Virtual consoles only. +.RE +.sp +\fB\-\-blength\fP[=0\-2000] +.RS 4 +Sets the bell duration in milliseconds. Without an argument, it defaults to \fB0\fP. Virtual consoles only. +.RE +.sp +\fB\-\-blink\fP on|off +.RS 4 +Turns blink mode on or off. Except on a virtual console, \fB\-\-blink off\fP turns off all attributes (bold, half\-brightness, blink, reverse). +.RE +.sp +\fB\-\-bold\fP on|off +.RS 4 +urns bold (extra bright) mode on or off. Except on a virtual console, \fB\-\-bold off\fP turns off all attributes (bold, half\-brightness, blink, reverse). +.RE +.sp +\fB\-\-clear\fP[=all|rest] +.RS 4 +Without an argument or with the argument \fBall\fP, the entire screen is cleared and the cursor is set to the home position, just like \fBclear\fP(1) does. With the argument \fBrest\fP, the screen is cleared from the current cursor position to the end. +.RE +.sp +\fB\-\-clrtabs\fP[=\fItab1 tab2 tab3\fP ...] +.RS 4 +Clears tab stops from the given horizontal cursor positions, in the range \fB1\-160\fP. Without arguments, it clears all tab stops. Virtual consoles only. +.RE +.sp +\fB\-\-cursor\fP on|off +.RS 4 +Turns the terminal\(cqs cursor on or off. +.RE +.sp +\fB\-\-default\fP +.RS 4 +Sets the terminal\(cqs rendering options to the default values. +.RE +.sp +\fB\-\-dump\fP[=\fIconsole_number\fP] +.RS 4 +Writes a snapshot of the virtual console with the given number to the file specified with the \fB\-\-file\fP option, overwriting its contents; the default is \fIscreen.dump\fP. Without an argument, it dumps the current virtual console. This overrides \fB\-\-append\fP. +.RE +.sp +\fB\-\-file\fP \fIfilename\fP +.RS 4 +Sets the snapshot file name for any \fB\-\-dump\fP or \fB\-\-append\fP options on the same command line. If this option is not present, the default is \fIscreen.dump\fP in the current directory. A path name that exceeds the system maximum will be truncated, see \fBPATH_MAX\fP from \fIlinux/limits.h\fP for the value. +.RE +.sp +\fB\-\-foreground\fP \fI8\-color\fP|default +.RS 4 +Sets the foreground text color. +.RE +.sp +\fB\-\-half\-bright\fP on|off +.RS 4 +Turns dim (half\-brightness) mode on or off. Except on a virtual console, \fB\-\-half\-bright off\fP turns off all attributes (bold, half\-brightness, blink, reverse). +.RE +.sp +\fB\-\-hbcolor\fP [bright] \fI16\-color\fP +.RS 4 +Sets the color for half\-bright characters. +.RE +.sp +\fB\-\-initialize\fP +.RS 4 +Displays the terminal initialization string, which typically sets the terminal\(cqs rendering options, and other attributes to the default values. +.RE +.sp +\fB\-\-inversescreen\fP on|off +.RS 4 +Swaps foreground and background colors for the whole screen. +.RE +.sp +\fB\-\-linewrap\fP on|off +.RS 4 +Makes the terminal continue on a new line when a line is full. +.RE +.sp +\fB\-\-msg\fP on|off +.RS 4 +Enables or disables the sending of kernel \fBprintk\fP() messages to the console. Virtual consoles only. +.RE +.sp +\fB\-\-msglevel\fP 0\-8 +.RS 4 +Sets the console logging level for kernel \fBprintk()\fP messages. All messages strictly more important than this will be printed, so a logging level of \fB0\fP has the same effect as \fB\-\-msg on\fP and a logging level of \fB8\fP will print all kernel messages. \fBklogd\fP(8) may be a more convenient interface to the logging of kernel messages. +.sp +Virtual consoles only. +.RE +.sp +\fB\-\-powerdown\fP[=0\-60] +.RS 4 +Sets the VESA powerdown interval in minutes. Without an argument, it defaults to \fB0\fP (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. +.RE +.sp +\fB\-\-powersave\fP \fImode\fP +.RS 4 +Valid values for \fImode\fP are: +.sp +\fBvsync|on\fP +.RS 4 +Puts the monitor into VESA vsync suspend mode. +.RE +.sp +\fBhsync\fP +.RS 4 +Puts the monitor into VESA hsync suspend mode. +.RE +.sp +\fBpowerdown\fP +.RS 4 +Puts the monitor into VESA powerdown mode. +.RE +.sp +\fBoff\fP +.RS 4 +Turns monitor VESA powersaving features. +.RE +.RE +.sp +\fB\-\-regtabs\fP[=1\-160] +.RS 4 +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 \fB8\fP. Virtual consoles only. +.RE +.sp +\fB\-\-repeat\fP on|off +.RS 4 +Turns keyboard repeat on or off. Virtual consoles only. +.RE +.sp +\fB\-\-reset\fP +.RS 4 +Displays the terminal reset string, which typically resets the terminal to its power\-on state. +.RE +.sp +\fB\-\-resize\fP +.RS 4 +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 \fBioctl\fP(3p) but just byte streams and breaks. +.RE +.sp +\fB\-\-reverse\fP on|off +.RS 4 +Turns reverse video mode on or off. Except on a virtual console, \fB\-\-reverse off\fP turns off all attributes (bold, half\-brightness, blink, reverse). +.RE +.sp +\fB\-\-store\fP +.RS 4 +Stores the terminal\(cqs current rendering options (foreground and background colors) as the values to be used at reset\-to\-default. Virtual consoles only. +.RE +.sp +\fB\-\-tabs\fP[=\fItab1 tab2 tab3\fP ...] +.RS 4 +Sets tab stops at the given horizontal cursor positions, in the range \fB1\-160\fP. Without arguments, it shows the current tab stop settings. +.RE +.sp +\fB\-\-term\fP \fIterminal_name\fP +.RS 4 +Overrides the \fBTERM\fP environment variable. +.RE +.sp +\fB\-\-ulcolor\fP [bright] \fI16\-color\fP +.RS 4 +Sets the color for underlined characters. Virtual consoles only. +.RE +.sp +\fB\-\-underline\fP on|off +.RS 4 +Turns underline mode on or off. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "COMPATIBILITY" +.sp +Since version 2.25 \fBsetterm\fP has support for long options with two hyphens, for example \fB\-\-help\fP, beside the historical long options with a single hyphen, for example \fB\-help\fP. 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" +.sp +Differences between the Minix and Linux versions are not documented. +.SH "SEE ALSO" +.sp +\fBstty\fP(1), +\fBtput\fP(1), +\fBtty\fP(4), +\fBterminfo\fP(5) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBsetterm\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/term-utils/setterm.1.adoc b/term-utils/setterm.1.adoc new file mode 100644 index 0000000..68b8554 --- /dev/null +++ b/term-utils/setterm.1.adoc @@ -0,0 +1,180 @@ +//po4a: entry man manual +//// +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 +//// += setterm(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: setterm + +== NAME + +setterm - set terminal attributes + +== SYNOPSIS + +*setterm* [options] + +== DESCRIPTION + +*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 (marked "virtual consoles only" below) do not correspond to a *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. + +== OPTIONS + +For boolean options (*on* or *off*), the default is *on*. + +Below, an _8-color_ can be *black*, *red*, *green*, *yellow*, *blue*, *magenta*, *cyan*, or *white*. + +A _16-color_ can be an _8-color_, or *grey*, or *bright* followed by *red*, *green*, *yellow*, *blue*, *magenta*, *cyan*, or *white*. + +The various color options may be set independently, at least on virtual consoles, though the results of setting multiple modes (for example, *--underline* and *--half-bright*) are hardware-dependent. + +The optional arguments are recommended with '=' (equals sign) and not space between the option and the argument. For example --option=argument. *setterm* can interpret the next non-option argument as an optional argument too. + +*--appcursorkeys* 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 _vi and Cursor-Keys_ section of the _Text-Terminal-HOWTO_ for how this can cause problems for *vi* users. Virtual consoles only. + +*--append* _console_number_:: +Like *--dump*, but appends to the snapshot file instead of overwriting it. Only works if no *--dump* options are given. + +*--background* __8-color__|default:: +Sets the background text color. + +*--blank*[=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. ++ +The *force* argument keeps the screen blank even if a key is pressed. ++ +The *poke* argument unblanks the screen. + +*--bfreq*[=_number_]:: +Sets the bell frequency in Hertz. Without an argument, it defaults to *0*. Virtual consoles only. + +*--blength*[=0-2000]:: +Sets the bell duration in milliseconds. Without an argument, it defaults to *0*. Virtual consoles only. + +*--blink* on|off:: +Turns blink mode on or off. Except on a virtual console, *--blink off* turns off all attributes (bold, half-brightness, blink, reverse). + +*--bold* on|off:: +urns bold (extra bright) mode on or off. Except on a virtual console, *--bold off* turns off all attributes (bold, half-brightness, blink, reverse). + +*--clear*[=all|rest]:: +Without an argument or with the argument *all*, the entire screen is cleared and the cursor is set to the home position, just like *clear*(1) does. With the argument *rest*, the screen is cleared from the current cursor position to the end. + +*--clrtabs*[=_tab1 tab2 tab3_ ...]:: +Clears tab stops from the given horizontal cursor positions, in the range *1-160*. Without arguments, it clears all tab stops. Virtual consoles only. + +*--cursor* on|off:: +Turns the terminal's cursor on or off. + +*--default*:: +Sets the terminal's rendering options to the default values. + +*--dump*[=_console_number_]:: +Writes a snapshot of the virtual console with the given number to the file specified with the *--file* option, overwriting its contents; the default is _screen.dump_. Without an argument, it dumps the current virtual console. This overrides *--append*. + +*--file* _filename_:: +Sets the snapshot file name for any *--dump* or *--append* options on the same command line. If this option is not present, the default is _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. + +*--foreground* __8-color__|default:: +Sets the foreground text color. + +*--half-bright* on|off:: +Turns dim (half-brightness) mode on or off. Except on a virtual console, *--half-bright off* turns off all attributes (bold, half-brightness, blink, reverse). + +*--hbcolor* [bright] _16-color_:: +Sets the color for half-bright characters. + +*--initialize*:: +Displays the terminal initialization string, which typically sets the terminal's rendering options, and other attributes to the default values. + +*--inversescreen* on|off:: +Swaps foreground and background colors for the whole screen. + +*--linewrap* on|off:: +Makes the terminal continue on a new line when a line is full. + +*--msg* on|off:: +Enables or disables the sending of kernel *printk*() messages to the console. Virtual consoles only. + +*--msglevel* 0-8:: +Sets the console logging level for kernel *printk()* messages. All messages strictly more important than this will be printed, so a logging level of *0* has the same effect as *--msg on* and a logging level of *8* will print all kernel messages. *klogd*(8) may be a more convenient interface to the logging of kernel messages. ++ +Virtual consoles only. + +*--powerdown*[=0-60]:: +Sets the VESA powerdown interval in minutes. Without an argument, it defaults to *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. + +*--powersave* _mode_:: +Valid values for _mode_ are: + +*vsync|on*;; +Puts the monitor into VESA vsync suspend mode. +*hsync*;; +Puts the monitor into VESA hsync suspend mode. +*powerdown*;; +Puts the monitor into VESA powerdown mode. +*off*;; +Turns monitor VESA powersaving features. + +*--regtabs*[=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 *8*. Virtual consoles only. + +*--repeat* on|off:: +Turns keyboard repeat on or off. Virtual consoles only. + +*--reset*:: +Displays the terminal reset string, which typically resets the terminal to its power-on state. + +*--resize*:: +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 *ioctl*(3p) but just byte streams and breaks. + +*--reverse* on|off:: +Turns reverse video mode on or off. Except on a virtual console, *--reverse off* turns off all attributes (bold, half-brightness, blink, reverse). + +*--store*:: +Stores the terminal's current rendering options (foreground and background colors) as the values to be used at reset-to-default. Virtual consoles only. + +*--tabs*[=_tab1 tab2 tab3_ ...]:: +Sets tab stops at the given horizontal cursor positions, in the range *1-160*. Without arguments, it shows the current tab stop settings. + +*--term* _terminal_name_:: +Overrides the *TERM* environment variable. + +*--ulcolor* [bright] _16-color_:: +Sets the color for underlined characters. Virtual consoles only. + +*--underline* on|off:: +Turns underline mode on or off. + +include::man-common/help-version.adoc[] + +== COMPATIBILITY + +Since version 2.25 *setterm* has support for long options with two hyphens, for example *--help*, beside the historical long options with a single hyphen, for example *-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. + +== BUGS + +Differences between the Minix and Linux versions are not documented. + +== SEE ALSO + +*stty*(1), +*tput*(1), +*tty*(4), +*terminfo*(5) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] 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..14d6ca6 --- /dev/null +++ b/term-utils/ttymsg.c @@ -0,0 +1,192 @@ +/* + * 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 they 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; + } + + close(fd); + + 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..5cffe93 --- /dev/null +++ b/term-utils/wall.1 @@ -0,0 +1,87 @@ +'\" t +.\" Title: wall +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.15 +.\" Date: 2022-05-11 +.\" Manual: User Commands +.\" Source: util-linux 2.38.1 +.\" Language: English +.\" +.TH "WALL" "1" "2022-05-11" "util\-linux 2.38.1" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +wall \- write a message to all users +.SH "SYNOPSIS" +.sp +\fBwall\fP [\fB\-n\fP] [\fB\-t\fP \fItimeout\fP] [\fB\-g\fP \fIgroup\fP] [\fImessage\fP | \fIfile\fP] +.SH "DESCRIPTION" +.sp +\fBwall\fP displays a \fImessage\fP, or the contents of a \fIfile\fP, 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. +.sp +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. +.sp +Reading from a \fIfile\fP is refused when the invoker is not superuser and the program is set\-user\-ID or set\-group\-ID. +.SH "OPTIONS" +.sp +\fB\-n\fP, \fB\-\-nobanner\fP +.RS 4 +Suppress the banner. +.RE +.sp +\fB\-t\fP, \fB\-\-timeout\fP \fItimeout\fP +.RS 4 +Abandon the write attempt to the terminals after \fItimeout\fP seconds. This \fItimeout\fP 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. +.RE +.sp +\fB\-g\fP, \fB\-\-group\fP \fIgroup\fP +.RS 4 +Limit printing message to members of group defined as a \fIgroup\fP argument. The argument can be group name or GID. +.RE +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "NOTES" +.sp +Some sessions, such as \fBwdm\fP(1x), that have in the beginning of \fButmp\fP(5) ut_type data a \(aq:\(aq character will not get the message from \fBwall\fP. This is done to avoid write errors. +.SH "HISTORY" +.sp +A \fBwall\fP command appeared in Version 7 AT&T UNIX. +.SH "SEE ALSO" +.sp +\fBmesg\fP(1), +\fBtalk\fP(1), +\fBwrite\fP(1), +\fBshutdown\fP(8) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBwall\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/term-utils/wall.1.adoc b/term-utils/wall.1.adoc new file mode 100644 index 0000000..441871d --- /dev/null +++ b/term-utils/wall.1.adoc @@ -0,0 +1,94 @@ +//po4a: entry man manual +//// +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 + +//// += wall(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: wall + +== NAME + +wall - write a message to all users + +== SYNOPSIS + +*wall* [*-n*] [*-t* _timeout_] [*-g* _group_] [_message_ | _file_] + +== DESCRIPTION + +*wall* displays a _message_, or the contents of a _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. + +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. + +Reading from a _file_ is refused when the invoker is not superuser and the program is set-user-ID or set-group-ID. + +== OPTIONS + +*-n*, *--nobanner*:: +Suppress the banner. + +*-t*, *--timeout* _timeout_:: +Abandon the write attempt to the terminals after _timeout_ seconds. This _timeout_ 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. + +*-g*, *--group* _group_:: +Limit printing message to members of group defined as a _group_ argument. The argument can be group name or GID. + +include::man-common/help-version.adoc[] + +== NOTES + +Some sessions, such as *wdm*(1x), that have in the beginning of *utmp*(5) ut_type data a ':' character will not get the message from *wall*. This is done to avoid write errors. + +== HISTORY + +A *wall* command appeared in Version 7 AT&T UNIX. + +== SEE ALSO + +*mesg*(1), +*talk*(1), +*write*(1), +*shutdown*(8) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/term-utils/wall.c b/term-utils/wall.c new file mode 100644 index 0000000..c601d3e --- /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" +#include "pwdutils.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 __attribute__((__format__ (__printf__, 2, 3))) + 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]; + time_t now; + + whom = xgetlogin(); + 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..edb288b --- /dev/null +++ b/term-utils/write.1 @@ -0,0 +1,83 @@ +'\" t +.\" Title: write +.\" Author: [see the "AUTHOR(S)" section] +.\" Generator: Asciidoctor 2.0.15 +.\" Date: 2022-05-11 +.\" Manual: User Commands +.\" Source: util-linux 2.38.1 +.\" Language: English +.\" +.TH "WRITE" "1" "2022-05-11" "util\-linux 2.38.1" "User Commands" +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.ss \n[.ss] 0 +.nh +.ad l +.de URL +\fI\\$2\fP <\\$1>\\$3 +.. +.als MTO URL +.if \n[.g] \{\ +. mso www.tmac +. am URL +. ad l +. . +. am MTO +. ad l +. . +. LINKSTYLE blue R < > +.\} +.SH "NAME" +write \- send a message to another user +.sp +\fBwrite\fP \fIuser\fP [\fIttyname\fP] +.SH "DESCRIPTION" +.sp +\fBwrite\fP allows you to communicate with other users, by copying lines from your terminal to theirs. +.sp +When you run the \fBwrite\fP command, the user you are writing to gets a message of the form: +.sp +.if n .RS 4 +.nf +.fam C +Message from yourname@yourhost on yourtty at hh:mm ... +.fam +.fi +.if n .RE +.sp +Any further lines you enter will be copied to the specified user\(cqs terminal. If the other user wants to reply, they must run \fBwrite\fP as well. +.sp +When you are done, type an end\-of\-file or interrupt character. The other user will see the message \fBEOF\fP indicating that the conversation is over. +.sp +You can prevent people (other than the superuser) from writing to you with the \fBmesg\fP(1) command. Some commands, for example \fBnroff\fP(1) and \fBpr\fP(1), may automatically disallow writing, so that the output they produce isn\(cqt overwritten. +.sp +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 \fBwrite\fP command. Alternatively, you can let \fBwrite\fP 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. +.sp +The traditional protocol for writing to someone is that the string \fI\-o\fP, either at the end of a line or on a line by itself, means that it\(cqs the other person\(cqs turn to talk. The string \fIoo\fP means that the person believes the conversation to be over. +.SH "OPTIONS" +.sp +\fB\-h\fP, \fB\-\-help\fP +.RS 4 +Display help text and exit. +.RE +.sp +\fB\-V\fP, \fB\-\-version\fP +.RS 4 +Print version and exit. +.RE +.SH "HISTORY" +.sp +A \fBwrite\fP command appeared in Version 6 AT&T UNIX. +.SH "SEE ALSO" +.sp +\fBmesg\fP(1), +\fBtalk\fP(1), +\fBwho\fP(1) +.SH "REPORTING BUGS" +.sp +For bug reports, use the issue tracker at \c +.URL "https://github.com/util\-linux/util\-linux/issues" "" "." +.SH "AVAILABILITY" +.sp +The \fBwrite\fP command is part of the util\-linux package which can be downloaded from \c +.URL "https://www.kernel.org/pub/linux/utils/util\-linux/" "Linux Kernel Archive" "."
\ No newline at end of file diff --git a/term-utils/write.1.adoc b/term-utils/write.1.adoc new file mode 100644 index 0000000..d18977f --- /dev/null +++ b/term-utils/write.1.adoc @@ -0,0 +1,92 @@ +//po4a: entry man manual +//// +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 +//// += write(1) +:doctype: manpage +:man manual: User Commands +:man source: util-linux {release-version} +:page-layout: base +:command: write + +== NAME + +write - send a message to another user + +*write* _user_ [_ttyname_] + +== DESCRIPTION + +*write* allows you to communicate with other users, by copying lines from your terminal to theirs. + +When you run the *write* command, the user you are writing to gets a message of the form: + +.... +Message from yourname@yourhost on yourtty at hh:mm ... +.... + +Any further lines you enter will be copied to the specified user's terminal. If the other user wants to reply, they must run *write* as well. + +When you are done, type an end-of-file or interrupt character. The other user will see the message *EOF* indicating that the conversation is over. + +You can prevent people (other than the superuser) from writing to you with the *mesg*(1) command. Some commands, for example *nroff*(1) and *pr*(1), may automatically disallow writing, so that the output they produce isn't overwritten. + +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 *write* command. Alternatively, you can let *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. + +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. + +== OPTIONS + +include::man-common/help-version.adoc[] + +== HISTORY + +A *write* command appeared in Version 6 AT&T UNIX. + +== SEE ALSO + +*mesg*(1), +*talk*(1), +*who*(1) + +include::man-common/bugreports.adoc[] + +include::man-common/footer.adoc[] + +ifdef::translation[] +include::man-common/translation.adoc[] +endif::[] diff --git a/term-utils/write.c b/term-utils/write.c new file mode 100644 index 0000000..ee31580 --- /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++; + snprintf(path, sizeof(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; +} |