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 }