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