xref: /freebsd/usr.bin/write/write.c (revision 8a16b7a18f5d0b031f09832fd7752fba717e2a97)
1*8a16b7a1SPedro F. Giffuni /*-
2*8a16b7a1SPedro F. Giffuni  * SPDX-License-Identifier: BSD-3-Clause
3*8a16b7a1SPedro F. Giffuni  *
49b50d902SRodney W. Grimes  * Copyright (c) 1989, 1993
59b50d902SRodney W. Grimes  *	The Regents of the University of California.  All rights reserved.
69b50d902SRodney W. Grimes  *
79b50d902SRodney W. Grimes  * This code is derived from software contributed to Berkeley by
89b50d902SRodney W. Grimes  * Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory.
99b50d902SRodney W. Grimes  *
109b50d902SRodney W. Grimes  * Redistribution and use in source and binary forms, with or without
119b50d902SRodney W. Grimes  * modification, are permitted provided that the following conditions
129b50d902SRodney W. Grimes  * are met:
139b50d902SRodney W. Grimes  * 1. Redistributions of source code must retain the above copyright
149b50d902SRodney W. Grimes  *    notice, this list of conditions and the following disclaimer.
159b50d902SRodney W. Grimes  * 2. Redistributions in binary form must reproduce the above copyright
169b50d902SRodney W. Grimes  *    notice, this list of conditions and the following disclaimer in the
179b50d902SRodney W. Grimes  *    documentation and/or other materials provided with the distribution.
18fbbd9655SWarner Losh  * 3. Neither the name of the University nor the names of its contributors
199b50d902SRodney W. Grimes  *    may be used to endorse or promote products derived from this software
209b50d902SRodney W. Grimes  *    without specific prior written permission.
219b50d902SRodney W. Grimes  *
229b50d902SRodney W. Grimes  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
239b50d902SRodney W. Grimes  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
249b50d902SRodney W. Grimes  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
259b50d902SRodney W. Grimes  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
269b50d902SRodney W. Grimes  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
279b50d902SRodney W. Grimes  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
289b50d902SRodney W. Grimes  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
299b50d902SRodney W. Grimes  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
309b50d902SRodney W. Grimes  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
319b50d902SRodney W. Grimes  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
329b50d902SRodney W. Grimes  * SUCH DAMAGE.
339b50d902SRodney W. Grimes  */
349b50d902SRodney W. Grimes 
359b50d902SRodney W. Grimes #ifndef lint
3629909ffeSPhilippe Charnier static const char copyright[] =
379b50d902SRodney W. Grimes "@(#) Copyright (c) 1989, 1993\n\
389b50d902SRodney W. Grimes 	The Regents of the University of California.  All rights reserved.\n";
399ba3a235SMark Murray #endif
409b50d902SRodney W. Grimes 
41421dfbcfSDavid Malone #if 0
429b50d902SRodney W. Grimes #ifndef lint
43421dfbcfSDavid Malone static char sccsid[] = "@(#)write.c	8.1 (Berkeley) 6/6/93";
4429909ffeSPhilippe Charnier #endif
45421dfbcfSDavid Malone #endif
46421dfbcfSDavid Malone 
47421dfbcfSDavid Malone #include <sys/cdefs.h>
48421dfbcfSDavid Malone __FBSDID("$FreeBSD$");
499b50d902SRodney W. Grimes 
509b50d902SRodney W. Grimes #include <sys/param.h>
5138adbfe6SConrad Meyer #include <sys/capsicum.h>
5238adbfe6SConrad Meyer #include <sys/filio.h>
539b50d902SRodney W. Grimes #include <sys/signal.h>
549b50d902SRodney W. Grimes #include <sys/stat.h>
559b50d902SRodney W. Grimes #include <sys/time.h>
5638adbfe6SConrad Meyer 
5738adbfe6SConrad Meyer #include <capsicum_helpers.h>
589b50d902SRodney W. Grimes #include <ctype.h>
5929909ffeSPhilippe Charnier #include <err.h>
6038adbfe6SConrad Meyer #include <errno.h>
6177caf211SAndrey A. Chernov #include <locale.h>
622d754ac8SAlexander Langer #include <paths.h>
639b50d902SRodney W. Grimes #include <pwd.h>
649b50d902SRodney W. Grimes #include <stdio.h>
65421dfbcfSDavid Malone #include <stdlib.h>
669b50d902SRodney W. Grimes #include <string.h>
6729909ffeSPhilippe Charnier #include <unistd.h>
68ab90a4d1SEd Schouten #include <utmpx.h>
690de6400bSGleb Smirnoff #include <wchar.h>
700de6400bSGleb Smirnoff #include <wctype.h>
719b50d902SRodney W. Grimes 
723f330d7dSWarner Losh void done(int);
7338adbfe6SConrad Meyer void do_write(int, char *, char *, const char *);
743f330d7dSWarner Losh static void usage(void);
7538adbfe6SConrad Meyer int term_chk(int, char *, int *, time_t *, int);
760de6400bSGleb Smirnoff void wr_fputs(wchar_t *s);
7738adbfe6SConrad Meyer void search_utmp(int, char *, char *, char *, uid_t);
783f330d7dSWarner Losh int utmp_chk(char *, char *);
799b50d902SRodney W. Grimes 
8029909ffeSPhilippe Charnier int
81f4ac32deSDavid Malone main(int argc, char **argv)
829b50d902SRodney W. Grimes {
8338adbfe6SConrad Meyer 	unsigned long cmds[] = { TIOCGETA, TIOCGWINSZ, FIODGNAME };
8438adbfe6SConrad Meyer 	cap_rights_t rights;
8538adbfe6SConrad Meyer 	struct passwd *pwd;
869b50d902SRodney W. Grimes 	time_t atime;
879b50d902SRodney W. Grimes 	uid_t myuid;
889b50d902SRodney W. Grimes 	int msgsok, myttyfd;
8929909ffeSPhilippe Charnier 	char tty[MAXPATHLEN], *mytty;
9038adbfe6SConrad Meyer 	const char *login;
9138adbfe6SConrad Meyer 	int devfd;
929b50d902SRodney W. Grimes 
9377caf211SAndrey A. Chernov 	(void)setlocale(LC_CTYPE, "");
9477caf211SAndrey A. Chernov 
9538adbfe6SConrad Meyer 	devfd = open(_PATH_DEV, O_RDONLY);
9638adbfe6SConrad Meyer 	if (devfd < 0)
9738adbfe6SConrad Meyer 		err(1, "open(/dev)");
9838adbfe6SConrad Meyer 	cap_rights_init(&rights, CAP_FCNTL, CAP_FSTAT, CAP_IOCTL, CAP_LOOKUP,
9938adbfe6SConrad Meyer 	    CAP_PWRITE);
10038adbfe6SConrad Meyer 	if (cap_rights_limit(devfd, &rights) < 0 && errno != ENOSYS)
10138adbfe6SConrad Meyer 		err(1, "can't limit devfd rights");
10238adbfe6SConrad Meyer 
10338adbfe6SConrad Meyer 	/*
10438adbfe6SConrad Meyer 	 * Can't use capsicum helpers here because we need the additional
10538adbfe6SConrad Meyer 	 * FIODGNAME ioctl.
10638adbfe6SConrad Meyer 	 */
10738adbfe6SConrad Meyer 	cap_rights_init(&rights, CAP_FCNTL, CAP_FSTAT, CAP_IOCTL, CAP_READ,
10838adbfe6SConrad Meyer 	    CAP_WRITE);
10938adbfe6SConrad Meyer 	if ((cap_rights_limit(STDIN_FILENO, &rights) < 0 && errno != ENOSYS) ||
11038adbfe6SConrad Meyer 	    (cap_rights_limit(STDOUT_FILENO, &rights) < 0 && errno != ENOSYS) ||
11138adbfe6SConrad Meyer 	    (cap_rights_limit(STDERR_FILENO, &rights) < 0 && errno != ENOSYS) ||
11238adbfe6SConrad Meyer 	    (cap_ioctls_limit(STDIN_FILENO, cmds, nitems(cmds)) < 0 && errno != ENOSYS) ||
11338adbfe6SConrad Meyer 	    (cap_ioctls_limit(STDOUT_FILENO, cmds, nitems(cmds)) < 0 && errno != ENOSYS) ||
11438adbfe6SConrad Meyer 	    (cap_ioctls_limit(STDERR_FILENO, cmds, nitems(cmds)) < 0 && errno != ENOSYS) ||
11538adbfe6SConrad Meyer 	    (cap_fcntls_limit(STDIN_FILENO, CAP_FCNTL_GETFL) < 0 && errno != ENOSYS) ||
11638adbfe6SConrad Meyer 	    (cap_fcntls_limit(STDOUT_FILENO, CAP_FCNTL_GETFL) < 0 && errno != ENOSYS) ||
11738adbfe6SConrad Meyer 	    (cap_fcntls_limit(STDERR_FILENO, CAP_FCNTL_GETFL) < 0 && errno != ENOSYS))
11838adbfe6SConrad Meyer 		err(1, "can't limit stdio rights");
11938adbfe6SConrad Meyer 
12038adbfe6SConrad Meyer 	caph_cache_catpages();
12138adbfe6SConrad Meyer 	caph_cache_tzdata();
12238adbfe6SConrad Meyer 
12338adbfe6SConrad Meyer 	/*
12438adbfe6SConrad Meyer 	 * Cache UTX database fds.
12538adbfe6SConrad Meyer 	 */
12638adbfe6SConrad Meyer 	setutxent();
12738adbfe6SConrad Meyer 
12838adbfe6SConrad Meyer 	/*
12938adbfe6SConrad Meyer 	 * Determine our login name before we reopen() stdout
13038adbfe6SConrad Meyer 	 * and before entering capability sandbox.
13138adbfe6SConrad Meyer 	 */
13238adbfe6SConrad Meyer 	myuid = getuid();
13338adbfe6SConrad Meyer 	if ((login = getlogin()) == NULL) {
13438adbfe6SConrad Meyer 		if ((pwd = getpwuid(myuid)))
13538adbfe6SConrad Meyer 			login = pwd->pw_name;
13638adbfe6SConrad Meyer 		else
13738adbfe6SConrad Meyer 			login = "???";
13838adbfe6SConrad Meyer 	}
13938adbfe6SConrad Meyer 
14038adbfe6SConrad Meyer 	if (cap_enter() < 0 && errno != ENOSYS)
14138adbfe6SConrad Meyer 		err(1, "cap_enter");
14238adbfe6SConrad Meyer 
143759484baSTim J. Robbins 	while (getopt(argc, argv, "") != -1)
144759484baSTim J. Robbins 		usage();
145759484baSTim J. Robbins 	argc -= optind;
146759484baSTim J. Robbins 	argv += optind;
147759484baSTim J. Robbins 
1489b50d902SRodney W. Grimes 	/* check that sender has write enabled */
1499b50d902SRodney W. Grimes 	if (isatty(fileno(stdin)))
1509b50d902SRodney W. Grimes 		myttyfd = fileno(stdin);
1519b50d902SRodney W. Grimes 	else if (isatty(fileno(stdout)))
1529b50d902SRodney W. Grimes 		myttyfd = fileno(stdout);
1539b50d902SRodney W. Grimes 	else if (isatty(fileno(stderr)))
1549b50d902SRodney W. Grimes 		myttyfd = fileno(stderr);
15529909ffeSPhilippe Charnier 	else
15629909ffeSPhilippe Charnier 		errx(1, "can't find your tty");
15729909ffeSPhilippe Charnier 	if (!(mytty = ttyname(myttyfd)))
15829909ffeSPhilippe Charnier 		errx(1, "can't find your tty's name");
159a4cc298aSJohn Baldwin 	if (!strncmp(mytty, _PATH_DEV, strlen(_PATH_DEV)))
160a4cc298aSJohn Baldwin 		mytty += strlen(_PATH_DEV);
16138adbfe6SConrad Meyer 	if (term_chk(devfd, mytty, &msgsok, &atime, 1))
1629b50d902SRodney W. Grimes 		exit(1);
16329909ffeSPhilippe Charnier 	if (!msgsok)
16429909ffeSPhilippe Charnier 		errx(1, "you have write permission turned off");
1659b50d902SRodney W. Grimes 
1669b50d902SRodney W. Grimes 	/* check args */
1679b50d902SRodney W. Grimes 	switch (argc) {
168759484baSTim J. Robbins 	case 1:
16938adbfe6SConrad Meyer 		search_utmp(devfd, argv[0], tty, mytty, myuid);
17038adbfe6SConrad Meyer 		do_write(devfd, tty, mytty, login);
1719b50d902SRodney W. Grimes 		break;
172759484baSTim J. Robbins 	case 2:
173759484baSTim J. Robbins 		if (!strncmp(argv[1], _PATH_DEV, strlen(_PATH_DEV)))
174759484baSTim J. Robbins 			argv[1] += strlen(_PATH_DEV);
175759484baSTim J. Robbins 		if (utmp_chk(argv[0], argv[1]))
176759484baSTim J. Robbins 			errx(1, "%s is not logged in on %s", argv[0], argv[1]);
17738adbfe6SConrad Meyer 		if (term_chk(devfd, argv[1], &msgsok, &atime, 1))
1789b50d902SRodney W. Grimes 			exit(1);
17929909ffeSPhilippe Charnier 		if (myuid && !msgsok)
180759484baSTim J. Robbins 			errx(1, "%s has messages disabled on %s", argv[0], argv[1]);
18138adbfe6SConrad Meyer 		do_write(devfd, argv[1], mytty, login);
1829b50d902SRodney W. Grimes 		break;
1839b50d902SRodney W. Grimes 	default:
18429909ffeSPhilippe Charnier 		usage();
1859b50d902SRodney W. Grimes 	}
18677caf211SAndrey A. Chernov 	done(0);
18729909ffeSPhilippe Charnier 	return (0);
18829909ffeSPhilippe Charnier }
18929909ffeSPhilippe Charnier 
19029909ffeSPhilippe Charnier static void
191f4ac32deSDavid Malone usage(void)
19229909ffeSPhilippe Charnier {
19329909ffeSPhilippe Charnier 	(void)fprintf(stderr, "usage: write user [tty]\n");
19429909ffeSPhilippe Charnier 	exit(1);
1959b50d902SRodney W. Grimes }
1969b50d902SRodney W. Grimes 
1979b50d902SRodney W. Grimes /*
1989b50d902SRodney W. Grimes  * utmp_chk - checks that the given user is actually logged in on
1999b50d902SRodney W. Grimes  *     the given tty
2009b50d902SRodney W. Grimes  */
20129909ffeSPhilippe Charnier int
202f4ac32deSDavid Malone utmp_chk(char *user, char *tty)
2039b50d902SRodney W. Grimes {
20450950530SEd Schouten 	struct utmpx lu, *u;
2059b50d902SRodney W. Grimes 
20650950530SEd Schouten 	strncpy(lu.ut_line, tty, sizeof lu.ut_line);
20750950530SEd Schouten 	setutxent();
20850950530SEd Schouten 	while ((u = getutxline(&lu)) != NULL)
20950950530SEd Schouten 		if (u->ut_type == USER_PROCESS &&
21050950530SEd Schouten 		    strcmp(user, u->ut_user) == 0) {
21150950530SEd Schouten 			endutxent();
2129b50d902SRodney W. Grimes 			return(0);
2139b50d902SRodney W. Grimes 		}
21450950530SEd Schouten 	endutxent();
2159b50d902SRodney W. Grimes 	return(1);
2169b50d902SRodney W. Grimes }
2179b50d902SRodney W. Grimes 
2189b50d902SRodney W. Grimes /*
2199b50d902SRodney W. Grimes  * search_utmp - search utmp for the "best" terminal to write to
2209b50d902SRodney W. Grimes  *
2219b50d902SRodney W. Grimes  * Ignores terminals with messages disabled, and of the rest, returns
2229b50d902SRodney W. Grimes  * the one with the most recent access time.  Returns as value the number
2239b50d902SRodney W. Grimes  * of the user's terminals with messages enabled, or -1 if the user is
2249b50d902SRodney W. Grimes  * not logged in at all.
2259b50d902SRodney W. Grimes  *
2269b50d902SRodney W. Grimes  * Special case for writing to yourself - ignore the terminal you're
2279b50d902SRodney W. Grimes  * writing from, unless that's the only terminal with messages enabled.
2289b50d902SRodney W. Grimes  */
22929909ffeSPhilippe Charnier void
23038adbfe6SConrad Meyer search_utmp(int devfd, char *user, char *tty, char *mytty, uid_t myuid)
2319b50d902SRodney W. Grimes {
23250950530SEd Schouten 	struct utmpx *u;
2339b50d902SRodney W. Grimes 	time_t bestatime, atime;
23450950530SEd Schouten 	int nloggedttys, nttys, msgsok, user_is_me;
2359b50d902SRodney W. Grimes 
2369b50d902SRodney W. Grimes 	nloggedttys = nttys = 0;
2379b50d902SRodney W. Grimes 	bestatime = 0;
2389b50d902SRodney W. Grimes 	user_is_me = 0;
23950950530SEd Schouten 
24050950530SEd Schouten 	setutxent();
24150950530SEd Schouten 	while ((u = getutxent()) != NULL)
24250950530SEd Schouten 		if (u->ut_type == USER_PROCESS &&
24350950530SEd Schouten 		    strcmp(user, u->ut_user) == 0) {
2449b50d902SRodney W. Grimes 			++nloggedttys;
24538adbfe6SConrad Meyer 			if (term_chk(devfd, u->ut_line, &msgsok, &atime, 0))
2469b50d902SRodney W. Grimes 				continue;	/* bad term? skip */
2479b50d902SRodney W. Grimes 			if (myuid && !msgsok)
2489b50d902SRodney W. Grimes 				continue;	/* skip ttys with msgs off */
24950950530SEd Schouten 			if (strcmp(u->ut_line, mytty) == 0) {
2509b50d902SRodney W. Grimes 				user_is_me = 1;
2519b50d902SRodney W. Grimes 				continue;	/* don't write to yourself */
2529b50d902SRodney W. Grimes 			}
2539b50d902SRodney W. Grimes 			++nttys;
2549b50d902SRodney W. Grimes 			if (atime > bestatime) {
2559b50d902SRodney W. Grimes 				bestatime = atime;
25650950530SEd Schouten 				(void)strlcpy(tty, u->ut_line, MAXPATHLEN);
2579b50d902SRodney W. Grimes 			}
2589b50d902SRodney W. Grimes 		}
25950950530SEd Schouten 	endutxent();
2609b50d902SRodney W. Grimes 
26129909ffeSPhilippe Charnier 	if (nloggedttys == 0)
26229909ffeSPhilippe Charnier 		errx(1, "%s is not logged in", user);
2639b50d902SRodney W. Grimes 	if (nttys == 0) {
2649b50d902SRodney W. Grimes 		if (user_is_me) {		/* ok, so write to yourself! */
26550950530SEd Schouten 			(void)strlcpy(tty, mytty, MAXPATHLEN);
2669b50d902SRodney W. Grimes 			return;
2679b50d902SRodney W. Grimes 		}
26829909ffeSPhilippe Charnier 		errx(1, "%s has messages disabled", user);
2699b50d902SRodney W. Grimes 	} else if (nttys > 1) {
27029909ffeSPhilippe Charnier 		warnx("%s is logged in more than once; writing to %s", user, tty);
2719b50d902SRodney W. Grimes 	}
2729b50d902SRodney W. Grimes }
2739b50d902SRodney W. Grimes 
2749b50d902SRodney W. Grimes /*
2759b50d902SRodney W. Grimes  * term_chk - check that a terminal exists, and get the message bit
2769b50d902SRodney W. Grimes  *     and the access time
2779b50d902SRodney W. Grimes  */
27829909ffeSPhilippe Charnier int
27938adbfe6SConrad Meyer term_chk(int devfd, char *tty, int *msgsokP, time_t *atimeP, int showerror)
2809b50d902SRodney W. Grimes {
2819b50d902SRodney W. Grimes 	struct stat s;
2829b50d902SRodney W. Grimes 
28338adbfe6SConrad Meyer 	if (fstatat(devfd, tty, &s, 0) < 0) {
2849b50d902SRodney W. Grimes 		if (showerror)
28538adbfe6SConrad Meyer 			warn("%s%s", _PATH_DEV, tty);
2869b50d902SRodney W. Grimes 		return(1);
2879b50d902SRodney W. Grimes 	}
2889b50d902SRodney W. Grimes 	*msgsokP = (s.st_mode & (S_IWRITE >> 3)) != 0;	/* group write bit */
2899b50d902SRodney W. Grimes 	*atimeP = s.st_atime;
2909b50d902SRodney W. Grimes 	return(0);
2919b50d902SRodney W. Grimes }
2929b50d902SRodney W. Grimes 
2939b50d902SRodney W. Grimes /*
2949b50d902SRodney W. Grimes  * do_write - actually make the connection
2959b50d902SRodney W. Grimes  */
29629909ffeSPhilippe Charnier void
29738adbfe6SConrad Meyer do_write(int devfd, char *tty, char *mytty, const char *login)
2989b50d902SRodney W. Grimes {
2999ba3a235SMark Murray 	char *nows;
30029909ffeSPhilippe Charnier 	time_t now;
30138adbfe6SConrad Meyer 	char host[MAXHOSTNAMELEN];
3020de6400bSGleb Smirnoff 	wchar_t line[512];
30338adbfe6SConrad Meyer 	int fd;
3049b50d902SRodney W. Grimes 
30538adbfe6SConrad Meyer 	fd = openat(devfd, tty, O_WRONLY);
30638adbfe6SConrad Meyer 	if (fd < 0)
30738adbfe6SConrad Meyer 		err(1, "openat(%s%s)", _PATH_DEV, tty);
30838adbfe6SConrad Meyer 	fclose(stdout);
30938adbfe6SConrad Meyer 	stdout = fdopen(fd, "w");
31038adbfe6SConrad Meyer 	if (stdout == NULL)
31138adbfe6SConrad Meyer 		err(1, "%s%s", _PATH_DEV, tty);
3129b50d902SRodney W. Grimes 
3139b50d902SRodney W. Grimes 	(void)signal(SIGINT, done);
3149b50d902SRodney W. Grimes 	(void)signal(SIGHUP, done);
3159b50d902SRodney W. Grimes 
3169b50d902SRodney W. Grimes 	/* print greeting */
3179b50d902SRodney W. Grimes 	if (gethostname(host, sizeof(host)) < 0)
3189b50d902SRodney W. Grimes 		(void)strcpy(host, "???");
3199b50d902SRodney W. Grimes 	now = time((time_t *)NULL);
3209b50d902SRodney W. Grimes 	nows = ctime(&now);
3219b50d902SRodney W. Grimes 	nows[16] = '\0';
3229b50d902SRodney W. Grimes 	(void)printf("\r\n\007\007\007Message from %s@%s on %s at %s ...\r\n",
3239b50d902SRodney W. Grimes 	    login, host, mytty, nows + 11);
3249b50d902SRodney W. Grimes 
3250de6400bSGleb Smirnoff 	while (fgetws(line, sizeof(line)/sizeof(wchar_t), stdin) != NULL)
3269b50d902SRodney W. Grimes 		wr_fputs(line);
3279b50d902SRodney W. Grimes }
3289b50d902SRodney W. Grimes 
3299b50d902SRodney W. Grimes /*
3309b50d902SRodney W. Grimes  * done - cleanup and exit
3319b50d902SRodney W. Grimes  */
3329b50d902SRodney W. Grimes void
333f4ac32deSDavid Malone done(int n __unused)
3349b50d902SRodney W. Grimes {
3359b50d902SRodney W. Grimes 	(void)printf("EOF\r\n");
3369b50d902SRodney W. Grimes 	exit(0);
3379b50d902SRodney W. Grimes }
3389b50d902SRodney W. Grimes 
3399b50d902SRodney W. Grimes /*
3409b50d902SRodney W. Grimes  * wr_fputs - like fputs(), but makes control characters visible and
3419b50d902SRodney W. Grimes  *     turns \n into \r\n
3429b50d902SRodney W. Grimes  */
34329909ffeSPhilippe Charnier void
3440de6400bSGleb Smirnoff wr_fputs(wchar_t *s)
3459b50d902SRodney W. Grimes {
3469b50d902SRodney W. Grimes 
3470de6400bSGleb Smirnoff #define	PUTC(c)	if (putwchar(c) == WEOF) err(1, NULL);
3489b50d902SRodney W. Grimes 
3490de6400bSGleb Smirnoff 	for (; *s != L'\0'; ++s) {
3500de6400bSGleb Smirnoff 		if (*s == L'\n') {
3510de6400bSGleb Smirnoff 			PUTC(L'\r');
3520de6400bSGleb Smirnoff 			PUTC(L'\n');
3530de6400bSGleb Smirnoff 		} else if (iswprint(*s) || iswspace(*s)) {
354b6c671c7SAndrey A. Chernov 			PUTC(*s);
3550de6400bSGleb Smirnoff 		} else {
3560de6400bSGleb Smirnoff 			wprintf(L"<0x%X>", *s);
3570de6400bSGleb Smirnoff 		}
3589b50d902SRodney W. Grimes 	}
3599b50d902SRodney W. Grimes 	return;
3609b50d902SRodney W. Grimes #undef PUTC
3619b50d902SRodney W. Grimes }
362