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 }