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