1 /* $OpenBSD: w.c,v 1.66 2019/06/28 13:35:05 deraadt Exp $ */ 2 3 /*- 4 * Copyright (c) 1980, 1991, 1993, 1994 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 /* 33 * w - print system status (who and what) 34 * 35 * This program is similar to the systat command on Tenex/Tops 10/20 36 * 37 */ 38 #include <sys/param.h> /* MAXCOMLEN */ 39 #include <sys/time.h> 40 #include <sys/stat.h> 41 #include <sys/sysctl.h> 42 #include <sys/signal.h> 43 #include <sys/proc.h> 44 #include <sys/ioctl.h> 45 #include <sys/socket.h> 46 #include <sys/tty.h> 47 48 #include <netinet/in.h> 49 #include <arpa/inet.h> 50 51 #include <ctype.h> 52 #include <err.h> 53 #include <errno.h> 54 #include <fcntl.h> 55 #include <kvm.h> 56 #include <netdb.h> 57 #include <nlist.h> 58 #include <paths.h> 59 #include <stdio.h> 60 #include <stdlib.h> 61 #include <string.h> 62 #include <unistd.h> 63 #include <limits.h> 64 #include <utmp.h> 65 #include <vis.h> 66 67 #include "extern.h" 68 69 struct utmp utmp; 70 struct winsize ws; 71 kvm_t *kd; 72 time_t now; /* the current time of day */ 73 int ttywidth; /* width of tty */ 74 int argwidth; /* width of tty */ 75 int header = 1; /* true if -h flag: don't print heading */ 76 int nflag = 1; /* true if -n flag: don't convert addrs */ 77 int sortidle; /* sort by idle time */ 78 char *sel_user; /* login of particular user selected */ 79 char domain[HOST_NAME_MAX+1]; 80 81 #define NAME_WIDTH 8 82 #define HOST_WIDTH 16 83 84 /* 85 * One of these per active utmp entry. 86 */ 87 struct entry { 88 struct entry *next; 89 struct utmp utmp; 90 dev_t tdev; /* dev_t of terminal */ 91 time_t idle; /* idle time of terminal in seconds */ 92 struct kinfo_proc *kp; /* `most interesting' proc */ 93 } *ep, *ehead = NULL, **nextp = &ehead; 94 95 static void fmt_putc(int, int *); 96 static void fmt_puts(const char *, int *); 97 static void pr_args(struct kinfo_proc *); 98 static void pr_header(time_t *, int); 99 static struct stat 100 *ttystat(char *); 101 static void usage(int); 102 103 int 104 main(int argc, char *argv[]) 105 { 106 extern char *__progname; 107 struct kinfo_proc *kp; 108 struct hostent *hp; 109 struct stat *stp; 110 FILE *ut; 111 struct in_addr addr; 112 int ch, i, nentries, nusers, wcmd; 113 char *memf, *nlistf, *p, *x; 114 char buf[HOST_NAME_MAX+1], errbuf[_POSIX2_LINE_MAX]; 115 116 /* Are we w(1) or uptime(1)? */ 117 p = __progname; 118 if (*p == '-') 119 p++; 120 if (p[0] == 'w' && p[1] == '\0') { 121 wcmd = 1; 122 p = "hiflM:N:asuw"; 123 } else if (!strcmp(p, "uptime")) { 124 wcmd = 0; 125 p = ""; 126 } else 127 errx(1, 128 "this program should be invoked only as \"w\" or \"uptime\""); 129 130 memf = nlistf = NULL; 131 while ((ch = getopt(argc, argv, p)) != -1) 132 switch (ch) { 133 case 'h': 134 header = 0; 135 break; 136 case 'i': 137 sortidle = 1; 138 break; 139 case 'M': 140 header = 0; 141 memf = optarg; 142 break; 143 case 'N': 144 nlistf = optarg; 145 break; 146 case 'a': 147 nflag = 0; 148 break; 149 case 'f': case 'l': case 's': case 'u': case 'w': 150 warnx("[-flsuw] no longer supported"); 151 /* FALLTHROUGH */ 152 case '?': 153 default: 154 usage(wcmd); 155 } 156 argc -= optind; 157 argv += optind; 158 159 if (nflag == 0) { 160 if (pledge("stdio tty rpath dns ps vminfo", NULL) == -1) 161 err(1, "pledge"); 162 } else { 163 if (pledge("stdio tty rpath ps vminfo", NULL) == -1) 164 err(1, "pledge"); 165 } 166 167 if (nlistf == NULL && memf == NULL) { 168 if ((kd = kvm_openfiles(nlistf, memf, NULL, KVM_NO_FILES, 169 errbuf)) == NULL) 170 errx(1, "%s", errbuf); 171 } else { 172 if ((kd = kvm_openfiles(nlistf, memf, NULL, O_RDONLY, errbuf)) == NULL) 173 errx(1, "%s", errbuf); 174 } 175 176 (void)time(&now); 177 if ((ut = fopen(_PATH_UTMP, "r")) == NULL) 178 err(1, "%s", _PATH_UTMP); 179 180 if (*argv) 181 sel_user = *argv; 182 183 for (nusers = 0; fread(&utmp, sizeof(utmp), 1, ut);) { 184 if (utmp.ut_name[0] == '\0') 185 continue; 186 ++nusers; 187 if (wcmd == 0 || (sel_user && 188 strncmp(utmp.ut_name, sel_user, UT_NAMESIZE) != 0)) 189 continue; 190 if ((ep = calloc(1, sizeof(*ep))) == NULL) 191 err(1, NULL); 192 *nextp = ep; 193 nextp = &(ep->next); 194 memcpy(&(ep->utmp), &utmp, sizeof(utmp)); 195 if (!(stp = ttystat(ep->utmp.ut_line))) 196 continue; 197 ep->tdev = stp->st_rdev; 198 199 /* 200 * If this is the console device, attempt to ascertain 201 * the true console device dev_t. 202 */ 203 if (ep->tdev == 0) { 204 int mib[2]; 205 size_t size; 206 207 mib[0] = CTL_KERN; 208 mib[1] = KERN_CONSDEV; 209 size = sizeof(dev_t); 210 (void) sysctl(mib, 2, &ep->tdev, &size, NULL, 0); 211 } 212 213 if ((ep->idle = now - stp->st_atime) < 0) 214 ep->idle = 0; 215 } 216 (void)fclose(ut); 217 218 if (header || wcmd == 0) { 219 pr_header(&now, nusers); 220 if (wcmd == 0) 221 exit (0); 222 } 223 224 #define HEADER "USER TTY FROM LOGIN@ IDLE WHAT" 225 #define WUSED (sizeof(HEADER) - sizeof("WHAT")) 226 if (header) 227 (void)puts(HEADER); 228 229 kp = kvm_getprocs(kd, KERN_PROC_ALL, 0, sizeof(*kp), &nentries); 230 if (kp == NULL) 231 errx(1, "%s", kvm_geterr(kd)); 232 233 if ((ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1 && 234 ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) == -1 && 235 ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) || ws.ws_col == 0) 236 ttywidth = 79; 237 else 238 ttywidth = ws.ws_col - 1; 239 argwidth = ttywidth - WUSED; 240 if (argwidth < 4) 241 argwidth = 8; 242 243 for (i = 0; i < nentries; i++, kp++) { 244 if (kp->p_psflags & (PS_EMBRYO | PS_ZOMBIE)) 245 continue; 246 for (ep = ehead; ep != NULL; ep = ep->next) { 247 /* ftp is a special case. */ 248 if (strncmp(ep->utmp.ut_line, "ftp", 3) == 0) { 249 char pidstr[UT_LINESIZE-2]; 250 pid_t fp; 251 252 (void)strncpy(pidstr, &ep->utmp.ut_line[3], 253 sizeof(pidstr) - 1); 254 pidstr[sizeof(pidstr) - 1] = '\0'; 255 fp = (pid_t)strtol(pidstr, NULL, 10); 256 if (kp->p_pid == fp) { 257 ep->kp = kp; 258 break; 259 } 260 } else if (ep->tdev == kp->p_tdev && 261 kp->p__pgid == kp->p_tpgid) { 262 /* 263 * Proc is in foreground of this terminal 264 */ 265 if (proc_compare(ep->kp, kp)) 266 ep->kp = kp; 267 break; 268 } 269 } 270 } 271 /* sort by idle time */ 272 if (sortidle && ehead != NULL) { 273 struct entry *from = ehead, *save; 274 275 ehead = NULL; 276 while (from != NULL) { 277 for (nextp = &ehead; 278 (*nextp) && from->idle >= (*nextp)->idle; 279 nextp = &(*nextp)->next) 280 continue; 281 save = from; 282 from = from->next; 283 save->next = *nextp; 284 *nextp = save; 285 } 286 } 287 288 if (!nflag) { 289 if (gethostname(domain, sizeof(domain)) == -1 || 290 (p = strchr(domain, '.')) == 0) 291 domain[0] = '\0'; 292 else { 293 domain[sizeof(domain) - 1] = '\0'; 294 memmove(domain, p, strlen(p) + 1); 295 } 296 } 297 298 for (ep = ehead; ep != NULL; ep = ep->next) { 299 p = *ep->utmp.ut_host ? ep->utmp.ut_host : "-"; 300 for (x = NULL, i = 0; p[i] != '\0' && i < UT_HOSTSIZE; i++) 301 if (p[i] == ':') { 302 x = &p[i]; 303 *x++ = '\0'; 304 break; 305 } 306 if (!nflag && inet_aton(p, &addr) && 307 (hp = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET))) { 308 if (domain[0] != '\0') { 309 p = hp->h_name; 310 p += strlen(hp->h_name); 311 p -= strlen(domain); 312 if (p > hp->h_name && 313 strcasecmp(p, domain) == 0) 314 *p = '\0'; 315 } 316 p = hp->h_name; 317 } 318 if (x) { 319 (void)snprintf(buf, sizeof(buf), "%s:%.*s", p, 320 (int)(ep->utmp.ut_host + UT_HOSTSIZE - x), x); 321 p = buf; 322 } 323 (void)printf("%-*.*s %-2.2s %-*.*s ", 324 NAME_WIDTH, UT_NAMESIZE, ep->utmp.ut_name, 325 strncmp(ep->utmp.ut_line, "tty", 3) ? 326 ep->utmp.ut_line : ep->utmp.ut_line + 3, 327 HOST_WIDTH, HOST_WIDTH, *p ? p : "-"); 328 pr_attime(&ep->utmp.ut_time, &now); 329 pr_idle(ep->idle); 330 pr_args(ep->kp); 331 printf("\n"); 332 } 333 exit(0); 334 } 335 336 static void 337 fmt_putc(int c, int *leftp) 338 { 339 340 if (*leftp == 0) 341 return; 342 if (*leftp != -1) 343 *leftp -= 1; 344 putchar(c); 345 } 346 347 static void 348 fmt_puts(const char *s, int *leftp) 349 { 350 static char *v = NULL; 351 static size_t maxlen = 0; 352 size_t len; 353 354 if (*leftp == 0) 355 return; 356 len = strlen(s) * 4 + 1; 357 if (len > maxlen) { 358 free(v); 359 maxlen = 0; 360 if (len < getpagesize()) 361 len = getpagesize(); 362 v = malloc(len); 363 if (v == NULL) 364 return; 365 maxlen = len; 366 } 367 strvis(v, s, VIS_TAB | VIS_NL | VIS_CSTYLE); 368 if (*leftp != -1) { 369 len = strlen(v); 370 if (len > *leftp) { 371 v[*leftp] = '\0'; 372 *leftp = 0; 373 } else 374 *leftp -= len; 375 } 376 printf("%s", v); 377 } 378 379 380 static void 381 pr_args(struct kinfo_proc *kp) 382 { 383 char **argv, *str; 384 int left; 385 386 if (kp == NULL) 387 goto nothing; /* no matching process found */ 388 left = argwidth; 389 argv = kvm_getargv(kd, kp, argwidth+60); /* +60 for ftpd snip */ 390 if (argv == NULL) 391 goto nothing; 392 393 if (*argv == NULL || **argv == '\0') { 394 /* Process has zeroed argv[0], display executable name. */ 395 fmt_putc('(', &left); 396 fmt_puts(kp->p_comm, &left); 397 fmt_putc(')', &left); 398 } 399 while (*argv) { 400 /* 401 * ftp argv[0] is in the following format: 402 * ftpd: HOSTNAME: [USER/PASS: ]CMD args (ftpd) 403 */ 404 if (strncmp(*argv, "ftpd:", 5) == 0) { 405 if ((str = strchr(*argv + 5, ':')) != NULL) 406 str = strchr(str + 1, ':'); 407 if (str != NULL) { 408 if ((str[0] == ':') && 409 isspace((unsigned char)str[1])) 410 str += 2; 411 fmt_puts(str, &left); 412 } else 413 fmt_puts(*argv, &left); 414 } else 415 fmt_puts(*argv, &left); 416 argv++; 417 fmt_putc(' ', &left); 418 } 419 return; 420 nothing: 421 putchar('-'); 422 } 423 424 static void 425 pr_header(time_t *nowp, int nusers) 426 { 427 double avenrun[3]; 428 struct timespec boottime; 429 time_t uptime; 430 int days, hrs, i, mins; 431 char buf[256]; 432 433 /* 434 * Print time of day. 435 */ 436 (void)strftime(buf, sizeof(buf) - 1, "%l:%M%p", localtime(nowp)); 437 buf[sizeof(buf) - 1] = '\0'; 438 (void)printf("%s ", buf); 439 440 /* 441 * Print how long system has been up. 442 */ 443 if (clock_gettime(CLOCK_BOOTTIME, &boottime) != -1) { 444 uptime = boottime.tv_sec; 445 if (uptime > 59) { 446 uptime += 30; 447 days = uptime / SECSPERDAY; 448 uptime %= SECSPERDAY; 449 hrs = uptime / SECSPERHOUR; 450 uptime %= SECSPERHOUR; 451 mins = uptime / 60; 452 (void)printf(" up"); 453 if (days > 0) 454 (void)printf(" %d day%s,", days, 455 days > 1 ? "s" : ""); 456 if (hrs > 0 && mins > 0) 457 (void)printf(" %2d:%02d,", hrs, mins); 458 else { 459 if (hrs > 0) 460 (void)printf(" %d hr%s,", 461 hrs, hrs > 1 ? "s" : ""); 462 if (mins > 0 || (days == 0 && hrs == 0)) 463 (void)printf(" %d min%s,", 464 mins, mins != 1 ? "s" : ""); 465 } 466 } else 467 printf(" %d secs,", (int)uptime); 468 } 469 470 /* Print number of users logged in to system */ 471 (void)printf(" %d user%s", nusers, nusers != 1 ? "s" : ""); 472 473 /* 474 * Print 1, 5, and 15 minute load averages. 475 */ 476 if (getloadavg(avenrun, sizeof(avenrun) / sizeof(avenrun[0])) == -1) 477 (void)printf(", no load average information available\n"); 478 else { 479 (void)printf(", load averages:"); 480 for (i = 0; i < (sizeof(avenrun) / sizeof(avenrun[0])); i++) { 481 if (i > 0) 482 (void)printf(","); 483 (void)printf(" %.2f", avenrun[i]); 484 } 485 (void)printf("\n"); 486 } 487 } 488 489 static struct stat * 490 ttystat(char *line) 491 { 492 static struct stat sb; 493 char ttybuf[sizeof(_PATH_DEV) + UT_LINESIZE]; 494 495 /* Note, line may not be NUL-terminated */ 496 (void)strlcpy(ttybuf, _PATH_DEV, sizeof(ttybuf)); 497 (void)strncat(ttybuf, line, sizeof(ttybuf) - 1 - strlen(ttybuf)); 498 if (stat(ttybuf, &sb)) 499 return (NULL); 500 return (&sb); 501 } 502 503 static void 504 usage(int wcmd) 505 { 506 if (wcmd) 507 (void)fprintf(stderr, 508 "usage: w [-ahi] [-M core] [-N system] [user]\n"); 509 else 510 (void)fprintf(stderr, 511 "usage: uptime\n"); 512 exit (1); 513 } 514