]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/rm-rf.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
8 #include <sys/statfs.h>
11 #include "alloc-util.h"
12 #include "btrfs-util.h"
13 #include "cgroup-util.h"
14 #include "dirent-util.h"
18 #include "mountpoint-util.h"
19 #include "path-util.h"
21 #include "stat-util.h"
22 #include "string-util.h"
24 static bool is_physical_fs(const struct statfs
*sfs
) {
25 return !is_temporary_fs(sfs
) && !is_cgroup_fs(sfs
);
28 int rm_rf_children(int fd
, RemoveFlags flags
, struct stat
*root_dev
) {
29 _cleanup_closedir_
DIR *d
= NULL
;
36 /* This returns the first error we run into, but nevertheless
37 * tries to go on. This closes the passed fd. */
39 if (!(flags
& REMOVE_PHYSICAL
)) {
41 r
= fstatfs(fd
, &sfs
);
47 if (is_physical_fs(&sfs
)) {
48 /* We refuse to clean physical file systems with this call,
49 * unless explicitly requested. This is extra paranoia just
50 * to be sure we never ever remove non-state data. */
51 _cleanup_free_
char *path
= NULL
;
53 (void) fd_get_path(fd
, &path
);
54 log_error("Attempted to remove disk file system under \"%s\", and we can't allow that.",
65 return errno
== ENOENT
? 0 : -errno
;
68 FOREACH_DIRENT_ALL(de
, d
, return -errno
) {
72 if (dot_or_dot_dot(de
->d_name
))
75 if (de
->d_type
== DT_UNKNOWN
||
76 (de
->d_type
== DT_DIR
&& (root_dev
|| (flags
& REMOVE_SUBVOLUME
)))) {
77 if (fstatat(fd
, de
->d_name
, &st
, AT_SYMLINK_NOFOLLOW
) < 0) {
78 if (ret
== 0 && errno
!= ENOENT
)
83 is_dir
= S_ISDIR(st
.st_mode
);
85 is_dir
= de
->d_type
== DT_DIR
;
90 /* if root_dev is set, remove subdirectories only if device is same */
91 if (root_dev
&& st
.st_dev
!= root_dev
->st_dev
)
94 subdir_fd
= openat(fd
, de
->d_name
, O_RDONLY
|O_NONBLOCK
|O_DIRECTORY
|O_CLOEXEC
|O_NOFOLLOW
|O_NOATIME
);
96 if (ret
== 0 && errno
!= ENOENT
)
101 /* Stop at mount points */
102 r
= fd_is_mount_point(fd
, de
->d_name
, 0);
104 if (ret
== 0 && r
!= -ENOENT
)
107 safe_close(subdir_fd
);
111 safe_close(subdir_fd
);
115 if ((flags
& REMOVE_SUBVOLUME
) && st
.st_ino
== 256) {
117 /* This could be a subvolume, try to remove it */
119 r
= btrfs_subvol_remove_fd(fd
, de
->d_name
, BTRFS_REMOVE_RECURSIVE
|BTRFS_REMOVE_QUOTA
);
121 if (!IN_SET(r
, -ENOTTY
, -EINVAL
)) {
125 safe_close(subdir_fd
);
129 /* ENOTTY, then it wasn't a
130 * btrfs subvolume, continue
133 /* It was a subvolume, continue. */
134 safe_close(subdir_fd
);
139 /* We pass REMOVE_PHYSICAL here, to avoid
140 * doing the fstatfs() to check the file
141 * system type again for each directory */
142 r
= rm_rf_children(subdir_fd
, flags
| REMOVE_PHYSICAL
, root_dev
);
143 if (r
< 0 && ret
== 0)
146 if (unlinkat(fd
, de
->d_name
, AT_REMOVEDIR
) < 0) {
147 if (ret
== 0 && errno
!= ENOENT
)
151 } else if (!(flags
& REMOVE_ONLY_DIRECTORIES
)) {
153 if (unlinkat(fd
, de
->d_name
, 0) < 0) {
154 if (ret
== 0 && errno
!= ENOENT
)
162 int rm_rf(const char *path
, RemoveFlags flags
) {
168 /* For now, don't support dropping subvols when also only dropping directories, since we can't do
169 * this race-freely. */
170 if (FLAGS_SET(flags
, REMOVE_ONLY_DIRECTORIES
|REMOVE_SUBVOLUME
))
173 /* We refuse to clean the root file system with this
174 * call. This is extra paranoia to never cause a really
175 * seriously broken system. */
176 if (path_equal_or_files_same(path
, "/", AT_SYMLINK_NOFOLLOW
))
177 return log_error_errno(SYNTHETIC_ERRNO(EPERM
),
178 "Attempted to remove entire root file system (\"%s\"), and we can't allow that.",
181 if (FLAGS_SET(flags
, REMOVE_SUBVOLUME
| REMOVE_ROOT
| REMOVE_PHYSICAL
)) {
182 /* Try to remove as subvolume first */
183 r
= btrfs_subvol_remove(path
, BTRFS_REMOVE_RECURSIVE
|BTRFS_REMOVE_QUOTA
);
187 if (!IN_SET(r
, -ENOTTY
, -EINVAL
, -ENOTDIR
))
190 /* Not btrfs or not a subvolume */
193 fd
= open(path
, O_RDONLY
|O_NONBLOCK
|O_DIRECTORY
|O_CLOEXEC
|O_NOFOLLOW
|O_NOATIME
);
195 if (!IN_SET(errno
, ENOTDIR
, ELOOP
))
198 if (!(flags
& REMOVE_PHYSICAL
)) {
199 if (statfs(path
, &s
) < 0)
202 if (is_physical_fs(&s
))
203 return log_error_errno(SYNTHETIC_ERRNO(EPERM
),
204 "Attempted to remove files from a disk file system under \"%s\", refusing.",
208 if ((flags
& REMOVE_ROOT
) && !(flags
& REMOVE_ONLY_DIRECTORIES
))
209 if (unlink(path
) < 0 && errno
!= ENOENT
)
215 r
= rm_rf_children(fd
, flags
, NULL
);
217 if (flags
& REMOVE_ROOT
) {
218 if (rmdir(path
) < 0) {
219 if (r
== 0 && errno
!= ENOENT
)