Skip to content

Commit 3510cef

Browse files
authored
Merge pull request systemd#21401 from poettering/open-mkdir-at
add open_mkdir_at() helper and use it
2 parents e92777d + 96603ea commit 3510cef

File tree

7 files changed

+144
-27
lines changed

7 files changed

+144
-27
lines changed

src/basic/fs-util.c

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,3 +1011,77 @@ int parse_cifs_service(
10111011

10121012
return 0;
10131013
}
1014+
1015+
int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) {
1016+
_cleanup_close_ int fd = -1, parent_fd = -1;
1017+
_cleanup_free_ char *fname = NULL;
1018+
bool made;
1019+
int r;
1020+
1021+
/* Creates a directory with mkdirat() and then opens it, in the "most atomic" fashion we can
1022+
* do. Guarantees that the returned fd refers to a directory. If O_EXCL is specified will fail if the
1023+
* dir already exists. Otherwise will open an existing dir, but only if it is one. */
1024+
1025+
if (flags & ~(O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_EXCL|O_NOATIME|O_NOFOLLOW|O_PATH))
1026+
return -EINVAL;
1027+
if ((flags & O_ACCMODE) != O_RDONLY)
1028+
return -EINVAL;
1029+
1030+
/* Note that O_DIRECTORY|O_NOFOLLOW is implied, but we allow specifying it anyway. The following
1031+
* flags actually make sense to specify: O_CLOEXEC, O_EXCL, O_NOATIME, O_PATH */
1032+
1033+
if (isempty(path))
1034+
return -EINVAL;
1035+
1036+
if (!filename_is_valid(path)) {
1037+
_cleanup_free_ char *parent = NULL;
1038+
1039+
/* If this is not a valid filename, it's a path. Let's open the parent directory then, so
1040+
* that we can pin it, and operate below it. */
1041+
1042+
r = path_extract_directory(path, &parent);
1043+
if (r < 0)
1044+
return r;
1045+
1046+
r = path_extract_filename(path, &fname);
1047+
if (r < 0)
1048+
return r;
1049+
1050+
parent_fd = openat(dirfd, parent, O_PATH|O_DIRECTORY|O_CLOEXEC);
1051+
if (parent_fd < 0)
1052+
return -errno;
1053+
1054+
dirfd = parent_fd;
1055+
path = fname;
1056+
}
1057+
1058+
r = RET_NERRNO(mkdirat(dirfd, path, mode));
1059+
if (r == -EEXIST) {
1060+
if (FLAGS_SET(flags, O_EXCL))
1061+
return -EEXIST;
1062+
1063+
made = false;
1064+
} else if (r < 0)
1065+
return r;
1066+
else
1067+
made = true;
1068+
1069+
fd = RET_NERRNO(openat(dirfd, path, (flags & ~O_EXCL)|O_DIRECTORY|O_NOFOLLOW));
1070+
if (fd < 0) {
1071+
if (fd == -ENOENT) /* We got ENOENT? then someone else immediately removed it after we
1072+
* created it. In that case let's return immediately without unlinking
1073+
* anything, because there simply isn't anything to unlink anymore. */
1074+
return -ENOENT;
1075+
if (fd == -ELOOP) /* is a symlink? exists already → created by someone else, don't unlink */
1076+
return -EEXIST;
1077+
if (fd == -ENOTDIR) /* not a directory? exists already → created by someone else, don't unlink */
1078+
return -EEXIST;
1079+
1080+
if (made)
1081+
(void) unlinkat(dirfd, path, AT_REMOVEDIR);
1082+
1083+
return fd;
1084+
}
1085+
1086+
return TAKE_FD(fd);
1087+
}

src/basic/fs-util.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,5 @@ static inline int conservative_rename(const char *oldpath, const char *newpath)
108108
int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size);
109109

110110
int parse_cifs_service(const char *s, char **ret_host, char **ret_service, char **ret_path);
111+
112+
int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode);

src/core/namespace.c

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2499,6 +2499,7 @@ int temporary_filesystem_add(
24992499

25002500
static int make_tmp_prefix(const char *prefix) {
25012501
_cleanup_free_ char *t = NULL;
2502+
_cleanup_close_ int fd = -1;
25022503
int r;
25032504

25042505
/* Don't do anything unless we know the dir is actually missing */
@@ -2517,18 +2518,20 @@ static int make_tmp_prefix(const char *prefix) {
25172518
if (r < 0)
25182519
return r;
25192520

2520-
if (mkdir(t, 0777) < 0) /* umask will corrupt this access mode, but that doesn't matter, we need to
2521-
* call chmod() anyway for the suid bit, below. */
2522-
return -errno;
2521+
/* umask will corrupt this access mode, but that doesn't matter, we need to call chmod() anyway for
2522+
* the suid bit, below. */
2523+
fd = open_mkdir_at(AT_FDCWD, t, O_EXCL|O_CLOEXEC, 0777);
2524+
if (fd < 0)
2525+
return fd;
25232526

2524-
if (chmod(t, 01777) < 0) {
2525-
r = -errno;
2527+
r = RET_NERRNO(fchmod(fd, 01777));
2528+
if (r < 0) {
25262529
(void) rmdir(t);
25272530
return r;
25282531
}
25292532

2530-
if (rename(t, prefix) < 0) {
2531-
r = -errno;
2533+
r = RET_NERRNO(rename(t, prefix));
2534+
if (r < 0) {
25322535
(void) rmdir(t);
25332536
return r == -EEXIST ? 0 : r; /* it's fine if someone else created the dir by now */
25342537
}

src/home/homework-cifs.c

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,15 +127,17 @@ int home_setup_cifs(
127127
return log_oom();
128128

129129
if (FLAGS_SET(flags, HOME_SETUP_CIFS_MKDIR)) {
130-
r = mkdir_p(j, 0700);
131-
if (r < 0)
132-
return log_error_errno(r, "Failed to create CIFS subdirectory: %m");
130+
setup->root_fd = open_mkdir_at(AT_FDCWD, j, O_CLOEXEC, 0700);
131+
if (setup->root_fd < 0)
132+
return log_error_errno(setup->root_fd, "Failed to create CIFS subdirectory: %m");
133133
}
134134
}
135135

136-
setup->root_fd = open(j ?: HOME_RUNTIME_WORK_DIR, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
137-
if (setup->root_fd < 0)
138-
return log_error_errno(errno, "Failed to open home directory: %m");
136+
if (setup->root_fd < 0) {
137+
setup->root_fd = open(j ?: HOME_RUNTIME_WORK_DIR, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
138+
if (setup->root_fd < 0)
139+
return log_error_errno(errno, "Failed to open home directory: %m");
140+
}
139141

140142
setup->mount_suffix = TAKE_PTR(cdir);
141143
return 0;

src/shared/copy.c

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -485,8 +485,6 @@ static int hardlink_context_setup(
485485
}
486486

487487
static int hardlink_context_realize(HardlinkContext *c) {
488-
int r;
489-
490488
if (!c)
491489
return 0;
492490

@@ -498,15 +496,9 @@ static int hardlink_context_realize(HardlinkContext *c) {
498496

499497
assert(c->subdir);
500498

501-
if (mkdirat(c->parent_fd, c->subdir, 0700) < 0)
502-
return -errno;
503-
504-
c->dir_fd = openat(c->parent_fd, c->subdir, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
505-
if (c->dir_fd < 0) {
506-
r = -errno;
507-
(void) unlinkat(c->parent_fd, c->subdir, AT_REMOVEDIR);
508-
return r;
509-
}
499+
c->dir_fd = open_mkdir_at(c->parent_fd, c->subdir, O_EXCL|O_CLOEXEC, 0700);
500+
if (c->dir_fd < 0)
501+
return c->dir_fd;
510502

511503
return 1;
512504
}

src/shared/creds-util.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,10 @@ int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *
215215
fn = "credential.secret";
216216
}
217217

218-
(void) mkdir_p(p, 0755);
219-
dfd = open(p, O_CLOEXEC|O_DIRECTORY|O_RDONLY);
218+
mkdir_parents(p, 0755);
219+
dfd = open_mkdir_at(AT_FDCWD, p, O_CLOEXEC, 0755);
220220
if (dfd < 0)
221-
return -errno;
221+
return dfd;
222222

223223
if (FLAGS_SET(flags, CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS)) {
224224
r = fd_is_temporary_fs(dfd);

src/test/test-fs-util.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,49 @@ static void test_parse_cifs_service(void) {
952952
test_parse_cifs_service_one("//./a", NULL, NULL, NULL, -EINVAL);
953953
}
954954

955+
static void test_open_mkdir_at(void) {
956+
_cleanup_close_ int fd = -1, subdir_fd = -1, subsubdir_fd = -1;
957+
_cleanup_(rm_rf_physical_and_freep) char *t = NULL;
958+
log_info("/* %s */", __func__);
959+
960+
assert_se(open_mkdir_at(AT_FDCWD, "/proc", O_EXCL|O_CLOEXEC, 0) == -EEXIST);
961+
962+
fd = open_mkdir_at(AT_FDCWD, "/proc", O_CLOEXEC, 0);
963+
assert_se(fd >= 0);
964+
fd = safe_close(fd);
965+
966+
assert_se(open_mkdir_at(AT_FDCWD, "/bin/sh", O_EXCL|O_CLOEXEC, 0) == -EEXIST);
967+
assert_se(open_mkdir_at(AT_FDCWD, "/bin/sh", O_CLOEXEC, 0) == -EEXIST);
968+
969+
assert_se(mkdtemp_malloc(NULL, &t) >= 0);
970+
971+
assert_se(open_mkdir_at(AT_FDCWD, t, O_EXCL|O_CLOEXEC, 0) == -EEXIST);
972+
assert_se(open_mkdir_at(AT_FDCWD, t, O_PATH|O_EXCL|O_CLOEXEC, 0) == -EEXIST);
973+
974+
fd = open_mkdir_at(AT_FDCWD, t, O_CLOEXEC, 0000);
975+
assert_se(fd >= 0);
976+
fd = safe_close(fd);
977+
978+
fd = open_mkdir_at(AT_FDCWD, t, O_PATH|O_CLOEXEC, 0000);
979+
assert_se(fd >= 0);
980+
981+
subdir_fd = open_mkdir_at(fd, "xxx", O_PATH|O_EXCL|O_CLOEXEC, 0700);
982+
assert_se(subdir_fd >= 0);
983+
984+
assert_se(open_mkdir_at(fd, "xxx", O_PATH|O_EXCL|O_CLOEXEC, 0) == -EEXIST);
985+
986+
subsubdir_fd = open_mkdir_at(subdir_fd, "yyy", O_EXCL|O_CLOEXEC, 0700);
987+
assert_se(subsubdir_fd >= 0);
988+
subsubdir_fd = safe_close(subsubdir_fd);
989+
990+
assert_se(open_mkdir_at(subdir_fd, "yyy", O_EXCL|O_CLOEXEC, 0) == -EEXIST);
991+
992+
assert_se(open_mkdir_at(fd, "xxx/yyy", O_EXCL|O_CLOEXEC, 0) == -EEXIST);
993+
994+
subsubdir_fd = open_mkdir_at(fd, "xxx/yyy", O_CLOEXEC, 0700);
995+
assert_se(subsubdir_fd >= 0);
996+
}
997+
955998
int main(int argc, char *argv[]) {
956999
test_setup_logging(LOG_INFO);
9571000

@@ -972,6 +1015,7 @@ int main(int argc, char *argv[]) {
9721015
test_conservative_rename();
9731016
test_rmdir_parents();
9741017
test_parse_cifs_service();
1018+
test_open_mkdir_at();
9751019

9761020
return 0;
9771021
}

0 commit comments

Comments
 (0)