]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/mkdir.c
core: reduce scope of variants
[thirdparty/systemd.git] / src / basic / mkdir.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <errno.h>
4 #include <stdbool.h>
5 #include <string.h>
6
7 #include "alloc-util.h"
8 #include "fd-util.h"
9 #include "format-util.h"
10 #include "fs-util.h"
11 #include "macro.h"
12 #include "mkdir.h"
13 #include "path-util.h"
14 #include "stat-util.h"
15 #include "stdio-util.h"
16 #include "user-util.h"
17
18 int mkdir_safe_internal(
19 const char *path,
20 mode_t mode,
21 uid_t uid, gid_t gid,
22 MkdirFlags flags,
23 mkdir_func_t _mkdir) {
24
25 struct stat st;
26 int r;
27
28 assert(path);
29 assert(_mkdir && _mkdir != mkdir);
30
31 if (_mkdir(path, mode) >= 0) {
32 r = chmod_and_chown(path, mode, uid, gid);
33 if (r < 0)
34 return r;
35 }
36
37 if (lstat(path, &st) < 0)
38 return -errno;
39
40 if ((flags & MKDIR_FOLLOW_SYMLINK) && S_ISLNK(st.st_mode)) {
41 _cleanup_free_ char *p = NULL;
42
43 r = chase_symlinks(path, NULL, CHASE_NONEXISTENT, &p, NULL);
44 if (r < 0)
45 return r;
46 if (r == 0)
47 return mkdir_safe_internal(p, mode, uid, gid,
48 flags & ~MKDIR_FOLLOW_SYMLINK,
49 _mkdir);
50
51 if (lstat(p, &st) < 0)
52 return -errno;
53 }
54
55 if (!S_ISDIR(st.st_mode))
56 return log_full_errno(flags & MKDIR_WARN_MODE ? LOG_WARNING : LOG_DEBUG, SYNTHETIC_ERRNO(ENOTDIR),
57 "Path \"%s\" already exists and is not a directory, refusing.", path);
58 if ((st.st_mode & 0007) > (mode & 0007) ||
59 (st.st_mode & 0070) > (mode & 0070) ||
60 (st.st_mode & 0700) > (mode & 0700))
61 return log_full_errno(flags & MKDIR_WARN_MODE ? LOG_WARNING : LOG_DEBUG, SYNTHETIC_ERRNO(EEXIST),
62 "Directory \"%s\" already exists, but has mode %04o that is too permissive (%04o was requested), refusing.",
63 path, st.st_mode & 0777, mode);
64
65 if ((uid != UID_INVALID && st.st_uid != uid) ||
66 (gid != GID_INVALID && st.st_gid != gid)) {
67 char u[DECIMAL_STR_MAX(uid_t)] = "-", g[DECIMAL_STR_MAX(gid_t)] = "-";
68
69 if (uid != UID_INVALID)
70 xsprintf(u, UID_FMT, uid);
71 if (gid != UID_INVALID)
72 xsprintf(g, GID_FMT, gid);
73 return log_full_errno(flags & MKDIR_WARN_MODE ? LOG_WARNING : LOG_DEBUG, SYNTHETIC_ERRNO(EEXIST),
74 "Directory \"%s\" already exists, but is owned by "UID_FMT":"GID_FMT" (%s:%s was requested), refusing.",
75 path, st.st_uid, st.st_gid, u, g);
76 }
77
78 return 0;
79 }
80
81 int mkdir_errno_wrapper(const char *pathname, mode_t mode) {
82 if (mkdir(pathname, mode) < 0)
83 return -errno;
84 return 0;
85 }
86
87 int mkdirat_errno_wrapper(int dirfd, const char *pathname, mode_t mode) {
88 if (mkdirat(dirfd, pathname, mode) < 0)
89 return -errno;
90 return 0;
91 }
92
93 int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags) {
94 return mkdir_safe_internal(path, mode, uid, gid, flags, mkdir_errno_wrapper);
95 }
96
97 int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags, mkdir_func_t _mkdir) {
98 const char *p, *e;
99 int r;
100
101 assert(path);
102 assert(_mkdir != mkdir);
103
104 if (prefix && !path_startswith(path, prefix))
105 return -ENOTDIR;
106
107 /* return immediately if directory exists */
108 e = strrchr(path, '/');
109 if (!e)
110 return 0;
111
112 if (e == path)
113 return 0;
114
115 p = strndupa(path, e - path);
116 r = is_dir(p, true);
117 if (r > 0)
118 return 0;
119 if (r == 0)
120 return -ENOTDIR;
121
122 /* create every parent directory in the path, except the last component */
123 p = path + strspn(path, "/");
124 for (;;) {
125 char t[strlen(path) + 1];
126
127 e = p + strcspn(p, "/");
128 p = e + strspn(e, "/");
129
130 /* Is this the last component? If so, then we're done */
131 if (*p == 0)
132 return 0;
133
134 memcpy(t, path, e - path);
135 t[e-path] = 0;
136
137 if (prefix && path_startswith(prefix, t))
138 continue;
139
140 if (!uid_is_valid(uid) && !gid_is_valid(gid) && flags == 0) {
141 r = _mkdir(t, mode);
142 if (r < 0 && r != -EEXIST)
143 return r;
144 } else {
145 r = mkdir_safe_internal(t, mode, uid, gid, flags, _mkdir);
146 if (r < 0 && r != -EEXIST)
147 return r;
148 }
149 }
150 }
151
152 int mkdir_parents(const char *path, mode_t mode) {
153 return mkdir_parents_internal(NULL, path, mode, UID_INVALID, UID_INVALID, 0, mkdir_errno_wrapper);
154 }
155
156 int mkdir_parents_safe(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags) {
157 return mkdir_parents_internal(prefix, path, mode, uid, gid, flags, mkdir_errno_wrapper);
158 }
159
160 int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags, mkdir_func_t _mkdir) {
161 int r;
162
163 /* Like mkdir -p */
164
165 assert(_mkdir != mkdir);
166
167 r = mkdir_parents_internal(prefix, path, mode, uid, gid, flags, _mkdir);
168 if (r < 0)
169 return r;
170
171 if (!uid_is_valid(uid) && !gid_is_valid(gid) && flags == 0) {
172 r = _mkdir(path, mode);
173 if (r < 0 && (r != -EEXIST || is_dir(path, true) <= 0))
174 return r;
175 } else {
176 r = mkdir_safe_internal(path, mode, uid, gid, flags, _mkdir);
177 if (r < 0 && r != -EEXIST)
178 return r;
179 }
180
181 return 0;
182 }
183
184 int mkdir_p(const char *path, mode_t mode) {
185 return mkdir_p_internal(NULL, path, mode, UID_INVALID, UID_INVALID, 0, mkdir_errno_wrapper);
186 }
187
188 int mkdir_p_safe(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags) {
189 return mkdir_p_internal(prefix, path, mode, uid, gid, flags, mkdir_errno_wrapper);
190 }
191
192 int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m) {
193 _cleanup_free_ char *pp = NULL;
194 _cleanup_close_ int dfd = -1;
195 const char *bn;
196 int r;
197
198 pp = dirname_malloc(p);
199 if (!pp)
200 return -ENOMEM;
201
202 /* Not top-level? */
203 if (!(path_equal(pp, "/") || isempty(pp) || path_equal(pp, "."))) {
204
205 /* Recurse up */
206 r = mkdir_p_root(root, pp, uid, gid, m);
207 if (r < 0)
208 return r;
209 }
210
211 bn = basename(p);
212 if (path_equal(bn, "/") || isempty(bn) || path_equal(bn, "."))
213 return 0;
214
215 if (!filename_is_valid(bn))
216 return -EINVAL;
217
218 dfd = chase_symlinks_and_open(pp, root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_DIRECTORY, NULL);
219 if (dfd < 0)
220 return dfd;
221
222 if (mkdirat(dfd, bn, m) < 0) {
223 if (errno == EEXIST)
224 return 0;
225
226 return -errno;
227 }
228
229 if (uid_is_valid(uid) || gid_is_valid(gid)) {
230 _cleanup_close_ int nfd = -1;
231
232 nfd = openat(dfd, bn, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
233 if (nfd < 0)
234 return -errno;
235
236 if (fchown(nfd, uid, gid) < 0)
237 return -errno;
238 }
239
240 return 1;
241 }