summaryrefslogtreecommitdiff
path: root/term-utils
diff options
context:
space:
mode:
Diffstat (limited to 'term-utils')
-rw-r--r--term-utils/Makemodule.am94
-rw-r--r--term-utils/agetty.8371
-rw-r--r--term-utils/agetty.c1914
-rw-r--r--term-utils/mesg.1112
-rw-r--r--term-utils/mesg.c158
-rwxr-xr-xterm-utils/reset13
-rwxr-xr-xterm-utils/reset.033c11
-rw-r--r--term-utils/reset.145
-rw-r--r--term-utils/script.1156
-rw-r--r--term-utils/script.c568
-rw-r--r--term-utils/scriptreplay.1107
-rw-r--r--term-utils/scriptreplay.c234
-rw-r--r--term-utils/setterm.1196
-rw-r--r--term-utils/setterm.c1290
-rw-r--r--term-utils/ttymsg.c191
-rw-r--r--term-utils/ttymsg.h2
-rw-r--r--term-utils/wall.186
-rw-r--r--term-utils/wall.c280
-rw-r--r--term-utils/write.1102
-rw-r--r--term-utils/write.c381
20 files changed, 6311 insertions, 0 deletions
diff --git a/term-utils/Makemodule.am b/term-utils/Makemodule.am
new file mode 100644
index 0000000..f4fa92d
--- /dev/null
+++ b/term-utils/Makemodule.am
@@ -0,0 +1,94 @@
+
+usrbin_exec_PROGRAMS += script
+dist_man_MANS += term-utils/script.1
+script_SOURCES = term-utils/script.c
+script_LDADD = $(LDADD)
+if HAVE_UTIL
+script_LDADD += -lutil
+endif
+if HAVE_UTEMPTER
+script_LDADD += -lutempter
+endif
+
+
+usrbin_exec_PROGRAMS += scriptreplay
+dist_man_MANS += term-utils/scriptreplay.1
+scriptreplay_SOURCES = term-utils/scriptreplay.c
+
+
+if BUILD_AGETTY
+sbin_PROGRAMS += agetty
+dist_man_MANS += term-utils/agetty.8
+agetty_SOURCES = term-utils/agetty.c
+agetty_LDADD = $(LDADD) libcommon.la
+endif # BUILD_AGETTY
+
+
+# TODO: add BUILD_SETTERM to configure.am
+if HAVE_NCURSES
+if LINUX
+usrbin_exec_PROGRAMS += setterm
+dist_man_MANS += term-utils/setterm.1
+setterm_SOURCES = term-utils/setterm.c
+endif
+if HAVE_TINFO
+setterm_LDADD = $(LDADD) -ltinfo
+else
+setterm_LDADD = $(LDADD) @NCURSES_LIBS@
+endif
+endif
+
+
+if BUILD_RESET
+dist_usrbin_exec_SCRIPTS += term-utils/reset
+dist_man_MANS += term-utils/reset.1
+endif
+EXTRA_DIST += term-utils/reset.033c
+
+
+if BUILD_MESG
+usrbin_exec_PROGRAMS += mesg
+dist_man_MANS += term-utils/mesg.1
+mesg_SOURCES = term-utils/mesg.c
+endif
+
+
+if BUILD_WALL
+usrbin_exec_PROGRAMS += wall
+wall_SOURCES = \
+ term-utils/wall.c \
+ term-utils/ttymsg.c \
+ term-utils/ttymsg.h
+dist_man_MANS += term-utils/wall.1
+wall_CFLAGS = $(SUID_CFLAGS) $(AM_CFLAGS)
+wall_LDFLAGS = $(SUID_LDFLAGS) $(AM_LDFLAGS)
+wall_LDADD = $(LDADD) libcommon.la
+if USE_TTY_GROUP
+if MAKEINSTALL_DO_CHOWN
+install-exec-hook-wall::
+ chgrp tty $(DESTDIR)$(usrbin_execdir)/wall
+ chmod g+s $(DESTDIR)$(usrbin_execdir)/wall
+
+INSTALL_EXEC_HOOKS += install-exec-hook-wall
+endif
+endif
+endif # BUILD_WALL
+
+
+if BUILD_WRITE
+usrbin_exec_PROGRAMS += write
+dist_man_MANS += term-utils/write.1
+write_SOURCES = term-utils/write.c
+write_CFLAGS = $(SUID_CFLAGS) $(AM_CFLAGS)
+write_LDFLAGS = $(SUID_LDFLAGS) $(AM_LDFLAGS)
+
+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..ebfdb96
--- /dev/null
+++ b/term-utils/agetty.8
@@ -0,0 +1,371 @@
+.TH AGETTY 8 "May 2011" "util-linux" "System Administration"
+.SH NAME
+agetty \- alternative Linux getty
+
+.SH SYNOPSIS
+.BR "agetty " [\-8chiLmnsUw]
+.RI "[\-a " user ]
+.RI "[\-f " issue_file ]
+.RI "[\-H " login_host ]
+.RI "[\-I " init ]
+.RI "[\-l " login_program ]
+.RI "[\-t " timeout ]
+.I port
+.I baud_rate,...
+.RI [ term ]
+
+.SH DESCRIPTION
+.ad
+.fi
+\fBagetty\fP opens a tty port, prompts for a login name and invokes
+the /bin/login command. It is normally invoked by \fIinit(8)\fP.
+
+\fBagetty\fP has several \fInon-standard\fP features that are useful
+for hard-wired and for dial-in lines:
+.IP o
+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: @ and Control-U (kill); #, DEL and
+back space (erase); carriage return and line feed (end of line).
+.IP o
+Optionally deduces the baud rate from the CONNECT messages produced by
+Hayes(tm)-compatible modems.
+.IP o
+Optionally does not hang up when it is given an already opened line
+(useful for call-back applications).
+.IP o
+Optionally does not display the contents of the \fI/etc/issue\fP file.
+.IP o
+Optionally displays an alternative issue file instead of \fI/etc/issue\fP.
+.IP o
+Optionally does not ask for a login name.
+.IP o
+Optionally invokes a non-standard login program instead of
+\fI/bin/login\fP.
+.IP o
+Optionally turns on hard-ware flow control
+.IP o
+Optionally forces the line to be local with no need for carrier detect.
+.PP
+This program does not use the \fI/etc/gettydefs\fP (System V) or
+\fI/etc/gettytab\fP (SunOS 4) files.
+.SH ARGUMENTS
+.na
+.nf
+.fi
+.ad
+.TP
+port
+A path name relative to the \fI/dev\fP directory. If a "\-" is
+specified, \fBagetty\fP assumes that its standard input is
+already connected to a tty port and that a connection to a
+remote user has already been established.
+.sp
+Under System V, a "\-" \fIport\fP argument should be preceded
+by a "\-\-".
+.TP
+baud_rate,...
+A comma-separated list of one or more baud rates. Each time
+\fBagetty\fP receives a BREAK character it advances through
+the list, which is treated as if it were circular.
+.sp
+Baud rates should be specified in descending order, so that the
+null character (Ctrl\-@) can also be used for baud rate switching.
+.TP
+term
+The value to be used for the TERM environment variable. This overrides
+whatever init(8) may have set, and is inherited by login and the shell.
+.SH OPTIONS
+.na
+.nf
+.fi
+.ad
+.TP
+\-8, \-\-8bits
+Assume that the tty is 8-bit clean, hence disable parity detection.
+.TP
+\-a, \-\-autologin \fIusername\fP
+Log the specified user automatically in without asking for a login name and
+password. The \-f \fIusername\fP option is added to the \fB/bin/login\fP
+command line by default. The \-\-login-options option changes this default
+behaviour and then only \\u is replaced by the \fIusername\fP and no other
+option is added to the login command line.
+.TP
+\-c, \-\-noreset
+Don't reset terminal cflags (control modes). See \fItermios(3)\fP for more
+details.
+.TP
+\-E, \-\-remote
+If \-H \fIfakehost\fP option is given then \-r \fIfakehost\fP options is
+added to the the \fB/bin/login\fP command line.
+.TP
+\-f, \-\-issue\-file \fIissue_file\fP
+Display the contents of \fIissue_file\fP instead of \fI/etc/issue\fP.
+This allows custom messages to be displayed on different terminals.
+The \-i option will override this option.
+.TP
+\-h, \-\-flow\-control
+Enable hardware (RTS/CTS) flow control. It is left up to the
+application to disable software (XON/XOFF) flow protocol where
+appropriate.
+.TP
+\-H, \-\-host \fIlogin_host\fP
+Write the specified \fIlogin_host\fP into the utmp file. (Normally,
+no login host is given, since \fBagetty\fP is used for local hardwired
+connections and consoles. However, this option can be useful for
+identifying terminal concentrators and the like.
+.TP
+\-i, \-\-noissue
+Do not display the contents of \fI/etc/issue\fP (or other) before writing the
+login prompt. Terminals or communications hardware may become confused
+when receiving lots of text at the wrong baud rate; dial-up scripts
+may fail if the login prompt is preceded by too much text.
+.TP
+\-I, \-\-init\-string \fIinitstring\fP
+Set an initial string to be sent to the tty or modem before sending
+anything else. This may be used to initialize a modem. Non printable
+characters may be sent by writing their octal code preceded by a
+backslash (\\). For example to send a linefeed character (ASCII 10,
+octal 012) write \\012.
+.PP
+.TP
+\-l, \-\-login\-program \fIlogin_program\fP
+Invoke the specified \fIlogin_program\fP instead of /bin/login.
+This allows the use of a non-standard login program (for example,
+one that asks for a dial-up password or that uses a different
+password file).
+.TP
+\-L, \-\-local\-line
+Force the line to be a local line with no need for carrier detect. This can
+be useful when you have a locally attached terminal where the serial line
+does not set the carrier detect signal.
+.TP
+\-m, \-\-extract\-baud
+Try to extract the baud rate 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 \fI\-m\fP feature may fail on heavily-loaded systems,
+you still should enable BREAK processing by enumerating all
+expected baud rates on the command line.
+.TP
+\-n, \-\-skip\-login
+Do not prompt the user for a login name. This can be used in
+connection with \-l option to invoke a non-standard login process such
+as a BBS system. Note that with the \-n option, \fBagetty\fR gets no input from
+user who logs in and therefore won't be able to figure out parity,
+character size, and newline processing of the connection. It defaults to
+space parity, 7 bit characters, and ASCII CR (13) end-of-line character.
+Beware that the program that \fBagetty\fR starts (usually /bin/login)
+is run as root.
+.TP
+\-o, \-\-login\-options \fI"login_options"\fP
+Options that are passed to the login program. \\u is replaced
+by the login name. The default \fB/bin/login\fP command line
+is "/bin/login -- <username>".
+
+Please read the SECURITY NOTICE below if you want to use this.
+.TP
+\-p, \-\-login\-pause
+Wait for any key before dropping to the login prompt. Can be combined
+with \fB\-\-autologin\fP to save memory by lazily spawning shells.
+.TP
+\-R, \-\-hangup
+Do call vhangup() for a virtually hangup of the specified terminal.
+.TP
+\-s, \-\-keep\-baud
+Try to keep the existing baud rate. The baud rates from
+the command line are used when agetty receives a BREAK character.
+.TP
+\-t, \-\-timeout \fItimeout\fP
+Terminate if no user name could be read within \fItimeout\fP
+seconds. This option should probably not be used with hard-wired
+lines.
+.TP
+\-U, \-\-detect\-case
+Turn on support for detecting an uppercase only terminal. This setting will
+detect a login name containing only capitals as indicating an uppercase
+only terminal and turn on some upper to lower case conversions. Note that
+this has no support for any unicode characters.
+.TP
+\-w, \-\-wait\-cr
+Wait for the user or the modem to send a carriage-return or a
+linefeed character before sending the \fI/etc/issue\fP (or other) file
+and the login prompt. Very useful in connection with the \-I option.
+.TP
+\-\-noclear
+Do not clear the screen before prompting for the login name
+(the screen is normally cleared).
+.TP
+\-\-nohints
+Do not print hints about Num, Caps and Scroll Locks.
+.TP
+\-\-nonewline
+Do not print a newline before writing out /etc/issue.
+.TP
+\-\-nohostname
+By default the hostname will be printed. With this option enabled,
+no hostname at all will be shown.
+.TP
+\-\-long\-hostname
+By default the hostname is only printed until the first dot. With
+this option enabled, the full qualified hostname by gethostname()
+or if not found by gethostbyname() is shown.
+.TP
+\-\-version
+Output version information and exit.
+.TP
+\-\-help
+Output help screen and exit.
+.PP
+.SH EXAMPLES
+This section shows examples for the process field of an entry in the
+\fI/etc/inittab\fP file. You'll have to prepend appropriate values
+for the other fields. See \fIinittab(5)\fP for more details.
+
+For a hard-wired line or a console tty:
+.ti +5
+/sbin/agetty 9600 ttyS1
+
+For a directly connected terminal without proper carriage detect wiring:
+(try this if your terminal just sleeps instead of giving you a password:
+prompt.)
+.ti +5
+/sbin/agetty \-L 9600 ttyS1 vt100
+
+For a old style dial-in line with a 9600/2400/1200 baud modem:
+.ti +5
+/sbin/agetty \-mt60 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
+dis-connection and turn on auto-answer after 1 ring.)
+.ti +5
+/sbin/agetty \-w \-I 'ATE0Q1&D2&C1S0=1\\015' 115200 ttyS1
+
+.SH SECURITY NOTICE
+If you use the \fB\-\-login\-program\fP and \fB\-\-login\-options\fP options,
+be aware that a malicious user may try to enter lognames with embedded options,
+which then get passed to the used login program. Agetty does check
+for a leading "\-" and makes sure the logname gets passed as one parameter
+(so embedded spaces will not create yet another parameter), but depending
+on how the login binary parses the command line that might not be sufficient.
+Check that the used login program can not be abused this way.
+.PP
+Some programs use "\-\-" to indicate that the rest of the commandline should
+not be interpreted as options. Use this feature if available by passing "\-\-"
+before the username gets passed by \\u.
+
+.SH ISSUE ESCAPES
+The issue-file (\fI/etc/issue\fP or the file set with the \-f option)
+may contain certain escape codes to display the system name, date and
+time etc. All escape codes consist of a backslash (\\) immediately
+followed by one of the letters explained below.
+
+.TP
+b
+Insert the baudrate of the current line.
+.TP
+d
+Insert the current date.
+.TP
+s
+Insert the system name, the name of the operating system. Same as `uname \-s'.
+.TP
+l
+Insert the name of the current tty line.
+.TP
+m
+Insert the architecture identifier of the machine. Same as `uname \-m'.
+.TP
+n
+Insert the nodename of the machine, also known as the hostname. Same as `uname \-n'.
+.TP
+o
+Insert the NIS domainname of the machine. Same as `hostname \-d'.
+.TP
+O
+Insert the DNS domainname of the machine.
+.TP
+r
+Insert the release number of the OS. Same as `uname \-r'.
+.TP
+t
+Insert the current time.
+.TP
+u
+Insert the number of current users logged in.
+.TP
+U
+Insert the string "1 user" or "<n> users" where <n> is the number of current
+users logged in.
+.TP
+v
+Insert the version of the OS, eg. the build-date etc.
+.TP
+Example: On my system, the following \fI/etc/issue\fP file:
+
+.na
+.nf
+.ti +.5
+This is \\n.\\o (\\s \\m \\r) \\t
+.TP
+displays as
+
+.ti +.5
+This is thingol.orcan.dk (Linux i386 1.1.9) 18:29:30
+
+.fi
+
+.SH FILES
+.na
+.nf
+/var/run/utmp, the system status file.
+/etc/issue, printed before the login prompt.
+/dev/console, problem reports (if syslog(3) is not used).
+/etc/inittab, \fIinit\fP(8) configuration file.
+.SH BUGS
+.ad
+.fi
+The baud-rate detection feature (the \fI\-m\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 \fI\-m\fP option in combination with a multiple baud
+rate command-line argument, so that BREAK processing is enabled.
+
+The text in the \fI/etc/issue\fP file (or other) and the login prompt
+are always output with 7-bit characters and space parity.
+
+The baud-rate detection feature (the \fI\-m\fP option) requires that
+the modem emits its status message \fIafter\fP raising the DCD line.
+.SH DIAGNOSTICS
+.ad
+.fi
+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 \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 AUTHOR(S)
+.na
+.nf
+W.Z. Venema <wietse@wzv.win.tue.nl>
+Eindhoven University of Technology
+Department of Mathematics and Computer Science
+Den Dolech 2, P.O. Box 513, 5600 MB Eindhoven, The Netherlands
+
+Peter Orbaek <poe@daimi.aau.dk>
+Linux port and more options. Still maintains the code.
+
+Eric Rasmussen <ear@usfirst.org>
+Added \-f option to display custom login messages on different terminals.
+
+.SH AVAILABILITY
+The agetty command is part of the util-linux package and is available from
+ftp://ftp.kernel.org/pub/linux/utils/util\-linux/.
diff --git a/term-utils/agetty.c b/term-utils/agetty.c
new file mode 100644
index 0000000..43243f9
--- /dev/null
+++ b/term-utils/agetty.c
@@ -0,0 +1,1914 @@
+/*
+ * 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 <fcntl.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <utmp.h>
+#include <getopt.h>
+#include <time.h>
+#include <sys/file.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <langinfo.h>
+#include <grp.h>
+
+#include "strutils.h"
+#include "all-io.h"
+#include "nls.h"
+#include "pathnames.h"
+#include "c.h"
+#include "widechar.h"
+#include "ttyutils.h"
+
+#ifdef __linux__
+# include <sys/kd.h>
+# include <sys/param.h>
+# define USE_SYSLOG
+# ifndef DEFAULT_VCTERM
+# define DEFAULT_VCTERM "linux"
+# 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
+
+/* 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 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 _PATH_ISSUE
+# include <sys/utsname.h>
+#endif
+
+/* Login prompt. */
+#define LOGIN "login: "
+#define LOGIN_ARGV_MAX 16 /* Numbers of args for login */
+
+/* Some shorthands for control characters. */
+#define CTL(x) (x ^ 0100) /* Assumes ASCII dialect */
+#define CR CTL('M') /* carriage return */
+#define NL CTL('J') /* line feed */
+#define BS CTL('H') /* back space */
+#define DEL CTL('?') /* delete */
+
+/* Defaults for line-editing etc. characters; you may want to change these. */
+#define DEF_ERASE DEL /* default erase character */
+#define DEF_INTR CTL('C') /* default interrupt character */
+#define DEF_QUIT CTL('\\') /* default quit char */
+#define DEF_KILL CTL('U') /* default kill char */
+#define DEF_EOF CTL('D') /* default EOF char */
+#define DEF_EOL 0
+#define DEF_SWITCH 0 /* default switch char */
+
+/*
+ * 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 */
+ int timeout; /* time-out period */
+ char *autolog; /* login the user automatically */
+ char *chdir; /* Chdir before the login */
+ char *chroot; /* Chroot before the login */
+ char *login; /* login program */
+ char *logopt; /* options for login program */
+ char *tty; /* name of tty */
+ char *vcline; /* line of virtual console */
+ char *term; /* terminal type */
+ char *initstring; /* modem init string */
+ char *issue; /* alternative issue file */
+ int delay; /* Sleep seconds before prompt */
+ int nice; /* Run login with this priority */
+ int numspeed; /* number of baud rates to try */
+ speed_t speeds[MAX_SPEED]; /* baud rates to be tried */
+};
+
+#define F_PARSE (1<<0) /* process modem status messages */
+#define F_ISSUE (1<<1) /* display /etc/issue */
+#define F_RTSCTS (1<<2) /* enable RTS/CTS flow control */
+#define F_LOCAL (1<<3) /* force local */
+#define F_INITSTRING (1<<4) /* initstring is set */
+#define F_WAITCRLF (1<<5) /* wait for CR or LF */
+#define F_CUSTISSUE (1<<6) /* give alternative issue file */
+#define F_NOPROMPT (1<<7) /* do not ask for login name! */
+#define F_LCUC (1<<8) /* support for *LCUC stty modes */
+#define F_KEEPSPEED (1<<9) /* follow baud rate from kernel */
+#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))
+
+/* Storage for things detected while the login name was read. */
+struct chardata {
+ int erase; /* erase character */
+ int kill; /* kill character */
+ int eol; /* end-of-line character */
+ int parity; /* what parity did we see */
+ int capslock; /* upper case without lower case */
+};
+
+/* Initial values for the above. */
+static const struct chardata init_chardata = {
+ DEF_ERASE, /* default erase character */
+ DEF_KILL, /* default kill character */
+ 13, /* default eol char */
+ 0, /* space parity */
+ 0, /* no capslock */
+};
+
+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},
+#endif
+#ifdef B38400
+ {38400, B38400},
+#endif
+#ifdef EXTA
+ {19200, EXTA},
+#endif
+#ifdef EXTB
+ {38400, EXTB},
+#endif
+#ifdef B57600
+ {57600, B57600},
+#endif
+#ifdef B115200
+ {115200, B115200},
+#endif
+#ifdef B230400
+ {230400, B230400},
+#endif
+ {0, 0},
+};
+
+static void init_special_char(char* arg, struct options *op);
+static void parse_args(int argc, char **argv, struct options *op);
+static void parse_speeds(struct options *op, char *arg);
+static void update_utmp(struct options *op);
+static void open_tty(char *tty, struct termios *tp, struct options *op);
+static void termio_init(struct options *op, struct termios *tp);
+static void reset_vc (const struct options *op, struct termios *tp);
+static void auto_baud(struct termios *tp);
+static void output_special_char (unsigned char c, struct options *op, struct termios *tp);
+static void do_prompt(struct options *op, struct termios *tp);
+static void next_speed(struct options *op, struct termios *tp);
+static char *get_logname(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(FILE * out) __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);
+
+/* Fake hostname for ut_host specified on command line. */
+static char *fakehost;
+
+#ifdef DEBUGGING
+#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 */
+ .term = DEFAULT_VCTERM, /* terminal type */
+ .issue = ISSUE /* default issue file */
+ };
+ char *login_argv[LOGIN_ARGV_MAX + 1];
+ int login_argc = 0;
+ struct sigaction sa, sa_hup, sa_quit, sa_int;
+ sigset_t set;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+
+ /* In case vhangup(2) has to called */
+ sa.sa_handler = SIG_IGN;
+ sa.sa_flags = SA_RESTART;
+ sigemptyset (&sa.sa_mask);
+ sigaction(SIGHUP, &sa, &sa_hup);
+ sigaction(SIGQUIT, &sa, &sa_quit);
+ sigaction(SIGINT, &sa, &sa_int);
+
+#ifdef DEBUGGING
+ dbf = fopen("/dev/ttyp0", "w");
+ for (int i = 1; i < argc; i++)
+ debug(argv[i]);
+#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());
+ /* Initialize the termios settings (raw mode, eight-bit, blocking i/o). */
+ debug("calling termio_init\n");
+ termio_init(&options, &termios);
+
+ /* Write the modem init string and DO NOT flush the buffers. */
+ if (serial_tty_option(&options, F_INITSTRING) &&
+ options.initstring && *options.initstring != '\0') {
+ debug("writing init string\n");
+ write_all(STDOUT_FILENO, options.initstring,
+ strlen(options.initstring));
+ }
+
+ if (!serial_tty_option(&options, F_LOCAL))
+ /* Go to blocking write mode unless -L is specified. */
+ 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((unsigned) 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;
+ }
+ }
+
+ chardata = init_chardata;
+ if ((options.flags & F_NOPROMPT) == 0) {
+ if (options.autolog) {
+ /* Do the auto login. */
+ debug("doing auto login\n");
+ do_prompt(&options, &termios);
+ printf("%s%s (automatic login)\n", LOGIN, options.autolog);
+ username = options.autolog;
+ } else {
+ /* Read the login name. */
+ debug("reading login name\n");
+ while ((username =
+ get_logname(&options, &termios, &chardata)) == 0)
+ if ((options.flags & F_VCONSOLE) == 0)
+ next_speed(&options, &termios);
+ }
+ }
+
+ /* Disable timer. */
+ if (options.timeout)
+ alarm(0);
+
+ if ((options.flags & F_VCONSOLE) == 0) {
+ /* Finalize the termios settings. */
+ termio_final(&options, &termios, &chardata);
+
+ /* 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 (fakehost && (options.flags & F_REMOTE)) {
+ login_argv[login_argc++] = "-h";
+ login_argv[login_argc++] = fakehost;
+ }
+ 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) {
+ if (chroot(options.chroot) < 0)
+ log_err(_("%s: can't change root directory %s: %m"),
+ options.tty, options.chroot);
+ }
+ if (options.chdir) {
+ if (chdir(options.chdir) < 0)
+ log_err(_("%s: can't change working directory %s: %m"),
+ options.tty, options.chdir);
+ }
+ if (options.nice) {
+ if (nice(options.nice) < 0)
+ log_warn(_("%s: can't change process priority: %m"),
+ options.tty);
+ }
+
+ /* 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)) {
+ p++;
+ continue; /* no \u */
+ }
+ sz = strlen(str);
+
+ if (p == str && sz == 2)
+ /* 'str' contains only '\u' */
+ return username;
+
+ tp = entry = malloc(sz + usz);
+ if (!tp)
+ log_err(_("failed to allocate memory: %m"));
+
+ if (p != str) {
+ /* copy chars befor \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;
+}
+
+/* Parse command-line arguments. */
+static void parse_args(int argc, char **argv, struct options *op)
+{
+ int c;
+
+ enum {
+ VERSION_OPTION = CHAR_MAX + 1,
+ NOHINTS_OPTION,
+ NOHOSTNAME_OPTION,
+ LONGHOSTNAME_OPTION,
+ HELP_OPTION
+ };
+ const struct option longopts[] = {
+ { "8bits", no_argument, 0, '8' },
+ { "autologin", required_argument, 0, 'a' },
+ { "noreset", no_argument, 0, 'c' },
+ { "chdir", required_argument, 0, 'C' },
+ { "delay", required_argument, 0, 'd' },
+ { "remote", no_argument, 0, 'E' },
+ { "issue-file", required_argument, 0, 'f' },
+ { "flow-control", no_argument, 0, 'h' },
+ { "host", required_argument, 0, 'H' },
+ { "noissue", no_argument, 0, 'i' },
+ { "init-string", required_argument, 0, 'I' },
+ { "noclear", no_argument, 0, 'J' },
+ { "login-program", required_argument, 0, 'l' },
+ { "local-line", no_argument, 0, 'L' },
+ { "extract-baud", no_argument, 0, 'm' },
+ { "skip-login", no_argument, 0, 'n' },
+ { "nonewline", no_argument, 0, 'N' },
+ { "login-options", required_argument, 0, 'o' },
+ { "login-pause", no_argument, 0, 'p' },
+ { "nice", required_argument, 0, 'P' },
+ { "chroot", required_argument, 0, 'r' },
+ { "hangup", no_argument, 0, 'R' },
+ { "keep-baud", no_argument, 0, 's' },
+ { "timeout", required_argument, 0, 't' },
+ { "detect-case", no_argument, 0, 'U' },
+ { "wait-cr", no_argument, 0, 'w' },
+ { "nohints", no_argument, 0, NOHINTS_OPTION },
+ { "nohostname", no_argument, 0, NOHOSTNAME_OPTION },
+ { "long-hostname", no_argument, 0, LONGHOSTNAME_OPTION },
+ { "version", no_argument, 0, VERSION_OPTION },
+ { "help", no_argument, 0, HELP_OPTION },
+ { NULL, 0, 0, 0 }
+ };
+
+ while ((c = getopt_long(argc, argv,
+ "8a:cC:d:Ef:hH:iI:Jl:LmnNo: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 = atoi(optarg);
+ break;
+ case 'E':
+ op->flags |= F_REMOTE;
+ break;
+ case 'f':
+ op->flags |= F_CUSTISSUE;
+ 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':
+ op->flags |= F_LOCAL;
+ break;
+ case 'm':
+ op->flags |= F_PARSE;
+ break;
+ case 'n':
+ op->flags |= F_NOPROMPT;
+ break;
+ case 'o':
+ op->logopt = optarg;
+ break;
+ case 'p':
+ op->flags |= F_LOGINPAUSE;
+ break;
+ case 'P':
+ op->nice = atoi(optarg);
+ break;
+ case 'r':
+ op->chroot = optarg;
+ break;
+ case 'R':
+ op->flags |= F_HANGUP;
+ break;
+ case 's':
+ op->flags |= F_KEEPSPEED;
+ break;
+ case 't':
+ if ((op->timeout = atoi(optarg)) <= 0)
+ log_err(_("bad timeout value: %s"), optarg);
+ 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 VERSION_OPTION:
+ printf(_("%s from %s\n"), program_invocation_short_name,
+ PACKAGE_STRING);
+ exit(EXIT_SUCCESS);
+ case HELP_OPTION:
+ usage(stdout);
+ default:
+ usage(stderr);
+ }
+ }
+
+ debug("after getopt loop\n");
+
+ if (argc < optind + 1) {
+ log_warn(_("not enough arguments"));
+ usage(stderr);
+ }
+
+ /* Accept "tty", "baudrate tty", and "tty baudrate". */
+ if ('0' <= argv[optind][0] && argv[optind][0] <= '9') {
+ /* Assume BSD style speed. */
+ parse_speeds(op, argv[optind++]);
+ if (argc < optind + 1) {
+ warn(_("not enough arguments"));
+ usage(stderr);
+ }
+ op->tty = argv[optind++];
+ } else {
+ op->tty = argv[optind++];
+ if (argc > optind) {
+ char *v = argv[optind++];
+ if ('0' <= *v && *v <= '9')
+ parse_speeds(op, v);
+ else
+ op->speeds[op->numspeed++] = bcode("9600");
+ }
+ }
+
+ /* 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];
+
+#ifdef DO_DEVFS_FIDDLING
+ /*
+ * Some devfs junk, following Goswin Brederlow:
+ * turn ttyS<n> into tts/<n>
+ * turn tty<n> into vc/<n>
+ * http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=72241
+ */
+ if (op->tty && strlen(op->tty) < 90) {
+ char dev_name[100];
+ struct stat st;
+
+ if (strncmp(op->tty, "ttyS", 4) == 0) {
+ strcpy(dev_name, "/dev/");
+ strcat(dev_name, op->tty);
+ if (stat(dev_name, &st) < 0) {
+ strcpy(dev_name, "/dev/tts/");
+ strcat(dev_name, op->tty + 4);
+ if (stat(dev_name, &st) == 0) {
+ op->tty = strdup(dev_name + 5);
+ if (!op->tty)
+ log_err(_("failed to allocate memory: %m"));
+ }
+ }
+ } else if (strncmp(op->tty, "tty", 3) == 0) {
+ strcpy(dev_name, "/dev/");
+ strncat(dev_name, op->tty, 90);
+ if (stat(dev_name, &st) < 0) {
+ strcpy(dev_name, "/dev/vc/");
+ strcat(dev_name, op->tty + 3);
+ if (stat(dev_name, &st) == 0) {
+ op->tty = strdup(dev_name + 5);
+ if (!op->tty)
+ log_err(_("failed to allocate memory: %m"));
+ }
+ }
+ }
+ }
+#endif /* DO_DEVFS_FIDDLING */
+
+ debug("exiting parseargs\n");
+}
+
+/* Parse alternate baud rates. */
+static void parse_speeds(struct options *op, char *arg)
+{
+ char *cp;
+
+ debug("entered parse_speeds\n");
+ for (cp = strtok(arg, ","); cp != 0; 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");
+}
+
+#ifdef SYSV_STYLE
+
+/* Update our utmp entry. */
+static void update_utmp(struct options *op)
+{
+ struct utmp ut;
+ time_t t;
+ pid_t pid = getpid();
+ pid_t sid = getsid(0);
+ char *vcline = op->vcline;
+ char *line = op->tty;
+ struct utmp *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.
+ */
+ utmpname(_PATH_UTMP);
+ setutent();
+
+ /*
+ * 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 = getutent()))
+ 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 */
+ strncpy(ut.ut_id, vcline, sizeof(ut.ut_id));
+ else {
+ size_t len = strlen(line);
+ char * ptr;
+ if (len >= sizeof(ut.ut_id))
+ ptr = line + len - sizeof(ut.ut_id);
+ else
+ ptr = line;
+ strncpy(ut.ut_id, ptr, sizeof(ut.ut_id));
+ }
+ }
+
+ strncpy(ut.ut_user, "LOGIN", sizeof(ut.ut_user));
+ strncpy(ut.ut_line, line, sizeof(ut.ut_line));
+ if (fakehost)
+ strncpy(ut.ut_host, fakehost, sizeof(ut.ut_host));
+ time(&t);
+ ut.ut_time = t;
+ ut.ut_type = LOGIN_PROCESS;
+ ut.ut_pid = pid;
+ ut.ut_session = sid;
+
+ pututline(&ut);
+ endutent();
+
+ {
+#ifdef HAVE_UPDWTMP
+ updwtmp(_PATH_WTMP, &ut);
+#else
+ int ut_fd;
+ int lf;
+
+ if ((lf = open(_PATH_WTMPLOCK, O_CREAT | O_WRONLY, 0660)) >= 0) {
+ flock(lf, LOCK_EX);
+ if ((ut_fd =
+ open(_PATH_WTMP, O_APPEND | O_WRONLY)) >= 0) {
+ write_all(ut_fd, &ut, sizeof(ut));
+ close(ut_fd);
+ }
+ flock(lf, LOCK_UN);
+ close(lf);
+ }
+#endif /* HAVE_UPDWTMP */
+ }
+}
+
+#endif /* SYSV_STYLE */
+
+/* Set up tty as stdin, stdout & stderr. */
+static void open_tty(char *tty, struct termios *tp, struct options *op)
+{
+ const pid_t pid = getpid();
+ int serial, closed = 0;
+
+ /* Set up new standard input, unless we are given an already opened port. */
+
+ if (strcmp(tty, "-") != 0) {
+ char buf[PATH_MAX+1];
+ struct group *gr = NULL;
+ struct stat st;
+ int fd, len;
+ pid_t tid;
+ gid_t gid = 0;
+
+ /* Use tty group if available */
+ if ((gr = getgrnam("tty")))
+ gid = gr->gr_gid;
+
+ if (((len = snprintf(buf, sizeof(buf), "/dev/%s", tty)) >=
+ (int)sizeof(buf)) || (len < 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 (chown(buf, 0, gid) || chmod(buf, (gid ? 0660 : 0600))) {
+ if (errno == EROFS)
+ log_warn("%s: %m", buf);
+ else
+ log_err("%s: %m", buf);
+ }
+
+ /* Open the tty as standard input. */
+ if ((fd = open(buf, O_RDWR|O_NOCTTY|O_NONBLOCK, 0)) < 0)
+ log_err(_("/dev/%s: cannot open as standard input: %m"), tty);
+
+ /* Sanity checks... */
+ if (!isatty(fd))
+ log_err(_("/dev/%s: not a character device"), tty);
+ if (fstat(fd, &st) < 0)
+ log_err("%s: %m", buf);
+ if ((st.st_mode & S_IFMT) != S_IFCHR)
+ log_err(_("/dev/%s: not a character device"), tty);
+
+ if (((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 decriptors 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: tcgetattr: %m", tty);
+
+ /*
+ * Detect if this is a virtual console or serial/modem line.
+ * In case of a virtual console the ioctl TIOCMGET fails and
+ * the error number will be set to EINVAL.
+ */
+ if (ioctl(STDIN_FILENO, TIOCMGET, &serial) < 0 && (errno == EINVAL)) {
+ op->flags |= F_VCONSOLE;
+ if (!op->term)
+ op->term = DEFAULT_VCTERM;
+ } else if (!op->term)
+ op->term = DEFAULT_STERM;
+
+ setenv("TERM", op->term, 1);
+}
+
+/* Initialize termios settings. */
+static void termio_init(struct options *op, struct termios *tp)
+{
+ speed_t ispeed, ospeed;
+ struct winsize ws;
+
+ if (op->flags & F_VCONSOLE) {
+#if defined(IUTF8) && defined(KDGKBMODE)
+ int mode;
+
+ /* Detect mode of current keyboard setup, e.g. for UTF-8 */
+ if (ioctl(STDIN_FILENO, KDGKBMODE, &mode) < 0)
+ mode = K_RAW;
+ switch(mode) {
+ 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);
+
+ if ((tp->c_cflag & (CS8|PARODD|PARENB)) == CS8)
+ op->flags |= F_EIGHTBITS;
+
+ if ((op->flags & F_NOCLEAR) == 0) {
+ /*
+ * Do not write a full reset (ESC c) because this destroys
+ * the unicode mode again if the terminal was in unicode
+ * mode. Also it clears the CONSOLE_MAGIC features which
+ * are required for some languages/console-fonts.
+ * Just put the cursor to the home position (ESC [ H),
+ * erase everything below the cursor (ESC [ J), and set the
+ * scrolling region to the full window (ESC [ r)
+ */
+ write_all(STDOUT_FILENO, "\033[r\033[H\033[J", 9);
+ }
+ return;
+ }
+
+ if (op->flags & F_KEEPSPEED) {
+ /* Save the original setting. */
+ ispeed = cfgetispeed(tp);
+ ospeed = cfgetospeed(tp);
+
+ 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.
+ */
+
+ /* Flush input and output queues, important for modems! */
+ tcflush(STDIN_FILENO, TCIOFLUSH);
+
+#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 se reseted.
+ */
+ cfsetispeed(tp, ispeed);
+ cfsetospeed(tp, ospeed);
+
+ if (op->flags & F_LOCAL)
+ tp->c_cflag |= CLOCAL;
+#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) {
+ int set = 0;
+ if (ws.ws_row == 0) {
+ ws.ws_row = 24;
+ set++;
+ }
+ if (ws.ws_col == 0) {
+ ws.ws_col = 80;
+ set++;
+ }
+ 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
+
+ tcsetattr(STDIN_FILENO, TCSANOW, tp);
+
+ /* 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 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);
+
+ if (tcsetattr(STDIN_FILENO, TCSADRAIN, tp))
+ log_warn("tcsetattr problem: %m");
+}
+
+/* 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 (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);
+}
+
+/* Show login prompt, optionally preceded by /etc/issue contents. */
+static void do_prompt(struct options *op, struct termios *tp)
+{
+#ifdef ISSUE
+ FILE *fd;
+#endif /* ISSUE */
+
+ if ((op->flags & F_NONL) == 0) {
+ /* Issue not in use, start with a new line. */
+ write_all(STDOUT_FILENO, "\r\n", 2);
+ }
+
+#ifdef ISSUE
+ if ((op->flags & F_ISSUE) && (fd = fopen(op->issue, "r"))) {
+ int c, oflag = tp->c_oflag; /* Save current setting. */
+
+ 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);
+ }
+
+ while ((c = getc(fd)) != EOF) {
+ if (c == '\\')
+ output_special_char(getc(fd), op, tp);
+ else
+ putchar(c);
+ }
+ fflush(stdout);
+
+ if ((op->flags & F_VCONSOLE) == 0) {
+ /* Restore settings. */
+ tp->c_oflag = oflag;
+ /* Wait till output is gone. */
+ tcsetattr(STDIN_FILENO, TCSADRAIN, tp);
+ }
+ fclose(fd);
+ }
+#endif /* ISSUE */
+ if (op->flags & F_LOGINPAUSE) {
+ puts("[press ENTER to login]");
+ 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[MAXHOSTNAMELEN + 1];
+ if (gethostname(hn, sizeof(hn)) == 0) {
+ struct hostent *ht;
+ char *dot = strchr(hn, '.');
+
+ hn[MAXHOSTNAMELEN] = '\0';
+ if ((op->flags & F_LONGHNAME) == 0) {
+ if (dot)
+ *dot = '\0';
+ write_all(STDOUT_FILENO, hn, strlen(hn));
+ } else if (dot == NULL && (ht = gethostbyname(hn)))
+ write_all(STDOUT_FILENO, ht->h_name, strlen(ht->h_name));
+ else
+ write_all(STDOUT_FILENO, hn, strlen(hn));
+ write_all(STDOUT_FILENO, " ", 1);
+ }
+ }
+ if (op->autolog == (char*)0) {
+ /* 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 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). */
+ *cp = init_chardata;
+
+ /*
+ * 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);
+ bp = logname;
+ *bp = '\0';
+
+ while (*logname == '\0') {
+
+ /* Write issue file and prompt */
+ do_prompt(op, tp);
+
+ cp->eol = '\0';
+
+ /* Read name, watch for break and end-of-line. */
+ while (cp->eol == '\0') {
+
+ if (read(STDIN_FILENO, &c, 1) < 1) {
+
+ /* Do not report trivial like EINTR/EIO errors. */
+ if (errno == EINTR || errno == EAGAIN) {
+ usleep(1000);
+ continue;
+ }
+ switch (errno) {
+ case 0:
+ case EIO:
+ case ESRCH:
+ case EINVAL:
+ case ENOENT:
+ break;
+ default:
+ log_err(_("%s: read: %m"), op->tty);
+ }
+ }
+
+ /* 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);
+ }
+
+ /* Do erase, kill and end-of-line processing. */
+ switch (ascval) {
+ case 0:
+ *bp = 0;
+ if (op->numspeed > 1)
+ return NULL;
+ break;
+ case CR:
+ case NL:
+ *bp = 0; /* terminate logname */
+ cp->eol = ascval; /* set end-of-line char */
+ break;
+ case BS:
+ case DEL:
+ case '#':
+ 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'):
+ case '@':
+ cp->kill = ascval; /* set kill character */
+ while (bp > logname) {
+ if ((tp->c_lflag & ECHO) == 0)
+ write_all(1, erase[cp->parity], 3);
+ bp--;
+ }
+ break;
+ case CTL('D'):
+ exit(EXIT_SUCCESS);
+ default:
+ if (!isascii(ascval) || !isprint(ascval))
+ break;
+ 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;
+ }
+ }
+ }
+#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 = (wchar_t *) 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;
+ /* do not break */
+ case 2:
+ /* even parity */
+ tp->c_cflag |= PARENB;
+ tp->c_iflag |= INPCK | ISTRIP;
+ /* do not break */
+ 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: tcsetattr: TCSANOW: %m", op->tty);
+}
+
+/*
+ * String contains upper case without lower case.
+ * http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=52940
+ * http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=156242
+ */
+static int caps_lock(char *s)
+{
+ int capslock;
+
+ for (capslock = 0; *s; s++) {
+ if (islower(*s))
+ return EXIT_SUCCESS;
+ if (capslock == 0)
+ capslock = isupper(*s);
+ }
+ return capslock;
+}
+
+/* Convert speed string to speed code; return 0 on failure. */
+static speed_t bcode(char *s)
+{
+ const struct Speedtab *sp;
+ long speed = atol(s);
+
+ for (sp = speedtab; sp->speed; sp++)
+ if (sp->speed == speed)
+ return sp->code;
+ return 0;
+}
+
+static void __attribute__ ((__noreturn__)) usage(FILE * out)
+{
+ fprintf(out, _("\nUsage:\n"
+ " %1$s [options] line baud_rate,... [termtype]\n"
+ " %1$s [options] baud_rate,... line [termtype]\n"),
+ program_invocation_short_name);
+
+ fprintf(out, _("\nOptions:\n"
+ " -8, --8bits assume 8-bit tty\n"
+ " -a, --autologin <user> login the specified user automatically\n"
+ " -c, --noreset do not reset control mode\n"
+ " -f, --issue-file <file> display issue file\n"
+ " -h, --flow-control enable hardware flow control\n"
+ " -H, --host <hostname> specify login host\n"
+ " -i, --noissue do not display issue file\n"
+ " -I, --init-string <string> set init string\n"
+ " -l, --login-program <file> specify login program\n"
+ " -L, --local-line force local line\n"
+ " -m, --extract-baud extract baud rate during connect\n"
+ " -n, --skip-login do not prompt for login\n"
+ " -o, --login-options <opts> options that are passed to login\n"
+ " -p, --loginpause wait for any key before the login\n"
+ " -R, --hangup do virtually hangup on the tty\n"
+ " -s, --keep-baud try to keep baud rate after break\n"
+ " -t, --timeout <number> login process timeout\n"
+ " -U, --detect-case detect uppercase terminal\n"
+ " -w, --wait-cr wait carriage-return\n"
+ " --noclear do not clear the screen before prompt\n"
+ " --nohints do not print hints\n"
+ " --nonewline do not print a newline before issue\n"
+ " --no-hostname no hostname at all will be shown\n"
+ " --long-hostname show full qualified hostname\n"
+ " --version output version information and exit\n"
+ " --help display this help and exit\n\n"));
+
+ exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+/*
+ * 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.
+ */
+#define str2cpy(b,s1,s2) strcat(strcpy(b,s1),s2)
+
+static void dolog(int priority, const char *fmt, va_list ap)
+{
+#ifndef USE_SYSLOG
+ int fd;
+#endif
+ char buf[BUFSIZ];
+ char *bp;
+
+ /*
+ * 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.
+ */
+#ifdef USE_SYSLOG
+ buf[0] = '\0';
+ bp = buf;
+#else
+ str2cpy(buf, program_invocation_short_name, ": ");
+ bp = buf + strlen(buf);
+#endif /* USE_SYSLOG */
+ vsnprintf(bp, sizeof(buf)-strlen(buf), fmt, ap);
+
+ /*
+ * Write the diagnostic directly to /dev/console if we do not use the
+ * syslog(3) facility.
+ */
+#ifdef USE_SYSLOG
+ openlog(program_invocation_short_name, LOG_PID, LOG_AUTHPRIV);
+ syslog(priority, "%s", buf);
+ closelog();
+#else
+ /* Terminate with CR-LF since the console mode is unknown. */
+ strcat(bp, "\r\n");
+ if ((fd = open("/dev/console", 1)) >= 0) {
+ write_all(fd, buf, strlen(buf));
+ close(fd);
+ }
+#endif /* USE_SYSLOG */
+}
+
+static void log_err(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ dolog(LOG_ERR, fmt, ap);
+ va_end(ap);
+
+ /* Be kind to init(8). */
+ sleep(10);
+ exit(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 output_special_char(unsigned char c, struct options *op,
+ struct termios *tp)
+{
+ struct utsname uts;
+
+ uname(&uts);
+
+ switch (c) {
+ case 's':
+ printf("%s", uts.sysname);
+ break;
+ case 'n':
+ printf("%s", uts.nodename);
+ break;
+ case 'r':
+ printf("%s", uts.release);
+ break;
+ case 'v':
+ printf("%s", uts.version);
+ break;
+ case 'm':
+ printf("%s", uts.machine);
+ break;
+ case 'o':
+ {
+ char domainname[MAXHOSTNAMELEN+1];
+#ifdef HAVE_GETDOMAINNAME
+ if (getdomainname(domainname, sizeof(domainname)))
+#endif
+ strcpy(domainname, "unknown_domain");
+ domainname[sizeof(domainname)-1] = '\0';
+ printf("%s", domainname);
+ break;
+ }
+ case 'O':
+ {
+ char *dom = "unknown_domain";
+ char host[MAXHOSTNAMELEN+1];
+ struct addrinfo hints, *info = NULL;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_CANONNAME;
+
+ if (gethostname(host, sizeof(host)) ||
+ getaddrinfo(host, NULL, &hints, &info) ||
+ info == NULL)
+ fputs(dom, stdout);
+ else {
+ char *canon;
+ if (info->ai_canonname &&
+ (canon = strchr(info->ai_canonname, '.')))
+ dom = canon + 1;
+ fputs(dom, stdout);
+ freeaddrinfo(info);
+ }
+ break;
+ }
+ case 'd':
+ case 't':
+ {
+ time_t now;
+ struct tm *tm;
+
+ time(&now);
+ tm = localtime(&now);
+
+ if (!tm)
+ break;
+
+ if (c == 'd') /* ISO 8601 */
+ printf("%s %s %d %d",
+ nl_langinfo(ABDAY_1 + tm->tm_wday),
+ nl_langinfo(ABMON_1 + tm->tm_mon),
+ tm->tm_mday,
+ tm->tm_year < 70 ? tm->tm_year + 2000 :
+ tm->tm_year + 1900);
+ else
+ printf("%02d:%02d:%02d",
+ tm->tm_hour, tm->tm_min, tm->tm_sec);
+ break;
+ }
+ case 'l':
+ printf ("%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) {
+ printf("%ld", speedtab[i].speed);
+ break;
+ }
+ }
+ break;
+ }
+ case 'u':
+ case 'U':
+ {
+ int users = 0;
+ struct utmp *ut;
+ setutent();
+ while ((ut = getutent()))
+ if (ut->ut_type == USER_PROCESS)
+ users++;
+ endutent();
+ printf ("%d ", users);
+ if (c == 'U')
+ printf((users == 1) ? _("user") : _("users"));
+ break;
+ }
+ default:
+ putchar(c);
+ 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 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: %m");
+}
+
diff --git a/term-utils/mesg.1 b/term-utils/mesg.1
new file mode 100644
index 0000000..60d9423
--- /dev/null
+++ b/term-utils/mesg.1
@@ -0,0 +1,112 @@
+.\" 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
+.\"
+.\" Fri Mar 10 20:31:02 1995, modified for standard man macros,
+.\" faith@cs.unc.edu
+.\"
+.\"
+.\" "
+.TH MESG 1 "April 2011" "util-linux" "User Commands"
+.SH NAME
+mesg \- display (do not display) messages from other users
+.SH SYNOPSIS
+.B mesg
+.RB [options]
+.RB [ n | y ]
+.SH DESCRIPTION
+The
+.B mesg
+utility is invoked by a users to control write access others have to the
+terminal device associated with the standard error output. If write access
+is allowed, then programs such as
+.BR talk (1)
+and
+.BR write (1)
+may display messages on the terminal.
+.PP
+Traditionally, write access is allowed by default. However, as users
+become more conscious of various security risks, there is a trend to remove
+write access by default, at least for the primary login shell. To make
+sure your ttys are set the way you want them to be set,
+.B mesg
+should be executed in your login scripts.
+.SH ARGUMENTS
+.TP
+.B n
+Disallows messages.
+.TP
+.B y
+Permits messages to be displayed.
+.SH OPTIONS
+.TP
+.B \-v, \-\-verbose
+Explain what is being done.
+.TP
+.B \-V, \-\-verbose
+Output version information and exit.
+.TP
+.B \-h, \-\-help
+Output help screen and exit.
+.PP
+If no arguments are given,
+.B mesg
+displays the present message status to the standard error output.
+.PP
+The
+.B mesg
+utility exits with one of the following values:
+.TP
+.I "\ 0"
+Messages are allowed.
+.TP
+.I "\ 1"
+Messages are not allowed.
+.TP
+.I ">1"
+An error has occurred.
+.SH FILES
+.I /dev/[pt]ty[pq]?
+.SH "SEE ALSO"
+.BR talk (1),
+.BR write (1),
+.BR wall (1),
+.BR login (1),
+.BR xterm (1)
+.SH HISTORY
+A
+.B mesg
+command appeared in Version 6 AT&T UNIX.
+
+.SH AVAILABILITY
+The mesg command is part of the util-linux package and is available from
+ftp://ftp.kernel.org/pub/linux/utils/util-linux/.
diff --git a/term-utils/mesg.c b/term-utils/mesg.c
new file mode 100644
index 0000000..b24e783
--- /dev/null
+++ b/term-utils/mesg.c
@@ -0,0 +1,158 @@
+/*
+ * 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"
+
+/* 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(FILE * out)
+{
+ fputs(_("\nUsage:\n"), 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(_("\nOptions:\n"), out);
+ fputs(_(" -v, --verbose explain what is being done\n"
+ " -V, --version output version information and exit\n"
+ " -h, --help output help screen and exit\n\n"), out);
+
+ exit(out == stderr ? MESG_EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+ struct stat sb;
+ char *tty;
+ int ch, verbose = FALSE;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ atexit(close_stdout);
+
+ static const struct option longopts[] = {
+ { "verbose", no_argument, 0, 'v' },
+ { "version", no_argument, 0, 'V' },
+ { "help", no_argument, 0, 'h' },
+ { NULL, 0, 0, 0 }
+ };
+
+ while ((ch = getopt_long(argc, argv, "vVh", longopts, NULL)) != -1)
+ switch (ch) {
+ case 'v':
+ verbose = TRUE;
+ break;
+ case 'V':
+ printf(_("%s from %s\n"), program_invocation_short_name,
+ PACKAGE_STRING);
+ exit(EXIT_SUCCESS);
+ case 'h':
+ usage(stdout);
+ default:
+ usage(stderr);
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if ((tty = ttyname(STDERR_FILENO)) == NULL)
+ err(MESG_EXIT_FAILURE, _("ttyname failed"));
+
+ if (stat(tty, &sb) < 0)
+ err(MESG_EXIT_FAILURE, _("stat failed %s"), tty);
+
+ if (!*argv) {
+ if (sb.st_mode & (S_IWGRP | S_IWOTH)) {
+ puts(_("is y"));
+ return IS_ALLOWED;
+ }
+ puts(_("is n"));
+ return IS_NOT_ALLOWED;
+ }
+
+ switch (rpmatch(argv[0])) {
+ case 1:
+#ifdef USE_TTY_GROUP
+ if (chmod(tty, sb.st_mode | S_IWGRP) < 0)
+#else
+ if (chmod(tty, 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"));
+ return IS_ALLOWED;
+ case 0:
+ if (chmod(tty, 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"));
+ return IS_NOT_ALLOWED;
+ case -1:
+ warnx(_("invalid argument: %s"), argv[0]);
+ usage(stderr);
+ default:
+ abort();
+ }
+}
diff --git a/term-utils/reset b/term-utils/reset
new file mode 100755
index 0000000..68de82e
--- /dev/null
+++ b/term-utils/reset
@@ -0,0 +1,13 @@
+#!/bin/sh
+stty sane
+tput clear
+tput rmacs
+tput rmm
+tput rmso
+tput rmul
+tput rs1
+tput rs2
+tput rs3
+bot=$((${LINES:-$(tput lines)} - 1))
+if test "${bot}" -le "0"; then bot=24; fi
+tput csr 0 ${bot}
diff --git a/term-utils/reset.033c b/term-utils/reset.033c
new file mode 100755
index 0000000..fc7fad6
--- /dev/null
+++ b/term-utils/reset.033c
@@ -0,0 +1,11 @@
+#!/bin/sh
+stty sane
+tput clear
+tput rmacs
+tput rmm
+tput rmso
+tput rmul
+tput rs1
+tput rs2
+tput rs3
+printf "\\033c\n"
diff --git a/term-utils/reset.1 b/term-utils/reset.1
new file mode 100644
index 0000000..fdf67e2
--- /dev/null
+++ b/term-utils/reset.1
@@ -0,0 +1,45 @@
+.\" Copyright 1992 Rickard E. Faith (faith@cs.unc.edu)
+.\" May be distributed under the GNU General Public License
+.TH RESET 1 "October 1993" "util-linux" "User Commands"
+.SH NAME
+reset \- reset the terminal
+.SH SYNOPSIS
+.BR reset
+.SH DESCRIPTION
+.B reset
+calls
+.BR tput (1)
+with the
+.IR clear ,
+.IR rmacs ,
+.IR rmm ,
+.IR rmul ,
+.IR rs1 ,
+.IR rs2 ,
+and
+.I rs3
+arguments. This causes
+.B tput
+to send appropriate reset strings to the terminal based on information in
+.I /etc/termcap
+(for the GNU or BSD
+.BR tput )
+or in the terminfo database
+(for the
+.B ncurses
+.BR tput ).
+This sequence seems to be sufficient to reset the Linux VC's when they
+start printing "funny-looking" characters. For good measure,
+.BR stty (1)
+is called with the
+.I sane
+argument in an attempt to get cooked mode back.
+.SH "SEE ALSO"
+.BR clear (1),
+.BR stty (1),
+.BR tput (1)
+.SH AUTHOR
+Rik Faith (faith@cs.unc.edu)
+.SH AVAILABILITY
+The reset command is part of the util-linux package and is available from
+ftp://ftp.kernel.org/pub/linux/utils/util-linux/.
diff --git a/term-utils/script.1 b/term-utils/script.1
new file mode 100644
index 0000000..1e430d8
--- /dev/null
+++ b/term-utils/script.1
@@ -0,0 +1,156 @@
+.\" Copyright (c) 1980, 1990 Regents of the University of California.
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\" 3. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. Neither the name of the University nor the names of its contributors
+.\" may be used to endorse or promote products derived from this software
+.\" without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" @(#)script.1 6.5 (Berkeley) 7/27/91
+.\"
+.TH SCRIPT "1" "September 2011" "util-linux" "User Commands"
+.SH NAME
+script \- make typescript of terminal session
+.SH SYNOPSIS
+.B script
+[options] [file]
+.SH DESCRIPTION
+.B script
+makes a typescript of everything printed on your terminal. It is useful for
+students who need a hardcopy record of an interactive session as proof of an
+assignment, as the typescript file can be printed out later with
+.BR lpr (1).
+.PP
+If the argument
+.I file
+is given,
+.B script
+saves all dialogue in
+.IR file .
+If no file name is given, the typescript is saved in the file
+.IR typescript .
+.SH OPTIONS
+.TP
+\fB\-a\fR, \fB\-\-append\fR
+Append the output to
+.I file
+or
+.IR typescript ,
+retaining the prior contents.
+.TP
+\fB\-c\fR, \fB\-\-command\fR \fIcommand\fR
+Run the
+.I command
+rather than an interactive shell. This makes it easy for a script to capture
+the output of a program that behaves differently when its stdout is not a
+tty.
+.TP
+\fB\-e\fR, \fB\-\-return\fR
+Return the exit code of the child process. Uses the same format as bash
+termination on signal termination exit code is 128+n.
+.TP
+\fB\-f\fR, \fB\-\-flush\fR
+Flush output after each write. This is nice for telecooperation: one person
+does `mkfifo foo; script -f foo', and another can supervise real-time what is
+being done using `cat foo'.
+.TP
+\fB\-\-force\fR
+Allow the default output destination, i.e. the typescript file, to be a hard
+or symbolic link. The command will follow a symbolic link.
+.TP
+\fB\-q\fR, \fB\-\-quiet\fR
+Be quiet.
+.TP
+\fB\-t\fR, \fB\-\-timing\fR[=\fIfile\fR]
+Output timing data to standard error, or to
+.I file
+when given. This data 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. This information
+can be used to replay typescripts with realistic typing and output delays.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Output version information and exit.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Output help and exit.
+.SH NOTES
+The script ends when the forked shell exits (a
+.I control-D
+to exit
+the Bourne shell
+.RB ( sh (1)),
+and
+.IR exit ,
+.I logout
+or
+.I control-d
+(if
+.I ignoreeof
+is not set) for the
+C-shell,
+.BR csh (1)).
+.PP
+Certain interactive commands, such as
+.BR vi (1),
+create garbage in the typescript file.
+.B Script
+works best with commands that do not manipulate the screen, the results are
+meant to emulate a hardcopy terminal.
+.SH ENVIRONMENT
+The following environment variable is utilized by
+.BR script :
+.TP
+.B SHELL
+If the variable
+.I SHELL
+exists, the shell forked by
+.B script
+will be that shell. If
+.I SHELL
+is not set, the Bourne shell is assumed. (Most shells set this variable
+automatically).
+.SH SEE ALSO
+.BR csh (1)
+(for the
+.I history
+mechanism),
+.BR scriptreplay (1).
+.SH HISTORY
+The
+.B script
+command appeared in 3.0BSD.
+.SH BUGS
+.B Script
+places
+.B everything
+in the log file, including linefeeds and backspaces. This is not what the
+naive user expects.
+.SH AVAILABILITY
+The script command is part of the util-linux package and is available from
+.UR ftp://\:ftp.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/term-utils/script.c b/term-utils/script.c
new file mode 100644
index 0000000..ccd8873
--- /dev/null
+++ b/term-utils/script.c
@@ -0,0 +1,568 @@
+/*
+ * Copyright (c) 1980 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.
+ */
+
+/*
+ * 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL>
+ * - added Native Language Support
+ *
+ * 2000-07-30 Per Andreas Buer <per@linpro.no> - added "q"-option
+ */
+
+/*
+ * script
+ */
+#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 "closestream.h"
+#include "nls.h"
+#include "c.h"
+
+#if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H)
+# include <pty.h>
+#endif
+
+#ifdef HAVE_LIBUTEMPTER
+# include <utempter.h>
+#endif
+
+#define DEFAULT_OUTPUT "typescript"
+
+void finish(int);
+void done(void);
+void fail(void);
+void resize(int);
+void fixtty(void);
+void getmaster(void);
+void getslave(void);
+void doinput(void);
+void dooutput(FILE *timingfd);
+void doshell(void);
+
+char *shell;
+FILE *fscript;
+int master = -1;
+int slave;
+pid_t child;
+pid_t subchild;
+int childstatus;
+char *fname;
+
+struct termios tt;
+struct winsize win;
+int lb;
+int l;
+#if !HAVE_LIBUTIL || !HAVE_PTY_H
+char line[] = "/dev/ptyXX";
+#endif
+int aflg = 0;
+char *cflg = NULL;
+int eflg = 0;
+int fflg = 0;
+int qflg = 0;
+int tflg = 0;
+int forceflg = 0;
+
+int die;
+int resized;
+
+static void
+die_if_link(char *fn) {
+ struct stat s;
+
+ if (forceflg)
+ return;
+ if (lstat(fn, &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."), fn);
+}
+
+static void __attribute__((__noreturn__))
+usage(FILE *out)
+{
+ fputs(_("\nUsage:\n"), out);
+ fprintf(out,
+ _(" %s [options] [file]\n"), program_invocation_short_name);
+
+ fputs(_("\nOptions:\n"), out);
+ fputs(_(" -a, --append append the output\n"
+ " -c, --command <command> run command rather than interactive shell\n"
+ " -e, --return return exit code of the child process\n"
+ " -f, --flush run flush after each write\n"
+ " --force use output file even when it is a link\n"
+ " -q, --quiet be quiet\n"
+ " -t, --timing[=<file>] output timing data to stderr (or to FILE)\n"
+ " -V, --version output version information and exit\n"
+ " -h, --help display this help and exit\n\n"), out);
+
+ exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+/*
+ * 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.
+ */
+
+int
+main(int argc, char **argv) {
+ sigset_t block_mask, unblock_mask;
+ struct sigaction sa;
+ int ch;
+ FILE *timingfd = stderr;
+
+ enum { FORCE_OPTION = CHAR_MAX + 1 };
+
+ static const struct option longopts[] = {
+ { "append", no_argument, NULL, 'a' },
+ { "command", required_argument, NULL, 'c' },
+ { "return", no_argument, NULL, 'e' },
+ { "flush", no_argument, NULL, 'f' },
+ { "force", no_argument, NULL, FORCE_OPTION, },
+ { "quiet", no_argument, NULL, 'q' },
+ { "timing", optional_argument, NULL, 't' },
+ { "version", no_argument, NULL, 'V' },
+ { "help", no_argument, NULL, 'h' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ setlocale(LC_ALL, "");
+ setlocale(LC_NUMERIC, "C"); /* see comment above */
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ atexit(close_stdout);
+
+ while ((ch = getopt_long(argc, argv, "ac:efqt::Vh", longopts, NULL)) != -1)
+ switch(ch) {
+ case 'a':
+ aflg = 1;
+ break;
+ case 'c':
+ cflg = optarg;
+ break;
+ case 'e':
+ eflg = 1;
+ break;
+ case 'f':
+ fflg = 1;
+ break;
+ case FORCE_OPTION:
+ forceflg = 1;
+ break;
+ case 'q':
+ qflg = 1;
+ break;
+ case 't':
+ if (optarg)
+ if ((timingfd = fopen(optarg, "w")) == NULL)
+ err(EXIT_FAILURE, _("cannot open %s"), optarg);
+ tflg = 1;
+ break;
+ case 'V':
+ printf(_("%s from %s\n"), program_invocation_short_name,
+ PACKAGE_STRING);
+ exit(EXIT_SUCCESS);
+ break;
+ case 'h':
+ usage(stdout);
+ break;
+ case '?':
+ default:
+ usage(stderr);
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (argc > 0)
+ fname = argv[0];
+ else {
+ fname = DEFAULT_OUTPUT;
+ die_if_link(fname);
+ }
+ if ((fscript = fopen(fname, aflg ? "a" : "w")) == NULL) {
+ warn(_("cannot open %s"), fname);
+ fail();
+ }
+
+ shell = getenv("SHELL");
+ if (shell == NULL)
+ shell = _PATH_BSHELL;
+
+ getmaster();
+ if (!qflg)
+ printf(_("Script started, file is %s\n"), fname);
+ fixtty();
+
+#ifdef HAVE_LIBUTEMPTER
+ utempter_add_record(master, NULL);
+#endif
+ /* setup SIGCHLD handler */
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sa.sa_handler = finish;
+ sigaction(SIGCHLD, &sa, NULL);
+
+ /* init mask for SIGCHLD */
+ sigprocmask(SIG_SETMASK, NULL, &block_mask);
+ sigaddset(&block_mask, SIGCHLD);
+
+ sigprocmask(SIG_SETMASK, &block_mask, &unblock_mask);
+ child = fork();
+ sigprocmask(SIG_SETMASK, &unblock_mask, NULL);
+
+ if (child < 0) {
+ warn(_("fork failed"));
+ fail();
+ }
+ if (child == 0) {
+
+ sigprocmask(SIG_SETMASK, &block_mask, NULL);
+ subchild = child = fork();
+ sigprocmask(SIG_SETMASK, &unblock_mask, NULL);
+
+ if (child < 0) {
+ warn(_("fork failed"));
+ fail();
+ }
+ if (child)
+ dooutput(timingfd);
+ else
+ doshell();
+ } else {
+ sa.sa_handler = resize;
+ sigaction(SIGWINCH, &sa, NULL);
+ }
+ doinput();
+
+ if (close_stream(timingfd) != 0)
+ errx(EXIT_FAILURE, _("write error"));
+ return EXIT_SUCCESS;
+}
+
+void __attribute__((__noreturn__))
+doinput(void) {
+ ssize_t cc;
+ char ibuf[BUFSIZ];
+
+ if (close_stream(fscript) != 0)
+ errx(EXIT_FAILURE, _("write error"));
+
+ while (die == 0) {
+ if ((cc = read(STDIN_FILENO, ibuf, BUFSIZ)) > 0) {
+ ssize_t wrt = write(master, ibuf, cc);
+ if (wrt < 0) {
+ warn (_("write failed"));
+ fail();
+ }
+ }
+ else if (cc < 0 && errno == EINTR && resized)
+ resized = 0;
+ else
+ break;
+ }
+
+ done();
+}
+
+#include <sys/wait.h>
+
+void
+finish(int dummy __attribute__ ((__unused__))) {
+ int status;
+ pid_t pid;
+
+ while ((pid = wait3(&status, WNOHANG, 0)) > 0)
+ if (pid == child) {
+ childstatus = status;
+ die = 1;
+ }
+}
+
+void
+resize(int dummy __attribute__ ((__unused__))) {
+ resized = 1;
+ /* transmit window change information to the child */
+ ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&win);
+ ioctl(slave, TIOCSWINSZ, (char *)&win);
+}
+
+/*
+ * Stop extremely silly gcc complaint on %c:
+ * warning: `%c' yields only last 2 digits of year in some locales
+ */
+static void
+my_strftime(char *buf, size_t len, const char *fmt, const struct tm *tm) {
+ strftime(buf, len, fmt, tm);
+}
+
+void __attribute__((__noreturn__))
+dooutput(FILE *timingfd) {
+ ssize_t cc;
+ time_t tvec;
+ char obuf[BUFSIZ];
+ struct timeval tv;
+ double oldtime=time(NULL), newtime;
+ int flgs = 0;
+ ssize_t wrt;
+ ssize_t fwrt;
+
+ close(STDIN_FILENO);
+#ifdef HAVE_LIBUTIL
+ close(slave);
+#endif
+ tvec = time((time_t *)NULL);
+ my_strftime(obuf, sizeof obuf, "%c\n", localtime(&tvec));
+ fprintf(fscript, _("Script started on %s"), obuf);
+
+ do {
+ if (die && flgs == 0) {
+ /* ..child is dead, but it doesn't mean that there is
+ * nothing in buffers.
+ */
+ flgs = fcntl(master, F_GETFL, 0);
+ if (fcntl(master, F_SETFL, (flgs | O_NONBLOCK)) == -1)
+ break;
+ }
+ if (tflg)
+ gettimeofday(&tv, NULL);
+
+ errno = 0;
+ cc = read(master, obuf, sizeof (obuf));
+
+ if (die && errno == EINTR && cc <= 0)
+ /* read() has been interrupted by SIGCHLD, try it again
+ * with O_NONBLOCK
+ */
+ continue;
+ if (cc <= 0)
+ break;
+ if (tflg) {
+ newtime = tv.tv_sec + (double) tv.tv_usec / 1000000;
+ fprintf(timingfd, "%f %zd\n", newtime - oldtime, cc);
+ oldtime = newtime;
+ }
+ wrt = write(STDOUT_FILENO, obuf, cc);
+ if (wrt < 0) {
+ warn (_("write failed"));
+ fail();
+ }
+ fwrt = fwrite(obuf, 1, cc, fscript);
+ if (fwrt < cc) {
+ warn (_("cannot write script file"));
+ fail();
+ }
+ if (fflg)
+ fflush(fscript);
+ } while(1);
+
+ if (flgs)
+ fcntl(master, F_SETFL, flgs);
+ if (close_stream(timingfd) != 0)
+ errx(EXIT_FAILURE, _("write error"));
+ done();
+}
+
+void __attribute__((__noreturn__))
+doshell(void) {
+ char *shname;
+
+ getslave();
+ close(master);
+ if (close_stream(fscript) != 0)
+ errx(EXIT_FAILURE, _("write error"));
+ dup2(slave, STDIN_FILENO);
+ dup2(slave, STDOUT_FILENO);
+ dup2(slave, STDERR_FILENO);
+ close(slave);
+
+ master = -1;
+
+ shname = strrchr(shell, '/');
+ if (shname)
+ shname++;
+ else
+ shname = shell;
+
+ /*
+ * When invoked from within /etc/csh.login, script spawns a csh shell
+ * that spawns programs that cannot be killed with a SIGTERM. This is
+ * because csh has a documented behaviour wherein it disables all
+ * signals when processing the /etc/csh.* files.
+ *
+ * Let's restore the default behavior.
+ */
+ signal(SIGTERM, SIG_DFL);
+
+ if (cflg)
+ execl(shell, shname, "-c", cflg, NULL);
+ else
+ execl(shell, shname, "-i", NULL);
+
+ warn(_("failed to execute %s"), shell);
+ fail();
+}
+
+void
+fixtty(void) {
+ struct termios rtt;
+
+ rtt = tt;
+ cfmakeraw(&rtt);
+ rtt.c_lflag &= ~ECHO;
+ tcsetattr(STDIN_FILENO, TCSANOW, &rtt);
+}
+
+void __attribute__((__noreturn__))
+fail(void) {
+
+ kill(0, SIGTERM);
+ done();
+}
+
+void __attribute__((__noreturn__))
+done(void) {
+ time_t tvec;
+
+ if (subchild) {
+ if (!qflg) {
+ char buf[BUFSIZ];
+ tvec = time((time_t *)NULL);
+ my_strftime(buf, sizeof buf, "%c\n", localtime(&tvec));
+ fprintf(fscript, _("\nScript done on %s"), buf);
+ }
+ if (close_stream(fscript) != 0)
+ errx(EXIT_FAILURE, _("write error"));
+ close(master);
+
+ master = -1;
+ } else {
+ tcsetattr(STDIN_FILENO, TCSADRAIN, &tt);
+ if (!qflg)
+ printf(_("Script done, file is %s\n"), fname);
+#ifdef HAVE_LIBUTEMPTER
+ if (master >= 0)
+ utempter_remove_record(master);
+#endif
+ }
+
+ if(eflg) {
+ if (WIFSIGNALED(childstatus))
+ exit(WTERMSIG(childstatus) + 0x80);
+ else
+ exit(WEXITSTATUS(childstatus));
+ }
+ exit(EXIT_SUCCESS);
+}
+
+void
+getmaster(void) {
+#if defined(HAVE_LIBUTIL) && defined(HAVE_PTY_H)
+ tcgetattr(STDIN_FILENO, &tt);
+ ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&win);
+ if (openpty(&master, &slave, NULL, &tt, &win) < 0) {
+ warn(_("openpty failed"));
+ fail();
+ }
+#else
+ char *pty, *bank, *cp;
+ struct stat stb;
+
+ pty = &line[strlen("/dev/ptyp")];
+ for (bank = "pqrs"; *bank; bank++) {
+ line[strlen("/dev/pty")] = *bank;
+ *pty = '0';
+ if (stat(line, &stb) < 0)
+ break;
+ for (cp = "0123456789abcdef"; *cp; cp++) {
+ *pty = *cp;
+ master = open(line, O_RDWR);
+ if (master >= 0) {
+ char *tp = &line[strlen("/dev/")];
+ int ok;
+
+ /* verify slave side is usable */
+ *tp = 't';
+ ok = access(line, R_OK|W_OK) == 0;
+ *tp = 'p';
+ if (ok) {
+ tcgetattr(STDIN_FILENO, &tt);
+ ioctl(STDIN_FILENO, TIOCGWINSZ,
+ (char *)&win);
+ return;
+ }
+ close(master);
+ master = -1;
+ }
+ }
+ }
+ master = -1;
+ warn(_("out of pty's"));
+ fail();
+#endif /* not HAVE_LIBUTIL */
+}
+
+void
+getslave(void) {
+#ifndef HAVE_LIBUTIL
+ line[strlen("/dev/")] = 't';
+ slave = open(line, O_RDWR);
+ if (slave < 0) {
+ warn(_("cannot open %s"), line);
+ fail();
+ }
+ tcsetattr(slave, TCSANOW, &tt);
+ ioctl(slave, TIOCSWINSZ, (char *)&win);
+#endif
+ setsid();
+ ioctl(slave, TIOCSCTTY, 0);
+}
diff --git a/term-utils/scriptreplay.1 b/term-utils/scriptreplay.1
new file mode 100644
index 0000000..b4b34a6
--- /dev/null
+++ b/term-utils/scriptreplay.1
@@ -0,0 +1,107 @@
+.TH SCRIPTREPLAY 1 "September 2011" "util-linux" "User Commands"
+.SH "NAME"
+scriptreplay \- play back typescripts, using timing information
+.SH "SYNOPSIS"
+.B scriptreplay
+.RI [ options ]
+.RB [ \-t ]
+.I timingfile
+.RI [ typescript
+.RI [ divisor ]]
+.SH "DESCRIPTION"
+This program replays a typescript, using timing information to ensure that
+output happens at the same speed as it originally appeared when the script
+was recorded.
+.PP
+The replay simply displays the information again; the programs
+that were run when the typescript was being recorded are not run again.
+Since the same information is simply being displayed,
+.B scriptreplay
+is only guaranteed to work properly if run on the same type of
+terminal the typescript was recorded on. Otherwise, any escape characters
+in the typescript may be interpreted differently by the terminal to
+which
+.B scriptreplay
+is sending its output.
+.PP
+The timing information is what
+.BR script (1)
+outputs to standard error if it is
+run with the
+.B \-t
+parameter.
+.PP
+By default, the typescript to display is assumed to be named
+.BR typescript ,
+but other filenames may be specified, as the second parameter or with option
+.BR \-s .
+.PP
+If the third parameter is specified, it is used as a speed-up multiplier.
+For example, a speed-up of 2 makes
+.B scriptreplay
+go twice as fast, and a speed-up of 0.1 makes it go ten times slower
+than the original session.
+.SH OPTIONS
+The first three options will overide old-style arguments.
+.TP
+.BR \-t , " \-\-timing " \fIfile\fR
+File containing script timing output.
+.TP
+.BR \-s , " \-\-typescript " \fIfile\fR
+File containing the script terminal output.
+.TP
+.BR \-d , " \-\-divisor " \fInumber\fR
+Speed up the replay displaying this
+.I number
+of times. The argument is a floating point number. It's called divisor
+because it divides the timings by this factor.
+.TP
+.BR \-V , " \-\-version"
+Display version information and exit.
+.TP
+.BR \-h , " \-\-help"
+Display a help message and exit.
+.SH "EXAMPLE"
+.nf
+% script --timing=file.tm script.out
+Script started, file is script.out
+% ls
+<etc, etc>
+% exit
+Script done, file is script.out
+% scriptreplay --timing file.tm --typescript script.out
+.nf
+.SH "SEE ALSO"
+.BR script (1)
+.SH "COPYRIGHT"
+Copyright \(co 2008 James Youngman
+.br
+Copyright \(co 2008 Karel Zak
+.PP
+This is free software; see the source for copying conditions. There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR
+PURPOSE.
+.PP
+Released under the GNU General Public License version 2 or later.
+.SH "AUTHOR"
+The original
+.B scriptreplay
+program was written by
+.MT joey@\:kitenet.net
+Joey Hess
+.ME .
+The program was re-written in C by
+.MT jay@\:gnu.org
+James Youngman
+.ME
+and
+.MT kzak@\:redhat.com
+Karel Zak
+.ME .
+.SH AVAILABILITY
+The
+.B scriptreplay
+command is part of the util-linux package and is available from
+.UR ftp://\:ftp.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/term-utils/scriptreplay.c b/term-utils/scriptreplay.c
new file mode 100644
index 0000000..46468be
--- /dev/null
+++ b/term-utils/scriptreplay.c
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2008, 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 "closestream.h"
+#include "nls.h"
+#include "c.h"
+
+#define SCRIPT_MIN_DELAY 0.0001 /* from original sripreplay.pl */
+
+static void __attribute__((__noreturn__))
+usage(FILE *out)
+{
+ fputs(_("\nUsage:\n"), out);
+ fprintf(out,
+ _(" %s [-t] timingfile [typescript] [divisor]\n"),
+ program_invocation_short_name);
+
+ fputs(_("\nOptions:\n"), out);
+ fputs(_(" -t, --timing <file> script timing output file\n"
+ " -s, --typescript <file> script terminal session output file\n"
+ " -d, --divisor <num> speed up or slow down execution with time divisor\n"
+ " -V, --version output version information and exit\n"
+ " -h, --help display this help and exit\n\n"), out);
+
+ exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+static double
+getnum(const char *s)
+{
+ double d;
+ char *end;
+
+ errno = 0;
+ d = strtod(s, &end);
+
+ if (end && *end != '\0')
+ errx(EXIT_FAILURE, _("expected a number, but got '%s'"), s);
+
+ if ((d == HUGE_VAL || d == -HUGE_VAL) && ERANGE == errno)
+ err(EXIT_FAILURE, _("divisor '%s'"), s);
+
+ if (!(d==d)) { /* did they specify "nan"? */
+ errno = EINVAL;
+ err(EXIT_FAILURE, _("divisor '%s'"), s);
+ }
+ return d;
+}
+
+static void
+delay_for(double delay)
+{
+#ifdef HAVE_NANOSLEEP
+ struct timespec ts, remainder;
+ ts.tv_sec = (time_t) delay;
+ ts.tv_nsec = (delay - ts.tv_sec) * 1.0e9;
+
+ while (-1 == nanosleep(&ts, &remainder)) {
+ if (EINTR == errno)
+ ts = remainder;
+ else
+ break;
+ }
+#else
+ struct timeval tv;
+ tv.tv_sec = (long) delay;
+ tv.tv_usec = (delay - tv.tv_sec) * 1.0e6;
+ select(0, NULL, NULL, NULL, &tv);
+#endif
+}
+
+static void
+emit(FILE *fd, const char *filename, size_t ct)
+{
+ char buf[BUFSIZ];
+
+ while(ct) {
+ size_t len, cc;
+
+ cc = ct > sizeof(buf) ? sizeof(buf) : ct;
+ len = fread(buf, 1, cc, fd);
+
+ if (!len)
+ break;
+
+ ct -= len;
+ cc = write(STDOUT_FILENO, buf, len);
+ if (cc != len)
+ err(EXIT_FAILURE, _("write to stdout failed"));
+ }
+
+ if (!ct)
+ return;
+ if (feof(fd))
+ errx(EXIT_FAILURE, _("unexpected end of file on %s"), filename);
+
+ err(EXIT_FAILURE, _("failed to read typescript file %s"), filename);
+}
+
+
+int
+main(int argc, char *argv[])
+{
+ FILE *tfile, *sfile;
+ const char *sname = NULL, *tname = NULL;
+ double divi = 1;
+ int c, diviopt = FALSE, idx;
+ unsigned long line;
+ size_t oldblk = 0;
+ char ch;
+
+ static const struct option longopts[] = {
+ { "timing", required_argument, 0, 't' },
+ { "typescript", required_argument, 0, 's' },
+ { "divisor", required_argument, 0, 'd' },
+ { "version", no_argument, 0, 'V' },
+ { "help", no_argument, 0, 'h' },
+ { NULL, 0, 0, 0 }
+ };
+
+ /* 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);
+ atexit(close_stdout);
+
+ while ((ch = getopt_long(argc, argv, "t:s:d:Vh", longopts, NULL)) != -1)
+ switch(ch) {
+ case 't':
+ tname = optarg;
+ break;
+ case 's':
+ sname = optarg;
+ break;
+ case 'd':
+ diviopt = TRUE;
+ divi = getnum(optarg);
+ break;
+ case 'V':
+ printf(_("%s from %s\n"), program_invocation_short_name,
+ PACKAGE_STRING);
+ exit(EXIT_SUCCESS);
+ case 'h':
+ usage(stdout);
+ default:
+ usage(stderr);
+ }
+ argc -= optind;
+ argv += optind;
+ idx = 0;
+
+ if ((argc < 1 && !tname) || argc > 3) {
+ warnx(_("wrong number of arguments"));
+ usage(stderr);
+ }
+ if (!tname)
+ tname = argv[idx++];
+ if (!sname)
+ sname = idx < argc ? argv[idx++] : "typescript";
+ if (!diviopt)
+ divi = idx < argc ? getnum(argv[idx]) : 1;
+
+ tfile = fopen(tname, "r");
+ if (!tfile)
+ err(EXIT_FAILURE, _("cannot open %s"), tname);
+ sfile = fopen(sname, "r");
+ if (!sfile)
+ err(EXIT_FAILURE, _("cannot open %s"), sname);
+
+ /* ignore the first typescript line */
+ while((c = fgetc(sfile)) != EOF && c != '\n');
+
+ for(line = 0; ; line++) {
+ double delay;
+ size_t blk;
+ char nl;
+ if (fscanf(tfile, "%lf %zu%c\n", &delay, &blk, &nl) != 3 ||
+ nl != '\n') {
+ if (feof(tfile))
+ break;
+ if (ferror(tfile))
+ err(EXIT_FAILURE,
+ _("failed to read timing file %s"), tname);
+ errx(EXIT_FAILURE,
+ _("timings file %s: %lu: unexpected format"),
+ tname, line);
+ }
+ delay /= divi;
+
+ if (delay > SCRIPT_MIN_DELAY)
+ delay_for(delay);
+
+ if (oldblk)
+ emit(sfile, sname, oldblk);
+ oldblk = blk;
+ }
+
+ fclose(sfile);
+ fclose(tfile);
+ printf("\n");
+ exit(EXIT_SUCCESS);
+}
diff --git a/term-utils/setterm.1 b/term-utils/setterm.1
new file mode 100644
index 0000000..cde4114
--- /dev/null
+++ b/term-utils/setterm.1
@@ -0,0 +1,196 @@
+\" Copyright 1990 Gordon Irlam (gordoni@cs.ua.oz.au)
+.\" Copyright 1992 Rickard E. Faith (faith@cs.unc.edu)
+.\" Most of this was copied from the source code.
+.\" Do not restrict distribution.
+.\" May be distributed under the GNU General Public License
+.\"
+.\" Most options documented by Colin Watson (cjw44@cam.ac.uk)
+.\" Undocumented: -snow, -softscroll, -standout; these are
+.\" commented out in the source
+.\"
+.TH SETTERM 1 "January 2000" "util-linux" "User Commands"
+.SH NAME
+setterm \- set terminal attributes
+.SH SYNOPSIS
+.B setterm
+.RI [ options ]
+.SH DESCRIPTION
+.B setterm
+writes to standard output a character string that will invoke the
+specified terminal capabilities. Where possible
+.I terminfo
+is consulted to find the string to use. Some options however (marked
+"virtual consoles only" below) do not correspond to a
+.BR terminfo (5)
+capability. In this case, if the terminal type is "con" or "linux" the
+string that invokes the specified capabilities on the PC Minix virtual
+console driver is output. Options that are not implemented by the terminal
+are ignored.
+.SH OPTIONS
+For boolean options (\fBon\fP or \fBoff\fP), the default is \fBon\fP.
+.P
+For conciseness, an \fI8-color\fP below is \fBblack\fP, \fBred\fP,
+\fBgreen\fP, \fByellow\fP, \fBblue\fP, \fBmagenta\fP, \fBcyan\fP, or
+\fBwhite\fP.
+.P
+A \fI16-color\fP is an \fI8-color\fP, \fBgrey\fP, or \fBbright\fP followed
+by \fBred\fP, \fBgreen\fP, \fByellow\fP, \fBblue\fP, \fBmagenta\fP,
+\fBcyan\fP, or \fBwhite\fP.
+.P
+The various color options may be set independently, at least at virtual
+consoles, though the results of setting multiple modes (for example,
+.BR \-underline " and " \-half-bright )
+are hardware-dependent.
+.TP
+.BR \-appcursorkeys " [" on | off "] (virtual consoles only)"
+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.
+.TP
+.BR \-append " [\fI1-NR_CONS\fP]"
+Like \fB\-dump\fP, but appends to the snapshot file instead of overwriting
+it. Only works if no \fB\-dump\fP options are given.
+.TP
+\fB\-background\fP \fI8-color\fP|\fBdefault\fP (virtual consoles only)
+Sets the background text color.
+.TP
+.BR \-blank " [\fI0-60\fP|\fBforce\fP|\fBpoke\fP] (virtual consoles only)"
+Sets the interval of inactivity, in minutes, after which the screen will be
+automatically blanked (using APM if available). Without an argument, gets the
+blank status (returns which vt was blanked or zero for unblanked vt).
+
+The
+.B force
+option keeps screen blank even if a key is pressed.
+
+The
+.B poke
+option unblank the screen.
+.TP
+.BR \-bfreq " [\fIfreqnumber\fP]"
+Sets the bell frequency in Hz. Without an argument, defaults to 0.
+.TP
+.BR \-blength " [\fI0-2000\fP]"
+Sets the bell duration in milliseconds. Without an argument, defaults to 0.
+.TP
+.BR \-blink " [" on | off ]
+Turns blink mode on or off. Except at a virtual console, \fB\-blink off\fP
+turns off all attributes (bold, half-brightness, blink, reverse).
+.TP
+.BR \-bold " [" on | off ]
+Turns bold (extra bright) mode on or off. Except at a virtual console,
+\fB\-bold off\fP turns off all attributes (bold, half-brightness, blink,
+reverse).
+.TP
+.BR \-clear " [" all ]
+Clears the screen and "homes" the cursor, as
+.BR clear (1).
+.TP
+.B \-clear rest
+Clears from the current cursor position to the end of the screen.
+.TP
+.BR \-clrtabs " [\fItab1 tab2 tab3\fP ...] (virtual consoles only)"
+Clears tab stops from the given horizontal cursor positions, in the range
+1-160. Without arguments, clears all tab stops.
+.TP
+.BR \-cursor " [" on | off ]
+Turns the terminal's cursor on or off.
+.TP
+.B \-default
+Sets the terminal's rendering options to the default values.
+.TP
+.BR \-dump " [\fI1-NR_CONS\fP]"
+Writes a snapshot of the given virtual console (with attributes) to the file
+specified in the \fB\-file\fP option, overwriting its contents; the default
+is screen.dump. Without an argument, dumps the current virtual console.
+Overrides \fB\-append\fP.
+.TP
+.BR \-append " [\fI1-NR_CONS\fP]"
+Like \fB\-dump\fP, but appends to the snapshot file instead of overwriting
+it. Only works if no \fB\-dump\fP options are given.
+.TP
+.BI \-file " dumpfilename"
+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
+screen.dump in the current directory. A path name that exceeds system
+maximum will be truncated, see PATH_MAX from linux/limits.h for the value.
+.TP
+.BR \-msg " [" on | off "] (virtual consoles only)"
+Enables or disables the sending of kernel \fBprintk()\fP messages to the
+console.
+.TP
+.BR \-msglevel " \fI1-8\fP (virtual consoles only)"
+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 0 has the same effect as \fB\-msg on\fP and a logging level of 8
+will print all kernel messages.
+.BR klogd (8)
+may be a more convenient interface to the logging of kernel messages.
+.TP
+.BR \-powerdown " [\fI0-60\fP]"
+Sets the VESA powerdown interval in minutes. Without an argument, 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.
+.TP
+.BR \-underline " [" on | off ]
+Turns underline mode on or off (see \fB\-ulcolor\fP).
+.TP
+.BR \-powersave " [" off "]"
+Turns off monitor VESA powersaving features.
+.TP
+.BR "\-powersave on" | vsync
+Puts the monitor into VESA vsync suspend mode.
+.TP
+.B \-powersave powerdown
+Puts the monitor into VESA powerdown mode.
+.TP
+.B \-powersave hsync
+Puts the monitor into VESA hsync suspend mode.
+.TP
+.BR \-regtabs " [\fI1-160\fP] (virtual consoles only)"
+Clears all tab stops, then sets a regular tab stop pattern, with one tab
+every specified number of positions. Without an argument, defaults to 8.
+.TP
+.BR \-repeat " [" on | off "] (virtual consoles only)"
+Turns keyboard repeat on or off.
+.TP
+.B \-reset
+Displays the terminal reset string, which typically resets the terminal to
+its power on state.
+.TP
+.BR \-reverse " [" on | off ]
+Turns reverse video mode on or off. Except at a virtual console,
+\fB\-reverse off\fP turns off all attributes (bold, half-brightness, blink,
+reverse).
+.TP
+.BR \-store " (virtual consoles only)"
+Stores the terminal's current rendering options (foreground and
+background colors) as the values to be used at reset-to-default.
+.TP
+.BR \-tabs " [\fItab1 tab2 tab3\fP ...] (virtual consoles only)"
+Sets tab stops at the given horizontal cursor positions, in the range 1-160.
+Without arguments, shows the current tab stop settings.
+.TP
+.BR \-term " terminal_name"
+Overrides the TERM environment variable.
+.TP
+\fB\-ulcolor\fP \fI16-color\fP (virtual consoles only)
+Sets the color for underlined characters.
+.TP
+.BR \-version
+Output version information and exit.
+.TP
+.BR \-help
+Output help screen and exit.
+.SH "SEE ALSO"
+.BR tput (1),
+.BR stty (1),
+.BR terminfo (5),
+.BR tty (4)
+.SH BUGS
+Differences between the Minix and Linux versions are not documented.
+.SH AVAILABILITY
+The setterm command is part of the util-linux package and is available from
+ftp://ftp.kernel.org/pub/linux/utils/util-linux/.
diff --git a/term-utils/setterm.c b/term-utils/setterm.c
new file mode 100644
index 0000000..a2cf93b
--- /dev/null
+++ b/term-utils/setterm.c
@@ -0,0 +1,1290 @@
+/* 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
+ *
+ *
+ * Syntax:
+ *
+ * setterm
+ * [ -term terminal_name ]
+ * [ -reset ]
+ * [ -initialize ]
+ * [ -cursor [on|off] ]
+ * [ -repeat [on|off] ]
+ * [ -appcursorkeys [on|off] ]
+ * [ -linewrap [on|off] ]
+ * [ -snow [on|off] ]
+ * [ -softscroll [on|off] ]
+ * [ -defaults ]
+ * [ -foreground black|red|green|yellow|blue|magenta|cyan|white|default ]
+ * [ -background black|red|green|yellow|blue|magenta|cyan|white|default ]
+ * [ -ulcolor black|grey|red|green|yellow|blue|magenta|cyan|white ]
+ * [ -ulcolor bright red|green|yellow|blue|magenta|cyan|white ]
+ * [ -hbcolor black|grey|red|green|yellow|blue|magenta|cyan|white ]
+ * [ -hbcolor bright red|green|yellow|blue|magenta|cyan|white ]
+ * [ -inversescreen [on|off] ]
+ * [ -bold [on|off] ]
+ * [ -half-bright [on|off] ]
+ * [ -blink [on|off] ]
+ * [ -reverse [on|off] ]
+ * [ -underline [on|off] ]
+ * [ -store ]
+ * [ -clear [ all|rest ] ]
+ * [ -tabs [tab1 tab2 tab3 ... ] ] (tabn = 1-160)
+ * [ -clrtabs [ tab1 tab2 tab3 ... ] (tabn = 1-160)
+ * [ -regtabs [1-160] ]
+ * [ -blank [0-60|force|poke|] ]
+ * [ -dump [1-NR_CONS ] ]
+ * [ -append [1-NR_CONS ] ]
+ * [ -file dumpfilename ]
+ * [ -standout [attr] ]
+ * [ -msg [on|off] ]
+ * [ -msglevel [0-8] ]
+ * [ -powersave [on|vsync|hsync|powerdown|off] ]
+ * [ -powerdown [0-60] ]
+ * [ -blength [0-2000] ]
+ * [ -bfreq freq ]
+ * [ -version ]
+ * [ -help ]
+ *
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <termios.h>
+#include <string.h>
+#include <fcntl.h>
+#ifndef NCURSES_CONST
+#define NCURSES_CONST const /* define before including term.h */
+#endif
+#include <term.h>
+
+#ifdef HAVE_NCURSES_H
+#include <ncurses.h>
+#elif defined(HAVE_NCURSES_NCURSES_H)
+#include <ncurses/ncurses.h>
+#endif
+
+#include <sys/param.h> /* for MAXPATHLEN */
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#ifdef HAVE_LINUX_TIOCL_H
+#include <linux/tiocl.h>
+#endif
+
+#include "c.h"
+#include "xalloc.h"
+#include "nls.h"
+#include "closestream.h"
+
+#if __GNU_LIBRARY__ < 5
+#ifndef __alpha__
+# include <linux/unistd.h>
+#define __NR_klogctl __NR_syslog
+_syscall3(int, klogctl, int, type, char*, buf, int, len);
+#else /* __alpha__ */
+#define klogctl syslog
+#endif
+#endif
+extern int klogctl(int type, char *buf, int len);
+
+/* Constants. */
+
+/* Non-standard return values. */
+#define EXIT_DUMPFILE -1
+
+/* Keyboard types. */
+#define PC 0
+#define OLIVETTI 1
+#define DUTCH 2
+#define EXTENDED 3
+
+/* Colors. */
+#define BLACK 0
+#define RED 1
+#define GREEN 2
+#define YELLOW 3
+#define BLUE 4
+#define MAGENTA 5
+#define CYAN 6
+#define WHITE 7
+#define GREY 8
+#define DEFAULT 9
+
+/* Blank commands */
+#define BLANKSCREEN -1
+#define UNBLANKSCREEN -2
+#define BLANKEDSCREEN -3
+
+/* <linux/tiocl.h> fallback */
+#ifndef TIOCL_BLANKSCREEN
+# define TIOCL_UNBLANKSCREEN 4 /* unblank screen */
+# define TIOCL_SETVESABLANK 10 /* set vesa blanking mode */
+# define TIOCL_BLANKSCREEN 14 /* keep screen blank even if a key is pressed */
+# define TIOCL_BLANKEDSCREEN 15 /* return which vt was blanked */
+#endif
+
+/* Control sequences. */
+#define ESC "\033"
+#define DCS "\033P"
+#define ST "\033\\"
+
+/* Static variables. */
+
+/* Option flags. Set if the option is to be invoked. */
+int opt_term, opt_reset, opt_initialize, opt_cursor;
+int opt_linewrap, opt_snow, opt_softscroll, opt_default, opt_foreground;
+int opt_background, opt_bold, opt_blink, opt_reverse, opt_underline;
+int opt_store, opt_clear, opt_blank, opt_snap, opt_snapfile, opt_standout;
+int opt_append, opt_ulcolor, opt_hbcolor, opt_halfbright, opt_repeat;
+int opt_tabs, opt_clrtabs, opt_regtabs, opt_appcursorkeys, opt_inversescreen;
+int opt_msg, opt_msglevel, opt_powersave, opt_powerdown;
+int opt_blength, opt_bfreq;
+
+/* Option controls. The variable names have been contracted to ensure
+ * uniqueness.
+ */
+char *opt_te_terminal_name; /* Terminal name. */
+int opt_cu_on, opt_li_on, opt_sn_on, opt_so_on, opt_bo_on, opt_hb_on, opt_bl_on;
+int opt_re_on, opt_un_on, opt_rep_on, opt_appck_on, opt_invsc_on;
+int opt_msg_on; /* Boolean switches. */
+int opt_ke_type; /* Keyboard type. */
+int opt_fo_color, opt_ba_color; /* Colors. */
+int opt_ul_color, opt_hb_color;
+int opt_cl_all; /* Clear all or rest. */
+int opt_bl_min; /* Blank screen. */
+int opt_blength_l;
+int opt_bfreq_f;
+int opt_sn_num; /* Snap screen. */
+int opt_st_attr;
+int opt_rt_len; /* regular tab length */
+int opt_tb_array[161]; /* Array for tab list */
+int opt_msglevel_num;
+int opt_ps_mode, opt_pd_min; /* powersave mode/powerdown time */
+
+char opt_sn_name[PATH_MAX + 1] = "screen.dump";
+
+static void screendump(int vcnum, FILE *F);
+
+/* Command line parsing routines.
+ *
+ * Note that it is an error for a given option to be invoked more than once.
+ */
+
+static void
+parse_term(int argc, char **argv, int *option, char **ttyname, int *bad_arg) {
+ /* argc: Number of arguments for this option. */
+ /* argv: Arguments for this option. */
+ /* option: Term flag to set. */
+ /* ttyname: Terminal name to set. */
+ /* bad_arg: Set to true if an error is detected. */
+
+/* Parse a -term specification. */
+
+ if (argc != 1 || *option)
+ *bad_arg = TRUE;
+ *option = TRUE;
+ if (argc == 1)
+ *ttyname = argv[0];
+}
+
+static void
+parse_none(int argc, char **argv __attribute__ ((__unused__)), int *option, int *bad_arg) {
+ /* argc: Number of arguments for this option. */
+ /* argv: Arguments for this option. */
+ /* option: Term flag to set. */
+ /* bad_arg: Set to true if an error is detected. */
+
+/* Parse a parameterless specification. */
+
+ if (argc != 0 || *option)
+ *bad_arg = TRUE;
+ *option = TRUE;
+}
+
+static void
+parse_switch(int argc, char **argv, int *option, int *opt_on, int *bad_arg) {
+ /* argc: Number of arguments for this option. */
+ /* argv: Arguments for this option. */
+ /* option: Option flag to set. */
+ /* opt_on: Boolean option switch to set or reset. */
+ /* bad_arg: Set to true if an error is detected. */
+
+/* Parse a boolean (on/off) specification. */
+
+ if (argc > 1 || *option)
+ *bad_arg = TRUE;
+ *option = TRUE;
+ if (argc == 1) {
+ if (strcmp(argv[0], "on") == 0)
+ *opt_on = TRUE;
+ else if (strcmp(argv[0], "off") == 0)
+ *opt_on = FALSE;
+ else
+ *bad_arg = TRUE;
+ } else {
+ *opt_on = TRUE;
+ }
+}
+
+static void
+par_color(int argc, char **argv, int *option, int *opt_color, int *bad_arg) {
+ /* argc: Number of arguments for this option. */
+ /* argv: Arguments for this option. */
+ /* option: Color flag to set. */
+ /* opt_color: Color to set. */
+ /* bad_arg: Set to true if an error is detected. */
+
+/* Parse a -foreground or -background specification. */
+
+ if (argc != 1 || *option)
+ *bad_arg = TRUE;
+ *option = TRUE;
+ if (argc == 1) {
+ if (strcmp(argv[0], "black") == 0)
+ *opt_color = BLACK;
+ else if (strcmp(argv[0], "red") == 0)
+ *opt_color = RED;
+ else if (strcmp(argv[0], "green") == 0)
+ *opt_color = GREEN;
+ else if (strcmp(argv[0], "yellow") == 0)
+ *opt_color = YELLOW;
+ else if (strcmp(argv[0], "blue") == 0)
+ *opt_color = BLUE;
+ else if (strcmp(argv[0], "magenta") == 0)
+ *opt_color = MAGENTA;
+ else if (strcmp(argv[0], "cyan") == 0)
+ *opt_color = CYAN;
+ else if (strcmp(argv[0], "white") == 0)
+ *opt_color = WHITE;
+ else if (strcmp(argv[0], "default") == 0)
+ *opt_color = DEFAULT;
+ else if (isdigit(argv[0][0]))
+ *opt_color = atoi(argv[0]);
+ else
+ *bad_arg = TRUE;
+
+ if(*opt_color < 0 || *opt_color > 9 || *opt_color == 8)
+ *bad_arg = TRUE;
+ }
+}
+
+static void
+par_color2(int argc, char **argv, int *option, int *opt_color, int *bad_arg) {
+ /* argc: Number of arguments for this option. */
+ /* argv: Arguments for this option. */
+ /* option: Color flag to set. */
+ /* opt_color: Color to set. */
+ /* bad_arg: Set to true if an error is detected. */
+
+/* Parse a -ulcolor or -hbcolor specification. */
+
+ if (!argc || argc > 2 || *option)
+ *bad_arg = TRUE;
+ *option = TRUE;
+ *opt_color = 0;
+ if (argc == 2) {
+ if (strcmp(argv[0], "bright") == 0)
+ *opt_color = 8;
+ else {
+ *bad_arg = TRUE;
+ return;
+ }
+ }
+ if (argc) {
+ if (strcmp(argv[argc-1], "black") == 0) {
+ if(*opt_color)
+ *bad_arg = TRUE;
+ else
+ *opt_color = BLACK;
+ } else if (strcmp(argv[argc-1], "grey") == 0) {
+ if(*opt_color)
+ *bad_arg = TRUE;
+ else
+ *opt_color = GREY;
+ } else if (strcmp(argv[argc-1], "red") == 0)
+ *opt_color |= RED;
+ else if (strcmp(argv[argc-1], "green") == 0)
+ *opt_color |= GREEN;
+ else if (strcmp(argv[argc-1], "yellow") == 0)
+ *opt_color |= YELLOW;
+ else if (strcmp(argv[argc-1], "blue") == 0)
+ *opt_color |= BLUE;
+ else if (strcmp(argv[argc-1], "magenta") == 0)
+ *opt_color |= MAGENTA;
+ else if (strcmp(argv[argc-1], "cyan") == 0)
+ *opt_color |= CYAN;
+ else if (strcmp(argv[argc-1], "white") == 0)
+ *opt_color |= WHITE;
+ else if (isdigit(argv[argc-1][0]))
+ *opt_color = atoi(argv[argc-1]);
+ else
+ *bad_arg = TRUE;
+ if(*opt_color < 0 || *opt_color > 15)
+ *bad_arg = TRUE;
+ }
+}
+
+static void
+parse_clear(int argc, char **argv, int *option, int *opt_all, int *bad_arg) {
+ /* argc: Number of arguments for this option. */
+ /* argv: Arguments for this option. */
+ /* option: Clear flag to set. */
+ /* opt_all: Clear all switch to set or reset. */
+ /* bad_arg: Set to true if an error is detected. */
+
+/* Parse a -clear specification. */
+
+ if (argc > 1 || *option)
+ *bad_arg = TRUE;
+ *option = TRUE;
+ if (argc == 1) {
+ if (strcmp(argv[0], "all") == 0)
+ *opt_all = TRUE;
+ else if (strcmp(argv[0], "rest") == 0)
+ *opt_all = FALSE;
+ else
+ *bad_arg = TRUE;
+ } else {
+ *opt_all = TRUE;
+ }
+}
+
+static void
+parse_blank(int argc, char **argv, int *option, int *opt_all, int *bad_arg) {
+ /* argc: Number of arguments for this option. */
+ /* argv: Arguments for this option. */
+ /* option: Clear flag to set. */
+ /* opt_all: Clear all switch to set or reset. */
+ /* bad_arg: Set to true if an error is detected. */
+
+/* Parse a -blank specification. */
+
+ if (argc > 1 || *option)
+ *bad_arg = TRUE;
+ *option = TRUE;
+ if (argc == 1) {
+ if (!strcmp(argv[0], "force"))
+ *opt_all = BLANKSCREEN;
+ else if (!strcmp(argv[0], "poke"))
+ *opt_all = UNBLANKSCREEN;
+ else {
+ *opt_all = atoi(argv[0]);
+ if ((*opt_all > 60) || (*opt_all < 0))
+ *bad_arg = TRUE;
+ }
+ } else {
+ *opt_all = BLANKEDSCREEN;
+ }
+}
+
+static void
+parse_powersave(int argc, char **argv, int *option, int *opt_mode, int *bad_arg) {
+ /* argc: Number of arguments for this option. */
+ /* argv: Arguments for this option. */
+ /* option: powersave flag to set. */
+ /* opt_mode: Powersaving mode, defined in vesa_blank.c */
+ /* bad_arg: Set to true if an error is detected. */
+
+/* Parse a -powersave mode specification. */
+
+ if (argc > 1 || *option)
+ *bad_arg = TRUE;
+ *option = TRUE;
+ if (argc == 1) {
+ if (strcmp(argv[0], "on") == 0)
+ *opt_mode = 1;
+ else if (strcmp(argv[0], "vsync") == 0)
+ *opt_mode = 1;
+ else if (strcmp(argv[0], "hsync") == 0)
+ *opt_mode = 2;
+ else if (strcmp(argv[0], "powerdown") == 0)
+ *opt_mode = 3;
+ else if (strcmp(argv[0], "off") == 0)
+ *opt_mode = 0;
+ else
+ *bad_arg = TRUE;
+ } else {
+ *opt_mode = 0;
+ }
+}
+
+#if 0
+static void
+parse_standout(int argc, char *argv, int *option, int *opt_all, int *bad_arg) {
+ /* argc: Number of arguments for this option. */
+ /* argv: Arguments for this option. */
+ /* option: Clear flag to set. */
+ /* opt_all: Clear all switch to set or reset. */
+ /* bad_arg: Set to true if an error is detected. */
+
+/* Parse a -standout specification. */
+
+ if (argc > 1 || *option)
+ *bad_arg = TRUE;
+ *option = TRUE;
+ if (argc == 1)
+ *opt_all = atoi(argv[0]);
+ else
+ *opt_all = -1;
+}
+#endif
+
+static void
+parse_msglevel(int argc, char **argv, int *option, int *opt_all, int *bad_arg) {
+ /* argc: Number of arguments for this option. */
+ /* argv: Arguments for this option. */
+ /* option: Clear flag to set. */
+ /* opt_all: Clear all switch to set or reset. */
+ /* bad_arg: Set to true if an error is detected. */
+
+ if (argc > 1 || *option)
+ *bad_arg = TRUE;
+ *option = TRUE;
+ if (argc == 1) {
+ *opt_all = atoi(argv[0]);
+ if (*opt_all < 0 || *opt_all > 8)
+ *bad_arg = TRUE;
+ } else {
+ *opt_all = -1;
+ }
+}
+
+static void
+parse_snap(int argc, char **argv, int *option, int *opt_all, int *bad_arg) {
+ /* argc: Number of arguments for this option. */
+ /* argv: Arguments for this option. */
+ /* option: Clear flag to set. */
+ /* opt_all: Clear all switch to set or reset. */
+ /* bad_arg: Set to true if an error is detected. */
+
+/* Parse a -dump or -append specification. */
+
+ if (argc > 1 || *option)
+ *bad_arg = TRUE;
+ *option = TRUE;
+ if (argc == 1) {
+ *opt_all = atoi(argv[0]);
+ if ((*opt_all <= 0))
+ *bad_arg = TRUE;
+ } else {
+ *opt_all = 0;
+ }
+}
+
+static void
+parse_snapfile(int argc, char **argv, int *option, int *opt_all, int *bad_arg) {
+ /* argc: Number of arguments for this option. */
+ /* argv: Arguments for this option. */
+ /* option: Clear flag to set. */
+ /* opt_all: Clear all switch to set or reset. */
+ /* bad_arg: Set to true if an error is detected. */
+
+/* Parse a -file specification. */
+
+ if (argc != 1 || *option)
+ *bad_arg = TRUE;
+ *option = TRUE;
+ memset(opt_all, 0, PATH_MAX + 1);
+ if (argc == 1)
+ strncpy((char *)opt_all, argv[0], PATH_MAX);
+}
+
+static void
+parse_tabs(int argc, char **argv, int *option, int *tab_array, int *bad_arg) {
+ /* argc: Number of arguments for this option. */
+ /* argv: Arguments for this option. */
+ /* option: Clear flag to set. */
+ /* tab_array: Array of tabs */
+ /* bad_arg: Set to true if an error is detected. */
+
+ if (*option || argc > 160)
+ *bad_arg = TRUE;
+ *option = TRUE;
+ tab_array[argc] = -1;
+ while(argc--) {
+ tab_array[argc] = atoi(argv[argc]);
+ if(tab_array[argc] < 1 || tab_array[argc] > 160) {
+ *bad_arg = TRUE;
+ return;
+ }
+ }
+}
+
+static void
+parse_clrtabs(int argc, char **argv, int *option, int *tab_array, int *bad_arg) {
+ /* argc: Number of arguments for this option. */
+ /* argv: Arguments for this option. */
+ /* option: Clear flag to set. */
+ /* tab_array: Array of tabs */
+ /* bad_arg: Set to true if an error is detected. */
+
+ if (*option || argc > 160)
+ *bad_arg = TRUE;
+ *option = TRUE;
+ if(argc == 0) {
+ tab_array[0] = -1;
+ return;
+ }
+ tab_array[argc] = -1;
+ while(argc--) {
+ tab_array[argc] = atoi(argv[argc]);
+ if(tab_array[argc] < 1 || tab_array[argc] > 160) {
+ *bad_arg = TRUE;
+ return;
+ }
+ }
+}
+
+static void
+parse_regtabs(int argc, char **argv, int *option, int *opt_len, int *bad_arg) {
+ /* argc: Number of arguments for this option. */
+ /* argv: Arguments for this option. */
+ /* option: Clear flag to set. */
+ /* opt_len: Regular tab length. */
+ /* bad_arg: Set to true if an error is detected. */
+
+ if (*option || argc > 1)
+ *bad_arg = TRUE;
+ *option = TRUE;
+ if(argc == 0) {
+ *opt_len = 8;
+ return;
+ }
+ *opt_len = atoi(argv[0]);
+ if(*opt_len < 1 || *opt_len > 160) {
+ *bad_arg = TRUE;
+ return;
+ }
+}
+
+
+static void
+parse_blength(int argc, char **argv, int *option, int *opt_all, int *bad_arg) {
+ /* argc: Number of arguments for this option. */
+ /* argv: Arguments for this option. */
+ /* option: Clear flag to set. */
+ /* opt_all */
+ /* bad_arg: Set to true if an error is detected. */
+
+/* Parse -blength specification. */
+
+ if (argc > 1 || *option)
+ *bad_arg = TRUE;
+ *option = TRUE;
+ if (argc == 1) {
+ *opt_all = atoi(argv[0]);
+ if (*opt_all > 2000)
+ *bad_arg = TRUE;
+ } else {
+ *opt_all = 0;
+ }
+}
+
+static void
+parse_bfreq(int argc, char **argv, int *option, int *opt_all, int *bad_arg) {
+ /* argc: Number of arguments for this option. */
+ /* argv: Arguments for this option. */
+ /* option: Clear flag to set. */
+ /* opt_all */
+ /* bad_arg: Set to true if an error is detected. */
+
+/* Parse -bfreq specification. */
+
+ if (argc > 1 || *option)
+ *bad_arg = TRUE;
+ *option = TRUE;
+ if (argc == 1) {
+ *opt_all = atoi(argv[0]);
+ } else {
+ *opt_all = 0;
+ }
+}
+
+
+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 void __attribute__ ((__noreturn__))
+usage(FILE *out) {
+/* Print error message about arguments, and the command's syntax. */
+
+ if (out == stderr)
+ warnx(_("Argument error."));
+
+ fputs(_("\nUsage:\n"), out);
+ fprintf(out,
+ _(" %s [options]\n"), program_invocation_short_name);
+
+ fputs(_("\nOptions:\n"), out);
+ fputs(_(" -term <terminal_name>\n"
+ " -reset\n"
+ " -initialize\n"
+ " -cursor <on|off>\n"
+ " -repeat <on|off>\n"
+ " -appcursorkeys <on|off>\n"
+ " -linewrap <on|off>\n"
+ " -default\n"
+ " -foreground <black|blue|green|cyan|red|magenta|yellow|white|default>\n"
+ " -background <black|blue|green|cyan|red|magenta|yellow|white|default>\n"
+ " -ulcolor <black|grey|blue|green|cyan|red|magenta|yellow|white>\n"
+ " -ulcolor <bright blue|green|cyan|red|magenta|yellow|white>\n"
+ " -hbcolor <black|grey|blue|green|cyan|red|magenta|yellow|white>\n"
+ " -hbcolor <bright blue|green|cyan|red|magenta|yellow|white>\n"
+ " -inversescreen <on|off>\n"
+ " -bold <on|off>\n"
+ " -half-bright <on|off>\n"
+ " -blink <on|off>\n"
+ " -reverse <on|off>\n"
+ " -underline <on|off>\n"
+ " -store >\n"
+ " -clear <all|rest>\n"
+ " -tabs < tab1 tab2 tab3 ... > (tabn = 1-160)\n"
+ " -clrtabs < tab1 tab2 tab3 ... > (tabn = 1-160)\n"
+ " -regtabs <1-160>\n"
+ " -blank <0-60|force|poke>\n"
+ " -dump <1-NR_CONSOLES>\n"
+ " -append <1-NR_CONSOLES>\n"
+ " -file dumpfilename\n"
+ " -msg <on|off>\n"
+ " -msglevel <0-8>\n"
+ " -powersave <on|vsync|hsync|powerdown|off>\n"
+ " -powerdown <0-60>\n"
+ " -blength <0-2000>\n"
+ " -bfreq freqnumber\n"
+ " -version\n"
+ " -help\n"), out);
+
+ fprintf(out, USAGE_MAN_TAIL("setterm(1)"));
+
+ exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+#define STRCMP(str1,str2) strncmp(str1,str2,strlen(str1))
+
+static void
+parse_option(char *option, int argc, char **argv, int *bad_arg) {
+ /* option: Option with leading '-' removed. */
+ /* argc: Number of arguments for this option. */
+ /* argv: Arguments for this option. */
+ /* bad_arg: Set to true if an error is detected. */
+
+/* Parse a single specification. */
+
+ if (STRCMP(option, "term") == 0)
+ parse_term(argc, argv, &opt_term, &opt_te_terminal_name, bad_arg);
+ else if (STRCMP(option, "reset") == 0)
+ parse_none(argc, argv, &opt_reset, bad_arg);
+ else if (STRCMP(option, "initialize") == 0)
+ parse_none(argc, argv, &opt_initialize, bad_arg);
+ else if (STRCMP(option, "cursor") == 0)
+ parse_switch(argc, argv, &opt_cursor, &opt_cu_on, bad_arg);
+ else if (STRCMP(option, "repeat") == 0)
+ parse_switch(argc, argv, &opt_repeat, &opt_rep_on, bad_arg);
+ else if (STRCMP(option, "appcursorkeys") == 0)
+ parse_switch(argc, argv, &opt_appcursorkeys, &opt_appck_on, bad_arg);
+ else if (STRCMP(option, "linewrap") == 0)
+ parse_switch(argc, argv, &opt_linewrap, &opt_li_on, bad_arg);
+#if 0
+ else if (STRCMP(option, "snow") == 0)
+ parse_switch(argc, argv, &opt_snow, &opt_sn_on, bad_arg);
+ else if (STRCMP(option, "softscroll") == 0)
+ parse_switch(argc, argv, &opt_softscroll, &opt_so_on, bad_arg);
+#endif
+ else if (STRCMP(option, "default") == 0)
+ parse_none(argc, argv, &opt_default, bad_arg);
+ else if (STRCMP(option, "foreground") == 0)
+ par_color(argc, argv, &opt_foreground, &opt_fo_color, bad_arg);
+ else if (STRCMP(option, "background") == 0)
+ par_color(argc, argv, &opt_background, &opt_ba_color, bad_arg);
+ else if (STRCMP(option, "ulcolor") == 0)
+ par_color2(argc, argv, &opt_ulcolor, &opt_ul_color, bad_arg);
+ else if (STRCMP(option, "hbcolor") == 0)
+ par_color2(argc, argv, &opt_hbcolor, &opt_hb_color, bad_arg);
+ else if (STRCMP(option, "inversescreen") == 0)
+ parse_switch(argc, argv, &opt_inversescreen, &opt_invsc_on, bad_arg);
+ else if (STRCMP(option, "bold") == 0)
+ parse_switch(argc, argv, &opt_bold, &opt_bo_on, bad_arg);
+ else if (STRCMP(option, "half-bright") == 0)
+ parse_switch(argc, argv, &opt_halfbright, &opt_hb_on, bad_arg);
+ else if (STRCMP(option, "blink") == 0)
+ parse_switch(argc, argv, &opt_blink, &opt_bl_on, bad_arg);
+ else if (STRCMP(option, "reverse") == 0)
+ parse_switch(argc, argv, &opt_reverse, &opt_re_on, bad_arg);
+ else if (STRCMP(option, "underline") == 0)
+ parse_switch(argc, argv, &opt_underline, &opt_un_on, bad_arg);
+ else if (STRCMP(option, "store") == 0)
+ parse_none(argc, argv, &opt_store, bad_arg);
+ else if (STRCMP(option, "clear") == 0)
+ parse_clear(argc, argv, &opt_clear, &opt_cl_all, bad_arg);
+ else if (STRCMP(option, "tabs") == 0)
+ parse_tabs(argc, argv, &opt_tabs, opt_tb_array, bad_arg);
+ else if (STRCMP(option, "clrtabs") == 0)
+ parse_clrtabs(argc, argv, &opt_clrtabs, opt_tb_array, bad_arg);
+ else if (STRCMP(option, "regtabs") == 0)
+ parse_regtabs(argc, argv, &opt_regtabs, &opt_rt_len, bad_arg);
+ else if (STRCMP(option, "blank") == 0)
+ parse_blank(argc, argv, &opt_blank, &opt_bl_min, bad_arg);
+ else if (STRCMP(option, "dump") == 0)
+ parse_snap(argc, argv, &opt_snap, &opt_sn_num, bad_arg);
+ else if (STRCMP(option, "append") == 0)
+ parse_snap(argc, argv, &opt_append, &opt_sn_num, bad_arg);
+ else if (STRCMP(option, "file") == 0)
+ parse_snapfile(argc, argv, &opt_snapfile, (int *)opt_sn_name, bad_arg);
+ else if (STRCMP(option, "msg") == 0)
+ parse_switch(argc, argv, &opt_msg, &opt_msg_on, bad_arg);
+ else if (STRCMP(option, "msglevel") == 0)
+ parse_msglevel(argc, argv, &opt_msglevel, &opt_msglevel_num, bad_arg);
+ else if (STRCMP(option, "powersave") == 0)
+ parse_powersave(argc, argv, &opt_powersave, &opt_ps_mode, bad_arg);
+ else if (STRCMP(option, "powerdown") == 0)
+ parse_blank(argc, argv, &opt_powerdown, &opt_pd_min, bad_arg);
+ else if (STRCMP(option, "blength") == 0)
+ parse_blength(argc, argv, &opt_blength, &opt_blength_l, bad_arg);
+ else if (STRCMP(option, "bfreq") == 0)
+ parse_bfreq(argc, argv, &opt_bfreq, &opt_bfreq_f, bad_arg);
+#if 0
+ else if (STRCMP(option, "standout") == 0)
+ parse_standout(argc, argv, &opt_standout, &opt_st_attr, bad_arg);
+#endif
+ else if (STRCMP(option, "version") == 0) {
+ printf(_("%s from %s\n"), program_invocation_short_name,
+ PACKAGE_STRING);
+ exit(EXIT_SUCCESS);
+ } else if (STRCMP(option, "help") == 0)
+ usage(stdout);
+ else
+ *bad_arg = TRUE;
+}
+
+/* End of command line parsing routines. */
+
+static char *ti_entry(const char *name) {
+ /* name: Terminfo capability string to lookup. */
+
+/* Return the specified terminfo string, or an empty string if no such terminfo
+ * capability exists.
+ */
+
+ char *buf_ptr;
+
+ if ((buf_ptr = tigetstr((char *)name)) == (char *)-1)
+ buf_ptr = NULL;
+ return buf_ptr;
+}
+
+static void
+perform_sequence(int vcterm) {
+ /* vcterm: Set if terminal is a virtual console. */
+
+ int result;
+/* Perform the selected options. */
+
+ /* -reset. */
+ if (opt_reset) {
+ putp(ti_entry("rs1"));
+ }
+
+ /* -initialize. */
+ if (opt_initialize) {
+ putp(ti_entry("is2"));
+ }
+
+ /* -cursor [on|off]. */
+ if (opt_cursor) {
+ if (opt_cu_on)
+ putp(ti_entry("cnorm"));
+ else
+ putp(ti_entry("civis"));
+ }
+
+ /* -linewrap [on|off]. Vc only (vt102) */
+ if (opt_linewrap && vcterm) {
+ if (opt_li_on)
+ printf("\033[?7h");
+ else
+ printf("\033[?7l");
+ }
+
+ /* -repeat [on|off]. Vc only (vt102) */
+ if (opt_repeat && vcterm) {
+ if (opt_rep_on)
+ printf("\033[?8h");
+ else
+ printf("\033[?8l");
+ }
+
+ /* -appcursorkeys [on|off]. Vc only (vt102) */
+ if (opt_appcursorkeys && vcterm) {
+ if (opt_appck_on)
+ printf("\033[?1h");
+ else
+ printf("\033[?1l");
+ }
+
+#if 0
+ /* -snow [on|off]. Vc only. */
+ if (opt_snow && vcterm) {
+ if (opt_sn_on)
+ printf("%s%s%s", DCS, "snow.on", ST);
+ else
+ printf("%s%s%s", DCS, "snow.off", ST);
+ }
+
+ /* -softscroll [on|off]. Vc only. */
+ if (opt_softscroll && vcterm) {
+ if (opt_so_on)
+ printf("%s%s%s", DCS, "softscroll.on", ST);
+ else
+ printf("%s%s%s", DCS, "softscroll.off", ST);
+ }
+#endif
+
+ /* -default. Vc sets default rendition, otherwise clears all
+ * attributes.
+ */
+ if (opt_default) {
+ if (vcterm)
+ printf("\033[0m");
+ else
+ putp(ti_entry("sgr0"));
+ }
+
+ /* -foreground black|red|green|yellow|blue|magenta|cyan|white|default.
+ * Vc only (ANSI).
+ */
+ if (opt_foreground && vcterm) {
+ printf("%s%s%c%s", ESC, "[3", '0' + opt_fo_color, "m");
+ }
+
+ /* -background black|red|green|yellow|blue|magenta|cyan|white|default.
+ * Vc only (ANSI).
+ */
+ if (opt_background && vcterm) {
+ printf("%s%s%c%s", ESC, "[4", '0' + opt_ba_color, "m");
+ }
+
+ /* -ulcolor black|red|green|yellow|blue|magenta|cyan|white|default.
+ * Vc only.
+ */
+ if (opt_ulcolor && vcterm) {
+ printf("\033[1;%d]", opt_ul_color);
+ }
+
+ /* -hbcolor black|red|green|yellow|blue|magenta|cyan|white|default.
+ * Vc only.
+ */
+ if (opt_hbcolor && vcterm) {
+ printf("\033[2;%d]", opt_hb_color);
+ }
+
+ /* -inversescreen [on|off]. Vc only (vt102).
+ */
+ if (opt_inversescreen) {
+ if (vcterm) {
+ if (opt_invsc_on)
+ printf("\033[?5h");
+ else
+ printf("\033[?5l");
+ }
+ }
+
+ /* -bold [on|off]. Vc behaves as expected, otherwise off turns off
+ * all attributes.
+ */
+ if (opt_bold) {
+ if (opt_bo_on)
+ putp(ti_entry("bold"));
+ else {
+ if (vcterm)
+ printf("%s%s", ESC, "[22m");
+ else
+ putp(ti_entry("sgr0"));
+ }
+ }
+
+ /* -half-bright [on|off]. Vc behaves as expected, otherwise off turns off
+ * all attributes.
+ */
+ if (opt_halfbright) {
+ if (opt_hb_on)
+ putp(ti_entry("dim"));
+ else {
+ if (vcterm)
+ printf("%s%s", ESC, "[22m");
+ else
+ putp(ti_entry("sgr0"));
+ }
+ }
+
+ /* -blink [on|off]. Vc behaves as expected, otherwise off turns off
+ * all attributes.
+ */
+ if (opt_blink) {
+ if (opt_bl_on)
+ putp(ti_entry("blink"));
+ else {
+ if (vcterm)
+ printf("%s%s", ESC, "[25m");
+ else
+ putp(ti_entry("sgr0"));
+ }
+ }
+
+ /* -reverse [on|off]. Vc behaves as expected, otherwise off turns
+ * off all attributes.
+ */
+ if (opt_reverse) {
+ if (opt_re_on)
+ putp(ti_entry("rev"));
+ else {
+ if (vcterm)
+ printf("%s%s", ESC, "[27m");
+ else
+ putp(ti_entry("sgr0"));
+ }
+ }
+
+ /* -underline [on|off]. */
+ if (opt_underline) {
+ if (opt_un_on)
+ putp(ti_entry("smul"));
+ else
+ putp(ti_entry("rmul"));
+ }
+
+ /* -store. Vc only. */
+ if (opt_store && vcterm) {
+ printf("\033[8]");
+ }
+
+ /* -clear [all|rest]. */
+ if (opt_clear) {
+ if (opt_cl_all)
+ putp(ti_entry("clear"));
+ else
+ putp(ti_entry("ed"));
+ }
+
+ /* -tabs Vc only. */
+ if (opt_tabs && vcterm) {
+ int i;
+
+ if (opt_tb_array[0] == -1)
+ show_tabs();
+ else {
+ for(i=0; opt_tb_array[i] > 0; i++)
+ printf("\033[%dG\033H", opt_tb_array[i]);
+ putchar('\r');
+ }
+ }
+
+ /* -clrtabs Vc only. */
+ if (opt_clrtabs && vcterm) {
+ int i;
+
+ if (opt_tb_array[0] == -1)
+ printf("\033[3g");
+ else
+ for(i=0; opt_tb_array[i] > 0; i++)
+ printf("\033[%dG\033[g", opt_tb_array[i]);
+ putchar('\r');
+ }
+
+ /* -regtabs Vc only. */
+ if (opt_regtabs && vcterm) {
+ int i;
+
+ printf("\033[3g\r");
+ for(i=opt_rt_len+1; i<=160; i+=opt_rt_len)
+ printf("\033[%dC\033H",opt_rt_len);
+ putchar('\r');
+ }
+
+ /* -blank [0-60]. */
+ if (opt_blank && vcterm) {
+ if (opt_bl_min >= 0)
+ printf("\033[9;%d]", opt_bl_min);
+ else if (opt_bl_min == BLANKSCREEN) {
+ char ioctlarg = TIOCL_BLANKSCREEN;
+ if (ioctl(0,TIOCLINUX,&ioctlarg))
+ warn(_("cannot force blank"));
+ } else if (opt_bl_min == UNBLANKSCREEN) {
+ char ioctlarg = TIOCL_UNBLANKSCREEN;
+ if (ioctl(0,TIOCLINUX,&ioctlarg))
+ warn(_("cannot force unblank"));
+ } else if (opt_bl_min == BLANKEDSCREEN) {
+ char ioctlarg = TIOCL_BLANKEDSCREEN;
+ int ret;
+ ret = ioctl(0,TIOCLINUX,&ioctlarg);
+ if (ret < 0)
+ warn(_("cannot get blank status"));
+ else
+ printf("%d\n",ret);
+ }
+ }
+
+ /* -powersave [on|vsync|hsync|powerdown|off] (console) */
+ if (opt_powersave) {
+ char ioctlarg[2];
+ ioctlarg[0] = TIOCL_SETVESABLANK;
+ ioctlarg[1] = opt_ps_mode;
+ if (ioctl(0,TIOCLINUX,ioctlarg))
+ warn(_("cannot (un)set powersave mode"));
+ }
+
+ /* -powerdown [0-60]. */
+ if (opt_powerdown) {
+ printf("\033[14;%d]", opt_pd_min);
+ }
+
+#if 0
+ /* -standout [num]. */
+ if (opt_standout)
+ /* nothing */;
+#endif
+
+ /* -snap [1-NR_CONS]. */
+ if (opt_snap || opt_append) {
+ FILE *F;
+
+ F = fopen(opt_sn_name, opt_snap ? "w" : "a");
+ if (!F)
+ err(EXIT_DUMPFILE, _("can not open dump file %s for output"),
+ opt_sn_name);
+ screendump(opt_sn_num, F);
+ if (close_stream(F) != 0)
+ errx(EXIT_FAILURE, _("write error"));
+ }
+
+ /* -msg [on|off]. */
+ if (opt_msg && vcterm) {
+ if (opt_msg_on)
+ /* 7 -- Enable printk's to console */
+ result = klogctl(7, NULL, 0);
+ else
+ /* 6 -- Disable printk's to console */
+ result = klogctl(6, NULL, 0);
+
+ if (result != 0)
+ warn(_("klogctl error"));
+ }
+
+ /* -msglevel [0-8] */
+ if (opt_msglevel && vcterm) {
+ /* 8 -- Set level of messages printed to console */
+ result = klogctl(8, NULL, opt_msglevel_num);
+ if (result != 0)
+ warn(_("klogctl error"));
+ }
+
+ /* -blength [0-2000] */
+ if (opt_blength && vcterm) {
+ printf("\033[11;%d]", opt_blength_l);
+ }
+
+ /* -bfreq freqnumber */
+ if (opt_bfreq && vcterm) {
+ printf("\033[10;%d]", opt_bfreq_f);
+ }
+
+}
+
+static void
+screendump(int vcnum, FILE * F)
+{
+ char infile[MAXPATHLEN];
+ unsigned char header[4];
+ unsigned int rows, cols;
+ int fd;
+ size_t i, j;
+ char *inbuf, *outbuf, *p, *q;
+
+ sprintf(infile, "/dev/vcsa%d", vcnum);
+ fd = open(infile, O_RDONLY);
+ if (fd < 0 && vcnum == 0) {
+ /* vcsa0 is often called vcsa */
+ sprintf(infile, "/dev/vcsa");
+ fd = open(infile, O_RDONLY);
+ }
+ if (fd < 0) {
+ /* try devfs name - for zero vcnum just /dev/vcc/a */
+ /* some gcc's warn for %.u - add 0 */
+ sprintf(infile, "/dev/vcc/a%.0u", vcnum);
+ fd = open(infile, O_RDONLY);
+ }
+ if (fd < 0) {
+ sprintf(infile, "/dev/vcsa%d", vcnum);
+ goto read_error;
+ }
+ if (read(fd, header, 4) != 4)
+ goto read_error;
+ rows = header[0];
+ cols = header[1];
+ if (rows * cols == 0)
+ goto read_error;
+
+ inbuf = xmalloc(rows * cols * 2);
+ outbuf = xmalloc(rows * (cols + 1));
+
+ if (read(fd, inbuf, rows * cols * 2) != rows * cols * 2)
+ goto read_error;
+ p = inbuf;
+ q = 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';
+ }
+ if (fwrite(outbuf, 1, q - outbuf, F) != (size_t) (q - outbuf)) {
+ warnx(_("Error writing screendump"));
+ goto error;
+ }
+ close(fd);
+ return;
+
+ read_error:
+ warnx(_("Couldn't read %s"), infile);
+ error:
+ if (fd >= 0)
+ close(fd);
+ exit(EXIT_FAILURE);
+}
+
+int
+main(int argc, char **argv) {
+ int bad_arg = FALSE; /* Set if error in arguments. */
+ int arg, modifier;
+ char *term; /* Terminal type. */
+ int vcterm; /* Set if terminal is a virtual console. */
+ int errret;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ atexit(close_stdout);
+
+ if (argc < 2)
+ bad_arg = TRUE;
+
+ /* Parse arguments. */
+
+ for (arg = 1; arg < argc;) {
+ if (*argv[arg] == '-') {
+
+ /* Parse a single option. */
+
+ for (modifier = arg + 1; modifier < argc; modifier++) {
+ if (*argv[modifier] == '-') break;
+ }
+ parse_option(argv[arg] + 1, modifier - arg - 1,
+ &argv[arg + 1], &bad_arg);
+ arg = modifier;
+ } else {
+ bad_arg = TRUE;
+ arg++;
+ }
+ }
+
+ /* Display syntax message if error in arguments. */
+
+ if (bad_arg)
+ usage(stderr);
+
+ /* Find out terminal name. */
+
+ if (opt_term) {
+ term = opt_te_terminal_name;
+ } else {
+ term = getenv("TERM");
+ if (term == NULL)
+ errx(EXIT_FAILURE, _("$TERM is not defined."));
+ }
+
+ /* Find terminfo entry. */
+
+ if (setupterm(term, 1, &errret))
+ switch(errret) {
+ case -1:
+ errx(EXIT_FAILURE, _("terminfo database cannot be found"));
+ case 0:
+ errx(EXIT_FAILURE, _("%s: unknown terminal type"), term);
+ case 1:
+ errx(EXIT_FAILURE, _("terminal is hardcopy"));
+ }
+
+ /* See if the terminal is a virtual console terminal. */
+
+ vcterm = (!strncmp(term, "con", 3) || !strncmp(term, "linux", 5));
+
+ /* Perform the selected options. */
+
+ perform_sequence(vcterm);
+
+ return EXIT_SUCCESS;
+}
diff --git a/term-utils/ttymsg.c b/term-utils/ttymsg.c
new file mode 100644
index 0000000..aea6c26
--- /dev/null
+++ b/term-utils/ttymsg.c
@@ -0,0 +1,191 @@
+/*
+ * 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/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"
+
+/*
+ * 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[MAXNAMLEN+1024];
+ size_t cnt, left;
+ ssize_t wret;
+ struct iovec localiov[6];
+ int fd, forked = 0, errsv;
+
+ if (iovcnt > sizeof(localiov) / sizeof(localiov[0]))
+ return (_("too many iov's (change code in wall/ttymsg.c)"));
+
+ /* The old code here rejected the line argument when it contained a '/',
+ saying: "A slash may be an attempt to break security...".
+ However, if a user can control the line argument here
+ then he can make this routine write to /dev/hda or /dev/sda
+ already. So, this test was worthless, and these days it is
+ also wrong since people use /dev/pts/xxx. */
+
+ if (strlen(line) + sizeof(_PATH_DEV) + 1 > sizeof(device)) {
+ (void) sprintf(errbuf, _("excessively long line arg"));
+ return (errbuf);
+ }
+ (void) sprintf(device, "%s%s", _PATH_DEV, line);
+
+ /*
+ * 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);
+ if (strlen(strerror(errno)) > 1000)
+ return (NULL);
+ (void) sprintf(errbuf, "%s: %m", device);
+ errbuf[1024] = 0;
+ 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) {
+ (void) close(fd);
+ _exit(EXIT_FAILURE);
+ }
+ cpid = fork();
+ if (cpid < 0) {
+ if (strlen(strerror(errno)) > 1000)
+ (void) sprintf(errbuf, _("cannot fork"));
+ else {
+ errsv = errno;
+ (void) sprintf(errbuf,
+ _("fork: %s"), strerror(errsv));
+ }
+ (void) close(fd);
+ return (errbuf);
+ }
+ if (cpid) { /* parent */
+ (void) close(fd);
+ return (NULL);
+ }
+ forked++;
+ /* wait at most tmout seconds */
+ (void) signal(SIGALRM, SIG_DFL);
+ (void) signal(SIGTERM, SIG_DFL); /* XXX */
+ sigemptyset(&sigmask);
+ sigprocmask (SIG_SETMASK, &sigmask, NULL);
+ (void) 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;
+ (void) close(fd);
+ if (forked)
+ _exit(EXIT_FAILURE);
+ if (strlen(strerror(errno)) > 1000)
+ (void) sprintf(errbuf, _("%s: BAD ERROR, message is "
+ "far too long"), device);
+ else {
+ errsv = errno;
+ (void) sprintf(errbuf, "%s: %s", device,
+ strerror(errsv));
+ }
+ errbuf[1024] = 0;
+ return (errbuf);
+ }
+
+ (void) 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..2cfa730
--- /dev/null
+++ b/term-utils/ttymsg.h
@@ -0,0 +1,2 @@
+char *ttymsg(struct iovec *iov, size_t iovcnt, char *line, int tmout);
+
diff --git a/term-utils/wall.1 b/term-utils/wall.1
new file mode 100644
index 0000000..88e816c
--- /dev/null
+++ b/term-utils/wall.1
@@ -0,0 +1,86 @@
+.\" 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
+.\"
+.\" Modified for Linux, Mon Mar 8 18:07:38 1993, faith@cs.unc.edu
+.\"
+.TH WALL "1" "September 2011" "util-linux" "User Commands"
+.SH NAME
+wall \- write a message to users
+.SH SYNOPSIS
+.B wall
+[-n] [-t TIMEOUT] [file]
+.SH DESCRIPTION
+.B Wall
+displays the contents of
+.I file
+or, by default, its standard input, on the terminals of all currently logged
+in users. The command will cut over 79 character long lines to new lines.
+Short lines are white space padded to have 79 characters. The command will
+always put carriage return and new line at the end of each line.
+.PP
+Only the super-user can write on the terminals of users who have chosen to
+deny messages or are using a program which automatically denies messages.
+.PP
+Reading from a file is refused when the invoker is not superuser and the
+program is suid or sgid.
+.SH OPTIONS
+.TP
+\fB\-n\fR, \fB\-\-nobanner\fR
+Suppress banner
+.TP
+\fB\-t\fR, \fB\-\-timeout\fR \fItimeout\fR
+Write
+.I timeout
+to terminals in seconds. Argument must be positive integer. Default value
+is 300 seconds, which is a legacy from time when people ran terminals over
+modem lines.
+.TP
+\fB\-V\fR, \fB\-\-version\fR
+Output version and exit.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+Output help and exit.
+.SH SEE ALSO
+.BR mesg (1),
+.BR talk (1),
+.BR write (1),
+.BR shutdown (8)
+.SH HISTORY
+A
+.B wall
+command appeared in Version 7 AT&T UNIX.
+.SH AVAILABILITY
+The wall command is part of the util-linux package and is available from
+.UR ftp://\:ftp.kernel.org\:/pub\:/linux\:/utils\:/util-linux/
+Linux Kernel Archive
+.UE .
diff --git a/term-utils/wall.c b/term-utils/wall.c
new file mode 100644
index 0000000..30324aa
--- /dev/null
+++ b/term-utils/wall.c
@@ -0,0 +1,280 @@
+/*
+ * 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 <utmp.h>
+#include <getopt.h>
+
+#include "nls.h"
+#include "xalloc.h"
+#include "strutils.h"
+#include "ttymsg.h"
+#include "pathnames.h"
+#include "carefulputc.h"
+#include "c.h"
+#include "fileutils.h"
+#include "closestream.h"
+
+#define IGNOREUSER "sleeper"
+#define WRITE_TIME_OUT 300 /* in seconds */
+
+/* Function prototypes */
+char *makemsg(char *fname, size_t *mbufsize, int print_banner);
+static void usage(FILE *out);
+
+static void __attribute__((__noreturn__)) usage(FILE *out)
+{
+ fputs(_("\nUsage:\n"), out);
+ fprintf(out,
+ _(" %s [options] [<file>]\n"),program_invocation_short_name);
+
+ fputs(_("\nOptions:\n"), out);
+ fputs(_(" -n, --nobanner do not print banner, works only for root\n"
+ " -t, --timeout <timeout> write timeout in seconds\n"
+ " -V, --version output version information and exit\n"
+ " -h, --help display this help and exit\n\n"), out);
+
+ exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+int
+main(int argc, char **argv) {
+ int ch;
+ struct iovec iov;
+ struct utmp *utmpptr;
+ char *p;
+ char line[sizeof(utmpptr->ut_line) + 1];
+ int print_banner = TRUE;
+ char *mbuf;
+ size_t mbufsize;
+ unsigned timeout = WRITE_TIME_OUT;
+
+ setlocale(LC_ALL, "");
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ textdomain(PACKAGE);
+ atexit(close_stdout);
+
+ static const struct option longopts[] = {
+ { "nobanner", no_argument, 0, 'n' },
+ { "timeout", required_argument, 0, 't' },
+ { "version", no_argument, 0, 'V' },
+ { "help", no_argument, 0, 'h' },
+ { NULL, 0, 0, 0 }
+ };
+
+ while ((ch = getopt_long(argc, argv, "nt: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 'V':
+ printf(_("%s from %s\n"), program_invocation_short_name,
+ PACKAGE_STRING);
+ exit(EXIT_SUCCESS);
+ case 'h':
+ usage(stdout);
+ default:
+ usage(stderr);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc > 1)
+ usage(stderr);
+
+ mbuf = makemsg(*argv, &mbufsize, print_banner);
+
+ iov.iov_base = mbuf;
+ iov.iov_len = mbufsize;
+ while((utmpptr = getutent())) {
+ if (!utmpptr->ut_name[0] ||
+ !strncmp(utmpptr->ut_name, IGNOREUSER,
+ sizeof(utmpptr->ut_name)))
+ 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. */
+ if (utmpptr->ut_line[0] == ':')
+ continue;
+
+ xstrncpy(line, utmpptr->ut_line, sizeof(utmpptr->ut_line));
+ if ((p = ttymsg(&iov, 1, line, timeout)) != NULL)
+ warnx("%s", p);
+ }
+ endutent();
+ free(mbuf);
+ exit(EXIT_SUCCESS);
+}
+
+char *
+makemsg(char *fname, size_t *mbufsize, int print_banner)
+{
+ register int ch, cnt;
+ struct tm *lt;
+ struct passwd *pw;
+ struct stat sbuf;
+ time_t now;
+ FILE *fp;
+ char *p, *whom, *where, *hostname, *lbuf, *tmpname, *mbuf;
+ long line_max;
+
+ hostname = xmalloc(sysconf(_SC_HOST_NAME_MAX) + 1);
+ line_max = sysconf(_SC_LINE_MAX);
+ lbuf = xmalloc(line_max);
+
+ if ((fp = xfmkstemp(&tmpname, NULL)) == NULL)
+ err(EXIT_FAILURE, _("can't open temporary file"));
+ unlink(tmpname);
+ free(tmpname);
+
+ if (print_banner == TRUE) {
+ if (!(whom = getlogin()) || !*whom)
+ whom = (pw = getpwuid(getuid())) ? pw->pw_name : "???";
+ if (!whom) {
+ whom = "someone";
+ warn(_("cannot get passwd uid"));
+ }
+ where = ttyname(STDOUT_FILENO);
+ if (!where) {
+ where = "somewhere";
+ warn(_("cannot get tty name"));
+ }
+ gethostname(hostname, sizeof(hostname));
+ time(&now);
+ lt = localtime(&now);
+
+ /*
+ * 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 */
+ fprintf(fp, "\r%79s\r\n", " ");
+ sprintf(lbuf, _("Broadcast Message from %s@%s"),
+ whom, hostname);
+ fprintf(fp, "%-79.79s\007\007\r\n", lbuf);
+ sprintf(lbuf, " (%s) at %d:%02d ...",
+ where, lt->tm_hour, lt->tm_min);
+ fprintf(fp, "%-79.79s\r\n", lbuf);
+ }
+ fprintf(fp, "%79s\r\n", " ");
+
+ free(hostname);
+
+ 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);
+ }
+
+ while (fgets(lbuf, line_max, stdin)) {
+ for (cnt = 0, p = lbuf; (ch = *p) != '\0'; ++p, ++cnt) {
+ if (cnt == 79 || ch == '\n') {
+ for (; cnt < 79; ++cnt)
+ putc(' ', fp);
+ putc('\r', fp);
+ putc('\n', fp);
+ cnt = 0;
+ }
+ if (ch != '\n')
+ carefulputc(ch, fp);
+ }
+ }
+ fprintf(fp, "%79s\r\n", " ");
+
+ free(lbuf);
+ rewind(fp);
+
+ if (fstat(fileno(fp), &sbuf))
+ err(EXIT_FAILURE, _("stat failed"));
+
+ *mbufsize = (size_t) sbuf.st_size;
+ mbuf = xmalloc(*mbufsize);
+
+ if (fread(mbuf, 1, *mbufsize, fp) != *mbufsize)
+ err(EXIT_FAILURE, _("fread failed"));
+
+ if (close_stream(fp) != 0)
+ errx(EXIT_FAILURE, _("write error"));
+ return mbuf;
+}
diff --git a/term-utils/write.1 b/term-utils/write.1
new file mode 100644
index 0000000..b606575
--- /dev/null
+++ b/term-utils/write.1
@@ -0,0 +1,102 @@
+.\" 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
+.\"
+.\" Modified for Linux, Sun Mar 12 10:21:01 1995, faith@cs.unc.edu
+.\"
+.TH WRITE 1 "March 1995" "util-linux" "User Commands"
+.SH NAME
+write \- send a message to another user
+.SH SYNOPSIS
+.B write
+.I user
+.RI [ ttyname ]
+.SH DESCRIPTION
+.B Write
+allows you to communicate with other users, by copying lines from
+your terminal to theirs.
+.PP
+When you run the
+.B write
+command, the user you are writing to gets a message of the form:
+.PP
+.RS
+Message from yourname@yourhost on yourtty at hh:mm ...
+.RE
+.PP
+Any further lines you enter will be copied to the specified user's
+terminal. If the other user wants to reply, they must run
+.B write
+as well.
+.PP
+When you are done, type an end-of-file or interrupt character. The other
+user will see the message
+.B EOF
+indicating that the conversation is over.
+.PP
+You can prevent people (other than the super-user) from writing to you with
+the
+.BR mesg (1)
+command. Some commands, for example
+.BR nroff (1)
+and
+.BR pr (1),
+may disallow writing automatically, so that your output isn't overwritten.
+.PP
+If the user you want to write to is logged in on more than one terminal,
+you can specify which terminal to write to by specifying the terminal
+name as the second operand to the
+.B write
+command. Alternatively, you can let
+.B write
+select one of the terminals \- it will pick the one with the shortest idle
+time. This is so that if the user is logged in at work and also dialed up
+from home, the message will go to the right place.
+.PP
+The traditional protocol for writing to someone is that the string `\-o',
+either at the end of a line or on a line by itself, means that it's the
+other person's turn to talk. The string `oo' means that the person
+believes the conversation to be over.
+.SH "SEE ALSO"
+.BR mesg (1),
+.BR talk (1),
+.BR who (1)
+.SH HISTORY
+A
+.B write
+command appeared in Version 6 AT&T UNIX.
+.SH AVAILABILITY
+The write command is part of the util-linux package and is available from
+ftp://ftp.kernel.org/pub/linux/utils/util-linux/.
diff --git a/term-utils/write.c b/term-utils/write.c
new file mode 100644
index 0000000..6c746b4
--- /dev/null
+++ b/term-utils/write.c
@@ -0,0 +1,381 @@
+/*
+ * 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 enabeling 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 <stdio.h>
+#include <unistd.h>
+#include <utmp.h>
+#include <errno.h>
+#include <time.h>
+#include <pwd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <paths.h>
+#include <getopt.h>
+
+#include "c.h"
+#include "carefulputc.h"
+#include "closestream.h"
+#include "nls.h"
+
+static void __attribute__ ((__noreturn__)) usage(FILE * out);
+void search_utmp(char *, char *, char *, uid_t);
+void do_write(char *, char *, uid_t);
+void wr_fputs(char *);
+static void __attribute__ ((__noreturn__)) done(int);
+int term_chk(char *, int *, time_t *, int);
+int utmp_chk(char *, char *);
+
+static gid_t myegid;
+
+static void __attribute__ ((__noreturn__)) usage(FILE * out)
+{
+ fputs(_("\nUsage:\n"), out);
+ fprintf(out,
+ _(" %s [options] <user> [<ttyname>]\n"),
+ program_invocation_short_name);
+
+ fputs(_("\nOptions:\n"), out);
+ fputs(_(" -V, --version output version information and exit\n"
+ " -h, --help display this help and exit\n\n"), out);
+
+ exit(out == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+int main(int argc, char **argv)
+{
+ time_t atime;
+ uid_t myuid;
+ int msgsok, myttyfd, c;
+ char tty[PATH_MAX], *mytty;
+
+ 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);
+ atexit(close_stdout);
+
+ while ((c = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1)
+ switch (c) {
+ case 'V':
+ printf(_("%s from %s\n"),
+ program_invocation_short_name,
+ PACKAGE_STRING);
+ return EXIT_SUCCESS;
+ case 'h':
+ usage(stdout);
+ default:
+ usage(stderr);
+ }
+
+ myegid = getegid();
+
+ /* check that sender has write enabled */
+ if (isatty(fileno(stdin)))
+ myttyfd = fileno(stdin);
+ else if (isatty(fileno(stdout)))
+ myttyfd = fileno(stdout);
+ else if (isatty(fileno(stderr)))
+ myttyfd = fileno(stderr);
+ else
+ myttyfd = -1;
+
+ if (myttyfd != -1) {
+ if (!(mytty = ttyname(myttyfd)))
+ errx(EXIT_FAILURE,
+ _("can't find your tty's name"));
+
+ /*
+ * We may have /dev/ttyN but also /dev/pts/xx. Below,
+ * term_chk() will put "/dev/" in front, so remove that
+ * part.
+ */
+ if (!strncmp(mytty, "/dev/", 5))
+ mytty += 5;
+ if (term_chk(mytty, &msgsok, &atime, 1))
+ exit(EXIT_FAILURE);
+ if (!msgsok)
+ errx(EXIT_FAILURE,
+ _("you have write permission turned off"));
+
+ } else
+ mytty = "<no tty>";
+
+ myuid = getuid();
+
+ /* check args */
+ switch (argc) {
+ case 2:
+ search_utmp(argv[1], tty, mytty, myuid);
+ do_write(tty, mytty, myuid);
+ break;
+ case 3:
+ if (!strncmp(argv[2], "/dev/", 5))
+ argv[2] += 5;
+ if (utmp_chk(argv[1], argv[2]))
+ errx(EXIT_FAILURE,
+ _("%s is not logged in on %s"),
+ argv[1], argv[2]);
+ if (term_chk(argv[2], &msgsok, &atime, 1))
+ exit(EXIT_FAILURE);
+ if (myuid && !msgsok)
+ errx(EXIT_FAILURE,
+ _("%s has messages disabled on %s"),
+ argv[1], argv[2]);
+ do_write(argv[2], mytty, myuid);
+ break;
+ default:
+ usage(stderr);
+ }
+
+ done(0);
+ /* NOTREACHED */
+ return EXIT_FAILURE;
+}
+
+
+/*
+ * utmp_chk - checks that the given user is actually logged in on
+ * the given tty
+ */
+int utmp_chk(char *user, char *tty)
+{
+ struct utmp u;
+ struct utmp *uptr;
+ int res = 1;
+
+ utmpname(_PATH_UTMP);
+ setutent();
+
+ while ((uptr = getutent())) {
+ memcpy(&u, uptr, sizeof(u));
+ if (strncmp(user, u.ut_name, sizeof(u.ut_name)) == 0 &&
+ strncmp(tty, u.ut_line, sizeof(u.ut_line)) == 0) {
+ res = 0;
+ break;
+ }
+ }
+
+ endutent();
+ 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.
+ */
+void search_utmp(char *user, char *tty, char *mytty, uid_t myuid)
+{
+ struct utmp u;
+ struct utmp *uptr;
+ time_t bestatime, atime;
+ int nloggedttys, nttys, msgsok, user_is_me;
+ char atty[sizeof(u.ut_line) + 1];
+
+ utmpname(_PATH_UTMP);
+ setutent();
+
+ nloggedttys = nttys = 0;
+ bestatime = 0;
+ user_is_me = 0;
+ while ((uptr = getutent())) {
+ memcpy(&u, uptr, sizeof(u));
+ if (strncmp(user, u.ut_name, sizeof(u.ut_name)) == 0) {
+ ++nloggedttys;
+ strncpy(atty, u.ut_line, sizeof(u.ut_line));
+ atty[sizeof(u.ut_line)] = '\0';
+ if (term_chk(atty, &msgsok, &atime, 0))
+ /* bad term? skip */
+ continue;
+ if (myuid && !msgsok)
+ /* skip ttys with msgs off */
+ continue;
+ if (strcmp(atty, mytty) == 0) {
+ user_is_me = 1;
+ /* don't write to yourself */
+ continue;
+ }
+ if (u.ut_type != USER_PROCESS)
+ /* it's not a valid entry */
+ continue;
+ ++nttys;
+ if (atime > bestatime) {
+ bestatime = atime;
+ strcpy(tty, atty);
+ }
+ }
+ }
+
+ endutent();
+ if (nloggedttys == 0)
+ errx(EXIT_FAILURE, _("%s is not logged in"), user);
+ if (nttys == 0) {
+ if (user_is_me) {
+ /* ok, so write to yourself! */
+ strcpy(tty, mytty);
+ return;
+ }
+ errx(EXIT_FAILURE, _("%s has messages disabled"), user);
+ } else if (nttys > 1) {
+ warnx(_("%s is logged in more than once; writing to %s"),
+ user, tty);
+ }
+}
+
+/*
+ * term_chk - check that a terminal exists, and get the message bit
+ * and the access time
+ */
+int term_chk(char *tty, int *msgsokP, time_t * atimeP, int showerror)
+{
+ struct stat s;
+ char path[PATH_MAX];
+
+ if (strlen(tty) + 6 > sizeof(path))
+ return 1;
+ sprintf(path, "/dev/%s", tty);
+ if (stat(path, &s) < 0) {
+ if (showerror)
+ warn("%s", path);
+ return 1;
+ }
+
+ /* group write bit and group ownership */
+ *msgsokP = (s.st_mode & (S_IWRITE >> 3)) && myegid == s.st_gid;
+ *atimeP = s.st_atime;
+ return 0;
+}
+
+/*
+ * do_write - actually make the connection
+ */
+void do_write(char *tty, char *mytty, uid_t myuid)
+{
+ char *login, *pwuid, *nows;
+ struct passwd *pwd;
+ time_t now;
+ char path[PATH_MAX], host[MAXHOSTNAMELEN], line[512];
+
+ /* Determine our login name(s) before the we reopen() stdout */
+ if ((pwd = getpwuid(myuid)) != NULL)
+ pwuid = pwd->pw_name;
+ else
+ pwuid = "???";
+ if ((login = getlogin()) == NULL)
+ login = pwuid;
+
+ if (strlen(tty) + 6 > sizeof(path))
+ errx(EXIT_FAILURE, _("tty path %s too long"), tty);
+ snprintf(path, sizeof(path), "/dev/%s", tty);
+ if ((freopen(path, "w", stdout)) == NULL)
+ err(EXIT_FAILURE, "%s", path);
+
+ signal(SIGINT, done);
+ signal(SIGHUP, done);
+
+ /* print greeting */
+ if (gethostname(host, sizeof(host)) < 0)
+ strcpy(host, "???");
+ now = time((time_t *) NULL);
+ nows = ctime(&now);
+ nows[16] = '\0';
+ printf("\r\n\007\007\007");
+ if (strcmp(login, pwuid))
+ printf(_("Message from %s@%s (as %s) on %s at %s ..."),
+ login, host, pwuid, mytty, nows + 11);
+ else
+ printf(_("Message from %s@%s on %s at %s ..."),
+ login, host, mytty, nows + 11);
+ printf("\r\n");
+
+ while (fgets(line, sizeof(line), stdin) != NULL)
+ wr_fputs(line);
+}
+
+/*
+ * done - cleanup and exit
+ */
+static void __attribute__ ((__noreturn__))
+ done(int dummy __attribute__ ((__unused__)))
+{
+ printf("EOF\r\n");
+ _exit(EXIT_SUCCESS);
+}
+
+/*
+ * wr_fputs - like fputs(), but makes control characters visible and
+ * turns \n into \r\n.
+ */
+void wr_fputs(char *s)
+{
+ char c;
+
+#define PUTC(c) if (carefulputc(c, stdout) == EOF) \
+ err(EXIT_FAILURE, _("carefulputc failed"));
+ while (*s) {
+ c = *s++;
+ if (c == '\n')
+ PUTC('\r');
+ PUTC(c);
+ }
+ return;
+#undef PUTC
+}