mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 19:43:32 +00:00
965516e313
- Get SIGWINCH working again on the New Technology - Correctly handle O_NOFOLLOW in open() on Windows - Implement synthetic umask() functionality on Windows - Do a better job managing file execute access on Windows - Fill in `st_uid` and `st_gid` with username hash on Windows - Munge UNICODE control pictures into control codes on Windows - Do a better job ensuring Windows console settings are restored - Introduce KPRINTF_LOG environment variable to log kprintf to a file
205 lines
10 KiB
C
205 lines
10 KiB
C
/*-*- 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/cp.internal.h"
|
|
#include "libc/calls/internal.h"
|
|
#include "libc/calls/state.internal.h"
|
|
#include "libc/calls/syscall-nt.internal.h"
|
|
#include "libc/calls/syscall-sysv.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/log/log.h"
|
|
#include "libc/runtime/zipos.internal.h"
|
|
#include "libc/str/str.h"
|
|
#include "libc/sysv/consts/at.h"
|
|
#include "libc/sysv/consts/o.h"
|
|
#include "libc/sysv/errfuns.h"
|
|
|
|
/**
|
|
* Opens 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("zipos");
|
|
*
|
|
* 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);
|
|
*
|
|
* Cosmopolitan's general approach on Windows to path translation is to
|
|
*
|
|
* - replace `/' with `\`
|
|
* - translate utf-8 into utf-16
|
|
* - turn `"\X\foo"` into `"\\?\X:\foo"`
|
|
* - turn `"\X"` into `"\\?\X:\"`
|
|
* - turn `"X:\foo"` into `"\\?\X:\foo"`
|
|
*
|
|
* On Windows, opening files in `/tmp` will open them in GetTempPath(),
|
|
* which is a secure per-user directory. Opening `/dev/tty` will open a
|
|
* special console file descriptor holding both `CONIN$` and `CONOUT$`,
|
|
* 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:\`
|
|
* @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_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_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)
|
|
* - `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 EEXIST if `O_CREAT|O_EXCL` are used and `file` already existed
|
|
* @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 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
|
|
* @cancellationpoint
|
|
* @asyncsignalsafe
|
|
* @restartable
|
|
* @threadsafe
|
|
* @vforksafe
|
|
*/
|
|
int openat(int dirfd, const char *file, int flags, ...) {
|
|
int rc;
|
|
va_list va;
|
|
unsigned mode;
|
|
struct ZiposUri zipname;
|
|
va_start(va, flags);
|
|
mode = va_arg(va, unsigned);
|
|
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 {
|
|
rc = efault();
|
|
}
|
|
|
|
END_CANCELLATION_POINT;
|
|
STRACE("openat(%s, %#s, %s, %#o) → %d% m", DescribeDirfd(dirfd), file,
|
|
DescribeOpenFlags(flags), (flags & (O_CREAT | O_TMPFILE)) ? mode : 0,
|
|
rc);
|
|
return rc;
|
|
}
|