]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
a1164ae3 | 2 | |
a1164ae3 | 3 | #include <fcntl.h> |
5de6cce5 LP |
4 | #include <sys/stat.h> |
5 | #include <sys/types.h> | |
f89bc84f | 6 | #include <sys/xattr.h> |
a1164ae3 | 7 | |
a1164ae3 | 8 | #include "chown-recursive.h" |
5de6cce5 LP |
9 | #include "dirent-util.h" |
10 | #include "fd-util.h" | |
11 | #include "macro.h" | |
12 | #include "stdio-util.h" | |
13 | #include "strv.h" | |
14 | #include "user-util.h" | |
a1164ae3 | 15 | |
5de6cce5 LP |
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]; | |
f89bc84f | 18 | const char *n; |
a1164ae3 LP |
19 | |
20 | assert(fd >= 0); | |
21 | assert(st); | |
22 | ||
23 | if ((!uid_is_valid(uid) || st->st_uid == uid) && | |
24 | (!gid_is_valid(gid) || st->st_gid == gid)) | |
25 | return 0; | |
26 | ||
5de6cce5 LP |
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); | |
a1164ae3 | 30 | |
f89bc84f LP |
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)) | |
35 | return -errno; | |
36 | ||
5de6cce5 | 37 | if (chown(procfs_path, uid, gid) < 0) |
a1164ae3 LP |
38 | return -errno; |
39 | ||
f89bc84f LP |
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. */ | |
5de6cce5 LP |
44 | if (!S_ISLNK(st->st_mode)) |
45 | if (chmod(procfs_path, st->st_mode & 07777) < 0) | |
46 | return -errno; | |
47 | ||
a1164ae3 LP |
48 | return 1; |
49 | } | |
50 | ||
51 | static int chown_recursive_internal(int fd, const struct stat *st, uid_t uid, gid_t gid) { | |
5de6cce5 | 52 | _cleanup_closedir_ DIR *d = NULL; |
a1164ae3 | 53 | bool changed = false; |
5de6cce5 | 54 | struct dirent *de; |
a1164ae3 LP |
55 | int r; |
56 | ||
57 | assert(fd >= 0); | |
58 | assert(st); | |
59 | ||
5de6cce5 LP |
60 | d = fdopendir(fd); |
61 | if (!d) { | |
62 | safe_close(fd); | |
63 | return -errno; | |
64 | } | |
65 | ||
66 | FOREACH_DIRENT_ALL(de, d, return -errno) { | |
67 | _cleanup_close_ int path_fd = -1; | |
68 | struct stat fst; | |
69 | ||
70 | if (dot_or_dot_dot(de->d_name)) | |
71 | continue; | |
72 | ||
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); | |
76 | if (path_fd < 0) | |
77 | return -errno; | |
78 | ||
79 | if (fstat(path_fd, &fst) < 0) | |
80 | return -errno; | |
81 | ||
82 | if (S_ISDIR(fst.st_mode)) { | |
83 | int subdir_fd; | |
84 | ||
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); | |
87 | if (subdir_fd < 0) | |
88 | return subdir_fd; | |
89 | ||
90 | r = chown_recursive_internal(subdir_fd, &fst, uid, gid); /* takes possession of subdir_fd even on failure */ | |
91 | if (r < 0) | |
92 | return r; | |
93 | if (r > 0) | |
94 | changed = true; | |
95 | } else { | |
96 | r = chown_one(path_fd, &fst, uid, gid); | |
97 | if (r < 0) | |
98 | return r; | |
99 | if (r > 0) | |
100 | changed = true; | |
a1164ae3 | 101 | } |
5de6cce5 | 102 | } |
a1164ae3 | 103 | |
5de6cce5 | 104 | r = chown_one(dirfd(d), st, uid, gid); |
a1164ae3 | 105 | if (r < 0) |
5de6cce5 | 106 | return r; |
a1164ae3 | 107 | |
5de6cce5 | 108 | return r > 0 || changed; |
a1164ae3 LP |
109 | } |
110 | ||
111 | int path_chown_recursive(const char *path, uid_t uid, gid_t gid) { | |
112 | _cleanup_close_ int fd = -1; | |
113 | struct stat st; | |
a1164ae3 | 114 | |
5de6cce5 | 115 | fd = open(path, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); |
a1164ae3 LP |
116 | if (fd < 0) |
117 | return -errno; | |
118 | ||
119 | if (!uid_is_valid(uid) && !gid_is_valid(gid)) | |
120 | return 0; /* nothing to do */ | |
121 | ||
122 | if (fstat(fd, &st) < 0) | |
123 | return -errno; | |
124 | ||
125 | /* Let's take a shortcut: if the top-level directory is properly owned, we don't descend into the whole tree, | |
126 | * under the assumption that all is OK anyway. */ | |
127 | ||
128 | if ((!uid_is_valid(uid) || st.st_uid == uid) && | |
129 | (!gid_is_valid(gid) || st.st_gid == gid)) | |
130 | return 0; | |
131 | ||
cd6b7d50 | 132 | return chown_recursive_internal(TAKE_FD(fd), &st, uid, gid); /* we donate the fd to the call, regardless if it succeeded or failed */ |
a1164ae3 | 133 | } |