Make improvements

- Introduce ualarm() function
- Make rename() report EISEMPTY on Windows
- Always raise EINVAL upon open(O_RDONLY|O_TRUNC)
- Add macro so ./configure will detect SOCK_CLOEXEC
- Fix O_TRUNC without O_CREAT not working on Windows
- Let fcntl(F_SETFL) change O_APPEND status on Windows
- Make sure pwrite() / pread() report ESPIPE on sockets
- Raise ESPIPE on Windows when pwrite() is used on pipe
- Properly compute O_APPEND CreateFile() flags on Windows
- Don't require O_DIRECTORY to open directories on Windows
- Fix more instances of Windows reporting EISDIR and ENOTDIR
- Normalize EFTYPE and EMLINK to ELOOP on NetBSD and FreeBSD
- Make unlink() / rmdir() work on read-only files on Windows
- Validate UTF-8 on Windows paths to fix bug with overlong NUL
- Always print signal name to stderr when crashing due to SIG_DFL
- Fix Windows bug where denormalized paths >260 chars didn't work
- Block signals on BSDs when thread exits before trashing its own stack
This commit is contained in:
Justine Tunney 2023-08-21 02:28:24 -07:00
parent ec957491ea
commit ebf784d4f5
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
76 changed files with 1019 additions and 568 deletions

View file

@ -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 <stdio.h>
#include <cosmo.h>
int main() {
ShowCrashReports();
printf("hello world\n");
__builtin_trap();
}
```

View file

@ -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)();
}

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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),

View file

@ -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();

View file

@ -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

View file

@ -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);

View file

@ -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;
}

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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))) {

View file

@ -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);

View file

@ -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 {

View file

@ -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();
}

View file

@ -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 {

View file

@ -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();
}

View file

@ -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 {

View file

@ -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};

40
libc/calls/ualarm.c Normal file
View file

@ -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);
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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

View file

@ -66,6 +66,7 @@ TryAgain:
if ((dwDesiredAccess & kNtGenericExecute) &&
(dwCreationDisposition == kNtOpenExisting ||
dwCreationDisposition == kNtTruncateExisting)) {
NTTRACE("CreateFile removed kNtGenericExecute");
dwDesiredAccess &= ~kNtGenericExecute;
goto TryAgain;
}

View file

@ -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);
}

View file

@ -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';

View file

@ -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) */

View file

@ -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

View file

@ -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] == '.' && //

View file

@ -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();

View file

@ -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;

View file

@ -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) {

View file

@ -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}) ^

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1,2 +0,0 @@
#include "libc/sysv/consts/syscon.internal.h"
.syscon open,O_NDELAY,0x00000800,0x00000800,0x00000004,0x00000004,0x00000004,0x00000004,0x00000004,0x00000800

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -1,2 +0,0 @@
#include "libc/sysv/consts/syscon.internal.h"
.syscon open,O_SPARSE,0,0,0,0,0,0,0,0

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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) */

View file

@ -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

View file

@ -14,4 +14,5 @@ kDos2Errno.EINVAL:
.e kNtErrorNotAReparsePoint,EINVAL
.e kNtErrorInvalidFunction,EINVAL
.e kNtErrorNegativeSeek,EINVAL
.e kNtErrorInvalidName,EINVAL
.e WSAEINVAL,EINVAL

View file

@ -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 */

View file

@ -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 </dev/null; kdump -f trace | less
* dtruss o/default/test.com |& less
*
* Test cases are iterable via a decentralized section. Your TEST()
* macro inserts .testcase.SUITENAME sections into the binary which
* the linker sorts into an array.
*
* @see ape/ape.lds
*/
// 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 </dev/null; kdump -f trace | less
// dtruss o/default/test.com |& less
//
// Test cases are iterable via a decentralized section. Your TEST()
// macro inserts .testcase.SUITENAME sections into the binary which
// the linker sorts into an array.
//
// @see ape/ape.lds
const testfn_t *fn;
CopySignalHandlers();
if (_weaken(testlib_enable_tmp_setup_teardown) ||
_weaken(testlib_enable_tmp_setup_teardown_once)) {
CHECK_NOTNULL(getcwd(g_testlib_olddir, sizeof(g_testlib_olddir)));
if (!getcwd(g_olddir, sizeof(g_olddir))) {
perror("getcwd");
exit(1);
}
}
if (_weaken(testlib_enable_tmp_setup_teardown_once)) {
SetupTmpDir();
}
if (_weaken(SetUpOnce)) _weaken(SetUpOnce)();
for (x = 0, fn = start; fn != end; ++fn) {
for (fn = start; fn != end; ++fn) {
STRACE("");
STRACE("# setting up %t", fn);
if (_weaken(testlib_enable_tmp_setup_teardown)) SetupTmpDir();
@ -237,9 +147,6 @@ void testlib_runtestcases(testfn_t *start, testfn_t *end, testfn_t warmup) {
if (!IsWindows()) sys_getpid();
if (_weaken(TearDown)) _weaken(TearDown)();
if (_weaken(testlib_enable_tmp_setup_teardown)) TearDownTmpDir();
CheckForFileDescriptors();
CheckForSignalHandlers();
CheckForZombies();
}
if (_weaken(TearDownOnce)) {
_weaken(TearDownOnce)();

View file

@ -17,10 +17,19 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/stdio/ftw.h"
#include "libc/sysv/errfuns.h"
#include "libc/x/x.h"
#include "libc/stdio/ftw.h"
static inline bool IsSlash(char c) {
return c == '/' || c == '\\';
}
static inline int IsAlpha(int c) {
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
}
static int rmrf_callback(const char *fpath, //
const struct stat *st, //
@ -48,6 +57,12 @@ static int rmrf_callback(const char *fpath, //
* @return 0 on success, or -1 w/ errno
*/
int rmrf(const char *path) {
if (path[0] == '/' && !path[1]) return enotsup();
if ((IsSlash(path[0]) && !path[1]) ||
(IsWindows() && ((IsSlash(path[0]) && IsAlpha(path[1]) &&
(!path[2] || (IsSlash(path[2]) && !path[3]))) ||
(IsAlpha(path[0]) && path[1] == ':' &&
(!path[2] || (IsSlash(path[2]) && !path[3])))))) {
return enotsup(); // if you really want rmrf("/") try rmrf("/.")
}
return nftw(path, rmrf_callback, 128, FTW_PHYS | FTW_DEPTH);
}

View file

@ -65,8 +65,25 @@ TEST(fcntl_getfl, testRemembersAccessMode) {
EXPECT_NE(-1, close(fd));
}
TEST(fcntl_setfl, testChangeAppendStatus) {
if (IsWindows()) return; // Can't ReOpenFile() w/ O_APPEND
TEST(fcntl_setfl, testChangeAppendStatus_proper) {
char buf[8] = {0};
ASSERT_SYS(0, 3, open("foo", O_CREAT | O_WRONLY, 0644));
// F_GETFL on XNU reports FWASWRITTEN (0x00010000) after write()
int old = fcntl(3, F_GETFL);
EXPECT_SYS(0, 3, write(3, "foo", 3));
EXPECT_SYS(0, 0, lseek(3, 0, SEEK_SET));
EXPECT_SYS(0, 0, fcntl(3, F_SETFL, old | O_APPEND));
EXPECT_SYS(0, 3, write(3, "bar", 3));
EXPECT_SYS(0, 0, lseek(3, 0, SEEK_SET));
EXPECT_SYS(0, 0, fcntl(3, F_SETFL, old));
EXPECT_SYS(0, 0, close(3));
ASSERT_SYS(0, 3, open("foo", 0));
EXPECT_SYS(0, 6, read(3, buf, 6));
EXPECT_STREQ("foobar", buf);
EXPECT_SYS(0, 0, close(3));
}
TEST(fcntl_setfl, testChangeAppendStatus_sloppy) {
char buf[8] = {0};
ASSERT_SYS(0, 3, open("foo", O_CREAT | O_WRONLY, 0644));
EXPECT_SYS(0, 3, write(3, "foo", 3));

View file

@ -29,6 +29,7 @@
#include "libc/sock/struct/ifreq.h"
#include "libc/stdio/stdio.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/consts/fio.h"
#include "libc/sysv/consts/ipproto.h"
#include "libc/sysv/consts/sio.h"
#include "libc/sysv/consts/sock.h"
@ -82,3 +83,18 @@ TEST(siocgifconf, mkntenvblock_systemroot) {
EXITS(0);
}
#endif
TEST(fionread, pipe) {
int pfds[2];
int pending;
ASSERT_SYS(0, 0, pipe(pfds));
ASSERT_SYS(0, 2, write(pfds[1], "hi", 2));
// checking the reading end is agreed upon
ASSERT_SYS(0, 0, ioctl(pfds[0], FIONREAD, &pending));
ASSERT_EQ(2, pending);
// checking the writing end is real hairy
// ASSERT_SYS(0, 0, ioctl(pfds[1], FIONREAD, &pending));
// ASSERT_EQ(2, pending);
ASSERT_SYS(0, 0, close(pfds[1]));
ASSERT_SYS(0, 0, close(pfds[0]));
}

View file

@ -23,7 +23,11 @@
#include "libc/limits.h"
#include "libc/log/check.h"
#include "libc/runtime/runtime.h"
#include "libc/sock/sock.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/consts/ipproto.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/sock.h"
#include "libc/testlib/subprocess.h"
#include "libc/testlib/testlib.h"
#include "libc/x/x.h"
@ -31,7 +35,7 @@
char testlib_enable_tmp_setup_teardown;
void SetUpOnce(void) {
ASSERT_SYS(0, 0, pledge("stdio rpath wpath cpath fattr proc", 0));
ASSERT_SYS(0, 0, pledge("stdio rpath wpath cpath fattr proc inet", 0));
}
TEST(lseek, ebadf) {
@ -59,14 +63,26 @@ TEST(lseek, 64bit) {
EXPECT_SYS(0, 0, close(3));
}
TEST(lseek, nonSeekableFd_espipe) {
TEST(lseek, isPipe_ESPIPE) {
int fds[2];
char buf[2];
ASSERT_SYS(0, 0, pipe(fds));
ASSERT_SYS(ESPIPE, -1, lseek(3, 0, SEEK_SET));
ASSERT_SYS(ESPIPE, -1, pwrite(4, "hi", 2, 0));
ASSERT_SYS(ESPIPE, -1, pread(3, buf, 2, 0));
EXPECT_SYS(0, 0, close(4));
EXPECT_SYS(0, 0, close(3));
}
TEST(lseek, isSocket_ESPIPE) {
char buf[2];
ASSERT_SYS(0, 3, socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
ASSERT_SYS(ESPIPE, -1, lseek(3, 0, SEEK_SET));
ASSERT_SYS(ESPIPE, -1, pwrite(3, "hi", 2, 0));
ASSERT_SYS(ESPIPE, -1, pread(3, buf, 2, 0));
EXPECT_SYS(0, 0, close(3));
}
TEST(lseek, filePositionChanges_areObservableAcrossDup) {
ASSERT_SYS(0, 3, creat("wut", 0644));
ASSERT_SYS(0, 4, dup(3));

View file

@ -18,14 +18,20 @@
*/
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/stat.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/macros.internal.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/testlib/testlib.h"
#include "libc/x/x.h"
#include "libc/x/xasprintf.h"
#define abs(rel) gc(xasprintf("%s/%s", gc(getcwd(0, 0)), rel))
char testlib_enable_tmp_setup_teardown;
@ -113,6 +119,7 @@ TEST(open, testOpenExistingForAppendWriteOnly_seeksToEnd) {
char buf[16] = {0};
ASSERT_SYS(0, 0, xbarf("hello.txt", "hell", -1));
ASSERT_SYS(0, 3, open("hello.txt", O_WRONLY | O_APPEND));
EXPECT_SYS(EBADF, -1, pread(3, buf, 4, 0)); // in O_WRONLY mode
EXPECT_SYS(0, 1, write(3, "o", 1));
EXPECT_SYS(0, 0, lseek(3, 0, SEEK_SET));
EXPECT_SYS(0, 1, write(3, "!", 1));
@ -123,6 +130,50 @@ TEST(open, testOpenExistingForAppendWriteOnly_seeksToEnd) {
EXPECT_SYS(0, 0, close(3));
}
TEST(open, appendRwMode_readsStartZero_writesAlwaysEof) {
char buf[8] = {0};
ASSERT_SYS(0, 0, xbarf("hello.txt", "hell", -1));
ASSERT_SYS(0, 3, open("hello.txt", O_RDWR | O_APPEND));
ASSERT_SYS(0, 2, read(3, buf, 2));
ASSERT_SYS(0, 2, read(3, buf + 2, 2));
EXPECT_STREQ("hell", buf);
EXPECT_SYS(0, 2, write(3, "o!", 2));
ASSERT_SYS(0, 6, pread(3, buf, 8, 0));
EXPECT_STREQ("hello!", buf);
EXPECT_SYS(0, 0, close(3));
}
TEST(open, appendReadOnlyMode_appendIsIgnored) {
char buf[8] = {0};
ASSERT_SYS(0, 0, xbarf("hello.txt", "hell", -1));
ASSERT_SYS(0, 3, open("hello.txt", O_RDONLY | O_APPEND));
EXPECT_SYS(EBADF, -1, write(3, "o!", 2)); // due to O_RDONLY
ASSERT_EQ(0, errno);
ASSERT_SYS(0, 2, read(3, buf, 2));
ASSERT_SYS(0, 2, read(3, buf + 2, 2));
EXPECT_STREQ("hell", buf);
ASSERT_SYS(0, 4, pread(3, buf, 4, 0));
EXPECT_SYS(0, 0, close(3));
}
TEST(open, truncReadWriteMode_getsTruncated) {
char buf[8] = {0};
ASSERT_FALSE(fileexists("hello.txt"));
ASSERT_SYS(ENOENT, -1, open("hello.txt", O_RDWR | O_TRUNC));
ASSERT_SYS(ENOENT, -1, open("hello.txt/there", O_RDWR | O_TRUNC));
ASSERT_SYS(0, 0, xbarf("hello.txt", "hell", -1));
ASSERT_SYS(ENOTDIR, -1, open("hello.txt/there", O_RDWR | O_TRUNC));
ASSERT_SYS(0, 3, open("hello.txt", O_RDWR | O_TRUNC));
ASSERT_SYS(0, 0, read(3, buf, 8));
EXPECT_STREQ("", buf);
ASSERT_SYS(0, 8, write(3, buf, 8));
EXPECT_SYS(0, 0, close(3));
}
TEST(open, truncReadOnlyMode_wontTruncate) {
ASSERT_SYS(EINVAL, -1, open("hello.txt", O_RDONLY | O_TRUNC));
}
TEST(open, testRelativePath_opensRelativeToDirFd) {
ASSERT_SYS(0, 0, mkdir("foo", 0755));
ASSERT_SYS(0, 3, open("foo", O_RDONLY | O_DIRECTORY));
@ -132,6 +183,104 @@ TEST(open, testRelativePath_opensRelativeToDirFd) {
EXPECT_SYS(0, 0, close(3));
}
TEST(open, eloop) {
ASSERT_SYS(0, 0, symlink("froot", "link"));
ASSERT_TRUE(issymlink("link"));
ASSERT_SYS(ELOOP, -1, open("link", O_RDONLY | O_NOFOLLOW));
}
TEST(open, norm) {
ASSERT_SYS(0, 0, mkdir("fun", 0755));
ASSERT_SYS(0, 0, mkdir("fun/house", 0755));
ASSERT_SYS(0, 0, touch("fun/house/norm", 0644));
ASSERT_SYS(0, 3, open("fun//house//norm", O_RDONLY));
ASSERT_SYS(0, 0, close(3));
ASSERT_SYS(0, 3, open(abs("fun//house//norm"), O_RDONLY));
ASSERT_SYS(0, 0, close(3));
ASSERT_SYS(0, 3, open("fun//house/./norm", O_RDONLY));
ASSERT_SYS(0, 0, close(3));
ASSERT_SYS(0, 3, open(abs("fun//house/./norm"), O_RDONLY));
ASSERT_SYS(0, 0, close(3));
ASSERT_SYS(0, 3, open("fun//house/../house/norm", O_RDONLY));
ASSERT_SYS(0, 0, close(3));
ASSERT_SYS(0, 3, open(abs("fun//house/../house/norm"), O_RDONLY));
ASSERT_SYS(0, 0, close(3));
ASSERT_SYS(ENOTDIR, -1, open("fun//house//norm/", O_RDONLY));
ASSERT_SYS(ENOTDIR, -1, open("fun//house//norm/.", O_RDONLY));
ASSERT_SYS(ENOTDIR, -1, open("fun//house//norm/./", O_RDONLY));
ASSERT_SYS(0, 3, open("fun//house//", O_RDONLY | O_DIRECTORY));
ASSERT_SYS(0, 0, close(3));
}
TEST(open, longNormDot) {
#define NAME \
"funfunfunfunfunfunfunfunfunfunfunfunfunfunfunfunfunfunfunfunfunfunfunfunfu" \
"nfunfunfunfunfunfunnfunfunfunfunfunfunnfunfunfunfunfunfununfunfunfunfunfun"
ASSERT_SYS(0, 0, mkdir(NAME, 0755));
ASSERT_SYS(0, 0, mkdir(abs(NAME "/" NAME), 0755));
ASSERT_SYS(0, 3, creat(abs(NAME "//" NAME "/./norm"), 0644));
ASSERT_TRUE(fileexists(abs(NAME "//" NAME "/norm")));
ASSERT_SYS(0, 0, close(3));
}
TEST(open, longNormDotDot) {
#define NAME \
"funfunfunfunfunfunfunfunfunfunfunfunfunfunfunfunfunfunfunfunfunfunfunfunfu" \
"nfunfunfunfunfunfunnfunfunfunfunfunfunnfunfunfunfunfunfununfunfunfunfunfun"
ASSERT_SYS(0, 0, mkdir(NAME, 0755));
ASSERT_SYS(0, 0, mkdir(abs(NAME "/" NAME), 0755));
ASSERT_SYS(0, 0, mkdir(abs(NAME "/" NAME "/" NAME), 0755));
ASSERT_SYS(0, 3, creat(abs(NAME "//" NAME "//" NAME "/../norm"), 0644));
ASSERT_TRUE(fileexists(abs(NAME "//" NAME "/norm")));
ASSERT_SYS(0, 0, close(3));
}
TEST(open, creat_directory) {
ASSERT_SYS(ENOENT, -1, open("fun", O_WRONLY | O_DIRECTORY));
ASSERT_FALSE(fileexists("fun"));
if (1) return; // linux 5.15.122-0-lts creates file and returns error D:
ASSERT_SYS(ENOTDIR, -1, open("fun", O_CREAT | O_WRONLY | O_DIRECTORY, 0644));
ASSERT_TRUE(fileexists("fun"));
}
TEST(open, O_DIRECTORY_preventsOpeningRegularFiles) {
ASSERT_SYS(0, 0, touch("file", 0644));
ASSERT_SYS(ENOTDIR, -1, open("file", O_WRONLY | O_DIRECTORY));
}
TEST(open, O_DIRECTORY_isNotARequirementToOpenDirectory) {
ASSERT_SYS(0, 0, mkdir("dir", 0755));
ASSERT_SYS(0, 3, open("dir", O_RDONLY));
ASSERT_SYS(0, 0, close(3));
}
TEST(open, openExistingDirectoryForWriting_raisesError) {
ASSERT_SYS(0, 0, mkdir("dir", 0755));
ASSERT_SYS(EISDIR, -1, open("dir", O_WRONLY));
}
TEST(open, nameWithControlCode) {
if (IsWindows()) {
ASSERT_SYS(EINVAL, -1, touch("hi\1there", 0755));
} else {
ASSERT_SYS(0, 0, touch("hi\1there", 0755));
}
}
TEST(open, nameWithOverlongNul_doesntCreateTruncatedName) {
if (IsXnu() || IsWindows()) {
// XNU is the only one that thought to restrict this. XNU chose
// EILSEQ which makes the most sense. Linux says it'll raise EINVAL
// if invalid characters are detected. Not sure yet which characters
// those are. POSIX says nothing about invalid charaters in open.
ASSERT_SYS(EILSEQ, -1, touch("hi\300\200there", 0755));
} else {
ASSERT_SYS(0, 0, touch("hi\300\200there", 0755));
ASSERT_TRUE(fileexists("hi\300\200there"));
ASSERT_FALSE(fileexists("hi"));
}
}
int CountFds(void) {
int i, count;
for (count = i = 0; i < g_fds.n; ++i) {
@ -154,3 +303,18 @@ TEST(open, lotsOfFds) {
EXPECT_SYS(0, 0, close(i));
}
}
static int64_t GetInode(const char *path) {
struct stat st;
ASSERT_SYS(0, 0, stat(path, &st));
return st.st_ino;
}
TEST(open, drive) {
if (!IsWindows()) return;
ASSERT_NE(GetInode("/"), GetInode("."));
ASSERT_EQ(GetInode("/"), GetInode("/c")); // sorry you have to run on c:/
ASSERT_EQ(GetInode("/"), GetInode("/c/"));
ASSERT_SYS(0, 3, open("/", O_RDONLY));
ASSERT_SYS(0, 0, close(3));
}

View file

@ -447,7 +447,7 @@ TEST(pledge, open_rpath) {
if (!pid) {
ASSERT_SYS(0, 0, pledge("stdio rpath", 0));
ASSERT_SYS(0, 3, open("foo", O_RDONLY));
ASSERT_SYS(EPERM, -1, open("foo", O_RDONLY | O_TRUNC));
ASSERT_SYS(EINVAL, -1, open("foo", O_RDONLY | O_TRUNC));
ASSERT_SYS(EPERM, -1, open("foo", O_RDONLY | O_TMPFILE));
ASSERT_SYS(EPERM, -1, open("foo", O_RDWR | O_TRUNC | O_CREAT, 0644));
ASSERT_SYS(EPERM, -1, open("foo", O_WRONLY | O_TRUNC | O_CREAT, 0644));

View file

@ -62,9 +62,6 @@ TEST(readlinkat, test) {
EXPECT_EQ(255, buf[8] & 255);
buf[8] = 0;
EXPECT_STREQ("hello→", buf);
p = gc(xjoinpaths(g_testlib_tmpdir, "hello→"));
q = gc(realpath("there→", 0));
EXPECT_EQ(0, strcmp(p, q), "%`'s\n\t%`'s", p, q);
}
TEST(readlinkat, efault) {
@ -89,10 +86,7 @@ TEST(readlinkat, frootloop) {
ASSERT_SYS(0, 0, symlink("froot", "froot"));
ASSERT_SYS(ELOOP, -1, readlink("froot/loop", buf, sizeof(buf)));
if (O_NOFOLLOW) {
ASSERT_SYS(IsFreebsd() ? EMLINK
: IsNetbsd() ? EFTYPE
: ELOOP,
-1, open("froot", O_RDONLY | O_NOFOLLOW));
ASSERT_SYS(ELOOP, -1, open("froot", O_RDONLY | O_NOFOLLOW));
if (0 && O_PATH) { /* need rhel5 test */
ASSERT_NE(-1, (fd = open("froot", O_RDONLY | O_NOFOLLOW | O_PATH)));
ASSERT_NE(-1, close(fd));

View file

@ -17,7 +17,9 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/runtime/runtime.h"
#include "libc/sysv/consts/at.h"
#include "libc/testlib/testlib.h"
#include "libc/x/x.h"
@ -42,18 +44,76 @@ TEST(renameat, enotdir) {
// EXPECT_SYS(ENOTDIR, -1, rename("zoo", "yo/there"));
}
TEST(rename, eisdir) {
// new is a directory but old is not a directory
ASSERT_SYS(0, 0, mkdir("foo", 0755));
ASSERT_SYS(0, 0, touch("foo/bar", 0644));
ASSERT_SYS(0, 0, touch("lol", 0644));
ASSERT_SYS(EISDIR, -1, rename("lol", "foo"));
}
TEST(rename, enotdir) {
// old is a directory but new is not a directory
ASSERT_SYS(0, 0, mkdir("lol", 0755));
ASSERT_SYS(0, 0, touch("foo", 0644));
ASSERT_SYS(ENOTDIR, -1, rename("lol", "foo"));
}
TEST(rename, eisdir_again) {
// old is a directory but new is not a directory
ASSERT_SYS(0, 0, touch("foo", 0644));
ASSERT_SYS(0, 0, touch("bar", 0644));
ASSERT_SYS(ENOTDIR, -1, rename("foo/", "bar"));
ASSERT_SYS(ENOTDIR, -1, rename("foo", "bar/"));
}
TEST(rename, moveDirectoryOverDirectory_replacesOldDirectory) {
ASSERT_SYS(0, 0, mkdir("foo//", 0755));
ASSERT_SYS(0, 0, mkdir("lol", 0755));
ASSERT_SYS(0, 0, rename("lol", "foo"));
ASSERT_TRUE(fileexists("foo"));
ASSERT_FALSE(fileexists("lol"));
}
TEST(rename, enotempty) {
// POSIX specifies EEXIST or ENOTEMPTY when new path is non-empty dir.
// Old version of Linux (e.g. RHEL7) return EEXIST.
// Everything else returns ENOTEMPTY.
int rc;
ASSERT_SYS(0, 0, mkdir("foo", 0755));
ASSERT_SYS(0, 0, touch("foo/bar", 0644));
ASSERT_SYS(0, 0, mkdir("lol", 0755));
ASSERT_EQ(-1, (rc = rename("lol", "foo")));
ASSERT_TRUE(errno == ENOTEMPTY || errno == EEXIST);
errno = 0;
}
TEST(rename, moveIntoNonWritableDirectory_raisesEacces) {
// old versions of linux allow this
// new versions of linux report exdev?!
if (IsLinux()) return;
// netbsd and openbsd allow this
if (IsNetbsd() || IsOpenbsd()) return;
// windows doesn't really have permissions
if (IsWindows()) return;
// posix specifies this behavior
ASSERT_SYS(0, 0, mkdir("foo", 0111));
ASSERT_SYS(0, 0, touch("lol", 0644));
ASSERT_SYS(EACCES, -1, rename("lol", "foo/bar"));
}
TEST(renameat, testNull_returnsEfault) {
ASSERT_SYS(0, 0, close(creat("hello", 0644)));
EXPECT_SYS(EFAULT, -1, renameat(AT_FDCWD, 0, AT_FDCWD, 0));
EXPECT_SYS(EFAULT, -1, renameat(AT_FDCWD, 0, AT_FDCWD, "hello"));
EXPECT_SYS(EFAULT, -1, renameat(AT_FDCWD, "hello", AT_FDCWD, 0));
ASSERT_SYS(EFAULT, -1, renameat(AT_FDCWD, 0, AT_FDCWD, 0));
ASSERT_SYS(EFAULT, -1, renameat(AT_FDCWD, 0, AT_FDCWD, "hello"));
ASSERT_SYS(EFAULT, -1, renameat(AT_FDCWD, "hello", AT_FDCWD, 0));
}
TEST(renameat, test) {
ASSERT_SYS(0, 0, close(creat("first", 0644)));
EXPECT_TRUE(fileexists("first"));
EXPECT_TRUE(!fileexists("second"));
EXPECT_SYS(0, 0, renameat(AT_FDCWD, "first", AT_FDCWD, "second"));
EXPECT_TRUE(!fileexists("first"));
EXPECT_TRUE(fileexists("second"));
ASSERT_TRUE(fileexists("first"));
ASSERT_TRUE(!fileexists("second"));
ASSERT_SYS(0, 0, renameat(AT_FDCWD, "first", AT_FDCWD, "second"));
ASSERT_TRUE(!fileexists("first"));
ASSERT_TRUE(fileexists("second"));
}

View file

@ -25,6 +25,7 @@
#include "libc/errno.h"
#include "libc/macros.internal.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/stack.h"
#include "libc/stdio/rand.h"
#include "libc/str/str.h"
@ -66,13 +67,17 @@ TEST(reservefd, testGrowthOfFdsDataStructure) {
int i, n;
struct rlimit rlim;
n = 1700; // pe '2**16/40' → 1638 (likely value of g_fds.n)
if (!getrlimit(RLIMIT_NOFILE, &rlim)) n = MIN(n, rlim.rlim_cur - 3);
if (!getrlimit(RLIMIT_NOFILE, &rlim)) {
n = MIN(n, rlim.rlim_cur - 3);
} else {
errno = 0;
}
for (i = 0; i < n; ++i) {
EXPECT_SYS(0, i + 3, open("/zip/usr/share/zoneinfo/UTC", O_RDONLY));
ASSERT_SYS(0, i + 3, open("/zip/usr/share/zoneinfo/UTC", O_RDONLY));
}
ASSERT_GT(g_fds.n, OPEN_MAX);
for (i = 0; i < n; ++i) {
EXPECT_SYS(0, 0, close(i + 3));
ASSERT_SYS(0, 0, close(i + 3));
}
}

View file

@ -150,6 +150,7 @@ o/$(MODE)/test/libc/calls/zipread.com.zip.o: private \
o/$(MODE)/test/libc/calls/ioctl_test.com.runs: \
private .PLEDGE =
o/$(MODE)/test/libc/calls/lseek_test.com.runs \
o/$(MODE)/test/libc/calls/poll_test.com.runs: \
private .PLEDGE = stdio rpath wpath cpath fattr proc inet

View file

@ -20,6 +20,7 @@
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/consts/o.h"
#include "libc/testlib/testlib.h"
char testlib_enable_tmp_setup_teardown;
@ -45,6 +46,16 @@ TEST(unlink, enotdir) {
ASSERT_SYS(ENOTDIR, -1, unlink("o/doesnotexist"));
}
TEST(rmdir, willDeleteRegardlessOfAccessBits) {
ASSERT_SYS(0, 0, mkdir("foo", 0));
ASSERT_SYS(0, 0, rmdir("foo/"));
}
TEST(unlink, willDeleteRegardlessOfAccessBits) {
ASSERT_SYS(0, 0, touch("foo", 0));
ASSERT_SYS(0, 0, unlink("foo"));
}
TEST(unlinkat, test) {
int i, fd;
EXPECT_EQ(0, touch("mytmp", 0644));

View file

@ -94,6 +94,7 @@ TEST(__zipos_normpath, vectors) {
{"./", ""},
{"..", ""},
{"../", ""},
{"./foo/", "foo/"},
{"foo/", "foo/"},
{"../abc/def", "abc/def"},
{"../abc/def/..", "abc/"},

View file

@ -28,25 +28,24 @@
void SetUpOnce(void) {
GetSymbolTable();
ASSERT_SYS(0, 0, pledge("stdio rpath", 0));
}
TEST(isutf8, good) {
ASSERT_TRUE(_isutf8("\0\1\2\3", 4));
EXPECT_TRUE(_isutf8(kHyperion, kHyperionSize));
EXPECT_TRUE(_isutf8("𐌰𐌱𐌲𐌳𐌴𐌵𐌶𐌷▒▒▒▒▒▒▒▒▒▒▒▒", -1));
EXPECT_TRUE(_isutf8("天地玄黄 宇宙洪荒 日月盈昃 辰宿列张 寒来暑往 秋收冬藏"
"闰馀成岁 律吕调阳 云腾致雨 露结为霜 金生丽水 玉出昆冈"
"剑号巨阙 珠称夜光 果珍李柰 菜重芥姜 海咸河淡 鳞潜羽翔"
"龙师火帝 鸟官人皇 始制文字 乃服衣裳 推位让国 有虞陶唐",
-1));
ASSERT_TRUE(isutf8("\0\1\2\3", 4));
EXPECT_TRUE(isutf8(kHyperion, kHyperionSize));
EXPECT_TRUE(isutf8("𐌰𐌱𐌲𐌳𐌴𐌵𐌶𐌷▒▒▒▒▒▒▒▒▒▒▒▒", -1));
EXPECT_TRUE(isutf8("天地玄黄 宇宙洪荒 日月盈昃 辰宿列张 寒来暑往 秋收冬藏"
"闰馀成岁 律吕调阳 云腾致雨 露结为霜 金生丽水 玉出昆冈"
"剑号巨阙 珠称夜光 果珍李柰 菜重芥姜 海咸河淡 鳞潜羽翔"
"龙师火帝 鸟官人皇 始制文字 乃服衣裳 推位让国 有虞陶唐",
-1));
}
TEST(isutf8, bad) {
ASSERT_FALSE(_isutf8("\300\200", -1)); // overlong nul
ASSERT_FALSE(_isutf8("\200\300", -1)); // latin1 c1 control code
ASSERT_FALSE(_isutf8("\300\300", -1)); // missing continuation
ASSERT_FALSE(_isutf8("\377\200\200\200\200", -1)); // thompson-pike varint
ASSERT_FALSE(isutf8("\300\200", -1)); // overlong nul
ASSERT_FALSE(isutf8("\200\300", -1)); // latin1 c1 control code
ASSERT_FALSE(isutf8("\300\300", -1)); // missing continuation
ASSERT_FALSE(isutf8("\377\200\200\200\200", -1)); // thompson-pike varint
}
TEST(isutf8, oob) {
@ -54,15 +53,15 @@ TEST(isutf8, oob) {
char *p;
for (n = 0; n < 32; ++n) {
p = memset(malloc(n), 'a', n);
ASSERT_TRUE(_isutf8(p, n));
ASSERT_TRUE(isutf8(p, n));
free(p);
}
}
BENCH(isutf8, bench) {
EZBENCH_N("_isutf8", 0, _isutf8(0, 0));
EZBENCH_N("_isutf8", 5, _isutf8("hello", 5));
EZBENCH_N("_isutf8 ascii", kHyperionSize, _isutf8(kHyperion, kHyperionSize));
EZBENCH_N("_isutf8 unicode", kBlocktronicsSize,
_isutf8(kBlocktronics, kBlocktronicsSize));
EZBENCH_N("isutf8", 0, isutf8(0, 0));
EZBENCH_N("isutf8", 5, isutf8("hello", 5));
EZBENCH_N("isutf8 ascii", kHyperionSize, isutf8(kHyperion, kHyperionSize));
EZBENCH_N("isutf8 unicode", kBlocktronicsSize,
isutf8(kBlocktronics, kBlocktronicsSize));
}

View file

@ -192,7 +192,7 @@ static int SerializeString(lua_State *L, char **buf, int idx) {
size_t i, n;
const char *s;
s = lua_tolstring(L, idx, &n);
utf8 = _isutf8(s, n);
utf8 = isutf8(s, n);
RETURN_ON_ERROR(appendw(buf, '"'));
for (i = 0; i < n; i++) {
switch ((x = kLuaStrXlat[(c = s[i] & 255)])) {

View file

@ -209,7 +209,7 @@ typedef unsigned char u8;
# define access(f,m) _access((f),(m))
# endif
# ifndef unlink
# define unlink _unlink
# define unlink_ _unlink
# endif
# ifndef strdup
# define strdup _strdup

View file

@ -163,8 +163,8 @@ void elfwriter_zip(struct ElfWriter *elf, const char *symbol, const char *cname,
lfilehdrsize = kZipLfileHdrMinSize + namesize;
crc = crc32_z(0, data, uncompsize);
GetDosLocalTime(mtim.tv_sec, &mtime, &mdate);
if (_isutf8(name, namesize)) gflags |= kZipGflagUtf8;
if (S_ISREG(mode) && _istext(data, size)) {
if (isutf8(name, namesize)) gflags |= kZipGflagUtf8;
if (S_ISREG(mode) && istext(data, size)) {
iattrs |= kZipIattrText;
}
dosmode = !(mode & 0200) ? kNtFileAttributeReadonly : 0;

View file

@ -1084,7 +1084,7 @@ static bool HasString(struct Strings *l, const char *s, size_t n) {
return false;
}
static const char* DEFAULTLUAPATH = "/zip/.lua/?.lua;/zip/.lua/?/init.lua";
static const char *DEFAULTLUAPATH = "/zip/.lua/?.lua;/zip/.lua/?/init.lua";
static void UpdateLuaPath(const char *s) {
#ifndef STATIC
@ -1100,11 +1100,10 @@ static void UpdateLuaPath(const char *s) {
if (t = strstr(curpath, DEFAULTLUAPATH)) {
// if the DEFAULT path is found, prepend the path in front of it
respath = xasprintf("%.*s%s/.lua/?.lua;%s/.lua/?/init.lua;%s",
t-curpath, curpath, s, s, t);
t - curpath, curpath, s, s, t);
} else {
// if the DEFAULT path is not found, append to the end
respath = xasprintf("%s;%s/.lua/?.lua;%s/.lua/?/init.lua",
curpath, s, s);
respath = xasprintf("%s;%s/.lua/?.lua;%s/.lua/?/init.lua", curpath, s, s);
}
lua_pushstring(L, _gc(respath));
lua_setfield(L, -3, "path");
@ -3659,8 +3658,8 @@ static void StoreAsset(char *path, size_t pathlen, char *data, size_t datalen,
}
INFOF("(srvr) storing asset %`'s", path);
disk = gflags = iattrs = 0;
if (_isutf8(path, pathlen)) gflags |= kZipGflagUtf8;
if (_istext(data, datalen)) iattrs |= kZipIattrText;
if (isutf8(path, pathlen)) gflags |= kZipGflagUtf8;
if (istext(data, datalen)) iattrs |= kZipIattrText;
crc = crc32_z(0, data, datalen);
if (datalen < 100) {
method = kZipCompressionNone;