1270096Strasz/*- 2270096Strasz * Copyright (c) 2014 The FreeBSD Foundation 3270096Strasz * All rights reserved. 4270096Strasz * 5270096Strasz * This software was developed by Edward Tomasz Napierala under sponsorship 6270096Strasz * from the FreeBSD Foundation. 7270096Strasz * 8270096Strasz * Redistribution and use in source and binary forms, with or without 9270096Strasz * modification, are permitted provided that the following conditions 10270096Strasz * are met: 11270096Strasz * 1. Redistributions of source code must retain the above copyright 12270096Strasz * notice, this list of conditions and the following disclaimer. 13270096Strasz * 2. Redistributions in binary form must reproduce the above copyright 14270096Strasz * notice, this list of conditions and the following disclaimer in the 15270096Strasz * documentation and/or other materials provided with the distribution. 16270096Strasz * 17270096Strasz * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18270096Strasz * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19270096Strasz * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20270096Strasz * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21270096Strasz * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22270096Strasz * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23270096Strasz * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24270096Strasz * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25270096Strasz * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26270096Strasz * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27270096Strasz * SUCH DAMAGE. 28270096Strasz * 29270096Strasz */ 30270096Strasz 31270897Strasz#include <sys/cdefs.h> 32270897Strasz__FBSDID("$FreeBSD: stable/10/usr.sbin/autofs/autounmountd.c 309509 2016-12-03 19:55:55Z trasz $"); 33270897Strasz 34270096Strasz#include <sys/types.h> 35270096Strasz#include <sys/event.h> 36270096Strasz#include <sys/mount.h> 37270096Strasz#include <sys/time.h> 38270096Strasz#include <assert.h> 39270096Strasz#include <errno.h> 40270096Strasz#include <stdbool.h> 41270096Strasz#include <stdint.h> 42270096Strasz#include <stdio.h> 43270096Strasz#include <stdlib.h> 44270096Strasz#include <string.h> 45270096Strasz#include <unistd.h> 46270096Strasz#include <libutil.h> 47270096Strasz 48270096Strasz#include "common.h" 49270096Strasz 50270096Strasz#define AUTOUNMOUNTD_PIDFILE "/var/run/autounmountd.pid" 51270096Strasz 52270096Straszstruct automounted_fs { 53270096Strasz TAILQ_ENTRY(automounted_fs) af_next; 54270096Strasz time_t af_mount_time; 55270096Strasz bool af_mark; 56270096Strasz fsid_t af_fsid; 57270096Strasz char af_mountpoint[MNAMELEN]; 58270096Strasz}; 59270096Strasz 60270096Straszstatic TAILQ_HEAD(, automounted_fs) automounted; 61270096Strasz 62270096Straszstatic struct automounted_fs * 63270096Straszautomounted_find(fsid_t fsid) 64270096Strasz{ 65270096Strasz struct automounted_fs *af; 66270096Strasz 67270096Strasz TAILQ_FOREACH(af, &automounted, af_next) { 68270096Strasz if (af->af_fsid.val[0] == fsid.val[0] && 69270096Strasz af->af_fsid.val[1] == fsid.val[1]) 70270096Strasz return (af); 71270096Strasz } 72270096Strasz 73270096Strasz return (NULL); 74270096Strasz} 75270096Strasz 76270096Straszstatic struct automounted_fs * 77270096Straszautomounted_add(fsid_t fsid, const char *mountpoint) 78270096Strasz{ 79270096Strasz struct automounted_fs *af; 80270096Strasz 81270096Strasz af = calloc(sizeof(*af), 1); 82270096Strasz if (af == NULL) 83270096Strasz log_err(1, "calloc"); 84270096Strasz af->af_mount_time = time(NULL); 85270096Strasz af->af_fsid = fsid; 86270096Strasz strlcpy(af->af_mountpoint, mountpoint, sizeof(af->af_mountpoint)); 87270096Strasz 88270096Strasz TAILQ_INSERT_TAIL(&automounted, af, af_next); 89270096Strasz 90270096Strasz return (af); 91270096Strasz} 92270096Strasz 93270096Straszstatic void 94270096Straszautomounted_remove(struct automounted_fs *af) 95270096Strasz{ 96270096Strasz 97270096Strasz TAILQ_REMOVE(&automounted, af, af_next); 98270096Strasz free(af); 99270096Strasz} 100270096Strasz 101270096Straszstatic void 102270096Straszrefresh_automounted(void) 103270096Strasz{ 104270096Strasz struct automounted_fs *af, *tmpaf; 105270096Strasz struct statfs *mntbuf; 106270096Strasz int i, nitems; 107270096Strasz 108270096Strasz nitems = getmntinfo(&mntbuf, MNT_WAIT); 109270096Strasz if (nitems <= 0) 110270096Strasz log_err(1, "getmntinfo"); 111270096Strasz 112270096Strasz log_debugx("refreshing list of automounted filesystems"); 113270096Strasz 114270096Strasz TAILQ_FOREACH(af, &automounted, af_next) 115270096Strasz af->af_mark = false; 116270096Strasz 117270096Strasz for (i = 0; i < nitems; i++) { 118270096Strasz if (strcmp(mntbuf[i].f_fstypename, "autofs") == 0) { 119270096Strasz log_debugx("skipping %s, filesystem type is autofs", 120270096Strasz mntbuf[i].f_mntonname); 121270096Strasz continue; 122270096Strasz } 123270096Strasz 124270096Strasz if ((mntbuf[i].f_flags & MNT_AUTOMOUNTED) == 0) { 125270096Strasz log_debugx("skipping %s, not automounted", 126270096Strasz mntbuf[i].f_mntonname); 127270096Strasz continue; 128270096Strasz } 129270096Strasz 130270096Strasz af = automounted_find(mntbuf[i].f_fsid); 131270096Strasz if (af == NULL) { 132270096Strasz log_debugx("new automounted filesystem found on %s " 133270096Strasz "(FSID:%d:%d)", mntbuf[i].f_mntonname, 134270096Strasz mntbuf[i].f_fsid.val[0], mntbuf[i].f_fsid.val[1]); 135270096Strasz af = automounted_add(mntbuf[i].f_fsid, 136270096Strasz mntbuf[i].f_mntonname); 137270096Strasz } else { 138270096Strasz log_debugx("already known automounted filesystem " 139270096Strasz "found on %s (FSID:%d:%d)", mntbuf[i].f_mntonname, 140270096Strasz mntbuf[i].f_fsid.val[0], mntbuf[i].f_fsid.val[1]); 141270096Strasz } 142270096Strasz af->af_mark = true; 143270096Strasz } 144270096Strasz 145270096Strasz TAILQ_FOREACH_SAFE(af, &automounted, af_next, tmpaf) { 146270096Strasz if (af->af_mark) 147270096Strasz continue; 148270096Strasz log_debugx("lost filesystem mounted on %s (FSID:%d:%d)", 149270096Strasz af->af_mountpoint, af->af_fsid.val[0], af->af_fsid.val[1]); 150270096Strasz automounted_remove(af); 151270096Strasz } 152270096Strasz} 153270096Strasz 154270096Straszstatic int 155270096Straszunmount_by_fsid(const fsid_t fsid, const char *mountpoint) 156270096Strasz{ 157270096Strasz char *fsid_str; 158270096Strasz int error, ret; 159270096Strasz 160270096Strasz ret = asprintf(&fsid_str, "FSID:%d:%d", fsid.val[0], fsid.val[1]); 161270096Strasz if (ret < 0) 162270096Strasz log_err(1, "asprintf"); 163270096Strasz 164270096Strasz error = unmount(fsid_str, MNT_BYFSID); 165270096Strasz if (error != 0) { 166270096Strasz if (errno == EBUSY) { 167270096Strasz log_debugx("cannot unmount %s (%s): %s", 168270096Strasz mountpoint, fsid_str, strerror(errno)); 169270096Strasz } else { 170270096Strasz log_warn("cannot unmount %s (%s)", 171270096Strasz mountpoint, fsid_str); 172270096Strasz } 173270096Strasz } 174270096Strasz 175270096Strasz free(fsid_str); 176270096Strasz 177270096Strasz return (error); 178270096Strasz} 179270096Strasz 180270096Straszstatic double 181270096Straszexpire_automounted(double expiration_time) 182270096Strasz{ 183270096Strasz struct automounted_fs *af, *tmpaf; 184270096Strasz time_t now; 185279745Strasz double mounted_for, mounted_max = -1.0; 186270096Strasz int error; 187270096Strasz 188270096Strasz now = time(NULL); 189270096Strasz 190270096Strasz log_debugx("expiring automounted filesystems"); 191270096Strasz 192270096Strasz TAILQ_FOREACH_SAFE(af, &automounted, af_next, tmpaf) { 193270096Strasz mounted_for = difftime(now, af->af_mount_time); 194270096Strasz 195270096Strasz if (mounted_for < expiration_time) { 196270096Strasz log_debugx("skipping %s (FSID:%d:%d), mounted " 197270096Strasz "for %.0f seconds", af->af_mountpoint, 198270096Strasz af->af_fsid.val[0], af->af_fsid.val[1], 199270096Strasz mounted_for); 200270096Strasz 201270096Strasz if (mounted_for > mounted_max) 202270096Strasz mounted_max = mounted_for; 203270096Strasz 204270096Strasz continue; 205270096Strasz } 206270096Strasz 207270096Strasz log_debugx("filesystem mounted on %s (FSID:%d:%d), " 208270096Strasz "was mounted for %.0f seconds; unmounting", 209270096Strasz af->af_mountpoint, af->af_fsid.val[0], af->af_fsid.val[1], 210270096Strasz mounted_for); 211270096Strasz error = unmount_by_fsid(af->af_fsid, af->af_mountpoint); 212270096Strasz if (error != 0) { 213270096Strasz if (mounted_for > mounted_max) 214270096Strasz mounted_max = mounted_for; 215270096Strasz } 216270096Strasz } 217270096Strasz 218270096Strasz return (mounted_max); 219270096Strasz} 220270096Strasz 221270096Straszstatic void 222270096Straszusage_autounmountd(void) 223270096Strasz{ 224270096Strasz 225270096Strasz fprintf(stderr, "usage: autounmountd [-r time][-t time][-dv]\n"); 226270096Strasz exit(1); 227270096Strasz} 228270096Strasz 229270096Straszstatic void 230270096Straszdo_wait(int kq, double sleep_time) 231270096Strasz{ 232270096Strasz struct timespec timeout; 233270096Strasz struct kevent unused; 234279745Strasz int nevents; 235270096Strasz 236279745Strasz if (sleep_time != -1.0) { 237279745Strasz assert(sleep_time > 0.0); 238279745Strasz timeout.tv_sec = sleep_time; 239279745Strasz timeout.tv_nsec = 0; 240270096Strasz 241279745Strasz log_debugx("waiting for filesystem event for %.0f seconds", sleep_time); 242279745Strasz nevents = kevent(kq, NULL, 0, &unused, 1, &timeout); 243279745Strasz } else { 244279745Strasz log_debugx("waiting for filesystem event"); 245279745Strasz nevents = kevent(kq, NULL, 0, &unused, 1, NULL); 246279745Strasz } 247309509Strasz if (nevents < 0) { 248309509Strasz if (errno == EINTR) 249309509Strasz return; 250270096Strasz log_err(1, "kevent"); 251309509Strasz } 252270096Strasz 253279745Strasz if (nevents == 0) { 254270096Strasz log_debugx("timeout reached"); 255279745Strasz assert(sleep_time > 0.0); 256279745Strasz } else { 257270096Strasz log_debugx("got filesystem event"); 258279745Strasz } 259270096Strasz} 260270096Strasz 261270096Straszint 262270096Straszmain_autounmountd(int argc, char **argv) 263270096Strasz{ 264270096Strasz struct kevent event; 265270096Strasz struct pidfh *pidfh; 266270096Strasz pid_t otherpid; 267270096Strasz const char *pidfile_path = AUTOUNMOUNTD_PIDFILE; 268270096Strasz int ch, debug = 0, error, kq; 269270096Strasz double expiration_time = 600, retry_time = 600, mounted_max, sleep_time; 270270096Strasz bool dont_daemonize = false; 271270096Strasz 272270096Strasz while ((ch = getopt(argc, argv, "dr:t:v")) != -1) { 273270096Strasz switch (ch) { 274270096Strasz case 'd': 275270096Strasz dont_daemonize = true; 276270096Strasz debug++; 277270096Strasz break; 278270096Strasz case 'r': 279270096Strasz retry_time = atoi(optarg); 280270096Strasz break; 281270096Strasz case 't': 282270096Strasz expiration_time = atoi(optarg); 283270096Strasz break; 284270096Strasz case 'v': 285270096Strasz debug++; 286270096Strasz break; 287270096Strasz case '?': 288270096Strasz default: 289270096Strasz usage_autounmountd(); 290270096Strasz } 291270096Strasz } 292270096Strasz argc -= optind; 293270096Strasz if (argc != 0) 294270096Strasz usage_autounmountd(); 295270096Strasz 296270096Strasz if (retry_time <= 0) 297270096Strasz log_errx(1, "retry time must be greater than zero"); 298270096Strasz if (expiration_time <= 0) 299270096Strasz log_errx(1, "expiration time must be greater than zero"); 300270096Strasz 301270096Strasz log_init(debug); 302270096Strasz 303270096Strasz pidfh = pidfile_open(pidfile_path, 0600, &otherpid); 304270096Strasz if (pidfh == NULL) { 305270096Strasz if (errno == EEXIST) { 306270096Strasz log_errx(1, "daemon already running, pid: %jd.", 307270096Strasz (intmax_t)otherpid); 308270096Strasz } 309270096Strasz log_err(1, "cannot open or create pidfile \"%s\"", 310270096Strasz pidfile_path); 311270096Strasz } 312270096Strasz 313270096Strasz if (dont_daemonize == false) { 314270096Strasz if (daemon(0, 0) == -1) { 315270096Strasz log_warn("cannot daemonize"); 316270096Strasz pidfile_remove(pidfh); 317270096Strasz exit(1); 318270096Strasz } 319270096Strasz } 320270096Strasz 321270096Strasz pidfile_write(pidfh); 322270096Strasz 323270096Strasz TAILQ_INIT(&automounted); 324270096Strasz 325270096Strasz kq = kqueue(); 326270096Strasz if (kq < 0) 327270096Strasz log_err(1, "kqueue"); 328270096Strasz 329270096Strasz EV_SET(&event, 0, EVFILT_FS, EV_ADD | EV_CLEAR, 0, 0, NULL); 330270096Strasz error = kevent(kq, &event, 1, NULL, 0, NULL); 331270096Strasz if (error < 0) 332270096Strasz log_err(1, "kevent"); 333270096Strasz 334270096Strasz for (;;) { 335270096Strasz refresh_automounted(); 336270096Strasz mounted_max = expire_automounted(expiration_time); 337279745Strasz if (mounted_max == -1.0) { 338279745Strasz sleep_time = mounted_max; 339279745Strasz log_debugx("no filesystems to expire"); 340279745Strasz } else if (mounted_max < expiration_time) { 341270096Strasz sleep_time = difftime(expiration_time, mounted_max); 342270096Strasz log_debugx("some filesystems expire in %.0f seconds", 343270096Strasz sleep_time); 344270096Strasz } else { 345270096Strasz sleep_time = retry_time; 346270096Strasz log_debugx("some expired filesystems remain mounted, " 347270096Strasz "will retry in %.0f seconds", sleep_time); 348270096Strasz } 349270096Strasz 350270096Strasz do_wait(kq, sleep_time); 351270096Strasz } 352270096Strasz 353270096Strasz return (0); 354270096Strasz} 355