1270096Strasz/*-
2270096Strasz * Copyright (c) 2014 The FreeBSD Foundation
3270096Strasz * All rights reserved.
4270096Strasz *
5270096Strasz * This software was developed by Edward Tomasz Napierala under sponsorship
6270096Strasz * from the FreeBSD Foundation.
7270096Strasz *
8270096Strasz * Redistribution and use in source and binary forms, with or without
9270096Strasz * modification, are permitted provided that the following conditions
10270096Strasz * are met:
11270096Strasz * 1. Redistributions of source code must retain the above copyright
12270096Strasz *    notice, this list of conditions and the following disclaimer.
13270096Strasz * 2. Redistributions in binary form must reproduce the above copyright
14270096Strasz *    notice, this list of conditions and the following disclaimer in the
15270096Strasz *    documentation and/or other materials provided with the distribution.
16270096Strasz *
17270096Strasz * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18270096Strasz * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19270096Strasz * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20270096Strasz * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21270096Strasz * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22270096Strasz * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23270096Strasz * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24270096Strasz * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25270096Strasz * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26270096Strasz * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27270096Strasz * SUCH DAMAGE.
28270096Strasz *
29270096Strasz */
30270096Strasz
31270897Strasz#include <sys/cdefs.h>
32270897Strasz__FBSDID("$FreeBSD: stable/10/usr.sbin/autofs/autounmountd.c 309509 2016-12-03 19:55:55Z trasz $");
33270897Strasz
34270096Strasz#include <sys/types.h>
35270096Strasz#include <sys/event.h>
36270096Strasz#include <sys/mount.h>
37270096Strasz#include <sys/time.h>
38270096Strasz#include <assert.h>
39270096Strasz#include <errno.h>
40270096Strasz#include <stdbool.h>
41270096Strasz#include <stdint.h>
42270096Strasz#include <stdio.h>
43270096Strasz#include <stdlib.h>
44270096Strasz#include <string.h>
45270096Strasz#include <unistd.h>
46270096Strasz#include <libutil.h>
47270096Strasz
48270096Strasz#include "common.h"
49270096Strasz
50270096Strasz#define AUTOUNMOUNTD_PIDFILE	"/var/run/autounmountd.pid"
51270096Strasz
52270096Straszstruct automounted_fs {
53270096Strasz	TAILQ_ENTRY(automounted_fs)	af_next;
54270096Strasz	time_t				af_mount_time;
55270096Strasz	bool				af_mark;
56270096Strasz	fsid_t				af_fsid;
57270096Strasz	char				af_mountpoint[MNAMELEN];
58270096Strasz};
59270096Strasz
60270096Straszstatic TAILQ_HEAD(, automounted_fs)	automounted;
61270096Strasz
62270096Straszstatic struct automounted_fs *
63270096Straszautomounted_find(fsid_t fsid)
64270096Strasz{
65270096Strasz	struct automounted_fs *af;
66270096Strasz
67270096Strasz	TAILQ_FOREACH(af, &automounted, af_next) {
68270096Strasz		if (af->af_fsid.val[0] == fsid.val[0] &&
69270096Strasz		    af->af_fsid.val[1] == fsid.val[1])
70270096Strasz			return (af);
71270096Strasz	}
72270096Strasz
73270096Strasz	return (NULL);
74270096Strasz}
75270096Strasz
76270096Straszstatic struct automounted_fs *
77270096Straszautomounted_add(fsid_t fsid, const char *mountpoint)
78270096Strasz{
79270096Strasz	struct automounted_fs *af;
80270096Strasz
81270096Strasz	af = calloc(sizeof(*af), 1);
82270096Strasz	if (af == NULL)
83270096Strasz		log_err(1, "calloc");
84270096Strasz	af->af_mount_time = time(NULL);
85270096Strasz	af->af_fsid = fsid;
86270096Strasz	strlcpy(af->af_mountpoint, mountpoint, sizeof(af->af_mountpoint));
87270096Strasz
88270096Strasz	TAILQ_INSERT_TAIL(&automounted, af, af_next);
89270096Strasz
90270096Strasz	return (af);
91270096Strasz}
92270096Strasz
93270096Straszstatic void
94270096Straszautomounted_remove(struct automounted_fs *af)
95270096Strasz{
96270096Strasz
97270096Strasz	TAILQ_REMOVE(&automounted, af, af_next);
98270096Strasz	free(af);
99270096Strasz}
100270096Strasz
101270096Straszstatic void
102270096Straszrefresh_automounted(void)
103270096Strasz{
104270096Strasz	struct automounted_fs *af, *tmpaf;
105270096Strasz	struct statfs *mntbuf;
106270096Strasz	int i, nitems;
107270096Strasz
108270096Strasz	nitems = getmntinfo(&mntbuf, MNT_WAIT);
109270096Strasz	if (nitems <= 0)
110270096Strasz		log_err(1, "getmntinfo");
111270096Strasz
112270096Strasz	log_debugx("refreshing list of automounted filesystems");
113270096Strasz
114270096Strasz	TAILQ_FOREACH(af, &automounted, af_next)
115270096Strasz		af->af_mark = false;
116270096Strasz
117270096Strasz	for (i = 0; i < nitems; i++) {
118270096Strasz		if (strcmp(mntbuf[i].f_fstypename, "autofs") == 0) {
119270096Strasz			log_debugx("skipping %s, filesystem type is autofs",
120270096Strasz			    mntbuf[i].f_mntonname);
121270096Strasz			continue;
122270096Strasz		}
123270096Strasz
124270096Strasz		if ((mntbuf[i].f_flags & MNT_AUTOMOUNTED) == 0) {
125270096Strasz			log_debugx("skipping %s, not automounted",
126270096Strasz			    mntbuf[i].f_mntonname);
127270096Strasz			continue;
128270096Strasz		}
129270096Strasz
130270096Strasz		af = automounted_find(mntbuf[i].f_fsid);
131270096Strasz		if (af == NULL) {
132270096Strasz			log_debugx("new automounted filesystem found on %s "
133270096Strasz			    "(FSID:%d:%d)", mntbuf[i].f_mntonname,
134270096Strasz			    mntbuf[i].f_fsid.val[0], mntbuf[i].f_fsid.val[1]);
135270096Strasz			af = automounted_add(mntbuf[i].f_fsid,
136270096Strasz			    mntbuf[i].f_mntonname);
137270096Strasz		} else {
138270096Strasz			log_debugx("already known automounted filesystem "
139270096Strasz			    "found on %s (FSID:%d:%d)", mntbuf[i].f_mntonname,
140270096Strasz			    mntbuf[i].f_fsid.val[0], mntbuf[i].f_fsid.val[1]);
141270096Strasz		}
142270096Strasz		af->af_mark = true;
143270096Strasz	}
144270096Strasz
145270096Strasz	TAILQ_FOREACH_SAFE(af, &automounted, af_next, tmpaf) {
146270096Strasz		if (af->af_mark)
147270096Strasz			continue;
148270096Strasz		log_debugx("lost filesystem mounted on %s (FSID:%d:%d)",
149270096Strasz		    af->af_mountpoint, af->af_fsid.val[0], af->af_fsid.val[1]);
150270096Strasz		automounted_remove(af);
151270096Strasz	}
152270096Strasz}
153270096Strasz
154270096Straszstatic int
155270096Straszunmount_by_fsid(const fsid_t fsid, const char *mountpoint)
156270096Strasz{
157270096Strasz	char *fsid_str;
158270096Strasz	int error, ret;
159270096Strasz
160270096Strasz	ret = asprintf(&fsid_str, "FSID:%d:%d", fsid.val[0], fsid.val[1]);
161270096Strasz	if (ret < 0)
162270096Strasz		log_err(1, "asprintf");
163270096Strasz
164270096Strasz	error = unmount(fsid_str, MNT_BYFSID);
165270096Strasz	if (error != 0) {
166270096Strasz		if (errno == EBUSY) {
167270096Strasz			log_debugx("cannot unmount %s (%s): %s",
168270096Strasz			    mountpoint, fsid_str, strerror(errno));
169270096Strasz		} else {
170270096Strasz			log_warn("cannot unmount %s (%s)",
171270096Strasz			    mountpoint, fsid_str);
172270096Strasz		}
173270096Strasz	}
174270096Strasz
175270096Strasz	free(fsid_str);
176270096Strasz
177270096Strasz	return (error);
178270096Strasz}
179270096Strasz
180270096Straszstatic double
181270096Straszexpire_automounted(double expiration_time)
182270096Strasz{
183270096Strasz	struct automounted_fs *af, *tmpaf;
184270096Strasz	time_t now;
185279745Strasz	double mounted_for, mounted_max = -1.0;
186270096Strasz	int error;
187270096Strasz
188270096Strasz	now = time(NULL);
189270096Strasz
190270096Strasz	log_debugx("expiring automounted filesystems");
191270096Strasz
192270096Strasz	TAILQ_FOREACH_SAFE(af, &automounted, af_next, tmpaf) {
193270096Strasz		mounted_for = difftime(now, af->af_mount_time);
194270096Strasz
195270096Strasz		if (mounted_for < expiration_time) {
196270096Strasz			log_debugx("skipping %s (FSID:%d:%d), mounted "
197270096Strasz			    "for %.0f seconds", af->af_mountpoint,
198270096Strasz			    af->af_fsid.val[0], af->af_fsid.val[1],
199270096Strasz			    mounted_for);
200270096Strasz
201270096Strasz			if (mounted_for > mounted_max)
202270096Strasz				mounted_max = mounted_for;
203270096Strasz
204270096Strasz			continue;
205270096Strasz		}
206270096Strasz
207270096Strasz		log_debugx("filesystem mounted on %s (FSID:%d:%d), "
208270096Strasz		    "was mounted for %.0f seconds; unmounting",
209270096Strasz		    af->af_mountpoint, af->af_fsid.val[0], af->af_fsid.val[1],
210270096Strasz		    mounted_for);
211270096Strasz		error = unmount_by_fsid(af->af_fsid, af->af_mountpoint);
212270096Strasz		if (error != 0) {
213270096Strasz			if (mounted_for > mounted_max)
214270096Strasz				mounted_max = mounted_for;
215270096Strasz		}
216270096Strasz	}
217270096Strasz
218270096Strasz	return (mounted_max);
219270096Strasz}
220270096Strasz
221270096Straszstatic void
222270096Straszusage_autounmountd(void)
223270096Strasz{
224270096Strasz
225270096Strasz	fprintf(stderr, "usage: autounmountd [-r time][-t time][-dv]\n");
226270096Strasz	exit(1);
227270096Strasz}
228270096Strasz
229270096Straszstatic void
230270096Straszdo_wait(int kq, double sleep_time)
231270096Strasz{
232270096Strasz	struct timespec timeout;
233270096Strasz	struct kevent unused;
234279745Strasz	int nevents;
235270096Strasz
236279745Strasz	if (sleep_time != -1.0) {
237279745Strasz		assert(sleep_time > 0.0);
238279745Strasz		timeout.tv_sec = sleep_time;
239279745Strasz		timeout.tv_nsec = 0;
240270096Strasz
241279745Strasz		log_debugx("waiting for filesystem event for %.0f seconds", sleep_time);
242279745Strasz		nevents = kevent(kq, NULL, 0, &unused, 1, &timeout);
243279745Strasz	} else {
244279745Strasz		log_debugx("waiting for filesystem event");
245279745Strasz		nevents = kevent(kq, NULL, 0, &unused, 1, NULL);
246279745Strasz	}
247309509Strasz	if (nevents < 0) {
248309509Strasz		if (errno == EINTR)
249309509Strasz			return;
250270096Strasz		log_err(1, "kevent");
251309509Strasz	}
252270096Strasz
253279745Strasz	if (nevents == 0) {
254270096Strasz		log_debugx("timeout reached");
255279745Strasz		assert(sleep_time > 0.0);
256279745Strasz	} else {
257270096Strasz		log_debugx("got filesystem event");
258279745Strasz	}
259270096Strasz}
260270096Strasz
261270096Straszint
262270096Straszmain_autounmountd(int argc, char **argv)
263270096Strasz{
264270096Strasz	struct kevent event;
265270096Strasz	struct pidfh *pidfh;
266270096Strasz	pid_t otherpid;
267270096Strasz	const char *pidfile_path = AUTOUNMOUNTD_PIDFILE;
268270096Strasz	int ch, debug = 0, error, kq;
269270096Strasz	double expiration_time = 600, retry_time = 600, mounted_max, sleep_time;
270270096Strasz	bool dont_daemonize = false;
271270096Strasz
272270096Strasz	while ((ch = getopt(argc, argv, "dr:t:v")) != -1) {
273270096Strasz		switch (ch) {
274270096Strasz		case 'd':
275270096Strasz			dont_daemonize = true;
276270096Strasz			debug++;
277270096Strasz			break;
278270096Strasz		case 'r':
279270096Strasz			retry_time = atoi(optarg);
280270096Strasz			break;
281270096Strasz		case 't':
282270096Strasz			expiration_time = atoi(optarg);
283270096Strasz			break;
284270096Strasz		case 'v':
285270096Strasz			debug++;
286270096Strasz			break;
287270096Strasz		case '?':
288270096Strasz		default:
289270096Strasz			usage_autounmountd();
290270096Strasz		}
291270096Strasz	}
292270096Strasz	argc -= optind;
293270096Strasz	if (argc != 0)
294270096Strasz		usage_autounmountd();
295270096Strasz
296270096Strasz	if (retry_time <= 0)
297270096Strasz		log_errx(1, "retry time must be greater than zero");
298270096Strasz	if (expiration_time <= 0)
299270096Strasz		log_errx(1, "expiration time must be greater than zero");
300270096Strasz
301270096Strasz	log_init(debug);
302270096Strasz
303270096Strasz	pidfh = pidfile_open(pidfile_path, 0600, &otherpid);
304270096Strasz	if (pidfh == NULL) {
305270096Strasz		if (errno == EEXIST) {
306270096Strasz			log_errx(1, "daemon already running, pid: %jd.",
307270096Strasz			    (intmax_t)otherpid);
308270096Strasz		}
309270096Strasz		log_err(1, "cannot open or create pidfile \"%s\"",
310270096Strasz		    pidfile_path);
311270096Strasz	}
312270096Strasz
313270096Strasz	if (dont_daemonize == false) {
314270096Strasz		if (daemon(0, 0) == -1) {
315270096Strasz			log_warn("cannot daemonize");
316270096Strasz			pidfile_remove(pidfh);
317270096Strasz			exit(1);
318270096Strasz		}
319270096Strasz	}
320270096Strasz
321270096Strasz	pidfile_write(pidfh);
322270096Strasz
323270096Strasz	TAILQ_INIT(&automounted);
324270096Strasz
325270096Strasz	kq = kqueue();
326270096Strasz	if (kq < 0)
327270096Strasz		log_err(1, "kqueue");
328270096Strasz
329270096Strasz	EV_SET(&event, 0, EVFILT_FS, EV_ADD | EV_CLEAR, 0, 0, NULL);
330270096Strasz	error = kevent(kq, &event, 1, NULL, 0, NULL);
331270096Strasz	if (error < 0)
332270096Strasz		log_err(1, "kevent");
333270096Strasz
334270096Strasz	for (;;) {
335270096Strasz		refresh_automounted();
336270096Strasz		mounted_max = expire_automounted(expiration_time);
337279745Strasz		if (mounted_max == -1.0) {
338279745Strasz			sleep_time = mounted_max;
339279745Strasz			log_debugx("no filesystems to expire");
340279745Strasz		} else if (mounted_max < expiration_time) {
341270096Strasz			sleep_time = difftime(expiration_time, mounted_max);
342270096Strasz			log_debugx("some filesystems expire in %.0f seconds",
343270096Strasz			    sleep_time);
344270096Strasz		} else {
345270096Strasz			sleep_time = retry_time;
346270096Strasz			log_debugx("some expired filesystems remain mounted, "
347270096Strasz			    "will retry in %.0f seconds", sleep_time);
348270096Strasz		}
349270096Strasz
350270096Strasz		do_wait(kq, sleep_time);
351270096Strasz	}
352270096Strasz
353270096Strasz	return (0);
354270096Strasz}
355