autounmountd.c revision 270896
1/*-
2 * Copyright (c) 2014 The FreeBSD Foundation
3 * All rights reserved.
4 *
5 * This software was developed by Edward Tomasz Napierala under sponsorship
6 * from the FreeBSD Foundation.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 *
29 * $FreeBSD: stable/10/usr.sbin/autofs/autounmountd.c 270896 2014-08-31 21:46:32Z trasz $
30 */
31
32#include <sys/types.h>
33#include <sys/event.h>
34#include <sys/mount.h>
35#include <sys/time.h>
36#include <assert.h>
37#include <errno.h>
38#include <stdbool.h>
39#include <stdint.h>
40#include <stdio.h>
41#include <stdlib.h>
42#include <string.h>
43#include <unistd.h>
44#include <libutil.h>
45
46#include "common.h"
47
48#define AUTOUNMOUNTD_PIDFILE	"/var/run/autounmountd.pid"
49
50struct automounted_fs {
51	TAILQ_ENTRY(automounted_fs)	af_next;
52	time_t				af_mount_time;
53	bool				af_mark;
54	fsid_t				af_fsid;
55	char				af_mountpoint[MNAMELEN];
56};
57
58static TAILQ_HEAD(, automounted_fs)	automounted;
59
60static struct automounted_fs *
61automounted_find(fsid_t fsid)
62{
63	struct automounted_fs *af;
64
65	TAILQ_FOREACH(af, &automounted, af_next) {
66		if (af->af_fsid.val[0] == fsid.val[0] &&
67		    af->af_fsid.val[1] == fsid.val[1])
68			return (af);
69	}
70
71	return (NULL);
72}
73
74static struct automounted_fs *
75automounted_add(fsid_t fsid, const char *mountpoint)
76{
77	struct automounted_fs *af;
78
79	af = calloc(sizeof(*af), 1);
80	if (af == NULL)
81		log_err(1, "calloc");
82	af->af_mount_time = time(NULL);
83	af->af_fsid = fsid;
84	strlcpy(af->af_mountpoint, mountpoint, sizeof(af->af_mountpoint));
85
86	TAILQ_INSERT_TAIL(&automounted, af, af_next);
87
88	return (af);
89}
90
91static void
92automounted_remove(struct automounted_fs *af)
93{
94
95	TAILQ_REMOVE(&automounted, af, af_next);
96	free(af);
97}
98
99static void
100refresh_automounted(void)
101{
102	struct automounted_fs *af, *tmpaf;
103	struct statfs *mntbuf;
104	int i, nitems;
105
106	nitems = getmntinfo(&mntbuf, MNT_WAIT);
107	if (nitems <= 0)
108		log_err(1, "getmntinfo");
109
110	log_debugx("refreshing list of automounted filesystems");
111
112	TAILQ_FOREACH(af, &automounted, af_next)
113		af->af_mark = false;
114
115	for (i = 0; i < nitems; i++) {
116		if (strcmp(mntbuf[i].f_fstypename, "autofs") == 0) {
117			log_debugx("skipping %s, filesystem type is autofs",
118			    mntbuf[i].f_mntonname);
119			continue;
120		}
121
122		if ((mntbuf[i].f_flags & MNT_AUTOMOUNTED) == 0) {
123			log_debugx("skipping %s, not automounted",
124			    mntbuf[i].f_mntonname);
125			continue;
126		}
127
128		af = automounted_find(mntbuf[i].f_fsid);
129		if (af == NULL) {
130			log_debugx("new automounted filesystem found on %s "
131			    "(FSID:%d:%d)", mntbuf[i].f_mntonname,
132			    mntbuf[i].f_fsid.val[0], mntbuf[i].f_fsid.val[1]);
133			af = automounted_add(mntbuf[i].f_fsid,
134			    mntbuf[i].f_mntonname);
135		} else {
136			log_debugx("already known automounted filesystem "
137			    "found on %s (FSID:%d:%d)", mntbuf[i].f_mntonname,
138			    mntbuf[i].f_fsid.val[0], mntbuf[i].f_fsid.val[1]);
139		}
140		af->af_mark = true;
141	}
142
143	TAILQ_FOREACH_SAFE(af, &automounted, af_next, tmpaf) {
144		if (af->af_mark)
145			continue;
146		log_debugx("lost filesystem mounted on %s (FSID:%d:%d)",
147		    af->af_mountpoint, af->af_fsid.val[0], af->af_fsid.val[1]);
148		automounted_remove(af);
149	}
150}
151
152static int
153unmount_by_fsid(const fsid_t fsid, const char *mountpoint)
154{
155	char *fsid_str;
156	int error, ret;
157
158	ret = asprintf(&fsid_str, "FSID:%d:%d", fsid.val[0], fsid.val[1]);
159	if (ret < 0)
160		log_err(1, "asprintf");
161
162	error = unmount(fsid_str, MNT_BYFSID);
163	if (error != 0) {
164		if (errno == EBUSY) {
165			log_debugx("cannot unmount %s (%s): %s",
166			    mountpoint, fsid_str, strerror(errno));
167		} else {
168			log_warn("cannot unmount %s (%s)",
169			    mountpoint, fsid_str);
170		}
171	}
172
173	free(fsid_str);
174
175	return (error);
176}
177
178static double
179expire_automounted(double expiration_time)
180{
181	struct automounted_fs *af, *tmpaf;
182	time_t now;
183	double mounted_for, mounted_max = 0;
184	int error;
185
186	now = time(NULL);
187
188	log_debugx("expiring automounted filesystems");
189
190	TAILQ_FOREACH_SAFE(af, &automounted, af_next, tmpaf) {
191		mounted_for = difftime(now, af->af_mount_time);
192
193		if (mounted_for < expiration_time) {
194			log_debugx("skipping %s (FSID:%d:%d), mounted "
195			    "for %.0f seconds", af->af_mountpoint,
196			    af->af_fsid.val[0], af->af_fsid.val[1],
197			    mounted_for);
198
199			if (mounted_for > mounted_max)
200				mounted_max = mounted_for;
201
202			continue;
203		}
204
205		log_debugx("filesystem mounted on %s (FSID:%d:%d), "
206		    "was mounted for %.0f seconds; unmounting",
207		    af->af_mountpoint, af->af_fsid.val[0], af->af_fsid.val[1],
208		    mounted_for);
209		error = unmount_by_fsid(af->af_fsid, af->af_mountpoint);
210		if (error != 0) {
211			if (mounted_for > mounted_max)
212				mounted_max = mounted_for;
213		}
214	}
215
216	return (mounted_max);
217}
218
219static void
220usage_autounmountd(void)
221{
222
223	fprintf(stderr, "usage: autounmountd [-r time][-t time][-dv]\n");
224	exit(1);
225}
226
227static void
228do_wait(int kq, double sleep_time)
229{
230	struct timespec timeout;
231	struct kevent unused;
232	int error;
233
234	assert(sleep_time > 0);
235	timeout.tv_sec = sleep_time;
236	timeout.tv_nsec = 0;
237
238	log_debugx("waiting for filesystem event for %.0f seconds", sleep_time);
239	error = kevent(kq, NULL, 0, &unused, 1, &timeout);
240	if (error < 0)
241		log_err(1, "kevent");
242
243	if (error == 0)
244		log_debugx("timeout reached");
245	else
246		log_debugx("got filesystem event");
247}
248
249int
250main_autounmountd(int argc, char **argv)
251{
252	struct kevent event;
253	struct pidfh *pidfh;
254	pid_t otherpid;
255	const char *pidfile_path = AUTOUNMOUNTD_PIDFILE;
256	int ch, debug = 0, error, kq;
257	double expiration_time = 600, retry_time = 600, mounted_max, sleep_time;
258	bool dont_daemonize = false;
259
260	while ((ch = getopt(argc, argv, "dr:t:v")) != -1) {
261		switch (ch) {
262		case 'd':
263			dont_daemonize = true;
264			debug++;
265			break;
266		case 'r':
267			retry_time = atoi(optarg);
268			break;
269		case 't':
270			expiration_time = atoi(optarg);
271			break;
272		case 'v':
273			debug++;
274			break;
275		case '?':
276		default:
277			usage_autounmountd();
278		}
279	}
280	argc -= optind;
281	if (argc != 0)
282		usage_autounmountd();
283
284	if (retry_time <= 0)
285		log_errx(1, "retry time must be greater than zero");
286	if (expiration_time <= 0)
287		log_errx(1, "expiration time must be greater than zero");
288
289	log_init(debug);
290
291	pidfh = pidfile_open(pidfile_path, 0600, &otherpid);
292	if (pidfh == NULL) {
293		if (errno == EEXIST) {
294			log_errx(1, "daemon already running, pid: %jd.",
295			    (intmax_t)otherpid);
296		}
297		log_err(1, "cannot open or create pidfile \"%s\"",
298		    pidfile_path);
299	}
300
301	if (dont_daemonize == false) {
302		if (daemon(0, 0) == -1) {
303			log_warn("cannot daemonize");
304			pidfile_remove(pidfh);
305			exit(1);
306		}
307	}
308
309	pidfile_write(pidfh);
310
311	TAILQ_INIT(&automounted);
312
313	kq = kqueue();
314	if (kq < 0)
315		log_err(1, "kqueue");
316
317	EV_SET(&event, 0, EVFILT_FS, EV_ADD | EV_CLEAR, 0, 0, NULL);
318	error = kevent(kq, &event, 1, NULL, 0, NULL);
319	if (error < 0)
320		log_err(1, "kevent");
321
322	for (;;) {
323		refresh_automounted();
324		mounted_max = expire_automounted(expiration_time);
325		if (mounted_max < expiration_time) {
326			sleep_time = difftime(expiration_time, mounted_max);
327			log_debugx("some filesystems expire in %.0f seconds",
328			    sleep_time);
329		} else {
330			sleep_time = retry_time;
331			log_debugx("some expired filesystems remain mounted, "
332			    "will retry in %.0f seconds", sleep_time);
333		}
334
335		do_wait(kq, sleep_time);
336	}
337
338	return (0);
339}
340