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 #pragma ident "%Z%%M% %I% %E% SMI"
29 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
30 /* All Rights Reserved */
36 #include <libdevinfo.h>
44 #include <sys/mnttab.h>
45 #include <sys/modctl.h>
47 #include <sys/sysmacros.h>
48 #include <sys/types.h>
49 #include <sys/utssys.h>
53 * Command line options for fuser command. Mutually exclusive.
55 #define OPT_FILE_ONLY 0x0001 /* -f */
56 #define OPT_CONTAINED 0x0002 /* -c */
59 * Command line option modifiers for fuser command.
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 */
66 #define NELEM(a) (sizeof (a) / sizeof ((a)[0]))
69 * System call prototype
71 extern int utssys(void *buf
, int arg
, int type
, void *outbp
);
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.
79 typedef enum {EXCL_OPT
, MOD_OPT
} opt_flavor_t
;
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 */
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
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.
107 spec_to_mount(char *specname
)
109 struct mnttab mref
, mget
;
114 /* get mount-point */
115 if ((frp
= fopen(MNTTAB
, "r")) == NULL
)
119 mref
.mnt_special
= specname
;
120 ret
= getmntany(frp
, &mget
, &mref
);
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"));
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.
142 fu_data_t fu_header
, *fu_data
;
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"));
154 (void) kstat_close(kc
);
157 * get a count of the current number of kernel file consumers
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.
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.
170 * this call should never fail
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
);
176 count
= v
.v_proc
+ fu_header
.fud_user_count
;
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"));
184 fu_data
->fud_user_max
= count
;
185 fu_data
->fud_user_count
= 0;
190 * display the fuser usage message and exit
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"));
202 report_process(f_user_t
*f_user
, int options
, int sig
)
207 (void) fprintf(stdout
, " %7d", (int)f_user
->fu_pid
);
208 (void) fflush(stdout
);
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
);
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
);
221 /* optionally send a signal to the process */
222 if (options
& OPT_SIGNAL
)
223 (void) kill(f_user
->fu_pid
, sig
);
229 i_get_dev_path(f_user_t
*f_user
, char *drv_name
, int major
, di_node_t
*di_root
)
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.
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"));
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
)
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
;
258 dev
= makedev(major
, f_user
->fu_minor
);
260 /* walk all the device nodes bound to this driver */
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
))
269 path
= di_devfs_minor_path(di_minor
);
272 "unable to get device path"));
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
);
284 perror(gettext("unable to get device path"));
289 } while (di_node
= di_drv_next_node(di_node
));
295 report_kernel(f_user_t
*f_user
, di_node_t
*di_root
)
297 struct modinfo modinfo
;
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"));
310 * if we don't have any device info then just
311 * print the module name
313 if ((f_user
->fu_instance
== -1) && (f_user
->fu_minor
== -1)) {
314 (void) fprintf(stderr
, " [%s]", modinfo
.mi_name
);
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"));
325 path
= i_get_dev_path(f_user
, modinfo
.mi_name
, major
, di_root
);
326 if (path
== (char *)-1)
329 /* check if we couldn't get any device pathing info */
331 if (f_user
->fu_minor
== -1) {
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
337 (void) fprintf(stderr
, " [%s]", modinfo
.mi_name
);
341 * if we only have dev_t information, then display
342 * the driver name and the dev_t info
344 (void) fprintf(stderr
, " [%s,dev=(%d,%d)]",
345 modinfo
.mi_name
, major
, f_user
->fu_minor
);
350 /* display device pathing information */
351 if (f_user
->fu_minor
== -1) {
353 * display the driver name and a path to the device
356 (void) fprintf(stderr
, " [%s,dev_path=%s]",
357 modinfo
.mi_name
, path
);
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.
363 (void) fprintf(stderr
, " [%s,dev=(%d,%d),dev_path=%s]",
364 modinfo
.mi_name
, major
, f_user
->fu_minor
, path
);
367 di_devfs_path_free(path
);
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.
377 report(fu_data_t
*fu_data
, int options
, int sig
)
379 di_node_t di_root
= DI_NODE_NIL
;
383 for (err
= i
= 0; (err
== 0) && (i
< fu_data
->fud_user_count
); i
++) {
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
);
390 /* a userland process using the file */
391 err
= report_process(f_user
, options
, sig
);
395 if (di_root
!= DI_NODE_NIL
)
400 * Sanity check the option "nextopt" and OR it into *options.
403 set_option(int *options
, int nextopt
, opt_flavor_t type
)
405 static const char *excl_opts
[] = {"-c", "-f", "-d"};
409 * Disallow repeating options
411 if (*options
& nextopt
)
415 * If EXCL_OPT, allow only one option to be set
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
++) {
422 (void) fprintf(stderr
, gettext(" %s"),
425 (void) fprintf(stderr
, gettext(", %s"),
429 (void) fprintf(stderr
, "\n"),
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
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.
448 * The preferred use of the command is with a single file or file system.
452 main(int argc
, char **argv
)
456 int newfile
= 0, errors
= 0, opts
= 0, flags
= 0;
457 int uts_flags
, sig
, okay
, err
;
459 (void) setlocale(LC_ALL
, "");
460 (void) textdomain(TEXT_DOMAIN
);
466 while ((c
= getopt(argc
, argv
, "cdfkns:u")) != EOF
) {
469 * Starting a new group of files.
470 * Clear out options currently in
473 flags
= opts
= newfile
= 0;
477 set_option(&opts
, OPT_DEVINFO
, EXCL_OPT
);
480 set_option(&flags
, OPT_SIGNAL
, MOD_OPT
);
484 set_option(&flags
, OPT_SIGNAL
, MOD_OPT
);
485 if (str2sig(optarg
, &sig
) != 0) {
486 (void) fprintf(stderr
,
487 gettext("Invalid signal %s\n"),
493 set_option(&flags
, OPT_USERID
, MOD_OPT
);
497 * Report only users with NBMAND locks
499 set_option(&flags
, OPT_NBMANDLIST
, MOD_OPT
);
502 set_option(&opts
, OPT_CONTAINED
, EXCL_OPT
);
505 set_option(&opts
, OPT_FILE_ONLY
, EXCL_OPT
);
508 (void) fprintf(stderr
,
509 gettext("Illegal option %c.\n"), c
);
514 if ((optind
< argc
) && (newfile
)) {
516 * Cancel the options currently in
517 * force if a lone dash is specified.
519 if (strcmp(argv
[optind
], "-") == 0) {
520 flags
= opts
= newfile
= 0;
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
530 if (optind
> argc
- 1) {
532 (void) fprintf(stderr
,
533 gettext("fuser: missing file name\n"));
537 if (argv
[optind
][0] == '-') {
538 (void) fprintf(stderr
,
539 gettext("fuser: incorrect use of -\n"));
546 /* allocate a buffer to hold usage data */
547 fu_data
= get_f_user_buf();
550 * First print file name on stderr
551 * (so stdout (pids) can be piped to kill)
553 (void) fflush(stdout
);
554 (void) fprintf(stderr
, "%s: ", argv
[optind
]);
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.
563 (mntname
= spec_to_mount(argv
[optind
])) != NULL
) {
565 uts_flags
= F_CONTAINED
|
566 ((flags
& OPT_NBMANDLIST
) ? F_NBMANDLIST
: 0);
568 err
= utssys(mntname
, uts_flags
, UTS_FUSERS
, fu_data
);
570 report(fu_data
, flags
, sig
);
576 ((opts
& OPT_CONTAINED
) ? F_CONTAINED
: 0) |
577 ((opts
& OPT_DEVINFO
) ? F_DEVINFO
: 0) |
578 ((flags
& OPT_NBMANDLIST
) ? F_NBMANDLIST
: 0);
580 err
= utssys(argv
[optind
], uts_flags
, UTS_FUSERS
, fu_data
);
582 report(fu_data
, flags
, sig
);
590 (void) fprintf(stderr
, "\n");
592 } while (++optind
< argc
);