1 /*- 2 * Copyright (c) 2016 The DragonFly Project 3 * Copyright (c) 2014 The FreeBSD Foundation 4 * All rights reserved. 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/types.h> 33 #include <sys/mount.h> 34 #include <sys/event.h> 35 #include <sys/time.h> 36 #include <assert.h> 37 #include <errno.h> 38 #include <stdio.h> 39 #include <stdlib.h> 40 #include <string.h> 41 #include <unistd.h> 42 #include <libutil.h> 43 44 #include "common.h" 45 46 #define AUTOUNMOUNTD_PIDFILE "/var/run/autounmountd.pid" 47 48 struct automounted_fs { 49 TAILQ_ENTRY(automounted_fs) af_next; 50 time_t af_mount_time; 51 bool af_mark; 52 fsid_t af_fsid; 53 char af_mountpoint[MNAMELEN]; 54 }; 55 56 static TAILQ_HEAD(, automounted_fs) automounted; 57 58 static struct automounted_fs * 59 automounted_find(fsid_t fsid) 60 { 61 struct automounted_fs *af; 62 63 TAILQ_FOREACH(af, &automounted, af_next) { 64 if (af->af_fsid.val[0] == fsid.val[0] && 65 af->af_fsid.val[1] == fsid.val[1]) 66 return (af); 67 } 68 69 return (NULL); 70 } 71 72 static struct automounted_fs * 73 automounted_add(fsid_t fsid, const char *mountpoint) 74 { 75 struct automounted_fs *af; 76 77 af = calloc(sizeof(*af), 1); 78 if (af == NULL) 79 log_err(1, "calloc"); 80 af->af_mount_time = time(NULL); 81 af->af_fsid = fsid; 82 strlcpy(af->af_mountpoint, mountpoint, sizeof(af->af_mountpoint)); 83 84 TAILQ_INSERT_TAIL(&automounted, af, af_next); 85 86 return (af); 87 } 88 89 static void 90 automounted_remove(struct automounted_fs *af) 91 { 92 93 TAILQ_REMOVE(&automounted, af, af_next); 94 free(af); 95 } 96 97 static void 98 refresh_automounted(void) 99 { 100 struct automounted_fs *af, *tmpaf; 101 struct statfs *mntbuf; 102 int i, nitems; 103 104 nitems = getmntinfo(&mntbuf, MNT_WAIT); 105 if (nitems <= 0) 106 log_err(1, "getmntinfo"); 107 108 log_debugx("refreshing list of automounted filesystems"); 109 110 TAILQ_FOREACH(af, &automounted, af_next) 111 af->af_mark = false; 112 113 for (i = 0; i < nitems; i++) { 114 if (strcmp(mntbuf[i].f_fstypename, "autofs") == 0) { 115 log_debugx("skipping %s, filesystem type is autofs", 116 mntbuf[i].f_mntonname); 117 continue; 118 } 119 120 if ((mntbuf[i].f_flags & MNT_AUTOMOUNTED) == 0) { 121 log_debugx("skipping %s, not automounted", 122 mntbuf[i].f_mntonname); 123 continue; 124 } 125 126 af = automounted_find(mntbuf[i].f_fsid); 127 if (af == NULL) { 128 log_debugx("new automounted filesystem found on %s " 129 "(FSID:%d:%d)", mntbuf[i].f_mntonname, 130 mntbuf[i].f_fsid.val[0], mntbuf[i].f_fsid.val[1]); 131 af = automounted_add(mntbuf[i].f_fsid, 132 mntbuf[i].f_mntonname); 133 } else { 134 log_debugx("already known automounted filesystem " 135 "found on %s (FSID:%d:%d)", mntbuf[i].f_mntonname, 136 mntbuf[i].f_fsid.val[0], mntbuf[i].f_fsid.val[1]); 137 } 138 af->af_mark = true; 139 } 140 141 TAILQ_FOREACH_SAFE(af, &automounted, af_next, tmpaf) { 142 if (af->af_mark) 143 continue; 144 log_debugx("lost filesystem mounted on %s (FSID:%d:%d)", 145 af->af_mountpoint, af->af_fsid.val[0], af->af_fsid.val[1]); 146 automounted_remove(af); 147 } 148 } 149 150 static int 151 do_unmount(const fsid_t fsid __unused, const char *mountpoint) 152 { 153 struct stat sb; 154 int error, isbusy = 0; 155 156 error = unmount(mountpoint, 0); 157 if (error != 0) { 158 if (errno == EBUSY) { 159 isbusy = 1; 160 log_debugx("cannot unmount %s: %s", 161 mountpoint, strerror(errno)); 162 } else { 163 log_warn("cannot unmount %s", mountpoint); 164 } 165 } 166 167 /* 168 * XXX: Workaround for DragonFly kernel bug. 169 * https://bugs.dragonflybsd.org/issues/2908 170 */ 171 if (isbusy) { 172 log_debugx("workaround DragonFly kernel bug via stat(2)"); 173 if (stat(mountpoint, &sb)) 174 log_warn("cannot stat %s", mountpoint); 175 } 176 177 return (error); 178 } 179 180 static double 181 expire_automounted(double expiration_time) 182 { 183 struct automounted_fs *af, *tmpaf; 184 time_t now; 185 double mounted_for, mounted_max = -1.0; 186 int error; 187 188 now = time(NULL); 189 190 log_debugx("expiring automounted filesystems"); 191 192 TAILQ_FOREACH_SAFE(af, &automounted, af_next, tmpaf) { 193 mounted_for = difftime(now, af->af_mount_time); 194 195 if (mounted_for < expiration_time) { 196 log_debugx("skipping %s (FSID:%d:%d), mounted " 197 "for %.0f seconds", af->af_mountpoint, 198 af->af_fsid.val[0], af->af_fsid.val[1], 199 mounted_for); 200 201 if (mounted_for > mounted_max) 202 mounted_max = mounted_for; 203 204 continue; 205 } 206 207 log_debugx("filesystem mounted on %s (FSID:%d:%d), " 208 "was mounted for %.0f seconds; unmounting", 209 af->af_mountpoint, af->af_fsid.val[0], af->af_fsid.val[1], 210 mounted_for); 211 error = do_unmount(af->af_fsid, af->af_mountpoint); 212 if (error != 0) { 213 if (mounted_for > mounted_max) 214 mounted_max = mounted_for; 215 } 216 } 217 218 return (mounted_max); 219 } 220 221 static void 222 usage_autounmountd(void) 223 { 224 225 fprintf(stderr, "usage: autounmountd [-r time][-t time][-dv]\n"); 226 exit(1); 227 } 228 229 static void 230 do_wait(int kq, double sleep_time) 231 { 232 struct timespec timeout; 233 struct kevent unused; 234 int nevents; 235 236 if (sleep_time != -1.0) { 237 assert(sleep_time > 0.0); 238 timeout.tv_sec = sleep_time; 239 timeout.tv_nsec = 0; 240 241 log_debugx("waiting for filesystem event for %.0f seconds", 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 log_err(1, "kevent"); 249 250 if (nevents == 0) { 251 log_debugx("timeout reached"); 252 assert(sleep_time > 0.0); 253 } else { 254 log_debugx("got filesystem event"); 255 } 256 } 257 258 int 259 main_autounmountd(int argc, char **argv) 260 { 261 struct kevent event; 262 struct pidfh *pidfh; 263 pid_t otherpid; 264 const char *pidfile_path = AUTOUNMOUNTD_PIDFILE; 265 int ch, debug = 0, error, kq; 266 double expiration_time = 600, retry_time = 600, mounted_max, sleep_time; 267 bool dont_daemonize = false; 268 269 while ((ch = getopt(argc, argv, "dr:t:v")) != -1) { 270 switch (ch) { 271 case 'd': 272 dont_daemonize = true; 273 debug++; 274 break; 275 case 'r': 276 retry_time = atoi(optarg); 277 break; 278 case 't': 279 expiration_time = atoi(optarg); 280 break; 281 case 'v': 282 debug++; 283 break; 284 case '?': 285 default: 286 usage_autounmountd(); 287 } 288 } 289 argc -= optind; 290 if (argc != 0) 291 usage_autounmountd(); 292 293 if (retry_time <= 0) 294 log_errx(1, "retry time must be greater than zero"); 295 if (expiration_time <= 0) 296 log_errx(1, "expiration time must be greater than zero"); 297 298 log_init(debug); 299 300 pidfh = pidfile_open(pidfile_path, 0600, &otherpid); 301 if (pidfh == NULL) { 302 if (errno == EEXIST) { 303 log_errx(1, "daemon already running, pid: %jd.", 304 (intmax_t)otherpid); 305 } 306 log_err(1, "cannot open or create pidfile \"%s\"", 307 pidfile_path); 308 } 309 310 if (dont_daemonize == false) { 311 if (daemon(0, 0) == -1) { 312 log_warn("cannot daemonize"); 313 pidfile_remove(pidfh); 314 exit(1); 315 } 316 } 317 318 pidfile_write(pidfh); 319 320 TAILQ_INIT(&automounted); 321 322 kq = kqueue(); 323 if (kq < 0) 324 log_err(1, "kqueue"); 325 326 EV_SET(&event, 0, EVFILT_FS, EV_ADD | EV_CLEAR, 0, 0, NULL); 327 error = kevent(kq, &event, 1, NULL, 0, NULL); 328 if (error < 0) 329 log_err(1, "kevent"); 330 331 for (;;) { 332 refresh_automounted(); 333 mounted_max = expire_automounted(expiration_time); 334 if (mounted_max == -1.0) { 335 sleep_time = mounted_max; 336 log_debugx("no filesystems to expire"); 337 } else if (mounted_max < expiration_time) { 338 sleep_time = difftime(expiration_time, mounted_max); 339 log_debugx("some filesystems expire in %.0f seconds", 340 sleep_time); 341 } else { 342 sleep_time = retry_time; 343 log_debugx("some expired filesystems remain mounted, " 344 "will retry in %.0f seconds", sleep_time); 345 } 346 347 do_wait(kq, sleep_time); 348 } 349 350 return (0); 351 } 352