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
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.
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]
23 * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
27 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
28 /* All Rights Reserved */
34 #include <libdevinfo.h>
42 #include <sys/mnttab.h>
43 #include <sys/modctl.h>
45 #include <sys/types.h>
46 #include <sys/utssys.h>
48 #include <sys/mkdev.h>
51 * Command line options for fuser command. Mutually exclusive.
53 #define OPT_FILE_ONLY 0x0001 /* -f */
54 #define OPT_CONTAINED 0x0002 /* -c */
57 * Command line option modifiers for fuser command.
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 */
64 #define NELEM(a) (sizeof (a) / sizeof ((a)[0]))
67 * System call prototype
69 extern int utssys(void *buf
, int arg
, int type
, void *outbp
);
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.
77 typedef enum {EXCL_OPT
, MOD_OPT
} opt_flavor_t
;
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 */
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
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.
105 spec_to_mount(char *specname
)
107 struct mnttab mref
, mget
;
112 /* get mount-point */
113 if ((frp
= fopen(MNTTAB
, "r")) == NULL
)
117 mref
.mnt_special
= specname
;
118 ret
= getmntany(frp
, &mget
, &mref
);
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"));
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.
140 fu_data_t fu_header
, *fu_data
;
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"));
152 (void) kstat_close(kc
);
155 * get a count of the current number of kernel file consumers
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.
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.
168 * this call should never fail
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
);
174 count
= v
.v_proc
+ fu_header
.fud_user_count
;
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"));
182 fu_data
->fud_user_max
= count
;
183 fu_data
->fud_user_count
= 0;
188 * display the fuser usage message and exit
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"));
200 report_process(f_user_t
*f_user
, int options
, int sig
)
205 (void) fprintf(stdout
, " %7d", (int)f_user
->fu_pid
);
206 (void) fflush(stdout
);
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
);
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
);
219 /* optionally send a signal to the process */
220 if (options
& OPT_SIGNAL
)
221 (void) kill(f_user
->fu_pid
, sig
);
227 i_get_dev_path(f_user_t
*f_user
, char *drv_name
, int major
, di_node_t
*di_root
)
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.
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"));
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
)
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
;
256 dev
= makedev(major
, f_user
->fu_minor
);
258 /* walk all the device nodes bound to this driver */
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
))
267 path
= di_devfs_minor_path(di_minor
);
270 "unable to get device path"));
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
);
282 perror(gettext("unable to get device path"));
287 } while (di_node
= di_drv_next_node(di_node
));
293 report_kernel(f_user_t
*f_user
, di_node_t
*di_root
)
295 struct modinfo modinfo
;
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"));
308 * if we don't have any device info then just
309 * print the module name
311 if ((f_user
->fu_instance
== -1) && (f_user
->fu_minor
== -1)) {
312 (void) fprintf(stderr
, " [%s]", modinfo
.mi_name
);
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"));
323 path
= i_get_dev_path(f_user
, modinfo
.mi_name
, major
, di_root
);
324 if (path
== (char *)-1)
327 /* check if we couldn't get any device pathing info */
329 if (f_user
->fu_minor
== -1) {
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
335 (void) fprintf(stderr
, " [%s]", modinfo
.mi_name
);
339 * if we only have dev_t information, then display
340 * the driver name and the dev_t info
342 (void) fprintf(stderr
, " [%s,dev=(%d,%d)]",
343 modinfo
.mi_name
, major
, f_user
->fu_minor
);
348 /* display device pathing information */
349 if (f_user
->fu_minor
== -1) {
351 * display the driver name and a path to the device
354 (void) fprintf(stderr
, " [%s,dev_path=%s]",
355 modinfo
.mi_name
, path
);
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.
361 (void) fprintf(stderr
, " [%s,dev=(%d,%d),dev_path=%s]",
362 modinfo
.mi_name
, major
, f_user
->fu_minor
, path
);
365 di_devfs_path_free(path
);
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.
375 report(fu_data_t
*fu_data
, int options
, int sig
)
377 di_node_t di_root
= DI_NODE_NIL
;
381 for (err
= i
= 0; (err
== 0) && (i
< fu_data
->fud_user_count
); i
++) {
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
);
388 /* a userland process using the file */
389 err
= report_process(f_user
, options
, sig
);
393 if (di_root
!= DI_NODE_NIL
)
398 * Sanity check the option "nextopt" and OR it into *options.
401 set_option(int *options
, int nextopt
, opt_flavor_t type
)
403 static const char *excl_opts
[] = {"-c", "-f", "-d"};
407 * Disallow repeating options
409 if (*options
& nextopt
)
413 * If EXCL_OPT, allow only one option to be set
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
++) {
420 (void) fprintf(stderr
, gettext(" %s"),
423 (void) fprintf(stderr
, gettext(", %s"),
427 (void) fprintf(stderr
, "\n"),
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
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.
446 * The preferred use of the command is with a single file or file system.
450 main(int argc
, char **argv
)
454 int newfile
= 0, errors
= 0, opts
= 0, flags
= 0;
455 int uts_flags
, sig
, okay
, err
;
457 (void) setlocale(LC_ALL
, "");
458 (void) textdomain(TEXT_DOMAIN
);
464 while ((c
= getopt(argc
, argv
, "cdfkns:u")) != EOF
) {
467 * Starting a new group of files.
468 * Clear out options currently in
471 flags
= opts
= newfile
= 0;
475 set_option(&opts
, OPT_DEVINFO
, EXCL_OPT
);
478 set_option(&flags
, OPT_SIGNAL
, MOD_OPT
);
482 set_option(&flags
, OPT_SIGNAL
, MOD_OPT
);
483 if (str2sig(optarg
, &sig
) != 0) {
484 (void) fprintf(stderr
,
485 gettext("Invalid signal %s\n"),
491 set_option(&flags
, OPT_USERID
, MOD_OPT
);
495 * Report only users with NBMAND locks
497 set_option(&flags
, OPT_NBMANDLIST
, MOD_OPT
);
500 set_option(&opts
, OPT_CONTAINED
, EXCL_OPT
);
503 set_option(&opts
, OPT_FILE_ONLY
, EXCL_OPT
);
506 (void) fprintf(stderr
,
507 gettext("Illegal option %c.\n"), c
);
512 if ((optind
< argc
) && (newfile
)) {
514 * Cancel the options currently in
515 * force if a lone dash is specified.
517 if (strcmp(argv
[optind
], "-") == 0) {
518 flags
= opts
= newfile
= 0;
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
528 if (optind
> argc
- 1) {
530 (void) fprintf(stderr
,
531 gettext("fuser: missing file name\n"));
535 if (argv
[optind
][0] == '-') {
536 (void) fprintf(stderr
,
537 gettext("fuser: incorrect use of -\n"));
544 /* allocate a buffer to hold usage data */
545 fu_data
= get_f_user_buf();
548 * First print file name on stderr
549 * (so stdout (pids) can be piped to kill)
551 (void) fflush(stdout
);
552 (void) fprintf(stderr
, "%s: ", argv
[optind
]);
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.
561 (mntname
= spec_to_mount(argv
[optind
])) != NULL
) {
563 uts_flags
= F_CONTAINED
|
564 ((flags
& OPT_NBMANDLIST
) ? F_NBMANDLIST
: 0);
566 err
= utssys(mntname
, uts_flags
, UTS_FUSERS
, fu_data
);
568 report(fu_data
, flags
, sig
);
574 ((opts
& OPT_CONTAINED
) ? F_CONTAINED
: 0) |
575 ((opts
& OPT_DEVINFO
) ? F_DEVINFO
: 0) |
576 ((flags
& OPT_NBMANDLIST
) ? F_NBMANDLIST
: 0);
578 err
= utssys(argv
[optind
], uts_flags
, UTS_FUSERS
, fu_data
);
580 report(fu_data
, flags
, sig
);
588 (void) fprintf(stderr
, "\n");
590 } while (++optind
< argc
);