]>
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 | |
607b358e LP |
16 | static int chown_one( |
17 | int fd, | |
18 | const struct stat *st, | |
19 | uid_t uid, | |
20 | gid_t gid, | |
21 | mode_t mask) { | |
22 | ||
5de6cce5 | 23 | char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; |
f89bc84f | 24 | const char *n; |
a1164ae3 LP |
25 | |
26 | assert(fd >= 0); | |
27 | assert(st); | |
28 | ||
29 | if ((!uid_is_valid(uid) || st->st_uid == uid) && | |
30 | (!gid_is_valid(gid) || st->st_gid == gid)) | |
31 | return 0; | |
32 | ||
5de6cce5 LP |
33 | /* We change ownership through the /proc/self/fd/%i path, so that we have a stable reference that works with |
34 | * O_PATH. (Note: fchown() and fchmod() do not work with O_PATH, the kernel refuses that. */ | |
35 | xsprintf(procfs_path, "/proc/self/fd/%i", fd); | |
a1164ae3 | 36 | |
f89bc84f LP |
37 | /* Drop any ACL if there is one */ |
38 | FOREACH_STRING(n, "system.posix_acl_access", "system.posix_acl_default") | |
39 | if (removexattr(procfs_path, n) < 0) | |
40 | if (!IN_SET(errno, ENODATA, EOPNOTSUPP, ENOSYS, ENOTTY)) | |
41 | return -errno; | |
42 | ||
5de6cce5 | 43 | if (chown(procfs_path, uid, gid) < 0) |
a1164ae3 LP |
44 | return -errno; |
45 | ||
f89bc84f LP |
46 | /* The linux kernel alters the mode in some cases of chown(), as well when we change ACLs. Let's undo this. We |
47 | * do this only for non-symlinks however. That's because for symlinks the access mode is ignored anyway and | |
48 | * because on some kernels/file systems trying to change the access mode will succeed but has no effect while | |
49 | * on others it actively fails. */ | |
5de6cce5 | 50 | if (!S_ISLNK(st->st_mode)) |
607b358e | 51 | if (chmod(procfs_path, st->st_mode & 07777 & mask) < 0) |
5de6cce5 LP |
52 | return -errno; |
53 | ||
a1164ae3 LP |
54 | return 1; |
55 | } | |
56 | ||
607b358e LP |
57 | static int chown_recursive_internal( |
58 | int fd, | |
59 | const struct stat *st, | |
60 | uid_t uid, | |
61 | gid_t gid, | |
62 | mode_t mask) { | |
63 | ||
5de6cce5 | 64 | _cleanup_closedir_ DIR *d = NULL; |
a1164ae3 | 65 | bool changed = false; |
5de6cce5 | 66 | struct dirent *de; |
a1164ae3 LP |
67 | int r; |
68 | ||
69 | assert(fd >= 0); | |
70 | assert(st); | |
71 | ||
5de6cce5 LP |
72 | d = fdopendir(fd); |
73 | if (!d) { | |
74 | safe_close(fd); | |
75 | return -errno; | |
76 | } | |
77 | ||
78 | FOREACH_DIRENT_ALL(de, d, return -errno) { | |
79 | _cleanup_close_ int path_fd = -1; | |
80 | struct stat fst; | |
81 | ||
82 | if (dot_or_dot_dot(de->d_name)) | |
83 | continue; | |
84 | ||
85 | /* Let's pin the child inode we want to fix now with an O_PATH fd, so that it cannot be swapped out | |
86 | * while we manipulate it. */ | |
87 | path_fd = openat(dirfd(d), de->d_name, O_PATH|O_CLOEXEC|O_NOFOLLOW); | |
88 | if (path_fd < 0) | |
89 | return -errno; | |
90 | ||
91 | if (fstat(path_fd, &fst) < 0) | |
92 | return -errno; | |
93 | ||
94 | if (S_ISDIR(fst.st_mode)) { | |
95 | int subdir_fd; | |
96 | ||
97 | /* Convert it to a "real" (i.e. non-O_PATH) fd now */ | |
98 | subdir_fd = fd_reopen(path_fd, O_RDONLY|O_CLOEXEC|O_NOATIME); | |
99 | if (subdir_fd < 0) | |
100 | return subdir_fd; | |
101 | ||
607b358e | 102 | r = chown_recursive_internal(subdir_fd, &fst, uid, gid, mask); /* takes possession of subdir_fd even on failure */ |
5de6cce5 LP |
103 | if (r < 0) |
104 | return r; | |
105 | if (r > 0) | |
106 | changed = true; | |
107 | } else { | |
607b358e | 108 | r = chown_one(path_fd, &fst, uid, gid, mask); |
5de6cce5 LP |
109 | if (r < 0) |
110 | return r; | |
111 | if (r > 0) | |
112 | changed = true; | |
a1164ae3 | 113 | } |
5de6cce5 | 114 | } |
a1164ae3 | 115 | |
607b358e | 116 | r = chown_one(dirfd(d), st, uid, gid, mask); |
a1164ae3 | 117 | if (r < 0) |
5de6cce5 | 118 | return r; |
a1164ae3 | 119 | |
5de6cce5 | 120 | return r > 0 || changed; |
a1164ae3 LP |
121 | } |
122 | ||
607b358e LP |
123 | int path_chown_recursive( |
124 | const char *path, | |
125 | uid_t uid, | |
126 | gid_t gid, | |
127 | mode_t mask) { | |
128 | ||
a1164ae3 LP |
129 | _cleanup_close_ int fd = -1; |
130 | struct stat st; | |
a1164ae3 | 131 | |
5de6cce5 | 132 | fd = open(path, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); |
a1164ae3 LP |
133 | if (fd < 0) |
134 | return -errno; | |
135 | ||
136 | if (!uid_is_valid(uid) && !gid_is_valid(gid)) | |
137 | return 0; /* nothing to do */ | |
138 | ||
139 | if (fstat(fd, &st) < 0) | |
140 | return -errno; | |
141 | ||
142 | /* Let's take a shortcut: if the top-level directory is properly owned, we don't descend into the whole tree, | |
143 | * under the assumption that all is OK anyway. */ | |
144 | ||
145 | if ((!uid_is_valid(uid) || st.st_uid == uid) && | |
146 | (!gid_is_valid(gid) || st.st_gid == gid)) | |
147 | return 0; | |
148 | ||
607b358e | 149 | return chown_recursive_internal(TAKE_FD(fd), &st, uid, gid, mask); /* we donate the fd to the call, regardless if it succeeded or failed */ |
a1164ae3 | 150 | } |