diff --git a/sbin/mount_fusefs/mount_fusefs.c b/sbin/mount_fusefs/mount_fusefs.c --- a/sbin/mount_fusefs/mount_fusefs.c +++ b/sbin/mount_fusefs/mount_fusefs.c @@ -34,10 +34,12 @@ */ #include +#include #include -#include #include #include +#include +#include #include #include @@ -112,6 +114,112 @@ }; #define DEFAULT_MOUNT_FLAGS ALTF_PRIVATE +#define AUTO_UNMOUNT_OPTION 128 + +static int +resolve_pid(pid_t pid, struct kinfo_proc *out) +{ + int mib[4]; + size_t size = sizeof(struct kinfo_proc); + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = pid; + return (sysctl(mib, 4, out, &size, NULL, 0)); +} + +static int +auto_unmount_main(const char *mountpoint, const char *args) +{ + pid_t parent_pid; + struct timeval parent_start; + fsid_t fsid; + int parsed; + struct kinfo_proc proc_info; + struct statfs stat_buf; + int kq; + struct kevent kev; + + /* + * The caller should pass the following info for auto unmount to work: + * parent's PID to monitor + * parent PID's starting time to avoid races + * fsid values to let us be sure we're unmounting the right FS + */ + parsed = sscanf(args, "%d,%ld,%ld,%d,%d", &parent_pid, + &parent_start.tv_sec, &parent_start.tv_usec, + &fsid.val[0], &fsid.val[1]); + if (parsed != 5) { + warnx("fail to parse auto-unmount args"); + return (1); + } + + kq = kqueue(); + if (kq == -1) { + warn("fail to create kqueue"); + return (1); + } + + EV_SET(&kev, parent_pid, EVFILT_PROC, EV_ADD | EV_ENABLE, NOTE_EXIT, + 0, NULL); + if (kevent(kq, &kev, 1, NULL, 0, NULL) == -1) { + warn("fail to setup process monitor"); + close(kq); + return (1); + } + + /* If the process is already gone, unmount immediately and exit. */ + if (resolve_pid(parent_pid, &proc_info) != 0) + goto unmount; + + /* + * It might be a different process with the same PID, so + * check starting times. + */ + if (proc_info.ki_start.tv_sec != parent_start.tv_sec || + proc_info.ki_start.tv_usec != parent_start.tv_usec) + goto unmount; + + /* Wait for the parent process to die. */ + if (kevent(kq, NULL, 0, &kev, 1, NULL) == -1) { + warn("failure while waiting for process to die"); + close(kq) + return (1); + } + if ((kev.fflags & NOTE_EXIT) == 0) { + warnx("unknown event while waiting for process to die"); + close(kq); + return (1); + } + +unmount: + close(kq); + + if (statfs(mountpoint, &stat_buf) != 0) { + warn("fail to statfs the mountpoint"); + return (1); + } + /* + * Do not unmount if fsid's differ - otherwise we'll unmount + * someone's else FS. + */ + if (stat_buf.f_fsid.val[0] != fsid.val[0] || + stat_buf.f_fsid.val[1] != fsid.val[1]) + return (1); + /* Refuse to unmount anything other than fuse. */ + if (strncmp(stat_buf.f_fstypename, "fusefs", 6)) + return (1); + /* + * For usermount case we should only unmount filesystems + * mounted by the same user. + */ + if (stat_buf.f_owner != proc_info.ki_ruid) + return (1); + + unmount(mountpoint, 0); + return (0); +} int main(int argc, char *argv[]) @@ -136,12 +244,14 @@ {"mountpath", required_argument, NULL, 'm'}, {"version", no_argument, NULL, 'V'}, {"help", no_argument, NULL, 'h'}, + {"auto-unmount", required_argument, NULL, AUTO_UNMOUNT_OPTION}, {0,0,0,0} }; - int pid = 0; + pid_t pid = 0; int fd = -1, fdx; char *ep; char *daemon_str = NULL, *daemon_opts = NULL; + char *auto_unmount_args = NULL; /* * We want a parsing routine which is not sensitive to @@ -238,6 +348,11 @@ case 'V': showversion(); break; + case AUTO_UNMOUNT_OPTION: + if (auto_unmount_args) + errx(1, "auto-unmount args specified inconsistently"); + auto_unmount_args = optarg; + break; case '\0': break; case '?': @@ -277,6 +392,9 @@ if (! (dev && dir)) errx(1, "missing special and/or mountpoint"); + if (auto_unmount_args) + return (auto_unmount_main(dir, auto_unmount_args)); + for (mo = mopts; mo->m_flag; ++mo) { if (altflags & mo->m_flag) { int iov_done = 0;