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