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