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