who.c revision 121548
1/*-
2 * Copyright (c) 2002 Tim J. Robbins.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD: head/usr.bin/who/who.c 121548 2003-10-26 05:05:48Z peter $");
29
30#include <sys/types.h>
31#include <sys/ioctl.h>
32#include <sys/stat.h>
33
34#include <err.h>
35#include <errno.h>
36#include <langinfo.h>
37#include <limits.h>
38#include <locale.h>
39#include <paths.h>
40#include <pwd.h>
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#include <time.h>
45#include <timeconv.h>
46#include <unistd.h>
47#include <utmp.h>
48
49static void	heading(void);
50static void	process_utmp(FILE *);
51static void	quick(FILE *);
52static void	row(struct utmp *);
53static int	ttywidth(void);
54static void	usage(void);
55static void	whoami(FILE *);
56
57static int	Hflag;			/* Write column headings */
58static int	mflag;			/* Show info about current terminal */
59static int	qflag;			/* "Quick" mode */
60static int	sflag;			/* Show name, line, time */
61static int	Tflag;			/* Show terminal state */
62static int	uflag;			/* Show idle time */
63
64int
65main(int argc, char *argv[])
66{
67	int ch;
68	const char *file;
69	FILE *fp;
70
71	setlocale(LC_TIME, "");
72
73	while ((ch = getopt(argc, argv, "HTmqsu")) != -1) {
74		switch (ch) {
75		case 'H':		/* Write column headings */
76			Hflag = 1;
77			break;
78		case 'T':		/* Show terminal state */
79			Tflag = 1;
80			break;
81		case 'm':		/* Show info about current terminal */
82			mflag = 1;
83			break;
84		case 'q':		/* "Quick" mode */
85			qflag = 1;
86			break;
87		case 's':		/* Show name, line, time */
88			sflag = 1;
89			break;
90		case 'u':		/* Show idle time */
91			uflag = 1;
92			break;
93		default:
94			usage();
95			/*NOTREACHED*/
96		}
97	}
98	argc -= optind;
99	argv += optind;
100
101	if (argc >= 2 && strcmp(argv[0], "am") == 0 &&
102	    (strcmp(argv[1], "i") == 0 || strcmp(argv[1], "I") == 0)) {
103		/* "who am i" or "who am I", equivalent to -m */
104		mflag = 1;
105		argc -= 2;
106		argv += 2;
107	}
108	if (argc > 1)
109		usage();
110
111	if (*argv != NULL)
112		file = *argv;
113	else
114		file = _PATH_UTMP;
115	if ((fp = fopen(file, "r")) == NULL)
116		err(1, "%s", file);
117
118	if (qflag)
119		quick(fp);
120	else {
121		if (sflag)
122			Tflag = uflag = 0;
123		if (Hflag)
124			heading();
125		if (mflag)
126			whoami(fp);
127		else
128			process_utmp(fp);
129	}
130
131	fclose(fp);
132
133	exit(0);
134}
135
136static void
137usage(void)
138{
139
140	fprintf(stderr, "usage: who [-HmqsTu] [am I] [file]\n");
141	exit(1);
142}
143
144static void
145heading(void)
146{
147
148	printf("%-*s ", UT_NAMESIZE, "NAME");
149	if (Tflag)
150		printf("S ");
151	printf("%-*s ", UT_LINESIZE, "LINE");
152	printf("%-*s ", 12, "TIME");
153	if (uflag)
154		printf("IDLE  ");
155	printf("%-*s", UT_HOSTSIZE, "FROM");
156	putchar('\n');
157}
158
159static void
160row(struct utmp *ut)
161{
162	char buf[80], tty[sizeof(_PATH_DEV) + UT_LINESIZE];
163	struct stat sb;
164	time_t idle, t;
165	static int d_first = -1;
166	struct tm *tm;
167	char state;
168
169	if (d_first < 0)
170		d_first = (*nl_langinfo(D_MD_ORDER) == 'd');
171
172	if (Tflag || uflag) {
173		snprintf(tty, sizeof(tty), "%s%.*s", _PATH_DEV,
174			UT_LINESIZE, ut->ut_line);
175		state = '?';
176		idle = 0;
177		if (stat(tty, &sb) == 0) {
178			state = sb.st_mode & (S_IWOTH|S_IWGRP) ?
179			    '+' : '-';
180			idle = time(NULL) - sb.st_mtime;
181		}
182	}
183
184	printf("%-*.*s ", UT_NAMESIZE, UT_NAMESIZE, ut->ut_name);
185	if (Tflag)
186		printf("%c ", state);
187	printf("%-*.*s ", UT_LINESIZE, UT_LINESIZE, ut->ut_line);
188	t = _time32_to_time(ut->ut_time);
189	tm = localtime(&t);
190	strftime(buf, sizeof(buf), d_first ? "%e %b %R" : "%b %e %R", tm);
191	printf("%-*s ", 12, buf);
192	if (uflag) {
193		if (idle < 60)
194			printf("  .   ");
195		else if (idle < 24 * 60 * 60)
196			printf("%02d:%02d ", (int)(idle / 60 / 60),
197			    (int)(idle / 60 % 60));
198		else
199			printf(" old  ");
200	}
201	if (*ut->ut_host != '\0')
202		printf("(%.*s)", UT_HOSTSIZE, ut->ut_host);
203	putchar('\n');
204}
205
206static void
207process_utmp(FILE *fp)
208{
209	struct utmp ut;
210
211	while (fread(&ut, sizeof(ut), 1, fp) == 1)
212		if (*ut.ut_name != '\0')
213			row(&ut);
214}
215
216static void
217quick(FILE *fp)
218{
219	struct utmp ut;
220	int col, ncols, num;
221
222	ncols = ttywidth();
223	col = num = 0;
224	while (fread(&ut, sizeof(ut), 1, fp) == 1) {
225		if (*ut.ut_name == '\0')
226			continue;
227		printf("%-*.*s", UT_NAMESIZE, UT_NAMESIZE, ut.ut_name);
228		if (++col < ncols / (UT_NAMESIZE + 1))
229			putchar(' ');
230		else {
231			col = 0;
232			putchar('\n');
233		}
234		num++;
235	}
236	if (col != 0)
237		putchar('\n');
238
239	printf("# users = %d\n", num);
240}
241
242static void
243whoami(FILE *fp)
244{
245	struct utmp ut;
246	struct passwd *pwd;
247	const char *name, *p, *tty;
248
249	if ((tty = ttyname(STDIN_FILENO)) == NULL)
250		tty = "tty??";
251	else if ((p = strrchr(tty, '/')) != NULL)
252		tty = p + 1;
253
254	/* Search utmp for our tty, dump first matching record. */
255	while (fread(&ut, sizeof(ut), 1, fp) == 1)
256		if (*ut.ut_name != '\0' && strncmp(ut.ut_line, tty,
257		    UT_LINESIZE) == 0) {
258			row(&ut);
259			return;
260		}
261
262	/* Not found; fill the utmp structure with the information we have. */
263	memset(&ut, 0, sizeof(ut));
264	if ((pwd = getpwuid(getuid())) != NULL)
265		name = pwd->pw_name;
266	else
267		name = "?";
268	strncpy(ut.ut_name, name, UT_NAMESIZE);
269	strncpy(ut.ut_line, tty, UT_LINESIZE);
270	ut.ut_time = _time_to_time32(time(NULL));
271	row(&ut);
272}
273
274static int
275ttywidth(void)
276{
277	struct winsize ws;
278	long width;
279	char *cols, *ep;
280
281	if ((cols = getenv("COLUMNS")) != NULL && *cols != '\0') {
282		errno = 0;
283		width = strtol(cols, &ep, 10);
284		if (errno || width <= 0 || width > INT_MAX || ep == cols ||
285		    *ep != '\0')
286			warnx("invalid COLUMNS environment variable ignored");
287		else
288			return (width);
289	}
290	if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1)
291		return (ws.ws_col);
292
293	return (80);
294}
295