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