OpenSolaris_b135/cmd/fuser/fuser.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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 <errno.h>
#include <fcntl.h>
#include <kstat.h>
#include <libdevinfo.h>
#include <locale.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mnttab.h>
#include <sys/modctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <sys/utssys.h>
#include <sys/var.h>

/*
 * 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);
}