mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-07-03 09:48:29 +00:00
Rewrite getcwd()
This change addresses a bug that was reported in #923 where bash on Windows behaved strangely. It turned out that our weak linking of malloc() caused bash's configure script to favor its own getcwd() function, which is implemented in the most astonishing way, using opendir() and readdir() to recursively construct the current path. This change moves getcwd() into LIBC_STDIO so it can strongly link malloc(). A new __getcwd() function is now introduced, so all the low-level runtime services can still use the actual system call. It provides the Linux Kernel API convention across platforms, and is overall a higher-quality implementation than what we had before. In the future, we should probably take a closer look into why bash's getcwd() polyfill wasn't working as intended on Windows, since there might be a potential opportunity there to improve our readdir() too.
This commit is contained in:
parent
a46ec61787
commit
1eb6484c9c
13 changed files with 251 additions and 211 deletions
|
@ -16,81 +16,154 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/calls/state.internal.h"
|
||||
#include "libc/calls/struct/metastat.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/intrin/weaken.h"
|
||||
#include "libc/limits.h"
|
||||
#include "libc/log/backtrace.internal.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/nt/files.h"
|
||||
#include "libc/stdio/sysparam.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/at.h"
|
||||
#include "libc/sysv/consts/o.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
|
||||
/**
|
||||
* Returns current working directory, e.g.
|
||||
*
|
||||
* const char *dirname = gc(getcwd(0,0)); // if malloc is linked
|
||||
* const char *dirname = getcwd(alloca(PATH_MAX),PATH_MAX);
|
||||
*
|
||||
* @param buf is where UTF-8 NUL-terminated path string gets written,
|
||||
* which may be NULL to ask this function to malloc a buffer
|
||||
* @param size is number of bytes available in buf, e.g. PATH_MAX+1,
|
||||
* which may be 0 if buf is NULL
|
||||
* @return buf containing system-normative path or NULL w/ errno
|
||||
* @error ERANGE, EINVAL, ENOMEM
|
||||
*/
|
||||
char *getcwd(char *buf, size_t size) {
|
||||
char *p, *r;
|
||||
if (buf) {
|
||||
p = buf;
|
||||
if (!size) {
|
||||
einval();
|
||||
STRACE("getcwd(%p, %'zu) %m", buf, size);
|
||||
return 0;
|
||||
}
|
||||
} else if (_weaken(malloc)) {
|
||||
unassert(!__vforked);
|
||||
if (!size) size = PATH_MAX;
|
||||
if (!(p = _weaken(malloc)(size))) {
|
||||
STRACE("getcwd(%p, %'zu) %m", buf, size);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
einval();
|
||||
STRACE("getcwd() needs buf≠0 or __static_yoink(\"malloc\")");
|
||||
return 0;
|
||||
}
|
||||
*p = '\0';
|
||||
if (!IsWindows()) {
|
||||
if (IsMetal()) {
|
||||
r = size >= 5 ? strcpy(p, "/zip") : 0;
|
||||
} else if (IsXnu()) {
|
||||
r = sys_getcwd_xnu(p, size);
|
||||
} else if (sys_getcwd(p, size) != (void *)-1) {
|
||||
r = p;
|
||||
} else {
|
||||
r = 0;
|
||||
}
|
||||
} else {
|
||||
r = sys_getcwd_nt(p, size);
|
||||
}
|
||||
if (!buf) {
|
||||
if (!r) {
|
||||
if (_weaken(free)) {
|
||||
_weaken(free)(p);
|
||||
}
|
||||
} else {
|
||||
if (_weaken(realloc)) {
|
||||
if ((p = _weaken(realloc)(r, strlen(r) + 1))) {
|
||||
r = p;
|
||||
#define XNU_F_GETPATH 50
|
||||
#define XNU_MAXPATHLEN 1024
|
||||
|
||||
static int sys_getcwd_xnu(char *res, size_t size) {
|
||||
int fd, len, rc = -1;
|
||||
union metastat st[2];
|
||||
char buf[XNU_MAXPATHLEN];
|
||||
if ((fd = __sys_openat_nc(AT_FDCWD, ".", O_RDONLY | O_DIRECTORY, 0)) != -1) {
|
||||
if (__sys_fstat(fd, &st[0]) != -1) {
|
||||
if (st[0].xnu.st_dev && st[0].xnu.st_ino) {
|
||||
if (__sys_fcntl(fd, XNU_F_GETPATH, (uintptr_t)buf) != -1) {
|
||||
if (__sys_fstatat(AT_FDCWD, buf, &st[1], 0) != -1) {
|
||||
if (st[0].xnu.st_dev == st[1].xnu.st_dev &&
|
||||
st[0].xnu.st_ino == st[1].xnu.st_ino) {
|
||||
if ((len = strlen(buf)) < size) {
|
||||
memcpy(res, buf, (rc = len + 1));
|
||||
} else {
|
||||
erange();
|
||||
}
|
||||
} else {
|
||||
einval();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
einval();
|
||||
}
|
||||
}
|
||||
sys_close(fd);
|
||||
}
|
||||
STRACE("getcwd(%p, %'zu) → %#s", buf, size, r);
|
||||
return r;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int sys_getcwd_metal(char *buf, size_t size) {
|
||||
if (size >= 5) {
|
||||
strcpy(buf, "/zip");
|
||||
return 5;
|
||||
} else {
|
||||
return erange();
|
||||
}
|
||||
}
|
||||
|
||||
static inline int IsAlpha(int c) {
|
||||
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
|
||||
}
|
||||
|
||||
static dontinline textwindows int sys_getcwd_nt(char *buf, size_t size) {
|
||||
|
||||
// get current directory from the system
|
||||
char16_t p16[PATH_MAX];
|
||||
uint32_t n = GetCurrentDirectory(PATH_MAX, p16);
|
||||
if (!n) return eacces(); // system call failed
|
||||
if (n >= PATH_MAX) return erange(); // not enough room?!?
|
||||
|
||||
// convert utf-16 to utf-8
|
||||
// we can't modify `buf` until we're certain of success
|
||||
char p8[PATH_MAX], *p = p8;
|
||||
n = tprecode16to8(p, PATH_MAX, p16).ax;
|
||||
if (n >= PATH_MAX) return erange(); // utf-8 explosion
|
||||
|
||||
// turn \\?\c:\... into c:\...
|
||||
if (p[0] == '\\' && //
|
||||
p[1] == '\\' && //
|
||||
p[2] == '?' && //
|
||||
p[3] == '\\' && //
|
||||
IsAlpha(p[4]) && //
|
||||
p[5] == ':' && //
|
||||
p[6] == '\\') {
|
||||
p += 4;
|
||||
n -= 4;
|
||||
}
|
||||
|
||||
// turn c:\... into \c\...
|
||||
if (IsAlpha(p[0]) && //
|
||||
p[1] == ':' && //
|
||||
p[2] == '\\') {
|
||||
p[1] = p[0];
|
||||
p[0] = '\\';
|
||||
}
|
||||
|
||||
// we now know the final length
|
||||
// check if the user supplied a buffer large enough
|
||||
if (n >= size) {
|
||||
return erange();
|
||||
}
|
||||
|
||||
// copy bytes converting backslashes to slash
|
||||
for (int i = 0; i < n; ++i) {
|
||||
int c = p[i];
|
||||
if (c == '\\') {
|
||||
c = '/';
|
||||
}
|
||||
buf[i] = c;
|
||||
}
|
||||
|
||||
// return number of bytes including nul
|
||||
buf[n++] = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current working directory.
|
||||
*
|
||||
* Cosmo provides this function to address the shortcomings of getcwd().
|
||||
* First, this function doesn't link malloc(). Secondly, this offers the
|
||||
* Linux and NetBSD's getcwd() API across platforms since it's the best.
|
||||
*
|
||||
* @param buf receives utf-8 path and isn't modified on error
|
||||
* @param size is byte capacity of `buf`
|
||||
* @return bytes copied including nul on success, or -1 w/ errno
|
||||
* @raise EACCES if the current directory path couldn't be accessed
|
||||
* @raise ERANGE if `size` wasn't big enough for path and nul byte
|
||||
* @raise EFAULT if `buf` points to invalid memory
|
||||
*/
|
||||
int __getcwd(char *buf, size_t size) {
|
||||
int rc;
|
||||
if (IsLinux() || IsNetbsd()) {
|
||||
rc = sys_getcwd(buf, size);
|
||||
} else if (IsXnu()) {
|
||||
rc = sys_getcwd_xnu(buf, size);
|
||||
} else if (IsWindows()) {
|
||||
rc = sys_getcwd_nt(buf, size);
|
||||
} else if (IsFreebsd() || IsOpenbsd()) {
|
||||
if (sys_getcwd(buf, size) != -1) {
|
||||
rc = strlen(buf) + 1;
|
||||
} else if (SupportsFreebsd() && (errno == ENOMEM || errno == EINVAL)) {
|
||||
rc = erange();
|
||||
} else {
|
||||
rc = -1;
|
||||
}
|
||||
} else {
|
||||
rc = sys_getcwd_metal(buf, size);
|
||||
}
|
||||
STRACE("__getcwd([%#hhs], %'zu) → %d% m", rc != -1 ? buf : "n/a", size, rc);
|
||||
return rc;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue