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 }