]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/core/chown-recursive.c
tree-wide: remove Lennart's copyright lines
[thirdparty/systemd.git] / src / core / chown-recursive.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
a1164ae3
LP
2
3#include <sys/types.h>
4#include <sys/stat.h>
5#include <fcntl.h>
6
7#include "user-util.h"
8#include "macro.h"
9#include "fd-util.h"
10#include "dirent-util.h"
11#include "chown-recursive.h"
12
13static int chown_one(int fd, const char *name, const struct stat *st, uid_t uid, gid_t gid) {
14 int r;
15
16 assert(fd >= 0);
17 assert(st);
18
19 if ((!uid_is_valid(uid) || st->st_uid == uid) &&
20 (!gid_is_valid(gid) || st->st_gid == gid))
21 return 0;
22
23 if (name)
24 r = fchownat(fd, name, uid, gid, AT_SYMLINK_NOFOLLOW);
25 else
26 r = fchown(fd, uid, gid);
27 if (r < 0)
28 return -errno;
29
30 /* The linux kernel alters the mode in some cases of chown(). Let's undo this. */
31 if (name) {
32 if (!S_ISLNK(st->st_mode))
33 r = fchmodat(fd, name, st->st_mode, 0);
34 else /* There's currently no AT_SYMLINK_NOFOLLOW for fchmodat() */
35 r = 0;
36 } else
37 r = fchmod(fd, st->st_mode);
38 if (r < 0)
39 return -errno;
40
41 return 1;
42}
43
44static int chown_recursive_internal(int fd, const struct stat *st, uid_t uid, gid_t gid) {
45 bool changed = false;
46 int r;
47
48 assert(fd >= 0);
49 assert(st);
50
51 if (S_ISDIR(st->st_mode)) {
52 _cleanup_closedir_ DIR *d = NULL;
53 struct dirent *de;
54
55 d = fdopendir(fd);
56 if (!d) {
57 r = -errno;
58 goto finish;
59 }
60 fd = -1;
61
62 FOREACH_DIRENT_ALL(de, d, r = -errno; goto finish) {
63 struct stat fst;
64
65 if (dot_or_dot_dot(de->d_name))
66 continue;
67
68 if (fstatat(dirfd(d), de->d_name, &fst, AT_SYMLINK_NOFOLLOW) < 0) {
69 r = -errno;
70 goto finish;
71 }
72
73 if (S_ISDIR(fst.st_mode)) {
74 int subdir_fd;
75
76 subdir_fd = openat(dirfd(d), de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
77 if (subdir_fd < 0) {
78 r = -errno;
79 goto finish;
80 }
81
82 r = chown_recursive_internal(subdir_fd, &fst, uid, gid);
83 if (r < 0)
84 goto finish;
85 if (r > 0)
86 changed = true;
87 } else {
88 r = chown_one(dirfd(d), de->d_name, &fst, uid, gid);
89 if (r < 0)
90 goto finish;
91 if (r > 0)
92 changed = true;
93 }
94 }
95
96 r = chown_one(dirfd(d), NULL, st, uid, gid);
97 } else
98 r = chown_one(fd, NULL, st, uid, gid);
99 if (r < 0)
100 goto finish;
101
102 r = r > 0 || changed;
103
104finish:
105 safe_close(fd);
106 return r;
107}
108
109int path_chown_recursive(const char *path, uid_t uid, gid_t gid) {
110 _cleanup_close_ int fd = -1;
111 struct stat st;
112 int r;
113
114 fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
115 if (fd < 0)
116 return -errno;
117
118 if (!uid_is_valid(uid) && !gid_is_valid(gid))
119 return 0; /* nothing to do */
120
121 if (fstat(fd, &st) < 0)
122 return -errno;
123
124 /* Let's take a shortcut: if the top-level directory is properly owned, we don't descend into the whole tree,
125 * under the assumption that all is OK anyway. */
126
127 if ((!uid_is_valid(uid) || st.st_uid == uid) &&
128 (!gid_is_valid(gid) || st.st_gid == gid))
129 return 0;
130
131 r = chown_recursive_internal(fd, &st, uid, gid);
132 fd = -1; /* we donated the fd to the call, regardless if it succeeded or failed */
133
134 return r;
135}