]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/mkdir.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
7 #include "alloc-util.h"
11 #include "format-util.h"
15 #include "path-util.h"
16 #include "stat-util.h"
17 #include "stdio-util.h"
18 #include "user-util.h"
20 int mkdirat_safe_internal(
27 mkdirat_func_t _mkdirat
) {
33 assert(mode
!= MODE_INVALID
);
34 assert(_mkdirat
&& _mkdirat
!= mkdirat
);
36 r
= _mkdirat(dir_fd
, path
, mode
);
38 return chmod_and_chown_at(dir_fd
, path
, mode
, uid
, gid
);
42 if (fstatat(dir_fd
, path
, &st
, AT_SYMLINK_NOFOLLOW
) < 0)
45 if ((flags
& MKDIR_FOLLOW_SYMLINK
) && S_ISLNK(st
.st_mode
)) {
46 _cleanup_free_
char *p
= NULL
;
48 r
= chaseat(dir_fd
, path
, CHASE_NONEXISTENT
, &p
, NULL
);
52 return mkdirat_safe_internal(dir_fd
, p
, mode
, uid
, gid
,
53 flags
& ~MKDIR_FOLLOW_SYMLINK
,
56 if (fstatat(dir_fd
, p
, &st
, AT_SYMLINK_NOFOLLOW
) < 0)
60 if (flags
& MKDIR_IGNORE_EXISTING
)
63 if (!S_ISDIR(st
.st_mode
))
64 return log_full_errno(flags
& MKDIR_WARN_MODE
? LOG_WARNING
: LOG_DEBUG
, SYNTHETIC_ERRNO(ENOTDIR
),
65 "Path \"%s\" already exists and is not a directory, refusing.", path
);
67 if ((st
.st_mode
& ~mode
& 0777) != 0)
68 return log_full_errno(flags
& MKDIR_WARN_MODE
? LOG_WARNING
: LOG_DEBUG
, SYNTHETIC_ERRNO(EEXIST
),
69 "Directory \"%s\" already exists, but has mode %04o that is too permissive (%04o was requested), refusing.",
70 path
, st
.st_mode
& 0777, mode
);
72 if ((uid
!= UID_INVALID
&& st
.st_uid
!= uid
) ||
73 (gid
!= GID_INVALID
&& st
.st_gid
!= 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
, uid
!= UID_INVALID
? FORMAT_UID(uid
) : "-",
77 gid
!= UID_INVALID
? FORMAT_GID(gid
) : "-");
82 int mkdirat_errno_wrapper(int dirfd
, const char *pathname
, mode_t mode
) {
83 return RET_NERRNO(mkdirat(dirfd
, pathname
, mode
));
86 int mkdirat_safe(int dir_fd
, const char *path
, mode_t mode
, uid_t uid
, gid_t gid
, MkdirFlags flags
) {
87 return mkdirat_safe_internal(dir_fd
, path
, mode
, uid
, gid
, flags
, mkdirat_errno_wrapper
);
90 int mkdirat_parents_internal(int dir_fd
, const char *path
, mode_t mode
, uid_t uid
, gid_t gid
, MkdirFlags flags
, mkdirat_func_t _mkdirat
) {
95 assert(_mkdirat
!= mkdirat
);
100 if (!path_is_safe(path
))
103 /* return immediately if directory exists */
104 r
= path_find_last_component(path
, /* accept_dot_dot= */ false, &e
, NULL
);
105 if (r
<= 0) /* r == 0 means path is equivalent to prefix. */
113 /* drop the last component */
114 path
= strndupa_safe(path
, e
- path
);
115 r
= is_dir_at(dir_fd
, path
, /* follow = */ true);
121 /* create every parent directory in the path, except the last component */
122 for (const char *p
= path
;;) {
126 n
= path_find_first_component(&p
, /* accept_dot_dot= */ false, (const char **) &s
);
132 assert(IN_SET(s
[n
], '/', '\0'));
136 r
= mkdirat_safe_internal(dir_fd
, path
, mode
, uid
, gid
, flags
| MKDIR_IGNORE_EXISTING
, _mkdirat
);
137 if (r
< 0 && r
!= -EEXIST
)
140 s
[n
] = *p
== '\0' ? '\0' : '/';
144 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
) {
145 _cleanup_close_
int fd
= AT_FDCWD
;
149 assert(_mkdirat
!= mkdirat
);
152 p
= path_startswith_full(path
, prefix
, /* accept_dot_dot= */ false);
159 fd
= open(prefix
, O_PATH
|O_DIRECTORY
|O_CLOEXEC
);
164 return mkdirat_parents_internal(fd
, p
, mode
, uid
, gid
, flags
, _mkdirat
);
167 int mkdirat_parents(int dir_fd
, const char *path
, mode_t mode
) {
168 return mkdirat_parents_internal(dir_fd
, path
, mode
, UID_INVALID
, UID_INVALID
, 0, mkdirat_errno_wrapper
);
171 int mkdir_parents_safe(const char *prefix
, const char *path
, mode_t mode
, uid_t uid
, gid_t gid
, MkdirFlags flags
) {
172 return mkdir_parents_internal(prefix
, path
, mode
, uid
, gid
, flags
, mkdirat_errno_wrapper
);
175 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
) {
180 assert(_mkdirat
!= mkdirat
);
182 r
= mkdir_parents_internal(prefix
, path
, mode
, uid
, gid
, flags
| MKDIR_FOLLOW_SYMLINK
, _mkdirat
);
186 if (!uid_is_valid(uid
) && !gid_is_valid(gid
) && flags
== 0) {
187 r
= _mkdirat(AT_FDCWD
, path
, mode
);
188 if (r
< 0 && (r
!= -EEXIST
|| is_dir(path
, true) <= 0))
191 r
= mkdir_safe_internal(path
, mode
, uid
, gid
, flags
, _mkdirat
);
192 if (r
< 0 && r
!= -EEXIST
)
199 int mkdir_p(const char *path
, mode_t mode
) {
200 return mkdir_p_internal(NULL
, path
, mode
, UID_INVALID
, UID_INVALID
, 0, mkdirat_errno_wrapper
);
203 int mkdir_p_safe(const char *prefix
, const char *path
, mode_t mode
, uid_t uid
, gid_t gid
, MkdirFlags flags
) {
204 return mkdir_p_internal(prefix
, path
, mode
, uid
, gid
, flags
, mkdirat_errno_wrapper
);
207 int mkdir_p_root(const char *root
, const char *p
, uid_t uid
, gid_t gid
, mode_t m
, char **subvolumes
) {
208 _cleanup_free_
char *pp
= NULL
, *bn
= NULL
;
209 _cleanup_close_
int dfd
= -EBADF
;
212 r
= path_extract_directory(p
, &pp
);
213 if (r
== -EDESTADDRREQ
) {
214 /* only fname is passed, no prefix to operate on */
215 dfd
= open(".", O_RDONLY
|O_CLOEXEC
|O_DIRECTORY
);
218 } else if (r
== -EADDRNOTAVAIL
)
219 /* only root dir or "." was passed, i.e. there is no parent to extract, in that case there's nothing to do. */
224 /* Extracting the parent dir worked, hence we aren't top-level? Recurse up first. */
225 r
= mkdir_p_root(root
, pp
, uid
, gid
, m
, subvolumes
);
229 dfd
= chase_and_open(pp
, root
, CHASE_PREFIX_ROOT
, O_RDONLY
|O_CLOEXEC
|O_DIRECTORY
, NULL
);
234 r
= path_extract_filename(p
, &bn
);
235 if (r
== -EADDRNOTAVAIL
) /* Already top-level */
240 if (path_strv_contains(subvolumes
, p
))
241 r
= btrfs_subvol_make_fallback(dfd
, bn
, m
);
243 r
= RET_NERRNO(mkdirat(dfd
, bn
, m
));
251 if (uid_is_valid(uid
) || gid_is_valid(gid
)) {
252 _cleanup_close_
int nfd
= -EBADF
;
254 nfd
= openat(dfd
, bn
, O_RDONLY
|O_CLOEXEC
|O_DIRECTORY
|O_NOFOLLOW
);
258 if (fchown(nfd
, uid
, gid
) < 0)