From bf8b1623c84f67e4f0c6a88cbd5bcd64b7180561 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Fri, 29 Jan 2021 19:49:34 -0800 Subject: [PATCH] Normalize mkdir() error codes --- libc/calls/internal.h | 1 + libc/calls/mkdir-nt.c | 49 +++++++++++++++++++++++ libc/calls/mkdir.c | 19 ++++----- libc/calls/mkdirat.c | 23 +++++++++-- libc/sysv/consts.sh | 4 +- libc/x/makedirs.c | 47 ++++++++++++++++++++++ libc/x/rmrf.c | 6 ++- libc/x/x.h | 1 + test/libc/calls/commandv_test.c | 10 ++--- test/libc/calls/mkdir_test.c | 70 +++++++++++++++++++++++++++++++++ test/libc/fmt/dirname_test.c | 1 + 11 files changed, 206 insertions(+), 25 deletions(-) create mode 100644 libc/calls/mkdir-nt.c create mode 100644 libc/x/makedirs.c create mode 100644 test/libc/calls/mkdir_test.c diff --git a/libc/calls/internal.h b/libc/calls/internal.h index d3c8ea7f1..7fe639d12 100644 --- a/libc/calls/internal.h +++ b/libc/calls/internal.h @@ -243,6 +243,7 @@ int kill$nt(int, int) hidden; int link$nt(const char *, const char *) hidden; int lstat$nt(const char *, struct stat *) hidden; int madvise$nt(void *, size_t, int) hidden; +int mkdir$nt(const char *, uint32_t) hidden; int msync$nt(void *, size_t, int) hidden; int nanosleep$nt(const struct timespec *, struct timespec *) hidden; int pipe$nt(int[hasatleast 2], unsigned) hidden; diff --git a/libc/calls/mkdir-nt.c b/libc/calls/mkdir-nt.c new file mode 100644 index 000000000..73fd2d13e --- /dev/null +++ b/libc/calls/mkdir-nt.c @@ -0,0 +1,49 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/internal.h" +#include "libc/errno.h" +#include "libc/nt/enum/fileflagandattributes.h" +#include "libc/nt/files.h" +#include "libc/nt/runtime.h" +#include "libc/str/str.h" + +static textwindows bool SubpathExistsThatsNotDirectory(char16_t *path) { + char16_t *p; + uint32_t attrs; + while ((p = strrchr16(path, '\\'))) { + *p = u'\0'; + if ((attrs = GetFileAttributes(path)) != -1u) { + if (attrs & kNtFileAttributeDirectory) return false; + return true; + } + } + return false; +} + +textwindows int mkdir$nt(const char *path, uint32_t mode) { + int e; + char16_t *p, path16[PATH_MAX]; + if (__mkntpath(path, path16) == -1) return -1; + if (CreateDirectory(path16, NULL)) return 0; + e = GetLastError(); + /* WIN32 doesn't distinguish between ENOTDIR and ENOENT */ + if (e == ENOTDIR && !SubpathExistsThatsNotDirectory(path16)) e = ENOENT; + errno = e; + return -1; +} diff --git a/libc/calls/mkdir.c b/libc/calls/mkdir.c index 5fc46a3ce..d07c5af59 100644 --- a/libc/calls/mkdir.c +++ b/libc/calls/mkdir.c @@ -24,26 +24,23 @@ #include "libc/sysv/consts/at.h" #include "libc/sysv/errfuns.h" -static textwindows noinline int mkdir$nt(const char *path, uint32_t mode) { - uint16_t path16[PATH_MAX]; - if (__mkntpath(path, path16) == -1) return -1; - if (CreateDirectory(path16, NULL)) { - return 0; - } else { - return __winerr(); - } -} - /** * Creates directory a.k.a. folder. * + * mkdir o → 0 + * mkdir o/yo/yo/yo → -1 w/ ENOENT + * if o/yo is file → -1 w/ ENOTDIR + * if o/yo/yo/yo is dir → -1 w/ EEXIST + * if o/yo/yo/yo is file → -1 w/ EEXIST + * * @param path is a UTF-8 string, preferably relative w/ forward slashes * @param mode can be, for example, 0755 * @return 0 on success or -1 w/ errno * @error EEXIST, ENOTDIR, ENAMETOOLONG, EACCES * @asyncsignalsafe + * @see makedirs() */ -int mkdir(const char *path, uint32_t mode) { +int mkdir(const char *path, unsigned mode) { if (!path) return efault(); if (!IsWindows()) { return mkdirat$sysv(AT_FDCWD, path, mode); diff --git a/libc/calls/mkdirat.c b/libc/calls/mkdirat.c index d2be639eb..9af53ecc7 100644 --- a/libc/calls/mkdirat.c +++ b/libc/calls/mkdirat.c @@ -19,11 +19,26 @@ #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/sysv/consts/at.h" +#include "libc/sysv/errfuns.h" -int mkdirat(int dirfd, const char *pathname, unsigned mode) { - if (dirfd == AT_FDCWD) { - return mkdir(pathname, mode); +/** + * Creates directory a.k.a. folder. + * + * @param dirfd is normally AT_FDCWD + * @param path is a UTF-8 string, preferably relative w/ forward slashes + * @param mode can be, for example, 0755 + * @return 0 on success or -1 w/ errno + * @error EEXIST, ENOTDIR, ENAMETOOLONG, EACCES, ENOENT + * @asyncsignalsafe + * @see makedirs() + */ +int mkdirat(int dirfd, const char *path, unsigned mode) { + if (!path) return efault(); + if (!IsWindows()) { + return mkdirat$sysv(dirfd, path, mode); + } else if (dirfd == AT_FDCWD) { + return mkdir$nt(path, mode); } else { - return mkdirat$sysv(dirfd, pathname, mode); + return einval(); } } diff --git a/libc/sysv/consts.sh b/libc/sysv/consts.sh index 37ce65cc7..1acb9694d 100755 --- a/libc/sysv/consts.sh +++ b/libc/sysv/consts.sh @@ -25,7 +25,7 @@ dir=libc/sysv/consts # group name GNU/Systemd XNU's Not UNIX FreeBSD OpenBSD XENIX Commentary syscon errno ENOSYS 38 78 78 78 1 # bsd consensus & kNtErrorInvalidFunction syscon errno EPERM 1 1 1 1 12 # unix consensus & kNtErrorInvalidAccess (should be kNtErrorNotOwner but is that mutex only??) -syscon errno ENOENT 2 2 2 2 2 # unix consensus & kNtErrorFileNot_FOUND +syscon errno ENOENT 2 2 2 2 2 # unix consensus & kNtErrorFileNotFound syscon errno ESRCH 3 3 3 3 566 # "no such process" & kNtErrorThreadNotInProcess (cf. kNtErrorInvalidHandle) syscon errno EINTR 4 4 4 4 10004 # unix consensus & WSAEINTR syscon errno EIO 5 5 5 5 1117 # unix consensus & kNtErrorIoDevice @@ -44,7 +44,7 @@ syscon errno EBUSY 16 16 16 16 170 # unix consensus & kNtErrorBusy syscon errno EEXIST 17 17 17 17 183 # unix consensus & kNtErrorAlreadyExists (should be kNtErrorFileExists too) syscon errno EXDEV 18 18 18 18 17 # unix consensus & kNtErrorNotSameDevice syscon errno ENODEV 19 19 19 19 1200 # unix consensus & kNtErrorBadDevice -syscon errno ENOTDIR 20 20 20 20 3 # unix consensus & kNtErrorPathNotFound (TODO) +syscon errno ENOTDIR 20 20 20 20 3 # unix consensus & kNtErrorPathNotFound syscon errno EISDIR 21 21 21 21 267 # unix consensus & kNtErrorDirectoryNotSupported syscon errno EINVAL 22 22 22 22 87 # unix consensus & kNtErrorInvalidParameter syscon errno ENFILE 23 23 23 23 331 # unix consensus & kNtErrorTooManyDescriptors diff --git a/libc/x/makedirs.c b/libc/x/makedirs.c new file mode 100644 index 000000000..8a116423e --- /dev/null +++ b/libc/x/makedirs.c @@ -0,0 +1,47 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/errno.h" +#include "libc/mem/mem.h" +#include "libc/str/str.h" +#include "libc/x/x.h" + +/** + * Recursively creates directory a.k.a. folder. + * + * @param path is a UTF-8 string, preferably relative w/ forward slashes + * @param mode can be, for example, 0755 + * @return 0 on success or -1 w/ errno + * @see mkdir() + */ +int makedirs(const char *path, unsigned mode) { + int rc; + char *dir; + if (mkdir(path, mode) != -1) return 0; + if (errno != ENOENT) return -1; + dir = xdirname(path); + if (strcmp(dir, path)) { + rc = makedirs(dir, mode); + } else { + rc = -1; + } + free(dir); + if (rc == -1) return -1; + return mkdir(path, mode); +} diff --git a/libc/x/rmrf.c b/libc/x/rmrf.c index a74bd58ba..849f88f44 100644 --- a/libc/x/rmrf.c +++ b/libc/x/rmrf.c @@ -32,7 +32,7 @@ static int rmrfdir(const char *dirpath) { char *path; struct dirent *e; if (!(d = opendir(dirpath))) return -1; - for (rc = 0; (e = readdir(d));) { + while ((e = readdir(d))) { if (!strcmp(e->d_name, ".")) continue; if (!strcmp(e->d_name, "..")) continue; if (strchr(e->d_name, '/')) abort(); @@ -44,7 +44,9 @@ static int rmrfdir(const char *dirpath) { } free(path); } - return closedir(d); + rc = closedir(d); + rc |= rmdir(dirpath); + return rc; } /** diff --git a/libc/x/x.h b/libc/x/x.h index bf5ac600a..289a0536d 100644 --- a/libc/x/x.h +++ b/libc/x/x.h @@ -52,6 +52,7 @@ char *xinet_ntop(int, const void *) _XPNN _XMAL; ╚────────────────────────────────────────────────────────────────────────────│*/ int rmrf(const char *); +int makedirs(const char *, unsigned); char *xdirname(const char *) paramsnonnull() _XMAL; char *xjoinpaths(const char *, const char *) paramsnonnull() _XMAL; diff --git a/test/libc/calls/commandv_test.c b/test/libc/calls/commandv_test.c index e355a5319..b25256767 100644 --- a/test/libc/calls/commandv_test.c +++ b/test/libc/calls/commandv_test.c @@ -21,6 +21,7 @@ #include "libc/calls/struct/dirent.h" #include "libc/calls/struct/stat.h" #include "libc/dce.h" +#include "libc/fmt/fmt.h" #include "libc/log/check.h" #include "libc/mem/mem.h" #include "libc/runtime/gc.h" @@ -33,14 +34,11 @@ #include "libc/x/x.h" uint64_t i; -char pathbuf[PATH_MAX]; -const char *testdir, *oldpath; +char pathbuf[PATH_MAX], testdir[PATH_MAX], *oldpath; void SetUp(void) { - mkdir("o", 0755); - mkdir("o/tmp", 0755); - testdir = xasprintf("o/tmp/%s.%d", program_invocation_short_name, getpid()); - mkdir(testdir, 0755); + sprintf(testdir, "o/tmp/%s.%d", program_invocation_short_name, getpid()); + makedirs(testdir, 0755); CHECK_NE(-1, chdir(testdir)); mkdir("bin", 0755); mkdir("home", 0755); diff --git a/test/libc/calls/mkdir_test.c b/test/libc/calls/mkdir_test.c new file mode 100644 index 000000000..9becf356f --- /dev/null +++ b/test/libc/calls/mkdir_test.c @@ -0,0 +1,70 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2021 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/errno.h" +#include "libc/fmt/fmt.h" +#include "libc/log/check.h" +#include "libc/runtime/runtime.h" +#include "libc/testlib/testlib.h" +#include "libc/x/x.h" + +char testdir[PATH_MAX]; + +void SetUp(void) { + sprintf(testdir, "o/tmp/%s.%d", program_invocation_short_name, getpid()); + makedirs(testdir, 0755); + CHECK_NE(-1, chdir(testdir)); +} + +void TearDown(void) { + CHECK_NE(-1, chdir("../../..")); + CHECK_NE(-1, rmrf(testdir)); +} + +TEST(mkdir, testNothingExists_ENOENT) { + EXPECT_EQ(-1, mkdir("yo/yo/yo", 0755)); + EXPECT_EQ(ENOENT, errno); +} + +TEST(mkdir, testDirectoryComponentIsFile_ENOTDIR) { + EXPECT_NE(-1, touch("yo", 0644)); + EXPECT_EQ(-1, mkdir("yo/yo/yo", 0755)); + EXPECT_EQ(ENOTDIR, errno); +} + +TEST(mkdir, testPathIsFile_EEXIST) { + EXPECT_NE(-1, mkdir("yo", 0755)); + EXPECT_NE(-1, mkdir("yo/yo", 0755)); + EXPECT_NE(-1, touch("yo/yo/yo", 0644)); + EXPECT_EQ(-1, mkdir("yo/yo/yo", 0755)); + EXPECT_EQ(EEXIST, errno); +} + +TEST(mkdir, testPathIsDirectory_EEXIST) { + EXPECT_NE(-1, mkdir("yo", 0755)); + EXPECT_NE(-1, mkdir("yo/yo", 0755)); + EXPECT_NE(-1, mkdir("yo/yo/yo", 0755)); + EXPECT_EQ(-1, mkdir("yo/yo/yo", 0755)); + EXPECT_EQ(EEXIST, errno); +} + +TEST(makedirs, testEmptyString_ENOENT) { + EXPECT_EQ(-1, makedirs("", 0755)); + EXPECT_EQ(ENOENT, errno); +} diff --git a/test/libc/fmt/dirname_test.c b/test/libc/fmt/dirname_test.c index 32485d7b9..8e39bd175 100644 --- a/test/libc/fmt/dirname_test.c +++ b/test/libc/fmt/dirname_test.c @@ -30,4 +30,5 @@ TEST(dirname, test) { EXPECT_STREQ(".", dirname(gc(strdup("hello")))); EXPECT_STREQ(".", dirname(gc(strdup(".")))); EXPECT_STREQ(".", dirname(gc(strdup("..")))); + EXPECT_STREQ("", dirname(gc(strdup("")))); }