]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/chown-recursive.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
8 #include "chown-recursive.h"
9 #include "dirent-util.h"
13 #include "stdio-util.h"
15 #include "user-util.h"
19 const struct stat
*st
,
24 char procfs_path
[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
31 /* We change ACLs through the /proc/self/fd/%i path, so that we have a stable reference that works
33 xsprintf(procfs_path
, "/proc/self/fd/%i", fd
);
35 /* Drop any ACL if there is one */
36 FOREACH_STRING(n
, "system.posix_acl_access", "system.posix_acl_default")
37 if (removexattr(procfs_path
, n
) < 0)
38 if (!IN_SET(errno
, ENODATA
, EOPNOTSUPP
, ENOSYS
, ENOTTY
))
41 r
= fchmod_and_chown(fd
, st
->st_mode
& mask
, uid
, gid
);
48 static int chown_recursive_internal(
50 const struct stat
*st
,
55 _cleanup_closedir_
DIR *d
= NULL
;
69 FOREACH_DIRENT_ALL(de
, d
, return -errno
) {
70 _cleanup_close_
int path_fd
= -1;
73 if (dot_or_dot_dot(de
->d_name
))
76 /* Let's pin the child inode we want to fix now with an O_PATH fd, so that it cannot be swapped out
77 * while we manipulate it. */
78 path_fd
= openat(dirfd(d
), de
->d_name
, O_PATH
|O_CLOEXEC
|O_NOFOLLOW
);
82 if (fstat(path_fd
, &fst
) < 0)
85 if (S_ISDIR(fst
.st_mode
)) {
88 /* Convert it to a "real" (i.e. non-O_PATH) fd now */
89 subdir_fd
= fd_reopen(path_fd
, O_RDONLY
|O_CLOEXEC
|O_NOATIME
);
93 r
= chown_recursive_internal(subdir_fd
, &fst
, uid
, gid
, mask
); /* takes possession of subdir_fd even on failure */
99 r
= chown_one(path_fd
, &fst
, uid
, gid
, mask
);
107 r
= chown_one(dirfd(d
), st
, uid
, gid
, mask
);
111 return r
> 0 || changed
;
114 int path_chown_recursive(
120 _cleanup_close_
int fd
= -1;
123 fd
= open(path
, O_RDONLY
|O_DIRECTORY
|O_CLOEXEC
|O_NOFOLLOW
|O_NOATIME
);
127 if (!uid_is_valid(uid
) && !gid_is_valid(gid
) && (mask
& 07777) == 07777)
128 return 0; /* nothing to do */
130 if (fstat(fd
, &st
) < 0)
133 /* Let's take a shortcut: if the top-level directory is properly owned, we don't descend into the
134 * whole tree, under the assumption that all is OK anyway. */
135 if ((!uid_is_valid(uid
) || st
.st_uid
== uid
) &&
136 (!gid_is_valid(gid
) || st
.st_gid
== gid
) &&
137 ((st
.st_mode
& ~mask
& 07777) == 0))
140 return chown_recursive_internal(TAKE_FD(fd
), &st
, uid
, gid
, mask
); /* we donate the fd to the call, regardless if it succeeded or failed */
143 int fd_chown_recursive(
149 int duplicated_fd
= -1;
152 /* Note that the slightly different order of fstat() and the checks here and in
153 * path_chown_recursive(). That's because when we open the directory ourselves we can specify
154 * O_DIRECTORY and we always want to ensure we are operating on a directory before deciding whether
155 * the operation is otherwise redundant. */
157 if (fstat(fd
, &st
) < 0)
160 if (!S_ISDIR(st
.st_mode
))
163 if (!uid_is_valid(uid
) && !gid_is_valid(gid
) && (mask
& 07777) == 07777)
164 return 0; /* nothing to do */
166 /* Shortcut, as above */
167 if ((!uid_is_valid(uid
) || st
.st_uid
== uid
) &&
168 (!gid_is_valid(gid
) || st
.st_gid
== gid
) &&
169 ((st
.st_mode
& ~mask
& 07777) == 0))
172 /* Let's duplicate the fd here, as opendir() wants to take possession of it and close it afterwards */
173 duplicated_fd
= fcntl(fd
, F_DUPFD_CLOEXEC
, 3);
174 if (duplicated_fd
< 0)
177 return chown_recursive_internal(duplicated_fd
, &st
, uid
, gid
, mask
); /* fd donated even on failure */