/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * This program analyzes information found in /var/adm/utmpx * * Additionally information is gathered from /etc/inittab * if requested. * * * Syntax: * * who am i Displays info on yourself * * who -a Displays information about All * entries in /var/adm/utmpx * * who -b Displays info on last boot * * who -d Displays info on DEAD PROCESSES * * who -H Displays HEADERS for output * * who -l Displays info on LOGIN entries * * who -m Same as who am i * * who -p Displays info on PROCESSES spawned by init * * who -q Displays short information on * current users who LOGGED ON * * who -r Displays info of current run-level * * who -s Displays requested info in SHORT form * * who -t Displays info on TIME changes * * who -T Displays writeability of each user * (+ writeable, - non-writeable, ? hung) * * who -u Displays LONG info on users * who have LOGGED ON */ #define DATE_FMT "%b %e %H:%M" /* * %b Abbreviated month name * %e Day of month * %H hour (24-hour clock) * %M minute */ #include #include #include #include #include #include #include #include #include #include #include #include #include static void process(void); static void ck_file(char *); static void dump(void); static struct utmpx *utmpp; /* pointer for getutxent() */ /* * utmpx defines wider fields for user and line. For compatibility of output, * we are limiting these to the old maximums in utmp. Define UTMPX_NAMELEN * to use the full lengths. */ #ifndef UTMPX_NAMELEN /* XXX - utmp - fix name length */ #define NMAX (_POSIX_LOGIN_NAME_MAX - 1) #define LMAX 12 #else /* UTMPX_NAMELEN */ #define NMAX (sizeof (utmpp->ut_user)) #define LMAX (sizeof (utmpp->ut_line)) #endif static char comment[BUFSIZ]; /* holds inittab comment */ static char errmsg[BUFSIZ]; /* used in snprintf for errors */ static int fildes; /* file descriptor for inittab */ static int Hopt = 0; /* 1 = who -H */ static char *inittab; /* ptr to inittab contents */ static char *iinit; /* index into inittab */ static int justme = 0; /* 1 = who am i */ static struct tm *lptr; /* holds user login time */ static char *myname; /* pointer to invoker's name */ static char *mytty; /* holds device user is on */ static char nameval[sizeof (utmpp->ut_user) + 1]; /* invoker's name */ static int number = 8; /* number of users per -q line */ static int optcnt = 0; /* keeps count of options */ static char outbuf[BUFSIZ]; /* buffer for output */ static char *program; /* holds name of this program */ #ifdef XPG4 static int aopt = 0; /* 1 = who -a */ static int dopt = 0; /* 1 = who -d */ #endif /* XPG4 */ static int qopt = 0; /* 1 = who -q */ static int sopt = 0; /* 1 = who -s */ static struct stat stbuf; /* area for stat buffer */ static struct stat *stbufp; /* ptr to structure */ static int terse = 1; /* 1 = print terse msgs */ static int Topt = 0; /* 1 = who -T */ static time_t timnow; /* holds current time */ static int totlusrs = 0; /* cntr for users on system */ static int uopt = 0; /* 1 = who -u */ static char user[sizeof (utmpp->ut_user) + 1]; /* holds user name */ static int validtype[UTMAXTYPE+1]; /* holds valid types */ static int wrap; /* flag to indicate wrap */ static char time_buf[128]; /* holds date and time string */ static char *end; /* used in strtol for end pointer */ int main(int argc, char **argv) { int goerr = 0; /* non-zero indicates cmd error */ int i; int optsw; /* switch for while of getopt() */ (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ #define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ #endif (void) textdomain(TEXT_DOMAIN); validtype[USER_PROCESS] = 1; validtype[EMPTY] = 0; stbufp = &stbuf; /* * Strip off path name of this command */ for (i = strlen(argv[0]); i >= 0 && argv[0][i] != '/'; --i); if (i >= 0) argv[0] += i+1; program = argv[0]; /* * Buffer stdout for speed */ setbuf(stdout, outbuf); /* * Retrieve options specified on command line * XCU4 - add -m option */ while ((optsw = getopt(argc, argv, "abdHlmn:pqrstTu")) != EOF) { optcnt++; switch (optsw) { case 'a': optcnt += 7; validtype[BOOT_TIME] = 1; validtype[DEAD_PROCESS] = 1; validtype[LOGIN_PROCESS] = 1; validtype[INIT_PROCESS] = 1; validtype[RUN_LVL] = 1; validtype[OLD_TIME] = 1; validtype[NEW_TIME] = 1; validtype[USER_PROCESS] = 1; #ifdef XPG4 aopt = 1; #endif /* XPG4 */ uopt = 1; Topt = 1; if (!sopt) terse = 0; break; case 'b': validtype[BOOT_TIME] = 1; if (!uopt) validtype[USER_PROCESS] = 0; break; case 'd': validtype[DEAD_PROCESS] = 1; if (!uopt) validtype[USER_PROCESS] = 0; #ifdef XPG4 dopt = 1; #endif /* XPG4 */ break; case 'H': optcnt--; /* Don't count Header */ Hopt = 1; break; case 'l': validtype[LOGIN_PROCESS] = 1; if (!uopt) validtype[USER_PROCESS] = 0; terse = 0; break; case 'm': /* New XCU4 option */ justme = 1; break; case 'n': errno = 0; number = strtol(optarg, &end, 10); if (errno != 0 || *end != '\0') { (void) fprintf(stderr, gettext( "%s: Invalid numeric argument\n"), program); exit(1); } if (number < 1) { (void) fprintf(stderr, gettext( "%s: Number of users per line must " "be at least 1\n"), program); exit(1); } break; case 'p': validtype[INIT_PROCESS] = 1; if (!uopt) validtype[USER_PROCESS] = 0; break; case 'q': qopt = 1; break; case 'r': validtype[RUN_LVL] = 1; terse = 0; if (!uopt) validtype[USER_PROCESS] = 0; break; case 's': sopt = 1; terse = 1; break; case 't': validtype[OLD_TIME] = 1; validtype[NEW_TIME] = 1; if (!uopt) validtype[USER_PROCESS] = 0; break; case 'T': Topt = 1; #ifdef XPG4 terse = 1; /* XPG4 requires -T */ #else /* XPG4 */ terse = 0; #endif /* XPG4 */ break; case 'u': uopt = 1; validtype[USER_PROCESS] = 1; if (!sopt) terse = 0; break; case '?': goerr++; break; default: break; } } #ifdef XPG4 /* * XCU4 changes - check for illegal sopt, Topt & aopt combination */ if (sopt == 1) { terse = 1; if (Topt == 1 || aopt == 1) goerr++; } #endif /* XPG4 */ if (goerr > 0) { #ifdef XPG4 /* * XCU4 - slightly different usage with -s -a & -T */ (void) fprintf(stderr, gettext("\nUsage:\t%s"), program); (void) fprintf(stderr, gettext(" -s [-bdHlmpqrtu] [utmpx_like_file]\n")); (void) fprintf(stderr, gettext( "\t%s [-abdHlmpqrtTu] [utmpx_like_file]\n"), program); #else /* XPG4 */ (void) fprintf(stderr, gettext( "\nUsage:\t%s [-abdHlmpqrstTu] [utmpx_like_file]\n"), program); #endif /* XPG4 */ (void) fprintf(stderr, gettext("\t%s -q [-n x] [utmpx_like_file]\n"), program); (void) fprintf(stderr, gettext("\t%s [am i]\n"), program); /* * XCU4 changes - be explicit with "am i" options */ (void) fprintf(stderr, gettext("\t%s [am I]\n"), program); (void) fprintf(stderr, gettext( "a\tall (bdlprtu options)\n")); (void) fprintf(stderr, gettext("b\tboot time\n")); (void) fprintf(stderr, gettext("d\tdead processes\n")); (void) fprintf(stderr, gettext("H\tprint header\n")); (void) fprintf(stderr, gettext("l\tlogin processes\n")); (void) fprintf(stderr, gettext( "n #\tspecify number of users per line for -q\n")); (void) fprintf(stderr, gettext("p\tprocesses other than getty or users\n")); (void) fprintf(stderr, gettext("q\tquick %s\n"), program); (void) fprintf(stderr, gettext("r\trun level\n")); (void) fprintf(stderr, gettext( "s\tshort form of %s (no time since last output or pid)\n"), program); (void) fprintf(stderr, gettext("t\ttime changes\n")); (void) fprintf(stderr, gettext( "T\tstatus of tty (+ writable, - not writable, " "? hung)\n")); (void) fprintf(stderr, gettext("u\tuseful information\n")); (void) fprintf(stderr, gettext("m\tinformation only about current terminal\n")); (void) fprintf(stderr, gettext( "am i\tinformation about current terminal " "(same as -m)\n")); (void) fprintf(stderr, gettext( "am I\tinformation about current terminal " "(same as -m)\n")); exit(1); } /* * XCU4: If -q option ignore all other options */ if (qopt == 1) { Hopt = 0; sopt = 0; Topt = 0; uopt = 0; justme = 0; validtype[ACCOUNTING] = 0; validtype[BOOT_TIME] = 0; validtype[DEAD_PROCESS] = 0; validtype[LOGIN_PROCESS] = 0; validtype[INIT_PROCESS] = 0; validtype[RUN_LVL] = 0; validtype[OLD_TIME] = 0; validtype[NEW_TIME] = 0; validtype[USER_PROCESS] = 1; } if (argc == optind + 1) { optcnt++; ck_file(argv[optind]); (void) utmpxname(argv[optind]); } /* * Test for 'who am i' or 'who am I' * XCU4 - check if justme was already set by -m option */ if (justme == 1 || (argc == 3 && strcmp(argv[1], "am") == 0 && ((argv[2][0] == 'i' || argv[2][0] == 'I') && argv[2][1] == '\0'))) { justme = 1; myname = nameval; (void) cuserid(myname); if ((mytty = ttyname(fileno(stdin))) == NULL && (mytty = ttyname(fileno(stdout))) == NULL && (mytty = ttyname(fileno(stderr))) == NULL) { (void) fprintf(stderr, gettext( "Must be attached to terminal for 'am I' option\n")); (void) fflush(stderr); exit(1); } else mytty += 5; /* bump past "/dev/" */ } if (!terse) { if (Hopt) (void) printf(gettext( "NAME LINE TIME IDLE PID COMMENTS\n")); timnow = time(0); if ((fildes = open("/etc/inittab", O_NONBLOCK|O_RDONLY)) == -1) { (void) snprintf(errmsg, sizeof (errmsg), gettext("%s: Cannot open /etc/inittab"), program); perror(errmsg); exit(errno); } if (fstat(fildes, stbufp) == -1) { (void) snprintf(errmsg, sizeof (errmsg), gettext("%s: Cannot stat /etc/inittab"), program); perror(errmsg); exit(errno); } if ((inittab = malloc(stbufp->st_size + 1)) == NULL) { (void) snprintf(errmsg, sizeof (errmsg), gettext("%s: Cannot allocate %ld bytes"), program, stbufp->st_size); perror(errmsg); exit(errno); } if (read(fildes, inittab, stbufp->st_size) != stbufp->st_size) { (void) snprintf(errmsg, sizeof (errmsg), gettext("%s: Error reading /etc/inittab"), program); perror(errmsg); exit(errno); } inittab[stbufp->st_size] = '\0'; iinit = inittab; } else { if (Hopt) { #ifdef XPG4 if (dopt) { (void) printf(gettext( "NAME LINE TIME COMMENTS\n")); } else { (void) printf( gettext("NAME LINE TIME\n")); } #else /* XPG4 */ (void) printf( gettext("NAME LINE TIME\n")); #endif /* XPG4 */ } } process(); /* * 'who -q' requires EOL upon exit, * followed by total line */ if (qopt) (void) printf(gettext("\n# users=%d\n"), totlusrs); return (0); } static void dump() { char device[sizeof (utmpp->ut_line) + 1]; time_t hr; time_t idle; time_t min; char path[sizeof (utmpp->ut_line) + 6]; int pexit; int pterm; int rc; char w; /* writeability indicator */ /* * Get and check user name */ if (utmpp->ut_user[0] == '\0') (void) strcpy(user, " ."); else { (void) strncpy(user, utmpp->ut_user, sizeof (user)); user[sizeof (user) - 1] = '\0'; } totlusrs++; /* * Do print in 'who -q' format */ if (qopt) { /* * XCU4 - Use non user macro for correct user count */ if (((totlusrs - 1) % number) == 0 && totlusrs > 1) (void) printf("\n"); (void) printf("%-*s ", NMAX, user); return; } pexit = (int)' '; pterm = (int)' '; /* * Get exit info if applicable */ if (utmpp->ut_type == RUN_LVL || utmpp->ut_type == DEAD_PROCESS) { pterm = utmpp->ut_exit.e_termination; pexit = utmpp->ut_exit.e_exit; } /* * Massage ut_xtime field */ lptr = localtime(&utmpp->ut_xtime); (void) strftime(time_buf, sizeof (time_buf), dcgettext(NULL, DATE_FMT, LC_TIME), lptr); /* * Get and massage device */ if (utmpp->ut_line[0] == '\0') (void) strcpy(device, " ."); else { (void) strncpy(device, utmpp->ut_line, sizeof (utmpp->ut_line)); device[sizeof (utmpp->ut_line)] = '\0'; } /* * Get writeability if requested * XCU4 - only print + or - for user processes */ if (Topt && (utmpp->ut_type == USER_PROCESS)) { w = '-'; (void) strcpy(path, "/dev/"); (void) strncpy(path + 5, utmpp->ut_line, sizeof (utmpp->ut_line)); path[5 + sizeof (utmpp->ut_line)] = '\0'; if ((rc = stat(path, stbufp)) == -1) w = '?'; else if ((stbufp->st_mode & S_IWOTH) || (stbufp->st_mode & S_IWGRP)) /* Check group & other */ w = '+'; } else w = ' '; /* * Print the TERSE portion of the output */ (void) printf("%-*s %c %-12s %s", NMAX, user, w, device, time_buf); if (!terse) { /* * Stat device for idle time * (Don't complain if you can't) */ rc = -1; if (utmpp->ut_type == USER_PROCESS) { (void) strcpy(path, "/dev/"); (void) strncpy(path + 5, utmpp->ut_line, sizeof (utmpp->ut_line)); path[5 + sizeof (utmpp->ut_line)] = '\0'; rc = stat(path, stbufp); } if (rc != -1) { idle = timnow - stbufp->st_mtime; hr = idle/3600; min = (unsigned)(idle/60)%60; if (hr == 0 && min == 0) (void) printf(gettext(" . ")); else { if (hr < 24) (void) printf(" %2d:%2.2d", (int)hr, (int)min); else (void) printf(gettext(" old ")); } } /* * Add PID for verbose output */ if (utmpp->ut_type != BOOT_TIME && utmpp->ut_type != RUN_LVL && utmpp->ut_type != ACCOUNTING) (void) printf(" %5ld", utmpp->ut_pid); /* * Handle /etc/inittab comment */ if (utmpp->ut_type == DEAD_PROCESS) { (void) printf(gettext(" id=%4.4s "), utmpp->ut_id); (void) printf(gettext("term=%-3d "), pterm); (void) printf(gettext("exit=%d "), pexit); } else if (utmpp->ut_type != INIT_PROCESS) { /* * Search for each entry in inittab * string. Keep our place from * search to search to try and * minimize the work. Wrap once if needed * for each entry. */ wrap = 0; /* * Look for a line beginning with * utmpp->ut_id */ while ((rc = strncmp(utmpp->ut_id, iinit, strcspn(iinit, ":"))) != 0) { for (; *iinit != '\n'; iinit++); iinit++; /* * Wrap once if necessary to * find entry in inittab */ if (*iinit == '\0') { if (!wrap) { iinit = inittab; wrap = 1; } } } if (*iinit != '\0') { /* * We found our entry */ for (iinit++; *iinit != '#' && *iinit != '\n'; iinit++); if (*iinit == '#') { for (iinit++; *iinit == ' ' || *iinit == '\t'; iinit++); for (rc = 0; *iinit != '\n'; iinit++) comment[rc++] = *iinit; comment[rc] = '\0'; } else (void) strcpy(comment, " "); (void) printf(" %s", comment); } else iinit = inittab; /* Reset pointer */ } if (utmpp->ut_type == INIT_PROCESS) (void) printf(gettext(" id=%4.4s"), utmpp->ut_id); } #ifdef XPG4 else if (dopt && utmpp->ut_type == DEAD_PROCESS) { (void) printf(gettext("\tterm=%-3d "), pterm); (void) printf(gettext("exit=%d "), pexit); } #endif /* XPG4 */ /* * Handle RUN_LVL process - If no alt. file - Only one! */ if (utmpp->ut_type == RUN_LVL) { (void) printf(" %c %5ld %c", pterm, utmpp->ut_pid, pexit); if (optcnt == 1 && !validtype[USER_PROCESS]) { (void) printf("\n"); exit(0); } } /* * Handle BOOT_TIME process - If no alt. file - Only one! */ if (utmpp->ut_type == BOOT_TIME) { if (optcnt == 1 && !validtype[USER_PROCESS]) { (void) printf("\n"); exit(0); } } /* * Get remote host from utmpx structure */ if (utmpp && utmpp->ut_host[0]) (void) printf("\t(%.*s)", sizeof (utmpp->ut_host), utmpp->ut_host); /* * Now, put on the trailing EOL */ (void) printf("\n"); } static void process() { struct passwd *pwp; int i = 0; char *ttname; /* * Loop over each entry in /var/adm/utmpx */ setutxent(); while ((utmpp = getutxent()) != NULL) { #ifdef DEBUG (void) printf( "ut_user '%s'\nut_id '%s'\nut_line '%s'\nut_type '%d'\n\n", utmpp->ut_user, utmpp->ut_id, utmpp->ut_line, utmpp->ut_type); #endif if (utmpp->ut_type <= UTMAXTYPE) { /* * Handle "am i" */ if (justme) { if (strncmp(myname, utmpp->ut_user, sizeof (utmpp->ut_user)) == 0 && strncmp(mytty, utmpp->ut_line, sizeof (utmpp->ut_line)) == 0 && utmpp->ut_type == USER_PROCESS) { /* * we have have found ourselves * in the utmp file and the entry * is a user process, this is not * meaningful otherwise * */ dump(); exit(0); } continue; } /* * Print the line if we want it */ if (validtype[utmpp->ut_type]) { #ifdef XPG4 if (utmpp->ut_type == LOGIN_PROCESS) { if ((utmpp->ut_line[0] == '\0') || (strcmp(utmpp->ut_user, "LOGIN") != 0)) continue; } #endif /* XPG4 */ dump(); } } else { (void) fprintf(stderr, gettext("%s: Error --- entry has ut_type " "of %d\n"), program, utmpp->ut_type); (void) fprintf(stderr, gettext(" when maximum is %d\n"), UTMAXTYPE); } } /* * If justme is set at this point than the utmp entry * was not found. */ if (justme) { static struct utmpx utmpt; pwp = getpwuid(geteuid()); if (pwp != NULL) while (i < (int)sizeof (utmpt.ut_user) && *pwp->pw_name != 0) utmpt.ut_user[i++] = *pwp->pw_name++; ttname = ttyname(1); i = 0; if (ttname != NULL) while (i < (int)sizeof (utmpt.ut_line) && *ttname != 0) utmpt.ut_line[i++] = *ttname++; utmpt.ut_id[0] = 0; utmpt.ut_pid = getpid(); utmpt.ut_type = USER_PROCESS; (void) time(&utmpt.ut_xtime); utmpp = &utmpt; dump(); exit(0); } } /* * This routine checks the following: * * 1. File exists * * 2. We have read permissions * * 3. It is a multiple of utmp entries in size * * Failing any of these conditions causes who(1) to * abort processing. * * 4. If file is empty we exit right away as there * is no info to report on. * * This routine does not check utmpx files. */ static void ck_file(char *name) { struct stat sbuf; int rc; /* * Does file exist? Do stat to check, and save structure * so that we can check on the file's size later on. */ if ((rc = stat(name, &sbuf)) == -1) { (void) snprintf(errmsg, sizeof (errmsg), gettext("%s: Cannot stat file '%s'"), program, name); perror(errmsg); exit(1); } /* * The only real way we can be sure we can access the * file is to try. If we succeed then we close it. */ if (access(name, R_OK) < 0) { (void) snprintf(errmsg, sizeof (errmsg), gettext("%s: Cannot open file '%s'"), program, name); perror(errmsg); exit(1); } /* * If the file is empty, we are all done. */ if (!sbuf.st_size) exit(0); /* * Make sure the file is a utmp file. * We can only check for size being a multiple of * utmp structures in length. */ rc = sbuf.st_size % (int)sizeof (struct utmpx); if (rc) { (void) fprintf(stderr, gettext("%s: File '%s' is not " "a utmpx file\n"), program, name); exit(1); } }