]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/mkdir.c
stat-util: generalize is_* and verify_* handling
[thirdparty/systemd.git] / src / basic / mkdir.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <errno.h>
4 #include <stdbool.h>
5 #include <string.h>
6
7 #include "alloc-util.h"
8 #include "btrfs.h"
9 #include "chase.h"
10 #include "fd-util.h"
11 #include "format-util.h"
12 #include "fs-util.h"
13 #include "macro.h"
14 #include "mkdir.h"
15 #include "path-util.h"
16 #include "stat-util.h"
17 #include "stdio-util.h"
18 #include "user-util.h"
19
20 int mkdirat_safe_internal(
21 int dir_fd,
22 const char *path,
23 mode_t mode,
24 uid_t uid,
25 gid_t gid,
26 MkdirFlags flags,
27 mkdirat_func_t _mkdirat) {
28
29 struct stat st;
30 int r;
31
32 assert(path);
33 assert(mode != MODE_INVALID);
34 assert(_mkdirat && _mkdirat != mkdirat);
35
36 r = _mkdirat(dir_fd, path, mode);
37 if (r >= 0)
38 return chmod_and_chown_at(dir_fd, path, mode, uid, gid);
39 if (r != -EEXIST)
40 return r;
41
42 if (fstatat(dir_fd, path, &st, AT_SYMLINK_NOFOLLOW) < 0)
43 return -errno;
44
45 if ((flags & MKDIR_FOLLOW_SYMLINK) && S_ISLNK(st.st_mode)) {
46 _cleanup_free_ char *p = NULL;
47
48 r = chaseat(dir_fd, path, CHASE_NONEXISTENT, &p, NULL);
49 if (r < 0)
50 return r;
51 if (r == 0)
52 return mkdirat_safe_internal(dir_fd, p, mode, uid, gid,
53 flags & ~MKDIR_FOLLOW_SYMLINK,
54 _mkdirat);
55
56 if (fstatat(dir_fd, p, &st, AT_SYMLINK_NOFOLLOW) < 0)
57 return -errno;
58 }
59
60 if (flags & MKDIR_IGNORE_EXISTING)
61 return 0;
62
63 if (!S_ISDIR(st.st_mode))
64 return log_full_errno(flags & MKDIR_WARN_MODE ? LOG_WARNING : LOG_DEBUG, SYNTHETIC_ERRNO(ENOTDIR),
65 "Path \"%s\" already exists and is not a directory, refusing.", path);
66
67 if ((st.st_mode & ~mode & 0777) != 0)
68 return log_full_errno(flags & MKDIR_WARN_MODE ? LOG_WARNING : LOG_DEBUG, SYNTHETIC_ERRNO(EEXIST),
69 "Directory \"%s\" already exists, but has mode %04o that is too permissive (%04o was requested), refusing.",
70 path, st.st_mode & 0777, mode);
71
72 if ((uid != UID_INVALID && st.st_uid != uid) ||
73 (gid != GID_INVALID && st.st_gid != gid))
74 return log_full_errno(flags & MKDIR_WARN_MODE ? LOG_WARNING : LOG_DEBUG, SYNTHETIC_ERRNO(EEXIST),
75 "Directory \"%s\" already exists, but is owned by "UID_FMT":"GID_FMT" (%s:%s was requested), refusing.",
76 path, st.st_uid, st.st_gid, uid != UID_INVALID ? FORMAT_UID(uid) : "-",
77 gid != UID_INVALID ? FORMAT_GID(gid) : "-");
78
79 return 0;
80 }
81
82 int mkdirat_errno_wrapper(int dirfd, const char *pathname, mode_t mode) {
83 return RET_NERRNO(mkdirat(dirfd, pathname, mode));
84 }
85
86 int mkdirat_safe(int dir_fd, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags) {
87 return mkdirat_safe_internal(dir_fd, path, mode, uid, gid, flags, mkdirat_errno_wrapper);
88 }
89
90 int mkdirat_parents_internal(int dir_fd, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags, mkdirat_func_t _mkdirat) {
91 const char *e = NULL;
92 int r;
93
94 assert(path);
95 assert(_mkdirat != mkdirat);
96
97 if (isempty(path))
98 return 0;
99
100 if (!path_is_safe(path))
101 return -ENOTDIR;
102
103 /* return immediately if directory exists */
104 r = path_find_last_component(path, /* accept_dot_dot= */ false, &e, NULL);
105 if (r <= 0) /* r == 0 means path is equivalent to prefix. */
106 return r;
107 if (e == path)
108 return 0;
109
110 assert(e > path);
111 assert(*e == '/');
112
113 /* drop the last component */
114 path = strndupa_safe(path, e - path);
115 r = is_dir_at(dir_fd, path, /* follow = */ true);
116 if (r > 0)
117 return 0;
118 if (r == 0)
119 return -ENOTDIR;
120
121 /* create every parent directory in the path, except the last component */
122 for (const char *p = path;;) {
123 char *s;
124 int n;
125
126 n = path_find_first_component(&p, /* accept_dot_dot= */ false, (const char **) &s);
127 if (n <= 0)
128 return n;
129
130 assert(p);
131 assert(s >= path);
132 assert(IN_SET(s[n], '/', '\0'));
133
134 s[n] = '\0';
135
136 r = mkdirat_safe_internal(dir_fd, path, mode, uid, gid, flags | MKDIR_IGNORE_EXISTING, _mkdirat);
137 if (r < 0 && r != -EEXIST)
138 return r;
139
140 s[n] = *p == '\0' ? '\0' : '/';
141 }
142 }
143
144 int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags, mkdirat_func_t _mkdirat) {
145 _cleanup_close_ int fd = AT_FDCWD;
146 const char *p;
147
148 assert(path);
149 assert(_mkdirat != mkdirat);
150
151 if (prefix) {
152 p = path_startswith_full(path, prefix, /* accept_dot_dot= */ false);
153 if (!p)
154 return -ENOTDIR;
155 } else
156 p = path;
157
158 if (prefix) {
159 fd = open(prefix, O_PATH|O_DIRECTORY|O_CLOEXEC);
160 if (fd < 0)
161 return -errno;
162 }
163
164 return mkdirat_parents_internal(fd, p, mode, uid, gid, flags, _mkdirat);
165 }
166
167 int mkdirat_parents(int dir_fd, const char *path, mode_t mode) {
168 return mkdirat_parents_internal(dir_fd, path, mode, UID_INVALID, UID_INVALID, 0, mkdirat_errno_wrapper);
169 }
170
171 int mkdir_parents_safe(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags) {
172 return mkdir_parents_internal(prefix, path, mode, uid, gid, flags, mkdirat_errno_wrapper);
173 }
174
175 int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags, mkdirat_func_t _mkdirat) {
176 int r;
177
178 /* Like mkdir -p */
179
180 assert(_mkdirat != mkdirat);
181
182 r = mkdir_parents_internal(prefix, path, mode, uid, gid, flags | MKDIR_FOLLOW_SYMLINK, _mkdirat);
183 if (r < 0)
184 return r;
185
186 if (!uid_is_valid(uid) && !gid_is_valid(gid) && flags == 0) {
187 r = _mkdirat(AT_FDCWD, path, mode);
188 if (r < 0 && (r != -EEXIST || is_dir(path, true) <= 0))
189 return r;
190 } else {
191 r = mkdir_safe_internal(path, mode, uid, gid, flags, _mkdirat);
192 if (r < 0 && r != -EEXIST)
193 return r;
194 }
195
196 return 0;
197 }
198
199 int mkdir_p(const char *path, mode_t mode) {
200 return mkdir_p_internal(NULL, path, mode, UID_INVALID, UID_INVALID, 0, mkdirat_errno_wrapper);
201 }
202
203 int mkdir_p_safe(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags) {
204 return mkdir_p_internal(prefix, path, mode, uid, gid, flags, mkdirat_errno_wrapper);
205 }
206
207 int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m, char **subvolumes) {
208 _cleanup_free_ char *pp = NULL, *bn = NULL;
209 _cleanup_close_ int dfd = -EBADF;
210 int r;
211
212 r = path_extract_directory(p, &pp);
213 if (r == -EDESTADDRREQ) {
214 /* only fname is passed, no prefix to operate on */
215 dfd = open(".", O_RDONLY|O_CLOEXEC|O_DIRECTORY);
216 if (dfd < 0)
217 return -errno;
218 } else if (r == -EADDRNOTAVAIL)
219 /* only root dir or "." was passed, i.e. there is no parent to extract, in that case there's nothing to do. */
220 return 0;
221 else if (r < 0)
222 return r;
223 else {
224 /* Extracting the parent dir worked, hence we aren't top-level? Recurse up first. */
225 r = mkdir_p_root(root, pp, uid, gid, m, subvolumes);
226 if (r < 0)
227 return r;
228
229 dfd = chase_and_open(pp, root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_DIRECTORY, NULL);
230 if (dfd < 0)
231 return dfd;
232 }
233
234 r = path_extract_filename(p, &bn);
235 if (r == -EADDRNOTAVAIL) /* Already top-level */
236 return 0;
237 if (r < 0)
238 return r;
239
240 if (path_strv_contains(subvolumes, p))
241 r = btrfs_subvol_make_fallback(dfd, bn, m);
242 else
243 r = RET_NERRNO(mkdirat(dfd, bn, m));
244 if (r < 0) {
245 if (r == -EEXIST)
246 return 0;
247
248 return r;
249 }
250
251 if (uid_is_valid(uid) || gid_is_valid(gid)) {
252 _cleanup_close_ int nfd = -EBADF;
253
254 nfd = openat(dfd, bn, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
255 if (nfd < 0)
256 return -errno;
257
258 if (fchown(nfd, uid, gid) < 0)
259 return -errno;
260 }
261
262 return 1;
263 }