mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-06-27 06:48:31 +00:00
Fix handling of paths with dirfd on Windows
This change fixes an issue with all system calls ending with *at(), when the caller passes `dirfd != AT_FDCWD` and an absolute path. It's because the old code was turning paths like C:\bin\ls into \\C:\bin\ls\C:\bin\ls after being converted from paths like /C/bin/ls. I noticed this when the Emacs dired mode stopped working. It's unclear if it's a regression with Cosmopolitan Libc or if this was introduced by the Emacs v29 upgrade. It also impacted posix_spawn() for which a newly minted example now exists.
This commit is contained in:
parent
a089c07ddc
commit
39e7f24947
10 changed files with 373 additions and 46 deletions
|
@ -18,6 +18,7 @@
|
|||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/internal.h"
|
||||
#include "libc/calls/syscall_support-nt.internal.h"
|
||||
#include "libc/intrin/kprintf.h"
|
||||
#include "libc/intrin/strace.h"
|
||||
#include "libc/macros.h"
|
||||
#include "libc/nt/enum/fileflagandattributes.h"
|
||||
|
@ -27,6 +28,18 @@
|
|||
#include "libc/sysv/consts/at.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
|
||||
static int IsAlpha(int c) {
|
||||
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
|
||||
}
|
||||
|
||||
static bool IsAbsolutePathWin32(char16_t *path) {
|
||||
if (path[0] == '\\')
|
||||
return true;
|
||||
if (IsAlpha(path[0]) && path[1] == ':')
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static textwindows int __mkntpathath_impl(int64_t dirhand, const char *path,
|
||||
int flags,
|
||||
char16_t file[hasatleast PATH_MAX]) {
|
||||
|
@ -39,7 +52,7 @@ static textwindows int __mkntpathath_impl(int64_t dirhand, const char *path,
|
|||
return -1;
|
||||
if (!filelen)
|
||||
return enoent();
|
||||
if (file[0] != u'\\' && dirhand != AT_FDCWD) { // ProTip: \\?\C:\foo
|
||||
if (dirhand != AT_FDCWD && !IsAbsolutePathWin32(file)) {
|
||||
dirlen = GetFinalPathNameByHandle(dirhand, dir, ARRAYLEN(dir),
|
||||
kNtFileNameNormalized | kNtVolumeNameDos);
|
||||
if (!dirlen)
|
||||
|
@ -49,7 +62,8 @@ static textwindows int __mkntpathath_impl(int64_t dirhand, const char *path,
|
|||
dir[dirlen] = u'\\';
|
||||
memcpy(dir + dirlen + 1, file, (filelen + 1) * sizeof(char16_t));
|
||||
memcpy(file, dir, ((n = dirlen + 1 + filelen) + 1) * sizeof(char16_t));
|
||||
return __normntpath(file, n);
|
||||
int res = __normntpath(file, n);
|
||||
return res;
|
||||
} else {
|
||||
return filelen;
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ TryAgain:
|
|||
hHandle = __imp_CreateFileW(lpFileName, dwDesiredAccess, dwShareMode,
|
||||
opt_lpSecurity, dwCreationDisposition,
|
||||
dwFlagsAndAttributes, opt_hTemplateFile);
|
||||
NTTRACE("CreateFile(%#hs, %s, %s, %s, %s, %s, %ld) → {%ld, %d}", lpFileName,
|
||||
NTTRACE("CreateFile(%#!hs, %s, %s, %s, %s, %s, %ld) → {%ld, %d}", lpFileName,
|
||||
_DescribeNtFileAccessFlags(buf_accessflags, dwDesiredAccess),
|
||||
_DescribeNtFileShareFlags(buf_shareflags, dwShareMode),
|
||||
_DescribeNtSecurityAttributes(buf_secattr, opt_lpSecurity),
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "libc/intrin/likely.h"
|
||||
#include "libc/intrin/strace.h"
|
||||
#include "libc/nt/runtime.h"
|
||||
#include "libc/nt/struct/iovec.h"
|
||||
#include "libc/nt/thunk/msabi.h"
|
||||
#include "libc/nt/winsock.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
|
@ -54,8 +55,8 @@ textwindows int WSARecvFrom(
|
|||
}
|
||||
if (UNLIKELY(__strace > 0) && strace_enabled(0) > 0) {
|
||||
kprintf(STRACE_PROLOGUE "WSARecvFrom(%lu, [", s);
|
||||
DescribeIovNt(inout_lpBuffers, dwBufferCount,
|
||||
rc != -1 ? NumberOfBytesRecvd : 0);
|
||||
_DescribeIovNt(inout_lpBuffers, dwBufferCount,
|
||||
rc != -1 ? NumberOfBytesRecvd : 0);
|
||||
kprintf("], %u, [%'u], %p, %p, %p, %s, %p) → %d %d\n", dwBufferCount,
|
||||
NumberOfBytesRecvd, opt_out_fromsockaddr, opt_inout_fromsockaddrlen,
|
||||
inout_lpFlags, DescribeNtOverlapped(opt_inout_lpOverlapped),
|
||||
|
|
|
@ -450,23 +450,101 @@ static textwindows dontinline errno_t posix_spawn_nt(
|
|||
* posix_spawnattr_destroy(&sa);
|
||||
* while (wait(&status) != -1);
|
||||
*
|
||||
* This provides superior process creation performance across systems
|
||||
* The posix_spawn() function may be used to launch subprocesses. The
|
||||
* primary advantage of using posix_spawn() instead of the traditional
|
||||
* fork() / execve() combination for launching processes is efficiency
|
||||
* and cross-platform compatibility.
|
||||
*
|
||||
* Processes are normally spawned by calling fork() and execve(), but
|
||||
* that goes slow on Windows if the caller has allocated a nontrivial
|
||||
* number of memory mappings, all of which need to be copied into the
|
||||
* forked child, only to be destroyed a moment later. On UNIX systems
|
||||
* fork() bears a similar cost that's 100x less bad, which is copying
|
||||
* the page tables. So what this implementation does is on Windows it
|
||||
* calls CreateProcess() directly and on UNIX it uses vfork() if it's
|
||||
* possible (XNU and OpenBSD don't have it). On UNIX this API has the
|
||||
* benefit of avoiding the footguns of using vfork() directly because
|
||||
* this implementation will ensure signal handlers can't be called in
|
||||
* the child process since that'd likely corrupt the parent's memory.
|
||||
* On systems with a real vfork() implementation, the execve() status
|
||||
* code is returned by this function via shared memory; otherwise, it
|
||||
* gets passed via a temporary pipe (on systems like QEmu, Blink, and
|
||||
* XNU/OpenBSD) whose support is auto-detected at runtime.
|
||||
* 1. On Linux, FreeBSD, and NetBSD:
|
||||
*
|
||||
* Cosmopolitan Libc's posix_spawn() uses vfork() under the hood on
|
||||
* these platforms automatically, since it's faster than fork(). It's
|
||||
* because vfork() creates a child process without needing to copy
|
||||
* the parent's page tables, making it more efficient, especially for
|
||||
* large processes. Furthermore, vfork() avoids the need to acquire
|
||||
* every single mutex (see pthread_atfork() for more details) which
|
||||
* makes it scalable in multi-threaded apps, since the other threads
|
||||
* in your app can keep going while the spawning thread waits for the
|
||||
* subprocess to call execve(). Normally vfork() is error-prone since
|
||||
* there exists few functions that are @vforksafe. the posix_spawn()
|
||||
* API is designed to offer maximum assurance that you can't shoot
|
||||
* yourself in the foot. If you do, then file a bug with Cosmo.
|
||||
*
|
||||
* 2. On Windows:
|
||||
*
|
||||
* posix_spawn() avoids fork() entirely. Windows doesn't natively
|
||||
* support fork(), and emulating it can be slow and memory-intensive.
|
||||
* By using posix_spawn(), we get a much faster process creation on
|
||||
* Windows systems, because it only needs to call CreateProcess().
|
||||
* Your file actions are replayed beforehand in a simulated way. Only
|
||||
* Cosmopolitan Libc offers this level of quality. With Cygwin you'd
|
||||
* have to use its proprietary APIs to achieve the same performance.
|
||||
*
|
||||
* 3. Simplified error handling:
|
||||
*
|
||||
* posix_spawn() combines process creation and program execution in a
|
||||
* single call, reducing the points of failure and simplifying error
|
||||
* handling. One important thing that happens with Cosmopolitan's
|
||||
* posix_spawn() implementation is that the error code of execve()
|
||||
* inside your subprocess, should it fail, will be propagated to your
|
||||
* parent process. This will happen efficiently via vfork() shared
|
||||
* memory in the event your Linux environment supports this. If it
|
||||
* doesn't, then Cosmopolitan will fall back to a throwaway pipe().
|
||||
* The pipe is needed on platforms like XNU and OpenBSD which do not
|
||||
* support vfork(). It's also needed under QEMU User.
|
||||
*
|
||||
* 4. Signal safety:
|
||||
*
|
||||
* posix_spawn() guarantees your signal handler callback functions
|
||||
* won't be executed in the child process. By default, it'll remove
|
||||
* sigaction() callbacks atomically. This ensures that if something
|
||||
* like a SIGTERM or SIGHUP is sent to the child process before it's
|
||||
* had a chance to call execve(), then the child process will simply
|
||||
* be terminated (like the spawned process would) instead of running
|
||||
* whatever signal handlers the spawning process has installed. If
|
||||
* you've set some signals to SIG_IGN, then that'll be preserved for
|
||||
* the child process by posix_spawn(), unless you explicitly call
|
||||
* posix_spawnattr_setsigdefault() to reset them.
|
||||
*
|
||||
* 5. Portability:
|
||||
*
|
||||
* posix_spawn() is part of the POSIX standard, making it more
|
||||
* portable across different UNIX-like systems and Windows (with
|
||||
* appropriate libraries). Even the non-POSIX APIs we use here are
|
||||
* portable; e.g. posix_spawn_file_actions_addchdir_np() is supported
|
||||
* by glibc, musl libc, and apple libc too.
|
||||
*
|
||||
* When using posix_spawn() you have the option of passing an attributes
|
||||
* object that specifies how the child process should be created. These
|
||||
* functions are provided by Cosmopolitan Libc for setting attributes:
|
||||
*
|
||||
* - posix_spawnattr_init()
|
||||
* - posix_spawnattr_destroy()
|
||||
* - posix_spawnattr_setflags()
|
||||
* - posix_spawnattr_getflags()
|
||||
* - posix_spawnattr_setsigmask()
|
||||
* - posix_spawnattr_getsigmask()
|
||||
* - posix_spawnattr_setpgroup()
|
||||
* - posix_spawnattr_getpgroup()
|
||||
* - posix_spawnattr_setrlimit_np()
|
||||
* - posix_spawnattr_getrlimit_np()
|
||||
* - posix_spawnattr_setschedparam()
|
||||
* - posix_spawnattr_getschedparam()
|
||||
* - posix_spawnattr_setschedpolicy()
|
||||
* - posix_spawnattr_getschedpolicy()
|
||||
* - posix_spawnattr_setsigdefault()
|
||||
* - posix_spawnattr_getsigdefault()
|
||||
*
|
||||
* You can also pass an ordered list of file actions to perform. The
|
||||
* following APIs are provided by Cosmopolitan Libc for doing that:
|
||||
*
|
||||
* - posix_spawn_file_actions_init()
|
||||
* - posix_spawn_file_actions_destroy()
|
||||
* - posix_spawn_file_actions_adddup2()
|
||||
* - posix_spawn_file_actions_addopen()
|
||||
* - posix_spawn_file_actions_addclose()
|
||||
* - posix_spawn_file_actions_addchdir_np()
|
||||
* - posix_spawn_file_actions_addfchdir_np()
|
||||
*
|
||||
* @param pid if non-null shall be set to child pid on success
|
||||
* @param path is resolved path of program which is not `$PATH` searched
|
||||
|
@ -496,31 +574,30 @@ errno_t posix_spawn(int *pid, const char *path,
|
|||
sigset_t blockall, oldmask;
|
||||
int child, res, cs, e = errno;
|
||||
volatile bool can_clobber = false;
|
||||
short flags = attrp && *attrp ? (*attrp)->flags : 0;
|
||||
sigfillset(&blockall);
|
||||
sigprocmask(SIG_SETMASK, &blockall, &oldmask);
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
|
||||
if ((use_pipe = !atomic_load_explicit(&has_vfork, memory_order_acquire))) {
|
||||
if ((use_pipe = (flags & POSIX_SPAWN_USEFORK) ||
|
||||
!atomic_load_explicit(&has_vfork, memory_order_acquire))) {
|
||||
if (pipe2(pfds, O_CLOEXEC)) {
|
||||
res = errno;
|
||||
goto ParentFailed;
|
||||
}
|
||||
}
|
||||
if (!(child = vfork())) {
|
||||
if (!(child = (flags & POSIX_SPAWN_USEFORK) ? fork() : vfork())) {
|
||||
can_clobber = true;
|
||||
sigset_t childmask;
|
||||
bool lost_cloexec = 0;
|
||||
struct sigaction dfl = {0};
|
||||
short flags = attrp && *attrp ? (*attrp)->flags : 0;
|
||||
if (use_pipe)
|
||||
close(pfds[0]);
|
||||
for (int sig = 1; sig < _NSIG; sig++) {
|
||||
for (int sig = 1; sig < _NSIG; sig++)
|
||||
if (__sighandrvas[sig] != (long)SIG_DFL &&
|
||||
(__sighandrvas[sig] != (long)SIG_IGN ||
|
||||
((flags & POSIX_SPAWN_SETSIGDEF) &&
|
||||
sigismember(&(*attrp)->sigdefault, sig) == 1))) {
|
||||
sigismember(&(*attrp)->sigdefault, sig) == 1)))
|
||||
sigaction(sig, &dfl, 0);
|
||||
}
|
||||
}
|
||||
if (flags & POSIX_SPAWN_SETSID)
|
||||
setsid();
|
||||
if ((flags & POSIX_SPAWN_SETPGROUP) && setpgid(0, (*attrp)->pgroup))
|
||||
|
@ -585,7 +662,7 @@ errno_t posix_spawn(int *pid, const char *path,
|
|||
if (sched_setparam(0, &(*attrp)->schedparam))
|
||||
goto ChildFailed;
|
||||
}
|
||||
if (flags & POSIX_SPAWN_SETRLIMIT) {
|
||||
if (flags & POSIX_SPAWN_SETRLIMIT_NP) {
|
||||
int rlimset = (*attrp)->rlimset;
|
||||
while (rlimset) {
|
||||
int resource = bsf(rlimset);
|
||||
|
@ -618,9 +695,8 @@ errno_t posix_spawn(int *pid, const char *path,
|
|||
}
|
||||
_Exit(127);
|
||||
}
|
||||
if (use_pipe) {
|
||||
if (use_pipe)
|
||||
close(pfds[1]);
|
||||
}
|
||||
if (child != -1) {
|
||||
if (!use_pipe) {
|
||||
res = status;
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
#define POSIX_SPAWN_SETSCHEDPARAM 16
|
||||
#define POSIX_SPAWN_SETSCHEDULER 32
|
||||
#define POSIX_SPAWN_SETSID 128
|
||||
#define POSIX_SPAWN_SETRLIMIT 256
|
||||
#define POSIX_SPAWN_SETRLIMIT_NP 256
|
||||
#define POSIX_SPAWN_USEFORK 512
|
||||
|
||||
COSMOPOLITAN_C_START_
|
||||
|
||||
|
@ -55,10 +56,10 @@ int posix_spawnattr_getsigdefault(const posix_spawnattr_t *,
|
|||
sigset_t *) libcesque;
|
||||
int posix_spawnattr_setsigdefault(posix_spawnattr_t *,
|
||||
const sigset_t *) libcesque;
|
||||
int posix_spawnattr_getrlimit(const posix_spawnattr_t *, int,
|
||||
struct rlimit *) libcesque;
|
||||
int posix_spawnattr_setrlimit(posix_spawnattr_t *, int,
|
||||
const struct rlimit *) libcesque;
|
||||
int posix_spawnattr_getrlimit_np(const posix_spawnattr_t *, int,
|
||||
struct rlimit *) libcesque;
|
||||
int posix_spawnattr_setrlimit_np(posix_spawnattr_t *, int,
|
||||
const struct rlimit *) libcesque;
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
#endif /* COSMOPOLITAN_LIBC_STDIO_SPAWN_H_ */
|
||||
|
|
|
@ -31,8 +31,8 @@
|
|||
* @raise EINVAL if `resource` is invalid or unsupported by host
|
||||
* @raise ENOENT if `resource` is absent
|
||||
*/
|
||||
int posix_spawnattr_getrlimit(const posix_spawnattr_t *attr, int resource,
|
||||
struct rlimit *rlim) {
|
||||
int posix_spawnattr_getrlimit_np(const posix_spawnattr_t *attr, int resource,
|
||||
struct rlimit *rlim) {
|
||||
if (0 <= resource && resource < MIN(RLIM_NLIMITS, ARRAYLEN((*attr)->rlim))) {
|
||||
if (((*attr)->rlimset & (1u << resource))) {
|
||||
*rlim = (*attr)->rlim[resource];
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
*
|
||||
* @param attr was initialized by posix_spawnattr_init()
|
||||
* @param flags may have any of the following
|
||||
* - `POSIX_SPAWN_USEFORK`
|
||||
* - `POSIX_SPAWN_USEVFORK`
|
||||
* - `POSIX_SPAWN_RESETIDS`
|
||||
* - `POSIX_SPAWN_SETPGROUP`
|
||||
* - `POSIX_SPAWN_SETSIGDEF`
|
||||
|
@ -32,12 +34,13 @@
|
|||
* - `POSIX_SPAWN_SETSCHEDPARAM`
|
||||
* - `POSIX_SPAWN_SETSCHEDULER`
|
||||
* - `POSIX_SPAWN_SETSID`
|
||||
* - `POSIX_SPAWN_SETRLIMIT`
|
||||
* - `POSIX_SPAWN_SETRLIMIT_NP`
|
||||
* @return 0 on success, or errno on error
|
||||
* @raise EINVAL if `flags` has invalid bits
|
||||
*/
|
||||
int posix_spawnattr_setflags(posix_spawnattr_t *attr, short flags) {
|
||||
if (flags & ~(POSIX_SPAWN_USEVFORK | //
|
||||
if (flags & ~(POSIX_SPAWN_USEFORK | //
|
||||
POSIX_SPAWN_USEVFORK | //
|
||||
POSIX_SPAWN_RESETIDS | //
|
||||
POSIX_SPAWN_SETPGROUP | //
|
||||
POSIX_SPAWN_SETSIGDEF | //
|
||||
|
@ -45,7 +48,7 @@ int posix_spawnattr_setflags(posix_spawnattr_t *attr, short flags) {
|
|||
POSIX_SPAWN_SETSCHEDPARAM | //
|
||||
POSIX_SPAWN_SETSCHEDULER | //
|
||||
POSIX_SPAWN_SETSID | //
|
||||
POSIX_SPAWN_SETRLIMIT)) {
|
||||
POSIX_SPAWN_SETRLIMIT_NP)) {
|
||||
return EINVAL;
|
||||
}
|
||||
(*attr)->flags = flags;
|
||||
|
|
|
@ -26,14 +26,14 @@
|
|||
/**
|
||||
* Sets resource limit on spawned process.
|
||||
*
|
||||
* You also need to pass `POSIX_SPAWN_SETRLIMIT` to
|
||||
* You also need to pass `POSIX_SPAWN_SETRLIMIT_NP` to
|
||||
* posix_spawnattr_setflags() for it to take effect.
|
||||
*
|
||||
* @return 0 on success, or errno on error
|
||||
* @raise EINVAL if resource is invalid
|
||||
*/
|
||||
int posix_spawnattr_setrlimit(posix_spawnattr_t *attr, int resource,
|
||||
const struct rlimit *rlim) {
|
||||
int posix_spawnattr_setrlimit_np(posix_spawnattr_t *attr, int resource,
|
||||
const struct rlimit *rlim) {
|
||||
if (0 <= resource && resource < MIN(RLIM_NLIMITS, ARRAYLEN((*attr)->rlim))) {
|
||||
(*attr)->rlimset |= 1u << resource;
|
||||
(*attr)->rlim[resource] = *rlim;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue