]>
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" | |
4b3b5bc7 | 11 | #include "fs-util.h" |
5de6cce5 LP |
12 | #include "macro.h" |
13 | #include "stdio-util.h" | |
14 | #include "strv.h" | |
15 | #include "user-util.h" | |
a1164ae3 | 16 | |
607b358e LP |
17 | static int chown_one( |
18 | int fd, | |
19 | const struct stat *st, | |
20 | uid_t uid, | |
21 | gid_t gid, | |
22 | mode_t mask) { | |
23 | ||
5de6cce5 | 24 | char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; |
f89bc84f | 25 | const char *n; |
4b3b5bc7 | 26 | int r; |
a1164ae3 LP |
27 | |
28 | assert(fd >= 0); | |
29 | assert(st); | |
30 | ||
4b3b5bc7 LP |
31 | /* We change ACLs through the /proc/self/fd/%i path, so that we have a stable reference that works |
32 | * with O_PATH. */ | |
5de6cce5 | 33 | xsprintf(procfs_path, "/proc/self/fd/%i", fd); |
a1164ae3 | 34 | |
f89bc84f LP |
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)) | |
39 | return -errno; | |
40 | ||
4b3b5bc7 LP |
41 | r = fchmod_and_chown(fd, st->st_mode & mask, uid, gid); |
42 | if (r < 0) | |
43 | return r; | |
5de6cce5 | 44 | |
a1164ae3 LP |
45 | return 1; |
46 | } | |
47 | ||
607b358e LP |
48 | static int chown_recursive_internal( |
49 | int fd, | |
50 | const struct stat *st, | |
51 | uid_t uid, | |
52 | gid_t gid, | |
53 | mode_t mask) { | |
54 | ||
5de6cce5 | 55 | _cleanup_closedir_ DIR *d = NULL; |
a1164ae3 | 56 | bool changed = false; |
5de6cce5 | 57 | struct dirent *de; |
a1164ae3 LP |
58 | int r; |
59 | ||
60 | assert(fd >= 0); | |
61 | assert(st); | |
62 | ||
5de6cce5 LP |
63 | d = fdopendir(fd); |
64 | if (!d) { | |
65 | safe_close(fd); | |
66 | return -errno; | |
67 | } | |
68 | ||
69 | FOREACH_DIRENT_ALL(de, d, return -errno) { | |
70 | _cleanup_close_ int path_fd = -1; | |
71 | struct stat fst; | |
72 | ||
73 | if (dot_or_dot_dot(de->d_name)) | |
74 | continue; | |
75 | ||
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); | |
79 | if (path_fd < 0) | |
80 | return -errno; | |
81 | ||
82 | if (fstat(path_fd, &fst) < 0) | |
83 | return -errno; | |
84 | ||
85 | if (S_ISDIR(fst.st_mode)) { | |
86 | int subdir_fd; | |
87 | ||
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); | |
90 | if (subdir_fd < 0) | |
91 | return subdir_fd; | |
92 | ||
607b358e | 93 | r = chown_recursive_internal(subdir_fd, &fst, uid, gid, mask); /* takes possession of subdir_fd even on failure */ |
5de6cce5 LP |
94 | if (r < 0) |
95 | return r; | |
96 | if (r > 0) | |
97 | changed = true; | |
98 | } else { | |
607b358e | 99 | r = chown_one(path_fd, &fst, uid, gid, mask); |
5de6cce5 LP |
100 | if (r < 0) |
101 | return r; | |
102 | if (r > 0) | |
103 | changed = true; | |
a1164ae3 | 104 | } |
5de6cce5 | 105 | } |
a1164ae3 | 106 | |
607b358e | 107 | r = chown_one(dirfd(d), st, uid, gid, mask); |
a1164ae3 | 108 | if (r < 0) |
5de6cce5 | 109 | return r; |
a1164ae3 | 110 | |
5de6cce5 | 111 | return r > 0 || changed; |
a1164ae3 LP |
112 | } |
113 | ||
607b358e LP |
114 | int path_chown_recursive( |
115 | const char *path, | |
116 | uid_t uid, | |
117 | gid_t gid, | |
118 | mode_t mask) { | |
119 | ||
a1164ae3 LP |
120 | _cleanup_close_ int fd = -1; |
121 | struct stat st; | |
a1164ae3 | 122 | |
5de6cce5 | 123 | fd = open(path, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); |
a1164ae3 LP |
124 | if (fd < 0) |
125 | return -errno; | |
126 | ||
85318688 | 127 | if (!uid_is_valid(uid) && !gid_is_valid(gid) && (mask & 07777) == 07777) |
a1164ae3 LP |
128 | return 0; /* nothing to do */ |
129 | ||
130 | if (fstat(fd, &st) < 0) | |
131 | return -errno; | |
132 | ||
85318688 LP |
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. */ | |
a1164ae3 | 135 | if ((!uid_is_valid(uid) || st.st_uid == uid) && |
85318688 LP |
136 | (!gid_is_valid(gid) || st.st_gid == gid) && |
137 | ((st.st_mode & ~mask & 07777) == 0)) | |
a1164ae3 LP |
138 | return 0; |
139 | ||
607b358e | 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 */ |
a1164ae3 | 141 | } |