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