xref: /freebsd/sys/dev/filemon/filemon.c (revision 2b198fe92f4aebe14640c7c1256bb222d5fdbb2d)
1eb9aea5aSDavid E. O'Brien /*-
2eb9aea5aSDavid E. O'Brien  * Copyright (c) 2011, David E. O'Brien.
3eb9aea5aSDavid E. O'Brien  * Copyright (c) 2009-2011, Juniper Networks, Inc.
48183f2e3SBryan Drewery  * Copyright (c) 2015, EMC Corp.
5eb9aea5aSDavid E. O'Brien  * All rights reserved.
6eb9aea5aSDavid E. O'Brien  *
7eb9aea5aSDavid E. O'Brien  * Redistribution and use in source and binary forms, with or without
8eb9aea5aSDavid E. O'Brien  * modification, are permitted provided that the following conditions
9eb9aea5aSDavid E. O'Brien  * are met:
10eb9aea5aSDavid E. O'Brien  * 1. Redistributions of source code must retain the above copyright
11eb9aea5aSDavid E. O'Brien  *    notice, this list of conditions and the following disclaimer.
12eb9aea5aSDavid E. O'Brien  * 2. Redistributions in binary form must reproduce the above copyright
13eb9aea5aSDavid E. O'Brien  *    notice, this list of conditions and the following disclaimer in the
14eb9aea5aSDavid E. O'Brien  *    documentation and/or other materials provided with the distribution.
15eb9aea5aSDavid E. O'Brien  *
16eb9aea5aSDavid E. O'Brien  * THIS SOFTWARE IS PROVIDED BY JUNIPER NETWORKS AND CONTRIBUTORS ``AS IS'' AND
17eb9aea5aSDavid E. O'Brien  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18eb9aea5aSDavid E. O'Brien  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19eb9aea5aSDavid E. O'Brien  * ARE DISCLAIMED. IN NO EVENT SHALL JUNIPER NETWORKS OR CONTRIBUTORS BE LIABLE
20eb9aea5aSDavid E. O'Brien  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21eb9aea5aSDavid E. O'Brien  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22eb9aea5aSDavid E. O'Brien  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23eb9aea5aSDavid E. O'Brien  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24eb9aea5aSDavid E. O'Brien  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25eb9aea5aSDavid E. O'Brien  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26eb9aea5aSDavid E. O'Brien  * SUCH DAMAGE.
27eb9aea5aSDavid E. O'Brien  */
28eb9aea5aSDavid E. O'Brien 
29af13de0fSJohn Baldwin #include <sys/cdefs.h>
30eb9aea5aSDavid E. O'Brien __FBSDID("$FreeBSD$");
31eb9aea5aSDavid E. O'Brien 
32f9d4b392SDavid E. O'Brien #include "opt_compat.h"
33f9d4b392SDavid E. O'Brien 
34af13de0fSJohn Baldwin #include <sys/param.h>
35eb9aea5aSDavid E. O'Brien #include <sys/file.h>
36eb9aea5aSDavid E. O'Brien #include <sys/systm.h>
37eb9aea5aSDavid E. O'Brien #include <sys/buf.h>
38eb9aea5aSDavid E. O'Brien #include <sys/condvar.h>
39eb9aea5aSDavid E. O'Brien #include <sys/conf.h>
40eb9aea5aSDavid E. O'Brien #include <sys/fcntl.h>
41eb9aea5aSDavid E. O'Brien #include <sys/ioccom.h>
42eb9aea5aSDavid E. O'Brien #include <sys/kernel.h>
438183f2e3SBryan Drewery #include <sys/lock.h>
44eb9aea5aSDavid E. O'Brien #include <sys/malloc.h>
45eb9aea5aSDavid E. O'Brien #include <sys/module.h>
46eb9aea5aSDavid E. O'Brien #include <sys/poll.h>
47eb9aea5aSDavid E. O'Brien #include <sys/proc.h>
48eb9aea5aSDavid E. O'Brien #include <sys/queue.h>
498183f2e3SBryan Drewery #include <sys/sx.h>
50eb9aea5aSDavid E. O'Brien #include <sys/syscall.h>
51eb9aea5aSDavid E. O'Brien #include <sys/sysent.h>
52eb9aea5aSDavid E. O'Brien #include <sys/sysproto.h>
53eb9aea5aSDavid E. O'Brien #include <sys/uio.h>
54eb9aea5aSDavid E. O'Brien 
55eb9aea5aSDavid E. O'Brien #if __FreeBSD_version >= 900041
564a144410SRobert Watson #include <sys/capsicum.h>
57eb9aea5aSDavid E. O'Brien #endif
58eb9aea5aSDavid E. O'Brien 
59eb9aea5aSDavid E. O'Brien #include "filemon.h"
60eb9aea5aSDavid E. O'Brien 
61eb9aea5aSDavid E. O'Brien #if defined(COMPAT_IA32) || defined(COMPAT_FREEBSD32) || defined(COMPAT_ARCH32)
62eb9aea5aSDavid E. O'Brien #include <compat/freebsd32/freebsd32_syscall.h>
63eb9aea5aSDavid E. O'Brien #include <compat/freebsd32/freebsd32_proto.h>
64eb9aea5aSDavid E. O'Brien 
65eb9aea5aSDavid E. O'Brien extern struct sysentvec ia32_freebsd_sysvec;
66eb9aea5aSDavid E. O'Brien #endif
67eb9aea5aSDavid E. O'Brien 
68eb9aea5aSDavid E. O'Brien extern struct sysentvec elf32_freebsd_sysvec;
69eb9aea5aSDavid E. O'Brien extern struct sysentvec elf64_freebsd_sysvec;
70eb9aea5aSDavid E. O'Brien 
71eb9aea5aSDavid E. O'Brien static d_close_t	filemon_close;
72eb9aea5aSDavid E. O'Brien static d_ioctl_t	filemon_ioctl;
73eb9aea5aSDavid E. O'Brien static d_open_t		filemon_open;
74eb9aea5aSDavid E. O'Brien static int		filemon_unload(void);
75eb9aea5aSDavid E. O'Brien static void		filemon_load(void *);
76eb9aea5aSDavid E. O'Brien 
77eb9aea5aSDavid E. O'Brien static struct cdevsw filemon_cdevsw = {
78eb9aea5aSDavid E. O'Brien 	.d_version	= D_VERSION,
79eb9aea5aSDavid E. O'Brien 	.d_close	= filemon_close,
80eb9aea5aSDavid E. O'Brien 	.d_ioctl	= filemon_ioctl,
81eb9aea5aSDavid E. O'Brien 	.d_open		= filemon_open,
82eb9aea5aSDavid E. O'Brien 	.d_name		= "filemon",
83eb9aea5aSDavid E. O'Brien };
84eb9aea5aSDavid E. O'Brien 
85eb9aea5aSDavid E. O'Brien MALLOC_DECLARE(M_FILEMON);
86eb9aea5aSDavid E. O'Brien MALLOC_DEFINE(M_FILEMON, "filemon", "File access monitor");
87eb9aea5aSDavid E. O'Brien 
88eb9aea5aSDavid E. O'Brien struct filemon {
89eb9aea5aSDavid E. O'Brien 	TAILQ_ENTRY(filemon) link;	/* Link into the in-use list. */
908183f2e3SBryan Drewery 	struct sx	lock;		/* Lock mutex for this filemon. */
91eb9aea5aSDavid E. O'Brien 	struct file	*fp;		/* Output file pointer. */
92eb9aea5aSDavid E. O'Brien 	pid_t		pid;		/* The process ID being monitored. */
93eb9aea5aSDavid E. O'Brien 	char		fname1[MAXPATHLEN]; /* Temporary filename buffer. */
94eb9aea5aSDavid E. O'Brien 	char		fname2[MAXPATHLEN]; /* Temporary filename buffer. */
95eb9aea5aSDavid E. O'Brien 	char		msgbufr[1024];	/* Output message buffer. */
96eb9aea5aSDavid E. O'Brien };
97eb9aea5aSDavid E. O'Brien 
98eb9aea5aSDavid E. O'Brien static TAILQ_HEAD(, filemon) filemons_inuse = TAILQ_HEAD_INITIALIZER(filemons_inuse);
99eb9aea5aSDavid E. O'Brien static TAILQ_HEAD(, filemon) filemons_free = TAILQ_HEAD_INITIALIZER(filemons_free);
1008183f2e3SBryan Drewery static struct sx access_lock;
101eb9aea5aSDavid E. O'Brien 
102eb9aea5aSDavid E. O'Brien static struct cdev *filemon_dev;
103eb9aea5aSDavid E. O'Brien 
104eb9aea5aSDavid E. O'Brien #include "filemon_lock.c"
105eb9aea5aSDavid E. O'Brien #include "filemon_wrapper.c"
106eb9aea5aSDavid E. O'Brien 
107eb9aea5aSDavid E. O'Brien static void
108*2b198fe9SBryan Drewery filemon_comment(struct filemon *filemon)
109*2b198fe9SBryan Drewery {
110*2b198fe9SBryan Drewery 	int len;
111*2b198fe9SBryan Drewery 	struct timeval now;
112*2b198fe9SBryan Drewery 
113*2b198fe9SBryan Drewery 	getmicrotime(&now);
114*2b198fe9SBryan Drewery 
115*2b198fe9SBryan Drewery 	len = snprintf(filemon->msgbufr, sizeof(filemon->msgbufr),
116*2b198fe9SBryan Drewery 	    "# filemon version %d\n# Target pid %d\n# Start %ju.%06ju\nV %d\n",
117*2b198fe9SBryan Drewery 	    FILEMON_VERSION, curproc->p_pid, (uintmax_t)now.tv_sec,
118*2b198fe9SBryan Drewery 	    (uintmax_t)now.tv_usec, FILEMON_VERSION);
119*2b198fe9SBryan Drewery 
120*2b198fe9SBryan Drewery 	filemon_output(filemon, filemon->msgbufr, len);
121*2b198fe9SBryan Drewery }
122*2b198fe9SBryan Drewery 
123*2b198fe9SBryan Drewery static void
124eb9aea5aSDavid E. O'Brien filemon_dtr(void *data)
125eb9aea5aSDavid E. O'Brien {
126eb9aea5aSDavid E. O'Brien 	struct filemon *filemon = data;
127eb9aea5aSDavid E. O'Brien 
128eb9aea5aSDavid E. O'Brien 	if (filemon != NULL) {
129eb9aea5aSDavid E. O'Brien 		struct file *fp = filemon->fp;
130eb9aea5aSDavid E. O'Brien 
131eb9aea5aSDavid E. O'Brien 		/* Get exclusive write access. */
132eb9aea5aSDavid E. O'Brien 		filemon_lock_write();
133eb9aea5aSDavid E. O'Brien 
134eb9aea5aSDavid E. O'Brien 		/* Remove from the in-use list. */
135eb9aea5aSDavid E. O'Brien 		TAILQ_REMOVE(&filemons_inuse, filemon, link);
136eb9aea5aSDavid E. O'Brien 
137eb9aea5aSDavid E. O'Brien 		filemon->fp = NULL;
138eb9aea5aSDavid E. O'Brien 		filemon->pid = -1;
139eb9aea5aSDavid E. O'Brien 
140eb9aea5aSDavid E. O'Brien 		/* Add to the free list. */
141eb9aea5aSDavid E. O'Brien 		TAILQ_INSERT_TAIL(&filemons_free, filemon, link);
142eb9aea5aSDavid E. O'Brien 
143eb9aea5aSDavid E. O'Brien 		/* Give up write access. */
144eb9aea5aSDavid E. O'Brien 		filemon_unlock_write();
145eb9aea5aSDavid E. O'Brien 
146eb9aea5aSDavid E. O'Brien 		if (fp != NULL)
147eb9aea5aSDavid E. O'Brien 			fdrop(fp, curthread);
148eb9aea5aSDavid E. O'Brien 	}
149eb9aea5aSDavid E. O'Brien }
150eb9aea5aSDavid E. O'Brien 
151eb9aea5aSDavid E. O'Brien static int
152eb9aea5aSDavid E. O'Brien filemon_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag __unused,
153eb9aea5aSDavid E. O'Brien     struct thread *td)
154eb9aea5aSDavid E. O'Brien {
155eb9aea5aSDavid E. O'Brien 	int error = 0;
156eb9aea5aSDavid E. O'Brien 	struct filemon *filemon;
157872ce247SHiroki Sato 	struct proc *p;
1587008be5bSPawel Jakub Dawidek #if __FreeBSD_version >= 900041
1597008be5bSPawel Jakub Dawidek 	cap_rights_t rights;
1607008be5bSPawel Jakub Dawidek #endif
161eb9aea5aSDavid E. O'Brien 
162e8c87a09SBryan Drewery 	if ((error = devfs_get_cdevpriv((void **) &filemon)) != 0)
163e8c87a09SBryan Drewery 		return (error);
164eb9aea5aSDavid E. O'Brien 
16564c368a4SBryan Drewery 	filemon_filemon_lock(filemon);
16664c368a4SBryan Drewery 
167eb9aea5aSDavid E. O'Brien 	switch (cmd) {
168eb9aea5aSDavid E. O'Brien 	/* Set the output file descriptor. */
169eb9aea5aSDavid E. O'Brien 	case FILEMON_SET_FD:
170fac4a7acSBryan Drewery 		if (filemon->fp != NULL)
171fac4a7acSBryan Drewery 			fdrop(filemon->fp, td);
172fac4a7acSBryan Drewery 
1737008be5bSPawel Jakub Dawidek 		error = fget_write(td, *(int *)data,
1747008be5bSPawel Jakub Dawidek #if __FreeBSD_version >= 900041
1757008be5bSPawel Jakub Dawidek 		    cap_rights_init(&rights, CAP_PWRITE),
1767008be5bSPawel Jakub Dawidek #endif
1777008be5bSPawel Jakub Dawidek 		    &filemon->fp);
1787008be5bSPawel Jakub Dawidek 		if (error == 0)
179eb9aea5aSDavid E. O'Brien 			/* Write the file header. */
180eb9aea5aSDavid E. O'Brien 			filemon_comment(filemon);
181eb9aea5aSDavid E. O'Brien 		break;
182eb9aea5aSDavid E. O'Brien 
183eb9aea5aSDavid E. O'Brien 	/* Set the monitored process ID. */
184eb9aea5aSDavid E. O'Brien 	case FILEMON_SET_PID:
18589cac24eSHiroki Sato 		error = pget(*((pid_t *)data), PGET_CANDEBUG | PGET_NOTWEXIT,
18689cac24eSHiroki Sato 		    &p);
18789cac24eSHiroki Sato 		if (error == 0) {
188872ce247SHiroki Sato 			filemon->pid = p->p_pid;
189872ce247SHiroki Sato 			PROC_UNLOCK(p);
19089cac24eSHiroki Sato 		}
191eb9aea5aSDavid E. O'Brien 		break;
192eb9aea5aSDavid E. O'Brien 
193eb9aea5aSDavid E. O'Brien 	default:
194eb9aea5aSDavid E. O'Brien 		error = EINVAL;
195eb9aea5aSDavid E. O'Brien 		break;
196eb9aea5aSDavid E. O'Brien 	}
197eb9aea5aSDavid E. O'Brien 
19864c368a4SBryan Drewery 	filemon_filemon_unlock(filemon);
199eb9aea5aSDavid E. O'Brien 	return (error);
200eb9aea5aSDavid E. O'Brien }
201eb9aea5aSDavid E. O'Brien 
202eb9aea5aSDavid E. O'Brien static int
203eb9aea5aSDavid E. O'Brien filemon_open(struct cdev *dev, int oflags __unused, int devtype __unused,
204eb9aea5aSDavid E. O'Brien     struct thread *td __unused)
205eb9aea5aSDavid E. O'Brien {
206eb9aea5aSDavid E. O'Brien 	struct filemon *filemon;
207eb9aea5aSDavid E. O'Brien 
208eb9aea5aSDavid E. O'Brien 	/* Get exclusive write access. */
209eb9aea5aSDavid E. O'Brien 	filemon_lock_write();
210eb9aea5aSDavid E. O'Brien 
211eb9aea5aSDavid E. O'Brien 	if ((filemon = TAILQ_FIRST(&filemons_free)) != NULL)
212eb9aea5aSDavid E. O'Brien 		TAILQ_REMOVE(&filemons_free, filemon, link);
213eb9aea5aSDavid E. O'Brien 
214eb9aea5aSDavid E. O'Brien 	/* Give up write access. */
215eb9aea5aSDavid E. O'Brien 	filemon_unlock_write();
216eb9aea5aSDavid E. O'Brien 
217eb9aea5aSDavid E. O'Brien 	if (filemon == NULL) {
218eb9aea5aSDavid E. O'Brien 		filemon = malloc(sizeof(struct filemon), M_FILEMON,
219eb9aea5aSDavid E. O'Brien 		    M_WAITOK | M_ZERO);
2208183f2e3SBryan Drewery 		sx_init(&filemon->lock, "filemon");
221eb9aea5aSDavid E. O'Brien 	}
222eb9aea5aSDavid E. O'Brien 
223eb9aea5aSDavid E. O'Brien 	filemon->pid = curproc->p_pid;
224eb9aea5aSDavid E. O'Brien 
225eb9aea5aSDavid E. O'Brien 	devfs_set_cdevpriv(filemon, filemon_dtr);
226eb9aea5aSDavid E. O'Brien 
227eb9aea5aSDavid E. O'Brien 	/* Get exclusive write access. */
228eb9aea5aSDavid E. O'Brien 	filemon_lock_write();
229eb9aea5aSDavid E. O'Brien 
230eb9aea5aSDavid E. O'Brien 	/* Add to the in-use list. */
231eb9aea5aSDavid E. O'Brien 	TAILQ_INSERT_TAIL(&filemons_inuse, filemon, link);
232eb9aea5aSDavid E. O'Brien 
233eb9aea5aSDavid E. O'Brien 	/* Give up write access. */
234eb9aea5aSDavid E. O'Brien 	filemon_unlock_write();
235eb9aea5aSDavid E. O'Brien 
236eb9aea5aSDavid E. O'Brien 	return (0);
237eb9aea5aSDavid E. O'Brien }
238eb9aea5aSDavid E. O'Brien 
239eb9aea5aSDavid E. O'Brien static int
240eb9aea5aSDavid E. O'Brien filemon_close(struct cdev *dev __unused, int flag __unused, int fmt __unused,
241eb9aea5aSDavid E. O'Brien     struct thread *td __unused)
242eb9aea5aSDavid E. O'Brien {
243eb9aea5aSDavid E. O'Brien 
244eb9aea5aSDavid E. O'Brien 	return (0);
245eb9aea5aSDavid E. O'Brien }
246eb9aea5aSDavid E. O'Brien 
247eb9aea5aSDavid E. O'Brien static void
248eb9aea5aSDavid E. O'Brien filemon_load(void *dummy __unused)
249eb9aea5aSDavid E. O'Brien {
2508183f2e3SBryan Drewery 	sx_init(&access_lock, "filemons_inuse");
251eb9aea5aSDavid E. O'Brien 
252eb9aea5aSDavid E. O'Brien 	/* Install the syscall wrappers. */
253eb9aea5aSDavid E. O'Brien 	filemon_wrapper_install();
254eb9aea5aSDavid E. O'Brien 
255eb9aea5aSDavid E. O'Brien 	filemon_dev = make_dev(&filemon_cdevsw, 0, UID_ROOT, GID_WHEEL, 0666,
256eb9aea5aSDavid E. O'Brien 	    "filemon");
257eb9aea5aSDavid E. O'Brien }
258eb9aea5aSDavid E. O'Brien 
259eb9aea5aSDavid E. O'Brien static int
260eb9aea5aSDavid E. O'Brien filemon_unload(void)
261eb9aea5aSDavid E. O'Brien {
262eb9aea5aSDavid E. O'Brien  	struct filemon *filemon;
263eb9aea5aSDavid E. O'Brien 	int error = 0;
264eb9aea5aSDavid E. O'Brien 
265eb9aea5aSDavid E. O'Brien 	/* Get exclusive write access. */
266eb9aea5aSDavid E. O'Brien 	filemon_lock_write();
267eb9aea5aSDavid E. O'Brien 
268eb9aea5aSDavid E. O'Brien 	if (TAILQ_FIRST(&filemons_inuse) != NULL)
269eb9aea5aSDavid E. O'Brien 		error = EBUSY;
270eb9aea5aSDavid E. O'Brien 	else {
271eb9aea5aSDavid E. O'Brien 		destroy_dev(filemon_dev);
272eb9aea5aSDavid E. O'Brien 
273eb9aea5aSDavid E. O'Brien 		/* Deinstall the syscall wrappers. */
274eb9aea5aSDavid E. O'Brien 		filemon_wrapper_deinstall();
275eb9aea5aSDavid E. O'Brien 	}
276eb9aea5aSDavid E. O'Brien 
277eb9aea5aSDavid E. O'Brien 	/* Give up write access. */
278eb9aea5aSDavid E. O'Brien 	filemon_unlock_write();
279eb9aea5aSDavid E. O'Brien 
280eb9aea5aSDavid E. O'Brien 	if (error == 0) {
281eb9aea5aSDavid E. O'Brien 		/* free() filemon structs free list. */
282eb9aea5aSDavid E. O'Brien 		filemon_lock_write();
283eb9aea5aSDavid E. O'Brien 		while ((filemon = TAILQ_FIRST(&filemons_free)) != NULL) {
284eb9aea5aSDavid E. O'Brien 			TAILQ_REMOVE(&filemons_free, filemon, link);
2858183f2e3SBryan Drewery 			sx_destroy(&filemon->lock);
286eb9aea5aSDavid E. O'Brien 			free(filemon, M_FILEMON);
287eb9aea5aSDavid E. O'Brien 		}
288eb9aea5aSDavid E. O'Brien 		filemon_unlock_write();
289eb9aea5aSDavid E. O'Brien 
2908183f2e3SBryan Drewery 		sx_destroy(&access_lock);
291eb9aea5aSDavid E. O'Brien 	}
292eb9aea5aSDavid E. O'Brien 
293eb9aea5aSDavid E. O'Brien 	return (error);
294eb9aea5aSDavid E. O'Brien }
295eb9aea5aSDavid E. O'Brien 
296eb9aea5aSDavid E. O'Brien static int
297eb9aea5aSDavid E. O'Brien filemon_modevent(module_t mod __unused, int type, void *data)
298eb9aea5aSDavid E. O'Brien {
299eb9aea5aSDavid E. O'Brien 	int error = 0;
300eb9aea5aSDavid E. O'Brien 
301eb9aea5aSDavid E. O'Brien 	switch (type) {
302eb9aea5aSDavid E. O'Brien 	case MOD_LOAD:
303eb9aea5aSDavid E. O'Brien 		filemon_load(data);
304eb9aea5aSDavid E. O'Brien 		break;
305eb9aea5aSDavid E. O'Brien 
306eb9aea5aSDavid E. O'Brien 	case MOD_UNLOAD:
307eb9aea5aSDavid E. O'Brien 		error = filemon_unload();
308eb9aea5aSDavid E. O'Brien 		break;
309eb9aea5aSDavid E. O'Brien 
310eb9aea5aSDavid E. O'Brien 	case MOD_SHUTDOWN:
311eb9aea5aSDavid E. O'Brien 		break;
312eb9aea5aSDavid E. O'Brien 
313eb9aea5aSDavid E. O'Brien 	default:
314eb9aea5aSDavid E. O'Brien 		error = EOPNOTSUPP;
315eb9aea5aSDavid E. O'Brien 		break;
316eb9aea5aSDavid E. O'Brien 
317eb9aea5aSDavid E. O'Brien 	}
318eb9aea5aSDavid E. O'Brien 
319eb9aea5aSDavid E. O'Brien 	return (error);
320eb9aea5aSDavid E. O'Brien }
321eb9aea5aSDavid E. O'Brien 
322eb9aea5aSDavid E. O'Brien DEV_MODULE(filemon, filemon_modevent, NULL);
323eb9aea5aSDavid E. O'Brien MODULE_VERSION(filemon, 1);
324