node icon indicating copy to clipboard operation
node copied to clipboard

Incorrect cwd() inside subfolders of Windows directory symlinks or junctions

Open silverwind opened this issue 4 years ago • 9 comments

  • Version: v14.8.0
  • Platform: Windows 10 1809
  • Subsystem: process, path

What steps will reproduce the bug?

cd c:
mkdir folder
mklink /D link folder
cd link
node -p 'process.cwd()'
# C:\folder
mkdir sub
cd sub
node -p 'process.cwd()'
# C:\link\sub

Same behaviour with /J (junction) instead of /D (directory symlink).

What is the expected behavior?

It should resolve the path to C:\folder\sub, similar to how it works for symlinks on Unix:

cd /
mkdir folder
ln -s folder link
cd link
node -p 'process.cwd()'
# /folder
mkdir sub
cd sub
node -p 'process.cwd()'
# /folder/sub

Additional information

__filename, __dirname and fs.realpath(".") correctly resolve, process.cwd() and path.resolve(".") don't.

Related issue: https://github.com/babel/babel/issues/10232

silverwind avatar Aug 21 '20 18:08 silverwind

Deep, deep inside process.cwd() is just a GetCurrentDirectoryW. As such this is probably unfixable.

For babel, this probably can be worked around. Cwd is defined by the caller, so maybe adding fs.realpath.native call might help? Also, the cwd printed by Node depends on how you entered the folder.

Basically, I cant reproduce. If I do cd link I get link in process.cwd(). If I fo cd folder, only then I get folder:

C:\...\test>cd link

C:\...\test\link>node -p process.cwd()
C:\...\test\link

C:\...\test\link>cd ..\folder

C:\...\test\folder>node -p process.cwd()
C:\...\test\folder

C:\...\test\folder>cd ssub

C:\...\test\folder\sub>node -p process.cwd()
C:\...\test\folder\sub

C:\...\test\f\s>cd ..\..\link\sub

C:\...\test\link\sub>node -p process.cwd()
C:\...\test\link\sub

bzoz avatar Aug 24 '20 08:08 bzoz

Yes, I think ultimately the bug is in win32 GetCurrentDirectoryW and if we attempt a fix, it should probably be done as close to the source as possible, e.g. in libuv. I've been testing on Windows 10 1809, maybe that behaviour has changed in recent versions.

If I interpret your results correctly, they already show the bug as link should never appear in any cwd() output to match Unix behaviour.

silverwind avatar Aug 24 '20 08:08 silverwind

I don't think this is something we can fix. Forcing process.cwd() to always return a realpath will probably break a lot of users setups. In the end the calling process decides what is the working directory of the child process.

Running this script in the C:\...\test\link folder:

const path = require('path')
const fs = require('fs')

const cwd = path.join(fs.realpathSync(process.cwd()), 'sub');
require('child_process').spawnSync(
  process.execPath,
  ['-p', 'process.cwd()'],
  { stdio: 'inherit',
    cwd
  }
);

produces:

C:\...\test\link>node t.js
C:\...\test\folder\sub

bzoz avatar Aug 24 '20 09:08 bzoz

Forcing process.cwd() to always return a realpath will probably break a lot of users

It should probably be a semver-major change to be safe but I think it's the right thing to so it works the same on Unix (which already seems to return realpath in all cases) and Windows.

silverwind avatar Aug 24 '20 09:08 silverwind

For the record, subst would work if we would do a realpath on all child cwd:

C:\...\test\>subst z: C:\...\test 

C:\...\test\>z:

Z:\>cd link

Z:\link>node t.js
Z:\folder\sub

Bringing Windows and Unix closer together is always a good thing, but I would expect something somewhere to break horribly. We should probably do both a realpath on CWD when starting and when doing process.chdir.

/cc @nodejs/platform-windows

bzoz avatar Aug 24 '20 09:08 bzoz

Hi, just stumbled across this and I'm confused:

In Linux shell:

# setup
$> cd && pwd
/home/jahudka
$> mkdir folder
$> ln -s $(pwd)/folder link

# test
$> cd ~/folder && pwd
/home/jahudka/folder
$> node -e 'console.log(process.cwd());'
/home/jahudka/folder
$> cd ~/link1 && pwd
/home/jahudka/link
$> node -e 'console.log(process.cwd());'
/home/jahudka/folder

As you can see, in Linux the symlink in pwd is not resolved, whereas in Node it is resolved. In fact, the same happens even if the symlink is not the CWD itself, but somewhere higher in the fs tree:

# setup
$> cd && pwd
/home/jahudka
$> mkdir -p folder1/folder2/folder3
$> ln -s $(pwd)/folder1/folder2 link

# test
$> cd ~/link/folder3 && pwd
/home/jahudka/link/folder3
$> node -e 'console.log(process.cwd());'
/home/jahudka/folder1/folder2/folder3

So it seems NodeJS is already doing what you guys want it to do (ie. resolving symlinks in CWD always).. and there appears to be no way to convince it to do what I want (which is the exact opposite, ie. never resolve symlinks in CWD).. sux for me 😂 I've tested this on Node 12 and 14 on Debian and macOS and it behaves exactly the same everywhere.

jahudka avatar Jun 24 '21 18:06 jahudka

As you can see, in Linux the symlink in pwd is not resolved, whereas in Node it is resolved.

pwd is similar to cwd but it is not the same thing. In Linux, the pwd command defaults to -L (logical path) which will not resolve symlinks. You can force it to resolve symlinks with -P (physical path). However, the return value of the system call getcwd() will never contain a symlink. And that's what's relevant here. On Windows, the equivalent-ish system call does not have the same guarantee, apparently.

Trott avatar Nov 24 '22 20:11 Trott

Still relevant found in LTS (v20.11.1) and latest (v21.6.2).

davidnaumann-bastian avatar Mar 04 '24 22:03 davidnaumann-bastian

I have locally implemented a draft to assess the impact of this fix. The tests ran on both libuv and Node and a significant number of tests failed. This inconsistency originates from the Windows API, which has evolved into a feature over time. Therefore, it appears infeasible to fix this issue without affecting a lot of users. With all this considered, I propose closing this issue.

huseyinacacak-janea avatar May 10 '24 12:05 huseyinacacak-janea