xref: /freebsd/usr.sbin/pw/rm_r.c (revision d2f1f71ec8c62dd26d6169d0d671a5aa5a933c1a)
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