autounmountd.c revision 270896
1/*- 2 * Copyright (c) 2014 The FreeBSD Foundation 3 * All rights reserved. 4 * 5 * This software was developed by Edward Tomasz Napierala under sponsorship 6 * from the FreeBSD Foundation. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 * 29 * $FreeBSD: stable/10/usr.sbin/autofs/autounmountd.c 270896 2014-08-31 21:46:32Z trasz $ 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 <stdbool.h> 39#include <stdint.h> 40#include <stdio.h> 41#include <stdlib.h> 42#include <string.h> 43#include <unistd.h> 44#include <libutil.h> 45 46#include "common.h" 47 48#define AUTOUNMOUNTD_PIDFILE "/var/run/autounmountd.pid" 49 50struct 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 58static TAILQ_HEAD(, automounted_fs) automounted; 59 60static struct automounted_fs * 61automounted_find(fsid_t fsid) 62{ 63 struct automounted_fs *af; 64 65 TAILQ_FOREACH(af, &automounted, af_next) { 66 if (af->af_fsid.val[0] == fsid.val[0] && 67 af->af_fsid.val[1] == fsid.val[1]) 68 return (af); 69 } 70 71 return (NULL); 72} 73 74static struct automounted_fs * 75automounted_add(fsid_t fsid, const char *mountpoint) 76{ 77 struct automounted_fs *af; 78 79 af = calloc(sizeof(*af), 1); 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 91static void 92automounted_remove(struct automounted_fs *af) 93{ 94 95 TAILQ_REMOVE(&automounted, af, af_next); 96 free(af); 97} 98 99static void 100refresh_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 152static int 153unmount_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_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 } 172 173 free(fsid_str); 174 175 return (error); 176} 177 178static double 179expire_automounted(double expiration_time) 180{ 181 struct automounted_fs *af, *tmpaf; 182 time_t now; 183 double mounted_for, mounted_max = 0; 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 %.0f seconds", af->af_mountpoint, 196 af->af_fsid.val[0], af->af_fsid.val[1], 197 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 %.0f seconds; unmounting", 207 af->af_mountpoint, af->af_fsid.val[0], af->af_fsid.val[1], 208 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 219static void 220usage_autounmountd(void) 221{ 222 223 fprintf(stderr, "usage: autounmountd [-r time][-t time][-dv]\n"); 224 exit(1); 225} 226 227static void 228do_wait(int kq, double sleep_time) 229{ 230 struct timespec timeout; 231 struct kevent unused; 232 int error; 233 234 assert(sleep_time > 0); 235 timeout.tv_sec = sleep_time; 236 timeout.tv_nsec = 0; 237 238 log_debugx("waiting for filesystem event for %.0f seconds", sleep_time); 239 error = kevent(kq, NULL, 0, &unused, 1, &timeout); 240 if (error < 0) 241 log_err(1, "kevent"); 242 243 if (error == 0) 244 log_debugx("timeout reached"); 245 else 246 log_debugx("got filesystem event"); 247} 248 249int 250main_autounmountd(int argc, char **argv) 251{ 252 struct kevent event; 253 struct pidfh *pidfh; 254 pid_t otherpid; 255 const char *pidfile_path = AUTOUNMOUNTD_PIDFILE; 256 int ch, debug = 0, error, kq; 257 double expiration_time = 600, retry_time = 600, mounted_max, sleep_time; 258 bool dont_daemonize = false; 259 260 while ((ch = getopt(argc, argv, "dr:t:v")) != -1) { 261 switch (ch) { 262 case 'd': 263 dont_daemonize = true; 264 debug++; 265 break; 266 case 'r': 267 retry_time = atoi(optarg); 268 break; 269 case 't': 270 expiration_time = atoi(optarg); 271 break; 272 case 'v': 273 debug++; 274 break; 275 case '?': 276 default: 277 usage_autounmountd(); 278 } 279 } 280 argc -= optind; 281 if (argc != 0) 282 usage_autounmountd(); 283 284 if (retry_time <= 0) 285 log_errx(1, "retry time must be greater than zero"); 286 if (expiration_time <= 0) 287 log_errx(1, "expiration time must be greater than zero"); 288 289 log_init(debug); 290 291 pidfh = pidfile_open(pidfile_path, 0600, &otherpid); 292 if (pidfh == NULL) { 293 if (errno == EEXIST) { 294 log_errx(1, "daemon already running, pid: %jd.", 295 (intmax_t)otherpid); 296 } 297 log_err(1, "cannot open or create pidfile \"%s\"", 298 pidfile_path); 299 } 300 301 if (dont_daemonize == false) { 302 if (daemon(0, 0) == -1) { 303 log_warn("cannot daemonize"); 304 pidfile_remove(pidfh); 305 exit(1); 306 } 307 } 308 309 pidfile_write(pidfh); 310 311 TAILQ_INIT(&automounted); 312 313 kq = kqueue(); 314 if (kq < 0) 315 log_err(1, "kqueue"); 316 317 EV_SET(&event, 0, EVFILT_FS, EV_ADD | EV_CLEAR, 0, 0, NULL); 318 error = kevent(kq, &event, 1, NULL, 0, NULL); 319 if (error < 0) 320 log_err(1, "kevent"); 321 322 for (;;) { 323 refresh_automounted(); 324 mounted_max = expire_automounted(expiration_time); 325 if (mounted_max < expiration_time) { 326 sleep_time = difftime(expiration_time, mounted_max); 327 log_debugx("some filesystems expire in %.0f seconds", 328 sleep_time); 329 } else { 330 sleep_time = retry_time; 331 log_debugx("some expired filesystems remain mounted, " 332 "will retry in %.0f seconds", sleep_time); 333 } 334 335 do_wait(kq, sleep_time); 336 } 337 338 return (0); 339} 340