Improve docs of more system calls

This change also found a few POSIX compliance bugs with errnos. Another
bug was discovered where, on Windows, pread() and pwrite() could modify
the file position in cases where ReadFile() returned an error e.g. when
seeking past the end of file. We also have more tests!
This commit is contained in:
Justine Tunney 2022-10-02 22:14:33 -07:00
parent af24c19db3
commit ccbae7799e
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
39 changed files with 589 additions and 175 deletions

View file

@ -1,7 +1,8 @@
#ifndef COSMOPOLITAN_LIBC_CALLS_ASAN_INTERNAL_H_
#define COSMOPOLITAN_LIBC_CALLS_ASAN_INTERNAL_H_
#include "libc/intrin/asmflag.h"
#include "libc/calls/struct/timespec.h"
#include "libc/calls/struct/timeval.h"
#include "libc/intrin/asmflag.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
@ -14,6 +15,10 @@ forceinline bool __asan_is_valid_timespec(const struct timespec *ts) {
return zf;
}
forceinline bool __asan_is_valid_timeval(const struct timeval *tv) {
return __asan_is_valid_timespec((const struct timespec *)tv);
}
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_CALLS_ASAN_INTERNAL_H_ */

View file

@ -17,17 +17,43 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/intrin/strace.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/sysv/errfuns.h"
/**
* Changes root directory.
*
* Please consider using unveil() instead of chroot(). If you use this
* system call then consider using both chdir() and closefrom() before
* calling this function. Otherwise there's a small risk that fchdir()
* could be used to escape the chroot() environment. Cosmopolitan Libc
* focuses on static binaries which make chroot() infinitely easier to
* use since you don't need to construct an entire userspace each time
* however unveil() is still better to use on modern Linux and OpenBSD
* because it doesn't require root privileges.
*
* @param path shall become the new root directory
* @return 0 on success, or -1 w/ errno
* @raise EACCES if we don't have permission to search a component of `path`
* @raise ENOTDIR if a directory component in `path` exists as non-directory
* @raise ENAMETOOLONG if symlink-resolved `path` length exceeds `PATH_MAX`
* @raise ENAMETOOLONG if component in `path` exists longer than `NAME_MAX`
* @raise EPERM if not root or pledge() is in play
* @raise EIO if a low-level i/o error occurred
* @raise EFAULT if `path` is bad memory
* @raise ENOENT if `path` doesn't exist
* @raise ENOSYS on Windows
*/
int chroot(const char *path) {
int rc;
rc = sys_chroot(path);
if (!path || (IsAsan() && !__asan_is_valid(path, 1))) {
rc = efault();
} else {
rc = sys_chroot(path);
}
STRACE("chroot(%s) → %d% m", path, rc);
return rc;
}

View file

@ -17,10 +17,10 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/asan.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/calls/struct/timespec.internal.h"
#include "libc/dce.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/sysv/consts/clock.h"
#include "libc/sysv/errfuns.h"
#include "libc/time/time.h"
@ -49,6 +49,12 @@ static int sys_clock_getres_xnu(int clock, struct timespec *ts) {
/**
* Returns granularity of clock.
*
* @return 0 on success, or -1 w/ errno
* @error EPERM if pledge() is in play without stdio promise
* @error EINVAL if `clock` isn't supported on this system
* @error EFAULT if `ts` points to bad memory
* @threadsafe
*/
int clock_getres(int clock, struct timespec *ts) {
int rc;

View file

@ -20,7 +20,6 @@
#include "libc/calls/asan.internal.h"
#include "libc/calls/clock_gettime.internal.h"
#include "libc/calls/state.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/calls/struct/timespec.internal.h"
#include "libc/calls/struct/timeval.h"
#include "libc/calls/syscall_support-sysv.internal.h"
@ -30,6 +29,7 @@
#include "libc/intrin/asmflag.h"
#include "libc/intrin/bits.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/mem/alloca.h"
#include "libc/nt/synchronization.h"
#include "libc/sysv/errfuns.h"
@ -48,12 +48,30 @@
* __clock_gettime l: 35𝑐 11𝑛𝑠
* sys_clock_gettime l: 220𝑐 71𝑛𝑠
*
* @param clock can be CLOCK_REALTIME, CLOCK_MONOTONIC, etc.
* @param clock can be one of:
* - `CLOCK_REALTIME`: universally supported
* - `CLOCK_REALTIME_FAST`: ditto but faster on freebsd
* - `CLOCK_MONOTONIC`: universally supported
* - `CLOCK_MONOTONIC_FAST`: ditto but faster on freebsd
* - `CLOCK_MONOTONIC_RAW`: nearly universally supported
* - `CLOCK_PROCESS_CPUTIME_ID`: linux and bsd
* - `CLOCK_THREAD_CPUTIME_ID`: linux and bsd
* - `CLOCK_REALTIME_COARSE`: : linux and openbsd
* - `CLOCK_MONOTONIC_COARSE`: linux
* - `CLOCK_PROF`: linux and netbsd
* - `CLOCK_BOOTTIME`: linux and openbsd
* - `CLOCK_REALTIME_ALARM`: linux-only
* - `CLOCK_BOOTTIME_ALARM`: linux-only
* - `CLOCK_TAI`: linux-only
* @param ts is where the result is stored
* @return 0 on success, or -1 w/ errno
* @error EINVAL if clock isn't supported on this system
* @error EPERM if pledge() is in play without stdio promise
* @error EINVAL if `clock` isn't supported on this system
* @error EFAULT if `ts` points to bad memory
* @see strftime(), gettimeofday()
* @asyncsignalsafe
* @threadsafe
* @vforksafe
*/
int clock_gettime(int clock, struct timespec *ts) {
int rc;

View file

@ -16,9 +16,12 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/nt/enum/filemovemethod.h"
#include "libc/nt/errors.h"
#include "libc/nt/files.h"
#include "libc/nt/runtime.h"
#include "libc/sysv/errfuns.h"
textwindows int sys_ftruncate_nt(int64_t handle, uint64_t length) {
@ -28,7 +31,13 @@ textwindows int sys_ftruncate_nt(int64_t handle, uint64_t length) {
if ((ok = SetFilePointerEx(handle, 0, &tell, kNtFileCurrent))) {
ok = SetFilePointerEx(handle, length, NULL, kNtFileBegin) &&
SetEndOfFile(handle);
SetFilePointerEx(handle, tell, NULL, kNtFileBegin);
_npassert(SetFilePointerEx(handle, tell, NULL, kNtFileBegin));
}
if (ok) {
return 0;
} else if (GetLastError() == kNtErrorAccessDenied) {
return einval(); // ftruncate() doesn't raise EACCES
} else {
return __winerr();
}
return ok ? 0 : __winerr();
}

View file

@ -18,31 +18,65 @@
*/
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/calls/syscall-nt.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/strace.internal.h"
#include "libc/sysv/errfuns.h"
/**
* Changes size of file.
* Changes size of open file.
*
* If the file size is increased, the extended area shall appear as if
* it were zero-filled. If your file size is decreased, the extra data
* shall be lost.
*
* This function never changes the file position. This is true even if
* ftruncate() causes the position to become beyond the end of file in
* which case, the rules described in the lseek() documentation apply.
*
* Some operating systems implement an optimization, where `length` is
* treated as a logical size and the requested physical space won't be
* allocated until non-zero values get written into it. Our tests show
* this happens on Linux (usually with 4096 byte granularity), FreeBSD
* (which favors 512-byte granularity), and MacOS (prefers 4096 bytes)
* however Windows, OpenBSD, and NetBSD always reserve physical space.
* This may be inspected using fstat() and consulting stat::st_blocks.
*
* @param fd must be open for writing
* @param length may be greater than current current file size, in which
* case System V guarantees it'll be zero'd but Windows NT doesn't;
* since the prior extends logically and the latter physically
* @param length may be greater than current current file size
* @return 0 on success, or -1 w/ errno
* @raise EINVAL if `length` is negative
* @raise EINTR if signal was delivered instead
* @raise EIO if a low-level i/o error happened
* @raise EFBIG or EINVAL if `length` is too huge
* @raise ENOTSUP if `fd` is a zip file descriptor
* @raise EBADF if `fd` isn't an open file descriptor
* @raise EINVAL if `fd` is a non-file, e.g. pipe, socket
* @raise EINVAL if `fd` wasn't opened in a writeable mode
* @raise ENOSYS on bare metal
* @see truncate()
* @asyncsignalsafe
* @threadsafe
*/
int ftruncate(int fd, int64_t length) {
int rc;
if (fd < 0) {
rc = einval();
rc = ebadf();
} else if (__isfdkind(fd, kFdZip)) {
rc = enotsup();
} else if (IsMetal()) {
rc = enosys();
} else if (!IsWindows()) {
rc = sys_ftruncate(fd, length, length);
} else {
if (fd >= g_fds.n) rc = ebadf();
if (IsNetbsd() && rc == -1 && errno == ENOSPC) {
errno = EFBIG; // POSIX doesn't specify ENOSPC for ftruncate()
}
} else if (__isfdopen(fd)) {
rc = sys_ftruncate_nt(g_fds.p[fd].handle, length);
} else {
rc = ebadf();
}
STRACE("ftruncate(%d, %'ld) → %d% m", fd, length, rc);
return rc;

View file

@ -16,16 +16,36 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/internal.h"
#include "libc/calls/struct/timespec.h"
#include "libc/calls/struct/timespec.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/sysv/errfuns.h"
/**
* Sets atime/mtime on file descriptor.
* Changes access/modified time on open file, the modern way.
*
* This function is the same as `utimensat(fd, 0, ts, 0)`.
* XNU only has microsecond (1e-6) accuracy. Windows only has
* hectonanosecond (1e-7) accuracy. RHEL5 is somewhat broken so utimes()
* is recommended if portability to old versions of Linux is desired.
*
* @param ts is atime/mtime, or null for current time
* @raise ENOSYS on RHEL5
* @param fd is file descriptor of file whose timestamps will change
* @param ts is {access, modified} timestamps, or null for current time
* @return 0 on success, or -1 w/ errno
* @raise ENOTSUP if `fd` is on the zip filesystem
* @raise EINVAL if `flags` had an unrecognized value
* @raise EPERM if pledge() is in play without `fattr` promise
* @raise EINVAL if `ts` specifies a nanosecond value that's out of range
* @raise EBADF if `fd` isn't an open file descriptor
* @raise EFAULT if `ts` memory was invalid
* @raise ENOSYS on RHEL5 or bare metal
* @asyncsignalsafe
* @threadsafe
*/
int futimens(int fd, const struct timespec ts[2]) {
return utimensat(fd, 0, ts, 0);
int rc;
rc = __utimens(fd, 0, ts, 0);
STRACE("futimens(%d, {%s, %s}) → %d% m", fd, DescribeTimespec(0, ts),
DescribeTimespec(0, ts ? ts + 1 : 0), rc);
return rc;
}

View file

@ -26,7 +26,8 @@
* @note better than microsecond precision on most platforms
* @see fstat() for reading timestamps
*/
int futimes(int fd, const struct timeval tv[hasatleast 2]) {
int futimes(int fd, const struct timeval tv[2]) {
// TODO(jart): does this work on rhel5? what's up with this?
struct timespec ts[2];
if (tv) {
ts[0].tv_sec = tv[0].tv_sec;

View file

@ -16,18 +16,26 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/intrin/strace.internal.h"
#include "libc/assert.h"
#include "libc/calls/syscall-nt.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/intrin/strace.internal.h"
/**
* Returns parent process id.
*
* @return parent process id (always successful)
* @note slow on Windows; needs to iterate process tree
* @asyncsignalsafe
* @threadsafe
* @vforksafe
*/
int getppid(void) {
int rc;
if (!IsWindows()) {
if (IsMetal()) {
rc = 1;
} else if (!IsWindows()) {
if (!IsNetbsd()) {
rc = sys_getppid();
} else {
@ -36,6 +44,7 @@ int getppid(void) {
} else {
rc = sys_getppid_nt();
}
STRACE("getppid() → %d% m", rc);
_npassert(rc >= 0);
STRACE("%s() → %d", "getppid", rc);
return rc;
}

View file

@ -16,10 +16,12 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/calls/calls.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/intrin/strace.internal.h"
#include "libc/limits.h"
#include "libc/macros.internal.h"
#include "libc/nt/accounting.h"
#include "libc/runtime/runtime.h"
@ -38,7 +40,7 @@ static textwindows dontinline uint32_t GetUserNameHash(void) {
char16_t buf[257];
uint32_t size = ARRAYLEN(buf);
GetUserName(&buf, &size);
return KnuthMultiplicativeHash32(buf, size >> 1);
return KnuthMultiplicativeHash32(buf, size >> 1) & INT_MAX;
}
/**
@ -47,18 +49,22 @@ static textwindows dontinline uint32_t GetUserNameHash(void) {
* This never fails. On Windows, which doesn't really have this concept,
* we return a deterministic value that's likely to work.
*
* @return user id (always successful)
* @asyncsignalsafe
* @threadsafe
* @vforksafe
*/
uint32_t getuid(void) {
uint32_t rc;
if (!IsWindows()) {
int rc;
if (IsMetal()) {
rc = 0;
} else if (!IsWindows()) {
rc = sys_getuid();
} else {
rc = GetUserNameHash();
}
STRACE("%s() → %u% m", "getuid", rc);
_npassert(rc >= 0);
STRACE("%s() → %d", "getuid", rc);
return rc;
}
@ -68,16 +74,21 @@ uint32_t getuid(void) {
* This never fails. On Windows, which doesn't really have this concept,
* we return a deterministic value that's likely to work.
*
* @return group id (always successful)
* @asyncsignalsafe
* @threadsafe
* @vforksafe
*/
uint32_t getgid(void) {
uint32_t rc;
if (!IsWindows()) {
int rc;
if (IsMetal()) {
rc = 0;
} else if (!IsWindows()) {
rc = sys_getgid();
} else {
rc = GetUserNameHash();
}
STRACE("%s() → %u% m", "getgid", rc);
_npassert(rc >= 0);
STRACE("%s() → %d", "getgid", rc);
return rc;
}

View file

@ -28,13 +28,51 @@
#include "libc/zipos/zipos.internal.h"
/**
* Changes current position of file descriptor/handle.
* Changes current position of file descriptor, e.g.
*
* int fd = open("hello.bin", O_RDONLY);
* lseek(fd, 100, SEEK_SET); // set position to 100th byte
* read(fd, buf, 8); // read bytes 100 through 107
*
* This function may be used to inspect the current position:
*
* int64_t pos = lseek(fd, 0, SEEK_CUR);
*
* You may seek past the end of file. If a write happens afterwards
* then the gap leading up to it will be filled with zeroes. Please
* note that lseek() by itself will not extend the physical medium.
*
* If dup() is used then the current position will be shared across
* multiple file descriptors. If you seek in one it will implicitly
* seek the other too.
*
* The current position of a file descriptor is shared between both
* processes and threads. For example, if an fd is inherited across
* fork(), and both the child and parent want to read from it, then
* changes made by one are observable to the other.
*
* The pread() and pwrite() functions obfuscate the need for having
* global shared file position state. Consider using them, since it
* helps avoid the gotchas of this interface described above.
*
* This function is supported by all OSes within our support vector
* and our unit tests demonstrate the behaviors described above are
* consistent across platforms.
*
* @param fd is a number returned by open()
* @param offset is the relative byte count
* @param whence can be SEEK_SET, SEEK_CUR, or SEEK_END
* @return new position relative to beginning, or -1 on error
* @param offset is 0-indexed byte count w.r.t. `whence`
* @param whence can be one of:
* - `SEEK_SET`: Sets the file position to `offset` [default]
* - `SEEK_CUR`: Sets the file position to `position + offset`
* - `SEEK_END`: Sets the file position to `filesize + offset`
* @return new position relative to beginning, or -1 w/ errno
* @raise ESPIPE if `fd` is a pipe, socket, or fifo
* @raise EBADF if `fd` isn't an open file descriptor
* @raise EINVAL if resulting offset would be negative
* @raise EINVAL if `whence` isn't valid
* @asyncsignalsafe
* @threadsafe
* @vforksafe
*/
int64_t lseek(int fd, int64_t offset, unsigned whence) {
int64_t rc;

View file

@ -135,6 +135,7 @@
* @raise ENOMEM if insufficient memory was available
* @raise EMFILE if `RLIMIT_NOFILE` has been reached
* @raise EOPNOTSUPP if `file` names a named socket
* @raise EFAULT if `file` points to invalid memory
* @raise ETXTBSY if writing is requested on `file` that's being executed
* @raise ELOOP if `flags` had `O_NOFOLLOW` and `file` is a symbolic link
* @raise ELOOP if a loop was detected resolving components of `file`

View file

@ -32,7 +32,9 @@
#include "libc/zipos/zipos.internal.h"
/**
* Reads from file at offset, thus avoiding superfluous lseek().
* Reads from file at offset.
*
* This function never changes the current position of `fd`.
*
* @param fd is something open()'d earlier, noting pipes might not work
* @param buf is copied into, cf. copy_file_range(), sendfile(), etc.
@ -40,8 +42,14 @@
* @param offset is bytes from start of file at which read begins
* @return [1..size] bytes on success, 0 on EOF, or -1 w/ errno; with
* exception of size==0, in which case return zero means no error
* @raise ESPIPE if `fd` isn't seekable
* @raise EINVAL if `offset` is negative
* @raise EBADF if `fd` isn't an open file descriptor
* @raise EIO if a complicated i/o error happened
* @raise EINTR if signal was delivered instead
* @see pwrite(), write()
* @asyncsignalsafe
* @threadsafe
* @vforksafe
*/
ssize_t pread(int fd, void *buf, size_t size, int64_t offset) {

View file

@ -29,7 +29,9 @@
#include "libc/sysv/errfuns.h"
/**
* Writes to file at offset, thus avoiding superfluous lseek().
* Writes to file at offset.
*
* This function never changes the current position of `fd`.
*
* @param fd is something open()'d earlier, noting pipes might not work
* @param buf is copied from, cf. copy_file_range(), sendfile(), etc.
@ -41,6 +43,7 @@
* impossible unless size was passed as zero to do an error check
* @see pread(), write()
* @asyncsignalsafe
* @threadsafe
* @vforksafe
*/
ssize_t pwrite(int fd, const void *buf, size_t size, int64_t offset) {

View file

@ -37,10 +37,12 @@
static textwindows ssize_t sys_read_nt_impl(struct Fd *fd, void *data,
size_t size, ssize_t offset) {
bool32 ok;
int64_t p;
uint32_t got, avail;
struct NtOverlapped overlap;
// our terrible polling mechanism
if (GetFileType(fd->handle) == kNtFileTypePipe) {
for (;;) {
if (!PeekNamedPipe(fd->handle, 0, 0, 0, &avail, 0)) break;
@ -62,11 +64,15 @@ static textwindows ssize_t sys_read_nt_impl(struct Fd *fd, void *data,
_npassert(SetFilePointerEx(fd->handle, 0, &p, SEEK_CUR));
}
if (ReadFile(fd->handle, data, _clampio(size), &got,
_offset2overlap(fd->handle, offset, &overlap))) {
if (offset != -1) {
_npassert(SetFilePointerEx(fd->handle, p, 0, SEEK_SET));
}
ok = ReadFile(fd->handle, data, _clampio(size), &got,
_offset2overlap(fd->handle, offset, &overlap));
if (offset != -1) {
// windows clobbers file pointer even on error
_npassert(SetFilePointerEx(fd->handle, p, 0, SEEK_SET));
}
if (ok) {
return got;
}

View file

@ -33,6 +33,9 @@
/**
* Reads data from file descriptor.
*
* This function changes the current file position. For documentation
* on file position behaviors and gotchas, see the lseek() function.
*
* @param fd is something open()'d earlier
* @param buf is copied into, cf. copy_file_range(), sendfile(), etc.
* @param size in range [1..0x7ffff000] is reasonable
@ -41,6 +44,7 @@
* @see write(), pread(), readv()
* @asyncsignalsafe
* @restartable
* @vforksafe
*/
ssize_t read(int fd, void *buf, size_t size) {
ssize_t rc;

View file

@ -48,10 +48,9 @@
* @vforksafe
*/
int sigprocmask(int how, const sigset_t *opt_set, sigset_t *opt_out_oldset) {
sigset_t old;
int res, rc, arg1;
sigset_t old = {0};
const sigset_t *arg2;
sigemptyset(&old);
if (IsAsan() &&
((opt_set && !__asan_is_valid(opt_set, sizeof(*opt_set))) ||
(opt_out_oldset &&

View file

@ -46,7 +46,7 @@
int sigsuspend(const sigset_t *ignore) {
int rc;
long ms, totoms;
sigset_t save, mask, *arg;
sigset_t save, *arg, mask = {0};
STRACE("sigsuspend(%s) → ...", DescribeSigset(0, ignore));
if (IsAsan() && ignore && !__asan_is_valid(ignore, sizeof(*ignore))) {
rc = efault();
@ -63,7 +63,6 @@ int sigsuspend(const sigset_t *ignore) {
if (ignore) {
arg = ignore;
} else {
sigemptyset(&mask);
arg = &mask;
}
if (!IsWindows()) {

View file

@ -6,6 +6,7 @@
COSMOPOLITAN_C_START_
int __sys_utimensat(int, const char *, const struct timespec[2], int) hidden;
int __utimens(int, const char *, const struct timespec[2], int) hidden;
int sys_clock_getres(int, struct timespec *) hidden;
int sys_clock_gettime(int, struct timespec *) hidden;
int sys_clock_gettime_nt(int, struct timespec *) hidden;

View file

@ -27,7 +27,6 @@
textwindows int sys_truncate_nt(const char *path, uint64_t length) {
int rc;
bool32 ok;
int64_t fh;
uint16_t path16[PATH_MAX];
if (__mkntpath(path, path16) == -1) return -1;

View file

@ -17,25 +17,65 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/intrin/strace.internal.h"
#include "libc/calls/syscall-nt.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/sysv/errfuns.h"
#include "libc/zipos/zipos.internal.h"
/**
* Reduces or extends underlying physical medium of file.
* Changes size of file.
*
* If file was originally larger, content >length is lost.
* If the file size is increased, the extended area shall appear as if
* it were zero-filled. If your file size is decreased, the extra data
* shall be lost.
*
* @param path must exist
* @return 0 on success or -1 w/ errno
* Some operating systems implement an optimization, where `length` is
* treated as a logical size and the requested physical space won't be
* allocated until non-zero values get written into it. Our tests show
* this happens on Linux (usually with 4096 byte granularity), FreeBSD
* (which favors 512-byte granularity), and MacOS (prefers 4096 bytes)
* however Windows, OpenBSD, and NetBSD always reserve physical space.
* This may be inspected using stat() then consulting stat::st_blocks.
*
* @param path is name of file that shall be resized
* @return 0 on success, or -1 w/ errno
* @raise EINVAL if `length` is negative
* @raise EINTR if signal was delivered instead
* @raise EFBIG or EINVAL if `length` is too huge
* @raise EFAULT if `path` points to invalid memory
* @raise ENOTSUP if `path` is a zip filesystem path
* @raise EACCES if we don't have permission to search a component of `path`
* @raise ENOTDIR if a directory component in `path` exists as non-directory
* @raise ENAMETOOLONG if symlink-resolved `path` length exceeds `PATH_MAX`
* @raise ENAMETOOLONG if component in `path` exists longer than `NAME_MAX`
* @raise ELOOP if a loop was detected resolving components of `path`
* @raise ENOENT if `path` doesn't exist or is an empty string
* @raise ETXTBSY if `path` is an executable being executed
* @raise EROFS if `path` is on a read-only filesystem
* @raise ENOSYS on bare metal
* @see ftruncate()
* @error ENOENT
* @threadsafe
*/
int truncate(const char *path, uint64_t length) {
int rc;
if (!IsWindows()) {
struct ZiposUri zipname;
if (IsMetal()) {
rc = enosys();
} else if (!path || (IsAsan() && !__asan_is_valid(path, 1))) {
rc = efault();
} else if (_weaken(__zipos_parseuri) &&
_weaken(__zipos_parseuri)(path, &zipname) != -1) {
rc = enotsup();
} else if (!IsWindows()) {
rc = sys_truncate(path, length, length);
if (IsNetbsd() && rc == -1 && errno == ENOSPC) {
errno = EFBIG; // POSIX doesn't specify ENOSPC for truncate()
}
} else {
rc = sys_truncate_nt(path, length);
}

View file

@ -1,7 +1,7 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2021 Justine Alexandra Roberts Tunney
Copyright 2020 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
@ -16,11 +16,40 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/runtime/runtime.h"
#include "libc/stdio/cocmd.internal.h"
#include "libc/stdio/stdio.h"
#include "libc/assert.h"
#include "libc/calls/asan.internal.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/timespec.internal.h"
#include "libc/dce.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/errfuns.h"
#include "libc/zipos/zipos.internal.h"
// Support code for system() and popen().
int systemexec(const char *cmdline) {
_Exit(cocmd(3, (char *[]){"cocmd.com", "-c", cmdline, 0}));
int __utimens(int fd, const char *path, const struct timespec ts[2],
int flags) {
int rc;
struct ZiposUri zipname;
if (IsMetal()) {
rc = enosys();
} else if (IsAsan() && ((fd == AT_FDCWD && !__asan_is_valid(path, 1)) ||
(ts && (!__asan_is_valid_timespec(ts + 0) ||
!__asan_is_valid_timespec(ts + 1))))) {
rc = efault(); // bad memory
} else if ((flags & ~AT_SYMLINK_NOFOLLOW)) {
rc = einval(); // unsupported flag
} else if (__isfdkind(fd, kFdZip) ||
(path && (_weaken(__zipos_parseuri) &&
_weaken(__zipos_parseuri)(path, &zipname) != -1))) {
rc = enotsup();
} else if (!IsWindows()) {
rc = sys_utimensat(fd, path, ts, flags);
} else {
rc = sys_utimensat_nt(fd, path, ts, flags);
}
return rc;
}

View file

@ -30,54 +30,43 @@
#include "libc/zipos/zipos.internal.h"
/**
* Sets atime/mtime on file, the modern way.
* Sets access/modified time on file, the modern way.
*
* This is two functions in one. If `path` is null then this function
* becomes the same as `futimens(dirfd, ts)`.
* XNU only has microsecond (1e-6) accuracy and there's no
* `dirfd`-relative support. Windows only has hectonanosecond (1e-7)
* accuracy. RHEL5 is somewhat broken so utimes() is recommended if
* portability to old versions of Linux is desired.
*
* XNU and RHEL5 only have microsecond accuracy and there's no `dirfd`
* relative support.
*
* @param dirfd should AT_FDCWD
* @param ts is atime/mtime, or null for current time
* @param flags can have AT_SYMLINK_NOFOLLOW
* @raise ENOSYS on RHEL5 if path is NULL
* @raise EINVAL if flags had unrecognized bits
* @raise EINVAL if path is NULL and flags has AT_SYMLINK_NOFOLLOW
* @raise EBADF if dirfd isn't a valid fd or AT_FDCWD
* @raise EFAULT if path or ts memory was invalid
* @raise EROFS if file system is read-only
* @raise ENAMETOOLONG
* @raise EPERM
* @param dirfd is usually `AT_FDCWD`
* @param path is filename whose timestamps should be modified
* @param ts is {access, modified} timestamps, or null for current time
* @param flags can have `AT_SYMLINK_NOFOLLOW` when `path` is specified
* @return 0 on success, or -1 w/ errno
* @raise EINVAL if `flags` had an unrecognized value
* @raise EPERM if pledge() is in play without `fattr` promise
* @raise EACCES if unveil() is in play and `path` isn't unveiled
* @raise ENOTSUP if `path` is a zip filesystem path or `dirfd` is zip
* @raise EINVAL if `ts` specifies a nanosecond value that's out of range
* @raise ENAMETOOLONG if symlink-resolved `path` length exceeds `PATH_MAX`
* @raise ENAMETOOLONG if component in `path` exists longer than `NAME_MAX`
* @raise EBADF if `dirfd` isn't a valid fd or `AT_FDCWD`
* @raise EFAULT if `path` or `ts` memory was invalid
* @raise EROFS if `path` is on read-only filesystem
* @raise ENOSYS on bare metal
* @see futimens()
* @asyncsignalsafe
* @threadsafe
*/
int utimensat(int dirfd, const char *path, const struct timespec ts[2],
int flags) {
int rc;
if (IsAsan() && ((dirfd == AT_FDCWD && !__asan_is_valid(path, 1)) ||
(ts && (!__asan_is_valid_timespec(ts + 0) ||
!__asan_is_valid_timespec(ts + 1))))) {
rc = efault(); // bad memory
} else if ((flags & ~AT_SYMLINK_NOFOLLOW)) {
rc = einval(); // unsupported flag
} else if (!path && flags) {
rc = einval(); // futimens() doesn't take flags
} else if ((path || __isfdkind(dirfd, kFdZip)) && _weaken(__zipos_notat) &&
(rc = __zipos_notat(dirfd, path)) == -1) {
STRACE("zipos utimensat not supported yet");
} else if (!IsWindows()) {
rc = sys_utimensat(dirfd, path, ts, flags);
if (!path) {
rc = efault(); // linux kernel abi behavior isn't supported
} else {
rc = sys_utimensat_nt(dirfd, path, ts, flags);
}
if (ts) {
STRACE("utimensat(%s, %#s, {{%,ld, %,ld}, {%,ld, %,ld}}, %#b) → %d% m",
DescribeDirfd(dirfd), path, ts[0].tv_sec, ts[0].tv_nsec,
ts[1].tv_sec, ts[1].tv_nsec, flags, rc);
} else {
STRACE("utimensat(%s, %#s, 0, %#b) → %d% m", DescribeDirfd(dirfd), path,
flags, rc);
rc = __utimens(dirfd, path, ts, flags);
}
STRACE("utimensat(%s, %#s, {%s, %s}, %#o) → %d% m", DescribeDirfd(dirfd),
path, DescribeTimespec(0, ts), DescribeTimespec(0, ts ? ts + 1 : 0),
flags, rc);
return rc;
}

View file

@ -16,34 +16,59 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/asan.internal.h"
#include "libc/calls/struct/itimerval.internal.h"
#include "libc/calls/struct/timeval.h"
#include "libc/calls/struct/timeval.internal.h"
#include "libc/dce.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/errfuns.h"
#include "libc/zipos/zipos.internal.h"
/**
* Changes last accessed/modified times on file.
* Changes last accessed/modified timestamps on file.
*
* @param times is access/modified and NULL means now
* @return 0 on success or -1 w/ errno
* @param tv is access/modified timestamps, or NULL which means now
* @return 0 on success, or -1 w/ errno
* @raise ENOTSUP if `path` is a zip filesystem path
* @raise EROFS if `path` is on a read-only filesystem
* @raise EFAULT if `path` or `tv` points to invalid memory
* @raise EPERM if pledge() is in play without fattr promise
* @raise ENOENT if `path` doesn't exist or is an empty string
* @raise EACCES if unveil() is in play and `path` isn't unveiled
* @raise ELOOP if a loop was detected resolving components of `path`
* @raise ENAMETOOLONG if symlink-resolved `path` length exceeds `PATH_MAX`
* @raise ENAMETOOLONG if component in `path` exists longer than `NAME_MAX`
* @raise EINVAL if `tv` specifies a microseconds value that's out of range
* @raise EACCES if we don't have permission to search a component of `path`
* @raise ENOTDIR if a directory component in `path` exists as non-directory
* @raise ENOSYS on bare metal
* @asyncsignalsafe
* @see stat()
*/
int utimes(const char *path, const struct timeval tv[2]) {
if (IsAsan()) {
if (tv && !__asan_is_valid(tv, sizeof(*tv) * 2)) {
return efault();
}
}
if (!IsWindows()) {
/*
* we don't modernize utimes() into utimensat() because the
* latter is poorly supported and utimes() works everywhere
*/
return sys_utimes(path, tv);
int rc;
struct ZiposUri zipname;
if (IsMetal()) {
rc = enosys();
} else if (IsAsan() && tv &&
(!__asan_is_valid_timeval(tv + 0) ||
!__asan_is_valid_timeval(tv + 1))) {
rc = efault();
} else if (_weaken(__zipos_parseuri) &&
_weaken(__zipos_parseuri)(path, &zipname) != -1) {
rc = enotsup();
} else if (!IsWindows()) {
// we don't modernize utimes() into utimensat() because the
// latter is poorly supported and utimes() works everywhere
rc = sys_utimes(path, tv);
} else {
return sys_utimes_nt(path, tv);
rc = sys_utimes_nt(path, tv);
}
STRACE("utimes(%#s, {%s, %s}) → %d% m", path, DescribeTimeval(0, tv),
DescribeTimeval(0, tv ? tv + 1 : 0), rc);
return rc;
}

View file

@ -21,10 +21,10 @@
#include "libc/calls/internal.h"
#include "libc/calls/sig.internal.h"
#include "libc/calls/state.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/calls/struct/rusage.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/fmt/conv.h"
#include "libc/intrin/strace.internal.h"
#include "libc/macros.internal.h"
#include "libc/nt/accounting.h"
#include "libc/nt/enum/accessmask.h"
@ -149,8 +149,7 @@ static textwindows int sys_wait4_nt_impl(int pid, int *opt_out_wstatus,
textwindows int sys_wait4_nt(int pid, int *opt_out_wstatus, int options,
struct rusage *opt_out_rusage) {
int rc;
sigset_t mask, oldmask;
sigemptyset(&mask);
sigset_t oldmask, mask = {0};
sigaddset(&mask, SIGCHLD);
__sig_mask(SIG_BLOCK, &mask, &oldmask);
rc = sys_wait4_nt_impl(pid, opt_out_wstatus, options, opt_out_rusage);

View file

@ -38,6 +38,7 @@
static textwindows ssize_t sys_write_nt_impl(int fd, void *data, size_t size,
ssize_t offset) {
bool32 ok;
int64_t h, p;
uint32_t err, sent;
struct NtOverlapped overlap;
@ -49,11 +50,15 @@ static textwindows ssize_t sys_write_nt_impl(int fd, void *data, size_t size,
_npassert(SetFilePointerEx(h, 0, &p, SEEK_CUR));
}
if (WriteFile(h, data, _clampio(size), &sent,
_offset2overlap(h, offset, &overlap))) {
if (offset != -1) {
_npassert(SetFilePointerEx(h, p, 0, SEEK_SET));
}
ok = WriteFile(h, data, _clampio(size), &sent,
_offset2overlap(h, offset, &overlap));
if (offset != -1) {
// windows clobbers file pointer even on error
_npassert(SetFilePointerEx(h, p, 0, SEEK_SET));
}
if (ok) {
return sent;
}
@ -72,7 +77,7 @@ static textwindows ssize_t sys_write_nt_impl(int fd, void *data, size_t size,
_Exitr(128 + EPIPE);
}
case kNtErrorAccessDenied: // write doesn't return EACCESS
return ebadf(); //
return ebadf();
default:
return __winerr();
}

View file

@ -31,6 +31,9 @@
/**
* Writes data to file descriptor.
*
* This function changes the current file position. For documentation
* on file position behaviors and gotchas, see the lseek() function.
*
* @param fd is something open()'d earlier
* @param buf is copied from, cf. copy_file_range(), sendfile(), etc.
* @param size in range [1..0x7ffff000] is reasonable
@ -39,6 +42,7 @@
* @see read(), pwrite(), writev(), SIGPIPE
* @asyncsignalsafe
* @restartable
* @vforksafe
*/
ssize_t write(int fd, const void *buf, size_t size) {
ssize_t rc;

View file

@ -19,6 +19,7 @@
#include "libc/calls/calls.h"
#include "libc/calls/state.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/runtime/internal.h"
/**
@ -32,16 +33,19 @@
* programs that mix fork() with threads. In that case, apps should
* consider using `sys_getpid().ax` instead to force a system call.
*
* On Linux, and only Linux, the process id is guaranteed to be the same
* as gettid() for the main thread.
* On Linux, and only Linux, getpid() is guaranteed to equal gettid()
* for the main thread.
*
* @return process id (always successful)
* @asyncsignalsafe
* @threadsafe
* @vforksafe
*/
int getpid(void) {
int rc;
if (!__vforked) {
if (IsMetal()) {
rc = 1;
} else if (!__vforked) {
rc = __pid;
} else {
rc = sys_getpid().ax;

View file

@ -21,6 +21,7 @@
#include "libc/calls/state.internal.h"
#include "libc/calls/syscall_support-nt.internal.h"
#include "libc/calls/wincrash.internal.h"
#include "libc/errno.h"
#include "libc/fmt/itoa.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/strace.internal.h"
@ -52,6 +53,7 @@
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/prot.h"
#include "libc/sysv/errfuns.h"
#include "libc/thread/tls.h"
STATIC_YOINK("_check_sigchld");
@ -337,6 +339,9 @@ textwindows int sys_fork_nt(void) {
}
if (reader != -1) CloseHandle(reader);
if (writer != -1) CloseHandle(writer);
if (rc == -1 && errno != ENOMEM) {
eagain(); // posix fork() only specifies two errors
}
} else {
rc = 0;
}

View file

@ -17,11 +17,11 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/intrin/strace.internal.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/syscall-nt.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/intrin/strace.internal.h"
#include "libc/nt/process.h"
#include "libc/runtime/internal.h"
#include "libc/sysv/consts/sig.h"
@ -30,7 +30,9 @@
/**
* Creates new process.
*
* @return 0 to child, child pid to parent, or -1 on error
* @return 0 to child, child pid to parent, or -1 w/ errno
* @raise EAGAIN if `RLIMIT_NPROC` was exceeded or system lacked resources
* @raise ENOMEM if we require more vespene gas
* @asyncsignalsafe
*/
int fork(void) {
@ -62,11 +64,11 @@ int fork(void) {
if (__tls_enabled) {
__get_tls()->tib_tid = IsLinux() ? dx : sys_gettid();
}
sigprocmask(SIG_SETMASK, &old, 0);
STRACE("fork() → 0 (child of %d)", parent);
sigprocmask(SIG_SETMASK, &old, 0);
} else {
STRACE("fork() → %d% m", ax);
sigprocmask(SIG_SETMASK, &old, 0);
STRACE("fork() → %d% m", ax);
}
return ax;
}

View file

@ -25,12 +25,12 @@
//
// This is the same as fork() except it's optimized for the case
// where the caller invokes execve() immediately afterwards. You
// can also call functions like close(), dup2(), etc. You cannot
// call read() safely but you can call pread(). Call _exit() but
// don't call exit(). Look for the vforksafe function annotation
// can also call functions like close(), dup2(), etc. Call _exit
// but don't call exit. Look for vforksafe function annotations.
//
// Do not make the assumption that the parent is suspended until
// the child terminates since this impl calls fork() on Windows.
// the child terminates since this impl calls fork() on Windows,
// OpenBSD, and MacOS.
//
// @return pid of child process or 0 if forked process
// @returnstwice

View file

@ -20,6 +20,7 @@
#include "libc/errno.h"
#include "libc/paths.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/cocmd.internal.h"
#include "libc/stdio/internal.h"
#include "libc/stdio/stdio.h"
#include "libc/sysv/consts/f.h"
@ -50,7 +51,7 @@ FILE *popen(const char *cmdline, const char *mode) {
} else if ((flags & O_ACCMODE) == O_WRONLY) {
dir = 1;
} else {
errno = EINVAL;
einval();
return NULL;
}
if (pipe(pipefds) == -1) return NULL;
@ -59,8 +60,7 @@ FILE *popen(const char *cmdline, const char *mode) {
switch ((pid = fork())) {
case 0:
dup2(pipefds[!dir], !dir);
systemexec(cmdline);
_exit(127);
_Exit(cocmd(3, (char *[]){"popen", "-c", cmdline, 0}));
default:
f->pid = pid;
close(pipefds[!dir]);

View file

@ -83,7 +83,6 @@ void setlinebuf(FILE *);
void setbuf(FILE *, char *);
void setbuffer(FILE *, char *, size_t);
int setvbuf(FILE *, char *, int, size_t);
FILE *popen(const char *, const char *);
int pclose(FILE *);
char *ctermid(char *);
void perror(const char *) relegated;
@ -94,7 +93,7 @@ int fgetpos(FILE *, fpos_t *) paramsnonnull();
int fsetpos(FILE *, const fpos_t *) paramsnonnull();
int system(const char *);
int systemexec(const char *);
FILE *popen(const char *, const char *);
/*───────────────────────────────────────────────────────────────────────────│─╗
cosmopolitan § standard i/o » formatting

View file

@ -24,6 +24,7 @@
#include "libc/log/log.h"
#include "libc/paths.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/cocmd.internal.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/ok.h"
@ -62,8 +63,7 @@ int system(const char *cmdline) {
sigaction(SIGINT, &saveint, 0);
sigaction(SIGQUIT, &savequit, 0);
sigprocmask(SIG_SETMASK, &savemask, 0);
systemexec(cmdline);
_exit(127);
_Exit(cocmd(3, (char *[]){"system", "-c", cmdline, 0}));
} else if (pid != -1) {
while (wait4(pid, &wstatus, 0, 0) == -1) {
if (errno != EINTR) {

View file

@ -126,6 +126,7 @@ dos kNtErrorNoaccess EFAULT
dos kNtErrorInvalidAddress EADDRNOTAVAIL
dos kNtErrorNotAReparsePoint EINVAL
dos kNtErrorInvalidFunction EINVAL
dos kNtErrorNegativeSeek EINVAL
dos kNtErrorInvalidNetname EADDRNOTAVAIL
dos kNtErrorInvalidUserBuffer EMSGSIZE
dos kNtErrorIoPending EINPROGRESS

View file

@ -11,3 +11,4 @@ kDos2Errno.EINVAL:
.e kNtErrorNotAReparsePoint,EINVAL
.e kNtErrorInvalidFunction,EINVAL
.e WSAEINVAL,EINVAL
.e kNtErrorNegativeSeek,EINVAL

View file

@ -51,7 +51,7 @@ static int x;
char g_testlib_olddir[PATH_MAX];
char g_testlib_tmpdir[PATH_MAX];
struct sigaction wanthandlers[31];
static pthread_spinlock_t testlib_error_lock;
static pthread_mutex_t testlib_error_lock;
void testlib_finish(void) {
if (g_testlib_failed) {
@ -63,7 +63,7 @@ void testlib_finish(void) {
void testlib_error_enter(const char *file, const char *func) {
atomic_fetch_sub_explicit(&__ftrace, 1, memory_order_relaxed);
atomic_fetch_sub_explicit(&__strace, 1, memory_order_relaxed);
if (!__vforked) pthread_spin_lock(&testlib_error_lock);
if (!__vforked) pthread_mutex_lock(&testlib_error_lock);
if (!IsWindows()) sys_getpid(); /* make strace easier to read */
if (!IsWindows()) sys_getpid();
if (g_testlib_shoulddebugbreak) {
@ -76,7 +76,7 @@ void testlib_error_enter(const char *file, const char *func) {
void testlib_error_leave(void) {
atomic_fetch_add_explicit(&__ftrace, 1, memory_order_relaxed);
atomic_fetch_add_explicit(&__strace, 1, memory_order_relaxed);
pthread_spin_unlock(&testlib_error_lock);
pthread_mutex_unlock(&testlib_error_lock);
}
wontreturn void testlib_abort(void) {

View file

@ -16,18 +16,22 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/intrin/safemacros.internal.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/stat.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/safemacros.internal.h"
#include "libc/limits.h"
#include "libc/mem/gc.internal.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/sig.h"
#include "libc/testlib/testlib.h"
#include "libc/x/x.h"
int64_t fd;
struct stat st;
char testlib_enable_tmp_setup_teardown;
@ -36,15 +40,67 @@ void SetUpOnce(void) {
ASSERT_SYS(0, 0, pledge("stdio rpath wpath cpath", 0));
}
TEST(ftruncate, test) {
ASSERT_NE(-1, (fd = creat("foo", 0755)));
ASSERT_EQ(5, write(fd, "hello", 5));
errno = 31337;
ASSERT_NE(-1, ftruncate(fd, 31337));
EXPECT_EQ(31337, errno);
ASSERT_EQ(5, write(fd, "world", 5));
ASSERT_NE(-1, close(fd));
ASSERT_NE(-1, stat("foo", &st));
ASSERT_EQ(31337, st.st_size);
ASSERT_BINEQ(u"helloworld", gc(xslurp("foo", 0)));
TEST(ftruncate, ebadf) {
ASSERT_SYS(EBADF, -1, ftruncate(-1, 0));
ASSERT_SYS(EBADF, -1, ftruncate(+3, 0));
}
TEST(ftruncate, negativeLength_einval) {
ASSERT_SYS(0, 3, creat("foo", 0755));
ASSERT_SYS(EINVAL, -1, ftruncate(3, -1));
ASSERT_SYS(0, 0, close(3));
}
TEST(ftruncate, doesntHaveWritePermission_einval) {
ASSERT_SYS(0, 3, creat("foo", 0755));
ASSERT_SYS(0, 5, write(3, "hello", 5));
ASSERT_SYS(0, 0, close(3));
ASSERT_SYS(0, 3, open("foo", O_RDONLY));
ASSERT_SYS(EINVAL, -1, ftruncate(3, 0));
ASSERT_SYS(0, 0, close(3));
}
TEST(ftruncate, pipeFd_einval) {
int fds[2];
ASSERT_SYS(0, 0, pipe(fds));
ASSERT_SYS(EINVAL, -1, ftruncate(3, 0));
EXPECT_SYS(0, 0, close(4));
EXPECT_SYS(0, 0, close(3));
}
TEST(ftruncate, efbig) {
// FreeBSD and RHEL7 return 0 (why??)
if (IsLinux() || IsFreebsd()) return;
sighandler_t old = signal(SIGXFSZ, SIG_IGN);
ASSERT_SYS(0, 3, creat("foo", 0755));
ASSERT_SYS(IsWindows() ? EINVAL : EFBIG, -1, ftruncate(3, INT64_MAX));
ASSERT_SYS(0, 0, close(3));
signal(SIGXFSZ, old);
}
TEST(ftruncate, test) {
char got[512], want[512] = "helloworld"; // tests zero-extending
ASSERT_SYS(0, 3, open("foo", O_CREAT | O_TRUNC | O_RDWR, 0755));
ASSERT_SYS(0, 5, write(3, "hello", 5));
ASSERT_SYS(0, 0, ftruncate(3, 8192));
ASSERT_SYS(0, 5, lseek(3, 0, SEEK_CUR)); // doesn't change position
ASSERT_SYS(0, 5, write(3, "world", 5));
ASSERT_SYS(0, 0, fstat(3, &st));
ASSERT_EQ(8192, st.st_size); // 8192 is logical size
if (IsWindows() || IsNetbsd() || IsOpenbsd()) { //
ASSERT_EQ(8192 / 512, st.st_blocks); // 8192 is physical size
} else if (IsFreebsd()) { //
ASSERT_EQ(512 / 512, st.st_blocks); // 512 is physical size
} else if (IsLinux() || IsXnu()) { //
ASSERT_EQ(4096 / 512, st.st_blocks); // 4096 is physical size
} else {
notpossible;
}
ASSERT_SYS(0, 512, pread(3, got, 512, 0));
ASSERT_EQ(0, memcmp(want, got, 512));
ASSERT_SYS(0, 0, ftruncate(3, 0)); // shrink file to be empty
ASSERT_SYS(0, 0, pread(3, got, 512, 0));
ASSERT_SYS(0, 0, read(3, got, 512));
ASSERT_SYS(0, 10, lseek(3, 0, SEEK_CUR)); // position stays past eof
ASSERT_SYS(0, 0, close(3));
}

View file

@ -18,10 +18,13 @@
*/
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/errno.h"
#include "libc/fmt/fmt.h"
#include "libc/limits.h"
#include "libc/log/check.h"
#include "libc/runtime/runtime.h"
#include "libc/sysv/consts/o.h"
#include "libc/testlib/subprocess.h"
#include "libc/testlib/testlib.h"
#include "libc/x/x.h"
@ -31,18 +34,73 @@ void SetUpOnce(void) {
ASSERT_SYS(0, 0, pledge("stdio rpath wpath cpath fattr proc", 0));
}
TEST(lseek, wat) {
int fd, pid;
char buf[8] = {0};
ASSERT_NE(-1, (fd = open("wut", O_RDWR | O_CREAT, 0644)));
ASSERT_EQ(3, write(fd, "wut", 3));
ASSERT_NE(-1, lseek(fd, 0, SEEK_SET));
if (!(pid = fork())) {
lseek(fd, 1, SEEK_SET);
_exit(0);
}
EXPECT_NE(-1, waitpid(pid, 0, 0));
EXPECT_EQ(1, read(fd, buf, 1));
EXPECT_EQ('u', buf[0]); /* wat?! */
EXPECT_NE(-1, close(fd));
TEST(lseek, ebadf) {
ASSERT_SYS(EBADF, -1, lseek(-1, 0, SEEK_SET));
ASSERT_SYS(EBADF, -1, lseek(+3, 0, SEEK_SET));
}
TEST(lseek, badWhence_einval) {
ASSERT_SYS(0, 3, creat("foo", 0644));
ASSERT_SYS(EINVAL, -1, lseek(3, 0, -1));
EXPECT_SYS(0, 0, close(3));
}
TEST(lseek, negativeComputedOffset_einval) {
ASSERT_SYS(0, 3, creat("foo", 0644));
ASSERT_SYS(EINVAL, -1, lseek(3, -1, SEEK_SET));
ASSERT_SYS(EINVAL, -1, lseek(3, -1, SEEK_CUR));
ASSERT_SYS(EINVAL, -1, lseek(3, -1, SEEK_END));
EXPECT_SYS(0, 0, close(3));
}
TEST(lseek, 64bit) {
ASSERT_SYS(0, 3, creat("foo", 0644));
ASSERT_SYS(0, 0x100000001, lseek(3, 0x100000001, SEEK_SET));
EXPECT_SYS(0, 0, close(3));
}
TEST(lseek, nonSeekableFd_espipe) {
int fds[2];
ASSERT_SYS(0, 0, pipe(fds));
ASSERT_SYS(ESPIPE, -1, lseek(3, 0, SEEK_SET));
EXPECT_SYS(0, 0, close(4));
EXPECT_SYS(0, 0, close(3));
}
TEST(lseek, filePositionChanges_areObservableAcrossDup) {
ASSERT_SYS(0, 3, creat("wut", 0644));
ASSERT_SYS(0, 4, dup(3));
ASSERT_SYS(0, 0, lseek(3, 0, SEEK_CUR));
ASSERT_SYS(0, 1, lseek(4, 1, SEEK_SET));
ASSERT_SYS(0, 1, lseek(3, 0, SEEK_CUR));
EXPECT_SYS(0, 0, close(4));
EXPECT_SYS(0, 0, close(3));
}
TEST(lseek, filePositionChanges_areObservableAcrossProcesses) {
char buf[8] = {0};
ASSERT_SYS(0, 3, open("wut", O_RDWR | O_CREAT, 0644));
ASSERT_SYS(0, 3, write(3, "wut", 3));
ASSERT_SYS(0, 0, lseek(3, 0, SEEK_SET));
SPAWN(fork);
ASSERT_SYS(0, 1, lseek(3, 1, SEEK_SET));
EXITS(0);
EXPECT_SYS(0, 1, read(3, buf, 1));
EXPECT_EQ('u', buf[0]);
EXPECT_SYS(0, 0, close(3));
}
TEST(lseek, beyondEndOfFile_isZeroExtendedUponSubsequentWrite) {
char buf[8] = {1, 1};
ASSERT_SYS(0, 3, open("foo", O_RDWR | O_CREAT | O_TRUNC, 0644));
ASSERT_SYS(0, 2, lseek(3, 2, SEEK_SET));
ASSERT_SYS(0, 2, lseek(3, 0, SEEK_CUR));
ASSERT_SYS(0, 0, pread(3, buf, 8, 0)); // lseek() alone doesn't extend
ASSERT_SYS(0, 2, write(3, buf, 2)); // does extend once i/o happens
ASSERT_SYS(0, 4, pread(3, buf, 8, 0));
ASSERT_EQ(0, buf[0]);
ASSERT_EQ(0, buf[1]);
ASSERT_EQ(1, buf[2]);
ASSERT_EQ(1, buf[3]);
ASSERT_SYS(0, 0, close(3));
}