]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/core/chown-recursive.c
test: add tests for syscall:errno style in SystemCallFilter=
[thirdparty/systemd.git] / src / core / chown-recursive.c
1 /***
2 This file is part of systemd.
3
4 Copyright 2017 Lennart Poettering
5
6 systemd is free software; you can redistribute it and/or modify it
7 under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
10
11 systemd is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with systemd; If not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <fcntl.h>
23
24 #include "user-util.h"
25 #include "macro.h"
26 #include "fd-util.h"
27 #include "dirent-util.h"
28 #include "chown-recursive.h"
29
30 static int chown_one(int fd, const char *name, const struct stat *st, uid_t uid, gid_t gid) {
31 int r;
32
33 assert(fd >= 0);
34 assert(st);
35
36 if ((!uid_is_valid(uid) || st->st_uid == uid) &&
37 (!gid_is_valid(gid) || st->st_gid == gid))
38 return 0;
39
40 if (name)
41 r = fchownat(fd, name, uid, gid, AT_SYMLINK_NOFOLLOW);
42 else
43 r = fchown(fd, uid, gid);
44 if (r < 0)
45 return -errno;
46
47 /* The linux kernel alters the mode in some cases of chown(). Let's undo this. */
48 if (name) {
49 if (!S_ISLNK(st->st_mode))
50 r = fchmodat(fd, name, st->st_mode, 0);
51 else /* There's currently no AT_SYMLINK_NOFOLLOW for fchmodat() */
52 r = 0;
53 } else
54 r = fchmod(fd, st->st_mode);
55 if (r < 0)
56 return -errno;
57
58 return 1;
59 }
60
61 static int chown_recursive_internal(int fd, const struct stat *st, uid_t uid, gid_t gid) {
62 bool changed = false;
63 int r;
64
65 assert(fd >= 0);
66 assert(st);
67
68 if (S_ISDIR(st->st_mode)) {
69 _cleanup_closedir_ DIR *d = NULL;
70 struct dirent *de;
71
72 d = fdopendir(fd);
73 if (!d) {
74 r = -errno;
75 goto finish;
76 }
77 fd = -1;
78
79 FOREACH_DIRENT_ALL(de, d, r = -errno; goto finish) {
80 struct stat fst;
81
82 if (dot_or_dot_dot(de->d_name))
83 continue;
84
85 if (fstatat(dirfd(d), de->d_name, &fst, AT_SYMLINK_NOFOLLOW) < 0) {
86 r = -errno;
87 goto finish;
88 }
89
90 if (S_ISDIR(fst.st_mode)) {
91 int subdir_fd;
92
93 subdir_fd = openat(dirfd(d), de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
94 if (subdir_fd < 0) {
95 r = -errno;
96 goto finish;
97 }
98
99 r = chown_recursive_internal(subdir_fd, &fst, uid, gid);
100 if (r < 0)
101 goto finish;
102 if (r > 0)
103 changed = true;
104 } else {
105 r = chown_one(dirfd(d), de->d_name, &fst, uid, gid);
106 if (r < 0)
107 goto finish;
108 if (r > 0)
109 changed = true;
110 }
111 }
112
113 r = chown_one(dirfd(d), NULL, st, uid, gid);
114 } else
115 r = chown_one(fd, NULL, st, uid, gid);
116 if (r < 0)
117 goto finish;
118
119 r = r > 0 || changed;
120
121 finish:
122 safe_close(fd);
123 return r;
124 }
125
126 int path_chown_recursive(const char *path, uid_t uid, gid_t gid) {
127 _cleanup_close_ int fd = -1;
128 struct stat st;
129 int r;
130
131 fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME);
132 if (fd < 0)
133 return -errno;
134
135 if (!uid_is_valid(uid) && !gid_is_valid(gid))
136 return 0; /* nothing to do */
137
138 if (fstat(fd, &st) < 0)
139 return -errno;
140
141 /* Let's take a shortcut: if the top-level directory is properly owned, we don't descend into the whole tree,
142 * under the assumption that all is OK anyway. */
143
144 if ((!uid_is_valid(uid) || st.st_uid == uid) &&
145 (!gid_is_valid(gid) || st.st_gid == gid))
146 return 0;
147
148 r = chown_recursive_internal(fd, &st, uid, gid);
149 fd = -1; /* we donated the fd to the call, regardless if it succeeded or failed */
150
151 return r;
152 }