/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Command line options for fuser command. Mutually exclusive. */ #define OPT_FILE_ONLY 0x0001 /* -f */ #define OPT_CONTAINED 0x0002 /* -c */ /* * Command line option modifiers for fuser command. */ #define OPT_SIGNAL 0x0100 /* -k, -s */ #define OPT_USERID 0x0200 /* -u */ #define OPT_NBMANDLIST 0x0400 /* -n */ #define OPT_DEVINFO 0x0800 /* -d */ #define NELEM(a) (sizeof (a) / sizeof ((a)[0])) /* * System call prototype */ extern int utssys(void *buf, int arg, int type, void *outbp); /* * Option flavors or types of options fuser command takes. Exclusive * options (EXCL_OPT) are mutually exclusive key options, while * modifier options (MOD_OPT) add to the key option. Examples are -f * for EXCL_OPT and -u for MOD_OPT. */ typedef enum {EXCL_OPT, MOD_OPT} opt_flavor_t; struct co_tab { int c_flag; char c_char; }; static struct co_tab code_tab[] = { {F_CDIR, 'c'}, /* current directory */ {F_RDIR, 'r'}, /* root directory (via chroot) */ {F_TEXT, 't'}, /* textfile */ {F_OPEN, 'o'}, /* open (creat, etc.) file */ {F_MAP, 'm'}, /* mapped file */ {F_TTY, 'y'}, /* controlling tty */ {F_TRACE, 'a'}, /* trace file */ {F_NBM, 'n'} /* nbmand lock/share reservation on file */ }; /* * Return a pointer to the mount point matching the given special name, if * possible, otherwise, exit with 1 if mnttab corruption is detected, else * return NULL. * * NOTE: the underlying storage for mget and mref is defined static by * libos. Repeated calls to getmntany() overwrite it; to save mnttab * structures would require copying the member strings elsewhere. */ static char * spec_to_mount(char *specname) { struct mnttab mref, mget; struct stat st; FILE *frp; int ret; /* get mount-point */ if ((frp = fopen(MNTTAB, "r")) == NULL) return (NULL); mntnull(&mref); mref.mnt_special = specname; ret = getmntany(frp, &mget, &mref); (void) fclose(frp); if (ret == 0) { if ((stat(specname, &st) == 0) && S_ISBLK(st.st_mode)) return (mget.mnt_mountp); } else if (ret > 0) { (void) fprintf(stderr, gettext("mnttab is corrupted\n")); exit(1); } return (NULL); } /* * The main objective of this routine is to allocate an array of f_user_t's. * In order for it to know how large an array to allocate, it must know * the value of v.v_proc in the kernel. To get this, we do a kstat * lookup to get the var structure from the kernel. */ static fu_data_t * get_f_user_buf() { fu_data_t fu_header, *fu_data; kstat_ctl_t *kc; struct var v; kstat_t *ksp; int count; if ((kc = kstat_open()) == NULL || (ksp = kstat_lookup(kc, "unix", 0, "var")) == NULL || kstat_read(kc, ksp, &v) == -1) { perror(gettext("kstat_read() of struct var failed")); exit(1); } (void) kstat_close(kc); /* * get a count of the current number of kernel file consumers * * the number of kernel file consumers can change between * the time when we get this count of all kernel file * consumers and when we get the actual file usage * information back from the kernel. * * we use the current count as a maximum because we assume * that not all kernel file consumers are accessing the * file we're interested in. this assumption should make * the current number of kernel file consumers a valid * upper limit of possible file consumers. * * this call should never fail */ fu_header.fud_user_max = 0; fu_header.fud_user_count = 0; (void) utssys(NULL, F_KINFO_COUNT, UTS_FUSERS, &fu_header); count = v.v_proc + fu_header.fud_user_count; fu_data = (fu_data_t *)malloc(fu_data_size(count)); if (fu_data == NULL) { (void) fprintf(stderr, gettext("fuser: could not allocate buffer\n")); exit(1); } fu_data->fud_user_max = count; fu_data->fud_user_count = 0; return (fu_data); } /* * display the fuser usage message and exit */ static void usage() { (void) fprintf(stderr, gettext("Usage: fuser [-[k|s sig]un[c|f|d]] files" " [-[[k|s sig]un[c|f|d]] files]..\n")); exit(1); } static int report_process(f_user_t *f_user, int options, int sig) { struct passwd *pwdp; int i; (void) fprintf(stdout, " %7d", (int)f_user->fu_pid); (void) fflush(stdout); /* print out any character codes for the process */ for (i = 0; i < NELEM(code_tab); i++) { if (f_user->fu_flags & code_tab[i].c_flag) (void) fprintf(stderr, "%c", code_tab[i].c_char); } /* optionally print the login name for the process */ if ((options & OPT_USERID) && ((pwdp = getpwuid(f_user->fu_uid)) != NULL)) (void) fprintf(stderr, "(%s)", pwdp->pw_name); /* optionally send a signal to the process */ if (options & OPT_SIGNAL) (void) kill(f_user->fu_pid, sig); return (0); } static char * i_get_dev_path(f_user_t *f_user, char *drv_name, int major, di_node_t *di_root) { di_minor_t di_minor; di_node_t di_node; dev_t dev; char *path; /* * if we don't have a snapshot of the device tree yet, then * take one so we can try to look up the device node and * some kind of path to it. */ if (*di_root == DI_NODE_NIL) { *di_root = di_init("/", DINFOSUBTREE | DINFOMINOR); if (*di_root == DI_NODE_NIL) { perror(gettext("devinfo snapshot failed")); return ((char *)-1); } } /* find device nodes that are bound to this driver */ di_node = di_drv_first_node(drv_name, *di_root); if (di_node == DI_NODE_NIL) return (NULL); /* try to get a dev_t for the device node we want to look up */ if (f_user->fu_minor == -1) dev = DDI_DEV_T_NONE; else dev = makedev(major, f_user->fu_minor); /* walk all the device nodes bound to this driver */ do { /* see if we can get a path to the minor node */ if (dev != DDI_DEV_T_NONE) { di_minor = DI_MINOR_NIL; while (di_minor = di_minor_next(di_node, di_minor)) { if (dev != di_minor_devt(di_minor)) continue; path = di_devfs_minor_path(di_minor); if (path == NULL) { perror(gettext( "unable to get device path")); return ((char *)-1); } return (path); } } /* see if we can get a path to the device instance */ if ((f_user->fu_instance != -1) && (f_user->fu_instance == di_instance(di_node))) { path = di_devfs_path(di_node); if (path == NULL) { perror(gettext("unable to get device path")); return ((char *)-1); } return (path); } } while (di_node = di_drv_next_node(di_node)); return (NULL); } static int report_kernel(f_user_t *f_user, di_node_t *di_root) { struct modinfo modinfo; char *path; int major = -1; /* get the module name */ modinfo.mi_info = MI_INFO_ONE | MI_INFO_CNT | MI_INFO_NOBASE; modinfo.mi_id = modinfo.mi_nextid = f_user->fu_modid; if (modctl(MODINFO, f_user->fu_modid, &modinfo) < 0) { perror(gettext("unable to get kernel module information")); return (-1); } /* * if we don't have any device info then just * print the module name */ if ((f_user->fu_instance == -1) && (f_user->fu_minor == -1)) { (void) fprintf(stderr, " [%s]", modinfo.mi_name); return (0); } /* get the driver major number */ if (modctl(MODGETMAJBIND, modinfo.mi_name, strlen(modinfo.mi_name) + 1, &major) < 0) { perror(gettext("unable to get driver major number")); return (-1); } path = i_get_dev_path(f_user, modinfo.mi_name, major, di_root); if (path == (char *)-1) return (-1); /* check if we couldn't get any device pathing info */ if (path == NULL) { if (f_user->fu_minor == -1) { /* * we don't really have any more info on the device * so display the driver name in the same format * that we would for a plain module */ (void) fprintf(stderr, " [%s]", modinfo.mi_name); return (0); } else { /* * if we only have dev_t information, then display * the driver name and the dev_t info */ (void) fprintf(stderr, " [%s,dev=(%d,%d)]", modinfo.mi_name, major, f_user->fu_minor); return (0); } } /* display device pathing information */ if (f_user->fu_minor == -1) { /* * display the driver name and a path to the device * instance. */ (void) fprintf(stderr, " [%s,dev_path=%s]", modinfo.mi_name, path); } else { /* * here we have lot's of info. the driver name, the minor * node dev_t, and a path to the device. display it all. */ (void) fprintf(stderr, " [%s,dev=(%d,%d),dev_path=%s]", modinfo.mi_name, major, f_user->fu_minor, path); } di_devfs_path_free(path); return (0); } /* * Show pids and usage indicators for the nusers processes in the users list. * When OPT_USERID is set, give associated login names. When OPT_SIGNAL is * set, issue the specified signal to those processes. */ static void report(fu_data_t *fu_data, int options, int sig) { di_node_t di_root = DI_NODE_NIL; f_user_t *f_user; int err, i; for (err = i = 0; (err == 0) && (i < fu_data->fud_user_count); i++) { f_user = &(fu_data->fud_user[i]); if (f_user->fu_flags & F_KERNEL) { /* a kernel module is using the file */ err = report_kernel(f_user, &di_root); } else { /* a userland process using the file */ err = report_process(f_user, options, sig); } } if (di_root != DI_NODE_NIL) di_fini(di_root); } /* * Sanity check the option "nextopt" and OR it into *options. */ static void set_option(int *options, int nextopt, opt_flavor_t type) { static const char *excl_opts[] = {"-c", "-f", "-d"}; int i; /* * Disallow repeating options */ if (*options & nextopt) usage(); /* * If EXCL_OPT, allow only one option to be set */ if ((type == EXCL_OPT) && (*options)) { (void) fprintf(stderr, gettext("Use only one of the following options :")); for (i = 0; i < NELEM(excl_opts); i++) { if (i == 0) { (void) fprintf(stderr, gettext(" %s"), excl_opts[i]); } else { (void) fprintf(stderr, gettext(", %s"), excl_opts[i]); } } (void) fprintf(stderr, "\n"), usage(); } *options |= nextopt; } /* * Determine which processes are using a named file or file system. * On stdout, show the pid of each process using each command line file * with indication(s) of its use(s). Optionally display the login * name with each process. Also optionally, issue the specified signal to * each process. * * X/Open Commands and Utilites, Issue 5 requires fuser to process * the complete list of names it is given, so if an error is encountered * it will continue through the list, and then exit with a non-zero * value. This is a change from earlier behavior where the command * would exit immediately upon an error. * * The preferred use of the command is with a single file or file system. */ int main(int argc, char **argv) { fu_data_t *fu_data; char *mntname, c; int newfile = 0, errors = 0, opts = 0, flags = 0; int uts_flags, sig, okay, err; (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); if (argc < 2) usage(); do { while ((c = getopt(argc, argv, "cdfkns:u")) != EOF) { if (newfile) { /* * Starting a new group of files. * Clear out options currently in * force. */ flags = opts = newfile = 0; } switch (c) { case 'd': set_option(&opts, OPT_DEVINFO, EXCL_OPT); break; case 'k': set_option(&flags, OPT_SIGNAL, MOD_OPT); sig = SIGKILL; break; case 's': set_option(&flags, OPT_SIGNAL, MOD_OPT); if (str2sig(optarg, &sig) != 0) { (void) fprintf(stderr, gettext("Invalid signal %s\n"), optarg); usage(); } break; case 'u': set_option(&flags, OPT_USERID, MOD_OPT); break; case 'n': /* * Report only users with NBMAND locks */ set_option(&flags, OPT_NBMANDLIST, MOD_OPT); break; case 'c': set_option(&opts, OPT_CONTAINED, EXCL_OPT); break; case 'f': set_option(&opts, OPT_FILE_ONLY, EXCL_OPT); break; default: (void) fprintf(stderr, gettext("Illegal option %c.\n"), c); usage(); } } if ((optind < argc) && (newfile)) { /* * Cancel the options currently in * force if a lone dash is specified. */ if (strcmp(argv[optind], "-") == 0) { flags = opts = newfile = 0; optind++; } } /* * newfile is set when a new group of files is found. If all * arguments are processed and newfile isn't set here, then * the user did not use the correct syntax */ if (optind > argc - 1) { if (!newfile) { (void) fprintf(stderr, gettext("fuser: missing file name\n")); usage(); } } else { if (argv[optind][0] == '-') { (void) fprintf(stderr, gettext("fuser: incorrect use of -\n")); usage(); } else { newfile = 1; } } /* allocate a buffer to hold usage data */ fu_data = get_f_user_buf(); /* * First print file name on stderr * (so stdout (pids) can be piped to kill) */ (void) fflush(stdout); (void) fprintf(stderr, "%s: ", argv[optind]); /* * if not OPT_FILE_ONLY, OPT_DEVINFO, or OPT_CONTAINED, * attempt to translate the target file name to a mount * point via /etc/mnttab. */ okay = 0; if (!opts && (mntname = spec_to_mount(argv[optind])) != NULL) { uts_flags = F_CONTAINED | ((flags & OPT_NBMANDLIST) ? F_NBMANDLIST : 0); err = utssys(mntname, uts_flags, UTS_FUSERS, fu_data); if (err == 0) { report(fu_data, flags, sig); okay = 1; } } uts_flags = \ ((opts & OPT_CONTAINED) ? F_CONTAINED : 0) | ((opts & OPT_DEVINFO) ? F_DEVINFO : 0) | ((flags & OPT_NBMANDLIST) ? F_NBMANDLIST : 0); err = utssys(argv[optind], uts_flags, UTS_FUSERS, fu_data); if (err == 0) { report(fu_data, flags, sig); } else if (!okay) { perror("fuser"); errors = 1; free(fu_data); continue; } (void) fprintf(stderr, "\n"); free(fu_data); } while (++optind < argc); return (errors); }