diff --git a/README.md b/README.md index 35a7f5460..95a4dc16e 100644 --- a/README.md +++ b/README.md @@ -34,17 +34,14 @@ cosmocc --update # pull cosmo and rebuild toolchain ``` You've now successfully installed your very own cosmos. Now let's build -an example program, which demonstrates the crash reporting feature: +an example program: ```c // hello.c #include -#include int main() { - ShowCrashReports(); printf("hello world\n"); - __builtin_trap(); } ``` diff --git a/libc/calls/__sig2.c b/libc/calls/__sig2.c index 2b0fcb23e..a7873c2f6 100644 --- a/libc/calls/__sig2.c +++ b/libc/calls/__sig2.c @@ -181,13 +181,14 @@ textwindows bool __sig_handle(int sigops, int sig, int si_code, switch (__sighandrvas[sig]) { case (intptr_t)SIG_DFL: if (__sig_is_fatal(sig)) { - size_t len; - char name[16]; - strsignal_r(sig, name); - len = strlen(name); - name[len++] = '\n'; - WriteFile(GetStdHandle(kNtStdErrorHandle), name, len, 0, 0); - STRACE("terminating on %s", name); + intptr_t hStderr; + const char *signame; + char *end, sigbuf[15], output[16]; + signame = strsignal_r(sig, sigbuf); + STRACE("terminating due to uncaught %s", signame); + hStderr = GetStdHandle(kNtStdErrorHandle); + end = stpcpy(stpcpy(output, signame), "\n"); + WriteFile(hStderr, output, end - output, 0, 0); if (_weaken(__restore_console_win32)) { _weaken(__restore_console_win32)(); } diff --git a/libc/calls/calls.h b/libc/calls/calls.h index 9fa9f03ce..e81f9def6 100644 --- a/libc/calls/calls.h +++ b/libc/calls/calls.h @@ -193,6 +193,7 @@ unsigned geteuid(void) nosideeffect; unsigned getgid(void) nosideeffect; unsigned getuid(void) libcesque; unsigned sleep(unsigned); +unsigned ualarm(unsigned, unsigned); unsigned umask(unsigned); void sync(void); diff --git a/libc/calls/createfileflags.c b/libc/calls/createfileflags.c index 297a2ffc8..70acc7219 100644 --- a/libc/calls/createfileflags.c +++ b/libc/calls/createfileflags.c @@ -35,7 +35,7 @@ #define _O_DIRECTORY 0x00010000 // kNtFileFlagBackupSemantics #define _O_TMPFILE 0x00410000 // AttributeTemporary|FlagDeleteOnClose #define _O_DIRECT 0x00004000 // kNtFileFlagNoBuffering -#define _O_NONBLOCK 0x00000800 // kNtFileFlagWriteThrough +#define _O_NONBLOCK 0x00000800 // kNtFileFlagWriteThrough (not sent to win32) #define _O_RANDOM 0x80000000 // kNtFileFlagRandomAccess #define _O_SEQUENTIAL 0x40000000 // kNtFileFlagSequentialScan #define _O_COMPRESSED 0x20000000 // kNtFileAttributeCompressed @@ -62,16 +62,22 @@ textwindows int GetNtOpenFlags(int flags, int mode, uint32_t *out_perm, break; case O_WRONLY: perm = kNtFileGenericWrite; + if (flags & _O_APPEND) { + // kNtFileAppendData is already present in kNtFileGenericWrite. + // WIN32 wont act on append access when write is already there. + perm = kNtFileAppendData; + } break; case O_RDWR: - perm = kNtFileGenericRead | kNtFileGenericWrite; + if (flags & _O_APPEND) { + perm = kNtFileGenericRead | kNtFileAppendData; + } else { + perm = kNtFileGenericRead | kNtFileGenericWrite; + } break; default: return einval(); } - if (flags & _O_APPEND) { - perm = kNtFileAppendData; // todo: this is part of generic write above - } attr = 0; is_creating_file = (flags & _O_CREAT) || (flags & _O_TMPFILE) == _O_TMPFILE; diff --git a/libc/calls/fchmodat-nt.c b/libc/calls/fchmodat-nt.c index ee9966a9f..2e52eede7 100644 --- a/libc/calls/fchmodat-nt.c +++ b/libc/calls/fchmodat-nt.c @@ -26,7 +26,7 @@ textwindows int sys_fchmodat_nt(int dirfd, const char *path, uint32_t mode, uint16_t path16[PATH_MAX]; if (__mkntpathat(dirfd, path, 0, path16) == -1) return -1; if ((attr = GetFileAttributes(path16)) != -1u) { - if (mode & 0200) { + if (mode & 0222) { attr &= ~kNtFileAttributeReadonly; } else { attr |= kNtFileAttributeReadonly; diff --git a/libc/calls/fcntl-nt.c b/libc/calls/fcntl-nt.c index 3866824ce..a0df32741 100644 --- a/libc/calls/fcntl-nt.c +++ b/libc/calls/fcntl-nt.c @@ -26,10 +26,13 @@ #include "libc/calls/wincrash.internal.h" #include "libc/errno.h" #include "libc/intrin/kmalloc.h" +#include "libc/intrin/kprintf.h" #include "libc/intrin/weaken.h" #include "libc/limits.h" #include "libc/log/backtrace.internal.h" #include "libc/macros.internal.h" +#include "libc/nt/createfile.h" +#include "libc/nt/enum/fileflagandattributes.h" #include "libc/nt/enum/filelockflags.h" #include "libc/nt/errors.h" #include "libc/nt/files.h" @@ -310,52 +313,63 @@ static textwindows int sys_fcntl_nt_dupfd(int fd, int cmd, int start) { return sys_dup_nt(fd, -1, (cmd == F_DUPFD_CLOEXEC ? O_CLOEXEC : 0), start); } -static textwindows int sys_fcntl_nt_setfl(int fd, unsigned *flags, unsigned arg, - unsigned supported) { - unsigned old, neu, changed, other, allowed; - old = *flags & supported; - other = *flags & ~supported; - neu = arg & supported; - changed = old ^ neu; - // you may change the following access mode flags: +static textwindows int sys_fcntl_nt_setfl(int fd, unsigned *flags, + unsigned mode, unsigned arg, + intptr_t *handle) { + + // you may change the following: // // - O_NONBLOCK make read() raise EAGAIN - // - O_NDELAY same thing as O_NONBLOCK - // - O_ACCMODE but has a minimal effect + // - O_APPEND for toggling append mode + // - O_RANDOM alt. for posix_fadvise() + // - O_SEQUENTIAL alt. for posix_fadvise() + // - O_DIRECT works but haven't tested // - allowed = O_ACCMODE | O_NONBLOCK; - if (changed & ~allowed) { - // the following access mode flags are supported, but it's currently - // not possible to change them on windows. - // - // - O_APPEND tried to support but failed - // - O_RANDOM use posix_fadvise() instead - // - O_SEQUENTIAL use posix_fadvise() instead - // - O_DIRECT possibly in future? - // - O_DSYNC possibly in future? - // - O_RSYNC possibly in future? - // - O_SYNC possibly in future? - // - return enotsup(); + // the other bits are ignored. + unsigned allowed = O_APPEND | O_SEQUENTIAL | O_RANDOM | O_DIRECT | O_NONBLOCK; + unsigned needreo = O_APPEND | O_SEQUENTIAL | O_RANDOM | O_DIRECT; + unsigned newflag = (*flags & ~allowed) | (arg & allowed); + + if ((*flags & needreo) ^ (arg & needreo)) { + unsigned perm, share, attr; + if (GetNtOpenFlags(newflag, mode, &perm, &share, 0, &attr) == -1) { + return -1; + } + // MSDN says only these are allowed, otherwise it returns EINVAL. + attr &= kNtFileFlagBackupSemantics | kNtFileFlagDeleteOnClose | + kNtFileFlagNoBuffering | kNtFileFlagOpenNoRecall | + kNtFileFlagOpenReparsePoint | kNtFileFlagOverlapped | + kNtFileFlagPosixSemantics | kNtFileFlagRandomAccess | + kNtFileFlagSequentialScan | kNtFileFlagWriteThrough; + intptr_t hand; + if ((hand = ReOpenFile(*handle, perm, share, attr)) != -1) { + if (hand != *handle) { + CloseHandle(*handle); + *handle = hand; + } + } else { + return __winerr(); + } } + // 1. ignore flags that aren't access mode flags // 2. return zero if nothing's changed - *flags = other | neu; + *flags = newflag; return 0; } textwindows int sys_fcntl_nt(int fd, int cmd, uintptr_t arg) { int rc; uint32_t flags; - int access_mode_flags = O_ACCMODE | O_APPEND | O_ASYNC | O_DIRECT | - O_NOATIME | O_NONBLOCK | O_RANDOM | O_SEQUENTIAL; if (__isfdkind(fd, kFdFile) || // __isfdkind(fd, kFdSocket) || // __isfdkind(fd, kFdConsole)) { if (cmd == F_GETFL) { - rc = g_fds.p[fd].flags & access_mode_flags; + rc = g_fds.p[fd].flags & (O_ACCMODE | O_APPEND | O_DIRECT | O_NONBLOCK | + O_RANDOM | O_SEQUENTIAL); } else if (cmd == F_SETFL) { - rc = sys_fcntl_nt_setfl(fd, &g_fds.p[fd].flags, arg, access_mode_flags); + rc = sys_fcntl_nt_setfl(fd, &g_fds.p[fd].flags, g_fds.p[fd].mode, arg, + &g_fds.p[fd].handle); } else if (cmd == F_GETFD) { if (g_fds.p[fd].flags & O_CLOEXEC) { rc = FD_CLOEXEC; diff --git a/libc/calls/fcntl.c b/libc/calls/fcntl.c index 9cd24dce1..915040a30 100644 --- a/libc/calls/fcntl.c +++ b/libc/calls/fcntl.c @@ -27,9 +27,9 @@ #include "libc/intrin/describeflags.internal.h" #include "libc/intrin/strace.internal.h" #include "libc/intrin/weaken.h" +#include "libc/runtime/zipos.internal.h" #include "libc/sysv/consts/f.h" #include "libc/sysv/errfuns.h" -#include "libc/runtime/zipos.internal.h" /** * Does things with file descriptor, e.g. @@ -57,6 +57,12 @@ * - `F_SETFD` sets `FD_CLOEXEC` status of `arg` file descriptor * - `F_GETFL` returns file descriptor status flags * - `F_SETFL` sets file descriptor status flags + * - `O_NONBLOCK` may be changed + * - `O_APPEND` may be changed + * - `O_DIRECT` may be changed + * - `O_SEQUENTIAL` may be changed (no-op on non-Windows) + * - `O_RANDOM` may be changed (no-op on non-Windows) + * - Other bits (`O_ACCMODE`, `O_CREAT`, etc.) are ignored * - `F_DUPFD` is like dup() but `arg` is a minimum result, e.g. 3 * - `F_DUPFD_CLOEXEC` ditto but sets `O_CLOEXEC` on returned fd * - `F_SETLK` for record locking where `arg` is `struct flock *` @@ -131,11 +137,15 @@ int fcntl(int fd, int cmd, ...) { } #ifdef SYSDEBUG - if (cmd == F_GETFD || // - cmd == F_GETOWN || // - cmd == F_FULLFSYNC || // - cmd == F_BARRIERFSYNC || // - cmd == F_MAXFD) { + if (rc != -1 && cmd == F_GETFL) { + STRACE("fcntl(%d, F_GETFL) → %s", fd, DescribeOpenFlags(rc)); + } else if (cmd == F_SETFL) { + STRACE("fcntl(%d, F_SETFL, %s) → %d% m", fd, DescribeOpenFlags(arg), rc); + } else if (cmd == F_GETFD || // + cmd == F_GETOWN || // + cmd == F_FULLFSYNC || // + cmd == F_BARRIERFSYNC || // + cmd == F_MAXFD) { STRACE("fcntl(%d, %s) → %d% m", fd, DescribeFcntlCmd(cmd), rc); } else if (cmd == F_GETFL) { STRACE("fcntl(%d, %s) → %s% m", fd, DescribeFcntlCmd(cmd), diff --git a/libc/calls/fstatat.c b/libc/calls/fstatat.c index fe5262bca..2a0b6ddfa 100644 --- a/libc/calls/fstatat.c +++ b/libc/calls/fstatat.c @@ -52,27 +52,31 @@ static inline const char *__strace_fstatat_flags(char buf[12], int flags) { * file is a relative path, then `path` becomes relative to `dirfd` * @param st is where the result is stored * @param flags can have `AT_SYMLINK_NOFOLLOW` - * @param st is where result is stored * @raise EACCES if denied access to component in path prefix * @raise EIO if i/o error occurred while reading from filesystem * @raise ELOOP if a symbolic link loop exists in `path` * @raise ENAMETOOLONG if a component in `path` exceeds `NAME_MAX` * @raise ENOENT on empty string or if component in path doesn't exist * @raise ENOTDIR if a parent component existed that wasn't a directory + * @raise EILSEQ if `path` contains illegal UTF-8 sequences (Windows/MacOS) * @raise ENOTDIR if `path` is relative and `dirfd` isn't an open directory + * @raise ENOEXEC if `path` is a zip path and this executable isn't a zip file * @raise EOVERFLOW shouldn't be possible on 64-bit systems * @raise ELOOP may ahappen if `SYMLOOP_MAX` symlinks were dereferenced * @raise ENAMETOOLONG may happen if `path` exceeded `PATH_MAX` + * @raise EFAULT if `path` or `st` point to invalid memory + * @raise EINVAL if `flags` has unsupported bits * @return 0 on success, or -1 w/ errno * @see S_ISDIR(st.st_mode), S_ISREG() * @asyncsignalsafe * @vforksafe */ int fstatat(int dirfd, const char *path, struct stat *st, int flags) { - /* execve() depends on this */ + // execve() depends on this int rc; struct ZiposUri zipname; - if (IsAsan() && !__asan_is_valid(st, sizeof(*st))) { + if (IsAsan() && (!__asan_is_valid_str(path) || // + !__asan_is_valid(st, sizeof(*st)))) { rc = efault(); } else if (flags & ~AT_SYMLINK_NOFOLLOW) { return einval(); diff --git a/libc/calls/ioctl.c b/libc/calls/ioctl.c index bf40ebd0f..12f35b71d 100644 --- a/libc/calls/ioctl.c +++ b/libc/calls/ioctl.c @@ -596,68 +596,68 @@ static int ioctl_siocgifflags(int fd, void *arg) { /** * Performs special i/o operation on file descriptor. * - * @param request can be any of: + * The following i/o requests are available. * - * - `FIONREAD` takes an `int *` and returns how many bytes of input - * are available on a terminal or socket, waiting to be read. + * - `FIONREAD` takes an `int *` and returns how many bytes of input are + * available on a terminal/socket/pipe, waiting to be read. Be sure to + * only use it on the reading end of a pipe. * - * - `TIOCGWINSZ` populates `struct winsize *` with the dimensions - * of your teletypewriter. It's an alias for tcgetwinsize(). + * - `TIOCGWINSZ` populates `struct winsize *` with the dimensions of + * your teletypewriter. It's an alias for tcgetwinsize(). * - * - `TIOCSWINSZ` with the dimensions of your teletypewriter to - * `struct winsize *`. It's an alias for tcsetwinsize(). + * - `TIOCSWINSZ` with the dimensions of your teletypewriter to `struct + * winsize *`. It's an alias for tcsetwinsize(). * - * - `TIOCOUTQ` takes an `int *` and returns the number of bytes in - * the terminal's output buffer. Only available on UNIX. + * - `TIOCOUTQ` takes an `int *` and returns the number of bytes in the + * terminal's output buffer. Only available on UNIX. * - * - `TIOCSTI` takes a `const char *` and may be used to fake input - * to a tty. This API isn't available on OpenBSD. Only available - * on UNIX. + * - `TIOCSTI` takes a `const char *` and may be used to fake input to a + * tty. This API isn't available on OpenBSD. Only available on UNIX. * - * - `TIOCNOTTY` takes an `int tty_fd` arg and makes it the - * controlling terminal of the calling process, which should have - * called setsid() beforehand. + * - `TIOCNOTTY` takes an `int tty_fd` arg and makes it the controlling + * terminal of the calling process, which should have called setsid() + * beforehand. * - * - `TIOCNOTTY` to give up the controlling terminal. Only available - * on UNIX. + * - `TIOCNOTTY` to give up the controlling terminal. Only available on + * UNIX. * - * - `TIOCNXCL` to give up exclusive mode on terminal. Only - * available on UNIX. + * - `TIOCNXCL` to give up exclusive mode on terminal. Only available on + * UNIX. * - * - `SIOCGIFCONF` takes an struct ifconf object of a given size, - * whose arg is `struct ifconf *`. It implements the Linux style - * and modifies the following: - * - ifc_len: set it to the number of valid ifreq structures - * representingthe interfaces - * - ifc_ifcu.ifcu_req: sets the name of the interface for each - * interface - * The ifc_len is an input/output parameter: set it to the total - * size of the ifcu_buf (ifcu_req) buffer on input. + * - `SIOCGIFCONF` takes an struct ifconf object of a given size, + * whose arg is `struct ifconf *`. It implements the Linux style + * and modifies the following: + * - ifc_len: set it to the number of valid ifreq structures + * representingthe interfaces + * - ifc_ifcu.ifcu_req: sets the name of the interface for each + * interface + * The ifc_len is an input/output parameter: set it to the total + * size of the ifcu_buf (ifcu_req) buffer on input. * - * - `SIOCGIFNETMASK` populates a `struct ifconf *` record with the - * network interface mask. This data structure should be obtained - * by calling `SIOCGIFCONF`. + * - `SIOCGIFNETMASK` populates a `struct ifconf *` record with the + * network interface mask. This data structure should be obtained by + * calling `SIOCGIFCONF`. * - * - `SIOCGIFBRDADDR` populates a `struct ifconf *` record with the - * network broadcast addr. This data structure should be obtained - * by calling `SIOCGIFCONF`. + * - `SIOCGIFBRDADDR` populates a `struct ifconf *` record with the + * network broadcast addr. This data structure should be obtained by + * calling `SIOCGIFCONF`. * - * - `FIONBIO` isn't polyfilled; use `fcntl(F_SETFL, O_NONBLOCK)` - * - `FIOCLEX` isn't polyfilled; use `fcntl(F_SETFD, FD_CLOEXEC)` - * - `FIONCLEX` isn't polyfilled; use `fcntl(F_SETFD, 0)` - * - `TCGETS` isn't polyfilled; use tcgetattr() - * - `TCSETS` isn't polyfilled; use tcsetattr() - * - `TCSETSW` isn't polyfilled; use tcsetattr() - * - `TCSETSF` isn't polyfilled; use tcsetattr() - * - `TCXONC` isn't polyfilled; use tcflow() - * - `TCSBRK` isn't polyfilled; use tcdrain() - * - `TCFLSH` isn't polyfilled; use tcflush() - * - `TIOCGPTN` isn't polyfilled; use ptsname() - * - `TIOCGSID` isn't polyfilled; use tcgetsid() - * - `TCSBRK` isn't polyfilled; use tcsendbreak() - * - `TCSBRK` isn't polyfilled; use tcsendbreak() - * - `TIOCSPGRP` isn't polyfilled; use tcsetpgrp() - * - `TIOCSPTLCK` isn't polyfilled; use unlockpt() + * - `FIONBIO` isn't polyfilled; use `fcntl(F_SETFL, O_NONBLOCK)` + * - `FIOCLEX` isn't polyfilled; use `fcntl(F_SETFD, FD_CLOEXEC)` + * - `FIONCLEX` isn't polyfilled; use `fcntl(F_SETFD, 0)` + * - `TCGETS` isn't polyfilled; use tcgetattr() + * - `TCSETS` isn't polyfilled; use tcsetattr() + * - `TCSETSW` isn't polyfilled; use tcsetattr() + * - `TCSETSF` isn't polyfilled; use tcsetattr() + * - `TCXONC` isn't polyfilled; use tcflow() + * - `TCSBRK` isn't polyfilled; use tcdrain() + * - `TCFLSH` isn't polyfilled; use tcflush() + * - `TIOCGPTN` isn't polyfilled; use ptsname() + * - `TIOCGSID` isn't polyfilled; use tcgetsid() + * - `TCSBRK` isn't polyfilled; use tcsendbreak() + * - `TCSBRK` isn't polyfilled; use tcsendbreak() + * - `TIOCSPGRP` isn't polyfilled; use tcsetpgrp() + * - `TIOCSPTLCK` isn't polyfilled; use unlockpt() * * @restartable * @vforksafe diff --git a/libc/calls/mkdirat-nt.c b/libc/calls/mkdirat-nt.c index 5faa232e8..ff80df40f 100644 --- a/libc/calls/mkdirat-nt.c +++ b/libc/calls/mkdirat-nt.c @@ -16,15 +16,12 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall_support-nt.internal.h" #include "libc/nt/files.h" -#include "libc/str/str.h" -#include "libc/sysv/errfuns.h" textwindows int sys_mkdirat_nt(int dirfd, const char *path, uint32_t mode) { - int e; - char16_t *p, path16[PATH_MAX]; - /* if (strlen(path) > 248) return enametoolong(); */ + char16_t path16[PATH_MAX]; if (__mkntpathat(dirfd, path, 0, path16) == -1) return -1; if (CreateDirectory(path16, 0)) return 0; return __fix_enotdir(-1, path16); diff --git a/libc/calls/mkntpath.c b/libc/calls/mkntpath.c index 17d5dbe17..3948cafb8 100644 --- a/libc/calls/mkntpath.c +++ b/libc/calls/mkntpath.c @@ -18,6 +18,8 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/ntmagicpaths.internal.h" #include "libc/calls/syscall_support-nt.internal.h" +#include "libc/dce.h" +#include "libc/intrin/asan.internal.h" #include "libc/intrin/strace.internal.h" #include "libc/macros.internal.h" #include "libc/nt/systeminfo.h" @@ -75,22 +77,21 @@ textwindows int __mkntpath(const char *path, */ textwindows int __mkntpath2(const char *path, char16_t path16[hasatleast PATH_MAX], int flags) { - /* - * 1. Need +1 for NUL-terminator - * 2. Need +1 for UTF-16 overflow - * 3. Need ≥2 for SetCurrentDirectory trailing slash requirement - * 4. Need ≥13 for mkdir() i.e. 1+8+3+1, e.g. "\\ffffffff.xxx\0" - * which is an "8.3 filename" from the DOS days - */ - const char *q; - bool isdospath; - char16_t c, *p; - size_t i, j, n, m, x, z; - if (!path) return efault(); - path = FixNtMagicPath(path, flags); - p = path16; - q = path; + // 1. Need +1 for NUL-terminator + // 2. Need +1 for UTF-16 overflow + // 3. Need ≥2 for SetCurrentDirectory trailing slash requirement + // 4. Need ≥13 for mkdir() i.e. 1+8+3+1, e.g. "\\ffffffff.xxx\0" + // which is an "8.3 filename" from the DOS days + + if (!path || (IsAsan() && !__asan_is_valid_str(path))) { + return efault(); + } + path = FixNtMagicPath(path, flags); + + size_t x, z; + char16_t *p = path16; + const char *q = path; if (IsSlash(q[0]) && IsAlpha(q[1]) && IsSlash(q[2])) { z = MIN(32767, PATH_MAX); // turn "\c\foo" into "\\?\c:\foo" @@ -142,6 +143,7 @@ textwindows int __mkntpath2(const char *path, } // turn /tmp into GetTempPath() + size_t m; if (!x && IsSlash(q[0]) && q[1] == 't' && q[2] == 'm' && q[3] == 'p' && (IsSlash(q[4]) || !q[4])) { m = GetTempPath(z, p); @@ -154,23 +156,37 @@ textwindows int __mkntpath2(const char *path, } // turn utf-8 into utf-16 - n = tprecode8to16(p, z, q).ax; + size_t n = tprecode8to16(p, z, q).ax; if (n >= z - 1) { - STRACE("path too long for windows: %#s", path); return enametoolong(); } - // 1. turn `/` into `\` - // 2. turn `\\` into `\` if not at beginning + // normalize path + // we need it because \\?\... paths have to be normalized + // we don't remove the trailing slash since it is special + size_t i, j; for (j = i = 0; i < n; ++i) { - c = p[i]; + int c = p[i]; if (c == '/') { c = '\\'; } if (j > 1 && c == '\\' && p[j - 1] == '\\') { - continue; + // matched "^/" or "//" but not "^//" + } else if ((j && p[j - 1] == '\\') && // + c == '.' && // + (i + 1 == n || IsSlash(p[i + 1]))) { + // matched "/./" or "/.$" + i += !(i + 1 == n); + } else if ((j && p[j - 1] == '\\') && // + c == '.' && // + (i + 1 < n && p[i + 1] == '.') && // + (i + 2 == n || IsSlash(p[i + 2]))) { + // matched "/../" or "/..$" + while (j && p[j - 1] == '\\') --j; + while (j && p[j - 1] != '\\') --j; + } else { + p[j++] = c; } - p[j++] = c; } p[j] = 0; n = j; @@ -180,11 +196,11 @@ textwindows int __mkntpath2(const char *path, // To avoid toil like this: // - // CMD.EXE was started with the above path as the current directory. - // UNC paths are not supported. Defaulting to Windows directory. - // Access is denied. + // "CMD.EXE was started with the above path as the current + // directory. UNC paths are not supported. Defaulting to Windows + // directory. Access is denied." -Quoth CMD.EXE // - // Remove \\?\ prefix if we're within 260 character limit. + // Remove \\?\ prefix if we're within the 260 character limit. if (n > 4 && n < 260 && // path16[0] == '\\' && // path16[1] == '\\' && // @@ -194,10 +210,5 @@ textwindows int __mkntpath2(const char *path, n -= 4; } - // turn "foo\\." into "foo\\" - if (n > 2 && path16[n - 1] == u'.' && path16[n - 2] == u'\\') { - path16[--n] = 0; - } - return n; } diff --git a/libc/calls/mkntpathat.c b/libc/calls/mkntpathat.c index 5bdf76c32..098320181 100644 --- a/libc/calls/mkntpathat.c +++ b/libc/calls/mkntpathat.c @@ -29,9 +29,10 @@ int __mkntpathat(int dirfd, const char *path, int flags, char16_t file[hasatleast PATH_MAX]) { char16_t dir[PATH_MAX]; uint32_t dirlen, filelen; + if (!isutf8(path, -1)) return eilseq(); // thwart overlong nul in conversion if ((filelen = __mkntpath2(path, file, flags)) == -1) return -1; if (!filelen) return enoent(); - if (file[0] != u'\\' && dirfd != AT_FDCWD) { /* ProTip: \\?\C:\foo */ + if (file[0] != u'\\' && dirfd != AT_FDCWD) { // ProTip: \\?\C:\foo if (!__isfdkind(dirfd, kFdFile)) return ebadf(); dirlen = GetFinalPathNameByHandle(g_fds.p[dirfd].handle, dir, ARRAYLEN(dir), kNtFileNameNormalized | kNtVolumeNameDos); diff --git a/libc/calls/open-nt.c b/libc/calls/open-nt.c index 72d6c13bf..f30469b0f 100644 --- a/libc/calls/open-nt.c +++ b/libc/calls/open-nt.c @@ -22,34 +22,92 @@ #include "libc/calls/state.internal.h" #include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall_support-nt.internal.h" +#include "libc/errno.h" +#include "libc/intrin/kprintf.h" #include "libc/nt/createfile.h" +#include "libc/nt/enum/creationdisposition.h" #include "libc/nt/enum/fileflagandattributes.h" #include "libc/nt/enum/filetype.h" #include "libc/nt/files.h" +#include "libc/nt/process.h" #include "libc/nt/runtime.h" +#include "libc/nt/thunk/msabi.h" #include "libc/str/str.h" #include "libc/sysv/consts/fileno.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/errfuns.h" +__msabi extern typeof(GetFileAttributes) *const __imp_GetFileAttributesW; + static textwindows int64_t sys_open_nt_impl(int dirfd, const char *path, uint32_t flags, int32_t mode, uint32_t extra_attr) { + + // join(topath(dirfd), path) and translate from utf-8 to utf-16 char16_t path16[PATH_MAX]; - uint32_t perm, share, disp, attr; if (__mkntpathat(dirfd, path, flags, path16) == -1) { return kNtInvalidHandleValue; } + + // strip trailing slash + size_t n = strlen16(path16); + if (n > 1 && path16[n - 1] == '\\') { + // path denormalization only goes so far. when a trailing / or /. + // exists the kernel interprets that as having O_DIRECTORY intent + // furthermore, windows will throw an error on unc paths with it! + flags |= O_DIRECTORY; + path16[--n] = 0; + } + + // implement no follow flag + // you can't open symlinks; use readlink + // this flag only applies to the final path component + // if O_NOFOLLOW_ANY is passed (-1 on NT) it'll be rejected later + uint32_t fattr = __imp_GetFileAttributesW(path16); if (flags & O_NOFOLLOW) { - if ((attr = GetFileAttributes(path16)) != -1u && // - (attr & kNtFileAttributeReparsePoint)) { + if (fattr != -1u && (fattr & kNtFileAttributeReparsePoint)) { return eloop(); } - flags &= ~O_NOFOLLOW; + flags &= ~O_NOFOLLOW; // don't actually pass this to win32 } + + // handle some obvious cases while we have the attributes + // we should ideally resolve symlinks ourself before doing this + if (fattr != -1u) { + if (fattr & kNtFileAttributeDirectory) { + if ((flags & O_ACCMODE) != O_RDONLY || (flags & O_CREAT)) { + // tried to open directory for writing. note that our + // undocumented O_TMPFILE support on windows requires that a + // filename be passed, rather than a directory like linux. + return eisdir(); + } + // on posix, the o_directory flag is an advisory safeguard that + // isn't required. on windows, it's mandatory for opening a dir + flags |= O_DIRECTORY; + } else if (!(fattr & kNtFileAttributeReparsePoint)) { + // we know for certain file isn't a directory + if (flags & O_DIRECTORY) { + return enotdir(); + } + } + } + + // translate posix flags to win32 flags + uint32_t perm, share, disp, attr; if (GetNtOpenFlags(flags, mode, &perm, &share, &disp, &attr) == -1) { return kNtInvalidHandleValue; } + + // kNtTruncateExisting always returns kNtErrorInvalidParameter :'( + if (disp == kNtTruncateExisting) { + if (fattr != -1u) { + disp = kNtCreateAlways; // file exists (wish it could be more atomic) + } else { + return __fix_enotdir(enotdir(), path16); + } + } + + // open the file, following symlinks return __fix_enotdir(CreateFile(path16, perm, share, &kNtIsInheritable, disp, attr | extra_attr, 0), path16); diff --git a/libc/calls/openat.c b/libc/calls/openat.c index 903128eb7..7ee1d659c 100644 --- a/libc/calls/openat.c +++ b/libc/calls/openat.c @@ -24,6 +24,7 @@ #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/describeflags.internal.h" #include "libc/intrin/strace.internal.h" @@ -56,7 +57,7 @@ * * __static_yoink("zipos"); * - * Then you can read zip assets by adding a `"/zip/..."` prefix to `file`, e.g. + * Then you can read zip assets by adding a `"/zip/..."` prefix to `path`, e.g. * * // run `zip program.com hi.txt` beforehand * openat(AT_FDCWD, "/zip/hi.txt", O_RDONLY); @@ -64,10 +65,12 @@ * Cosmopolitan's general approach on Windows to path translation is to * * - replace `/' with `\` + * - normalize `.' and `..` * - translate utf-8 into utf-16 * - turn `"\X\foo"` into `"\\?\X:\foo"` * - turn `"\X"` into `"\\?\X:\"` * - turn `"X:\foo"` into `"\\?\X:\foo"` + * - turn `"\\?\X:\foo"` back into `X:\foo` if less than 260 chars * * On Windows, opening files in `/tmp` will open them in GetTempPath(), * which is a secure per-user directory. Opening `/dev/tty` will open a @@ -75,31 +78,24 @@ * which can't be fully closed. Opening `/dev/null` will open up `NUL`. * * @param dirfd is normally `AT_FDCWD` but if it's an open directory and - * `file` names a relative path then it's opened relative to `dirfd` - * @param file is a UTF-8 string naming filesystem entity, e.g. `foo/bar.txt`, - * which on Windows is bludgeoned into a WIN32 path automatically, e.g. - * - `foo/bar.txt` becomes `foo\bar.txt` - * - `/tmp/...` becomes whatever GetTempPath() is - * - `\\...` or `//...` is passed through to WIN32 unchanged - * - `/c/foo` or `\c\foo` becomes `\\?\c:\foo` - * - `c:/foo` or `c:\foo` becomes `\\?\c:\foo` - * - `/D` becomes `\\?\D:\` + * `path` names a relative path then it's opened relative to `dirfd` + * @param path is a UTF-8 string naming a filesystem entity * @param flags must have one of the following under the `O_ACCMODE` bits: - * - `O_RDONLY` to open `file` for reading only - * - `O_WRONLY` to open `file` for writing - * - `O_RDWR` to open `file` for reading and writing + * - `O_RDONLY` to open `path` for reading only + * - `O_WRONLY` to open `path` for writing + * - `O_RDWR` to open `path` for reading and writing * The following may optionally be bitwise or'd into `flags`: * - `O_CREAT` create file if it doesn't exist - * - `O_TRUNC` automatic `ftruncate(fd,0)` if exists + * - `O_TRUNC` automatic `ftruncate(fd,0)` if exists (atomic on unix) * - `O_CLOEXEC` automatic close() upon execve() * - `O_EXCL` exclusive access (see below) * - `O_APPEND` open file for appending only * - `O_NOFOLLOW` fail with ELOOP if it's a symlink * - `O_NONBLOCK` asks read/write to fail with `EAGAIN` rather than block * - `O_EXEC` open file for execution only; see fexecve() - * - `O_NOCTTY` prevents `file` possibly becoming controlling terminal + * - `O_NOCTTY` prevents `path` from becoming the controlling terminal + * - `O_DIRECTORY` advisory feature for avoiding accidentally opening files * - `O_DIRECT` it's complicated (not supported on Apple and OpenBSD) - * - `O_DIRECTORY` useful for stat'ing (hint on UNIX but required on NT) * - `O_DSYNC` it's complicated (zero on non-Linux/Apple) * - `O_RSYNC` it's complicated (zero on non-Linux/Apple) * - `O_VERIFY` it's complicated (zero on non-FreeBSD) @@ -131,40 +127,48 @@ * the executable bit is set thrice too * @return file descriptor (which needs to be close()'d), or -1 w/ errno * @raise EPERM if pledge() is in play w/o appropriate rpath/wpath/cpath - * @raise EACCES if unveil() is in play and didn't unveil your `file` path - * @raise EACCES if we don't have permission to search a component of `file` + * @raise EACCES if unveil() is in play and didn't unveil your `path` path + * @raise EACCES if we don't have permission to search a component of `path` * @raise EACCES if file exists but requested `flags & O_ACCMODE` was denied * @raise EACCES if file doesn't exist and parent dir lacks write permissions * @raise EACCES if `O_TRUNC` was specified in `flags` but writing was denied - * @raise ENOTSUP if `file` is on zip file system and `dirfd` isn't `AT_FDCWD` - * @raise ENOTDIR if a directory component in `file` exists as non-directory - * @raise ENOTDIR if `file` is relative and `dirfd` isn't an open directory - * @raise EROFS when writing is requested w/ `file` on read-only filesystem - * @raise ENAMETOOLONG if symlink-resolved `file` length exceeds `PATH_MAX` - * @raise ENAMETOOLONG if component in `file` exists longer than `NAME_MAX` - * @raise ENOTSUP if `file` is on zip file system and process is vfork()'d - * @raise ENOSPC if file system is full when `file` would be `O_CREAT`ed + * @raise ENOTSUP if `path` is on zip file system and `dirfd` isn't `AT_FDCWD` + * @raise ENOEXEC if `path` is a zip path and this executable isn't a zip file + * @raise ENOTDIR if a directory component in `path` exists as non-directory + * @raise ENOTDIR if `path` ends with a trailing slash and refers to a file + * @raise ENOTDIR if `path` is relative and `dirfd` isn't an open directory + * @raise ENOTDIR if `path` isn't a directory and `O_DIRECTORY` was passed + * @raise EILSEQ if `path` contains illegal UTF-8 sequences (Windows/MacOS) + * @raise EROFS when writing is requested w/ `path` on read-only filesystem + * @raise ENAMETOOLONG if symlink-resolved `path` length exceeds `PATH_MAX` + * @raise ENAMETOOLONG if component in `path` exists longer than `NAME_MAX` + * @raise ENAMETOOLONG if `path` is relative and longer than 260 characters + * @raise ENOTSUP if `path` is on zip file system and process is vfork()'d + * @raise ENOSPC if file system is full when `path` would be `O_CREAT`ed * @raise EINTR if we needed to block and a signal was delivered instead - * @raise EEXIST if `O_CREAT|O_EXCL` are used and `file` already existed + * @raise EEXIST if `O_CREAT|O_EXCL` are used and `path` already existed + * @raise EINVAL if ASCII control codes are used in `path` on Windows + * @raise EINVAL if `O_TRUNC` is specified in `O_RDONLY` mode + * @raise EINVAL if `flags` contains unsupported bits * @raise ECANCELED if thread was cancelled in masked mode - * @raise ENOENT if `file` doesn't exist when `O_CREAT` isn't in `flags` - * @raise ENOENT if `file` points to a string that's empty + * @raise ENOENT if `path` doesn't exist when `O_CREAT` isn't in `flags` + * @raise ENOENT if `path` points to a string that's empty * @raise ENOMEM if insufficient memory was available * @raise EMFILE if process `RLIMIT_NOFILE` has been reached * @raise ENFILE if system-wide file limit 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` - * @raise EISDIR if writing is requested and `file` names a directory + * @raise EOPNOTSUPP if `path` names a named socket + * @raise EFAULT if `path` points to invalid memory + * @raise ETXTBSY if writing is requested on `path` that's being executed + * @raise ELOOP if `flags` had `O_NOFOLLOW` and `path` is a symbolic link + * @raise ELOOP if a loop was detected resolving components of `path` + * @raise EISDIR if writing is requested and `path` names a directory * @cancellationpoint * @asyncsignalsafe * @restartable * @threadsafe * @vforksafe */ -int openat(int dirfd, const char *file, int flags, ...) { +int openat(int dirfd, const char *path, int flags, ...) { int rc; va_list va; unsigned mode; @@ -174,32 +178,48 @@ int openat(int dirfd, const char *file, int flags, ...) { va_end(va); BEGIN_CANCELLATION_POINT; - if (file && (!IsAsan() || __asan_is_valid_str(file))) { - if (!__isfdkind(dirfd, kFdZip)) { - if (_weaken(__zipos_open) && - _weaken(__zipos_parseuri)(file, &zipname) != -1) { - if (!__vforked && dirfd == AT_FDCWD) { - rc = _weaken(__zipos_open)(&zipname, flags); - } else { - rc = enotsup(); /* TODO */ - } - } else if (!IsWindows() && !IsMetal()) { - rc = sys_openat(dirfd, file, flags, mode); - } else if (IsMetal()) { - rc = sys_openat_metal(dirfd, file, flags, mode); - } else { - rc = sys_open_nt(dirfd, file, flags, mode); - } - } else { - rc = enotsup(); /* TODO */ - } - } else { + if (!path || (IsAsan() && !__asan_is_valid_str(path))) { rc = efault(); + } else if (__isfdkind(dirfd, kFdZip)) { + rc = enotsup(); // TODO + } else if (_weaken(__zipos_open) && + _weaken(__zipos_parseuri)(path, &zipname) != -1) { + if (!__vforked && dirfd == AT_FDCWD) { + rc = _weaken(__zipos_open)(&zipname, flags); + } else { + rc = enotsup(); // TODO + } + } else if ((flags & O_ACCMODE) == O_RDONLY && (flags & O_TRUNC)) { + rc = einval(); // Every OS except OpenBSD actually does this D: + } else if (IsLinux() || IsXnu() || IsFreebsd() || IsOpenbsd() || IsNetbsd()) { + rc = sys_openat(dirfd, path, flags, mode); + if (IsFreebsd()) { + // Address FreeBSD divergence from IEEE Std 1003.1-2008 (POSIX.1) + // in the case when O_NOFOLLOW is used, but fails due to symlink. + if (rc == -1 && errno == EMLINK) { + errno = ELOOP; + } + } + if (IsNetbsd()) { + // Address NetBSD divergence from IEEE Std 1003.1-2008 (POSIX.1) + // in the case when O_NOFOLLOW is used but fails due to symlink. + if (rc == -1 && errno == EFTYPE) { + errno = ELOOP; + } + } + } else if (IsMetal()) { + rc = sys_openat_metal(dirfd, path, flags, mode); + } else if (IsWindows()) { + rc = sys_open_nt(dirfd, path, flags, mode); + } else { + rc = enosys(); } END_CANCELLATION_POINT; - STRACE("openat(%s, %#s, %s, %#o) → %d% m", DescribeDirfd(dirfd), file, + STRACE("openat(%s, %#s, %s, %#o) → %d% m", DescribeDirfd(dirfd), path, DescribeOpenFlags(flags), (flags & (O_CREAT | O_TMPFILE)) ? mode : 0, rc); return rc; } + +__strong_reference(openat, openat64); diff --git a/libc/calls/pipe-nt.c b/libc/calls/pipe-nt.c index 7698dbbc1..1321343ef 100644 --- a/libc/calls/pipe-nt.c +++ b/libc/calls/pipe-nt.c @@ -58,11 +58,11 @@ textwindows int sys_pipe_nt(int pipefd[2], unsigned flags) { if ((hout = CreateFile(pipename, kNtGenericWrite, 0, &kNtIsInheritable, kNtOpenExisting, kNtFileFlagOverlapped, 0)) != -1) { g_fds.p[reader].kind = kFdFile; - g_fds.p[reader].flags = flags; + g_fds.p[reader].flags = O_RDONLY | flags; g_fds.p[reader].mode = 0010444; g_fds.p[reader].handle = hin; g_fds.p[writer].kind = kFdFile; - g_fds.p[writer].flags = flags; + g_fds.p[writer].flags = O_WRONLY | flags; g_fds.p[writer].mode = 0010222; g_fds.p[writer].handle = hout; pipefd[0] = reader; diff --git a/libc/calls/pipe2.c b/libc/calls/pipe2.c index 2cfcc625c..5a75571f0 100644 --- a/libc/calls/pipe2.c +++ b/libc/calls/pipe2.c @@ -30,7 +30,8 @@ * This function offers atomic operation on all supported platforms * except for XNU and RHEL5 where it's polyfilled. * - * @params flags may contain `O_CLOEXEC`, `O_NONBLOCK`, and `O_DIRECT` + * @params flags may contain `O_CLOEXEC`, `O_NONBLOCK`, or the non-POSIX + * packet mode flag `O_DIRECT`, which is `EINVAL` on MacOS / OpenBSD * @raise EINVAL if flags has invalid or unsupported bits * @raise EFAULT if `pipefd` doesn't point to valid memory * @raise EMFILE if process `RLIMIT_NOFILE` has been reached @@ -41,7 +42,7 @@ */ int pipe2(int pipefd[hasatleast 2], int flags) { int rc; - if (flags & ~(O_CLOEXEC | O_NONBLOCK | O_DIRECT)) { + if (flags & ~(O_CLOEXEC | O_NONBLOCK | (O_DIRECT != -1u ? O_DIRECT : 0))) { return einval(); } else if (!pipefd || (IsAsan() && !__asan_is_valid(pipefd, sizeof(int) * 2))) { diff --git a/libc/calls/posix_fadvise.c b/libc/calls/posix_fadvise.c index 8582b720e..1582e4280 100644 --- a/libc/calls/posix_fadvise.c +++ b/libc/calls/posix_fadvise.c @@ -18,6 +18,8 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" #include "libc/calls/calls.h" +#include "libc/calls/internal.h" +#include "libc/calls/struct/fd.internal.h" #include "libc/calls/syscall-nt.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" @@ -41,13 +43,16 @@ int sys_fadvise_netbsd(int, int, int64_t, int64_t, int) asm("sys_fadvise"); * @raise EBADF if `fd` isn't a valid file descriptor * @raise EINVAL if `advice` is invalid or `len` is huge * @raise ESPIPE if `fd` refers to a pipe + * @raise ENOTSUP if `fd` is a /zip file * @raise ENOSYS on XNU and OpenBSD * @returnserrno * @threadsafe */ errno_t posix_fadvise(int fd, int64_t offset, int64_t len, int advice) { int rc, e = errno; - if (IsLinux()) { + if (fd < g_fds.n && g_fds.p[fd].kind == kFdZip) { + rc = enotsup(); + } else if (IsLinux()) { rc = sys_fadvise(fd, offset, len, advice); } else if (IsFreebsd()) { rc = sys_fadvise(fd, offset, len, advice); diff --git a/libc/calls/pread.c b/libc/calls/pread.c index 6776e1f6c..7a124eb94 100644 --- a/libc/calls/pread.c +++ b/libc/calls/pread.c @@ -29,8 +29,8 @@ #include "libc/intrin/weaken.h" #include "libc/macros.internal.h" #include "libc/runtime/runtime.h" -#include "libc/sysv/errfuns.h" #include "libc/runtime/zipos.internal.h" +#include "libc/sysv/errfuns.h" /** * Reads from file at offset. @@ -71,6 +71,8 @@ ssize_t pread(int fd, void *buf, size_t size, int64_t offset) { (struct iovec[]){{buf, size}}, 1, offset); } else if (!IsWindows()) { rc = sys_pread(fd, buf, size, offset, offset); + } else if (__isfdkind(fd, kFdSocket)) { + rc = espipe(); } else if (__isfdkind(fd, kFdFile)) { rc = sys_read_nt(&g_fds.p[fd], (struct iovec[]){{buf, size}}, 1, offset); } else { diff --git a/libc/calls/preadv.c b/libc/calls/preadv.c index b98c598c9..ab64f22dc 100644 --- a/libc/calls/preadv.c +++ b/libc/calls/preadv.c @@ -59,7 +59,11 @@ static ssize_t Preadv(int fd, struct iovec *iov, int iovlen, int64_t off) { if (IsWindows()) { if (fd < g_fds.n) { - return sys_read_nt(g_fds.p + fd, iov, iovlen, off); + if (g_fds.p[fd].kind == kFdSocket) { + return espipe(); + } else { + return sys_read_nt(g_fds.p + fd, iov, iovlen, off); + } } else { return ebadf(); } diff --git a/libc/calls/pwrite.c b/libc/calls/pwrite.c index 475ad364f..db94c6dfe 100644 --- a/libc/calls/pwrite.c +++ b/libc/calls/pwrite.c @@ -64,6 +64,8 @@ ssize_t pwrite(int fd, const void *buf, size_t size, int64_t offset) { rc = ebadf(); } else if (!IsWindows()) { rc = sys_pwrite(fd, buf, size, offset, offset); + } else if (__isfdkind(fd, kFdSocket)) { + rc = espipe(); } else if (__isfdkind(fd, kFdFile)) { rc = sys_write_nt(fd, (struct iovec[]){{buf, size}}, 1, offset); } else { diff --git a/libc/calls/pwritev.c b/libc/calls/pwritev.c index c2cade35d..3e9e840f0 100644 --- a/libc/calls/pwritev.c +++ b/libc/calls/pwritev.c @@ -54,7 +54,11 @@ static ssize_t Pwritev(int fd, const struct iovec *iov, int iovlen, if (IsWindows()) { if (fd < g_fds.n) { - return sys_write_nt(fd, iov, iovlen, off); + if (g_fds.p[fd].kind == kFdSocket) { + return espipe(); + } else { + return sys_write_nt(fd, iov, iovlen, off); + } } else { return ebadf(); } diff --git a/libc/calls/renameat-nt.c b/libc/calls/renameat-nt.c index 8b094783d..3399bcc72 100644 --- a/libc/calls/renameat-nt.c +++ b/libc/calls/renameat-nt.c @@ -17,17 +17,74 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/syscall_support-nt.internal.h" +#include "libc/errno.h" +#include "libc/nt/enum/fileflagandattributes.h" #include "libc/nt/enum/movefileexflags.h" +#include "libc/nt/errors.h" #include "libc/nt/files.h" +#include "libc/nt/runtime.h" +#include "libc/nt/thunk/msabi.h" +#include "libc/str/str.h" +#include "libc/sysv/errfuns.h" + +__msabi extern typeof(GetFileAttributes) *const __imp_GetFileAttributesW; +__msabi extern typeof(RemoveDirectory) *const __imp_RemoveDirectoryW; + +static textwindows bool StripTrailingSlash(char16_t *path) { + size_t n = strlen16(path); + bool had_trailing_slash = false; + if (n > 1 && path[n - 1] == '\\') { + had_trailing_slash = true; + path[--n] = 0; + } + return had_trailing_slash; +} textwindows int sys_renameat_nt(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) { + + // translate unix to windows paths char16_t oldpath16[PATH_MAX]; char16_t newpath16[PATH_MAX]; if (__mkntpathat(olddirfd, oldpath, 0, oldpath16) == -1 || __mkntpathat(newdirfd, newpath, 0, newpath16) == -1) { return -1; } + + // strip trailing slash + // win32 will einval otherwise + // normally this is handled by __fix_enotdir() + bool old_must_be_dir = StripTrailingSlash(oldpath16); + bool new_must_be_dir = StripTrailingSlash(newpath16); + + // test for some known error conditions ahead of time + // the enotdir check can't be done reactively + // ideally we should resolve symlinks first + uint32_t oldattr = __imp_GetFileAttributesW(oldpath16); + uint32_t newattr = __imp_GetFileAttributesW(newpath16); + if ((old_must_be_dir && oldattr != -1u && + !(oldattr & kNtFileAttributeDirectory)) || + (new_must_be_dir && newattr != -1u && + !(newattr & kNtFileAttributeDirectory))) { + return enotdir(); + } + if (oldattr != -1u && newattr != -1u) { + if (!(oldattr & kNtFileAttributeDirectory) && + (newattr & kNtFileAttributeDirectory)) { + return eisdir(); // new is directory, but old isn't a directory + } else if ((oldattr & kNtFileAttributeDirectory) && + !(newattr & kNtFileAttributeDirectory)) { + return enotdir(); // old is directory, but new isn't a directory + } else if ((oldattr & kNtFileAttributeDirectory) && + (newattr & kNtFileAttributeDirectory)) { + // both old and new are directories + if (!__imp_RemoveDirectoryW(newpath16) && + GetLastError() == kNtErrorDirNotEmpty) { + return enotempty(); + } + } + } + if (MoveFileEx(oldpath16, newpath16, kNtMovefileReplaceExisting)) { return 0; } else { diff --git a/libc/calls/struct/timeval.h b/libc/calls/struct/timeval.h index 23642e7ca..2d59bf883 100644 --- a/libc/calls/struct/timeval.h +++ b/libc/calls/struct/timeval.h @@ -30,6 +30,7 @@ struct timeval timeval_add(struct timeval, struct timeval) pureconst; struct timeval timeval_sub(struct timeval, struct timeval) pureconst; struct timeval timeval_subz(struct timeval, struct timeval) pureconst; int64_t timeval_toseconds(struct timeval); +int64_t timeval_tomicros(struct timeval); struct timeval timespec_totimeval(struct timespec) pureconst; static inline struct timeval timeval_fromseconds(int64_t __x) { return (struct timeval){__x}; diff --git a/libc/calls/ualarm.c b/libc/calls/ualarm.c new file mode 100644 index 000000000..47ecc8233 --- /dev/null +++ b/libc/calls/ualarm.c @@ -0,0 +1,40 @@ +/*-*- 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 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 │ +│ 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/assert.h" +#include "libc/calls/calls.h" +#include "libc/calls/struct/itimerval.h" +#include "libc/calls/struct/timeval.h" +#include "libc/sysv/consts/itimer.h" + +/** + * Sets microsecond-precision alarm. + * + * This generates a `SIGALRM` signal after `usecs` microseconds. If + * `reload` is non-zero, then it'll keep generating `SIGALRM` every + * `reload` microseconds after the first signal. + * + * @asyncsignalsafe + */ +unsigned ualarm(unsigned usecs, unsigned reload) { + struct itimerval it, old; + it.it_value = timeval_frommicros(usecs); + it.it_interval = timeval_frommicros(reload); + npassert(!setitimer(ITIMER_REAL, &it, &old)); + return timeval_tomicros(old.it_value); +} diff --git a/libc/calls/unlinkat-nt.c b/libc/calls/unlinkat-nt.c index d65bb2fe0..6cced09da 100644 --- a/libc/calls/unlinkat-nt.c +++ b/libc/calls/unlinkat-nt.c @@ -33,48 +33,7 @@ #include "libc/nt/synchronization.h" #include "libc/str/str.h" #include "libc/sysv/consts/at.h" - -/** - * Performs synchronization on directory of pathname. - * - * This code is intended to help prevent subsequent i/o operations - * from failing for no reason at all. For example a unit test that - * repeatedly opens and unlinks the same filename. - */ -static textwindows int SyncDirectory(int df, char16_t path[PATH_MAX], int n) { - int rc; - int64_t fh; - char16_t *p; - if ((p = memrchr16(path, '\\', n))) { - if (p - path == 2 && path[1] == ':') return 0; // XXX: avoid syncing volume - *p = 0; - } else { - if (df != AT_FDCWD) { - if (FlushFileBuffers(df)) { - return 0; - } else { - return -1; - } - } - path[0] = '.'; - path[1] = 0; - } - if ((fh = CreateFile( - path, kNtFileGenericWrite, - kNtFileShareRead | kNtFileShareWrite | kNtFileShareDelete, 0, - kNtOpenExisting, kNtFileAttributeNormal | kNtFileFlagBackupSemantics, - 0)) != -1) { - if (FlushFileBuffers(fh)) { - rc = 0; - } else { - rc = -1; - } - CloseHandle(fh); - } else { - rc = -1; - } - return rc; -} +#include "libc/sysv/errfuns.h" static textwindows bool IsDirectorySymlink(const char16_t *path) { int e; @@ -102,13 +61,11 @@ static textwindows int sys_rmdir_nt(const char16_t *path) { if (RemoveDirectory(path)) { return 0; } - /* - * Files can linger, for absolutely no reason. - * Possibly some Windows Defender bug on Win7. - * Sleep for up to one second w/ expo backoff. - * Alternative is use Microsoft internal APIs. - * Never could have imagined it'd be this bad. - */ + // Files can linger, for absolutely no reason. + // Possibly some Windows Defender bug on Win7. + // Sleep for up to one second w/ expo backoff. + // Alternative is use Microsoft internal APIs. + // Never could have imagined it'd be this bad. if (GetLastError() == kNtErrorDirNotEmpty && ms <= 2048) { errno = e; Sleep(ms); @@ -130,19 +87,45 @@ static textwindows int sys_unlink_nt(const char16_t *path) { } } -textwindows int sys_unlinkat_nt(int dirfd, const char *path, int flags) { - int n, rc; - char16_t path16[PATH_MAX]; - if ((n = __mkntpathat(dirfd, path, 0, path16)) == -1) { - rc = -1; - } else if (flags & AT_REMOVEDIR) { - rc = sys_rmdir_nt(path16); +textwindows int sys_unlinkat_nt_impl(const char16_t *path, int flags) { + if (flags & AT_REMOVEDIR) { + return sys_rmdir_nt(path); } else { - rc = sys_unlink_nt(path16); - if (rc != -1) { - // TODO(jart): prove that it helps first - // rc = SyncDirectory(dirfd, path16, n); + return sys_unlink_nt(path); + } +} + +textwindows int sys_unlinkat_nt(int dirfd, const char *path, int flags) { + char16_t path16[PATH_MAX]; + + // check validity of flags + if (flags & ~AT_REMOVEDIR) { + return einval(); + } + + // translate unix to windows path + int n; + if ((n = __mkntpathat(dirfd, path, 0, path16)) == -1) { + return -1; + } + + // optimistic first attempt + int e = errno; + int rc = sys_unlinkat_nt_impl(path16, flags); + + // reactively ensure unlink() deletes read-only files + if (rc == -1 && errno == kNtErrorAccessDenied) { + uint32_t attr; + if ((attr = GetFileAttributes(path16)) != -1u && + (attr & kNtFileAttributeReadonly) && + SetFileAttributes(path16, attr & ~kNtFileAttributeReadonly)) { + errno = e; + rc = sys_unlinkat_nt_impl(path16, flags); + } else { + errno = kNtErrorAccessDenied; } } + + // return status return __fix_enotdir(rc, path16); } diff --git a/libc/calls/weirdtypes.h b/libc/calls/weirdtypes.h index 3dc1e8b6f..edcbb701e 100644 --- a/libc/calls/weirdtypes.h +++ b/libc/calls/weirdtypes.h @@ -28,7 +28,7 @@ typedef int64_t register_t; typedef uint16_t sa_family_t; /* bsd:uint8_t */ typedef uint32_t socklen_t; typedef uint32_t speed_t; -typedef int64_t suseconds_t; /* int32_t on xnu */ +typedef uint32_t suseconds_t; typedef uint64_t useconds_t; /* uint32_t on xnu */ typedef int64_t syscall_arg_t; /* uint64_t on xnu */ typedef uint32_t tcflag_t; diff --git a/libc/calls/write-nt.c b/libc/calls/write-nt.c index 41d97dc53..59f9d4885 100644 --- a/libc/calls/write-nt.c +++ b/libc/calls/write-nt.c @@ -26,11 +26,13 @@ #include "libc/intrin/strace.internal.h" #include "libc/intrin/weaken.h" #include "libc/macros.internal.h" +#include "libc/nt/enum/filetype.h" #include "libc/nt/errors.h" #include "libc/nt/files.h" #include "libc/nt/runtime.h" #include "libc/nt/struct/overlapped.h" #include "libc/runtime/runtime.h" +#include "libc/sysv/consts/o.h" #include "libc/sysv/consts/sicode.h" #include "libc/sysv/consts/sig.h" #include "libc/sysv/errfuns.h" @@ -42,11 +44,21 @@ static textwindows ssize_t sys_write_nt_impl(int fd, void *data, size_t size, bool32 ok; uint32_t sent; int64_t handle; + if (g_fds.p[fd].kind == kFdConsole) { handle = g_fds.p[fd].extra; // get write end of console } else { handle = g_fds.p[fd].handle; } + + // don't use pread() or pwrite() on a pipe + if (offset != -1) { + uint32_t filetype = GetFileType(handle); + if (filetype == kNtFileTypeChar || filetype == kNtFileTypePipe) { + return espipe(); + } + } + size = MIN(size, 0x7ffff000); if (offset == -1) { // perform simple blocking write diff --git a/libc/intrin/createfile.c b/libc/intrin/createfile.c index 33e0d420f..d2fa41201 100644 --- a/libc/intrin/createfile.c +++ b/libc/intrin/createfile.c @@ -66,6 +66,7 @@ TryAgain: if ((dwDesiredAccess & kNtGenericExecute) && (dwCreationDisposition == kNtOpenExisting || dwCreationDisposition == kNtTruncateExisting)) { + NTTRACE("CreateFile removed kNtGenericExecute"); dwDesiredAccess &= ~kNtGenericExecute; goto TryAgain; } diff --git a/libc/intrin/describentfileaccessflags.c b/libc/intrin/describentfileaccessflags.c index a29c17066..b25cd9a4b 100644 --- a/libc/intrin/describentfileaccessflags.c +++ b/libc/intrin/describentfileaccessflags.c @@ -20,50 +20,59 @@ #include "libc/macros.internal.h" #include "libc/nt/enum/accessmask.h" #include "libc/nt/enum/filesharemode.h" +// clang-format off static const struct DescribeFlags kFileAccessflags[] = { - {kNtFileAllAccess, "FileAllAccess"}, // order matters - {kNtFileGenericRead, "FileGenericRead"}, // order matters - {kNtFileGenericWrite, "FileGenericWrite"}, // order matters - {kNtFileGenericExecute, "FileGenericExecute"}, // order matters - {kNtGenericRead, "GenericRead"}, // - {kNtGenericWrite, "GenericWrite"}, // - {kNtGenericExecute, "GenericExecute"}, // - {kNtGenericAll, "GenericAll"}, // - {kNtDelete, "Delete"}, // - {kNtReadControl, "ReadControl"}, // - {kNtWriteDac, "WriteDac"}, // - {kNtWriteOwner, "WriteOwner"}, // - {kNtSynchronize, "Synchronize"}, // - {kNtStandardRightsRequired, "StandardRightsRequired"}, // - {kNtAccessSystemSecurity, "AccessSystemSecurity"}, // - {kNtMaximumAllowed, "MaximumAllowed"}, // - {kNtFileReadData, "FileReadData"}, // - {kNtFileListDirectory, "FileListDirectory"}, // - {kNtFileWriteData, "FileWriteData"}, // - {kNtFileAddFile, "FileAddFile"}, // - {kNtFileAppendData, "FileAppendData"}, // - {kNtFileAddSubdirectory, "FileAddSubdirectory"}, // - {kNtFileCreatePipeInstance, "FileCreatePipeInstance"}, // - {kNtFileReadEa, "FileReadEa"}, // - {kNtFileWriteEa, "FileWriteEa"}, // - {kNtFileExecute, "FileExecute"}, // - {kNtFileTraverse, "FileTraverse"}, // - {kNtFileDeleteChild, "FileDeleteChild"}, // - {kNtFileReadAttributes, "FileReadAttributes"}, // - {kNtFileWriteAttributes, "FileWriteAttributes"}, // - {kNtTokenAssignPrimary, "TokenAssignPrimary"}, // - {kNtTokenDuplicate, "TokenDuplicate"}, // - {kNtTokenImpersonate, "TokenImpersonate"}, // - {kNtTokenQuery, "TokenQuery"}, // - {kNtTokenQuerySource, "TokenQuerySource"}, // - {kNtTokenAdjustPrivileges, "TokenAdjustPrivileges"}, // - {kNtTokenAdjustGroups, "TokenAdjustGroups"}, // - {kNtTokenAdjustDefault, "TokenAdjustDefault"}, // - {kNtTokenAdjustSessionid, "TokenAdjustSessionid"}, // + {kNtFileAllAccess, "kNtFileAllAccess"}, + {kNtFileGenericRead|kNtFileGenericWrite|kNtFileGenericExecute, + "kNtFileGenericRead|kNtFileGenericWrite|kNtFileGenericExecute"}, + {kNtFileGenericRead|kNtFileGenericWrite, + "kNtFileGenericRead|kNtFileGenericWrite"}, + {kNtFileGenericRead|kNtFileGenericExecute, + "kNtFileGenericRead|kNtFileGenericExecute"}, + {kNtFileGenericWrite|kNtFileGenericExecute, + "kNtFileGenericWrite|kNtFileGenericExecute"}, + {kNtFileGenericRead, "kNtFileGenericRead"}, + {kNtFileGenericWrite, "kNtFileGenericWrite"}, + {kNtFileGenericExecute, "kNtFileGenericExecute"}, + {kNtGenericRead, "kNtGenericRead"}, + {kNtGenericWrite, "kNtGenericWrite"}, + {kNtGenericExecute, "kNtGenericExecute"}, + {kNtGenericAll, "kNtGenericAll"}, + {kNtDelete, "kNtDelete"}, + {kNtReadControl, "kNtReadControl"}, + {kNtWriteDac, "kNtWriteDac"}, + {kNtWriteOwner, "kNtWriteOwner"}, + {kNtSynchronize, "kNtSynchronize"}, + {kNtStandardRightsRequired, "kNtStandardRightsRequired"}, + {kNtAccessSystemSecurity, "kNtAccessSystemSecurity"}, + {kNtMaximumAllowed, "kNtMaximumAllowed"}, + {kNtFileReadData, "kNtFileReadData"}, + {kNtFileListDirectory, "kNtFileListDirectory"}, + {kNtFileWriteData, "kNtFileWriteData"}, + {kNtFileAddFile, "kNtFileAddFile"}, + {kNtFileAppendData, "kNtFileAppendData"}, + {kNtFileAddSubdirectory, "kNtFileAddSubdirectory"}, + {kNtFileCreatePipeInstance, "kNtFileCreatePipeInstance"}, + {kNtFileReadEa, "kNtFileReadEa"}, + {kNtFileWriteEa, "kNtFileWriteEa"}, + {kNtFileExecute, "kNtFileExecute"}, + {kNtFileTraverse, "kNtFileTraverse"}, + {kNtFileDeleteChild, "kNtFileDeleteChild"}, + {kNtFileReadAttributes, "kNtFileReadAttributes"}, + {kNtFileWriteAttributes, "kNtFileWriteAttributes"}, + {kNtTokenAssignPrimary, "kNtTokenAssignPrimary"}, + {kNtTokenDuplicate, "kNtTokenDuplicate"}, + {kNtTokenImpersonate, "kNtTokenImpersonate"}, + {kNtTokenQuery, "kNtTokenQuery"}, + {kNtTokenQuerySource, "kNtTokenQuerySource"}, + {kNtTokenAdjustPrivileges, "kNtTokenAdjustPrivileges"}, + {kNtTokenAdjustGroups, "kNtTokenAdjustGroups"}, + {kNtTokenAdjustDefault, "kNtTokenAdjustDefault"}, + {kNtTokenAdjustSessionid, "kNtTokenAdjustSessionid"}, }; const char *(DescribeNtFileAccessFlags)(char buf[512], uint32_t x) { return DescribeFlags(buf, 512, kFileAccessflags, ARRAYLEN(kFileAccessflags), - "kNt", x); + "", x); } diff --git a/libc/intrin/strsignal_r.c b/libc/intrin/strsignal_r.c index bcea1a96d..09b57ed70 100644 --- a/libc/intrin/strsignal_r.c +++ b/libc/intrin/strsignal_r.c @@ -36,12 +36,16 @@ * @asyncsignalsafe * @threadsafe */ -privileged char *strsignal_r(int sig, char buf[hasatleast 15]) { +privileged dontdiscard char *strsignal_r(int sig, char buf[15]) { int i; char *p; const char *s; - if (!sig) return "0"; - if ((s = GetMagnumStr(kSignalNames, sig))) return s; + if (!sig) { + return "0"; + } + if ((s = GetMagnumStr(kSignalNames, sig))) { + return s; + } if (SIGRTMIN <= sig && sig <= SIGRTMAX) { sig -= SIGRTMIN; buf[0] = 'S'; diff --git a/libc/nt/createfile.h b/libc/nt/createfile.h index 9030fb3c4..ae1a11072 100644 --- a/libc/nt/createfile.h +++ b/libc/nt/createfile.h @@ -18,8 +18,7 @@ int64_t CreateFileA( uint32_t dwFlagsAndAttributes, /* libc/nt/enum/fileflagandattributes.h */ int64_t opt_hTemplateFile) paramsnonnull((1)); -int GetNtOpenFlags(int flags, int mode, uint32_t *out_perm, uint32_t *out_share, - uint32_t *out_disp, uint32_t *out_attr); +int GetNtOpenFlags(int, int, uint32_t *, uint32_t *, uint32_t *, uint32_t *); COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/runtime/clone.c b/libc/runtime/clone.c index 2df2e2a30..59065f263 100644 --- a/libc/runtime/clone.c +++ b/libc/runtime/clone.c @@ -20,6 +20,7 @@ #include "libc/assert.h" #include "libc/atomic.h" #include "libc/calls/calls.h" +#include "libc/calls/struct/sigset.h" #include "libc/calls/struct/ucontext-netbsd.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/wincrash.internal.h" @@ -195,6 +196,9 @@ XnuThreadMain(void *pthread, // rdi func(arg, tid); + // avoid signal handler being triggered after we trash our stack + _sigblockall(); + // we no longer use the stack after this point // %rax = int bsdthread_terminate(%rdi = void *stackaddr, // %rsi = size_t freesize, @@ -234,6 +238,8 @@ static wontreturn void FreebsdThreadMain(void *p) { struct CloneArgs *wt = p; *wt->ctid = wt->tid; wt->func(wt->arg, wt->tid); + // avoid signal handler being triggered after we trash our stack + _sigblockall(); // we no longer use the stack after this point // void thr_exit(%rdi = long *state); asm volatile("movl\t$0,%0\n\t" // *wt->ztid = 0 @@ -349,6 +355,8 @@ static wontreturn void NetbsdThreadMain(void *arg, // rdi ax = sys_gettid(); *ctid = ax; func(arg, ax); + // avoid signal handler being triggered after we trash our stack + _sigblockall(); // we no longer use the stack after this point // %eax = int __lwp_exit(void); asm volatile("movl\t$0,%2\n\t" // *wt->ztid = 0 diff --git a/libc/runtime/zipos-normpath.c b/libc/runtime/zipos-normpath.c index 9a6bdedce..b218cf2fd 100644 --- a/libc/runtime/zipos-normpath.c +++ b/libc/runtime/zipos-normpath.c @@ -34,7 +34,6 @@ size_t __zipos_normpath(char *d, const char *s, size_t n) { s[0] == '.' && // (!s[1] || s[1] == '/')) { // matched "/./" or "^.$" or "^./" or "/.$" - s += !!s[1]; } else if ((p == d || p[-1] == '/') && // s[0] == '.' && // s[1] == '.' && // diff --git a/libc/sock/sendmsg.c b/libc/sock/sendmsg.c index fa38bcb12..33cd8440d 100644 --- a/libc/sock/sendmsg.c +++ b/libc/sock/sendmsg.c @@ -76,7 +76,7 @@ ssize_t sendmsg(int fd, const struct msghdr *msg, int flags) { } else if (__isfdkind(fd, kFdSocket)) { rc = sys_sendto_nt(fd, msg->msg_iov, msg->msg_iovlen, flags, msg->msg_name, msg->msg_namelen); - } else if (__isfdkind(fd, kFdFile)) { + } else if (__isfdkind(fd, kFdFile)) { // e.g. socketpair rc = sys_write_nt(fd, msg->msg_iov, msg->msg_iovlen, -1); } else { rc = enotsock(); diff --git a/libc/sock/socketpair-nt.c b/libc/sock/socketpair-nt.c index 1800fc2d3..171724b16 100644 --- a/libc/sock/socketpair-nt.c +++ b/libc/sock/socketpair-nt.c @@ -80,12 +80,12 @@ textwindows int sys_socketpair_nt(int family, int type, int proto, int sv[2]) { if (h1 != -1) { g_fds.p[reader].kind = kFdFile; - g_fds.p[reader].flags = oflags; + g_fds.p[reader].flags = O_RDWR | oflags; g_fds.p[reader].mode = 0140444; g_fds.p[reader].handle = hpipe; g_fds.p[writer].kind = kFdFile; - g_fds.p[writer].flags = oflags; + g_fds.p[writer].flags = O_RDWR | oflags; g_fds.p[writer].mode = 0140222; g_fds.p[writer].handle = h1; diff --git a/libc/str/istext.c b/libc/str/istext.c index 460626572..66571f4dd 100644 --- a/libc/str/istext.c +++ b/libc/str/istext.c @@ -21,7 +21,7 @@ /** * Returns true if buffer is most likely plaintext. */ -bool _istext(const void *data, size_t size) { +bool istext(const void *data, size_t size) { const unsigned char *p, *pe; for (p = data, pe = p + size; p < pe; ++p) { if (*p <= 3) { diff --git a/libc/str/isutf8.c b/libc/str/isutf8.c index 446a07093..4231f1de1 100644 --- a/libc/str/isutf8.c +++ b/libc/str/isutf8.c @@ -21,8 +21,6 @@ #include "libc/intrin/likely.h" #include "libc/str/str.h" -typedef char xmm_t __attribute__((__vector_size__(16), __aligned__(16))); - static const char kUtf8Dispatch[] = { 0, 0, 1, 1, 1, 1, 1, 1, // 0300 utf8-2 1, 1, 1, 1, 1, 1, 1, 1, // 0310 @@ -37,10 +35,10 @@ static const char kUtf8Dispatch[] = { /** * Returns true if text is utf-8. * - * _isutf8 n=0 1 nanoseconds - * _isutf8 n=5 661 ps/byte 1,476 mb/s - * _isutf8 ascii n=22851 26 ps/byte 35 GB/s - * _isutf8 unicode n=3193 543 ps/byte 1,795 mb/s + * isutf8 n=0 1 nanoseconds + * isutf8 n=5 661 ps/byte 1,476 mb/s + * isutf8 ascii n=22851 26 ps/byte 35 GB/s + * isutf8 unicode n=3193 543 ps/byte 1,795 mb/s * * This function considers all ASCII characters including NUL to be * valid UTF-8. The conditions for something not being valid are: @@ -51,7 +49,7 @@ static const char kUtf8Dispatch[] = { * * @param size if -1 implies strlen */ -dontasan bool _isutf8(const void *data, size_t size) { +dontasan bool isutf8(const void *data, size_t size) { long c; unsigned m; const char *p, *e; @@ -61,6 +59,7 @@ dontasan bool _isutf8(const void *data, size_t size) { e = p + size; while (p < e) { #if defined(__x86_64__) && !defined(__chibicc__) + typedef char xmm_t __attribute__((__vector_size__(16), __aligned__(16))); if (!((intptr_t)p & 15)) { for (;;) { if ((m = __builtin_ia32_pmovmskb128(*(xmm_t *)p >= (xmm_t){0}) ^ diff --git a/libc/str/str.h b/libc/str/str.h index 8d3dcb466..071653de4 100644 --- a/libc/str/str.h +++ b/libc/str/str.h @@ -11,6 +11,8 @@ #define chomp16 _chomp16 #define wchomp _wchomp #define tpenc _tpenc +#define isutf8 _isutf8 +#define istext _istext #define startswith _startswith #define startswithi _startswithi #define endswith _endswith @@ -187,9 +189,9 @@ wchar_t *wchomp(wchar_t *) libcesque; bool startswith(const char *, const char *) strlenesque; bool startswithi(const char *, const char *) strlenesque; bool endswith(const char *, const char *) strlenesque; -bool _istext(const void *, size_t) libcesque; -bool _isutf8(const void *, size_t) libcesque; -char *strsignal_r(int, char[hasatleast 15]) returnsnonnull libcesque; +bool istext(const void *, size_t) libcesque; +bool isutf8(const void *, size_t) libcesque; +char *strsignal_r(int, char[15]) returnsnonnull libcesque dontdiscard; int strerror_wr(int, uint32_t, char *, size_t) dontthrow nocallback; char16_t *chomp16(char16_t *) libcesque; diff --git a/libc/sysv/consts.sh b/libc/sysv/consts.sh index eeadf5ad9..8902cb3d3 100755 --- a/libc/sysv/consts.sh +++ b/libc/sysv/consts.sh @@ -185,30 +185,28 @@ syscon open O_CREAT 0x00000040 0x00000040 0x00000200 0x00000200 0x000002 syscon open O_EXCL 0x00000080 0x00000080 0x00000800 0x00000800 0x00000800 0x00000800 0x00000800 0x00000080 # bsd consensus & NT faked as Linux [SYNC libc/calls/open-nt.c] syscon open O_TRUNC 0x00000200 0x00000200 0x00000400 0x00000400 0x00000400 0x00000400 0x00000400 0x00000200 # bsd consensus & NT faked as Linux [SYNC libc/calls/open-nt.c] syscon open O_DIRECTORY 0x00010000 0x00004000 0x00100000 0x00100000 0x00020000 0x00020000 0x00200000 0x00010000 # useful hint on UNIX, but required on NT (see kNtFileFlagBackupSemantics) [SYNC libc/calls/open-nt.c] -syscon open O_NOFOLLOW 0x00020000 0x00008000 0x00000100 0x00000100 0x00000100 0x00000100 0x00000100 0x00020000 # bsd consensus; kNtFileFlagOpenReparsePoint -syscon open O_DIRECT 0x00004000 0x00010000 0 0 0x00010000 0 0x00080000 0x00004000 # kNtFileFlagNoBuffering [SYNC libc/calls/open-nt.c] -syscon open O_NDELAY 0x00000800 0x00000800 0x00000004 0x00000004 0x00000004 0x00000004 0x00000004 0x00000800 # kNtFileFlagWriteThrough [SYNC libc/calls/open-nt.c] -syscon open O_RANDOM 0 0 0 0 0 0 0 0x80000000 # kNtFileFlagRandomAccess [SYNC libc/calls/open-nt.c] -syscon open O_SEQUENTIAL 0 0 0 0 0 0 0 0x40000000 # kNtFileFlagSequentialScan [SYNC libc/calls/open-nt.c] -syscon open O_COMPRESSED 0 0 0 0 0 0 0 0x20000000 # kNtFileAttributeCompressed [SYNC libc/calls/open-nt.c] +syscon open O_NOFOLLOW 0x00020000 0x00008000 0x00000100 0x00000100 0x00000100 0x00000100 0x00000100 0x00020000 # don't follow symlinks in the final path component; bsd consensus; kNtFileFlagOpenReparsePoint +syscon open O_DIRECT 0x00004000 0x00010000 0xffffffff 0xffffffff 0x00010000 0xffffffff 0x00080000 0x00004000 # kNtFileFlagNoBuffering [SYNC libc/calls/open-nt.c] +syscon open O_NONBLOCK 0x00000800 0x00000800 0x00000004 0x00000004 0x00000004 0x00000004 0x00000004 0x00000800 # same as O_NDELAY; overlaps with kNtFileFlagWriteThrough which we don't actually pass to win32 (we implement non-blocking ourselves using overlapped i/o) +syscon open O_RANDOM 0 0 0 0 0 0 0 0x80000000 # kNtFileFlagRandomAccess [SYNC libc/calls/open-nt.c] +syscon open O_SEQUENTIAL 0 0 0 0 0 0 0 0x40000000 # kNtFileFlagSequentialScan [SYNC libc/calls/open-nt.c] +syscon open O_COMPRESSED 0 0 0 0 0 0 0 0x20000000 # kNtFileAttributeCompressed [SYNC libc/calls/open-nt.c] syscon open O_INDEXED 0 0 0 0 0 0 0 0x10000000 # !kNtFileAttributeNotContentIndexed [SYNC libc/calls/open-nt.c] syscon open O_CLOEXEC 0x00080000 0x00080000 0x01000000 0x01000000 0x00100000 0x00010000 0x00400000 0x00080000 # NT faked as Linux [SYNC libc/calls/open-nt.c] syscon open O_TMPFILE 0x00410000 0x00404000 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffffffff # please use tmpfd(); Linux 3.11+ (c. 2013) __O_TMPFILE | O_DIRECTORY; kNtFileAttributeTemporary|kNtFileFlagDeleteOnClose [SYNC libc/calls/open-nt.c] -syscon open O_SPARSE 0 0 0 0 0 0 0 0 # wut -syscon open O_NONBLOCK 0x00000800 0x00000800 0x00000004 0x00000004 0x00000004 0x00000004 0x00000004 0x00000800 # bsd consensus -syscon open O_ASYNC 0x00002000 0x00002000 0x00000040 0x00000040 0x00000040 0x00000040 0x00000040 0 # bsd consensus -syscon open O_NOFOLLOW_ANY 0 0 0x20000000 0x20000000 0 0 0 0 # -syscon open O_SYNC 0x00101000 0x00101000 0x00000080 0x00000080 0x00000080 0x00000080 0x00000080 0 # bsd consensus +syscon open O_ASYNC 0x00002000 0x00002000 0x00000040 0x00000040 0x00000040 0x00000040 0x00000040 0xffffffff # bsd consensus +syscon open O_NOFOLLOW_ANY 0xffffffff 0xffffffff 0x20000000 0x20000000 0xffffffff 0xffffffff 0xffffffff 0xffffffff # don't follow symlinks in any path component +syscon open O_SYNC 0x00101000 0x00101000 0x00000080 0x00000080 0x00000080 0x00000080 0x00000080 0xffffffff # bsd consensus syscon open O_NOCTTY 0x00000100 0x00000100 0x00020000 0x00020000 0x00008000 0x00008000 0x00008000 0 # used for remote viewing (default behavior on freebsd) syscon open O_NOATIME 0x00040000 0x00040000 0 0 0 0 0 0 # optimize away access time update syscon open O_EXEC 0x00200000 0x00200000 0 0x40000000 0x00040000 0 0x04000000 0 # open only for executing (POSIX.1 hack for when file mode is 0111); see fexecve(); O_PATH on Linux -syscon open O_SEARCH 0 0 0 0x40100000 0x00040000 0 0x00800000 0 # it's specified by posix what does it mean -syscon open O_DSYNC 0x00001000 0x00001000 0x00400000 0x00400000 0 0x00000080 0x00010000 0 # -syscon open O_RSYNC 0x00101000 0x00101000 0 0 0 0x00000080 0x00020000 0 # -syscon open O_PATH 0x00200000 0x00200000 0 0 0 0 0 0 # Linux 2.6.39+ -syscon open O_VERIFY 0 0 0 0 0x00200000 0 0 0 # -syscon open O_SHLOCK 0 0 0x00000010 0x00000010 0x00000010 0x00000010 0x00000010 0 # -syscon open O_EXLOCK 0 0 0x00000020 0x00000020 0x00000020 0x00000020 0x00000020 0 # +syscon open O_SEARCH 0xffffffff 0xffffffff 0xffffffff 0x40100000 0x00040000 0xffffffff 0x00800000 0xffffffff # it's specified by posix what does it mean +syscon open O_DSYNC 0x00001000 0x00001000 0x00400000 0x00400000 0xffffffff 0x00000080 0x00010000 0xffffffff # +syscon open O_RSYNC 0x00101000 0x00101000 0xffffffff 0xffffffff 0xffffffff 0x00000080 0x00020000 0xffffffff # +syscon open O_PATH 0x00200000 0x00200000 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0xffffffff # Linux 2.6.39+ +syscon open O_VERIFY 0xffffffff 0xffffffff 0xffffffff 0xffffffff 0x00200000 0xffffffff 0xffffffff 0xffffffff # +syscon open O_SHLOCK 0xffffffff 0xffffffff 0x00000010 0x00000010 0x00000010 0x00000010 0x00000010 0xffffffff # +syscon open O_EXLOCK 0xffffffff 0xffffffff 0x00000020 0x00000020 0x00000020 0x00000020 0x00000020 0xffffffff # syscon open O_TTY_INIT 0 0 0 0 0x00080000 0 0 0 # syscon compat O_LARGEFILE 0x00008000 0x00020000 0 0 0 0 0 0 # @@ -1047,7 +1045,7 @@ syscon limits MAX_INPUT 255 255 1024 1024 255 255 255 255 # w syscon limits SOMAXCONN 4096 4096 128 128 128 128 128 2147483647 # maximum backlog for listen() syscon limits _ARG_MAX 128*1024 128*1024 1024*1024 1024*1024 512*1024 512*1024 256*1024 32767*2 # bsd consensus syscon limits _NAME_MAX 255 255 255 255 255 255 511 255 # probably higher on windows? -syscon limits _PATH_MAX 4096 4096 1024 1024 1024 1024 1024 512 # cosmopolitan libc imposes a lower 512 limit; nt theoretically goes up to 32767 +syscon limits _PATH_MAX 4096 4096 1024 1024 1024 1024 1024 32767 # win32 paths are 260 characters max. even with unc paths, cosmo wrappers won't go beyond 1024 chars syscon limits _NSIG 64 64 32 32 128 32 64 64 # _SIG_MAXSIG on FreeBSD # unmount() flags diff --git a/libc/sysv/consts/O_ASYNC.S b/libc/sysv/consts/O_ASYNC.S index 969b3696e..c5ce8a9e9 100644 --- a/libc/sysv/consts/O_ASYNC.S +++ b/libc/sysv/consts/O_ASYNC.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon open,O_ASYNC,0x00002000,0x00002000,0x00000040,0x00000040,0x00000040,0x00000040,0x00000040,0 +.syscon open,O_ASYNC,0x00002000,0x00002000,0x00000040,0x00000040,0x00000040,0x00000040,0x00000040,0xffffffff diff --git a/libc/sysv/consts/O_DIRECT.S b/libc/sysv/consts/O_DIRECT.S index 3c6b195a6..457f71503 100644 --- a/libc/sysv/consts/O_DIRECT.S +++ b/libc/sysv/consts/O_DIRECT.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon open,O_DIRECT,0x00004000,0x00010000,0,0,0x00010000,0,0x00080000,0x00004000 +.syscon open,O_DIRECT,0x00004000,0x00010000,0xffffffff,0xffffffff,0x00010000,0xffffffff,0x00080000,0x00004000 diff --git a/libc/sysv/consts/O_DSYNC.S b/libc/sysv/consts/O_DSYNC.S index b39c94e37..22e97a1d3 100644 --- a/libc/sysv/consts/O_DSYNC.S +++ b/libc/sysv/consts/O_DSYNC.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon open,O_DSYNC,0x00001000,0x00001000,0x00400000,0x00400000,0,0x00000080,0x00010000,0 +.syscon open,O_DSYNC,0x00001000,0x00001000,0x00400000,0x00400000,0xffffffff,0x00000080,0x00010000,0xffffffff diff --git a/libc/sysv/consts/O_EXLOCK.S b/libc/sysv/consts/O_EXLOCK.S index 84a182a10..19eb1b2f6 100644 --- a/libc/sysv/consts/O_EXLOCK.S +++ b/libc/sysv/consts/O_EXLOCK.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon open,O_EXLOCK,0,0,0x00000020,0x00000020,0x00000020,0x00000020,0x00000020,0 +.syscon open,O_EXLOCK,0xffffffff,0xffffffff,0x00000020,0x00000020,0x00000020,0x00000020,0x00000020,0xffffffff diff --git a/libc/sysv/consts/O_NDELAY.S b/libc/sysv/consts/O_NDELAY.S deleted file mode 100644 index cef260d32..000000000 --- a/libc/sysv/consts/O_NDELAY.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon open,O_NDELAY,0x00000800,0x00000800,0x00000004,0x00000004,0x00000004,0x00000004,0x00000004,0x00000800 diff --git a/libc/sysv/consts/O_NOFOLLOW_ANY.S b/libc/sysv/consts/O_NOFOLLOW_ANY.S index e0eeb5aa5..522c528f0 100644 --- a/libc/sysv/consts/O_NOFOLLOW_ANY.S +++ b/libc/sysv/consts/O_NOFOLLOW_ANY.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon open,O_NOFOLLOW_ANY,0,0,0x20000000,0x20000000,0,0,0,0 +.syscon open,O_NOFOLLOW_ANY,0xffffffff,0xffffffff,0x20000000,0x20000000,0xffffffff,0xffffffff,0xffffffff,0xffffffff diff --git a/libc/sysv/consts/O_PATH.S b/libc/sysv/consts/O_PATH.S index 8d48cd6fe..4901c63ae 100644 --- a/libc/sysv/consts/O_PATH.S +++ b/libc/sysv/consts/O_PATH.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon open,O_PATH,0x00200000,0x00200000,0,0,0,0,0,0 +.syscon open,O_PATH,0x00200000,0x00200000,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0xffffffff diff --git a/libc/sysv/consts/O_RSYNC.S b/libc/sysv/consts/O_RSYNC.S index 79a6b45bf..41b324a8f 100644 --- a/libc/sysv/consts/O_RSYNC.S +++ b/libc/sysv/consts/O_RSYNC.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon open,O_RSYNC,0x00101000,0x00101000,0,0,0,0x00000080,0x00020000,0 +.syscon open,O_RSYNC,0x00101000,0x00101000,0xffffffff,0xffffffff,0xffffffff,0x00000080,0x00020000,0xffffffff diff --git a/libc/sysv/consts/O_SEARCH.S b/libc/sysv/consts/O_SEARCH.S index 1f5a47ab0..a710a8d1f 100644 --- a/libc/sysv/consts/O_SEARCH.S +++ b/libc/sysv/consts/O_SEARCH.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon open,O_SEARCH,0,0,0,0x40100000,0x00040000,0,0x00800000,0 +.syscon open,O_SEARCH,0xffffffff,0xffffffff,0xffffffff,0x40100000,0x00040000,0xffffffff,0x00800000,0xffffffff diff --git a/libc/sysv/consts/O_SHLOCK.S b/libc/sysv/consts/O_SHLOCK.S index 023b3f7d1..c3bc349fc 100644 --- a/libc/sysv/consts/O_SHLOCK.S +++ b/libc/sysv/consts/O_SHLOCK.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon open,O_SHLOCK,0,0,0x00000010,0x00000010,0x00000010,0x00000010,0x00000010,0 +.syscon open,O_SHLOCK,0xffffffff,0xffffffff,0x00000010,0x00000010,0x00000010,0x00000010,0x00000010,0xffffffff diff --git a/libc/sysv/consts/O_SPARSE.S b/libc/sysv/consts/O_SPARSE.S deleted file mode 100644 index 394087b29..000000000 --- a/libc/sysv/consts/O_SPARSE.S +++ /dev/null @@ -1,2 +0,0 @@ -#include "libc/sysv/consts/syscon.internal.h" -.syscon open,O_SPARSE,0,0,0,0,0,0,0,0 diff --git a/libc/sysv/consts/O_SYNC.S b/libc/sysv/consts/O_SYNC.S index 5d687d45d..758a09820 100644 --- a/libc/sysv/consts/O_SYNC.S +++ b/libc/sysv/consts/O_SYNC.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon open,O_SYNC,0x00101000,0x00101000,0x00000080,0x00000080,0x00000080,0x00000080,0x00000080,0 +.syscon open,O_SYNC,0x00101000,0x00101000,0x00000080,0x00000080,0x00000080,0x00000080,0x00000080,0xffffffff diff --git a/libc/sysv/consts/O_VERIFY.S b/libc/sysv/consts/O_VERIFY.S index 1063ddd83..60bd669a1 100644 --- a/libc/sysv/consts/O_VERIFY.S +++ b/libc/sysv/consts/O_VERIFY.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon open,O_VERIFY,0,0,0,0,0x00200000,0,0,0 +.syscon open,O_VERIFY,0xffffffff,0xffffffff,0xffffffff,0xffffffff,0x00200000,0xffffffff,0xffffffff,0xffffffff diff --git a/libc/sysv/consts/_PATH_MAX.S b/libc/sysv/consts/_PATH_MAX.S index 05633c754..63962ab57 100644 --- a/libc/sysv/consts/_PATH_MAX.S +++ b/libc/sysv/consts/_PATH_MAX.S @@ -1,2 +1,2 @@ #include "libc/sysv/consts/syscon.internal.h" -.syscon limits,_PATH_MAX,4096,4096,1024,1024,1024,1024,1024,512 +.syscon limits,_PATH_MAX,4096,4096,1024,1024,1024,1024,1024,32767 diff --git a/libc/sysv/consts/o.h b/libc/sysv/consts/o.h index 35780fc41..efeb3cfcb 100644 --- a/libc/sysv/consts/o.h +++ b/libc/sysv/consts/o.h @@ -22,7 +22,6 @@ extern const unsigned O_EXEC; extern const unsigned O_EXLOCK; extern const unsigned O_INDEXED; extern const unsigned O_LARGEFILE; -extern const unsigned O_NDELAY; extern const unsigned O_NOATIME; extern const unsigned O_NOCTTY; extern const unsigned O_NOFOLLOW; @@ -34,7 +33,6 @@ extern const unsigned O_RSYNC; extern const unsigned O_SEARCH; extern const unsigned O_SEQUENTIAL; extern const unsigned O_SHLOCK; -extern const unsigned O_SPARSE; extern const unsigned O_SYNC; extern const unsigned O_TMPFILE; /* use tmpfd() or tmpfile() */ extern const unsigned O_TRUNC; @@ -46,13 +44,12 @@ extern const unsigned O_VERIFY; #define O_CLOEXEC O_CLOEXEC #define O_COMPRESSED O_COMPRESSED #define O_CREAT O_CREAT -#define O_DIRECT O_DIRECT #define O_DIRECTORY O_DIRECTORY #define O_EXCL O_EXCL #define O_EXEC O_EXEC #define O_INDEXED O_INDEXED #define O_LARGEFILE O_LARGEFILE -#define O_NDELAY O_NDELAY +#define O_NDELAY O_NONBLOCK #define O_NOATIME O_NOATIME #define O_NOCTTY O_NOCTTY #define O_NOFOLLOW O_NOFOLLOW diff --git a/libc/sysv/consts/sock.h b/libc/sysv/consts/sock.h index 07b3aa882..ab3a6545e 100644 --- a/libc/sysv/consts/sock.h +++ b/libc/sysv/consts/sock.h @@ -18,6 +18,8 @@ extern const int SOCK_STREAM; #define SOCK_RAW 3 #define SOCK_RDM 4 #define SOCK_SEQPACKET 5 +#define SOCK_CLOEXEC SOCK_CLOEXEC +#define SOCK_NONBLOCK SOCK_NONBLOCK COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/sysv/dos2errno.sh b/libc/sysv/dos2errno.sh index 36240fcac..c12e8995d 100755 --- a/libc/sysv/dos2errno.sh +++ b/libc/sysv/dos2errno.sh @@ -53,6 +53,7 @@ dos kNtErrorInvalidAddress EADDRNOTAVAIL dos kNtErrorNotAReparsePoint EINVAL dos kNtErrorInvalidFunction EINVAL dos kNtErrorNegativeSeek EINVAL +dos kNtErrorInvalidName 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 6947dd7a5..82a3ae4dc 100644 --- a/libc/sysv/dos2errno/EINVAL.S +++ b/libc/sysv/dos2errno/EINVAL.S @@ -14,4 +14,5 @@ kDos2Errno.EINVAL: .e kNtErrorNotAReparsePoint,EINVAL .e kNtErrorInvalidFunction,EINVAL .e kNtErrorNegativeSeek,EINVAL + .e kNtErrorInvalidName,EINVAL .e WSAEINVAL,EINVAL diff --git a/libc/testlib/testlib.h b/libc/testlib/testlib.h index 606da71bb..071e6780c 100644 --- a/libc/testlib/testlib.h +++ b/libc/testlib/testlib.h @@ -12,7 +12,7 @@ COSMOPOLITAN_C_START_ * Test cases are guaranteed by the linker to be run in order, sorted by * the (SUITE, NAME) tuple passed here. */ -#define TEST(SUITE, NAME) \ +#define TEST(SUITE, NAME) \ __static_yoink("__testcase_start"); \ __TEST_PROTOTYPE(SUITE, NAME, __TEST_ARRAY, ) @@ -25,7 +25,7 @@ COSMOPOLITAN_C_START_ * temorarilly by the runtime while calling fixture functions. Fixtures * are also guaranteed by the linker to be run in sorted order. */ -#define FIXTURE(SUITE, NAME) \ +#define FIXTURE(SUITE, NAME) \ __static_yoink("__fixture_start"); \ __FIXTURE("fixture", SUITE, NAME) @@ -36,7 +36,7 @@ COSMOPOLITAN_C_START_ * Cartesian product of groups. That makes this similar to fixture, but * more appropriate for testing pure code (i.e. no syscalls) like math. */ -#define COMBO(GROUP, ENTRY) \ +#define COMBO(GROUP, ENTRY) \ __static_yoink("__combo_start"); \ __FIXTURE("combo", GROUP, ENTRY) @@ -49,7 +49,7 @@ COSMOPOLITAN_C_START_ * * @see EZBENCH() */ -#define BENCH(SUITE, NAME) \ +#define BENCH(SUITE, NAME) \ __static_yoink("__bench_start"); \ __TEST_PROTOTYPE(SUITE, NAME, __BENCH_ARRAY, optimizespeed) @@ -223,11 +223,12 @@ void TearDownOnce(void); #define EXPECT_SYS(ERRNO, WANT, GOT, ...) \ do { \ - testlib_seterrno(0); \ + int e = testlib_geterrno(); \ __TEST_EQ(expect, __FILE__, __LINE__, __FUNCTION__, #WANT, #GOT, WANT, \ GOT, __VA_ARGS__); \ __TEST_EQ(expect, __FILE__, __LINE__, __FUNCTION__, #ERRNO, \ testlib_strerror(), ERRNO, testlib_geterrno(), __VA_ARGS__); \ + testlib_seterrno(e); \ } while (0) #define EXPECT_FALSE(X) _TEST2("EXPECT_FALSE", false, ==, (X), #X, "", "", 0) @@ -347,8 +348,6 @@ struct TestFixture { }; extern char g_fixturename[256]; -extern char g_testlib_olddir[PATH_MAX]; -extern char g_testlib_tmpdir[PATH_MAX]; extern bool g_testlib_shoulddebugbreak; /* set by testmain */ extern _Atomic(unsigned) g_testlib_ran; /* set by wrappers */ extern _Atomic(unsigned) g_testlib_failed; /* set by wrappers */ diff --git a/libc/testlib/testrunner.c b/libc/testlib/testrunner.c index 347ee0270..e4fe5dc3a 100644 --- a/libc/testlib/testrunner.c +++ b/libc/testlib/testrunner.c @@ -16,42 +16,27 @@ │ 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/state.internal.h" -#include "libc/calls/struct/sigaction.h" -#include "libc/calls/struct/sigset.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" #include "libc/errno.h" -#include "libc/fmt/fmt.h" #include "libc/fmt/itoa.h" -#include "libc/intrin/atomic.h" #include "libc/intrin/kprintf.h" #include "libc/intrin/strace.internal.h" #include "libc/intrin/weaken.h" -#include "libc/log/check.h" -#include "libc/log/internal.h" +#include "libc/limits.h" #include "libc/macros.internal.h" #include "libc/nt/process.h" -#include "libc/runtime/internal.h" #include "libc/runtime/runtime.h" -#include "libc/runtime/symbols.internal.h" -#include "libc/sock/sock.h" +#include "libc/stdio/rand.h" #include "libc/stdio/stdio.h" #include "libc/str/str.h" -#include "libc/sysv/consts/poll.h" -#include "libc/sysv/consts/sa.h" -#include "libc/sysv/consts/sig.h" -#include "libc/sysv/consts/w.h" #include "libc/testlib/testlib.h" #include "libc/thread/thread.h" #include "libc/x/x.h" -static int x; -char g_testlib_olddir[PATH_MAX]; -char g_testlib_tmpdir[PATH_MAX]; -struct sigaction wanthandlers[31]; +static char g_olddir[PATH_MAX]; +static char g_tmpdir[PATH_MAX]; static pthread_mutex_t testlib_error_lock; void testlib_finish(void) { @@ -85,139 +70,64 @@ wontreturn void testlib_abort(void) { } static void SetupTmpDir(void) { - char *p = g_testlib_tmpdir; - p = stpcpy(p, kTmpPath); - p = stpcpy(p, program_invocation_short_name), *p++ = '.'; - p = FormatInt64(p, getpid()), *p++ = '.'; - p = FormatInt64(p, x++); - p[0] = '\0'; - CHECK_NE(-1, makedirs(g_testlib_tmpdir, 0755), "%s", g_testlib_tmpdir); - CHECK_NOTNULL(realpath(g_testlib_tmpdir, g_testlib_tmpdir), "%`'s", - g_testlib_tmpdir); - CHECK_NE(-1, chdir(g_testlib_tmpdir), "%s", g_testlib_tmpdir); + char number[21]; + FormatInt64(number, _rand64() & INT64_MAX); + g_tmpdir[0] = 0; + if (*kTmpPath != '/') { + strlcat(g_tmpdir, g_olddir, sizeof(g_tmpdir)); + strlcat(g_tmpdir, "/", sizeof(g_tmpdir)); + } + strlcat(g_tmpdir, kTmpPath, sizeof(g_tmpdir)); + strlcat(g_tmpdir, program_invocation_short_name, sizeof(g_tmpdir)); + strlcat(g_tmpdir, ".", sizeof(g_tmpdir)); + strlcat(g_tmpdir, number, sizeof(g_tmpdir)); + if (makedirs(g_tmpdir, 0755) || chdir(g_tmpdir)) { + perror(g_tmpdir); + exit(1); + } } static void TearDownTmpDir(void) { - CHECK_NE(-1, chdir(g_testlib_olddir)); - CHECK_NE(-1, rmrf(g_testlib_tmpdir), "%s", g_testlib_tmpdir); -} - -static void DoNothing(int sig) { - // function intentionally empty -} - -static void CopySignalHandlers(void) { -#if 0 - int i; - for (i = 0; i < ARRAYLEN(wanthandlers); ++i) { - if (i + 1 == SIGKILL || i + 1 == SIGSTOP) continue; - CHECK_EQ(0, sigaction(i + 1, 0, wanthandlers + i), "sig=%d", i + 1); + if (chdir(g_olddir)) { + perror(g_olddir); + exit(1); } -#endif -} - -static void CheckSignalHandler(int sig) { -#if 0 - int i; - struct sigaction sa = {0}; - unassert(0 <= sig - 1 && sig - 1 < ARRAYLEN(wanthandlers)); - CHECK_EQ(0, sigaction(sig, 0, &sa)); - CHECK_EQ(0, memcmp(wanthandlers + sig - 1, &sa, sizeof(sa)), - "signal handler for %s was %p/%#x/%#x:%x " - "but should have been restored to %p/%#x/%#x:%x", - strsignal(sig), sa.sa_handler, sa.sa_flags, sa.sa_mask.__bits[0], - sa.sa_mask.__bits[1], wanthandlers[sig - 1].sa_handler, - wanthandlers[sig - 1].sa_flags, - wanthandlers[sig - 1].sa_mask.__bits[0], - wanthandlers[sig - 1].sa_mask.__bits[1]); -#endif -} - -static void CheckForSignalHandlers(void) { -#if 0 - CheckSignalHandler(SIGINT); - CheckSignalHandler(SIGQUIT); - CheckSignalHandler(SIGCHLD); - CheckSignalHandler(SIGFPE); - CheckSignalHandler(SIGILL); - CheckSignalHandler(SIGSEGV); - CheckSignalHandler(SIGTRAP); - CheckSignalHandler(SIGABRT); - CheckSignalHandler(SIGBUS); - CheckSignalHandler(SIGSYS); - CheckSignalHandler(SIGWINCH); -#endif -} - -static void CheckForFileDescriptors(void) { -#if 0 - // TODO: race condition on fd cleanup :'( - int i; - struct pollfd pfds[16]; - if (!_weaken(open) && !_weaken(socket)) return; - for (i = 0; i < ARRAYLEN(pfds); ++i) { - pfds[i].fd = i + 3; - pfds[i].events = POLLIN; + if (rmrf(g_tmpdir)) { + perror(g_tmpdir); + exit(1); } - if (poll(pfds, ARRAYLEN(pfds), 0) > 0) { - for (i = 0; i < ARRAYLEN(pfds); ++i) { - if (pfds[i].revents & POLLNVAL) continue; - ++g_testlib_failed; - kprintf("error: test failed to close() fd %d\n", pfds[i].fd); - } - } -#endif -} - -static void CheckForZombies(void) { -#if 0 - int e, pid; - sigset_t ss, oldss; - struct sigaction oldsa; - struct sigaction ignore = {.sa_handler = DoNothing}; - if (!_weaken(fork)) return; - for (;;) { - if ((pid = wait(0)) == -1) { - CHECK_EQ(ECHILD, errno); - break; - } else { - ++g_testlib_failed; - kprintf("error: test failed to reap zombies %d\n", pid); - } - } -#endif } /** * Runs all test case functions in sorted order. */ void testlib_runtestcases(testfn_t *start, testfn_t *end, testfn_t warmup) { - /* - * getpid() calls are inserted to help visually see tests in traces - * which can be performed on Linux, FreeBSD, OpenBSD, and XNU: - * - * strace -f o/default/test.com |& less - * truss o/default/test.com |& less - * ktrace -f trace o/default/test.com