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