xref: /illumos-gate/usr/src/cmd/who/who.c (revision ba2be53024c0b999e74ba9adcd7d80fec5df8c57)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
22 /*	  All Rights Reserved  	*/
23 
24 
25 /*
26  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
27  * Use is subject to license terms.
28  */
29 
30 #pragma ident	"%Z%%M%	%I%	%E% SMI"
31 
32 /*
33  *	This program analyzes information found in /var/adm/utmpx
34  *
35  *	Additionally information is gathered from /etc/inittab
36  *	if requested.
37  *
38  *
39  *	Syntax:
40  *
41  *		who am i	Displays info on yourself
42  *
43  *		who -a		Displays information about All
44  *				entries in /var/adm/utmpx
45  *
46  *		who -b		Displays info on last boot
47  *
48  *		who -d		Displays info on DEAD PROCESSES
49  *
50  *		who -H		Displays HEADERS for output
51  *
52  *		who -l 		Displays info on LOGIN entries
53  *
54  *		who -m 		Same as who am i
55  *
56  *		who -p 		Displays info on PROCESSES spawned by init
57  *
58  *		who -q		Displays short information on
59  *				current users who LOGGED ON
60  *
61  *		who -r		Displays info of current run-level
62  *
63  *		who -s		Displays requested info in SHORT form
64  *
65  *		who -t		Displays info on TIME changes
66  *
67  *		who -T		Displays writeability of each user
68  *				(+ writeable, - non-writeable, ? hung)
69  *
70  *		who -u		Displays LONG info on users
71  *				who have LOGGED ON
72  */
73 
74 #define		DATE_FMT	"%b %e %H:%M"
75 
76 /*
77  *  %b	Abbreviated month name
78  *  %e	Day of month
79  *  %H	hour (24-hour clock)
80  *  %M  minute
81  */
82 #include	<errno.h>
83 #include	<fcntl.h>
84 #include	<stdio.h>
85 #include	<string.h>
86 #include	<sys/types.h>
87 #include	<unistd.h>
88 #include	<stdlib.h>
89 #include	<sys/stat.h>
90 #include	<time.h>
91 #include	<utmpx.h>
92 #include	<locale.h>
93 #include	<pwd.h>
94 #include	<limits.h>
95 
96 static void process(void);
97 static void ck_file(char *);
98 static void dump(void);
99 
100 static struct	utmpx *utmpp;	/* pointer for getutxent()	*/
101 
102 /*
103  * utmpx defines wider fields for user and line.  For compatibility of output,
104  * we are limiting these to the old maximums in utmp. Define UTMPX_NAMELEN
105  * to use the full lengths.
106  */
107 #ifndef UTMPX_NAMELEN
108 /* XXX - utmp - fix name length */
109 #define	NMAX	(_POSIX_LOGIN_NAME_MAX - 1)
110 #define	LMAX	12
111 #else /* UTMPX_NAMELEN */
112 #define	NMAX	(sizeof (utmpp->ut_user))
113 #define	LMAX	(sizeof (utmpp->ut_line))
114 #endif
115 
116 static char	comment[BUFSIZ]; /* holds inittab comment	*/
117 static char	errmsg[BUFSIZ];	/* used in snprintf for errors	*/
118 static int	fildes;		/* file descriptor for inittab	*/
119 static int	Hopt = 0;	/* 1 = who -H			*/
120 static char	*inittab;	/* ptr to inittab contents	*/
121 static char	*iinit;		/* index into inittab		*/
122 static int	justme = 0;	/* 1 = who am i			*/
123 static struct	tm *lptr;	/* holds user login time	*/
124 static char	*myname;	/* pointer to invoker's name 	*/
125 static char	*mytty;		/* holds device user is on	*/
126 static char	nameval[sizeof (utmpp->ut_user) + 1]; /*  invoker's name */
127 static int	number = 8;	/* number of users per -q line	*/
128 static int	optcnt = 0;	/* keeps count of options	*/
129 static char	outbuf[BUFSIZ];	/* buffer for output		*/
130 static char	*program;	/* holds name of this program	*/
131 #ifdef	XPG4
132 static int	aopt = 0;	/* 1 = who -a			*/
133 static int	dopt = 0;	/* 1 = who -d			*/
134 #endif	/* XPG4 */
135 static int	qopt = 0;	/* 1 = who -q			*/
136 static int	sopt = 0;	/* 1 = who -s			*/
137 static struct	stat stbuf;	/* area for stat buffer		*/
138 static struct	stat *stbufp;	/* ptr to structure		*/
139 static int	terse = 1;	/* 1 = print terse msgs		*/
140 static int	Topt = 0;	/* 1 = who -T			*/
141 static time_t	timnow;		/* holds current time		*/
142 static int	totlusrs = 0;	/* cntr for users on system	*/
143 static int	uopt = 0;	/* 1 = who -u			*/
144 static char	user[sizeof (utmpp->ut_user) + 1]; /* holds user name */
145 static int	validtype[UTMAXTYPE+1];	/* holds valid types	*/
146 static int	wrap;		/* flag to indicate wrap	*/
147 static char	time_buf[128];	/* holds date and time string	*/
148 static char	*end;		/* used in strtol for end pointer */
149 
150 int
151 main(int argc, char **argv)
152 {
153 	int	goerr = 0;	/* non-zero indicates cmd error	*/
154 	int	i;
155 	int	optsw;		/* switch for while of getopt()	*/
156 
157 	(void) setlocale(LC_ALL, "");
158 
159 #if	!defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
160 #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
161 #endif
162 	(void) textdomain(TEXT_DOMAIN);
163 
164 	validtype[USER_PROCESS] = 1;
165 	validtype[EMPTY] = 0;
166 	stbufp = &stbuf;
167 
168 	/*
169 	 *	Strip off path name of this command
170 	 */
171 	for (i = strlen(argv[0]); i >= 0 && argv[0][i] != '/'; --i);
172 	if (i >= 0)
173 		argv[0] += i+1;
174 	program = argv[0];
175 
176 	/*
177 	 *	Buffer stdout for speed
178 	 */
179 	setbuf(stdout, outbuf);
180 
181 	/*
182 	 *	Retrieve options specified on command line
183 	 *	XCU4 - add -m option
184 	 */
185 	while ((optsw = getopt(argc, argv, "abdHlmn:pqrstTu")) != EOF) {
186 		optcnt++;
187 		switch (optsw) {
188 
189 		case 'a':
190 			optcnt += 7;
191 			validtype[BOOT_TIME] = 1;
192 			validtype[DEAD_PROCESS] = 1;
193 			validtype[LOGIN_PROCESS] = 1;
194 			validtype[INIT_PROCESS] = 1;
195 			validtype[RUN_LVL] = 1;
196 			validtype[OLD_TIME] = 1;
197 			validtype[NEW_TIME] = 1;
198 			validtype[USER_PROCESS] = 1;
199 #ifdef	XPG4
200 			aopt = 1;
201 #endif	/* XPG4 */
202 			uopt = 1;
203 			Topt = 1;
204 			if (!sopt) terse = 0;
205 			break;
206 
207 		case 'b':
208 			validtype[BOOT_TIME] = 1;
209 			if (!uopt) validtype[USER_PROCESS] = 0;
210 			break;
211 
212 		case 'd':
213 			validtype[DEAD_PROCESS] = 1;
214 			if (!uopt) validtype[USER_PROCESS] = 0;
215 #ifdef	XPG4
216 			dopt = 1;
217 #endif	/* XPG4 */
218 			break;
219 
220 		case 'H':
221 			optcnt--; /* Don't count Header */
222 			Hopt = 1;
223 			break;
224 
225 		case 'l':
226 			validtype[LOGIN_PROCESS] = 1;
227 			if (!uopt) validtype[USER_PROCESS] = 0;
228 			terse = 0;
229 			break;
230 		case 'm':		/* New XCU4 option */
231 			justme = 1;
232 			break;
233 
234 		case 'n':
235 			errno = 0;
236 			number = strtol(optarg, &end, 10);
237 			if (errno != 0 || *end != '\0') {
238 				(void) fprintf(stderr, gettext(
239 				    "%s: Invalid numeric argument\n"),
240 				    program);
241 				exit(1);
242 			}
243 			if (number < 1) {
244 				(void) fprintf(stderr, gettext(
245 				    "%s: Number of users per line must "
246 					"be at least 1\n"), program);
247 				exit(1);
248 			}
249 			break;
250 
251 		case 'p':
252 			validtype[INIT_PROCESS] = 1;
253 			if (!uopt) validtype[USER_PROCESS] = 0;
254 			break;
255 
256 		case 'q':
257 			qopt = 1;
258 			break;
259 
260 		case 'r':
261 			validtype[RUN_LVL] = 1;
262 			terse = 0;
263 			if (!uopt) validtype[USER_PROCESS] = 0;
264 			break;
265 
266 		case 's':
267 			sopt = 1;
268 			terse = 1;
269 			break;
270 
271 		case 't':
272 			validtype[OLD_TIME] = 1;
273 			validtype[NEW_TIME] = 1;
274 			if (!uopt) validtype[USER_PROCESS] = 0;
275 			break;
276 
277 		case 'T':
278 			Topt = 1;
279 #ifdef	XPG4
280 			terse = 1;	/* XPG4 requires -T */
281 #else	/* XPG4 */
282 			terse = 0;
283 #endif	/* XPG4 */
284 			break;
285 
286 		case 'u':
287 			uopt = 1;
288 			validtype[USER_PROCESS] = 1;
289 			if (!sopt) terse = 0;
290 			break;
291 
292 		case '?':
293 			goerr++;
294 			break;
295 		default:
296 			break;
297 		}
298 	}
299 #ifdef	XPG4
300 	/*
301 	 * XCU4 changes - check for illegal sopt, Topt & aopt combination
302 	 */
303 	if (sopt == 1) {
304 		terse = 1;
305 		if (Topt == 1 || aopt == 1)
306 		goerr++;
307 	}
308 #endif	/* XPG4 */
309 
310 	if (goerr > 0) {
311 #ifdef	XPG4
312 		/*
313 		 * XCU4 - slightly different usage with -s -a & -T
314 		 */
315 		(void) fprintf(stderr, gettext("\nUsage:\t%s"), program);
316 		(void) fprintf(stderr,
317 		    gettext(" -s [-bdHlmpqrtu] [utmpx_like_file]\n"));
318 
319 		(void) fprintf(stderr, gettext(
320 		    "\t%s [-abdHlmpqrtTu] [utmpx_like_file]\n"), program);
321 #else	/* XPG4 */
322 		(void) fprintf(stderr, gettext(
323 		    "\nUsage:\t%s [-abdHlmpqrstTu] [utmpx_like_file]\n"),
324 		    program);
325 #endif	/* XPG4 */
326 		(void) fprintf(stderr,
327 		    gettext("\t%s -q [-n x] [utmpx_like_file]\n"), program);
328 		(void) fprintf(stderr, gettext("\t%s [am i]\n"), program);
329 		/*
330 		 * XCU4 changes - be explicit with "am i" options
331 		 */
332 		(void) fprintf(stderr, gettext("\t%s [am I]\n"), program);
333 		(void) fprintf(stderr, gettext(
334 		    "a\tall (bdlprtu options)\n"));
335 		(void) fprintf(stderr, gettext("b\tboot time\n"));
336 		(void) fprintf(stderr, gettext("d\tdead processes\n"));
337 		(void) fprintf(stderr, gettext("H\tprint header\n"));
338 		(void) fprintf(stderr, gettext("l\tlogin processes\n"));
339 		(void) fprintf(stderr, gettext(
340 		    "n #\tspecify number of users per line for -q\n"));
341 		(void) fprintf(stderr,
342 		    gettext("p\tprocesses other than getty or users\n"));
343 		(void) fprintf(stderr, gettext("q\tquick %s\n"), program);
344 		(void) fprintf(stderr, gettext("r\trun level\n"));
345 		(void) fprintf(stderr, gettext(
346 		"s\tshort form of %s (no time since last output or pid)\n"),
347 		    program);
348 		(void) fprintf(stderr, gettext("t\ttime changes\n"));
349 		(void) fprintf(stderr, gettext(
350 		    "T\tstatus of tty (+ writable, - not writable, "
351 			"? hung)\n"));
352 		(void) fprintf(stderr, gettext("u\tuseful information\n"));
353 		(void) fprintf(stderr,
354 		    gettext("m\tinformation only about current terminal\n"));
355 		(void) fprintf(stderr, gettext(
356 		    "am i\tinformation about current terminal "
357 			"(same as -m)\n"));
358 		(void) fprintf(stderr, gettext(
359 		    "am I\tinformation about current terminal "
360 			"(same as -m)\n"));
361 		exit(1);
362 	}
363 
364 	/*
365 	 * XCU4: If -q option ignore all other options
366 	 */
367 	if (qopt == 1) {
368 		Hopt = 0;
369 		sopt = 0;
370 		Topt = 0;
371 		uopt = 0;
372 		justme = 0;
373 		validtype[ACCOUNTING] = 0;
374 		validtype[BOOT_TIME] = 0;
375 		validtype[DEAD_PROCESS] = 0;
376 		validtype[LOGIN_PROCESS] = 0;
377 		validtype[INIT_PROCESS] = 0;
378 		validtype[RUN_LVL] = 0;
379 		validtype[OLD_TIME] = 0;
380 		validtype[NEW_TIME] = 0;
381 		validtype[USER_PROCESS] = 1;
382 	}
383 
384 	if (argc == optind + 1) {
385 		optcnt++;
386 		ck_file(argv[optind]);
387 		(void) utmpxname(argv[optind]);
388 	}
389 
390 	/*
391 	 *	Test for 'who am i' or 'who am I'
392 	 *	XCU4 - check if justme was already set by -m option
393 	 */
394 	if (justme == 1 || (argc == 3 && strcmp(argv[1], "am") == 0 &&
395 	    ((argv[2][0] == 'i' || argv[2][0] == 'I') &&
396 		argv[2][1] == '\0'))) {
397 		justme = 1;
398 		myname = nameval;
399 		(void) cuserid(myname);
400 		if ((mytty = ttyname(fileno(stdin))) == NULL &&
401 		    (mytty = ttyname(fileno(stdout))) == NULL &&
402 		    (mytty = ttyname(fileno(stderr))) == NULL) {
403 			(void) fprintf(stderr, gettext(
404 			"Must be attached to terminal for 'am I' option\n"));
405 			(void) fflush(stderr);
406 			exit(1);
407 		} else
408 			mytty += 5; /* bump past "/dev/" */
409 	}
410 
411 	if (!terse) {
412 		if (Hopt)
413 			(void) printf(gettext(
414 	"NAME       LINE         TIME          IDLE    PID  COMMENTS\n"));
415 
416 		timnow = time(0);
417 
418 		if ((fildes = open("/etc/inittab",
419 		    O_NONBLOCK|O_RDONLY)) == -1) {
420 			(void) snprintf(errmsg, sizeof (errmsg),
421 			    gettext("%s: Cannot open /etc/inittab"), program);
422 			perror(errmsg);
423 			exit(errno);
424 		}
425 
426 		if (fstat(fildes, stbufp) == -1) {
427 			(void) snprintf(errmsg, sizeof (errmsg),
428 			    gettext("%s: Cannot stat /etc/inittab"), program);
429 			perror(errmsg);
430 			exit(errno);
431 		}
432 
433 		if ((inittab = malloc(stbufp->st_size + 1)) == NULL) {
434 			(void) snprintf(errmsg, sizeof (errmsg),
435 			    gettext("%s: Cannot allocate %ld bytes"),
436 			    program, stbufp->st_size);
437 			perror(errmsg);
438 			exit(errno);
439 		}
440 
441 		if (read(fildes, inittab, stbufp->st_size)
442 		    != stbufp->st_size) {
443 			(void) snprintf(errmsg, sizeof (errmsg),
444 			    gettext("%s: Error reading /etc/inittab"),
445 			    program);
446 			perror(errmsg);
447 			exit(errno);
448 		}
449 
450 		inittab[stbufp->st_size] = '\0';
451 		iinit = inittab;
452 	} else {
453 		if (Hopt) {
454 #ifdef	XPG4
455 			if (dopt) {
456 				(void) printf(gettext(
457 			"NAME       LINE         TIME		COMMENTS\n"));
458 			} else {
459 				(void) printf(
460 				    gettext("NAME       LINE         TIME\n"));
461 			}
462 #else	/* XPG4 */
463 			(void) printf(
464 			    gettext("NAME       LINE         TIME\n"));
465 #endif	/* XPG4 */
466 		}
467 	}
468 	process();
469 
470 	/*
471 	 *	'who -q' requires EOL upon exit,
472 	 *	followed by total line
473 	 */
474 	if (qopt)
475 		(void) printf(gettext("\n# users=%d\n"), totlusrs);
476 	return (0);
477 }
478 
479 static void
480 dump()
481 {
482 	char	device[sizeof (utmpp->ut_line) + 1];
483 	time_t hr;
484 	time_t	idle;
485 	time_t min;
486 	char	path[sizeof (utmpp->ut_line) + 6];
487 	int	pexit;
488 	int	pterm;
489 	int	rc;
490 	char	w;	/* writeability indicator */
491 
492 	/*
493 	 * Get and check user name
494 	 */
495 	if (utmpp->ut_user[0] == '\0')
496 		(void) strcpy(user, "   .");
497 	else {
498 		(void) strncpy(user, utmpp->ut_user, sizeof (user));
499 		user[sizeof (user) - 1] = '\0';
500 	}
501 	totlusrs++;
502 
503 	/*
504 	 * Do print in 'who -q' format
505 	 */
506 	if (qopt) {
507 		/*
508 		 * XCU4 - Use non user macro for correct user count
509 		 */
510 		if (((totlusrs - 1) % number) == 0 && totlusrs > 1)
511 			(void) printf("\n");
512 		(void) printf("%-*s ", NMAX, user);
513 		return;
514 	}
515 
516 
517 	pexit = (int)' ';
518 	pterm = (int)' ';
519 
520 	/*
521 	 *	Get exit info if applicable
522 	 */
523 	if (utmpp->ut_type == RUN_LVL || utmpp->ut_type == DEAD_PROCESS) {
524 		pterm = utmpp->ut_exit.e_termination;
525 		pexit = utmpp->ut_exit.e_exit;
526 	}
527 
528 	/*
529 	 *	Massage ut_xtime field
530 	 */
531 	lptr = localtime(&utmpp->ut_xtime);
532 	(void) strftime(time_buf, sizeof (time_buf),
533 	    dcgettext(NULL, DATE_FMT, LC_TIME), lptr);
534 
535 	/*
536 	 *	Get and massage device
537 	 */
538 	if (utmpp->ut_line[0] == '\0')
539 		(void) strcpy(device, "     .");
540 	else {
541 		(void) strncpy(device, utmpp->ut_line,
542 		    sizeof (utmpp->ut_line));
543 		device[sizeof (utmpp->ut_line)] = '\0';
544 	}
545 
546 	/*
547 	 *	Get writeability if requested
548 	 *	XCU4 - only print + or - for user processes
549 	 */
550 	if (Topt && (utmpp->ut_type == USER_PROCESS)) {
551 		w = '-';
552 		(void) strcpy(path, "/dev/");
553 		(void) strncpy(path + 5, utmpp->ut_line,
554 		    sizeof (utmpp->ut_line));
555 		path[5 + sizeof (utmpp->ut_line)] = '\0';
556 
557 		if ((rc = stat(path, stbufp)) == -1) w = '?';
558 		else if ((stbufp->st_mode & S_IWOTH) ||
559 		    (stbufp->st_mode & S_IWGRP))  /* Check group & other */
560 			w = '+';
561 
562 	} else
563 		w = ' ';
564 
565 	/*
566 	 *	Print the TERSE portion of the output
567 	 */
568 	(void) printf("%-*s %c %-12s %s", NMAX, user, w, device, time_buf);
569 
570 	if (!terse) {
571 		/*
572 		 *	Stat device for idle time
573 		 *	(Don't complain if you can't)
574 		 */
575 		rc = -1;
576 		if (utmpp->ut_type == USER_PROCESS) {
577 			(void) strcpy(path, "/dev/");
578 			(void) strncpy(path + 5, utmpp->ut_line,
579 			    sizeof (utmpp->ut_line));
580 			path[5 + sizeof (utmpp->ut_line)] = '\0';
581 			rc = stat(path, stbufp);
582 		}
583 		if (rc != -1) {
584 			idle = timnow - stbufp->st_mtime;
585 			hr = idle/3600;
586 			min = (unsigned)(idle/60)%60;
587 			if (hr == 0 && min == 0)
588 				(void) printf(gettext("   .  "));
589 			else {
590 				if (hr < 24)
591 					(void) printf(" %2d:%2.2d", (int)hr,
592 					    (int)min);
593 				else
594 					(void) printf(gettext("  old "));
595 			}
596 		}
597 
598 		/*
599 		 *	Add PID for verbose output
600 		 */
601 		if (utmpp->ut_type != BOOT_TIME &&
602 		    utmpp->ut_type != RUN_LVL &&
603 		    utmpp->ut_type != ACCOUNTING)
604 			(void) printf("  %5ld", utmpp->ut_pid);
605 
606 		/*
607 		 *	Handle /etc/inittab comment
608 		 */
609 		if (utmpp->ut_type == DEAD_PROCESS) {
610 			(void) printf(gettext("  id=%4.4s "),
611 			    utmpp->ut_id);
612 			(void) printf(gettext("term=%-3d "), pterm);
613 			(void) printf(gettext("exit=%d  "), pexit);
614 		} else if (utmpp->ut_type != INIT_PROCESS) {
615 			/*
616 			 *	Search for each entry in inittab
617 			 *	string. Keep our place from
618 			 *	search to search to try and
619 			 *	minimize the work. Wrap once if needed
620 			 *	for each entry.
621 			 */
622 			wrap = 0;
623 			/*
624 			 *	Look for a line beginning with
625 			 *	utmpp->ut_id
626 			 */
627 			while ((rc = strncmp(utmpp->ut_id, iinit,
628 			    strcspn(iinit, ":"))) != 0) {
629 				for (; *iinit != '\n'; iinit++);
630 				iinit++;
631 
632 				/*
633 				 *	Wrap once if necessary to
634 				 *	find entry in inittab
635 				 */
636 				if (*iinit == '\0') {
637 					if (!wrap) {
638 						iinit = inittab;
639 						wrap = 1;
640 					}
641 				}
642 			}
643 
644 			if (*iinit != '\0') {
645 				/*
646 				 *	We found our entry
647 				 */
648 				for (iinit++; *iinit != '#' &&
649 					 *iinit != '\n'; iinit++);
650 				if (*iinit == '#') {
651 					for (iinit++; *iinit == ' ' ||
652 						 *iinit == '\t'; iinit++);
653 					for (rc = 0; *iinit != '\n'; iinit++)
654 						comment[rc++] = *iinit;
655 					comment[rc] = '\0';
656 				} else
657 					(void) strcpy(comment, " ");
658 
659 				(void) printf("  %s", comment);
660 			} else
661 				iinit = inittab;	/* Reset pointer */
662 		}
663 		if (utmpp->ut_type == INIT_PROCESS)
664 			(void) printf(gettext("  id=%4.4s"), utmpp->ut_id);
665 	}
666 #ifdef	XPG4
667 	else
668 		if (dopt && utmpp->ut_type == DEAD_PROCESS) {
669 			(void) printf(gettext("\tterm=%-3d "), pterm);
670 			(void) printf(gettext("exit=%d  "), pexit);
671 		}
672 #endif	/* XPG4 */
673 
674 
675 	/*
676 	 *	Handle RUN_LVL process - If no alt. file - Only one!
677 	 */
678 	if (utmpp->ut_type == RUN_LVL) {
679 		(void) printf("     %c  %5ld  %c", pterm, utmpp->ut_pid,
680 		    pexit);
681 		if (optcnt == 1 && !validtype[USER_PROCESS]) {
682 			(void) printf("\n");
683 			exit(0);
684 		}
685 	}
686 
687 	/*
688 	 *	Handle BOOT_TIME process -  If no alt. file - Only one!
689 	 */
690 	if (utmpp->ut_type == BOOT_TIME) {
691 		if (optcnt == 1 && !validtype[USER_PROCESS]) {
692 			(void) printf("\n");
693 			exit(0);
694 		}
695 	}
696 
697 	/*
698 	 *	Get remote host from utmpx structure
699 	 */
700 	if (utmpp && utmpp->ut_host[0])
701 		(void) printf("\t(%.*s)", sizeof (utmpp->ut_host),
702 		    utmpp->ut_host);
703 
704 	/*
705 	 *	Now, put on the trailing EOL
706 	 */
707 	(void) printf("\n");
708 }
709 
710 static void
711 process()
712 {
713 	struct passwd *pwp;
714 	int i = 0;
715 	char *ttname;
716 
717 	/*
718 	 *	Loop over each entry in /var/adm/utmpx
719 	 */
720 
721 	setutxent();
722 	while ((utmpp = getutxent()) != NULL) {
723 #ifdef DEBUG
724 	(void) printf(
725 	    "ut_user '%s'\nut_id '%s'\nut_line '%s'\nut_type '%d'\n\n",
726 	    utmpp->ut_user, utmpp->ut_id, utmpp->ut_line, utmpp->ut_type);
727 #endif
728 		if (utmpp->ut_type <= UTMAXTYPE) {
729 			/*
730 			 *	Handle "am i"
731 			 */
732 			if (justme) {
733 				if (strncmp(myname, utmpp->ut_user,
734 				    sizeof (utmpp->ut_user)) == 0 &&
735 				    strncmp(mytty, utmpp->ut_line,
736 					sizeof (utmpp->ut_line)) == 0 &&
737 				    utmpp->ut_type == USER_PROCESS) {
738 					/*
739 					 * we have have found ourselves
740 					 * in the utmp file and the entry
741 					 * is a user process, this is not
742 					 * meaningful otherwise
743 					 *
744 					 */
745 
746 					dump();
747 					exit(0);
748 				}
749 				continue;
750 			}
751 
752 			/*
753 			 *	Print the line if we want it
754 			 */
755 			if (validtype[utmpp->ut_type]) {
756 #ifdef	XPG4
757 				if (utmpp->ut_type == LOGIN_PROCESS) {
758 					if ((utmpp->ut_line[0] == '\0') ||
759 					(strcmp(utmpp->ut_user, "LOGIN") != 0))
760 						continue;
761 				}
762 #endif	/* XPG4 */
763 				dump();
764 			}
765 		} else {
766 			(void) fprintf(stderr,
767 			    gettext("%s: Error --- entry has ut_type "
768 				"of %d\n"), program, utmpp->ut_type);
769 			(void) fprintf(stderr,
770 			    gettext(" when maximum is %d\n"), UTMAXTYPE);
771 		}
772 	}
773 
774 	/*
775 	 * If justme is set at this point than the utmp entry
776 	 * was not found.
777 	 */
778 	if (justme) {
779 		static struct utmpx utmpt;
780 
781 		pwp = getpwuid(geteuid());
782 		if (pwp != NULL)
783 			while (i < (int)sizeof (utmpt.ut_user) &&
784 			    *pwp->pw_name != 0)
785 				utmpt.ut_user[i++] = *pwp->pw_name++;
786 
787 		ttname = ttyname(1);
788 
789 		i = 0;
790 		if (ttname != NULL)
791 			while (i < (int)sizeof (utmpt.ut_line) &&
792 			    *ttname != 0)
793 				utmpt.ut_line[i++] = *ttname++;
794 
795 		utmpt.ut_id[0] = 0;
796 		utmpt.ut_pid = getpid();
797 		utmpt.ut_type = USER_PROCESS;
798 		(void) time(&utmpt.ut_xtime);
799 		utmpp = &utmpt;
800 		dump();
801 		exit(0);
802 	}
803 }
804 
805 /*
806  *	This routine checks the following:
807  *
808  *	1.	File exists
809  *
810  *	2.	We have read permissions
811  *
812  *	3.	It is a multiple of utmp entries in size
813  *
814  *	Failing any of these conditions causes who(1) to
815  *	abort processing.
816  *
817  *	4.	If file is empty we exit right away as there
818  *		is no info to report on.
819  *
820  *	This routine does not check utmpx files.
821  */
822 static void
823 ck_file(char *name)
824 {
825 	struct	stat sbuf;
826 	int	rc;
827 
828 	/*
829 	 *	Does file exist? Do stat to check, and save structure
830 	 *	so that we can check on the file's size later on.
831 	 */
832 	if ((rc = stat(name, &sbuf)) == -1) {
833 		(void) snprintf(errmsg, sizeof (errmsg),
834 		    gettext("%s: Cannot stat file '%s'"), program, name);
835 		perror(errmsg);
836 		exit(1);
837 	}
838 
839 	/*
840 	 *	The only real way we can be sure we can access the
841 	 *	file is to try. If we succeed then we close it.
842 	 */
843 	if (access(name, R_OK) < 0) {
844 		(void) snprintf(errmsg, sizeof (errmsg),
845 		    gettext("%s: Cannot open file '%s'"), program, name);
846 		perror(errmsg);
847 		exit(1);
848 	}
849 
850 	/*
851 	 *	If the file is empty, we are all done.
852 	 */
853 	if (!sbuf.st_size)
854 		exit(0);
855 
856 	/*
857 	 *	Make sure the file is a utmp file.
858 	 *	We can only check for size being a multiple of
859 	 *	utmp structures in length.
860 	 */
861 	rc = sbuf.st_size % (int)sizeof (struct utmpx);
862 	if (rc) {
863 		(void) fprintf(stderr, gettext("%s: File '%s' is not "
864 		    "a utmpx file\n"), program, name);
865 		exit(1);
866 	}
867 }
868