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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 30 /* All Rights Reserved */ 31 32 33 #include <errno.h> 34 #include <fcntl.h> 35 #include <kstat.h> 36 #include <libdevinfo.h> 37 #include <locale.h> 38 #include <pwd.h> 39 #include <signal.h> 40 #include <stdio.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <unistd.h> 44 #include <sys/mnttab.h> 45 #include <sys/modctl.h> 46 #include <sys/stat.h> 47 #include <sys/sysmacros.h> 48 #include <sys/types.h> 49 #include <sys/utssys.h> 50 #include <sys/var.h> 51 52 /* 53 * Command line options for fuser command. Mutually exclusive. 54 */ 55 #define OPT_FILE_ONLY 0x0001 /* -f */ 56 #define OPT_CONTAINED 0x0002 /* -c */ 57 58 /* 59 * Command line option modifiers for fuser command. 60 */ 61 #define OPT_SIGNAL 0x0100 /* -k, -s */ 62 #define OPT_USERID 0x0200 /* -u */ 63 #define OPT_NBMANDLIST 0x0400 /* -n */ 64 #define OPT_DEVINFO 0x0800 /* -d */ 65 66 #define NELEM(a) (sizeof (a) / sizeof ((a)[0])) 67 68 /* 69 * System call prototype 70 */ 71 extern int utssys(void *buf, int arg, int type, void *outbp); 72 73 /* 74 * Option flavors or types of options fuser command takes. Exclusive 75 * options (EXCL_OPT) are mutually exclusive key options, while 76 * modifier options (MOD_OPT) add to the key option. Examples are -f 77 * for EXCL_OPT and -u for MOD_OPT. 78 */ 79 typedef enum {EXCL_OPT, MOD_OPT} opt_flavor_t; 80 81 struct co_tab { 82 int c_flag; 83 char c_char; 84 }; 85 86 static struct co_tab code_tab[] = { 87 {F_CDIR, 'c'}, /* current directory */ 88 {F_RDIR, 'r'}, /* root directory (via chroot) */ 89 {F_TEXT, 't'}, /* textfile */ 90 {F_OPEN, 'o'}, /* open (creat, etc.) file */ 91 {F_MAP, 'm'}, /* mapped file */ 92 {F_TTY, 'y'}, /* controlling tty */ 93 {F_TRACE, 'a'}, /* trace file */ 94 {F_NBM, 'n'} /* nbmand lock/share reservation on file */ 95 }; 96 97 /* 98 * Return a pointer to the mount point matching the given special name, if 99 * possible, otherwise, exit with 1 if mnttab corruption is detected, else 100 * return NULL. 101 * 102 * NOTE: the underlying storage for mget and mref is defined static by 103 * libos. Repeated calls to getmntany() overwrite it; to save mnttab 104 * structures would require copying the member strings elsewhere. 105 */ 106 static char * 107 spec_to_mount(char *specname) 108 { 109 struct mnttab mref, mget; 110 struct stat st; 111 FILE *frp; 112 int ret; 113 114 /* get mount-point */ 115 if ((frp = fopen(MNTTAB, "r")) == NULL) 116 return (NULL); 117 118 mntnull(&mref); 119 mref.mnt_special = specname; 120 ret = getmntany(frp, &mget, &mref); 121 (void) fclose(frp); 122 123 if (ret == 0) { 124 if ((stat(specname, &st) == 0) && S_ISBLK(st.st_mode)) 125 return (mget.mnt_mountp); 126 } else if (ret > 0) { 127 (void) fprintf(stderr, gettext("mnttab is corrupted\n")); 128 exit(1); 129 } 130 return (NULL); 131 } 132 133 /* 134 * The main objective of this routine is to allocate an array of f_user_t's. 135 * In order for it to know how large an array to allocate, it must know 136 * the value of v.v_proc in the kernel. To get this, we do a kstat 137 * lookup to get the var structure from the kernel. 138 */ 139 static fu_data_t * 140 get_f_user_buf() 141 { 142 fu_data_t fu_header, *fu_data; 143 kstat_ctl_t *kc; 144 struct var v; 145 kstat_t *ksp; 146 int count; 147 148 if ((kc = kstat_open()) == NULL || 149 (ksp = kstat_lookup(kc, "unix", 0, "var")) == NULL || 150 kstat_read(kc, ksp, &v) == -1) { 151 perror(gettext("kstat_read() of struct var failed")); 152 exit(1); 153 } 154 (void) kstat_close(kc); 155 156 /* 157 * get a count of the current number of kernel file consumers 158 * 159 * the number of kernel file consumers can change between 160 * the time when we get this count of all kernel file 161 * consumers and when we get the actual file usage 162 * information back from the kernel. 163 * 164 * we use the current count as a maximum because we assume 165 * that not all kernel file consumers are accessing the 166 * file we're interested in. this assumption should make 167 * the current number of kernel file consumers a valid 168 * upper limit of possible file consumers. 169 * 170 * this call should never fail 171 */ 172 fu_header.fud_user_max = 0; 173 fu_header.fud_user_count = 0; 174 (void) utssys(NULL, F_KINFO_COUNT, UTS_FUSERS, &fu_header); 175 176 count = v.v_proc + fu_header.fud_user_count; 177 178 fu_data = (fu_data_t *)malloc(fu_data_size(count)); 179 if (fu_data == NULL) { 180 (void) fprintf(stderr, 181 gettext("fuser: could not allocate buffer\n")); 182 exit(1); 183 } 184 fu_data->fud_user_max = count; 185 fu_data->fud_user_count = 0; 186 return (fu_data); 187 } 188 189 /* 190 * display the fuser usage message and exit 191 */ 192 static void 193 usage() 194 { 195 (void) fprintf(stderr, 196 gettext("Usage: fuser [-[k|s sig]un[c|f|d]] files" 197 " [-[[k|s sig]un[c|f|d]] files]..\n")); 198 exit(1); 199 } 200 201 static int 202 report_process(f_user_t *f_user, int options, int sig) 203 { 204 struct passwd *pwdp; 205 int i; 206 207 (void) fprintf(stdout, " %7d", (int)f_user->fu_pid); 208 (void) fflush(stdout); 209 210 /* print out any character codes for the process */ 211 for (i = 0; i < NELEM(code_tab); i++) { 212 if (f_user->fu_flags & code_tab[i].c_flag) 213 (void) fprintf(stderr, "%c", code_tab[i].c_char); 214 } 215 216 /* optionally print the login name for the process */ 217 if ((options & OPT_USERID) && 218 ((pwdp = getpwuid(f_user->fu_uid)) != NULL)) 219 (void) fprintf(stderr, "(%s)", pwdp->pw_name); 220 221 /* optionally send a signal to the process */ 222 if (options & OPT_SIGNAL) 223 (void) kill(f_user->fu_pid, sig); 224 225 return (0); 226 } 227 228 static char * 229 i_get_dev_path(f_user_t *f_user, char *drv_name, int major, di_node_t *di_root) 230 { 231 di_minor_t di_minor; 232 di_node_t di_node; 233 dev_t dev; 234 char *path; 235 236 /* 237 * if we don't have a snapshot of the device tree yet, then 238 * take one so we can try to look up the device node and 239 * some kind of path to it. 240 */ 241 if (*di_root == DI_NODE_NIL) { 242 *di_root = di_init("/", DINFOSUBTREE | DINFOMINOR); 243 if (*di_root == DI_NODE_NIL) { 244 perror(gettext("devinfo snapshot failed")); 245 return ((char *)-1); 246 } 247 } 248 249 /* find device nodes that are bound to this driver */ 250 di_node = di_drv_first_node(drv_name, *di_root); 251 if (di_node == DI_NODE_NIL) 252 return (NULL); 253 254 /* try to get a dev_t for the device node we want to look up */ 255 if (f_user->fu_minor == -1) 256 dev = DDI_DEV_T_NONE; 257 else 258 dev = makedev(major, f_user->fu_minor); 259 260 /* walk all the device nodes bound to this driver */ 261 do { 262 263 /* see if we can get a path to the minor node */ 264 if (dev != DDI_DEV_T_NONE) { 265 di_minor = DI_MINOR_NIL; 266 while (di_minor = di_minor_next(di_node, di_minor)) { 267 if (dev != di_minor_devt(di_minor)) 268 continue; 269 path = di_devfs_minor_path(di_minor); 270 if (path == NULL) { 271 perror(gettext( 272 "unable to get device path")); 273 return ((char *)-1); 274 } 275 return (path); 276 } 277 } 278 279 /* see if we can get a path to the device instance */ 280 if ((f_user->fu_instance != -1) && 281 (f_user->fu_instance == di_instance(di_node))) { 282 path = di_devfs_path(di_node); 283 if (path == NULL) { 284 perror(gettext("unable to get device path")); 285 return ((char *)-1); 286 } 287 return (path); 288 } 289 } while (di_node = di_drv_next_node(di_node)); 290 291 return (NULL); 292 } 293 294 static int 295 report_kernel(f_user_t *f_user, di_node_t *di_root) 296 { 297 struct modinfo modinfo; 298 char *path; 299 int major = -1; 300 301 /* get the module name */ 302 modinfo.mi_info = MI_INFO_ONE | MI_INFO_CNT | MI_INFO_NOBASE; 303 modinfo.mi_id = modinfo.mi_nextid = f_user->fu_modid; 304 if (modctl(MODINFO, f_user->fu_modid, &modinfo) < 0) { 305 perror(gettext("unable to get kernel module information")); 306 return (-1); 307 } 308 309 /* 310 * if we don't have any device info then just 311 * print the module name 312 */ 313 if ((f_user->fu_instance == -1) && (f_user->fu_minor == -1)) { 314 (void) fprintf(stderr, " [%s]", modinfo.mi_name); 315 return (0); 316 } 317 318 /* get the driver major number */ 319 if (modctl(MODGETMAJBIND, 320 modinfo.mi_name, strlen(modinfo.mi_name) + 1, &major) < 0) { 321 perror(gettext("unable to get driver major number")); 322 return (-1); 323 } 324 325 path = i_get_dev_path(f_user, modinfo.mi_name, major, di_root); 326 if (path == (char *)-1) 327 return (-1); 328 329 /* check if we couldn't get any device pathing info */ 330 if (path == NULL) { 331 if (f_user->fu_minor == -1) { 332 /* 333 * we don't really have any more info on the device 334 * so display the driver name in the same format 335 * that we would for a plain module 336 */ 337 (void) fprintf(stderr, " [%s]", modinfo.mi_name); 338 return (0); 339 } else { 340 /* 341 * if we only have dev_t information, then display 342 * the driver name and the dev_t info 343 */ 344 (void) fprintf(stderr, " [%s,dev=(%d,%d)]", 345 modinfo.mi_name, major, f_user->fu_minor); 346 return (0); 347 } 348 } 349 350 /* display device pathing information */ 351 if (f_user->fu_minor == -1) { 352 /* 353 * display the driver name and a path to the device 354 * instance. 355 */ 356 (void) fprintf(stderr, " [%s,dev_path=%s]", 357 modinfo.mi_name, path); 358 } else { 359 /* 360 * here we have lot's of info. the driver name, the minor 361 * node dev_t, and a path to the device. display it all. 362 */ 363 (void) fprintf(stderr, " [%s,dev=(%d,%d),dev_path=%s]", 364 modinfo.mi_name, major, f_user->fu_minor, path); 365 } 366 367 di_devfs_path_free(path); 368 return (0); 369 } 370 371 /* 372 * Show pids and usage indicators for the nusers processes in the users list. 373 * When OPT_USERID is set, give associated login names. When OPT_SIGNAL is 374 * set, issue the specified signal to those processes. 375 */ 376 static void 377 report(fu_data_t *fu_data, int options, int sig) 378 { 379 di_node_t di_root = DI_NODE_NIL; 380 f_user_t *f_user; 381 int err, i; 382 383 for (err = i = 0; (err == 0) && (i < fu_data->fud_user_count); i++) { 384 385 f_user = &(fu_data->fud_user[i]); 386 if (f_user->fu_flags & F_KERNEL) { 387 /* a kernel module is using the file */ 388 err = report_kernel(f_user, &di_root); 389 } else { 390 /* a userland process using the file */ 391 err = report_process(f_user, options, sig); 392 } 393 } 394 395 if (di_root != DI_NODE_NIL) 396 di_fini(di_root); 397 } 398 399 /* 400 * Sanity check the option "nextopt" and OR it into *options. 401 */ 402 static void 403 set_option(int *options, int nextopt, opt_flavor_t type) 404 { 405 static const char *excl_opts[] = {"-c", "-f", "-d"}; 406 int i; 407 408 /* 409 * Disallow repeating options 410 */ 411 if (*options & nextopt) 412 usage(); 413 414 /* 415 * If EXCL_OPT, allow only one option to be set 416 */ 417 if ((type == EXCL_OPT) && (*options)) { 418 (void) fprintf(stderr, 419 gettext("Use only one of the following options :")); 420 for (i = 0; i < NELEM(excl_opts); i++) { 421 if (i == 0) { 422 (void) fprintf(stderr, gettext(" %s"), 423 excl_opts[i]); 424 } else { 425 (void) fprintf(stderr, gettext(", %s"), 426 excl_opts[i]); 427 } 428 } 429 (void) fprintf(stderr, "\n"), 430 usage(); 431 } 432 *options |= nextopt; 433 } 434 435 /* 436 * Determine which processes are using a named file or file system. 437 * On stdout, show the pid of each process using each command line file 438 * with indication(s) of its use(s). Optionally display the login 439 * name with each process. Also optionally, issue the specified signal to 440 * each process. 441 * 442 * X/Open Commands and Utilites, Issue 5 requires fuser to process 443 * the complete list of names it is given, so if an error is encountered 444 * it will continue through the list, and then exit with a non-zero 445 * value. This is a change from earlier behavior where the command 446 * would exit immediately upon an error. 447 * 448 * The preferred use of the command is with a single file or file system. 449 */ 450 451 int 452 main(int argc, char **argv) 453 { 454 fu_data_t *fu_data; 455 char *mntname, c; 456 int newfile = 0, errors = 0, opts = 0, flags = 0; 457 int uts_flags, sig, okay, err; 458 459 (void) setlocale(LC_ALL, ""); 460 (void) textdomain(TEXT_DOMAIN); 461 462 if (argc < 2) 463 usage(); 464 465 do { 466 while ((c = getopt(argc, argv, "cdfkns:u")) != EOF) { 467 if (newfile) { 468 /* 469 * Starting a new group of files. 470 * Clear out options currently in 471 * force. 472 */ 473 flags = opts = newfile = 0; 474 } 475 switch (c) { 476 case 'd': 477 set_option(&opts, OPT_DEVINFO, EXCL_OPT); 478 break; 479 case 'k': 480 set_option(&flags, OPT_SIGNAL, MOD_OPT); 481 sig = SIGKILL; 482 break; 483 case 's': 484 set_option(&flags, OPT_SIGNAL, MOD_OPT); 485 if (str2sig(optarg, &sig) != 0) { 486 (void) fprintf(stderr, 487 gettext("Invalid signal %s\n"), 488 optarg); 489 usage(); 490 } 491 break; 492 case 'u': 493 set_option(&flags, OPT_USERID, MOD_OPT); 494 break; 495 case 'n': 496 /* 497 * Report only users with NBMAND locks 498 */ 499 set_option(&flags, OPT_NBMANDLIST, MOD_OPT); 500 break; 501 case 'c': 502 set_option(&opts, OPT_CONTAINED, EXCL_OPT); 503 break; 504 case 'f': 505 set_option(&opts, OPT_FILE_ONLY, EXCL_OPT); 506 break; 507 default: 508 (void) fprintf(stderr, 509 gettext("Illegal option %c.\n"), c); 510 usage(); 511 } 512 } 513 514 if ((optind < argc) && (newfile)) { 515 /* 516 * Cancel the options currently in 517 * force if a lone dash is specified. 518 */ 519 if (strcmp(argv[optind], "-") == 0) { 520 flags = opts = newfile = 0; 521 optind++; 522 } 523 } 524 525 /* 526 * newfile is set when a new group of files is found. If all 527 * arguments are processed and newfile isn't set here, then 528 * the user did not use the correct syntax 529 */ 530 if (optind > argc - 1) { 531 if (!newfile) { 532 (void) fprintf(stderr, 533 gettext("fuser: missing file name\n")); 534 usage(); 535 } 536 } else { 537 if (argv[optind][0] == '-') { 538 (void) fprintf(stderr, 539 gettext("fuser: incorrect use of -\n")); 540 usage(); 541 } else { 542 newfile = 1; 543 } 544 } 545 546 /* allocate a buffer to hold usage data */ 547 fu_data = get_f_user_buf(); 548 549 /* 550 * First print file name on stderr 551 * (so stdout (pids) can be piped to kill) 552 */ 553 (void) fflush(stdout); 554 (void) fprintf(stderr, "%s: ", argv[optind]); 555 556 /* 557 * if not OPT_FILE_ONLY, OPT_DEVINFO, or OPT_CONTAINED, 558 * attempt to translate the target file name to a mount 559 * point via /etc/mnttab. 560 */ 561 okay = 0; 562 if (!opts && 563 (mntname = spec_to_mount(argv[optind])) != NULL) { 564 565 uts_flags = F_CONTAINED | 566 ((flags & OPT_NBMANDLIST) ? F_NBMANDLIST : 0); 567 568 err = utssys(mntname, uts_flags, UTS_FUSERS, fu_data); 569 if (err == 0) { 570 report(fu_data, flags, sig); 571 okay = 1; 572 } 573 } 574 575 uts_flags = \ 576 ((opts & OPT_CONTAINED) ? F_CONTAINED : 0) | 577 ((opts & OPT_DEVINFO) ? F_DEVINFO : 0) | 578 ((flags & OPT_NBMANDLIST) ? F_NBMANDLIST : 0); 579 580 err = utssys(argv[optind], uts_flags, UTS_FUSERS, fu_data); 581 if (err == 0) { 582 report(fu_data, flags, sig); 583 } else if (!okay) { 584 perror("fuser"); 585 errors = 1; 586 free(fu_data); 587 continue; 588 } 589 590 (void) fprintf(stderr, "\n"); 591 free(fu_data); 592 } while (++optind < argc); 593 594 return (errors); 595 } 596