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