1221807Sstas/*-
2221807Sstas * Copyright (c) 2005-2009 Stanislav Sedov <stas@FreeBSD.org>
3221807Sstas * All rights reserved.
4221807Sstas *
5221807Sstas * Redistribution and use in source and binary forms, with or without
6221807Sstas * modification, are permitted provided that the following conditions
7221807Sstas * are met:
8221807Sstas * 1. Redistributions of source code must retain the above copyright
9221807Sstas *    notice, this list of conditions and the following disclaimer.
10221807Sstas * 2. Redistributions in binary form must reproduce the above copyright
11221807Sstas *    notice, this list of conditions and the following disclaimer in the
12221807Sstas *    documentation and/or other materials provided with the distribution.
13221807Sstas *
14221807Sstas * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15221807Sstas * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16221807Sstas * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17221807Sstas * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18221807Sstas * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19221807Sstas * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20221807Sstas * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21221807Sstas * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22221807Sstas * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23221807Sstas * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24221807Sstas * SUCH DAMAGE.
25221807Sstas *
26221807Sstas */
27221807Sstas#include <sys/cdefs.h>
28221807Sstas__FBSDID("$FreeBSD$");
29221807Sstas
30221807Sstas#include <sys/queue.h>
31221807Sstas#include <sys/stat.h>
32221807Sstas#include <sys/sysctl.h>
33221807Sstas#include <sys/user.h>
34221807Sstas
35221807Sstas#include <assert.h>
36221807Sstas#include <ctype.h>
37221807Sstas#include <err.h>
38221807Sstas#include <fcntl.h>
39221807Sstas#include <libprocstat.h>
40221807Sstas#include <limits.h>
41221807Sstas#include <paths.h>
42221807Sstas#include <pwd.h>
43221807Sstas#include <signal.h>
44221807Sstas#include <stdio.h>
45221807Sstas#include <stdlib.h>
46221807Sstas#include <string.h>
47221807Sstas#include <sysexits.h>
48221807Sstas#include <unistd.h>
49221807Sstas
50221807Sstas#include "functions.h"
51221807Sstas
52221807Sstas/*
53221807Sstas * File access mode flags table.
54221807Sstas */
55227239Sedstatic const struct {
56221807Sstas	int	flag;
57221807Sstas	char	ch;
58221807Sstas} fflags[] = {
59221807Sstas	{PS_FST_FFLAG_WRITE,	'w'},
60221807Sstas	{PS_FST_FFLAG_APPEND,	'a'},
61221807Sstas	{PS_FST_FFLAG_DIRECT,	'd'},
62221807Sstas	{PS_FST_FFLAG_SHLOCK,	's'},
63221807Sstas	{PS_FST_FFLAG_EXLOCK,	'e'}
64221807Sstas};
65221807Sstas#define	NFFLAGS	(sizeof(fflags) / sizeof(*fflags))
66221807Sstas
67221807Sstas/*
68221807Sstas * Usage flags translation table.
69221807Sstas */
70227239Sedstatic const struct {
71221807Sstas	int	flag;
72221807Sstas	char	ch;
73221807Sstas} uflags[] = {
74221807Sstas	{PS_FST_UFLAG_RDIR,	'r'},
75221807Sstas	{PS_FST_UFLAG_CDIR,	'c'},
76221807Sstas	{PS_FST_UFLAG_JAIL,	'j'},
77221807Sstas	{PS_FST_UFLAG_TRACE,	't'},
78221807Sstas	{PS_FST_UFLAG_TEXT,	'x'},
79221807Sstas	{PS_FST_UFLAG_MMAP,	'm'},
80221807Sstas	{PS_FST_UFLAG_CTTY,	'y'}
81221807Sstas};
82221807Sstas#define	NUFLAGS	(sizeof(uflags) / sizeof(*uflags))
83221807Sstas
84221807Sstasstruct consumer {
85221807Sstas	pid_t	pid;
86221807Sstas	uid_t	uid;
87221807Sstas	int	fd;
88221807Sstas	int	flags;
89221807Sstas	int	uflags;
90221807Sstas	STAILQ_ENTRY(consumer)	next;
91221807Sstas};
92221807Sstasstruct reqfile {
93221807Sstas	uint32_t	fsid;
94221807Sstas	uint64_t	fileid;
95221807Sstas	const char	*name;
96221807Sstas	STAILQ_HEAD(, consumer) consumers;
97221807Sstas};
98221807Sstas
99221807Sstas/*
100221807Sstas * Option flags.
101221807Sstas */
102221807Sstas#define	UFLAG	0x01	/* -u flag: show users				*/
103221807Sstas#define	FFLAG	0x02	/* -f flag: specified files only		*/
104221807Sstas#define	CFLAG	0x04	/* -c flag: treat as mpoints			*/
105221807Sstas#define	MFLAG	0x10	/* -m flag: mmapped files too			*/
106221807Sstas#define	KFLAG	0x20	/* -k flag: send signal (SIGKILL by default)	*/
107221807Sstas
108221807Sstasstatic int flags = 0;	/* Option flags. */
109221807Sstas
110221807Sstasstatic void	printflags(struct consumer *consumer);
111221807Sstasstatic int	str2sig(const char *str);
112221807Sstasstatic void	usage(void) __dead2;
113221807Sstasstatic int	addfile(const char *path, struct reqfile *reqfile);
114221807Sstasstatic void	dofiles(struct procstat *procstat, struct kinfo_proc *kp,
115221807Sstas    struct reqfile *reqfiles, size_t nfiles);
116221807Sstas
117221807Sstasstatic void
118221807Sstasusage(void)
119221807Sstas{
120221807Sstas
121221807Sstas	fprintf(stderr,
122221807Sstas"usage: fuser [-cfhkmu] [-M core] [-N system] [-s signal] file ...\n");
123221807Sstas	exit(EX_USAGE);
124221807Sstas}
125221807Sstas
126221807Sstasstatic void
127221807Sstasprintflags(struct consumer *cons)
128221807Sstas{
129221807Sstas	unsigned int i;
130221807Sstas
131221807Sstas	assert(cons);
132221807Sstas	for (i = 0; i < NUFLAGS; i++)
133221807Sstas		if ((cons->uflags & uflags[i].flag) != 0)
134221807Sstas			fputc(uflags[i].ch, stderr);
135221807Sstas	for (i = 0; i < NFFLAGS; i++)
136221807Sstas		if ((cons->flags & fflags[i].flag) != 0)
137221807Sstas			fputc(fflags[i].ch, stderr);
138221807Sstas}
139221807Sstas
140221807Sstas/*
141221807Sstas * Add file to the list.
142221807Sstas */
143221807Sstasstatic int
144221807Sstasaddfile(const char *path, struct reqfile *reqfile)
145221807Sstas{
146221807Sstas	struct stat sb;
147221807Sstas
148221807Sstas	assert(path);
149221807Sstas	if (stat(path, &sb) != 0) {
150221807Sstas		warn("%s", path);
151221807Sstas		return (1);
152221807Sstas	}
153221807Sstas	reqfile->fileid = sb.st_ino;
154221807Sstas	reqfile->fsid = sb.st_dev;
155221807Sstas	reqfile->name = path;
156221807Sstas	STAILQ_INIT(&reqfile->consumers);
157221807Sstas	return (0);
158221807Sstas}
159221807Sstas
160221807Sstasint
161221807Sstasdo_fuser(int argc, char *argv[])
162221807Sstas{
163221807Sstas	struct consumer *consumer;
164221807Sstas	struct kinfo_proc *p, *procs;
165221807Sstas	struct procstat *procstat;
166221807Sstas	struct reqfile *reqfiles;
167221807Sstas	char *ep, *nlistf, *memf;
168221807Sstas	int ch, cnt, sig;
169221807Sstas	unsigned int i, nfiles;
170221807Sstas
171221807Sstas	sig = SIGKILL;	/* Default to kill. */
172221807Sstas	nlistf = NULL;
173221807Sstas	memf = NULL;
174221807Sstas	while ((ch = getopt(argc, argv, "M:N:cfhkms:u")) != -1)
175221807Sstas		switch(ch) {
176221807Sstas		case 'f':
177221807Sstas			if ((flags & CFLAG) != 0)
178221807Sstas				usage();
179221807Sstas			flags |= FFLAG;
180221807Sstas			break;
181221807Sstas		case 'c':
182221807Sstas			if ((flags & FFLAG) != 0)
183221807Sstas				usage();
184221807Sstas			flags |= CFLAG;
185221807Sstas			break;
186221807Sstas		case 'N':
187221807Sstas			nlistf = optarg;
188221807Sstas			break;
189221807Sstas		case 'M':
190221807Sstas			memf = optarg;
191221807Sstas			break;
192221807Sstas		case 'u':
193221807Sstas			flags |= UFLAG;
194221807Sstas			break;
195221807Sstas		case 'm':
196221807Sstas			flags |= MFLAG;
197221807Sstas			break;
198221807Sstas		case 'k':
199221807Sstas			flags |= KFLAG;
200221807Sstas			break;
201221807Sstas		case 's':
202221807Sstas			if (isdigit(*optarg)) {
203221807Sstas				sig = strtol(optarg, &ep, 10);
204221807Sstas				if (*ep != '\0' || sig < 0 || sig >= sys_nsig)
205221807Sstas					errx(EX_USAGE, "illegal signal number" ": %s",
206221807Sstas					    optarg);
207221807Sstas			} else {
208221807Sstas				sig = str2sig(optarg);
209221807Sstas				if (sig < 0)
210221807Sstas					errx(EX_USAGE, "illegal signal name: "
211221807Sstas					    "%s", optarg);
212221807Sstas			}
213221807Sstas			break;
214221807Sstas		case 'h':
215221807Sstas			/* PASSTHROUGH */
216221807Sstas		default:
217221807Sstas			usage();
218221807Sstas			/* NORETURN */
219221807Sstas		}
220221807Sstas	argv += optind;
221221807Sstas	argc -= optind;
222221807Sstas
223221807Sstas	assert(argc >= 0);
224221807Sstas	if (argc == 0)
225221807Sstas		usage();
226221807Sstas		/* NORETURN */
227221807Sstas
228221807Sstas	/*
229221807Sstas	 * Process named files.
230221807Sstas	 */
231221807Sstas	reqfiles = malloc(argc * sizeof(struct reqfile));
232221807Sstas	if (reqfiles == NULL)
233221807Sstas		err(EX_OSERR, "malloc()");
234221807Sstas	nfiles = 0;
235221807Sstas	while (argc--)
236221807Sstas		if (!addfile(*(argv++), &reqfiles[nfiles]))
237221807Sstas			nfiles++;
238221807Sstas	if (nfiles == 0)
239221807Sstas		errx(EX_IOERR, "files not accessible");
240221807Sstas
241221807Sstas	if (memf != NULL)
242221807Sstas		procstat = procstat_open_kvm(nlistf, memf);
243221807Sstas	else
244221807Sstas		procstat = procstat_open_sysctl();
245221807Sstas	if (procstat == NULL)
246221807Sstas		errx(1, "procstat_open()");
247221807Sstas	procs = procstat_getprocs(procstat, KERN_PROC_PROC, 0, &cnt);
248221807Sstas	if (procs == NULL)
249221807Sstas		 errx(1, "procstat_getprocs()");
250221807Sstas
251221807Sstas	/*
252221807Sstas	 * Walk through process table and look for matching files.
253221807Sstas	 */
254221807Sstas	p = procs;
255221807Sstas	while(cnt--)
256221807Sstas		if (p->ki_stat != SZOMB)
257221807Sstas			dofiles(procstat, p++, reqfiles, nfiles);
258221807Sstas
259221807Sstas	for (i = 0; i < nfiles; i++) {
260221807Sstas		fprintf(stderr, "%s:", reqfiles[i].name);
261221807Sstas		fflush(stderr);
262221807Sstas		STAILQ_FOREACH(consumer, &reqfiles[i].consumers, next) {
263221807Sstas			if (consumer->flags != 0) {
264221807Sstas				fprintf(stdout, "%6d", consumer->pid);
265221807Sstas				fflush(stdout);
266221807Sstas				printflags(consumer);
267221807Sstas				if ((flags & UFLAG) != 0)
268221807Sstas					fprintf(stderr, "(%s)",
269221807Sstas					    user_from_uid(consumer->uid, 0));
270221807Sstas				if ((flags & KFLAG) != 0)
271221807Sstas					kill(consumer->pid, sig);
272221807Sstas				fflush(stderr);
273221807Sstas			}
274221807Sstas		}
275221807Sstas		(void)fprintf(stderr, "\n");
276221807Sstas	}
277221807Sstas	procstat_freeprocs(procstat, procs);
278221807Sstas	procstat_close(procstat);
279221807Sstas	free(reqfiles);
280221807Sstas	return (0);
281221807Sstas}
282221807Sstas
283221807Sstasstatic void
284221807Sstasdofiles(struct procstat *procstat, struct kinfo_proc *kp,
285221807Sstas    struct reqfile *reqfiles, size_t nfiles)
286221807Sstas{
287221807Sstas	struct vnstat vn;
288221807Sstas	struct consumer *cons;
289221807Sstas	struct filestat *fst;
290221807Sstas	struct filestat_list *head;
291221807Sstas	int error, match;
292221807Sstas	unsigned int i;
293221807Sstas	char errbuf[_POSIX2_LINE_MAX];
294221807Sstas
295221807Sstas	head = procstat_getfiles(procstat, kp, flags & MFLAG);
296221807Sstas	if (head == NULL)
297221807Sstas		return;
298221807Sstas	STAILQ_FOREACH(fst, head, next) {
299221807Sstas		if (fst->fs_type != PS_FST_TYPE_VNODE)
300221807Sstas			continue;
301221807Sstas		error = procstat_get_vnode_info(procstat, fst, &vn, errbuf);
302221807Sstas		if (error != 0)
303221807Sstas			continue;
304221807Sstas		for (i = 0; i < nfiles; i++) {
305221807Sstas			if (flags & CFLAG && reqfiles[i].fsid == vn.vn_fsid) {
306221807Sstas				break;
307221807Sstas			}
308221807Sstas			else if (reqfiles[i].fsid == vn.vn_fsid &&
309221807Sstas			    reqfiles[i].fileid == vn.vn_fileid) {
310221807Sstas				break;
311221807Sstas			}
312221807Sstas			else if (!(flags & FFLAG) &&
313221807Sstas			    (vn.vn_type == PS_FST_VTYPE_VCHR ||
314221807Sstas			    vn.vn_type == PS_FST_VTYPE_VBLK) &&
315221807Sstas			    vn.vn_fsid == reqfiles[i].fileid) {
316221807Sstas				break;
317221807Sstas			}
318221807Sstas		}
319221807Sstas		if (i == nfiles)
320221807Sstas			continue;	/* No match. */
321221807Sstas
322221807Sstas		/*
323221807Sstas		 * Look for existing entries.
324221807Sstas		 */
325221807Sstas		match = 0;
326221807Sstas		STAILQ_FOREACH(cons, &reqfiles[i].consumers, next)
327221807Sstas			if (cons->pid == kp->ki_pid) {
328221807Sstas				match = 1;
329221807Sstas				break;
330221807Sstas			}
331221807Sstas		if (match == 1) {	/* Use old entry. */
332221807Sstas			cons->flags |= fst->fs_fflags;
333221807Sstas			cons->uflags |= fst->fs_uflags;
334221807Sstas		} else {
335221807Sstas			/*
336221807Sstas			 * Create new entry in the consumer chain.
337221807Sstas			 */
338221807Sstas			cons = calloc(1, sizeof(struct consumer));
339221807Sstas			if (cons == NULL) {
340221807Sstas				warn("malloc()");
341221807Sstas				continue;
342221807Sstas			}
343221807Sstas			cons->uid = kp->ki_uid;
344221807Sstas			cons->pid = kp->ki_pid;
345221807Sstas			cons->uflags = fst->fs_uflags;
346221807Sstas			cons->flags = fst->fs_fflags;
347221807Sstas			STAILQ_INSERT_TAIL(&reqfiles[i].consumers, cons, next);
348221807Sstas		}
349221807Sstas	}
350221807Sstas	procstat_freefiles(procstat, head);
351221807Sstas}
352221807Sstas
353221807Sstas/*
354221807Sstas * Returns signal number for it's string representation.
355221807Sstas */
356221807Sstasstatic int
357221807Sstasstr2sig(const char *str)
358221807Sstas{
359221807Sstas	int i;
360221807Sstas
361223271Sjilles	if (!strncasecmp(str, "SIG", 3))
362223271Sjilles		str += 3;
363221807Sstas	for (i = 1; i < sys_nsig; i++) {
364221807Sstas                if (!strcasecmp(sys_signame[i], str))
365221807Sstas                        return (i);
366221807Sstas        }
367221807Sstas        return (-1);
368221807Sstas}
369