Improve system call documentation

This change also introduces partial faccessat() support for zipos and
makes some slight breaking changes in errno results. close() is fixed
to use `EBADF` rather than `EINVAL` and we're now using `ENOTSUP` not
`EOPNOTSUPP` to indicate that zipos doesn't support a system call yet
This commit is contained in:
Justine Tunney 2022-10-02 07:42:44 -07:00
parent 0b5f84dd20
commit ad97775370
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
18 changed files with 273 additions and 67 deletions

View file

@ -22,9 +22,14 @@
/**
* Checks if effective user can access path in particular ways.
*
* This is equivalent to saying:
*
* faccessat(AT_FDCWD, path, mode, 0);
*
* @param path is a filename or directory
* @param mode can be R_OK, W_OK, X_OK, F_OK
* @return 0 if ok, or -1 and sets errno
* @return 0 if ok, or -1 w/ errno
* @see faccessat() for further documentation
* @asyncsignalsafe
*/
int access(const char *path, int mode) {

View file

@ -32,17 +32,25 @@
/**
* Closes file descriptor.
*
* This function may be used for file descriptors returned by functions
* like open, socket, accept, epoll_create, and landlock_create_ruleset.
* This function releases resources returned by functions such as:
*
* This function should never be called twice for the same file
* descriptor, regardless of whether or not an error happened. However
* that doesn't mean the error should be ignored.
* - openat()
* - socket()
* - accept()
* - epoll_create()
* - landlock_create_ruleset()
*
* This function should never be reattempted if an error is returned;
* however, that doesn't mean the error should be ignored. This goes
* against the conventional wisdom of looping on `EINTR`.
*
* @return 0 on success, or -1 w/ errno
* @error EINTR means a signal was received while closing in which case
* close() does not need to be called again, since the fd will close
* in the background
* @raise EINTR if signal was delivered; do *not* retry
* @raise EBADF if `fd` is negative or not open; however, an exception
* is made by Cosmopolitan Libc for `close(-1)` which returns zero
* and does nothing, in order to assist with code that may wish to
* close the same resource multiple times without dirtying `errno`
* @raise EIO if a low-level i/o error occurred
* @asyncsignalsafe
* @vforksafe
*/
@ -51,7 +59,7 @@ int close(int fd) {
if (fd == -1) {
rc = 0;
} else if (fd < 0) {
rc = einval();
rc = ebadf();
} else {
// for performance reasons we want to avoid holding __fds_lock()
// while sys_close() is happening. this leaves the kernel / libc

View file

@ -17,6 +17,7 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/sigset.h"
#include "libc/calls/struct/sigset.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
@ -79,6 +80,7 @@ static void copy_file_range_init(void) {
* @raise EXDEV if source and destination are on different filesystems
* @raise EBADF if `infd` or `outfd` aren't open files or append-only
* @raise EPERM if `fdout` refers to an immutable file on Linux
* @raise ENOTSUP if `infd` or `outfd` is a zip file descriptor
* @raise EINVAL if ranges overlap or `flags` is non-zero
* @raise EFBIG if `setrlimit(RLIMIT_FSIZE)` is exceeded
* @raise EFAULT if one of the pointers memory is bad
@ -104,6 +106,8 @@ ssize_t copy_file_range(int infd, int64_t *opt_in_out_inoffset, int outfd,
(opt_in_out_outoffset &&
!__asan_is_valid(opt_in_out_outoffset, 8)))) {
rc = efault();
} else if (__isfdkind(infd, kFdZip) || __isfdkind(outfd, kFdZip)) {
rc = enotsup();
} else {
rc = sys_copy_file_range(infd, opt_in_out_inoffset, outfd,
opt_in_out_outoffset, uptobytes, flags);

View file

@ -21,20 +21,21 @@
#include "libc/sysv/consts/o.h"
/**
* Creates new file, returning open()'d file descriptor.
* Creates file.
*
* This function is shorthand for:
* This is equivalent to saying:
*
* open(file, O_CREAT | O_WRONLY | O_TRUNC, mode)
* int fd = openat(AT_FDCWD, file, O_CREAT | O_WRONLY | O_TRUNC, mode);
*
* @param file is a UTF-8 string, which is truncated if it exists
* @param mode is an octal user/group/other permission, e.g. 0755
* @return a number registered with the system to track the open file,
* which must be stored using a 64-bit type in order to support both
* System V and Windows, and must be closed later on using close()
* @see open(), touch()
* @param file specifies filesystem path to create
* @param mode is octal bits, e.g. 0644 usually
* @return file descriptor, or -1 w/ errno
* @see openat() for further documentation
* @asyncsignalsafe
* @restartable
* @threadsafe
* @vforksafe
*/
dontdiscard int creat(const char *file, uint32_t mode) {
int creat(const char *file, uint32_t mode) {
return openat(AT_FDCWD, file, O_CREAT | O_WRONLY | O_TRUNC, mode);
}

View file

@ -32,15 +32,16 @@
*
* @param fd remains open afterwards
* @return some arbitrary new number for fd
* @raise EOPNOTSUPP if zipos file
* @raise EBADF if fd isn't open
* @raise EPERM if pledge() is in play without stdio
* @raise ENOTSUP if `fd` is a zip file descriptor
* @raise EBADF if `fd` is negative or not open
* @asyncsignalsafe
* @vforksafe
*/
int dup(int fd) {
int rc;
if (__isfdkind(fd, kFdZip)) {
rc = eopnotsupp();
rc = enotsup();
} else if (!IsWindows()) {
rc = sys_dup(fd);
} else {

View file

@ -18,10 +18,10 @@
*/
#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/intrin/strace.internal.h"
#include "libc/sysv/errfuns.h"
/**
@ -38,16 +38,19 @@
* @param newfd if already assigned, is silently closed beforehand;
* unless it's equal to oldfd, in which case dup2() is a no-op
* @return new file descriptor, or -1 w/ errno
* @raise EBADF is oldfd isn't open
* @raise EBADF is newfd negative or too big
* @raise EPERM if pledge() is in play without stdio
* @raise EMFILE if `RLIMIT_NOFILE` has been reached
* @raise ENOTSUP if `oldfd` is on zip file system
* @raise EINTR if a signal handler was called
* @raise EBADF is `newfd` negative or too big
* @raise EBADF is `oldfd` isn't open
* @asyncsignalsafe
* @vforksafe
*/
int dup2(int oldfd, int newfd) {
int rc;
if (__isfdkind(oldfd, kFdZip)) {
rc = eopnotsupp();
rc = enotsup();
} else if (!IsWindows()) {
rc = sys_dup2(oldfd, newfd);
} else if (newfd < 0) {

View file

@ -38,11 +38,13 @@
* @param flags may have O_CLOEXEC which is needed to preserve the
* close-on-execve() state after file descriptor duplication
* @return newfd on success, or -1 w/ errno
* @raise EINVAL if flags has unsupported bits
* @raise EINVAL if newfd equals oldfd
* @raise EBADF is oldfd isn't open
* @raise EBADF is newfd negative or too big
* @raise ENOTSUP if `oldfd` is a zip file descriptor
* @raise EPERM if pledge() is in play without stdio
* @raise EINVAL if `flags` has unsupported bits
* @raise EINTR if a signal handler was called
* @raise EBADF is `newfd` negative or too big
* @raise EINVAL if `newfd` equals oldfd
* @raise EBADF is `oldfd` isn't open
* @see dup(), dup2()
*/
int dup3(int oldfd, int newfd, int flags) {
@ -52,7 +54,7 @@ int dup3(int oldfd, int newfd, int flags) {
} else if (oldfd < 0 || newfd < 0) {
rc = ebadf();
} else if (__isfdkind(oldfd, kFdZip)) {
rc = eopnotsupp();
rc = enotsup();
} else if (!IsWindows()) {
rc = sys_dup3(oldfd, newfd, flags);
} else {

View file

@ -17,6 +17,7 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/syscall-nt.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
@ -35,33 +36,42 @@
* @param dirfd is normally AT_FDCWD but if it's an open directory and
* file is a relative path, then file is opened relative to dirfd
* @param path is a filename or directory
* @param mode can be R_OK, W_OK, X_OK, F_OK
* @param flags can have AT_EACCESS, AT_SYMLINK_NOFOLLOW
* @note on Linux flags is only supported on Linux 5.8+
* @param amode can be `R_OK`, `W_OK`, `X_OK`, or `F_OK`
* @param flags can have `AT_EACCESS` and/or `AT_SYMLINK_NOFOLLOW`
* @return 0 if ok, or -1 and sets errno
* @raise EINVAL if `mode` has bad value
* @raise EPERM if pledge() is in play without rpath promise
* @raise EACCES if access for requested `mode` would be denied
* @raise ENOTDIR if a directory component in `path` exists as non-directory
* @raise ENOENT if component of `path` doesn't exist or `path` is empty
* @raise ENOTSUP if `path` is a zip file and `dirfd` isn't `AT_FDCWD`
* @note on Linux `flags` is only supported on Linux 5.8+
* @asyncsignalsafe
*/
int faccessat(int dirfd, const char *path, int mode, uint32_t flags) {
int faccessat(int dirfd, const char *path, int amode, uint32_t flags) {
int e, rc;
if (IsAsan() && !__asan_is_valid(path, 1)) {
struct ZiposUri zipname;
if (!path || (IsAsan() && !__asan_is_valid(path, 1))) {
rc = efault();
} else if (_weaken(__zipos_notat) &&
_weaken(__zipos_notat)(dirfd, path) == -1) {
rc = -1; /* TODO(jart): implement me */
} else if (__isfdkind(dirfd, kFdZip)) {
rc = enotsup();
} else if (_weaken(__zipos_open) &&
_weaken(__zipos_parseuri)(path, &zipname) != -1) {
rc = _weaken(__zipos_access)(&zipname, amode);
} else if (!IsWindows()) {
e = errno;
if (!flags) goto NoFlags;
if ((rc = sys_faccessat2(dirfd, path, mode, flags)) == -1) {
if ((rc = sys_faccessat2(dirfd, path, amode, flags)) == -1) {
if (errno == ENOSYS) {
errno = e;
NoFlags:
rc = sys_faccessat(dirfd, path, mode, flags);
rc = sys_faccessat(dirfd, path, amode, flags);
}
}
} else {
rc = sys_faccessat_nt(dirfd, path, mode, flags);
rc = sys_faccessat_nt(dirfd, path, amode, flags);
}
STRACE("faccessat(%s, %#s, %#o, %#x) → %d% m", DescribeDirfd(dirfd), path,
mode, flags, rc);
amode, flags, rc);
return rc;
}

View file

@ -37,6 +37,7 @@
* @param path must exist
* @param mode contains octal flags (base 8)
* @param flags can have `AT_SYMLINK_NOFOLLOW`
* @raise ENOTSUP if `dirfd` or `path` use zip file system
* @errors ENOENT, ENOTDIR, ENOSYS
* @asyncsignalsafe
* @see fchmod()
@ -47,7 +48,7 @@ int fchmodat(int dirfd, const char *path, uint32_t mode, int flags) {
rc = efault();
} else if (_weaken(__zipos_notat) &&
(rc = __zipos_notat(dirfd, path)) == -1) {
rc = eopnotsupp();
rc = enotsup();
} else if (!IsWindows()) {
rc = sys_fchmodat(dirfd, path, mode, flags);
} else {

View file

@ -34,6 +34,7 @@
* @param gid is group id, or -1 to not change
* @param flags can have AT_SYMLINK_NOFOLLOW, etc.
* @return 0 on success, or -1 w/ errno
* @raise ENOTSUP if `dirfd` or `path` use zip file system
* @see chown(), lchown() for shorthand notation
* @see /etc/passwd for user ids
* @see /etc/group for group ids
@ -46,7 +47,7 @@ int fchownat(int dirfd, const char *path, uint32_t uid, uint32_t gid,
rc = efault();
} else if (_weaken(__zipos_notat) &&
(rc = __zipos_notat(dirfd, path)) == -1) {
STRACE("zipos fchownat not supported yet");
rc = enotsup();
} else {
rc = sys_fchownat(dirfd, path, uid, gid, flags);
}

View file

@ -24,6 +24,6 @@
textwindows int sys_getsetpriority_nt(int which, unsigned who, int value,
int (*impl)(int)) {
if (which != PRIO_PROCESS && which != PRIO_PGRP) return einval();
if (who && who != getpid() && who != gettid()) return eopnotsupp();
if (who && who != getpid() && who != gettid()) return esrch();
return impl(value);
}

View file

@ -22,16 +22,17 @@
/**
* Opens file.
*
* @param file is a UTF-8 string, preferably relative w/ forward slashes
* @param flags should be O_RDONLY, O_WRONLY, or O_RDWR, and can be or'd
* with O_CREAT, O_TRUNC, O_APPEND, O_EXCL, O_CLOEXEC, O_TMPFILE
* @param mode is an octal user/group/other permission signifier, that's
* ignored if O_CREAT or O_TMPFILE weren't passed
* @return number needing close(), or -1 w/ errno
* @asyncsignalsafe (zip files may have issues)
* @vforksafe (raises error if zip file)
* This is equivalent to saying:
*
* int fd = openat(AT_FDCWD, file, flags, ...);
*
* @param file specifies filesystem path to open
* @return file descriptor, or -1 w/ errno
* @see openat() for further documentation
* @asyncsignalsafe
* @restartable
* @threadsafe
* @vforksafe
*/
int open(const char *file, int flags, ...) {
va_list va;

View file

@ -36,17 +36,113 @@
/**
* Opens file.
*
* @param dirfd is normally AT_FDCWD but if it's an open directory and
* file is a relative path, then file is opened relative to dirfd
* @param file is a UTF-8 string, preferably relative w/ forward slashes
* @param flags should be O_RDONLY, O_WRONLY, or O_RDWR, and can be or'd
* with O_CREAT, O_TRUNC, O_APPEND, O_EXCL, O_CLOEXEC, O_TMPFILE
* @param mode is an octal user/group/other permission signifier, that's
* ignored if O_CREAT or O_TMPFILE weren't passed
* @return number needing close(), or -1 w/ errno
* @asyncsignalsafe (zip files may have issues)
* @vforksafe (raises error if zip file)
* Here's an example of how a file can be created:
*
* int fd = openat(AT_FDCWD, "hi.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
* write(fd, "hello\n", 6);
* close(fd);
*
* Here's an example of how that file could read back into memory:
*
* char data[513] = {0};
* int fd = openat(AT_FDCWD, "hi.txt", O_RDONLY);
* read(fd, data, 512);
* close(fd);
* assert(!strcmp(data, "hello\n"));
*
* If your main() source file has this statement:
*
* STATIC_YOINK("zip_uri_support");
*
* Then you can read zip assets by adding a `"/zip/..."` prefix to `file`, e.g.
*
* // run `zip program.com hi.txt` beforehand
* openat(AT_FDCWD, "/zip/hi.txt", O_RDONLY);
*
* @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:\`
* @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
* 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_CLOEXEC` automatic close() upon execve()
* - `O_EXCL` exclusive access (see below)
* - `O_APPEND` open file for appending only
* - `O_EXEC` open file for execution only; see fexecve()
* - `O_NOCTTY` prevents `file` possibly becoming controlling terminal
* - `O_NONBLOCK` asks read/write to fail with `EAGAIN` rather than block
* - `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_NOFOLLOW` fail if it's a symlink (zero on Windows)
* - `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)
* - `O_SHLOCK` it's complicated (zero on non-BSD)
* - `O_EXLOCK` it's complicated (zero on non-BSD)
* - `O_PATH` open only for metadata (Linux 2.6.39+ otherwise zero)
* - `O_NOATIME` don't record access time (zero on non-Linux)
* - `O_RANDOM` hint random access intent (zero on non-Windows)
* - `O_SEQUENTIAL` hint sequential access intent (zero on non-Windows)
* - `O_COMPRESSED` ask fs to abstract compression (zero on non-Windows)
* - `O_INDEXED` turns on that slow performance (zero on non-Windows)
* - `O_TMPFILE` should not be used; use tmpfd() or tmpfile() instead
* There are three regular combinations for the above flags:
* - `O_RDONLY`: Opens existing file for reading. If it doesn't
* exist then nil is returned and errno will be `ENOENT` (or in
* some other cases `ENOTDIR`).
* - `O_WRONLY|O_CREAT|O_TRUNC`: Creates file. If it already
* exists, then the existing copy is destroyed and the opened
* file will start off with a length of zero. This is the
* behavior of the traditional creat() system call.
* - `O_WRONLY|O_CREAT|O_EXCL`: Create file only if doesn't exist
* already. If it does exist then `nil` is returned along with
* `errno` set to `EEXIST`.
* @param mode is an octal user/group/other permission signifier that's
* ignored if `O_CREAT` isn't passed in `flags`; when creating files
* you'll usually want `mode` to be `0644` which enables global read
* and only permits the owner to write; or when creating executables
* you'll usually want `mode` to be `0755` which is the same, except
* 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 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 EINTR if we needed to block and a signal was delivered instead
* @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 ENOMEM if insufficient memory was available
* @raise EMFILE if `RLIMIT_NOFILE` has been reached
* @raise EOPNOTSUPP if `file` names a named socket
* @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
* @asyncsignalsafe
* @restartable
* @threadsafe
* @vforksafe
*/
int openat(int dirfd, const char *file, int flags, ...) {
int rc;
@ -63,7 +159,7 @@ int openat(int dirfd, const char *file, int flags, ...) {
if (!__vforked && dirfd == AT_FDCWD) {
rc = _weaken(__zipos_open)(&zipname, flags, mode);
} else {
rc = eopnotsupp(); /* TODO */
rc = enotsup(); /* TODO */
}
} else if (!IsWindows() && !IsMetal()) {
rc = sys_openat(dirfd, file, flags, mode);
@ -73,7 +169,7 @@ int openat(int dirfd, const char *file, int flags, ...) {
rc = sys_open_nt(dirfd, file, flags, mode);
}
} else {
rc = eopnotsupp(); /* TODO */
rc = enotsup(); /* TODO */
}
} else {
rc = efault();

View file

@ -17,6 +17,7 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h"
#include "libc/errno.h"
@ -64,6 +65,7 @@ static void splice_init(void) {
* used as both an input and output parameter for pwrite() behavior
* @return number of bytes transferred, 0 on input end, or -1 w/ errno
* @raise EBADF if `infd` or `outfd` aren't open files or append-only
* @raise ENOTSUP if `infd` or `outfd` is a zip file descriptor
* @raise ESPIPE if an offset arg was specified for a pipe fd
* @raise EINVAL if offset was given for non-seekable device
* @raise EINVAL if file system doesn't support splice()
@ -85,6 +87,8 @@ ssize_t splice(int infd, int64_t *opt_in_out_inoffset, int outfd,
(opt_in_out_outoffset &&
!__asan_is_valid(opt_in_out_outoffset, 8)))) {
rc = efault();
} else if (__isfdkind(infd, kFdZip) || __isfdkind(outfd, kFdZip)) {
rc = enotsup();
} else {
rc = sys_splice(infd, opt_in_out_inoffset, outfd, opt_in_out_outoffset,
uptobytes, flags);