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