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 * 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 * 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 92 automounted_remove(struct automounted_fs *af) 93 { 94 95 TAILQ_REMOVE(&automounted, af, af_next); 96 free(af); 97 } 98 99 static 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 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 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 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 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 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, VQ_MOUNT | VQ_UNMOUNT, 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