From b3fb6cff43c7cdc6a82ebcc9191db3ba296cbb16 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Sat, 6 Jan 2024 18:05:14 -0800 Subject: [PATCH] Add /dev/fd support to Windows GNU bash needs this functionality, otherwise it can't do <(cmd...). --- libc/calls/fstatat-nt.c | 18 ++++++++++ libc/calls/open-nt.c | 20 ++++++++++- test/libc/calls/devfd_test.c | 66 +++++++++++++++++++++++++++++++++++ third_party/bash/README.cosmo | 1 + third_party/bash/config.h | 4 +-- third_party/bash/eaccess.c | 10 ++++-- 6 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 test/libc/calls/devfd_test.c diff --git a/libc/calls/fstatat-nt.c b/libc/calls/fstatat-nt.c index 32c361702..821b07e6e 100644 --- a/libc/calls/fstatat-nt.c +++ b/libc/calls/fstatat-nt.c @@ -33,11 +33,27 @@ #include "libc/sysv/consts/fileno.h" #include "libc/sysv/errfuns.h" +static int Atoi(const char *str) { + int c; + unsigned x = 0; + if (!*str) return -1; + while ((c = *str++)) { + if ('0' <= c && c <= '9') { + x *= 10; + x += c - '0'; + } else { + return -1; + } + } + return x; +} + textwindows int sys_fstatat_nt(int dirfd, const char *path, struct stat *st, int flags) { // handle special files if (startswith(path, "/dev/")) { + int fd; if (!strcmp(path + 5, "tty")) { return sys_fstat_nt_special(kFdConsole, st); } else if (!strcmp(path + 5, "null")) { @@ -48,6 +64,8 @@ textwindows int sys_fstatat_nt(int dirfd, const char *path, struct stat *st, return sys_fstat_nt(STDOUT_FILENO, st); } else if (!strcmp(path + 5, "stderr")) { return sys_fstat_nt(STDERR_FILENO, st); + } else if (startswith(path + 5, "fd/") && (fd = Atoi(path + 8)) != -1) { + return sys_fstat_nt(fd, st); } else { return enoent(); } diff --git a/libc/calls/open-nt.c b/libc/calls/open-nt.c index 10e84212a..50b7954ba 100644 --- a/libc/calls/open-nt.c +++ b/libc/calls/open-nt.c @@ -181,10 +181,25 @@ static textwindows int sys_open_nt_dup(int fd, int flags, int mode, int oldfd) { } } +static int Atoi(const char *str) { + int c; + unsigned x = 0; + if (!*str) return -1; + while ((c = *str++)) { + if ('0' <= c && c <= '9') { + x *= 10; + x += c - '0'; + } else { + return -1; + } + } + return x; +} + textwindows int sys_open_nt(int dirfd, const char *file, uint32_t flags, int32_t mode) { - int fd; ssize_t rc; + int fd, oldfd; BLOCK_SIGNALS; __fds_lock(); if (!(flags & _O_CREAT)) mode = 0; @@ -200,6 +215,9 @@ textwindows int sys_open_nt(int dirfd, const char *file, uint32_t flags, rc = sys_open_nt_dup(fd, flags, mode, STDOUT_FILENO); } else if (!strcmp(file + 5, "stderr")) { rc = sys_open_nt_dup(fd, flags, mode, STDERR_FILENO); + } else if (startswith(file + 5, "fd/") && + (oldfd = Atoi(file + 8)) != -1) { + rc = sys_open_nt_dup(fd, flags, mode, oldfd); } else { rc = enoent(); } diff --git a/test/libc/calls/devfd_test.c b/test/libc/calls/devfd_test.c new file mode 100644 index 000000000..68248c0b3 --- /dev/null +++ b/test/libc/calls/devfd_test.c @@ -0,0 +1,66 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2024 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/calls/struct/stat.h" +#include "libc/dce.h" +#include "libc/intrin/strace.internal.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/o.h" +#include "libc/testlib/testlib.h" +#include "libc/x/x.h" + +void SetUpOnce(void) { + testlib_enable_tmp_setup_teardown(); +} + +TEST(devfd, test) { + // TODO: What is up with this mysterious ENOENT error? + // The code appears like it should support this. + if (IsFreebsd()) return; + char buf[8] = {0}; + struct stat st[2] = {0}; + ASSERT_SYS(0, 0, xbarf("hello.txt", "bone", -1)); + ASSERT_SYS(0, 3, open("hello.txt", O_RDONLY)); + ASSERT_SYS(0, 4, open("/dev/fd/3", O_RDONLY)); + ASSERT_SYS(0, 4, read(4, buf, 7)); + ASSERT_STREQ("bone", buf); + ASSERT_SYS(0, 0, fstat(3, st)); + ASSERT_SYS(0, 0, fstat(4, st + 1)); + ASSERT_EQ(0, memcmp(st, st + 1, sizeof(struct stat))); + ASSERT_SYS(0, 0, close(4)); + ASSERT_SYS(0, 0, close(3)); +} + +TEST(devfd, not_DEV_FD_STAT_BROKEN) { + // fstat() and stat() are inconsistent on bsd systems + // with xnu it only appears to be st_dev that differs + if (IsBsd()) return; + char buf[8] = {0}; + struct stat st[2] = {0}; + ASSERT_SYS(0, 0, xbarf("hello.txt", "bone", -1)); + ASSERT_SYS(0, 3, open("hello.txt", O_RDONLY)); + ASSERT_SYS(0, 4, open("/dev/fd/3", O_RDONLY)); + ASSERT_SYS(0, 4, read(4, buf, 7)); + ASSERT_STREQ("bone", buf); + ASSERT_SYS(0, 0, fstat(3, st)); + ASSERT_SYS(0, 0, stat("/dev/fd/3", st + 1)); + ASSERT_EQ(0, memcmp(st, st + 1, sizeof(struct stat))); + ASSERT_SYS(0, 0, close(4)); + ASSERT_SYS(0, 0, close(3)); +} diff --git a/third_party/bash/README.cosmo b/third_party/bash/README.cosmo index cfee2c748..f56894f1b 100644 --- a/third_party/bash/README.cosmo +++ b/third_party/bash/README.cosmo @@ -13,3 +13,4 @@ ORIGIN LOCAL CHANGES - Force disable mkfifo() code + - Added runtime check for broken BSD /dev/fd stuff diff --git a/third_party/bash/config.h b/third_party/bash/config.h index ac1cacfaa..caeab0fa6 100644 --- a/third_party/bash/config.h +++ b/third_party/bash/config.h @@ -542,7 +542,7 @@ #define HAVE_HASH_BANG_EXEC 1 /* Define if you have the /dev/fd devices to map open files into the file system. */ -/* #define HAVE_DEV_FD 1 */ +#define HAVE_DEV_FD 1 /* Defined to /dev/fd or /proc/self/fd (linux). */ #define DEV_FD_PREFIX "/dev/fd/" @@ -1160,7 +1160,7 @@ /* #undef GETCWD_BROKEN */ -/* #undef DEV_FD_STAT_BROKEN */ +#define DEV_FD_STAT_BROKEN /* An array implementation that prioritizes speed (O(1) access) over space, in array2.c */ diff --git a/third_party/bash/eaccess.c b/third_party/bash/eaccess.c index 106062018..25a114cb2 100644 --- a/third_party/bash/eaccess.c +++ b/third_party/bash/eaccess.c @@ -1,3 +1,4 @@ +#include "libc/dce.h" /* eaccess.c - eaccess replacement for the shell, plus other access functions. */ /* Copyright (C) 2006-2020 Free Software Foundation, Inc. @@ -93,7 +94,8 @@ sh_stat (path, finfo) { /* If stating /dev/fd/n doesn't produce the same results as fstat of FD N, then define DEV_FD_STAT_BROKEN */ -#if !defined (HAVE_DEV_FD) || defined (DEV_FD_STAT_BROKEN) +//#if !defined (HAVE_DEV_FD) || defined (DEV_FD_STAT_BROKEN) +if (IsBsd()) {//[jart] intmax_t fd; int r; @@ -105,7 +107,8 @@ sh_stat (path, finfo) } errno = ENOENT; return (-1); -#else +//#else +} else {//[jart] /* If HAVE_DEV_FD is defined, DEV_FD_PREFIX is defined also, and has a trailing slash. Make sure /dev/fd/xx really uses DEV_FD_PREFIX/xx. On most systems, with the notable exception of linux, this is @@ -114,7 +117,8 @@ sh_stat (path, finfo) strcpy (pbuf, DEV_FD_PREFIX); strcat (pbuf, path + 8); return (stat (pbuf, finfo)); -#endif /* !HAVE_DEV_FD */ +//#endif /* !HAVE_DEV_FD */ +}//[jart] } #if !defined (HAVE_DEV_STDIN) else if (STREQN (path, "/dev/std", 8))