From ccbae7799e51f3e39764be460fe94cca88b4ab53 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Sun, 2 Oct 2022 22:14:33 -0700 Subject: [PATCH] Improve docs of more system calls This change also found a few POSIX compliance bugs with errnos. Another bug was discovered where, on Windows, pread() and pwrite() could modify the file position in cases where ReadFile() returned an error e.g. when seeking past the end of file. We also have more tests! --- libc/calls/asan.internal.h | 7 +- libc/calls/chroot.c | 30 ++++++- libc/calls/clock_getres.c | 8 +- libc/calls/clock_gettime.c | 24 +++++- libc/calls/ftruncate-nt.c | 13 ++- libc/calls/ftruncate.c | 50 ++++++++++-- libc/calls/futimens.c | 30 +++++-- libc/{time => calls}/futimes.c | 3 +- libc/calls/getppid.c | 15 +++- libc/calls/getuid.c | 27 ++++-- libc/calls/lseek.c | 46 ++++++++++- libc/calls/openat.c | 1 + libc/calls/pread.c | 10 ++- libc/calls/pwrite.c | 5 +- libc/calls/read-nt.c | 16 ++-- libc/calls/read.c | 4 + libc/calls/sigprocmask.c | 3 +- libc/calls/sigsuspend.c | 3 +- libc/calls/struct/timespec.internal.h | 1 + libc/calls/truncate-nt.c | 1 - libc/calls/truncate.c | 54 ++++++++++-- libc/{stdio/systemexec.c => calls/utimens.c} | 43 ++++++++-- libc/calls/utimensat.c | 67 +++++++-------- libc/calls/utimes.c | 55 +++++++++---- libc/calls/wait4-nt.c | 5 +- libc/calls/write-nt.c | 17 ++-- libc/calls/write.c | 4 + libc/intrin/getpid.c | 10 ++- libc/runtime/fork-nt.c | 5 ++ libc/runtime/fork.c | 10 ++- libc/runtime/vfork.S | 8 +- libc/stdio/popen.c | 6 +- libc/stdio/stdio.h | 3 +- libc/stdio/system.c | 4 +- libc/sysv/dos2errno.sh | 1 + libc/sysv/dos2errno/EINVAL.S | 1 + libc/testlib/testrunner.c | 6 +- test/libc/calls/ftruncate_test.c | 82 ++++++++++++++++--- test/libc/calls/lseek_test.c | 86 ++++++++++++++++---- 39 files changed, 589 insertions(+), 175 deletions(-) rename libc/{time => calls}/futimes.c (95%) rename libc/{stdio/systemexec.c => calls/utimens.c} (56%) diff --git a/libc/calls/asan.internal.h b/libc/calls/asan.internal.h index 40a13ad22..b732683a9 100644 --- a/libc/calls/asan.internal.h +++ b/libc/calls/asan.internal.h @@ -1,7 +1,8 @@ #ifndef COSMOPOLITAN_LIBC_CALLS_ASAN_INTERNAL_H_ #define COSMOPOLITAN_LIBC_CALLS_ASAN_INTERNAL_H_ -#include "libc/intrin/asmflag.h" #include "libc/calls/struct/timespec.h" +#include "libc/calls/struct/timeval.h" +#include "libc/intrin/asmflag.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ @@ -14,6 +15,10 @@ forceinline bool __asan_is_valid_timespec(const struct timespec *ts) { return zf; } +forceinline bool __asan_is_valid_timeval(const struct timeval *tv) { + return __asan_is_valid_timespec((const struct timespec *)tv); +} + COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_CALLS_ASAN_INTERNAL_H_ */ diff --git a/libc/calls/chroot.c b/libc/calls/chroot.c index 5cadf7444..a5553c536 100644 --- a/libc/calls/chroot.c +++ b/libc/calls/chroot.c @@ -17,17 +17,43 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" -#include "libc/intrin/strace.internal.h" #include "libc/calls/syscall-sysv.internal.h" +#include "libc/dce.h" +#include "libc/intrin/asan.internal.h" +#include "libc/intrin/strace.internal.h" +#include "libc/sysv/errfuns.h" /** * Changes root directory. * + * Please consider using unveil() instead of chroot(). If you use this + * system call then consider using both chdir() and closefrom() before + * calling this function. Otherwise there's a small risk that fchdir() + * could be used to escape the chroot() environment. Cosmopolitan Libc + * focuses on static binaries which make chroot() infinitely easier to + * use since you don't need to construct an entire userspace each time + * however unveil() is still better to use on modern Linux and OpenBSD + * because it doesn't require root privileges. + * + * @param path shall become the new root directory + * @return 0 on success, or -1 w/ errno + * @raise EACCES if we don't have permission to search a component of `path` + * @raise ENOTDIR if a directory component in `path` exists as non-directory + * @raise ENAMETOOLONG if symlink-resolved `path` length exceeds `PATH_MAX` + * @raise ENAMETOOLONG if component in `path` exists longer than `NAME_MAX` + * @raise EPERM if not root or pledge() is in play + * @raise EIO if a low-level i/o error occurred + * @raise EFAULT if `path` is bad memory + * @raise ENOENT if `path` doesn't exist * @raise ENOSYS on Windows */ int chroot(const char *path) { int rc; - rc = sys_chroot(path); + if (!path || (IsAsan() && !__asan_is_valid(path, 1))) { + rc = efault(); + } else { + rc = sys_chroot(path); + } STRACE("chroot(%s) → %d% m", path, rc); return rc; } diff --git a/libc/calls/clock_getres.c b/libc/calls/clock_getres.c index 7d5c0a531..18fcdfd53 100644 --- a/libc/calls/clock_getres.c +++ b/libc/calls/clock_getres.c @@ -17,10 +17,10 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/asan.internal.h" -#include "libc/intrin/strace.internal.h" #include "libc/calls/struct/timespec.internal.h" #include "libc/dce.h" #include "libc/intrin/describeflags.internal.h" +#include "libc/intrin/strace.internal.h" #include "libc/sysv/consts/clock.h" #include "libc/sysv/errfuns.h" #include "libc/time/time.h" @@ -49,6 +49,12 @@ static int sys_clock_getres_xnu(int clock, struct timespec *ts) { /** * Returns granularity of clock. + * + * @return 0 on success, or -1 w/ errno + * @error EPERM if pledge() is in play without stdio promise + * @error EINVAL if `clock` isn't supported on this system + * @error EFAULT if `ts` points to bad memory + * @threadsafe */ int clock_getres(int clock, struct timespec *ts) { int rc; diff --git a/libc/calls/clock_gettime.c b/libc/calls/clock_gettime.c index b943e2d8a..5ab89d11f 100644 --- a/libc/calls/clock_gettime.c +++ b/libc/calls/clock_gettime.c @@ -20,7 +20,6 @@ #include "libc/calls/asan.internal.h" #include "libc/calls/clock_gettime.internal.h" #include "libc/calls/state.internal.h" -#include "libc/intrin/strace.internal.h" #include "libc/calls/struct/timespec.internal.h" #include "libc/calls/struct/timeval.h" #include "libc/calls/syscall_support-sysv.internal.h" @@ -30,6 +29,7 @@ #include "libc/intrin/asmflag.h" #include "libc/intrin/bits.h" #include "libc/intrin/describeflags.internal.h" +#include "libc/intrin/strace.internal.h" #include "libc/mem/alloca.h" #include "libc/nt/synchronization.h" #include "libc/sysv/errfuns.h" @@ -48,12 +48,30 @@ * __clock_gettime l: 35𝑐 11𝑛𝑠 * sys_clock_gettime l: 220𝑐 71𝑛𝑠 * - * @param clock can be CLOCK_REALTIME, CLOCK_MONOTONIC, etc. + * @param clock can be one of: + * - `CLOCK_REALTIME`: universally supported + * - `CLOCK_REALTIME_FAST`: ditto but faster on freebsd + * - `CLOCK_MONOTONIC`: universally supported + * - `CLOCK_MONOTONIC_FAST`: ditto but faster on freebsd + * - `CLOCK_MONOTONIC_RAW`: nearly universally supported + * - `CLOCK_PROCESS_CPUTIME_ID`: linux and bsd + * - `CLOCK_THREAD_CPUTIME_ID`: linux and bsd + * - `CLOCK_REALTIME_COARSE`: : linux and openbsd + * - `CLOCK_MONOTONIC_COARSE`: linux + * - `CLOCK_PROF`: linux and netbsd + * - `CLOCK_BOOTTIME`: linux and openbsd + * - `CLOCK_REALTIME_ALARM`: linux-only + * - `CLOCK_BOOTTIME_ALARM`: linux-only + * - `CLOCK_TAI`: linux-only * @param ts is where the result is stored * @return 0 on success, or -1 w/ errno - * @error EINVAL if clock isn't supported on this system + * @error EPERM if pledge() is in play without stdio promise + * @error EINVAL if `clock` isn't supported on this system + * @error EFAULT if `ts` points to bad memory * @see strftime(), gettimeofday() * @asyncsignalsafe + * @threadsafe + * @vforksafe */ int clock_gettime(int clock, struct timespec *ts) { int rc; diff --git a/libc/calls/ftruncate-nt.c b/libc/calls/ftruncate-nt.c index 46574efa2..17c1adbf2 100644 --- a/libc/calls/ftruncate-nt.c +++ b/libc/calls/ftruncate-nt.c @@ -16,9 +16,12 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" #include "libc/calls/syscall_support-nt.internal.h" #include "libc/nt/enum/filemovemethod.h" +#include "libc/nt/errors.h" #include "libc/nt/files.h" +#include "libc/nt/runtime.h" #include "libc/sysv/errfuns.h" textwindows int sys_ftruncate_nt(int64_t handle, uint64_t length) { @@ -28,7 +31,13 @@ textwindows int sys_ftruncate_nt(int64_t handle, uint64_t length) { if ((ok = SetFilePointerEx(handle, 0, &tell, kNtFileCurrent))) { ok = SetFilePointerEx(handle, length, NULL, kNtFileBegin) && SetEndOfFile(handle); - SetFilePointerEx(handle, tell, NULL, kNtFileBegin); + _npassert(SetFilePointerEx(handle, tell, NULL, kNtFileBegin)); + } + if (ok) { + return 0; + } else if (GetLastError() == kNtErrorAccessDenied) { + return einval(); // ftruncate() doesn't raise EACCES + } else { + return __winerr(); } - return ok ? 0 : __winerr(); } diff --git a/libc/calls/ftruncate.c b/libc/calls/ftruncate.c index 9c9747f5f..944540d37 100644 --- a/libc/calls/ftruncate.c +++ b/libc/calls/ftruncate.c @@ -18,31 +18,65 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/calls/internal.h" -#include "libc/intrin/strace.internal.h" #include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" +#include "libc/errno.h" +#include "libc/intrin/strace.internal.h" #include "libc/sysv/errfuns.h" /** - * Changes size of file. + * Changes size of open file. + * + * If the file size is increased, the extended area shall appear as if + * it were zero-filled. If your file size is decreased, the extra data + * shall be lost. + * + * This function never changes the file position. This is true even if + * ftruncate() causes the position to become beyond the end of file in + * which case, the rules described in the lseek() documentation apply. + * + * Some operating systems implement an optimization, where `length` is + * treated as a logical size and the requested physical space won't be + * allocated until non-zero values get written into it. Our tests show + * this happens on Linux (usually with 4096 byte granularity), FreeBSD + * (which favors 512-byte granularity), and MacOS (prefers 4096 bytes) + * however Windows, OpenBSD, and NetBSD always reserve physical space. + * This may be inspected using fstat() and consulting stat::st_blocks. * * @param fd must be open for writing - * @param length may be greater than current current file size, in which - * case System V guarantees it'll be zero'd but Windows NT doesn't; - * since the prior extends logically and the latter physically + * @param length may be greater than current current file size * @return 0 on success, or -1 w/ errno + * @raise EINVAL if `length` is negative + * @raise EINTR if signal was delivered instead + * @raise EIO if a low-level i/o error happened + * @raise EFBIG or EINVAL if `length` is too huge + * @raise ENOTSUP if `fd` is a zip file descriptor + * @raise EBADF if `fd` isn't an open file descriptor + * @raise EINVAL if `fd` is a non-file, e.g. pipe, socket + * @raise EINVAL if `fd` wasn't opened in a writeable mode + * @raise ENOSYS on bare metal + * @see truncate() * @asyncsignalsafe + * @threadsafe */ int ftruncate(int fd, int64_t length) { int rc; if (fd < 0) { - rc = einval(); + rc = ebadf(); + } else if (__isfdkind(fd, kFdZip)) { + rc = enotsup(); + } else if (IsMetal()) { + rc = enosys(); } else if (!IsWindows()) { rc = sys_ftruncate(fd, length, length); - } else { - if (fd >= g_fds.n) rc = ebadf(); + if (IsNetbsd() && rc == -1 && errno == ENOSPC) { + errno = EFBIG; // POSIX doesn't specify ENOSPC for ftruncate() + } + } else if (__isfdopen(fd)) { rc = sys_ftruncate_nt(g_fds.p[fd].handle, length); + } else { + rc = ebadf(); } STRACE("ftruncate(%d, %'ld) → %d% m", fd, length, rc); return rc; diff --git a/libc/calls/futimens.c b/libc/calls/futimens.c index 97dacd518..952f8a9b5 100644 --- a/libc/calls/futimens.c +++ b/libc/calls/futimens.c @@ -16,16 +16,36 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/internal.h" #include "libc/calls/struct/timespec.h" +#include "libc/calls/struct/timespec.internal.h" +#include "libc/intrin/strace.internal.h" +#include "libc/sysv/errfuns.h" /** - * Sets atime/mtime on file descriptor. + * Changes access/modified time on open file, the modern way. * - * This function is the same as `utimensat(fd, 0, ts, 0)`. + * XNU only has microsecond (1e-6) accuracy. Windows only has + * hectonanosecond (1e-7) accuracy. RHEL5 is somewhat broken so utimes() + * is recommended if portability to old versions of Linux is desired. * - * @param ts is atime/mtime, or null for current time - * @raise ENOSYS on RHEL5 + * @param fd is file descriptor of file whose timestamps will change + * @param ts is {access, modified} timestamps, or null for current time + * @return 0 on success, or -1 w/ errno + * @raise ENOTSUP if `fd` is on the zip filesystem + * @raise EINVAL if `flags` had an unrecognized value + * @raise EPERM if pledge() is in play without `fattr` promise + * @raise EINVAL if `ts` specifies a nanosecond value that's out of range + * @raise EBADF if `fd` isn't an open file descriptor + * @raise EFAULT if `ts` memory was invalid + * @raise ENOSYS on RHEL5 or bare metal + * @asyncsignalsafe + * @threadsafe */ int futimens(int fd, const struct timespec ts[2]) { - return utimensat(fd, 0, ts, 0); + int rc; + rc = __utimens(fd, 0, ts, 0); + STRACE("futimens(%d, {%s, %s}) → %d% m", fd, DescribeTimespec(0, ts), + DescribeTimespec(0, ts ? ts + 1 : 0), rc); + return rc; } diff --git a/libc/time/futimes.c b/libc/calls/futimes.c similarity index 95% rename from libc/time/futimes.c rename to libc/calls/futimes.c index 92c61c47e..6e2fd03db 100644 --- a/libc/time/futimes.c +++ b/libc/calls/futimes.c @@ -26,7 +26,8 @@ * @note better than microsecond precision on most platforms * @see fstat() for reading timestamps */ -int futimes(int fd, const struct timeval tv[hasatleast 2]) { +int futimes(int fd, const struct timeval tv[2]) { + // TODO(jart): does this work on rhel5? what's up with this? struct timespec ts[2]; if (tv) { ts[0].tv_sec = tv[0].tv_sec; diff --git a/libc/calls/getppid.c b/libc/calls/getppid.c index 0d409f0be..e5e1b0efc 100644 --- a/libc/calls/getppid.c +++ b/libc/calls/getppid.c @@ -16,18 +16,26 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/strace.internal.h" +#include "libc/assert.h" #include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" +#include "libc/intrin/strace.internal.h" /** * Returns parent process id. + * + * @return parent process id (always successful) + * @note slow on Windows; needs to iterate process tree * @asyncsignalsafe + * @threadsafe + * @vforksafe */ int getppid(void) { int rc; - if (!IsWindows()) { + if (IsMetal()) { + rc = 1; + } else if (!IsWindows()) { if (!IsNetbsd()) { rc = sys_getppid(); } else { @@ -36,6 +44,7 @@ int getppid(void) { } else { rc = sys_getppid_nt(); } - STRACE("getppid() → %d% m", rc); + _npassert(rc >= 0); + STRACE("%s() → %d", "getppid", rc); return rc; } diff --git a/libc/calls/getuid.c b/libc/calls/getuid.c index 08e074e74..3eb6ce978 100644 --- a/libc/calls/getuid.c +++ b/libc/calls/getuid.c @@ -16,10 +16,12 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" #include "libc/calls/calls.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" #include "libc/intrin/strace.internal.h" +#include "libc/limits.h" #include "libc/macros.internal.h" #include "libc/nt/accounting.h" #include "libc/runtime/runtime.h" @@ -38,7 +40,7 @@ static textwindows dontinline uint32_t GetUserNameHash(void) { char16_t buf[257]; uint32_t size = ARRAYLEN(buf); GetUserName(&buf, &size); - return KnuthMultiplicativeHash32(buf, size >> 1); + return KnuthMultiplicativeHash32(buf, size >> 1) & INT_MAX; } /** @@ -47,18 +49,22 @@ static textwindows dontinline uint32_t GetUserNameHash(void) { * This never fails. On Windows, which doesn't really have this concept, * we return a deterministic value that's likely to work. * + * @return user id (always successful) * @asyncsignalsafe + * @threadsafe * @vforksafe */ uint32_t getuid(void) { - uint32_t rc; - if (!IsWindows()) { + int rc; + if (IsMetal()) { + rc = 0; + } else if (!IsWindows()) { rc = sys_getuid(); } else { rc = GetUserNameHash(); } - - STRACE("%s() → %u% m", "getuid", rc); + _npassert(rc >= 0); + STRACE("%s() → %d", "getuid", rc); return rc; } @@ -68,16 +74,21 @@ uint32_t getuid(void) { * This never fails. On Windows, which doesn't really have this concept, * we return a deterministic value that's likely to work. * + * @return group id (always successful) * @asyncsignalsafe + * @threadsafe * @vforksafe */ uint32_t getgid(void) { - uint32_t rc; - if (!IsWindows()) { + int rc; + if (IsMetal()) { + rc = 0; + } else if (!IsWindows()) { rc = sys_getgid(); } else { rc = GetUserNameHash(); } - STRACE("%s() → %u% m", "getgid", rc); + _npassert(rc >= 0); + STRACE("%s() → %d", "getgid", rc); return rc; } diff --git a/libc/calls/lseek.c b/libc/calls/lseek.c index 07f4332e5..bd004d19f 100644 --- a/libc/calls/lseek.c +++ b/libc/calls/lseek.c @@ -28,13 +28,51 @@ #include "libc/zipos/zipos.internal.h" /** - * Changes current position of file descriptor/handle. + * Changes current position of file descriptor, e.g. + * + * int fd = open("hello.bin", O_RDONLY); + * lseek(fd, 100, SEEK_SET); // set position to 100th byte + * read(fd, buf, 8); // read bytes 100 through 107 + * + * This function may be used to inspect the current position: + * + * int64_t pos = lseek(fd, 0, SEEK_CUR); + * + * You may seek past the end of file. If a write happens afterwards + * then the gap leading up to it will be filled with zeroes. Please + * note that lseek() by itself will not extend the physical medium. + * + * If dup() is used then the current position will be shared across + * multiple file descriptors. If you seek in one it will implicitly + * seek the other too. + * + * The current position of a file descriptor is shared between both + * processes and threads. For example, if an fd is inherited across + * fork(), and both the child and parent want to read from it, then + * changes made by one are observable to the other. + * + * The pread() and pwrite() functions obfuscate the need for having + * global shared file position state. Consider using them, since it + * helps avoid the gotchas of this interface described above. + * + * This function is supported by all OSes within our support vector + * and our unit tests demonstrate the behaviors described above are + * consistent across platforms. * * @param fd is a number returned by open() - * @param offset is the relative byte count - * @param whence can be SEEK_SET, SEEK_CUR, or SEEK_END - * @return new position relative to beginning, or -1 on error + * @param offset is 0-indexed byte count w.r.t. `whence` + * @param whence can be one of: + * - `SEEK_SET`: Sets the file position to `offset` [default] + * - `SEEK_CUR`: Sets the file position to `position + offset` + * - `SEEK_END`: Sets the file position to `filesize + offset` + * @return new position relative to beginning, or -1 w/ errno + * @raise ESPIPE if `fd` is a pipe, socket, or fifo + * @raise EBADF if `fd` isn't an open file descriptor + * @raise EINVAL if resulting offset would be negative + * @raise EINVAL if `whence` isn't valid * @asyncsignalsafe + * @threadsafe + * @vforksafe */ int64_t lseek(int fd, int64_t offset, unsigned whence) { int64_t rc; diff --git a/libc/calls/openat.c b/libc/calls/openat.c index 3637da916..d15e75453 100644 --- a/libc/calls/openat.c +++ b/libc/calls/openat.c @@ -135,6 +135,7 @@ * @raise ENOMEM if insufficient memory was available * @raise EMFILE if `RLIMIT_NOFILE` has been reached * @raise EOPNOTSUPP if `file` names a named socket + * @raise EFAULT if `file` points to invalid memory * @raise ETXTBSY if writing is requested on `file` that's being executed * @raise ELOOP if `flags` had `O_NOFOLLOW` and `file` is a symbolic link * @raise ELOOP if a loop was detected resolving components of `file` diff --git a/libc/calls/pread.c b/libc/calls/pread.c index 6b79f8315..6c1a6bf6f 100644 --- a/libc/calls/pread.c +++ b/libc/calls/pread.c @@ -32,7 +32,9 @@ #include "libc/zipos/zipos.internal.h" /** - * Reads from file at offset, thus avoiding superfluous lseek(). + * Reads from file at offset. + * + * This function never changes the current position of `fd`. * * @param fd is something open()'d earlier, noting pipes might not work * @param buf is copied into, cf. copy_file_range(), sendfile(), etc. @@ -40,8 +42,14 @@ * @param offset is bytes from start of file at which read begins * @return [1..size] bytes on success, 0 on EOF, or -1 w/ errno; with * exception of size==0, in which case return zero means no error + * @raise ESPIPE if `fd` isn't seekable + * @raise EINVAL if `offset` is negative + * @raise EBADF if `fd` isn't an open file descriptor + * @raise EIO if a complicated i/o error happened + * @raise EINTR if signal was delivered instead * @see pwrite(), write() * @asyncsignalsafe + * @threadsafe * @vforksafe */ ssize_t pread(int fd, void *buf, size_t size, int64_t offset) { diff --git a/libc/calls/pwrite.c b/libc/calls/pwrite.c index 2d9c7d561..a8a12cf9b 100644 --- a/libc/calls/pwrite.c +++ b/libc/calls/pwrite.c @@ -29,7 +29,9 @@ #include "libc/sysv/errfuns.h" /** - * Writes to file at offset, thus avoiding superfluous lseek(). + * Writes to file at offset. + * + * This function never changes the current position of `fd`. * * @param fd is something open()'d earlier, noting pipes might not work * @param buf is copied from, cf. copy_file_range(), sendfile(), etc. @@ -41,6 +43,7 @@ * impossible unless size was passed as zero to do an error check * @see pread(), write() * @asyncsignalsafe + * @threadsafe * @vforksafe */ ssize_t pwrite(int fd, const void *buf, size_t size, int64_t offset) { diff --git a/libc/calls/read-nt.c b/libc/calls/read-nt.c index c51fcd657..35e531c0d 100644 --- a/libc/calls/read-nt.c +++ b/libc/calls/read-nt.c @@ -37,10 +37,12 @@ static textwindows ssize_t sys_read_nt_impl(struct Fd *fd, void *data, size_t size, ssize_t offset) { + bool32 ok; int64_t p; uint32_t got, avail; struct NtOverlapped overlap; + // our terrible polling mechanism if (GetFileType(fd->handle) == kNtFileTypePipe) { for (;;) { if (!PeekNamedPipe(fd->handle, 0, 0, 0, &avail, 0)) break; @@ -62,11 +64,15 @@ static textwindows ssize_t sys_read_nt_impl(struct Fd *fd, void *data, _npassert(SetFilePointerEx(fd->handle, 0, &p, SEEK_CUR)); } - if (ReadFile(fd->handle, data, _clampio(size), &got, - _offset2overlap(fd->handle, offset, &overlap))) { - if (offset != -1) { - _npassert(SetFilePointerEx(fd->handle, p, 0, SEEK_SET)); - } + ok = ReadFile(fd->handle, data, _clampio(size), &got, + _offset2overlap(fd->handle, offset, &overlap)); + + if (offset != -1) { + // windows clobbers file pointer even on error + _npassert(SetFilePointerEx(fd->handle, p, 0, SEEK_SET)); + } + + if (ok) { return got; } diff --git a/libc/calls/read.c b/libc/calls/read.c index b01f4e166..a5ffa8531 100644 --- a/libc/calls/read.c +++ b/libc/calls/read.c @@ -33,6 +33,9 @@ /** * Reads data from file descriptor. * + * This function changes the current file position. For documentation + * on file position behaviors and gotchas, see the lseek() function. + * * @param fd is something open()'d earlier * @param buf is copied into, cf. copy_file_range(), sendfile(), etc. * @param size in range [1..0x7ffff000] is reasonable @@ -41,6 +44,7 @@ * @see write(), pread(), readv() * @asyncsignalsafe * @restartable + * @vforksafe */ ssize_t read(int fd, void *buf, size_t size) { ssize_t rc; diff --git a/libc/calls/sigprocmask.c b/libc/calls/sigprocmask.c index d51fc9e4f..9941de4c4 100644 --- a/libc/calls/sigprocmask.c +++ b/libc/calls/sigprocmask.c @@ -48,10 +48,9 @@ * @vforksafe */ int sigprocmask(int how, const sigset_t *opt_set, sigset_t *opt_out_oldset) { - sigset_t old; int res, rc, arg1; + sigset_t old = {0}; const sigset_t *arg2; - sigemptyset(&old); if (IsAsan() && ((opt_set && !__asan_is_valid(opt_set, sizeof(*opt_set))) || (opt_out_oldset && diff --git a/libc/calls/sigsuspend.c b/libc/calls/sigsuspend.c index eeae47cc0..fabb66a92 100644 --- a/libc/calls/sigsuspend.c +++ b/libc/calls/sigsuspend.c @@ -46,7 +46,7 @@ int sigsuspend(const sigset_t *ignore) { int rc; long ms, totoms; - sigset_t save, mask, *arg; + sigset_t save, *arg, mask = {0}; STRACE("sigsuspend(%s) → ...", DescribeSigset(0, ignore)); if (IsAsan() && ignore && !__asan_is_valid(ignore, sizeof(*ignore))) { rc = efault(); @@ -63,7 +63,6 @@ int sigsuspend(const sigset_t *ignore) { if (ignore) { arg = ignore; } else { - sigemptyset(&mask); arg = &mask; } if (!IsWindows()) { diff --git a/libc/calls/struct/timespec.internal.h b/libc/calls/struct/timespec.internal.h index b5eb5d3b5..f3e768f55 100644 --- a/libc/calls/struct/timespec.internal.h +++ b/libc/calls/struct/timespec.internal.h @@ -6,6 +6,7 @@ COSMOPOLITAN_C_START_ int __sys_utimensat(int, const char *, const struct timespec[2], int) hidden; +int __utimens(int, const char *, const struct timespec[2], int) hidden; int sys_clock_getres(int, struct timespec *) hidden; int sys_clock_gettime(int, struct timespec *) hidden; int sys_clock_gettime_nt(int, struct timespec *) hidden; diff --git a/libc/calls/truncate-nt.c b/libc/calls/truncate-nt.c index 71f9c2f4f..e61bd396a 100644 --- a/libc/calls/truncate-nt.c +++ b/libc/calls/truncate-nt.c @@ -27,7 +27,6 @@ textwindows int sys_truncate_nt(const char *path, uint64_t length) { int rc; - bool32 ok; int64_t fh; uint16_t path16[PATH_MAX]; if (__mkntpath(path, path16) == -1) return -1; diff --git a/libc/calls/truncate.c b/libc/calls/truncate.c index a1d69bb81..3b0890afd 100644 --- a/libc/calls/truncate.c +++ b/libc/calls/truncate.c @@ -17,25 +17,65 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" -#include "libc/intrin/strace.internal.h" #include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" +#include "libc/errno.h" +#include "libc/intrin/asan.internal.h" +#include "libc/intrin/strace.internal.h" +#include "libc/intrin/weaken.h" +#include "libc/sysv/errfuns.h" +#include "libc/zipos/zipos.internal.h" /** - * Reduces or extends underlying physical medium of file. + * Changes size of file. * - * If file was originally larger, content >length is lost. + * If the file size is increased, the extended area shall appear as if + * it were zero-filled. If your file size is decreased, the extra data + * shall be lost. * - * @param path must exist - * @return 0 on success or -1 w/ errno + * Some operating systems implement an optimization, where `length` is + * treated as a logical size and the requested physical space won't be + * allocated until non-zero values get written into it. Our tests show + * this happens on Linux (usually with 4096 byte granularity), FreeBSD + * (which favors 512-byte granularity), and MacOS (prefers 4096 bytes) + * however Windows, OpenBSD, and NetBSD always reserve physical space. + * This may be inspected using stat() then consulting stat::st_blocks. + * + * @param path is name of file that shall be resized + * @return 0 on success, or -1 w/ errno + * @raise EINVAL if `length` is negative + * @raise EINTR if signal was delivered instead + * @raise EFBIG or EINVAL if `length` is too huge + * @raise EFAULT if `path` points to invalid memory + * @raise ENOTSUP if `path` is a zip filesystem path + * @raise EACCES if we don't have permission to search a component of `path` + * @raise ENOTDIR if a directory component in `path` exists as non-directory + * @raise ENAMETOOLONG if symlink-resolved `path` length exceeds `PATH_MAX` + * @raise ENAMETOOLONG if component in `path` exists longer than `NAME_MAX` + * @raise ELOOP if a loop was detected resolving components of `path` + * @raise ENOENT if `path` doesn't exist or is an empty string + * @raise ETXTBSY if `path` is an executable being executed + * @raise EROFS if `path` is on a read-only filesystem + * @raise ENOSYS on bare metal * @see ftruncate() - * @error ENOENT + * @threadsafe */ int truncate(const char *path, uint64_t length) { int rc; - if (!IsWindows()) { + struct ZiposUri zipname; + if (IsMetal()) { + rc = enosys(); + } else if (!path || (IsAsan() && !__asan_is_valid(path, 1))) { + rc = efault(); + } else if (_weaken(__zipos_parseuri) && + _weaken(__zipos_parseuri)(path, &zipname) != -1) { + rc = enotsup(); + } else if (!IsWindows()) { rc = sys_truncate(path, length, length); + if (IsNetbsd() && rc == -1 && errno == ENOSPC) { + errno = EFBIG; // POSIX doesn't specify ENOSPC for truncate() + } } else { rc = sys_truncate_nt(path, length); } diff --git a/libc/stdio/systemexec.c b/libc/calls/utimens.c similarity index 56% rename from libc/stdio/systemexec.c rename to libc/calls/utimens.c index eb5d9baa0..5be86ea96 100644 --- a/libc/stdio/systemexec.c +++ b/libc/calls/utimens.c @@ -1,7 +1,7 @@ /*-*- 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 │ +│ Copyright 2020 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 │ @@ -16,11 +16,40 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/runtime/runtime.h" -#include "libc/stdio/cocmd.internal.h" -#include "libc/stdio/stdio.h" +#include "libc/assert.h" +#include "libc/calls/asan.internal.h" +#include "libc/calls/calls.h" +#include "libc/calls/internal.h" +#include "libc/calls/struct/timespec.internal.h" +#include "libc/dce.h" +#include "libc/intrin/asan.internal.h" +#include "libc/intrin/describeflags.internal.h" +#include "libc/intrin/strace.internal.h" +#include "libc/intrin/weaken.h" +#include "libc/sysv/consts/at.h" +#include "libc/sysv/errfuns.h" +#include "libc/zipos/zipos.internal.h" -// Support code for system() and popen(). -int systemexec(const char *cmdline) { - _Exit(cocmd(3, (char *[]){"cocmd.com", "-c", cmdline, 0})); +int __utimens(int fd, const char *path, const struct timespec ts[2], + int flags) { + int rc; + struct ZiposUri zipname; + if (IsMetal()) { + rc = enosys(); + } else if (IsAsan() && ((fd == AT_FDCWD && !__asan_is_valid(path, 1)) || + (ts && (!__asan_is_valid_timespec(ts + 0) || + !__asan_is_valid_timespec(ts + 1))))) { + rc = efault(); // bad memory + } else if ((flags & ~AT_SYMLINK_NOFOLLOW)) { + rc = einval(); // unsupported flag + } else if (__isfdkind(fd, kFdZip) || + (path && (_weaken(__zipos_parseuri) && + _weaken(__zipos_parseuri)(path, &zipname) != -1))) { + rc = enotsup(); + } else if (!IsWindows()) { + rc = sys_utimensat(fd, path, ts, flags); + } else { + rc = sys_utimensat_nt(fd, path, ts, flags); + } + return rc; } diff --git a/libc/calls/utimensat.c b/libc/calls/utimensat.c index 58df289f6..893cbe8a7 100644 --- a/libc/calls/utimensat.c +++ b/libc/calls/utimensat.c @@ -30,54 +30,43 @@ #include "libc/zipos/zipos.internal.h" /** - * Sets atime/mtime on file, the modern way. + * Sets access/modified time on file, the modern way. * - * This is two functions in one. If `path` is null then this function - * becomes the same as `futimens(dirfd, ts)`. + * XNU only has microsecond (1e-6) accuracy and there's no + * `dirfd`-relative support. Windows only has hectonanosecond (1e-7) + * accuracy. RHEL5 is somewhat broken so utimes() is recommended if + * portability to old versions of Linux is desired. * - * XNU and RHEL5 only have microsecond accuracy and there's no `dirfd` - * relative support. - * - * @param dirfd should AT_FDCWD - * @param ts is atime/mtime, or null for current time - * @param flags can have AT_SYMLINK_NOFOLLOW - * @raise ENOSYS on RHEL5 if path is NULL - * @raise EINVAL if flags had unrecognized bits - * @raise EINVAL if path is NULL and flags has AT_SYMLINK_NOFOLLOW - * @raise EBADF if dirfd isn't a valid fd or AT_FDCWD - * @raise EFAULT if path or ts memory was invalid - * @raise EROFS if file system is read-only - * @raise ENAMETOOLONG - * @raise EPERM + * @param dirfd is usually `AT_FDCWD` + * @param path is filename whose timestamps should be modified + * @param ts is {access, modified} timestamps, or null for current time + * @param flags can have `AT_SYMLINK_NOFOLLOW` when `path` is specified + * @return 0 on success, or -1 w/ errno + * @raise EINVAL if `flags` had an unrecognized value + * @raise EPERM if pledge() is in play without `fattr` promise + * @raise EACCES if unveil() is in play and `path` isn't unveiled + * @raise ENOTSUP if `path` is a zip filesystem path or `dirfd` is zip + * @raise EINVAL if `ts` specifies a nanosecond value that's out of range + * @raise ENAMETOOLONG if symlink-resolved `path` length exceeds `PATH_MAX` + * @raise ENAMETOOLONG if component in `path` exists longer than `NAME_MAX` + * @raise EBADF if `dirfd` isn't a valid fd or `AT_FDCWD` + * @raise EFAULT if `path` or `ts` memory was invalid + * @raise EROFS if `path` is on read-only filesystem + * @raise ENOSYS on bare metal * @see futimens() * @asyncsignalsafe + * @threadsafe */ int utimensat(int dirfd, const char *path, const struct timespec ts[2], int flags) { int rc; - if (IsAsan() && ((dirfd == AT_FDCWD && !__asan_is_valid(path, 1)) || - (ts && (!__asan_is_valid_timespec(ts + 0) || - !__asan_is_valid_timespec(ts + 1))))) { - rc = efault(); // bad memory - } else if ((flags & ~AT_SYMLINK_NOFOLLOW)) { - rc = einval(); // unsupported flag - } else if (!path && flags) { - rc = einval(); // futimens() doesn't take flags - } else if ((path || __isfdkind(dirfd, kFdZip)) && _weaken(__zipos_notat) && - (rc = __zipos_notat(dirfd, path)) == -1) { - STRACE("zipos utimensat not supported yet"); - } else if (!IsWindows()) { - rc = sys_utimensat(dirfd, path, ts, flags); + if (!path) { + rc = efault(); // linux kernel abi behavior isn't supported } else { - rc = sys_utimensat_nt(dirfd, path, ts, flags); - } - if (ts) { - STRACE("utimensat(%s, %#s, {{%,ld, %,ld}, {%,ld, %,ld}}, %#b) → %d% m", - DescribeDirfd(dirfd), path, ts[0].tv_sec, ts[0].tv_nsec, - ts[1].tv_sec, ts[1].tv_nsec, flags, rc); - } else { - STRACE("utimensat(%s, %#s, 0, %#b) → %d% m", DescribeDirfd(dirfd), path, - flags, rc); + rc = __utimens(dirfd, path, ts, flags); } + STRACE("utimensat(%s, %#s, {%s, %s}, %#o) → %d% m", DescribeDirfd(dirfd), + path, DescribeTimespec(0, ts), DescribeTimespec(0, ts ? ts + 1 : 0), + flags, rc); return rc; } diff --git a/libc/calls/utimes.c b/libc/calls/utimes.c index a1544e128..7d804e9ef 100644 --- a/libc/calls/utimes.c +++ b/libc/calls/utimes.c @@ -16,34 +16,59 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/asan.internal.h" +#include "libc/calls/struct/itimerval.internal.h" #include "libc/calls/struct/timeval.h" #include "libc/calls/struct/timeval.internal.h" #include "libc/dce.h" #include "libc/intrin/asan.internal.h" +#include "libc/intrin/strace.internal.h" +#include "libc/intrin/weaken.h" #include "libc/sysv/consts/at.h" #include "libc/sysv/errfuns.h" +#include "libc/zipos/zipos.internal.h" /** - * Changes last accessed/modified times on file. + * Changes last accessed/modified timestamps on file. * - * @param times is access/modified and NULL means now - * @return 0 on success or -1 w/ errno + * @param tv is access/modified timestamps, or NULL which means now + * @return 0 on success, or -1 w/ errno + * @raise ENOTSUP if `path` is a zip filesystem path + * @raise EROFS if `path` is on a read-only filesystem + * @raise EFAULT if `path` or `tv` points to invalid memory + * @raise EPERM if pledge() is in play without fattr promise + * @raise ENOENT if `path` doesn't exist or is an empty string + * @raise EACCES if unveil() is in play and `path` isn't unveiled + * @raise ELOOP if a loop was detected resolving components of `path` + * @raise ENAMETOOLONG if symlink-resolved `path` length exceeds `PATH_MAX` + * @raise ENAMETOOLONG if component in `path` exists longer than `NAME_MAX` + * @raise EINVAL if `tv` specifies a microseconds value that's out of range + * @raise EACCES if we don't have permission to search a component of `path` + * @raise ENOTDIR if a directory component in `path` exists as non-directory + * @raise ENOSYS on bare metal * @asyncsignalsafe * @see stat() */ int utimes(const char *path, const struct timeval tv[2]) { - if (IsAsan()) { - if (tv && !__asan_is_valid(tv, sizeof(*tv) * 2)) { - return efault(); - } - } - if (!IsWindows()) { - /* - * we don't modernize utimes() into utimensat() because the - * latter is poorly supported and utimes() works everywhere - */ - return sys_utimes(path, tv); + int rc; + struct ZiposUri zipname; + if (IsMetal()) { + rc = enosys(); + } else if (IsAsan() && tv && + (!__asan_is_valid_timeval(tv + 0) || + !__asan_is_valid_timeval(tv + 1))) { + rc = efault(); + } else if (_weaken(__zipos_parseuri) && + _weaken(__zipos_parseuri)(path, &zipname) != -1) { + rc = enotsup(); + } else if (!IsWindows()) { + // we don't modernize utimes() into utimensat() because the + // latter is poorly supported and utimes() works everywhere + rc = sys_utimes(path, tv); } else { - return sys_utimes_nt(path, tv); + rc = sys_utimes_nt(path, tv); } + STRACE("utimes(%#s, {%s, %s}) → %d% m", path, DescribeTimeval(0, tv), + DescribeTimeval(0, tv ? tv + 1 : 0), rc); + return rc; } diff --git a/libc/calls/wait4-nt.c b/libc/calls/wait4-nt.c index 07282ce3c..bd68c737e 100644 --- a/libc/calls/wait4-nt.c +++ b/libc/calls/wait4-nt.c @@ -21,10 +21,10 @@ #include "libc/calls/internal.h" #include "libc/calls/sig.internal.h" #include "libc/calls/state.internal.h" -#include "libc/intrin/strace.internal.h" #include "libc/calls/struct/rusage.h" #include "libc/calls/syscall_support-nt.internal.h" #include "libc/fmt/conv.h" +#include "libc/intrin/strace.internal.h" #include "libc/macros.internal.h" #include "libc/nt/accounting.h" #include "libc/nt/enum/accessmask.h" @@ -149,8 +149,7 @@ static textwindows int sys_wait4_nt_impl(int pid, int *opt_out_wstatus, textwindows int sys_wait4_nt(int pid, int *opt_out_wstatus, int options, struct rusage *opt_out_rusage) { int rc; - sigset_t mask, oldmask; - sigemptyset(&mask); + sigset_t oldmask, mask = {0}; sigaddset(&mask, SIGCHLD); __sig_mask(SIG_BLOCK, &mask, &oldmask); rc = sys_wait4_nt_impl(pid, opt_out_wstatus, options, opt_out_rusage); diff --git a/libc/calls/write-nt.c b/libc/calls/write-nt.c index b7939ba28..cf536e330 100644 --- a/libc/calls/write-nt.c +++ b/libc/calls/write-nt.c @@ -38,6 +38,7 @@ static textwindows ssize_t sys_write_nt_impl(int fd, void *data, size_t size, ssize_t offset) { + bool32 ok; int64_t h, p; uint32_t err, sent; struct NtOverlapped overlap; @@ -49,11 +50,15 @@ static textwindows ssize_t sys_write_nt_impl(int fd, void *data, size_t size, _npassert(SetFilePointerEx(h, 0, &p, SEEK_CUR)); } - if (WriteFile(h, data, _clampio(size), &sent, - _offset2overlap(h, offset, &overlap))) { - if (offset != -1) { - _npassert(SetFilePointerEx(h, p, 0, SEEK_SET)); - } + ok = WriteFile(h, data, _clampio(size), &sent, + _offset2overlap(h, offset, &overlap)); + + if (offset != -1) { + // windows clobbers file pointer even on error + _npassert(SetFilePointerEx(h, p, 0, SEEK_SET)); + } + + if (ok) { return sent; } @@ -72,7 +77,7 @@ static textwindows ssize_t sys_write_nt_impl(int fd, void *data, size_t size, _Exitr(128 + EPIPE); } case kNtErrorAccessDenied: // write doesn't return EACCESS - return ebadf(); // + return ebadf(); default: return __winerr(); } diff --git a/libc/calls/write.c b/libc/calls/write.c index 900183ed0..8b5a4cb08 100644 --- a/libc/calls/write.c +++ b/libc/calls/write.c @@ -31,6 +31,9 @@ /** * Writes data to file descriptor. * + * This function changes the current file position. For documentation + * on file position behaviors and gotchas, see the lseek() function. + * * @param fd is something open()'d earlier * @param buf is copied from, cf. copy_file_range(), sendfile(), etc. * @param size in range [1..0x7ffff000] is reasonable @@ -39,6 +42,7 @@ * @see read(), pwrite(), writev(), SIGPIPE * @asyncsignalsafe * @restartable + * @vforksafe */ ssize_t write(int fd, const void *buf, size_t size) { ssize_t rc; diff --git a/libc/intrin/getpid.c b/libc/intrin/getpid.c index 1d1513bdb..badabd355 100644 --- a/libc/intrin/getpid.c +++ b/libc/intrin/getpid.c @@ -19,6 +19,7 @@ #include "libc/calls/calls.h" #include "libc/calls/state.internal.h" #include "libc/calls/syscall-sysv.internal.h" +#include "libc/dce.h" #include "libc/runtime/internal.h" /** @@ -32,16 +33,19 @@ * programs that mix fork() with threads. In that case, apps should * consider using `sys_getpid().ax` instead to force a system call. * - * On Linux, and only Linux, the process id is guaranteed to be the same - * as gettid() for the main thread. + * On Linux, and only Linux, getpid() is guaranteed to equal gettid() + * for the main thread. * + * @return process id (always successful) * @asyncsignalsafe * @threadsafe * @vforksafe */ int getpid(void) { int rc; - if (!__vforked) { + if (IsMetal()) { + rc = 1; + } else if (!__vforked) { rc = __pid; } else { rc = sys_getpid().ax; diff --git a/libc/runtime/fork-nt.c b/libc/runtime/fork-nt.c index 0e814e721..490b44e73 100644 --- a/libc/runtime/fork-nt.c +++ b/libc/runtime/fork-nt.c @@ -21,6 +21,7 @@ #include "libc/calls/state.internal.h" #include "libc/calls/syscall_support-nt.internal.h" #include "libc/calls/wincrash.internal.h" +#include "libc/errno.h" #include "libc/fmt/itoa.h" #include "libc/intrin/kprintf.h" #include "libc/intrin/strace.internal.h" @@ -52,6 +53,7 @@ #include "libc/sysv/consts/map.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/prot.h" +#include "libc/sysv/errfuns.h" #include "libc/thread/tls.h" STATIC_YOINK("_check_sigchld"); @@ -337,6 +339,9 @@ textwindows int sys_fork_nt(void) { } if (reader != -1) CloseHandle(reader); if (writer != -1) CloseHandle(writer); + if (rc == -1 && errno != ENOMEM) { + eagain(); // posix fork() only specifies two errors + } } else { rc = 0; } diff --git a/libc/runtime/fork.c b/libc/runtime/fork.c index 91567fd36..1c573489d 100644 --- a/libc/runtime/fork.c +++ b/libc/runtime/fork.c @@ -17,11 +17,11 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" -#include "libc/intrin/strace.internal.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" +#include "libc/intrin/strace.internal.h" #include "libc/nt/process.h" #include "libc/runtime/internal.h" #include "libc/sysv/consts/sig.h" @@ -30,7 +30,9 @@ /** * Creates new process. * - * @return 0 to child, child pid to parent, or -1 on error + * @return 0 to child, child pid to parent, or -1 w/ errno + * @raise EAGAIN if `RLIMIT_NPROC` was exceeded or system lacked resources + * @raise ENOMEM if we require more vespene gas * @asyncsignalsafe */ int fork(void) { @@ -62,11 +64,11 @@ int fork(void) { if (__tls_enabled) { __get_tls()->tib_tid = IsLinux() ? dx : sys_gettid(); } + sigprocmask(SIG_SETMASK, &old, 0); STRACE("fork() → 0 (child of %d)", parent); - sigprocmask(SIG_SETMASK, &old, 0); } else { - STRACE("fork() → %d% m", ax); sigprocmask(SIG_SETMASK, &old, 0); + STRACE("fork() → %d% m", ax); } return ax; } diff --git a/libc/runtime/vfork.S b/libc/runtime/vfork.S index 6e84cf40e..a1bb44b53 100644 --- a/libc/runtime/vfork.S +++ b/libc/runtime/vfork.S @@ -25,12 +25,12 @@ // // This is the same as fork() except it's optimized for the case // where the caller invokes execve() immediately afterwards. You -// can also call functions like close(), dup2(), etc. You cannot -// call read() safely but you can call pread(). Call _exit() but -// don't call exit(). Look for the vforksafe function annotation +// can also call functions like close(), dup2(), etc. Call _exit +// but don't call exit. Look for vforksafe function annotations. // // Do not make the assumption that the parent is suspended until -// the child terminates since this impl calls fork() on Windows. +// the child terminates since this impl calls fork() on Windows, +// OpenBSD, and MacOS. // // @return pid of child process or 0 if forked process // @returnstwice diff --git a/libc/stdio/popen.c b/libc/stdio/popen.c index 252332b2e..8a385912d 100644 --- a/libc/stdio/popen.c +++ b/libc/stdio/popen.c @@ -20,6 +20,7 @@ #include "libc/errno.h" #include "libc/paths.h" #include "libc/runtime/runtime.h" +#include "libc/stdio/cocmd.internal.h" #include "libc/stdio/internal.h" #include "libc/stdio/stdio.h" #include "libc/sysv/consts/f.h" @@ -50,7 +51,7 @@ FILE *popen(const char *cmdline, const char *mode) { } else if ((flags & O_ACCMODE) == O_WRONLY) { dir = 1; } else { - errno = EINVAL; + einval(); return NULL; } if (pipe(pipefds) == -1) return NULL; @@ -59,8 +60,7 @@ FILE *popen(const char *cmdline, const char *mode) { switch ((pid = fork())) { case 0: dup2(pipefds[!dir], !dir); - systemexec(cmdline); - _exit(127); + _Exit(cocmd(3, (char *[]){"popen", "-c", cmdline, 0})); default: f->pid = pid; close(pipefds[!dir]); diff --git a/libc/stdio/stdio.h b/libc/stdio/stdio.h index 8a5e4e1ae..22e4d5966 100644 --- a/libc/stdio/stdio.h +++ b/libc/stdio/stdio.h @@ -83,7 +83,6 @@ void setlinebuf(FILE *); void setbuf(FILE *, char *); void setbuffer(FILE *, char *, size_t); int setvbuf(FILE *, char *, int, size_t); -FILE *popen(const char *, const char *); int pclose(FILE *); char *ctermid(char *); void perror(const char *) relegated; @@ -94,7 +93,7 @@ int fgetpos(FILE *, fpos_t *) paramsnonnull(); int fsetpos(FILE *, const fpos_t *) paramsnonnull(); int system(const char *); -int systemexec(const char *); +FILE *popen(const char *, const char *); /*───────────────────────────────────────────────────────────────────────────│─╗ │ cosmopolitan § standard i/o » formatting ─╬─│┼ diff --git a/libc/stdio/system.c b/libc/stdio/system.c index 98699c225..8fbf8c0c8 100644 --- a/libc/stdio/system.c +++ b/libc/stdio/system.c @@ -24,6 +24,7 @@ #include "libc/log/log.h" #include "libc/paths.h" #include "libc/runtime/runtime.h" +#include "libc/stdio/cocmd.internal.h" #include "libc/stdio/stdio.h" #include "libc/str/str.h" #include "libc/sysv/consts/ok.h" @@ -62,8 +63,7 @@ int system(const char *cmdline) { sigaction(SIGINT, &saveint, 0); sigaction(SIGQUIT, &savequit, 0); sigprocmask(SIG_SETMASK, &savemask, 0); - systemexec(cmdline); - _exit(127); + _Exit(cocmd(3, (char *[]){"system", "-c", cmdline, 0})); } else if (pid != -1) { while (wait4(pid, &wstatus, 0, 0) == -1) { if (errno != EINTR) { diff --git a/libc/sysv/dos2errno.sh b/libc/sysv/dos2errno.sh index 5781685f6..74af2c605 100755 --- a/libc/sysv/dos2errno.sh +++ b/libc/sysv/dos2errno.sh @@ -126,6 +126,7 @@ dos kNtErrorNoaccess EFAULT dos kNtErrorInvalidAddress EADDRNOTAVAIL dos kNtErrorNotAReparsePoint EINVAL dos kNtErrorInvalidFunction EINVAL +dos kNtErrorNegativeSeek EINVAL dos kNtErrorInvalidNetname EADDRNOTAVAIL dos kNtErrorInvalidUserBuffer EMSGSIZE dos kNtErrorIoPending EINPROGRESS diff --git a/libc/sysv/dos2errno/EINVAL.S b/libc/sysv/dos2errno/EINVAL.S index 1f4f528ee..78a3951fa 100644 --- a/libc/sysv/dos2errno/EINVAL.S +++ b/libc/sysv/dos2errno/EINVAL.S @@ -11,3 +11,4 @@ kDos2Errno.EINVAL: .e kNtErrorNotAReparsePoint,EINVAL .e kNtErrorInvalidFunction,EINVAL .e WSAEINVAL,EINVAL + .e kNtErrorNegativeSeek,EINVAL diff --git a/libc/testlib/testrunner.c b/libc/testlib/testrunner.c index e505727c5..0955a2929 100644 --- a/libc/testlib/testrunner.c +++ b/libc/testlib/testrunner.c @@ -51,7 +51,7 @@ static int x; char g_testlib_olddir[PATH_MAX]; char g_testlib_tmpdir[PATH_MAX]; struct sigaction wanthandlers[31]; -static pthread_spinlock_t testlib_error_lock; +static pthread_mutex_t testlib_error_lock; void testlib_finish(void) { if (g_testlib_failed) { @@ -63,7 +63,7 @@ void testlib_finish(void) { void testlib_error_enter(const char *file, const char *func) { atomic_fetch_sub_explicit(&__ftrace, 1, memory_order_relaxed); atomic_fetch_sub_explicit(&__strace, 1, memory_order_relaxed); - if (!__vforked) pthread_spin_lock(&testlib_error_lock); + if (!__vforked) pthread_mutex_lock(&testlib_error_lock); if (!IsWindows()) sys_getpid(); /* make strace easier to read */ if (!IsWindows()) sys_getpid(); if (g_testlib_shoulddebugbreak) { @@ -76,7 +76,7 @@ void testlib_error_enter(const char *file, const char *func) { void testlib_error_leave(void) { atomic_fetch_add_explicit(&__ftrace, 1, memory_order_relaxed); atomic_fetch_add_explicit(&__strace, 1, memory_order_relaxed); - pthread_spin_unlock(&testlib_error_lock); + pthread_mutex_unlock(&testlib_error_lock); } wontreturn void testlib_abort(void) { diff --git a/test/libc/calls/ftruncate_test.c b/test/libc/calls/ftruncate_test.c index 008c8d26c..3b8302a97 100644 --- a/test/libc/calls/ftruncate_test.c +++ b/test/libc/calls/ftruncate_test.c @@ -16,18 +16,22 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/intrin/safemacros.internal.h" #include "libc/calls/calls.h" +#include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/stat.h" #include "libc/dce.h" #include "libc/errno.h" +#include "libc/intrin/safemacros.internal.h" +#include "libc/limits.h" #include "libc/mem/gc.internal.h" #include "libc/runtime/runtime.h" #include "libc/stdio/stdio.h" +#include "libc/str/str.h" +#include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/sig.h" #include "libc/testlib/testlib.h" #include "libc/x/x.h" -int64_t fd; struct stat st; char testlib_enable_tmp_setup_teardown; @@ -36,15 +40,67 @@ void SetUpOnce(void) { ASSERT_SYS(0, 0, pledge("stdio rpath wpath cpath", 0)); } -TEST(ftruncate, test) { - ASSERT_NE(-1, (fd = creat("foo", 0755))); - ASSERT_EQ(5, write(fd, "hello", 5)); - errno = 31337; - ASSERT_NE(-1, ftruncate(fd, 31337)); - EXPECT_EQ(31337, errno); - ASSERT_EQ(5, write(fd, "world", 5)); - ASSERT_NE(-1, close(fd)); - ASSERT_NE(-1, stat("foo", &st)); - ASSERT_EQ(31337, st.st_size); - ASSERT_BINEQ(u"helloworld", gc(xslurp("foo", 0))); +TEST(ftruncate, ebadf) { + ASSERT_SYS(EBADF, -1, ftruncate(-1, 0)); + ASSERT_SYS(EBADF, -1, ftruncate(+3, 0)); +} + +TEST(ftruncate, negativeLength_einval) { + ASSERT_SYS(0, 3, creat("foo", 0755)); + ASSERT_SYS(EINVAL, -1, ftruncate(3, -1)); + ASSERT_SYS(0, 0, close(3)); +} + +TEST(ftruncate, doesntHaveWritePermission_einval) { + ASSERT_SYS(0, 3, creat("foo", 0755)); + ASSERT_SYS(0, 5, write(3, "hello", 5)); + ASSERT_SYS(0, 0, close(3)); + ASSERT_SYS(0, 3, open("foo", O_RDONLY)); + ASSERT_SYS(EINVAL, -1, ftruncate(3, 0)); + ASSERT_SYS(0, 0, close(3)); +} + +TEST(ftruncate, pipeFd_einval) { + int fds[2]; + ASSERT_SYS(0, 0, pipe(fds)); + ASSERT_SYS(EINVAL, -1, ftruncate(3, 0)); + EXPECT_SYS(0, 0, close(4)); + EXPECT_SYS(0, 0, close(3)); +} + +TEST(ftruncate, efbig) { + // FreeBSD and RHEL7 return 0 (why??) + if (IsLinux() || IsFreebsd()) return; + sighandler_t old = signal(SIGXFSZ, SIG_IGN); + ASSERT_SYS(0, 3, creat("foo", 0755)); + ASSERT_SYS(IsWindows() ? EINVAL : EFBIG, -1, ftruncate(3, INT64_MAX)); + ASSERT_SYS(0, 0, close(3)); + signal(SIGXFSZ, old); +} + +TEST(ftruncate, test) { + char got[512], want[512] = "helloworld"; // tests zero-extending + ASSERT_SYS(0, 3, open("foo", O_CREAT | O_TRUNC | O_RDWR, 0755)); + ASSERT_SYS(0, 5, write(3, "hello", 5)); + ASSERT_SYS(0, 0, ftruncate(3, 8192)); + ASSERT_SYS(0, 5, lseek(3, 0, SEEK_CUR)); // doesn't change position + ASSERT_SYS(0, 5, write(3, "world", 5)); + ASSERT_SYS(0, 0, fstat(3, &st)); + ASSERT_EQ(8192, st.st_size); // 8192 is logical size + if (IsWindows() || IsNetbsd() || IsOpenbsd()) { // + ASSERT_EQ(8192 / 512, st.st_blocks); // 8192 is physical size + } else if (IsFreebsd()) { // + ASSERT_EQ(512 / 512, st.st_blocks); // 512 is physical size + } else if (IsLinux() || IsXnu()) { // + ASSERT_EQ(4096 / 512, st.st_blocks); // 4096 is physical size + } else { + notpossible; + } + ASSERT_SYS(0, 512, pread(3, got, 512, 0)); + ASSERT_EQ(0, memcmp(want, got, 512)); + ASSERT_SYS(0, 0, ftruncate(3, 0)); // shrink file to be empty + ASSERT_SYS(0, 0, pread(3, got, 512, 0)); + ASSERT_SYS(0, 0, read(3, got, 512)); + ASSERT_SYS(0, 10, lseek(3, 0, SEEK_CUR)); // position stays past eof + ASSERT_SYS(0, 0, close(3)); } diff --git a/test/libc/calls/lseek_test.c b/test/libc/calls/lseek_test.c index a74a785a9..59f22566f 100644 --- a/test/libc/calls/lseek_test.c +++ b/test/libc/calls/lseek_test.c @@ -18,10 +18,13 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/calls/internal.h" +#include "libc/errno.h" #include "libc/fmt/fmt.h" +#include "libc/limits.h" #include "libc/log/check.h" #include "libc/runtime/runtime.h" #include "libc/sysv/consts/o.h" +#include "libc/testlib/subprocess.h" #include "libc/testlib/testlib.h" #include "libc/x/x.h" @@ -31,18 +34,73 @@ void SetUpOnce(void) { ASSERT_SYS(0, 0, pledge("stdio rpath wpath cpath fattr proc", 0)); } -TEST(lseek, wat) { - int fd, pid; - char buf[8] = {0}; - ASSERT_NE(-1, (fd = open("wut", O_RDWR | O_CREAT, 0644))); - ASSERT_EQ(3, write(fd, "wut", 3)); - ASSERT_NE(-1, lseek(fd, 0, SEEK_SET)); - if (!(pid = fork())) { - lseek(fd, 1, SEEK_SET); - _exit(0); - } - EXPECT_NE(-1, waitpid(pid, 0, 0)); - EXPECT_EQ(1, read(fd, buf, 1)); - EXPECT_EQ('u', buf[0]); /* wat?! */ - EXPECT_NE(-1, close(fd)); +TEST(lseek, ebadf) { + ASSERT_SYS(EBADF, -1, lseek(-1, 0, SEEK_SET)); + ASSERT_SYS(EBADF, -1, lseek(+3, 0, SEEK_SET)); +} + +TEST(lseek, badWhence_einval) { + ASSERT_SYS(0, 3, creat("foo", 0644)); + ASSERT_SYS(EINVAL, -1, lseek(3, 0, -1)); + EXPECT_SYS(0, 0, close(3)); +} + +TEST(lseek, negativeComputedOffset_einval) { + ASSERT_SYS(0, 3, creat("foo", 0644)); + ASSERT_SYS(EINVAL, -1, lseek(3, -1, SEEK_SET)); + ASSERT_SYS(EINVAL, -1, lseek(3, -1, SEEK_CUR)); + ASSERT_SYS(EINVAL, -1, lseek(3, -1, SEEK_END)); + EXPECT_SYS(0, 0, close(3)); +} + +TEST(lseek, 64bit) { + ASSERT_SYS(0, 3, creat("foo", 0644)); + ASSERT_SYS(0, 0x100000001, lseek(3, 0x100000001, SEEK_SET)); + EXPECT_SYS(0, 0, close(3)); +} + +TEST(lseek, nonSeekableFd_espipe) { + int fds[2]; + ASSERT_SYS(0, 0, pipe(fds)); + ASSERT_SYS(ESPIPE, -1, lseek(3, 0, SEEK_SET)); + EXPECT_SYS(0, 0, close(4)); + EXPECT_SYS(0, 0, close(3)); +} + +TEST(lseek, filePositionChanges_areObservableAcrossDup) { + ASSERT_SYS(0, 3, creat("wut", 0644)); + ASSERT_SYS(0, 4, dup(3)); + ASSERT_SYS(0, 0, lseek(3, 0, SEEK_CUR)); + ASSERT_SYS(0, 1, lseek(4, 1, SEEK_SET)); + ASSERT_SYS(0, 1, lseek(3, 0, SEEK_CUR)); + EXPECT_SYS(0, 0, close(4)); + EXPECT_SYS(0, 0, close(3)); +} + +TEST(lseek, filePositionChanges_areObservableAcrossProcesses) { + char buf[8] = {0}; + ASSERT_SYS(0, 3, open("wut", O_RDWR | O_CREAT, 0644)); + ASSERT_SYS(0, 3, write(3, "wut", 3)); + ASSERT_SYS(0, 0, lseek(3, 0, SEEK_SET)); + SPAWN(fork); + ASSERT_SYS(0, 1, lseek(3, 1, SEEK_SET)); + EXITS(0); + EXPECT_SYS(0, 1, read(3, buf, 1)); + EXPECT_EQ('u', buf[0]); + EXPECT_SYS(0, 0, close(3)); +} + +TEST(lseek, beyondEndOfFile_isZeroExtendedUponSubsequentWrite) { + char buf[8] = {1, 1}; + ASSERT_SYS(0, 3, open("foo", O_RDWR | O_CREAT | O_TRUNC, 0644)); + ASSERT_SYS(0, 2, lseek(3, 2, SEEK_SET)); + ASSERT_SYS(0, 2, lseek(3, 0, SEEK_CUR)); + ASSERT_SYS(0, 0, pread(3, buf, 8, 0)); // lseek() alone doesn't extend + ASSERT_SYS(0, 2, write(3, buf, 2)); // does extend once i/o happens + ASSERT_SYS(0, 4, pread(3, buf, 8, 0)); + ASSERT_EQ(0, buf[0]); + ASSERT_EQ(0, buf[1]); + ASSERT_EQ(1, buf[2]); + ASSERT_EQ(1, buf[3]); + ASSERT_SYS(0, 0, close(3)); }