Skip to content

Commit 1eb6484

Browse files
committed
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.
1 parent a46ec61 commit 1eb6484

File tree

13 files changed

+247
-207
lines changed

13 files changed

+247
-207
lines changed

libc/calls/calls.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ bool32 isexecutable(const char *);
219219
bool32 isregularfile(const char *);
220220
bool32 issymlink(const char *);
221221
char *commandv(const char *, char *, size_t);
222+
int __getcwd(char *, size_t);
222223
int clone(void *, void *, size_t, int, void *, void *, void *, void *);
223224
int fadvise(int, uint64_t, uint64_t, int);
224225
int makedirs(const char *, unsigned);

libc/calls/calls.mk

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,6 @@ o//libc/calls/prctl.o: \
9999
# we always want -Os because:
100100
# it's early runtime mandatory and quite huge without it
101101
o//libc/calls/getcwd.greg.o \
102-
o//libc/calls/getcwd-nt.greg.o \
103-
o//libc/calls/getcwd-xnu.greg.o \
104102
o//libc/calls/statfs2cosmo.o: private \
105103
CFLAGS += \
106104
-Os

libc/calls/getcwd-nt.greg.c

Lines changed: 0 additions & 84 deletions
This file was deleted.

libc/calls/getcwd.greg.c

Lines changed: 133 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -16,81 +16,154 @@
1616
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
1717
│ PERFORMANCE OF THIS SOFTWARE. │
1818
╚─────────────────────────────────────────────────────────────────────────────*/
19-
#include "libc/assert.h"
2019
#include "libc/calls/calls.h"
21-
#include "libc/calls/state.internal.h"
20+
#include "libc/calls/struct/metastat.internal.h"
2221
#include "libc/calls/syscall-nt.internal.h"
2322
#include "libc/calls/syscall-sysv.internal.h"
2423
#include "libc/dce.h"
24+
#include "libc/errno.h"
2525
#include "libc/intrin/strace.internal.h"
26-
#include "libc/intrin/weaken.h"
2726
#include "libc/limits.h"
28-
#include "libc/log/backtrace.internal.h"
29-
#include "libc/mem/mem.h"
27+
#include "libc/nt/files.h"
28+
#include "libc/stdio/sysparam.h"
3029
#include "libc/str/str.h"
30+
#include "libc/sysv/consts/at.h"
31+
#include "libc/sysv/consts/o.h"
3132
#include "libc/sysv/errfuns.h"
3233

33-
/**
34-
* Returns current working directory, e.g.
35-
*
36-
* const char *dirname = gc(getcwd(0,0)); // if malloc is linked
37-
* const char *dirname = getcwd(alloca(PATH_MAX),PATH_MAX);
38-
*
39-
* @param buf is where UTF-8 NUL-terminated path string gets written,
40-
* which may be NULL to ask this function to malloc a buffer
41-
* @param size is number of bytes available in buf, e.g. PATH_MAX+1,
42-
* which may be 0 if buf is NULL
43-
* @return buf containing system-normative path or NULL w/ errno
44-
* @error ERANGE, EINVAL, ENOMEM
45-
*/
46-
char *getcwd(char *buf, size_t size) {
47-
char *p, *r;
48-
if (buf) {
49-
p = buf;
50-
if (!size) {
51-
einval();
52-
STRACE("getcwd(%p, %'zu) %m", buf, size);
53-
return 0;
54-
}
55-
} else if (_weaken(malloc)) {
56-
unassert(!__vforked);
57-
if (!size) size = PATH_MAX;
58-
if (!(p = _weaken(malloc)(size))) {
59-
STRACE("getcwd(%p, %'zu) %m", buf, size);
60-
return 0;
34+
#define XNU_F_GETPATH 50
35+
#define XNU_MAXPATHLEN 1024
36+
37+
static int sys_getcwd_xnu(char *res, size_t size) {
38+
int fd, len, rc = -1;
39+
union metastat st[2];
40+
char buf[XNU_MAXPATHLEN];
41+
if ((fd = __sys_openat_nc(AT_FDCWD, ".", O_RDONLY | O_DIRECTORY, 0)) != -1) {
42+
if (__sys_fstat(fd, &st[0]) != -1) {
43+
if (st[0].xnu.st_dev && st[0].xnu.st_ino) {
44+
if (__sys_fcntl(fd, XNU_F_GETPATH, (uintptr_t)buf) != -1) {
45+
if (__sys_fstatat(AT_FDCWD, buf, &st[1], 0) != -1) {
46+
if (st[0].xnu.st_dev == st[1].xnu.st_dev &&
47+
st[0].xnu.st_ino == st[1].xnu.st_ino) {
48+
if ((len = strlen(buf)) < size) {
49+
memcpy(res, buf, (rc = len + 1));
50+
} else {
51+
erange();
52+
}
53+
} else {
54+
einval();
55+
}
56+
}
57+
}
58+
} else {
59+
einval();
60+
}
6161
}
62+
sys_close(fd);
63+
}
64+
return rc;
65+
}
66+
67+
static int sys_getcwd_metal(char *buf, size_t size) {
68+
if (size >= 5) {
69+
strcpy(buf, "/zip");
70+
return 5;
6271
} else {
63-
einval();
64-
STRACE("getcwd() needs buf≠0 or __static_yoink(\"malloc\")");
65-
return 0;
72+
return erange();
6673
}
67-
*p = '\0';
68-
if (!IsWindows()) {
69-
if (IsMetal()) {
70-
r = size >= 5 ? strcpy(p, "/zip") : 0;
71-
} else if (IsXnu()) {
72-
r = sys_getcwd_xnu(p, size);
73-
} else if (sys_getcwd(p, size) != (void *)-1) {
74-
r = p;
75-
} else {
76-
r = 0;
74+
}
75+
76+
static inline int IsAlpha(int c) {
77+
return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
78+
}
79+
80+
static dontinline textwindows int sys_getcwd_nt(char *buf, size_t size) {
81+
82+
// get current directory from the system
83+
char16_t p16[PATH_MAX];
84+
uint32_t n = GetCurrentDirectory(PATH_MAX, p16);
85+
if (!n) return eacces(); // system call failed
86+
if (n >= PATH_MAX) return erange(); // not enough room?!?
87+
88+
// convert utf-16 to utf-8
89+
// we can't modify `buf` until we're certain of success
90+
char p8[PATH_MAX], *p = p8;
91+
n = tprecode16to8(p, PATH_MAX, p16).ax;
92+
if (n >= PATH_MAX) return erange(); // utf-8 explosion
93+
94+
// turn \\?\c:\... into c:\...
95+
if (p[0] == '\\' && //
96+
p[1] == '\\' && //
97+
p[2] == '?' && //
98+
p[3] == '\\' && //
99+
IsAlpha(p[4]) && //
100+
p[5] == ':' && //
101+
p[6] == '\\') {
102+
p += 4;
103+
n -= 4;
104+
}
105+
106+
// turn c:\... into \c\...
107+
if (IsAlpha(p[0]) && //
108+
p[1] == ':' && //
109+
p[2] == '\\') {
110+
p[1] = p[0];
111+
p[0] = '\\';
112+
}
113+
114+
// we now know the final length
115+
// check if the user supplied a buffer large enough
116+
if (n >= size) {
117+
return erange();
118+
}
119+
120+
// copy bytes converting backslashes to slash
121+
for (int i = 0; i < n; ++i) {
122+
int c = p[i];
123+
if (c == '\\') {
124+
c = '/';
77125
}
78-
} else {
79-
r = sys_getcwd_nt(p, size);
126+
buf[i] = c;
80127
}
81-
if (!buf) {
82-
if (!r) {
83-
if (_weaken(free)) {
84-
_weaken(free)(p);
85-
}
128+
129+
// return number of bytes including nul
130+
buf[n++] = 0;
131+
return n;
132+
}
133+
134+
/**
135+
* Returns current working directory.
136+
*
137+
* Cosmo provides this function to address the shortcomings of getcwd().
138+
* First, this function doesn't link malloc(). Secondly, this offers the
139+
* Linux and NetBSD's getcwd() API across platforms since it's the best.
140+
*
141+
* @param buf receives utf-8 path and isn't modified on error
142+
* @param size is byte capacity of `buf`
143+
* @return bytes copied including nul on success, or -1 w/ errno
144+
* @raise EACCES if the current directory path couldn't be accessed
145+
* @raise ERANGE if `size` wasn't big enough for path and nul byte
146+
* @raise EFAULT if `buf` points to invalid memory
147+
*/
148+
int __getcwd(char *buf, size_t size) {
149+
int rc;
150+
if (IsLinux() || IsNetbsd()) {
151+
rc = sys_getcwd(buf, size);
152+
} else if (IsXnu()) {
153+
rc = sys_getcwd_xnu(buf, size);
154+
} else if (IsWindows()) {
155+
rc = sys_getcwd_nt(buf, size);
156+
} else if (IsFreebsd() || IsOpenbsd()) {
157+
if (sys_getcwd(buf, size) != -1) {
158+
rc = strlen(buf) + 1;
159+
} else if (SupportsFreebsd() && (errno == ENOMEM || errno == EINVAL)) {
160+
rc = erange();
86161
} else {
87-
if (_weaken(realloc)) {
88-
if ((p = _weaken(realloc)(r, strlen(r) + 1))) {
89-
r = p;
90-
}
91-
}
162+
rc = -1;
92163
}
164+
} else {
165+
rc = sys_getcwd_metal(buf, size);
93166
}
94-
STRACE("getcwd(%p, %'zu) → %#s", buf, size, r);
95-
return r;
167+
STRACE("__getcwd([%#hhs], %'zu) → %d% m", rc != -1 ? buf : "n/a", size, rc);
168+
return rc;
96169
}

libc/calls/getprogramexecutablename.greg.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,9 @@ static inline void InitProgramExecutableNameImpl(void) {
8686
if (q[0] == '.' && q[1] == '/') {
8787
q += 2;
8888
}
89-
if (getcwd(p, e - p - 1 - 4)) { // for / and .com
90-
while (*p) ++p;
89+
int got = __getcwd(p, e - p - 1 - 4); // for / and .com
90+
if (got != -1) {
91+
p += got - 1;
9192
*p++ = '/';
9293
}
9394
}

libc/calls/syscall-nt.internal.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
COSMOPOLITAN_C_START_
55

66
bool32 sys_isatty(int);
7-
char *sys_getcwd_nt(char *, size_t);
87
int sys_chdir_nt(const char *);
98
int sys_close_epoll_nt(int);
109
int sys_dup_nt(int, int, int, int);

libc/calls/syscall-sysv.internal.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ COSMOPOLITAN_C_START_
1313
axdx_t __sys_fork(void);
1414
axdx_t __sys_pipe(i32[hasatleast 2], i32);
1515
axdx_t sys_getpid(void);
16-
char *sys_getcwd(char *, u64);
17-
char *sys_getcwd_xnu(char *, u64);
1816
i32 __sys_dup3(i32, i32, i32);
1917
i32 __sys_execve(const char *, char *const[], char *const[]);
2018
i32 __sys_fcntl(i32, i32, ...);
@@ -53,6 +51,7 @@ i32 sys_fork(void);
5351
i32 sys_fsync(i32);
5452
i32 sys_ftruncate(i32, i64, i64);
5553
i32 sys_getcontext(void *);
54+
i32 sys_getcwd(char *, u64);
5655
i32 sys_getpgid(i32);
5756
i32 sys_getppid(void);
5857
i32 sys_getpriority(i32, u32);
@@ -85,9 +84,9 @@ i32 sys_posix_openpt(i32);
8584
i32 sys_renameat(i32, const char *, i32, const char *);
8685
i32 sys_sem_close(i64);
8786
i32 sys_sem_destroy(i64);
87+
i32 sys_sem_destroy(i64);
8888
i32 sys_sem_getvalue(i64, u32 *);
8989
i32 sys_sem_init(u32, i64 *);
90-
i32 sys_sem_destroy(i64);
9190
i32 sys_sem_open(const char *, int, u32, i64 *);
9291
i32 sys_sem_post(i64);
9392
i32 sys_sem_trywait(i64);

libc/calls/tmpdir.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ static void __tmpdir_init(void) {
4545

4646
if ((s = getenv("TMPDIR"))) {
4747
if (*s != '/') {
48-
if (!getcwd(__tmpdir.path, PATH_MAX)) {
48+
if (__getcwd(__tmpdir.path, PATH_MAX) == -1) {
4949
goto GiveUp;
5050
}
5151
strlcat(__tmpdir.path, "/", sizeof(__tmpdir.path));

libc/mem/realpath.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ char *realpath(const char *filename, char *resolved)
213213
output[q] = 0;
214214

215215
if (output[0] != '/') {
216-
if (!getcwd(stack, sizeof(stack))) return 0;
216+
if (__getcwd(stack, sizeof(stack)) == -1) return 0;
217217
l = strlen(stack);
218218
/* Cancel any initial .. components. */
219219
p = 0;

0 commit comments

Comments
 (0)