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