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