fix(kill): resolve a symlinked working directory like vev does
- commit
- 6a2bad67844b5f27c87cf3ac94b6cc54a2848bc1
- author
- brice · · 2026-09-23 21:25 UTC
- parent
- eef302d
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +31 -0
M internal/app/kill_all_linux.go +9 -0
| @@ -191,6 +191,15 @@ func processSocketDir(procDir string, uid int) string { | |||
| 191 | 191 | } | |
| 192 | 192 | } | |
| 193 | 193 | cwd, _ := os.Readlink(filepath.Join(procDir, "cwd")) | |
| 194 | + | // Like os.Getwd, prefer $PWD when it names the same directory, so a repo | |
| 195 | + | // opened through a symlink resolves to the path vev itself used. | |
| 196 | + | if pwd := env["PWD"]; filepath.IsAbs(pwd) { | |
| 197 | + | pwdInfo, pwdErr := os.Stat(pwd) | |
| 198 | + | cwdInfo, cwdErr := os.Stat(filepath.Join(procDir, "cwd")) | |
| 199 | + | if pwdErr == nil && cwdErr == nil && os.SameFile(pwdInfo, cwdInfo) { | |
| 200 | + | cwd = pwd | |
| 201 | + | } | |
| 202 | + | } | |
| 194 | 203 | runtime := platform.RuntimeDirFor(func(key string) string { return env[key] }, cwd) | |
| 195 | 204 | return ipc.SocketDirFor(runtime, uid) | |
| 196 | 205 | } | |
| @@ -191,6 +191,15 @@ func processSocketDir(procDir string, uid int) string { | |||||
| 191 | } | 191 | } | ||
| 192 | } | 192 | } | ||
| 193 | cwd, _ := os.Readlink(filepath.Join(procDir, "cwd")) | 193 | cwd, _ := os.Readlink(filepath.Join(procDir, "cwd")) | ||
| 194 | + | // Like os.Getwd, prefer $PWD when it names the same directory, so a repo | |||
| 195 | + | // opened through a symlink resolves to the path vev itself used. | |||
| 196 | + | if pwd := env["PWD"]; filepath.IsAbs(pwd) { | |||
| 197 | + | pwdInfo, pwdErr := os.Stat(pwd) | |||
| 198 | + | cwdInfo, cwdErr := os.Stat(filepath.Join(procDir, "cwd")) | |||
| 199 | + | if pwdErr == nil && cwdErr == nil && os.SameFile(pwdInfo, cwdInfo) { | |||
| 200 | + | cwd = pwd | |||
| 201 | + | } | |||
| 202 | + | } | |||
| 194 | runtime := platform.RuntimeDirFor(func(key string) string { return env[key] }, cwd) | 203 | runtime := platform.RuntimeDirFor(func(key string) string { return env[key] }, cwd) | ||
| 195 | return ipc.SocketDirFor(runtime, uid) | 204 | return ipc.SocketDirFor(runtime, uid) | ||
| 196 | } | 205 | } | ||
M internal/app/kill_all_linux_test.go +22 -0
| @@ -123,6 +123,28 @@ func TestFindVevProcesses(t *testing.T) { | |||
| 123 | 123 | require.Empty(t, none, "another user's processes are never matched") | |
| 124 | 124 | } | |
| 125 | 125 | ||
| 126 | + | // TestProcessSocketDirPrefersSymlinkedPWD pins that a VEV_ENV client started | |
| 127 | + | // in a repo reached through a symlink resolves to the same path vev used. | |
| 128 | + | func TestProcessSocketDirPrefersSymlinkedPWD(t *testing.T) { | |
| 129 | + | base := t.TempDir() | |
| 130 | + | real := filepath.Join(base, "real") | |
| 131 | + | require.NoError(t, os.Mkdir(real, 0o755)) | |
| 132 | + | link := filepath.Join(base, "link") | |
| 133 | + | require.NoError(t, os.Symlink(real, link)) | |
| 134 | + | ||
| 135 | + | proc := filepath.Join(base, "proc", "20") | |
| 136 | + | require.NoError(t, os.MkdirAll(proc, 0o755)) | |
| 137 | + | require.NoError(t, os.Symlink(real, filepath.Join(proc, "cwd"))) | |
| 138 | + | require.NoError(t, os.WriteFile(filepath.Join(proc, "environ"), []byte("VEV_ENV=dev\x00PWD="+link+"\x00"), 0o644)) | |
| 139 | + | ||
| 140 | + | uid := os.Getuid() | |
| 141 | + | require.Equal(t, ipc.SocketDirFor(filepath.Join(link, ".dev", "dev", "runtime"), uid), processSocketDir(proc, uid)) | |
| 142 | + | ||
| 143 | + | // A stale PWD that names another directory is ignored. | |
| 144 | + | require.NoError(t, os.WriteFile(filepath.Join(proc, "environ"), []byte("VEV_ENV=dev\x00PWD="+base+"\x00"), 0o644)) | |
| 145 | + | require.Equal(t, ipc.SocketDirFor(filepath.Join(real, ".dev", "dev", "runtime"), uid), processSocketDir(proc, uid)) | |
| 146 | + | } | |
| 147 | + | ||
| 126 | 148 | func TestRuntimeDirResolutionMatchesVev(t *testing.T) { | |
| 127 | 149 | tests := []struct { | |
| 128 | 150 | name string | |
| @@ -123,6 +123,28 @@ func TestFindVevProcesses(t *testing.T) { | |||||
| 123 | require.Empty(t, none, "another user's processes are never matched") | 123 | require.Empty(t, none, "another user's processes are never matched") | ||
| 124 | } | 124 | } | ||
| 125 | 125 | ||||
| 126 | + | // TestProcessSocketDirPrefersSymlinkedPWD pins that a VEV_ENV client started | |||
| 127 | + | // in a repo reached through a symlink resolves to the same path vev used. | |||
| 128 | + | func TestProcessSocketDirPrefersSymlinkedPWD(t *testing.T) { | |||
| 129 | + | base := t.TempDir() | |||
| 130 | + | real := filepath.Join(base, "real") | |||
| 131 | + | require.NoError(t, os.Mkdir(real, 0o755)) | |||
| 132 | + | link := filepath.Join(base, "link") | |||
| 133 | + | require.NoError(t, os.Symlink(real, link)) | |||
| 134 | + | ||||
| 135 | + | proc := filepath.Join(base, "proc", "20") | |||
| 136 | + | require.NoError(t, os.MkdirAll(proc, 0o755)) | |||
| 137 | + | require.NoError(t, os.Symlink(real, filepath.Join(proc, "cwd"))) | |||
| 138 | + | require.NoError(t, os.WriteFile(filepath.Join(proc, "environ"), []byte("VEV_ENV=dev\x00PWD="+link+"\x00"), 0o644)) | |||
| 139 | + | ||||
| 140 | + | uid := os.Getuid() | |||
| 141 | + | require.Equal(t, ipc.SocketDirFor(filepath.Join(link, ".dev", "dev", "runtime"), uid), processSocketDir(proc, uid)) | |||
| 142 | + | ||||
| 143 | + | // A stale PWD that names another directory is ignored. | |||
| 144 | + | require.NoError(t, os.WriteFile(filepath.Join(proc, "environ"), []byte("VEV_ENV=dev\x00PWD="+base+"\x00"), 0o644)) | |||
| 145 | + | require.Equal(t, ipc.SocketDirFor(filepath.Join(real, ".dev", "dev", "runtime"), uid), processSocketDir(proc, uid)) | |||
| 146 | + | } | |||
| 147 | + | ||||
| 126 | func TestRuntimeDirResolutionMatchesVev(t *testing.T) { | 148 | func TestRuntimeDirResolutionMatchesVev(t *testing.T) { | ||
| 127 | tests := []struct { | 149 | tests := []struct { | ||
| 128 | name string | 150 | name string | ||