Skip to content

Commit 5fce0ce

Browse files
author
Andy C
committed
[core] Implement POSIX rule for $PWD
This fixes the Perl build failure. If $PWD is an absolute path, and it's the same file as '.', then use it as our "cached" $PWD value. This means that symlinks to $PWD work. Otherwise, recompute it. Thanks to Samuel for finding this bug, and narrowing it down. It still took some effort to find on top: - grepping through dash source code (but missing this important part) - uploading dash source to Claude and asking questions - finding the reference to the POSIX rule in bash source code
1 parent c6a54f2 commit 5fce0ce

File tree

5 files changed

+59
-10
lines changed

5 files changed

+59
-10
lines changed

core/pyos.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,3 +294,24 @@ def MakeDirCacheKey(path):
294294
directory accesses."""
295295
st = posix.stat(path)
296296
return (path, int(st.st_mtime))
297+
298+
299+
def IsSameFile(path1, path2):
300+
# type: (str, str) -> bool
301+
302+
try:
303+
st1 = posix.stat(path1)
304+
except OSError:
305+
return False
306+
307+
try:
308+
st2 = posix.stat(path2)
309+
except OSError:
310+
return False
311+
312+
if st1.st_dev != st2.st_dev:
313+
return False
314+
if st1.st_ino != st2.st_ino:
315+
return False
316+
317+
return True

core/sh_init.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -248,22 +248,28 @@ def InitVarsAfterEnv(mem, mutable_opts):
248248

249249
# NOTE: bash also has BASHOPTS
250250

251+
our_pwd = None # type: Optional[str]
251252
val = mem.GetValue('PWD')
252-
if val.tag() == value_e.Undef:
253-
state.SetGlobalString(mem, 'PWD', GetWorkingDir())
253+
if val.tag() == value_e.Str:
254+
env_pwd = cast(value.Str, val).s
255+
# POSIX rule: PWD is inherited if it's an absolute path that corresponds to '.'
256+
if env_pwd.startswith('/') and pyos.IsSameFile(env_pwd, '.'):
257+
our_pwd = env_pwd
258+
259+
# POSIX: Otherwise, recalculate it
260+
if our_pwd is None:
261+
our_pwd = GetWorkingDir()
262+
254263
# It's EXPORTED, even if it's not set. bash and dash both do this:
255264
# env -i -- dash -c env
256265
mem.SetNamed(location.LName('PWD'),
257-
None,
266+
value.Str(our_pwd),
258267
scope_e.GlobalOnly,
259268
flags=state.SetExport)
260269

261270
# Set a MUTABLE GLOBAL that's SEPARATE from $PWD. It's used by the 'pwd'
262271
# builtin, and it can't be modified by users.
263-
val = mem.GetValue('PWD')
264-
assert val.tag() == value_e.Str, val
265-
pwd = cast(value.Str, val).s
266-
mem.SetPwd(pwd)
272+
mem.SetPwd(our_pwd)
267273

268274

269275
def InitInteractive(mem, sh_files, lang):

cpp/core.cc

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,6 @@ List<int>* WaitForReading(List<int>* fd_list) {
274274
return ret;
275275
}
276276

277-
278277
IOError_OSError* FlushStdout() {
279278
// Flush libc buffers
280279
if (::fflush(stdout) != 0) {
@@ -292,6 +291,23 @@ Tuple2<BigStr*, int>* MakeDirCacheKey(BigStr* path) {
292291
return Alloc<Tuple2<BigStr*, int>>(path, st.st_mtime);
293292
}
294293

294+
bool IsSameFile(BigStr* path1, BigStr* path2) {
295+
struct stat st1, st2;
296+
if (::stat(path1->data(), &st1)) {
297+
return false;
298+
}
299+
if (::stat(path2->data(), &st2)) {
300+
return false;
301+
}
302+
if (st1.st_dev != st2.st_dev) {
303+
return false;
304+
}
305+
if (st1.st_ino != st2.st_ino) {
306+
return false;
307+
}
308+
return true;
309+
}
310+
295311
Tuple2<int, void*> PushTermAttrs(int fd, int mask) {
296312
struct termios* term_attrs =
297313
static_cast<struct termios*>(malloc(sizeof(struct termios)));

cpp/core.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ void PopTermAttrs(int fd, int orig_local_modes, void* term_attrs);
8989

9090
Tuple2<BigStr*, int>* MakeDirCacheKey(BigStr* path);
9191

92+
bool IsSameFile(BigStr* path1, BigStr* path2);
93+
9294
} // namespace pyos
9395

9496
namespace pyutil {

spec/builtin-cd.test.sh

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
## compare_shells: dash bash mksh zsh
22
## oils_failures_allowed: 3
3-
## oils_cpp_failures_allowed: 5
3+
## oils_cpp_failures_allowed: 3
44

55
#### cd and $PWD
66
cd /
@@ -453,7 +453,11 @@ PWD = /tmp/osh-spec-cd/cpan/Encode/Byte
453453
/tmp/osh-spec-cd/cpan/Encode/Byte
454454
## END
455455

456-
#### getcwd() syscall is not made
456+
#### Survey of getcwd() syscall
457+
458+
# This is not that important -- see core/sh_init.py
459+
# Instead of verifying that stat('.') == stat(PWD), which is two sycalls,
460+
# OSH just calls getcwd() unconditionally.
457461

458462
# so C++ leak sanitizer doesn't print to stderr
459463
export ASAN_OPTIONS='detect_leaks=0'

0 commit comments

Comments
 (0)