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