]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/core/chown-recursive.c
447b7712676939d1c965b1b34b2dc395110febc3
1 /* SPDX-License-Identifier: LGPL-2.1+ */
8 #include "chown-recursive.h"
9 #include "dirent-util.h"
12 #include "stdio-util.h"
14 #include "user-util.h"
16 static int chown_one(int fd
, const struct stat
*st
, uid_t uid
, gid_t gid
) {
17 char procfs_path
[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
23 if ((!uid_is_valid(uid
) || st
->st_uid
== uid
) &&
24 (!gid_is_valid(gid
) || st
->st_gid
== gid
))
27 /* We change ownership through the /proc/self/fd/%i path, so that we have a stable reference that works with
28 * O_PATH. (Note: fchown() and fchmod() do not work with O_PATH, the kernel refuses that. */
29 xsprintf(procfs_path
, "/proc/self/fd/%i", fd
);
31 /* Drop any ACL if there is one */
32 FOREACH_STRING(n
, "system.posix_acl_access", "system.posix_acl_default")
33 if (removexattr(procfs_path
, n
) < 0)
34 if (!IN_SET(errno
, ENODATA
, EOPNOTSUPP
, ENOSYS
, ENOTTY
))
37 if (chown(procfs_path
, uid
, gid
) < 0)
40 /* The linux kernel alters the mode in some cases of chown(), as well when we change ACLs. Let's undo this. We
41 * do this only for non-symlinks however. That's because for symlinks the access mode is ignored anyway and
42 * because on some kernels/file systems trying to change the access mode will succeed but has no effect while
43 * on others it actively fails. */
44 if (!S_ISLNK(st
->st_mode
))
45 if (chmod(procfs_path
, st
->st_mode
& 07777) < 0)
51 static int chown_recursive_internal(int fd
, const struct stat
*st
, uid_t uid
, gid_t gid
) {
52 _cleanup_closedir_
DIR *d
= NULL
;
66 FOREACH_DIRENT_ALL(de
, d
, return -errno
) {
67 _cleanup_close_
int path_fd
= -1;
70 if (dot_or_dot_dot(de
->d_name
))
73 /* Let's pin the child inode we want to fix now with an O_PATH fd, so that it cannot be swapped out
74 * while we manipulate it. */
75 path_fd
= openat(dirfd(d
), de
->d_name
, O_PATH
|O_CLOEXEC
|O_NOFOLLOW
);
79 if (fstat(path_fd
, &fst
) < 0)
82 if (S_ISDIR(fst
.st_mode
)) {
85 /* Convert it to a "real" (i.e. non-O_PATH) fd now */
86 subdir_fd
= fd_reopen(path_fd
, O_RDONLY
|O_CLOEXEC
|O_NOATIME
);
90 r
= chown_recursive_internal(subdir_fd
, &fst
, uid
, gid
); /* takes possession of subdir_fd even on failure */
96 r
= chown_one(path_fd
, &fst
, uid
, gid
);
104 r
= chown_one(dirfd(d
), st
, uid
, gid
);
108 return r
> 0 || changed
;
111 int path_chown_recursive(const char *path
, uid_t uid
, gid_t gid
) {
112 _cleanup_close_
int fd
= -1;
116 fd
= open(path
, O_RDONLY
|O_DIRECTORY
|O_CLOEXEC
|O_NOFOLLOW
|O_NOATIME
);
120 if (!uid_is_valid(uid
) && !gid_is_valid(gid
))
121 return 0; /* nothing to do */
123 if (fstat(fd
, &st
) < 0)
126 /* Let's take a shortcut: if the top-level directory is properly owned, we don't descend into the whole tree,
127 * under the assumption that all is OK anyway. */
129 if ((!uid_is_valid(uid
) || st
.st_uid
== uid
) &&
130 (!gid_is_valid(gid
) || st
.st_gid
== gid
))
133 r
= chown_recursive_internal(fd
, &st
, uid
, gid
);
134 fd
= -1; /* we donated the fd to the call, regardless if it succeeded or failed */