chiark / gitweb /
tree-wide: warn when a directory path already exists but has bad mode/owner/type
[elogind.git] / src / basic / mkdir.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3   This file is part of systemd.
4
5   Copyright 2010 Lennart Poettering
6
7   systemd is free software; you can redistribute it and/or modify it
8   under the terms of the GNU Lesser General Public License as published by
9   the Free Software Foundation; either version 2.1 of the License, or
10   (at your option) any later version.
11
12   systemd is distributed in the hope that it will be useful, but
13   WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15   Lesser General Public License for more details.
16
17   You should have received a copy of the GNU Lesser General Public License
18   along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <errno.h>
22 #include <stdbool.h>
23 #include <string.h>
24 #include <sys/stat.h>
25
26 #include "alloc-util.h"
27 #include "fs-util.h"
28 #include "macro.h"
29 #include "mkdir.h"
30 #include "path-util.h"
31 #include "stat-util.h"
32 //#include "stdio-util.h"
33 #include "user-util.h"
34
35 /// Additional includes needed by elogind
36 #include "missing.h"
37
38 int mkdir_safe_internal(const char *path, mode_t mode, uid_t uid, gid_t gid, bool follow_symlink, mkdir_func_t _mkdir) {
39 int mkdir_safe_internal(const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags, mkdir_func_t _mkdir) {
40         struct stat st;
41         int r;
42
43         assert(_mkdir != mkdir);
44
45         if (_mkdir(path, mode) >= 0) {
46                 r = chmod_and_chown(path, mode, uid, gid);
47                 if (r < 0)
48                         return r;
49         }
50
51         if (lstat(path, &st) < 0)
52                 return -errno;
53
54         if ((flags & MKDIR_FOLLOW_SYMLINK) && S_ISLNK(st.st_mode)) {
55                 _cleanup_free_ char *p = NULL;
56
57                 r = chase_symlinks(path, NULL, CHASE_NONEXISTENT, &p);
58                 if (r < 0)
59                         return r;
60                 if (r == 0)
61                         return mkdir_safe_internal(p, mode, uid, gid,
62                                                    flags & ~MKDIR_FOLLOW_SYMLINK,
63                                                    _mkdir);
64
65                 if (lstat(p, &st) < 0)
66                         return -errno;
67         }
68
69         if (!S_ISDIR(st.st_mode)) {
70                 log_full(flags & MKDIR_WARN_MODE ? LOG_WARNING : LOG_DEBUG,
71                          "Path \"%s\" already exists and is not a directory, refusing.", path);
72                 return -ENOTDIR;
73         }
74         if ((st.st_mode & 0007) > (mode & 0007) ||
75             (st.st_mode & 0070) > (mode & 0070) ||
76             (st.st_mode & 0700) > (mode & 0700)) {
77                 log_full(flags & MKDIR_WARN_MODE ? LOG_WARNING : LOG_DEBUG,
78                          "Directory \"%s\" already exists, but has mode %04o that is too permissive (%04o was requested), refusing.",
79                          path, st.st_mode & 0777, mode);
80                 return -EEXIST;
81         }
82         if ((uid != UID_INVALID && st.st_uid != uid) ||
83             (gid != GID_INVALID && st.st_gid != gid)) {
84                 char u[DECIMAL_STR_MAX(uid_t)] = "-", g[DECIMAL_STR_MAX(gid_t)] = "-";
85
86                 if (uid != UID_INVALID)
87                         xsprintf(u, UID_FMT, uid);
88                 if (gid != UID_INVALID)
89                         xsprintf(g, GID_FMT, gid);
90                 log_full(flags & MKDIR_WARN_MODE ? LOG_WARNING : LOG_DEBUG,
91                          "Directory \"%s\" already exists, but is owned by "UID_FMT":"GID_FMT" (%s:%s was requested), refusing.",
92                          path, st.st_uid, st.st_gid, u, g);
93                 return -EEXIST;
94         }
95
96         return 0;
97 }
98
99 int mkdir_errno_wrapper(const char *pathname, mode_t mode) {
100         if (mkdir(pathname, mode) < 0)
101                 return -errno;
102         return 0;
103 }
104
105 int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags) {
106         return mkdir_safe_internal(path, mode, uid, gid, flags, mkdir_errno_wrapper);
107 }
108
109 int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir) {
110         const char *p, *e;
111         int r;
112
113         assert(path);
114         assert(_mkdir != mkdir);
115
116         if (prefix && !path_startswith(path, prefix))
117                 return -ENOTDIR;
118
119         /* return immediately if directory exists */
120         e = strrchr(path, '/');
121         if (!e)
122                 return -EINVAL;
123
124         if (e == path)
125                 return 0;
126
127         p = strndupa(path, e - path);
128         r = is_dir(p, true);
129         if (r > 0)
130                 return 0;
131         if (r == 0)
132                 return -ENOTDIR;
133
134         /* create every parent directory in the path, except the last component */
135         p = path + strspn(path, "/");
136         for (;;) {
137                 char t[strlen(path) + 1];
138
139                 e = p + strcspn(p, "/");
140                 p = e + strspn(e, "/");
141
142                 /* Is this the last component? If so, then we're done */
143                 if (*p == 0)
144                         return 0;
145
146                 memcpy(t, path, e - path);
147                 t[e-path] = 0;
148
149                 if (prefix && path_startswith(prefix, t))
150                         continue;
151
152                 r = _mkdir(t, mode);
153                 if (r < 0 && r != -EEXIST)
154                         return r;
155         }
156 }
157
158 int mkdir_parents(const char *path, mode_t mode) {
159         return mkdir_parents_internal(NULL, path, mode, mkdir_errno_wrapper);
160 }
161
162 int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir) {
163         int r;
164
165         /* Like mkdir -p */
166
167         assert(_mkdir != mkdir);
168
169         r = mkdir_parents_internal(prefix, path, mode, _mkdir);
170         if (r < 0)
171                 return r;
172
173         r = _mkdir(path, mode);
174         if (r < 0 && (r != -EEXIST || is_dir(path, true) <= 0))
175                 return r;
176
177         return 0;
178 }
179
180 int mkdir_p(const char *path, mode_t mode) {
181         return mkdir_p_internal(NULL, path, mode, mkdir_errno_wrapper);
182 }