diff options
Diffstat (limited to 'term-utils')
-rw-r--r-- | term-utils/Makemodule.am | 94 | ||||
-rw-r--r-- | term-utils/agetty.8 | 371 | ||||
-rw-r--r-- | term-utils/agetty.c | 1914 | ||||
-rw-r--r-- | term-utils/mesg.1 | 112 | ||||
-rw-r--r-- | term-utils/mesg.c | 158 | ||||
-rwxr-xr-x | term-utils/reset | 13 | ||||
-rwxr-xr-x | term-utils/reset.033c | 11 | ||||
-rw-r--r-- | term-utils/reset.1 | 45 | ||||
-rw-r--r-- | term-utils/script.1 | 156 | ||||
-rw-r--r-- | term-utils/script.c | 568 | ||||
-rw-r--r-- | term-utils/scriptreplay.1 | 107 | ||||
-rw-r--r-- | term-utils/scriptreplay.c | 234 | ||||
-rw-r--r-- | term-utils/setterm.1 | 196 | ||||
-rw-r--r-- | term-utils/setterm.c | 1290 | ||||
-rw-r--r-- | term-utils/ttymsg.c | 191 | ||||
-rw-r--r-- | term-utils/ttymsg.h | 2 | ||||
-rw-r--r-- | term-utils/wall.1 | 86 | ||||
-rw-r--r-- | term-utils/wall.c | 280 | ||||
-rw-r--r-- | term-utils/write.1 | 102 | ||||
-rw-r--r-- | term-utils/write.c | 381 |
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 +} |