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.
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.
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]
21 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
22 /* All Rights Reserved */
26 * Copyright (c) 2013 Gary Mills
28 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
29 * Use is subject to license terms.
33 * This program analyzes information found in /var/adm/utmpx
35 * Additionally information is gathered from /etc/inittab
41 * who am i Displays info on yourself
43 * who -a Displays information about All
44 * entries in /var/adm/utmpx
46 * who -b Displays info on last boot
48 * who -d Displays info on DEAD PROCESSES
50 * who -H Displays HEADERS for output
52 * who -l Displays info on LOGIN entries
54 * who -m Same as who am i
56 * who -p Displays info on PROCESSES spawned by init
58 * who -q Displays short information on
59 * current users who LOGGED ON
61 * who -r Displays info of current run-level
63 * who -s Displays requested info in SHORT form
65 * who -t Displays info on TIME changes
67 * who -T Displays writeability of each user
68 * (+ writeable, - non-writeable, ? hung)
70 * who -u Displays LONG info on users
74 #define DATE_FMT "%b %e %H:%M"
77 * %b Abbreviated month name
79 * %H hour (24-hour clock)
86 #include <sys/types.h>
96 static void process(void);
97 static void ck_file(char *);
98 static void dump(void);
100 static struct utmpx
*utmpp
; /* pointer for getutxent() */
103 * Use the full lengths from utmpx for user and line.
105 #define NMAX (sizeof (utmpp->ut_user))
106 #define LMAX (sizeof (utmpp->ut_line))
108 /* Print minimum field widths. */
109 #define LOGIN_WIDTH 8
110 #define LINE_WIDTH 12
112 static char comment
[BUFSIZ
]; /* holds inittab comment */
113 static char errmsg
[BUFSIZ
]; /* used in snprintf for errors */
114 static int fildes
; /* file descriptor for inittab */
115 static int Hopt
= 0; /* 1 = who -H */
116 static char *inittab
; /* ptr to inittab contents */
117 static char *iinit
; /* index into inittab */
118 static int justme
= 0; /* 1 = who am i */
119 static struct tm
*lptr
; /* holds user login time */
120 static char *myname
; /* pointer to invoker's name */
121 static char *mytty
; /* holds device user is on */
122 static char nameval
[sizeof (utmpp
->ut_user
) + 1]; /* invoker's name */
123 static int number
= 8; /* number of users per -q line */
124 static int optcnt
= 0; /* keeps count of options */
125 static char outbuf
[BUFSIZ
]; /* buffer for output */
126 static char *program
; /* holds name of this program */
128 static int aopt
= 0; /* 1 = who -a */
129 static int dopt
= 0; /* 1 = who -d */
131 static int qopt
= 0; /* 1 = who -q */
132 static int sopt
= 0; /* 1 = who -s */
133 static struct stat stbuf
; /* area for stat buffer */
134 static struct stat
*stbufp
; /* ptr to structure */
135 static int terse
= 1; /* 1 = print terse msgs */
136 static int Topt
= 0; /* 1 = who -T */
137 static time_t timnow
; /* holds current time */
138 static int totlusrs
= 0; /* cntr for users on system */
139 static int uopt
= 0; /* 1 = who -u */
140 static char user
[sizeof (utmpp
->ut_user
) + 1]; /* holds user name */
141 static int validtype
[UTMAXTYPE
+1]; /* holds valid types */
142 static int wrap
; /* flag to indicate wrap */
143 static char time_buf
[128]; /* holds date and time string */
144 static char *end
; /* used in strtol for end pointer */
147 main(int argc
, char **argv
)
149 int goerr
= 0; /* non-zero indicates cmd error */
151 int optsw
; /* switch for while of getopt() */
153 (void) setlocale(LC_ALL
, "");
155 #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
156 #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */
158 (void) textdomain(TEXT_DOMAIN
);
160 validtype
[USER_PROCESS
] = 1;
161 validtype
[EMPTY
] = 0;
165 * Strip off path name of this command
167 for (i
= strlen(argv
[0]); i
>= 0 && argv
[0][i
] != '/'; --i
)
174 * Buffer stdout for speed
176 setbuf(stdout
, outbuf
);
179 * Retrieve options specified on command line
180 * XCU4 - add -m option
182 while ((optsw
= getopt(argc
, argv
, "abdHlmn:pqrstTu")) != EOF
) {
188 validtype
[BOOT_TIME
] = 1;
189 validtype
[DEAD_PROCESS
] = 1;
190 validtype
[LOGIN_PROCESS
] = 1;
191 validtype
[INIT_PROCESS
] = 1;
192 validtype
[RUN_LVL
] = 1;
193 validtype
[OLD_TIME
] = 1;
194 validtype
[NEW_TIME
] = 1;
195 validtype
[USER_PROCESS
] = 1;
201 if (!sopt
) terse
= 0;
205 validtype
[BOOT_TIME
] = 1;
206 if (!uopt
) validtype
[USER_PROCESS
] = 0;
210 validtype
[DEAD_PROCESS
] = 1;
211 if (!uopt
) validtype
[USER_PROCESS
] = 0;
218 optcnt
--; /* Don't count Header */
223 validtype
[LOGIN_PROCESS
] = 1;
224 if (!uopt
) validtype
[USER_PROCESS
] = 0;
227 case 'm': /* New XCU4 option */
233 number
= strtol(optarg
, &end
, 10);
234 if (errno
!= 0 || *end
!= '\0') {
235 (void) fprintf(stderr
, gettext(
236 "%s: Invalid numeric argument\n"),
241 (void) fprintf(stderr
, gettext(
242 "%s: Number of users per line must "
243 "be at least 1\n"), program
);
249 validtype
[INIT_PROCESS
] = 1;
250 if (!uopt
) validtype
[USER_PROCESS
] = 0;
258 validtype
[RUN_LVL
] = 1;
260 if (!uopt
) validtype
[USER_PROCESS
] = 0;
269 validtype
[OLD_TIME
] = 1;
270 validtype
[NEW_TIME
] = 1;
271 if (!uopt
) validtype
[USER_PROCESS
] = 0;
277 terse
= 1; /* XPG4 requires -T */
285 validtype
[USER_PROCESS
] = 1;
286 if (!sopt
) terse
= 0;
298 * XCU4 changes - check for illegal sopt, Topt & aopt combination
302 if (Topt
== 1 || aopt
== 1)
310 * XCU4 - slightly different usage with -s -a & -T
312 (void) fprintf(stderr
, gettext("\nUsage:\t%s"), program
);
313 (void) fprintf(stderr
,
314 gettext(" -s [-bdHlmpqrtu] [utmpx_like_file]\n"));
316 (void) fprintf(stderr
, gettext(
317 "\t%s [-abdHlmpqrtTu] [utmpx_like_file]\n"), program
);
319 (void) fprintf(stderr
, gettext(
320 "\nUsage:\t%s [-abdHlmpqrstTu] [utmpx_like_file]\n"),
323 (void) fprintf(stderr
,
324 gettext("\t%s -q [-n x] [utmpx_like_file]\n"), program
);
325 (void) fprintf(stderr
, gettext("\t%s [am i]\n"), program
);
327 * XCU4 changes - be explicit with "am i" options
329 (void) fprintf(stderr
, gettext("\t%s [am I]\n"), program
);
330 (void) fprintf(stderr
, gettext(
331 "a\tall (bdlprtu options)\n"));
332 (void) fprintf(stderr
, gettext("b\tboot time\n"));
333 (void) fprintf(stderr
, gettext("d\tdead processes\n"));
334 (void) fprintf(stderr
, gettext("H\tprint header\n"));
335 (void) fprintf(stderr
, gettext("l\tlogin processes\n"));
336 (void) fprintf(stderr
, gettext(
337 "n #\tspecify number of users per line for -q\n"));
338 (void) fprintf(stderr
,
339 gettext("p\tprocesses other than getty or users\n"));
340 (void) fprintf(stderr
, gettext("q\tquick %s\n"), program
);
341 (void) fprintf(stderr
, gettext("r\trun level\n"));
342 (void) fprintf(stderr
, gettext(
343 "s\tshort form of %s (no time since last output or pid)\n"),
345 (void) fprintf(stderr
, gettext("t\ttime changes\n"));
346 (void) fprintf(stderr
, gettext(
347 "T\tstatus of tty (+ writable, - not writable, "
349 (void) fprintf(stderr
, gettext("u\tuseful information\n"));
350 (void) fprintf(stderr
,
351 gettext("m\tinformation only about current terminal\n"));
352 (void) fprintf(stderr
, gettext(
353 "am i\tinformation about current terminal "
355 (void) fprintf(stderr
, gettext(
356 "am I\tinformation about current terminal "
362 * XCU4: If -q option ignore all other options
370 validtype
[ACCOUNTING
] = 0;
371 validtype
[BOOT_TIME
] = 0;
372 validtype
[DEAD_PROCESS
] = 0;
373 validtype
[LOGIN_PROCESS
] = 0;
374 validtype
[INIT_PROCESS
] = 0;
375 validtype
[RUN_LVL
] = 0;
376 validtype
[OLD_TIME
] = 0;
377 validtype
[NEW_TIME
] = 0;
378 validtype
[USER_PROCESS
] = 1;
381 if (argc
== optind
+ 1) {
383 ck_file(argv
[optind
]);
384 (void) utmpxname(argv
[optind
]);
388 * Test for 'who am i' or 'who am I'
389 * XCU4 - check if justme was already set by -m option
391 if (justme
== 1 || (argc
== 3 && strcmp(argv
[1], "am") == 0 &&
392 ((argv
[2][0] == 'i' || argv
[2][0] == 'I') &&
393 argv
[2][1] == '\0'))) {
396 (void) cuserid(myname
);
397 if ((mytty
= ttyname(fileno(stdin
))) == NULL
&&
398 (mytty
= ttyname(fileno(stdout
))) == NULL
&&
399 (mytty
= ttyname(fileno(stderr
))) == NULL
) {
400 (void) fprintf(stderr
, gettext(
401 "Must be attached to terminal for 'am I' option\n"));
402 (void) fflush(stderr
);
405 mytty
+= 5; /* bump past "/dev/" */
410 (void) printf(gettext(
411 "NAME LINE TIME IDLE PID COMMENTS\n"));
415 if ((fildes
= open("/etc/inittab",
416 O_NONBLOCK
|O_RDONLY
)) == -1) {
417 (void) snprintf(errmsg
, sizeof (errmsg
),
418 gettext("%s: Cannot open /etc/inittab"), program
);
423 if (fstat(fildes
, stbufp
) == -1) {
424 (void) snprintf(errmsg
, sizeof (errmsg
),
425 gettext("%s: Cannot stat /etc/inittab"), program
);
430 if ((inittab
= malloc(stbufp
->st_size
+ 1)) == NULL
) {
431 (void) snprintf(errmsg
, sizeof (errmsg
),
432 gettext("%s: Cannot allocate %ld bytes"),
433 program
, stbufp
->st_size
);
438 if (read(fildes
, inittab
, stbufp
->st_size
)
439 != stbufp
->st_size
) {
440 (void) snprintf(errmsg
, sizeof (errmsg
),
441 gettext("%s: Error reading /etc/inittab"),
447 inittab
[stbufp
->st_size
] = '\0';
453 (void) printf(gettext(
454 "NAME LINE TIME COMMENTS\n"));
457 gettext("NAME LINE TIME\n"));
461 gettext("NAME LINE TIME\n"));
468 * 'who -q' requires EOL upon exit,
469 * followed by total line
472 (void) printf(gettext("\n# users=%d\n"), totlusrs
);
479 char device
[sizeof (utmpp
->ut_line
) + 1];
483 char path
[sizeof (utmpp
->ut_line
) + 6];
487 char w
; /* writeability indicator */
490 * Get and check user name
492 if (utmpp
->ut_user
[0] == '\0')
493 (void) strcpy(user
, " .");
495 (void) strncpy(user
, utmpp
->ut_user
, sizeof (user
));
496 user
[sizeof (user
) - 1] = '\0';
501 * Do print in 'who -q' format
505 * XCU4 - Use non user macro for correct user count
507 if (((totlusrs
- 1) % number
) == 0 && totlusrs
> 1)
509 (void) printf("%-*.*s ", LOGIN_WIDTH
, NMAX
, user
);
518 * Get exit info if applicable
520 if (utmpp
->ut_type
== RUN_LVL
|| utmpp
->ut_type
== DEAD_PROCESS
) {
521 pterm
= utmpp
->ut_exit
.e_termination
;
522 pexit
= utmpp
->ut_exit
.e_exit
;
526 * Massage ut_xtime field
528 lptr
= localtime(&utmpp
->ut_xtime
);
529 (void) strftime(time_buf
, sizeof (time_buf
),
530 dcgettext(NULL
, DATE_FMT
, LC_TIME
), lptr
);
533 * Get and massage device
535 if (utmpp
->ut_line
[0] == '\0')
536 (void) strcpy(device
, " .");
538 (void) strncpy(device
, utmpp
->ut_line
,
539 sizeof (utmpp
->ut_line
));
540 device
[sizeof (utmpp
->ut_line
)] = '\0';
544 * Get writeability if requested
545 * XCU4 - only print + or - for user processes
547 if (Topt
&& (utmpp
->ut_type
== USER_PROCESS
)) {
549 (void) strcpy(path
, "/dev/");
550 (void) strncpy(path
+ 5, utmpp
->ut_line
,
551 sizeof (utmpp
->ut_line
));
552 path
[5 + sizeof (utmpp
->ut_line
)] = '\0';
554 if ((rc
= stat(path
, stbufp
)) == -1) w
= '?';
555 else if ((stbufp
->st_mode
& S_IWOTH
) ||
556 (stbufp
->st_mode
& S_IWGRP
)) /* Check group & other */
563 * Print the TERSE portion of the output
565 (void) printf("%-*.*s %c %-12s %s", LOGIN_WIDTH
, NMAX
, user
,
566 w
, device
, time_buf
);
570 * Stat device for idle time
571 * (Don't complain if you can't)
574 if (utmpp
->ut_type
== USER_PROCESS
) {
575 (void) strcpy(path
, "/dev/");
576 (void) strncpy(path
+ 5, utmpp
->ut_line
,
577 sizeof (utmpp
->ut_line
));
578 path
[5 + sizeof (utmpp
->ut_line
)] = '\0';
579 rc
= stat(path
, stbufp
);
582 idle
= timnow
- stbufp
->st_mtime
;
584 min
= (unsigned)(idle
/60)%60;
585 if (hr
== 0 && min
== 0)
586 (void) printf(gettext(" . "));
589 (void) printf(" %2d:%2.2d", (int)hr
,
592 (void) printf(gettext(" old "));
597 * Add PID for verbose output
599 if (utmpp
->ut_type
!= BOOT_TIME
&&
600 utmpp
->ut_type
!= RUN_LVL
&&
601 utmpp
->ut_type
!= ACCOUNTING
)
602 (void) printf(" %5ld", utmpp
->ut_pid
);
605 * Handle /etc/inittab comment
607 if (utmpp
->ut_type
== DEAD_PROCESS
) {
608 (void) printf(gettext(" id=%4.4s "),
610 (void) printf(gettext("term=%-3d "), pterm
);
611 (void) printf(gettext("exit=%d "), pexit
);
612 } else if (utmpp
->ut_type
!= INIT_PROCESS
) {
614 * Search for each entry in inittab
615 * string. Keep our place from
616 * search to search to try and
617 * minimize the work. Wrap once if needed
622 * Look for a line beginning with
625 while ((rc
= strncmp(utmpp
->ut_id
, iinit
,
626 strcspn(iinit
, ":"))) != 0) {
627 for (; *iinit
!= '\n'; iinit
++)
632 * Wrap once if necessary to
633 * find entry in inittab
635 if (*iinit
== '\0') {
643 if (*iinit
!= '\0') {
647 for (iinit
++; *iinit
!= '#' &&
648 *iinit
!= '\n'; iinit
++)
651 for (iinit
++; *iinit
== ' ' ||
652 *iinit
== '\t'; iinit
++)
654 for (rc
= 0; *iinit
!= '\n'; iinit
++)
655 comment
[rc
++] = *iinit
;
658 (void) strcpy(comment
, " ");
660 (void) printf(" %s", comment
);
662 iinit
= inittab
; /* Reset pointer */
664 if (utmpp
->ut_type
== INIT_PROCESS
)
665 (void) printf(gettext(" id=%4.4s"), utmpp
->ut_id
);
669 if (dopt
&& utmpp
->ut_type
== DEAD_PROCESS
) {
670 (void) printf(gettext("\tterm=%-3d "), pterm
);
671 (void) printf(gettext("exit=%d "), pexit
);
677 * Handle RUN_LVL process - If no alt. file - Only one!
679 if (utmpp
->ut_type
== RUN_LVL
) {
680 (void) printf(" %c %5ld %c", pterm
, utmpp
->ut_pid
,
682 if (optcnt
== 1 && !validtype
[USER_PROCESS
]) {
689 * Handle BOOT_TIME process - If no alt. file - Only one!
691 if (utmpp
->ut_type
== BOOT_TIME
) {
692 if (optcnt
== 1 && !validtype
[USER_PROCESS
]) {
699 * Get remote host from utmpx structure
701 if (utmpp
&& utmpp
->ut_host
[0])
702 (void) printf("\t(%.*s)", sizeof (utmpp
->ut_host
),
706 * Now, put on the trailing EOL
719 * Loop over each entry in /var/adm/utmpx
723 while ((utmpp
= getutxent()) != NULL
) {
726 "ut_user '%s'\nut_id '%s'\nut_line '%s'\nut_type '%d'\n\n",
727 utmpp
->ut_user
, utmpp
->ut_id
, utmpp
->ut_line
, utmpp
->ut_type
);
729 if (utmpp
->ut_type
<= UTMAXTYPE
) {
734 if (strncmp(myname
, utmpp
->ut_user
,
735 sizeof (utmpp
->ut_user
)) == 0 &&
736 strncmp(mytty
, utmpp
->ut_line
,
737 sizeof (utmpp
->ut_line
)) == 0 &&
738 utmpp
->ut_type
== USER_PROCESS
) {
740 * we have have found ourselves
741 * in the utmp file and the entry
742 * is a user process, this is not
743 * meaningful otherwise
754 * Print the line if we want it
756 if (validtype
[utmpp
->ut_type
]) {
758 if (utmpp
->ut_type
== LOGIN_PROCESS
) {
759 if ((utmpp
->ut_line
[0] == '\0') ||
760 (strcmp(utmpp
->ut_user
,
768 (void) fprintf(stderr
,
769 gettext("%s: Error --- entry has ut_type "
770 "of %d\n"), program
, utmpp
->ut_type
);
771 (void) fprintf(stderr
,
772 gettext(" when maximum is %d\n"), UTMAXTYPE
);
777 * If justme is set at this point than the utmp entry
781 static struct utmpx utmpt
;
783 pwp
= getpwuid(geteuid());
785 while (i
< (int)sizeof (utmpt
.ut_user
) &&
787 utmpt
.ut_user
[i
++] = *pwp
->pw_name
++;
793 while (i
< (int)sizeof (utmpt
.ut_line
) &&
795 utmpt
.ut_line
[i
++] = *ttname
++;
798 utmpt
.ut_pid
= getpid();
799 utmpt
.ut_type
= USER_PROCESS
;
800 (void) time(&utmpt
.ut_xtime
);
808 * This routine checks the following:
812 * 2. We have read permissions
814 * 3. It is a multiple of utmp entries in size
816 * Failing any of these conditions causes who(1) to
819 * 4. If file is empty we exit right away as there
820 * is no info to report on.
822 * This routine does not check utmpx files.
831 * Does file exist? Do stat to check, and save structure
832 * so that we can check on the file's size later on.
834 if ((rc
= stat(name
, &sbuf
)) == -1) {
835 (void) snprintf(errmsg
, sizeof (errmsg
),
836 gettext("%s: Cannot stat file '%s'"), program
, name
);
842 * The only real way we can be sure we can access the
843 * file is to try. If we succeed then we close it.
845 if (access(name
, R_OK
) < 0) {
846 (void) snprintf(errmsg
, sizeof (errmsg
),
847 gettext("%s: Cannot open file '%s'"), program
, name
);
853 * If the file is empty, we are all done.
859 * Make sure the file is a utmp file.
860 * We can only check for size being a multiple of
861 * utmp structures in length.
863 rc
= sbuf
.st_size
% (int)sizeof (struct utmpx
);
865 (void) fprintf(stderr
, gettext("%s: File '%s' is not "
866 "a utmpx file\n"), program
, name
);