Skip to content

Commit de475e8

Browse files
committed
syscall: implement Fchdir on Windows
This CL adds support for os.File.Chdir() on Windows by implementing syscall.Fchdir, which is internally used by Chdir. Windows does not provide a function that sets the working directory using a file handle, so we have to fallback to retrieving the file handle path and then use it in SetCurrentDirectory. Change-Id: I2ae93575e50411e5a9426ea531541958d7c9e812 Reviewed-on: https://go-review.googlesource.com/c/go/+/480135 TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Alex Brainman <[email protected]> Run-TryBot: Quim Muntal <[email protected]> Reviewed-by: Damien Neil <[email protected]> Reviewed-by: Michael Knyszek <[email protected]>
1 parent bc5b194 commit de475e8

File tree

3 files changed

+56
-18
lines changed

3 files changed

+56
-18
lines changed

src/os/os_test.go

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1447,11 +1447,6 @@ func testChtimes(t *testing.T, name string) {
14471447
}
14481448

14491449
func TestFileChdir(t *testing.T) {
1450-
// TODO(brainman): file.Chdir() is not implemented on windows.
1451-
if runtime.GOOS == "windows" {
1452-
return
1453-
}
1454-
14551450
wd, err := Getwd()
14561451
if err != nil {
14571452
t.Fatalf("Getwd: %s", err)
@@ -1476,16 +1471,12 @@ func TestFileChdir(t *testing.T) {
14761471
if err != nil {
14771472
t.Fatalf("Getwd: %s", err)
14781473
}
1479-
if wdNew != wd {
1474+
if !equal(wdNew, wd) {
14801475
t.Fatalf("fd.Chdir failed, got %s, want %s", wdNew, wd)
14811476
}
14821477
}
14831478

14841479
func TestChdirAndGetwd(t *testing.T) {
1485-
// TODO(brainman): file.Chdir() is not implemented on windows.
1486-
if runtime.GOOS == "windows" {
1487-
return
1488-
}
14891480
fd, err := Open(".")
14901481
if err != nil {
14911482
t.Fatalf("Open .: %s", err)
@@ -1499,13 +1490,9 @@ func TestChdirAndGetwd(t *testing.T) {
14991490
dirs = []string{"/system/bin"}
15001491
case "plan9":
15011492
dirs = []string{"/", "/usr"}
1502-
case "ios":
1493+
case "ios", "windows":
15031494
dirs = nil
1504-
for _, d := range []string{"d1", "d2"} {
1505-
dir, err := MkdirTemp("", d)
1506-
if err != nil {
1507-
t.Fatalf("TempDir: %v", err)
1508-
}
1495+
for _, dir := range []string{t.TempDir(), t.TempDir()} {
15091496
// Expand symlinks so path equality tests work.
15101497
dir, err = filepath.EvalSymlinks(dir)
15111498
if err != nil {
@@ -1549,7 +1536,7 @@ func TestChdirAndGetwd(t *testing.T) {
15491536
fd.Close()
15501537
t.Fatalf("Getwd in %s: %s", d, err1)
15511538
}
1552-
if pwd != d {
1539+
if !equal(pwd, d) {
15531540
fd.Close()
15541541
t.Fatalf("Getwd returned %q want %q", pwd, d)
15551542
}

src/syscall/syscall_windows.go

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ func (e Errno) Error() string {
143143
}
144144

145145
const (
146+
_ERROR_NOT_ENOUGH_MEMORY = Errno(8)
146147
_ERROR_NOT_SUPPORTED = Errno(50)
147148
_ERROR_BAD_NETPATH = Errno(53)
148149
_ERROR_CALL_NOT_IMPLEMENTED = Errno(120)
@@ -311,6 +312,7 @@ func NewCallbackCDecl(fn any) uintptr {
311312
//sys initializeProcThreadAttributeList(attrlist *_PROC_THREAD_ATTRIBUTE_LIST, attrcount uint32, flags uint32, size *uintptr) (err error) = InitializeProcThreadAttributeList
312313
//sys deleteProcThreadAttributeList(attrlist *_PROC_THREAD_ATTRIBUTE_LIST) = DeleteProcThreadAttributeList
313314
//sys updateProcThreadAttribute(attrlist *_PROC_THREAD_ATTRIBUTE_LIST, flags uint32, attr uintptr, value unsafe.Pointer, size uintptr, prevvalue unsafe.Pointer, returnedsize *uintptr) (err error) = UpdateProcThreadAttribute
315+
//sys getFinalPathNameByHandle(file Handle, filePath *uint16, filePathSize uint32, flags uint32) (n uint32, err error) [n == 0 || n >= filePathSize] = kernel32.GetFinalPathNameByHandleW
314316

315317
// syscall interface implementation for other packages
316318

@@ -1193,8 +1195,47 @@ func Getppid() (ppid int) {
11931195
return int(pe.ParentProcessID)
11941196
}
11951197

1198+
func fdpath(fd Handle, buf []uint16) ([]uint16, error) {
1199+
const (
1200+
FILE_NAME_NORMALIZED = 0
1201+
VOLUME_NAME_DOS = 0
1202+
)
1203+
for {
1204+
n, err := getFinalPathNameByHandle(fd, &buf[0], uint32(len(buf)), FILE_NAME_NORMALIZED|VOLUME_NAME_DOS)
1205+
if err == nil {
1206+
buf = buf[:n]
1207+
break
1208+
}
1209+
if err != _ERROR_NOT_ENOUGH_MEMORY {
1210+
return nil, err
1211+
}
1212+
buf = append(buf, make([]uint16, n-uint32(len(buf)))...)
1213+
}
1214+
return buf, nil
1215+
}
1216+
1217+
func Fchdir(fd Handle) (err error) {
1218+
var buf [MAX_PATH + 1]uint16
1219+
path, err := fdpath(fd, buf[:])
1220+
if err != nil {
1221+
return err
1222+
}
1223+
// When using VOLUME_NAME_DOS, the path is always pefixed by "\\?\".
1224+
// That prefix tells the Windows APIs to disable all string parsing and to send
1225+
// the string that follows it straight to the file system.
1226+
// Although SetCurrentDirectory and GetCurrentDirectory do support the "\\?\" prefix,
1227+
// some other Windows APIs don't. If the prefix is not removed here, it will leak
1228+
// to Getwd, and we don't want such a general-purpose function to always return a
1229+
// path with the "\\?\" prefix after Fchdir is called.
1230+
// The downside is that APIs that do support it will parse the path and try to normalize it,
1231+
// when it's already normalized.
1232+
if len(path) >= 4 && path[0] == '\\' && path[1] == '\\' && path[2] == '?' && path[3] == '\\' {
1233+
path = path[4:]
1234+
}
1235+
return SetCurrentDirectory(&path[0])
1236+
}
1237+
11961238
// TODO(brainman): fix all needed for os
1197-
func Fchdir(fd Handle) (err error) { return EWINDOWS }
11981239
func Link(oldpath, newpath string) (err error) { return EWINDOWS }
11991240
func Symlink(path, link string) (err error) { return EWINDOWS }
12001241

src/syscall/zsyscall_windows.go

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)