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