1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2014 The FreeBSD Foundation
5 *
6 * This software was developed by Edward Tomasz Napierala under sponsorship
7 * from the FreeBSD Foundation.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 *
30 */
31
32 #include <sys/cdefs.h>
33 #include <sys/types.h>
34 #include <sys/event.h>
35 #include <sys/mount.h>
36 #include <sys/time.h>
37 #include <assert.h>
38 #include <errno.h>
39 #include <libutil.h>
40 #include <stdbool.h>
41 #include <stdint.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46
47 #include "common.h"
48
49 #define AUTOUNMOUNTD_PIDFILE "/var/run/autounmountd.pid"
50
51 struct automounted_fs {
52 TAILQ_ENTRY(automounted_fs) af_next;
53 time_t af_mount_time;
54 bool af_mark;
55 fsid_t af_fsid;
56 char af_mountpoint[MNAMELEN];
57 };
58
59 static TAILQ_HEAD(, automounted_fs) automounted;
60
61 static struct automounted_fs *
automounted_find(fsid_t fsid)62 automounted_find(fsid_t fsid)
63 {
64 struct automounted_fs *af;
65
66 TAILQ_FOREACH(af, &automounted, af_next) {
67 if (fsidcmp(&af->af_fsid, &fsid) == 0)
68 return (af);
69 }
70
71 return (NULL);
72 }
73
74 static struct automounted_fs *
automounted_add(fsid_t fsid,const char * mountpoint)75 automounted_add(fsid_t fsid, const char *mountpoint)
76 {
77 struct automounted_fs *af;
78
79 af = calloc(1, sizeof(*af));
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
91 static void
automounted_remove(struct automounted_fs * af)92 automounted_remove(struct automounted_fs *af)
93 {
94
95 TAILQ_REMOVE(&automounted, af, af_next);
96 free(af);
97 }
98
99 static void
refresh_automounted(void)100 refresh_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
152 static int
unmount_by_fsid(const fsid_t fsid,const char * mountpoint)153 unmount_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_NONBUSY | 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 } else
172 rpc_umntall();
173
174 free(fsid_str);
175
176 return (error);
177 }
178
179 static time_t
expire_automounted(time_t expiration_time)180 expire_automounted(time_t expiration_time)
181 {
182 struct automounted_fs *af, *tmpaf;
183 time_t now;
184 time_t mounted_for, mounted_max = -1;
185 int error;
186
187 now = time(NULL);
188
189 log_debugx("expiring automounted filesystems");
190
191 TAILQ_FOREACH_SAFE(af, &automounted, af_next, tmpaf) {
192 mounted_for = difftime(now, af->af_mount_time);
193
194 if (mounted_for < expiration_time) {
195 log_debugx("skipping %s (FSID:%d:%d), mounted "
196 "for %jd seconds", af->af_mountpoint,
197 af->af_fsid.val[0], af->af_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 %ld seconds; unmounting",
208 af->af_mountpoint, af->af_fsid.val[0], af->af_fsid.val[1],
209 (long)mounted_for);
210 error = unmount_by_fsid(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 static void
usage_autounmountd(void)221 usage_autounmountd(void)
222 {
223
224 fprintf(stderr, "usage: autounmountd [-r time][-t time][-dv]\n");
225 exit(1);
226 }
227
228 static void
do_wait(int kq,time_t sleep_time)229 do_wait(int kq, time_t sleep_time)
230 {
231 struct timespec timeout;
232 struct kevent unused;
233 int nevents;
234
235 if (sleep_time != -1) {
236 assert(sleep_time > 0);
237 timeout.tv_sec = sleep_time;
238 timeout.tv_nsec = 0;
239
240 log_debugx("waiting for filesystem event for %ld seconds",
241 (long)sleep_time);
242 nevents = kevent(kq, NULL, 0, &unused, 1, &timeout);
243 } else {
244 log_debugx("waiting for filesystem event");
245 nevents = kevent(kq, NULL, 0, &unused, 1, NULL);
246 }
247 if (nevents < 0) {
248 if (errno == EINTR)
249 return;
250 log_err(1, "kevent");
251 }
252
253 if (nevents == 0) {
254 log_debugx("timeout reached");
255 assert(sleep_time > 0);
256 } else {
257 log_debugx("got filesystem event");
258 }
259 }
260
261 int
main_autounmountd(int argc,char ** argv)262 main_autounmountd(int argc, char **argv)
263 {
264 struct kevent event;
265 struct pidfh *pidfh;
266 pid_t otherpid;
267 const char *pidfile_path = AUTOUNMOUNTD_PIDFILE;
268 int ch, debug = 0, error, kq;
269 time_t expiration_time = 600, retry_time = 600, mounted_max, sleep_time;
270 bool dont_daemonize = false;
271
272 while ((ch = getopt(argc, argv, "dr:t:v")) != -1) {
273 switch (ch) {
274 case 'd':
275 dont_daemonize = true;
276 debug++;
277 break;
278 case 'r':
279 retry_time = atoi(optarg);
280 break;
281 case 't':
282 expiration_time = atoi(optarg);
283 break;
284 case 'v':
285 debug++;
286 break;
287 case '?':
288 default:
289 usage_autounmountd();
290 }
291 }
292 argc -= optind;
293 if (argc != 0)
294 usage_autounmountd();
295
296 if (retry_time <= 0)
297 log_errx(1, "retry time must be greater than zero");
298 if (expiration_time <= 0)
299 log_errx(1, "expiration time must be greater than zero");
300
301 log_init(debug);
302
303 pidfh = pidfile_open(pidfile_path, 0600, &otherpid);
304 if (pidfh == NULL) {
305 if (errno == EEXIST) {
306 log_errx(1, "daemon already running, pid: %jd.",
307 (intmax_t)otherpid);
308 }
309 log_err(1, "cannot open or create pidfile \"%s\"",
310 pidfile_path);
311 }
312
313 if (dont_daemonize == false) {
314 if (daemon(0, 0) == -1) {
315 log_warn("cannot daemonize");
316 pidfile_remove(pidfh);
317 exit(1);
318 }
319 }
320
321 pidfile_write(pidfh);
322
323 TAILQ_INIT(&automounted);
324
325 kq = kqueue();
326 if (kq < 0)
327 log_err(1, "kqueue");
328
329 EV_SET(&event, 0, EVFILT_FS, EV_ADD | EV_CLEAR, 0, 0, NULL);
330 error = kevent(kq, &event, 1, NULL, 0, NULL);
331 if (error < 0)
332 log_err(1, "kevent");
333
334 for (;;) {
335 refresh_automounted();
336 mounted_max = expire_automounted(expiration_time);
337 if (mounted_max == -1) {
338 sleep_time = mounted_max;
339 log_debugx("no filesystems to expire");
340 } else if (mounted_max < expiration_time) {
341 sleep_time = difftime(expiration_time, mounted_max);
342 log_debugx("some filesystems expire in %ld seconds",
343 (long)sleep_time);
344 } else {
345 sleep_time = retry_time;
346 log_debugx("some expired filesystems remain mounted, "
347 "will retry in %ld seconds", (long)sleep_time);
348 }
349
350 do_wait(kq, sleep_time);
351 }
352
353 return (0);
354 }
355