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 *
spec_to_mount(char * specname)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 *
get_f_user_buf()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
usage()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
report_process(f_user_t * f_user,int options,int sig)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 *
i_get_dev_path(f_user_t * f_user,char * drv_name,int major,di_node_t * di_root)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
report_kernel(f_user_t * f_user,di_node_t * di_root)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
report(fu_data_t * fu_data,int options,int sig)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
set_option(int * options,int nextopt,opt_flavor_t type)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
main(int argc,char ** argv)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