1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (C) 1996 5 * David L. Nugent. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <sys/param.h> 30 #include <sys/mount.h> 31 #include <sys/stat.h> 32 #include <sys/wait.h> 33 34 #include <dirent.h> 35 #include <err.h> 36 #include <errno.h> 37 #include <fcntl.h> 38 #include <libgen.h> 39 #include <libutil.h> 40 #include <spawn.h> 41 #include <stdbool.h> 42 #include <stdio.h> 43 #include <stdlib.h> 44 #include <string.h> 45 #include <unistd.h> 46 47 #include "pwupd.h" 48 49 static bool try_dataset_remove(const char *home); 50 extern char **environ; 51 52 /* 53 * "rm -r" a directory tree. If the top-level directory cannot be removed 54 * due to EBUSY, indicating that it is a ZFS dataset, and we have emptied 55 * it, destroy the dataset. Return true if any files or directories 56 * remain. 57 */ 58 bool 59 rm_r(int rootfd, const char *path, uid_t uid) 60 { 61 int dirfd; 62 DIR *d; 63 struct dirent *e; 64 struct stat st; 65 const char *fullpath; 66 bool skipped = false; 67 68 fullpath = path; 69 if (*path == '/') 70 path++; 71 72 dirfd = openat(rootfd, path, O_DIRECTORY); 73 if (dirfd == -1) { 74 return (true); 75 } 76 77 d = fdopendir(dirfd); 78 if (d == NULL) { 79 (void)close(dirfd); 80 return (true); 81 } 82 while ((e = readdir(d)) != NULL) { 83 if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) 84 continue; 85 86 if (fstatat(dirfd, e->d_name, &st, AT_SYMLINK_NOFOLLOW) != 0) 87 continue; 88 if (S_ISDIR(st.st_mode)) { 89 if (rm_r(dirfd, e->d_name, uid) == true) 90 skipped = true; 91 } else if (S_ISLNK(st.st_mode) || st.st_uid == uid) 92 unlinkat(dirfd, e->d_name, 0); 93 else 94 skipped = true; 95 } 96 closedir(d); 97 if (fstatat(rootfd, path, &st, AT_SYMLINK_NOFOLLOW) != 0) 98 return (skipped); 99 if (S_ISLNK(st.st_mode)) { 100 if (unlinkat(rootfd, path, 0) == -1) 101 skipped = true; 102 } else if (st.st_uid == uid) { 103 if (unlinkat(rootfd, path, AT_REMOVEDIR) == -1) { 104 if (errno == EBUSY && skipped == false) 105 skipped = try_dataset_remove(fullpath); 106 else 107 skipped = true; 108 } 109 } else 110 skipped = true; 111 112 return (skipped); 113 } 114 115 /* 116 * If the home directory is a ZFS dataset, attempt to destroy it. 117 * Return true if the dataset is not destroyed. 118 * This would be more straightforward as a shell script. 119 */ 120 static bool 121 try_dataset_remove(const char *path) 122 { 123 bool skipped = true; 124 struct statfs stat; 125 const char *argv[] = { 126 "/sbin/zfs", 127 "destroy", 128 NULL, 129 NULL 130 }; 131 int status; 132 pid_t pid; 133 134 /* see if this is an absolute path (top-level directory) */ 135 if (*path != '/') 136 return (skipped); 137 /* see if ZFS is loaded */ 138 if (kld_isloaded("zfs") == 0) 139 return (skipped); 140 /* This won't work if root dir is not / (-R option) */ 141 if (strcmp(conf.rootdir, "/") != 0) { 142 warnx("cannot destroy home dataset when -R was used"); 143 return (skipped); 144 } 145 /* if so, find dataset name */ 146 if (statfs(path, &stat) != 0) { 147 warn("statfs %s", path); 148 return (skipped); 149 } 150 /* 151 * Check that the path refers to the dataset itself, 152 * not a subdirectory. 153 */ 154 if (strcmp(stat.f_mntonname, path) != 0) 155 return (skipped); 156 argv[2] = stat.f_mntfromname; 157 if ((skipped = posix_spawn(&pid, argv[0], NULL, NULL, 158 (char *const *) argv, environ)) != 0) { 159 warn("Failed to execute '%s %s %s'", 160 argv[0], argv[1], argv[2]); 161 } else { 162 if (waitpid(pid, &status, 0) != -1 && status != 0) { 163 warnx("'%s %s %s' exit status %d\n", 164 argv[0], argv[1], argv[2], status); 165 } 166 } 167 return (skipped); 168 } 169