1105197Ssam/*	$NetBSD: autounmountd.c,v 1.2 2019/11/21 16:45:05 tkusumi Exp $	*/
2105197Ssam
3105197Ssam/*-
4139823Simp * Copyright (c) 2017 The NetBSD Foundation, Inc.
5105197Ssam * Copyright (c) 2016 The DragonFly Project
6105197Ssam * Copyright (c) 2014 The FreeBSD Foundation
7105197Ssam * All rights reserved.
8105197Ssam *
9105197Ssam * This code is derived from software contributed to The NetBSD Foundation
10105197Ssam * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>.
11105197Ssam *
12105197Ssam * This software was developed by Edward Tomasz Napierala under sponsorship
13105197Ssam * from the FreeBSD Foundation.
14105197Ssam *
15105197Ssam * Redistribution and use in source and binary forms, with or without
16105197Ssam * modification, are permitted provided that the following conditions
17105197Ssam * are met:
18105197Ssam * 1. Redistributions of source code must retain the above copyright
19105197Ssam *    notice, this list of conditions and the following disclaimer.
20105197Ssam * 2. Redistributions in binary form must reproduce the above copyright
21105197Ssam *    notice, this list of conditions and the following disclaimer in the
22105197Ssam *    documentation and/or other materials provided with the distribution.
23105197Ssam *
24105197Ssam * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
25105197Ssam * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26105197Ssam * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27105197Ssam * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
28105197Ssam * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29105197Ssam * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30105197Ssam * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31105197Ssam * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32105197Ssam * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33105197Ssam * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34105197Ssam * SUCH DAMAGE.
35105197Ssam *
36105197Ssam */
37105197Ssam#include <sys/cdefs.h>
38105197Ssam__RCSID("$NetBSD: autounmountd.c,v 1.2 2019/11/21 16:45:05 tkusumi Exp $");
39105197Ssam
40105197Ssam#include <sys/types.h>
41105197Ssam#include <sys/mount.h>
42105197Ssam#include <sys/event.h>
43105197Ssam#include <sys/time.h>
44105197Ssam#include <sys/fstypes.h>
45105197Ssam#include <assert.h>
46105197Ssam#include <errno.h>
47105197Ssam#include <stdio.h>
48105197Ssam#include <stdlib.h>
49105197Ssam#include <string.h>
50105197Ssam#include <unistd.h>
51105197Ssam#include <util.h>
52105197Ssam
53105197Ssam#include "common.h"
54105197Ssam
55105197Ssamstruct automounted_fs {
56105197Ssam	TAILQ_ENTRY(automounted_fs)	af_next;
57	time_t				af_mount_time;
58	bool				af_mark;
59	fsid_t				af_fsid;
60	char				af_mountpoint[MNAMELEN];
61};
62
63static TAILQ_HEAD(, automounted_fs)	automounted;
64
65static struct automounted_fs *
66automounted_find(fsid_t fsid)
67{
68	struct automounted_fs *af;
69
70	TAILQ_FOREACH(af, &automounted, af_next) {
71		if (af->af_fsid.__fsid_val[0] == fsid.__fsid_val[0] &&
72		    af->af_fsid.__fsid_val[1] == fsid.__fsid_val[1])
73			return af;
74	}
75
76	return NULL;
77}
78
79static struct automounted_fs *
80automounted_add(fsid_t fsid, const char *mountpoint)
81{
82	struct automounted_fs *af;
83
84	af = calloc(1, sizeof(*af));
85	if (af == NULL)
86		log_err(1, "calloc");
87	af->af_mount_time = time(NULL);
88	af->af_fsid = fsid;
89	strlcpy(af->af_mountpoint, mountpoint, sizeof(af->af_mountpoint));
90
91	TAILQ_INSERT_TAIL(&automounted, af, af_next);
92
93	return af;
94}
95
96static void
97automounted_remove(struct automounted_fs *af)
98{
99
100	TAILQ_REMOVE(&automounted, af, af_next);
101	free(af);
102}
103
104static void
105refresh_automounted(void)
106{
107	struct automounted_fs *af, *tmpaf;
108	struct statvfs *mntbuf;
109	int i, nitems;
110
111	nitems = getmntinfo(&mntbuf, MNT_WAIT);
112	if (nitems <= 0)
113		log_err(1, "getmntinfo");
114
115	log_debugx("refreshing list of automounted filesystems");
116
117	TAILQ_FOREACH(af, &automounted, af_next)
118		af->af_mark = false;
119
120	for (i = 0; i < nitems; i++) {
121		if (strcmp(mntbuf[i].f_fstypename, "autofs") == 0) {
122			log_debugx("skipping %s, filesystem type is autofs",
123			    mntbuf[i].f_mntonname);
124			continue;
125		}
126
127		if ((mntbuf[i].f_flag & MNT_AUTOMOUNTED) == 0) {
128			log_debugx("skipping %s, not automounted",
129			    mntbuf[i].f_mntonname);
130			continue;
131		}
132
133		af = automounted_find(mntbuf[i].f_fsidx);
134		if (af == NULL) {
135			log_debugx("new automounted filesystem found on %s "
136			    "(FSID:%d:%d)", mntbuf[i].f_mntonname,
137			    mntbuf[i].f_fsidx.__fsid_val[0],
138			    mntbuf[i].f_fsidx.__fsid_val[1]);
139			af = automounted_add(mntbuf[i].f_fsidx,
140			    mntbuf[i].f_mntonname);
141		} else {
142			log_debugx("already known automounted filesystem "
143			    "found on %s (FSID:%d:%d)", mntbuf[i].f_mntonname,
144			    mntbuf[i].f_fsidx.__fsid_val[0],
145			    mntbuf[i].f_fsidx.__fsid_val[1]);
146		}
147		af->af_mark = true;
148	}
149
150	TAILQ_FOREACH_SAFE(af, &automounted, af_next, tmpaf) {
151		if (af->af_mark)
152			continue;
153		log_debugx("lost filesystem mounted on %s (FSID:%d:%d)",
154		    af->af_mountpoint, af->af_fsid.__fsid_val[0],
155		    af->af_fsid.__fsid_val[1]);
156		automounted_remove(af);
157	}
158}
159
160static int
161do_unmount(const fsid_t fsid __unused, const char *mountpoint)
162{
163	int error;
164
165	error = unmount(mountpoint, 0);
166	if (error != 0) {
167		if (errno == EBUSY) {
168			log_debugx("cannot unmount %s: %s",
169			    mountpoint, strerror(errno));
170		} else {
171			log_warn("cannot unmount %s", mountpoint);
172		}
173	}
174
175	return error;
176}
177
178static time_t
179expire_automounted(time_t expiration_time)
180{
181	struct automounted_fs *af, *tmpaf;
182	time_t now;
183	time_t mounted_for, mounted_max = -1;
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 = (time_t)difftime(now, af->af_mount_time);
192
193		if (mounted_for < expiration_time) {
194			log_debugx("skipping %s (FSID:%d:%d), mounted "
195			    "for %jd seconds", af->af_mountpoint,
196			    af->af_fsid.__fsid_val[0],
197			    af->af_fsid.__fsid_val[1],
198			    (intmax_t)mounted_for);
199
200			if (mounted_for > mounted_max)
201				mounted_max = mounted_for;
202
203			continue;
204		}
205
206		log_debugx("filesystem mounted on %s (FSID:%d:%d), "
207		    "was mounted for %jd seconds; unmounting",
208		    af->af_mountpoint, af->af_fsid.__fsid_val[0],
209		    af->af_fsid.__fsid_val[1], (intmax_t)mounted_for);
210		error = do_unmount(af->af_fsid, af->af_mountpoint);
211		if (error != 0) {
212			if (mounted_for > mounted_max)
213				mounted_max = mounted_for;
214		}
215	}
216
217	return mounted_max;
218}
219
220__dead static void
221usage_autounmountd(void)
222{
223
224	fprintf(stderr, "Usage: %s [-r time][-t time][-dv]\n",
225	    getprogname());
226	exit(1);
227}
228
229static void
230do_wait(int kq, time_t sleep_time)
231{
232	struct timespec timeout;
233	struct kevent unused;
234	int nevents;
235
236	if (sleep_time != -1) {
237		assert(sleep_time > 0);
238		timeout.tv_sec = (int)sleep_time;
239		timeout.tv_nsec = 0;
240
241		log_debugx("waiting for filesystem event for %jd seconds",
242		    (intmax_t)sleep_time);
243		nevents = kevent(kq, NULL, 0, &unused, 1, &timeout);
244	} else {
245		log_debugx("waiting for filesystem event");
246		nevents = kevent(kq, NULL, 0, &unused, 1, NULL);
247	}
248	if (nevents < 0) {
249		if (errno == EINTR)
250			return;
251		log_err(1, "kevent");
252	}
253
254	if (nevents == 0) {
255		log_debugx("timeout reached");
256		assert(sleep_time > 0);
257	} else {
258		log_debugx("got filesystem event");
259	}
260}
261
262int
263main_autounmountd(int argc, char **argv)
264{
265	struct kevent event;
266	int ch, debug = 0, error, kq;
267	time_t expiration_time = 600, retry_time = 600, mounted_max, sleep_time;
268	bool dont_daemonize = false;
269
270	while ((ch = getopt(argc, argv, "dr:t:v")) != -1) {
271		switch (ch) {
272		case 'd':
273			dont_daemonize = true;
274			debug++;
275			break;
276		case 'r':
277			retry_time = atoi(optarg);
278			break;
279		case 't':
280			expiration_time = atoi(optarg);
281			break;
282		case 'v':
283			debug++;
284			break;
285		case '?':
286		default:
287			usage_autounmountd();
288		}
289	}
290	argc -= optind;
291	if (argc != 0)
292		usage_autounmountd();
293
294	if (retry_time <= 0)
295		log_errx(1, "retry time must be greater than zero");
296	if (expiration_time <= 0)
297		log_errx(1, "expiration time must be greater than zero");
298
299	log_init(debug);
300
301	if (dont_daemonize == false) {
302		if (daemon(0, 0) == -1) {
303			log_warn("cannot daemonize");
304			pidfile_clean();
305			exit(1);
306		}
307	}
308
309	/*
310	 * Call pidfile(3) after daemon(3).
311	 */
312	if (pidfile(NULL) == -1) {
313		if (errno == EEXIST)
314			log_errx(1, "daemon already running");
315		else if (errno == ENAMETOOLONG)
316			log_errx(1, "pidfile name too long");
317		log_err(1, "cannot create pidfile");
318	}
319	if (pidfile_lock(NULL) == -1)
320		log_err(1, "cannot lock pidfile");
321
322	TAILQ_INIT(&automounted);
323
324	kq = kqueue();
325	if (kq < 0)
326		log_err(1, "kqueue");
327
328	EV_SET(&event, 0, EVFILT_FS, EV_ADD | EV_CLEAR, 0, 0, (intptr_t)NULL);
329	error = kevent(kq, &event, 1, NULL, 0, NULL);
330	if (error < 0)
331		log_err(1, "kevent");
332
333	for (;;) {
334		refresh_automounted();
335		mounted_max = expire_automounted(expiration_time);
336		if (mounted_max == -1) {
337			sleep_time = mounted_max;
338			log_debugx("no filesystems to expire");
339		} else if (mounted_max < expiration_time) {
340			sleep_time =
341			    (time_t)difftime(expiration_time, mounted_max);
342			log_debugx("some filesystems expire in %jd seconds",
343			    (intmax_t)sleep_time);
344		} else {
345			sleep_time = retry_time;
346			log_debugx("some expired filesystems remain mounted, "
347			    "will retry in %jd seconds", (intmax_t)sleep_time);
348		}
349
350		do_wait(kq, sleep_time);
351	}
352
353	return 0;
354}
355