]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/rm-rf.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
9 #include "alloc-util.h"
10 #include "btrfs-util.h"
11 #include "cgroup-util.h"
12 #include "dirent-util.h"
16 #include "mountpoint-util.h"
17 #include "path-util.h"
19 #include "stat-util.h"
20 #include "string-util.h"
22 static bool is_physical_fs(const struct statfs
*sfs
) {
23 return !is_temporary_fs(sfs
) && !is_cgroup_fs(sfs
);
26 static int unlinkat_harder(
30 RemoveFlags remove_flags
) {
35 /* Like unlinkat(), but tries harder: if we get EACCESS we'll try to set the r/w/x bits on the
36 * directory. This is useful if we run unprivileged and have some files where the w bit is
39 if (unlinkat(dfd
, filename
, unlink_flags
) >= 0)
41 if (errno
!= EACCES
|| !FLAGS_SET(remove_flags
, REMOVE_CHMOD
))
44 if (fstat(dfd
, &st
) < 0)
46 if (!S_ISDIR(st
.st_mode
))
48 if ((st
.st_mode
& 0700) == 0700) /* Already set? */
49 return -EACCES
; /* original error */
50 if (st
.st_uid
!= geteuid()) /* this only works if the UID matches ours */
53 if (fchmod(dfd
, (st
.st_mode
| 0700) & 07777) < 0)
56 if (unlinkat(dfd
, filename
, unlink_flags
) < 0) {
58 /* Try to restore the original access mode if this didn't work */
59 (void) fchmod(dfd
, st
.st_mode
& 07777);
66 int rm_rf_children(int fd
, RemoveFlags flags
, struct stat
*root_dev
) {
67 _cleanup_closedir_
DIR *d
= NULL
;
74 /* This returns the first error we run into, but nevertheless tries to go on. This closes the passed
75 * fd, in all cases, including on failure.. */
77 if (!(flags
& REMOVE_PHYSICAL
)) {
79 r
= fstatfs(fd
, &sfs
);
85 if (is_physical_fs(&sfs
)) {
86 /* We refuse to clean physical file systems with this call,
87 * unless explicitly requested. This is extra paranoia just
88 * to be sure we never ever remove non-state data. */
89 _cleanup_free_
char *path
= NULL
;
91 (void) fd_get_path(fd
, &path
);
92 log_error("Attempted to remove disk file system under \"%s\", and we can't allow that.",
103 return errno
== ENOENT
? 0 : -errno
;
106 FOREACH_DIRENT_ALL(de
, d
, return -errno
) {
110 if (dot_or_dot_dot(de
->d_name
))
113 if (de
->d_type
== DT_UNKNOWN
||
114 (de
->d_type
== DT_DIR
&& (root_dev
|| (flags
& REMOVE_SUBVOLUME
)))) {
115 if (fstatat(fd
, de
->d_name
, &st
, AT_SYMLINK_NOFOLLOW
) < 0) {
116 if (ret
== 0 && errno
!= ENOENT
)
121 is_dir
= S_ISDIR(st
.st_mode
);
123 is_dir
= de
->d_type
== DT_DIR
;
126 _cleanup_close_
int subdir_fd
= -1;
128 /* if root_dev is set, remove subdirectories only if device is same */
129 if (root_dev
&& st
.st_dev
!= root_dev
->st_dev
)
132 subdir_fd
= openat(fd
, de
->d_name
, O_RDONLY
|O_NONBLOCK
|O_DIRECTORY
|O_CLOEXEC
|O_NOFOLLOW
|O_NOATIME
);
134 if (ret
== 0 && errno
!= ENOENT
)
139 /* Stop at mount points */
140 r
= fd_is_mount_point(fd
, de
->d_name
, 0);
142 if (ret
== 0 && r
!= -ENOENT
)
150 if ((flags
& REMOVE_SUBVOLUME
) && st
.st_ino
== 256) {
152 /* This could be a subvolume, try to remove it */
154 r
= btrfs_subvol_remove_fd(fd
, de
->d_name
, BTRFS_REMOVE_RECURSIVE
|BTRFS_REMOVE_QUOTA
);
156 if (!IN_SET(r
, -ENOTTY
, -EINVAL
)) {
163 /* ENOTTY, then it wasn't a btrfs subvolume, continue below. */
165 /* It was a subvolume, continue. */
169 /* We pass REMOVE_PHYSICAL here, to avoid doing the fstatfs() to check the file
170 * system type again for each directory */
171 r
= rm_rf_children(TAKE_FD(subdir_fd
), flags
| REMOVE_PHYSICAL
, root_dev
);
172 if (r
< 0 && ret
== 0)
175 r
= unlinkat_harder(fd
, de
->d_name
, AT_REMOVEDIR
, flags
);
176 if (r
< 0 && r
!= -ENOENT
&& ret
== 0)
179 } else if (!(flags
& REMOVE_ONLY_DIRECTORIES
)) {
181 r
= unlinkat_harder(fd
, de
->d_name
, 0, flags
);
182 if (r
< 0 && r
!= -ENOENT
&& ret
== 0)
189 int rm_rf(const char *path
, RemoveFlags flags
) {
195 /* For now, don't support dropping subvols when also only dropping directories, since we can't do
196 * this race-freely. */
197 if (FLAGS_SET(flags
, REMOVE_ONLY_DIRECTORIES
|REMOVE_SUBVOLUME
))
200 /* We refuse to clean the root file system with this call. This is extra paranoia to never cause a
201 * really seriously broken system. */
202 if (path_equal_or_files_same(path
, "/", AT_SYMLINK_NOFOLLOW
))
203 return log_error_errno(SYNTHETIC_ERRNO(EPERM
),
204 "Attempted to remove entire root file system (\"%s\"), and we can't allow that.",
207 if (FLAGS_SET(flags
, REMOVE_SUBVOLUME
| REMOVE_ROOT
| REMOVE_PHYSICAL
)) {
208 /* Try to remove as subvolume first */
209 r
= btrfs_subvol_remove(path
, BTRFS_REMOVE_RECURSIVE
|BTRFS_REMOVE_QUOTA
);
213 if (FLAGS_SET(flags
, REMOVE_MISSING_OK
) && r
== -ENOENT
)
216 if (!IN_SET(r
, -ENOTTY
, -EINVAL
, -ENOTDIR
))
219 /* Not btrfs or not a subvolume */
222 fd
= open(path
, O_RDONLY
|O_NONBLOCK
|O_DIRECTORY
|O_CLOEXEC
|O_NOFOLLOW
|O_NOATIME
);
224 if (FLAGS_SET(flags
, REMOVE_MISSING_OK
) && errno
== ENOENT
)
227 if (!IN_SET(errno
, ENOTDIR
, ELOOP
))
230 if (FLAGS_SET(flags
, REMOVE_ONLY_DIRECTORIES
))
233 if (FLAGS_SET(flags
, REMOVE_ROOT
)) {
235 if (!FLAGS_SET(flags
, REMOVE_PHYSICAL
)) {
236 if (statfs(path
, &s
) < 0)
239 if (is_physical_fs(&s
))
240 return log_error_errno(SYNTHETIC_ERRNO(EPERM
),
241 "Attempted to remove files from a disk file system under \"%s\", refusing.",
245 if (unlink(path
) < 0) {
246 if (FLAGS_SET(flags
, REMOVE_MISSING_OK
) && errno
== ENOENT
)
256 r
= rm_rf_children(fd
, flags
, NULL
);
258 if (FLAGS_SET(flags
, REMOVE_ROOT
) &&
261 (!FLAGS_SET(flags
, REMOVE_MISSING_OK
) || errno
!= ENOENT
))