chiark / gitweb /
Prep v239: Uncomment header inclusions that are new or needed now.
[elogind.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 #include <sys/stat.h>
7
8 #include "alloc-util.h"
9 #include "fs-util.h"
10 #include "macro.h"
11 #include "mkdir.h"
12 #include "path-util.h"
13 #include "stat-util.h"
14 #include "stdio-util.h"
15 #include "user-util.h"
16
17 /// Additional includes needed by elogind
18 #include "missing.h"
19
20 int mkdir_safe_internal(const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags, mkdir_func_t _mkdir) {
21         struct stat st;
22         int r;
23
24         assert(_mkdir != mkdir);
25
26         if (_mkdir(path, mode) >= 0) {
27                 r = chmod_and_chown(path, mode, uid, gid);
28                 if (r < 0)
29                         return r;
30         }
31
32         if (lstat(path, &st) < 0)
33                 return -errno;
34
35         if ((flags & MKDIR_FOLLOW_SYMLINK) && S_ISLNK(st.st_mode)) {
36                 _cleanup_free_ char *p = NULL;
37
38                 r = chase_symlinks(path, NULL, CHASE_NONEXISTENT, &p);
39                 if (r < 0)
40                         return r;
41                 if (r == 0)
42                         return mkdir_safe_internal(p, mode, uid, gid,
43                                                    flags & ~MKDIR_FOLLOW_SYMLINK,
44                                                    _mkdir);
45
46                 if (lstat(p, &st) < 0)
47                         return -errno;
48         }
49
50         if (!S_ISDIR(st.st_mode)) {
51                 log_full(flags & MKDIR_WARN_MODE ? LOG_WARNING : LOG_DEBUG,
52                          "Path \"%s\" already exists and is not a directory, refusing.", path);
53                 return -ENOTDIR;
54         }
55         if ((st.st_mode & 0007) > (mode & 0007) ||
56             (st.st_mode & 0070) > (mode & 0070) ||
57             (st.st_mode & 0700) > (mode & 0700)) {
58                 log_full(flags & MKDIR_WARN_MODE ? LOG_WARNING : LOG_DEBUG,
59                          "Directory \"%s\" already exists, but has mode %04o that is too permissive (%04o was requested), refusing.",
60                          path, st.st_mode & 0777, mode);
61                 return -EEXIST;
62         }
63         if ((uid != UID_INVALID && st.st_uid != uid) ||
64             (gid != GID_INVALID && st.st_gid != gid)) {
65                 char u[DECIMAL_STR_MAX(uid_t)] = "-", g[DECIMAL_STR_MAX(gid_t)] = "-";
66
67                 if (uid != UID_INVALID)
68                         xsprintf(u, UID_FMT, uid);
69                 if (gid != UID_INVALID)
70                         xsprintf(g, GID_FMT, gid);
71                 log_full(flags & MKDIR_WARN_MODE ? LOG_WARNING : LOG_DEBUG,
72                          "Directory \"%s\" already exists, but is owned by "UID_FMT":"GID_FMT" (%s:%s was requested), refusing.",
73                          path, st.st_uid, st.st_gid, u, g);
74                 return -EEXIST;
75         }
76
77         return 0;
78 }
79
80 int mkdir_errno_wrapper(const char *pathname, mode_t mode) {
81         if (mkdir(pathname, mode) < 0)
82                 return -errno;
83         return 0;
84 }
85
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, mkdir_errno_wrapper);
88 }
89
90 int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir) {
91         const char *p, *e;
92         int r;
93
94         assert(path);
95         assert(_mkdir != mkdir);
96
97         if (prefix && !path_startswith(path, prefix))
98                 return -ENOTDIR;
99
100         /* return immediately if directory exists */
101         e = strrchr(path, '/');
102         if (!e)
103                 return -EINVAL;
104
105         if (e == path)
106                 return 0;
107
108         p = strndupa(path, e - path);
109         r = is_dir(p, true);
110         if (r > 0)
111                 return 0;
112         if (r == 0)
113                 return -ENOTDIR;
114
115         /* create every parent directory in the path, except the last component */
116         p = path + strspn(path, "/");
117         for (;;) {
118                 char t[strlen(path) + 1];
119
120                 e = p + strcspn(p, "/");
121                 p = e + strspn(e, "/");
122
123                 /* Is this the last component? If so, then we're done */
124                 if (*p == 0)
125                         return 0;
126
127                 memcpy(t, path, e - path);
128                 t[e-path] = 0;
129
130                 if (prefix && path_startswith(prefix, t))
131                         continue;
132
133                 r = _mkdir(t, mode);
134                 if (r < 0 && r != -EEXIST)
135                         return r;
136         }
137 }
138
139 int mkdir_parents(const char *path, mode_t mode) {
140         return mkdir_parents_internal(NULL, path, mode, mkdir_errno_wrapper);
141 }
142
143 int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir) {
144         int r;
145
146         /* Like mkdir -p */
147
148         assert(_mkdir != mkdir);
149
150         r = mkdir_parents_internal(prefix, path, mode, _mkdir);
151         if (r < 0)
152                 return r;
153
154         r = _mkdir(path, mode);
155         if (r < 0 && (r != -EEXIST || is_dir(path, true) <= 0))
156                 return r;
157
158         return 0;
159 }
160
161 int mkdir_p(const char *path, mode_t mode) {
162         return mkdir_p_internal(NULL, path, mode, mkdir_errno_wrapper);
163 }