windows: add GetDiskFreeSpaceEx function

ref:
https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-getdiskfreespaceexw

Change-Id: If57b0777106a2253e4287818d2c5aee2d6be13d3
Reviewed-on: https://go-review.googlesource.com/c/sys/+/200257
Run-TryBot: Jason A. Donenfeld <Jason@zx2c4.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Jason A. Donenfeld <Jason@zx2c4.com>
diff --git a/windows/syscall_windows.go b/windows/syscall_windows.go
index 33513e3..df0ffc6 100644
--- a/windows/syscall_windows.go
+++ b/windows/syscall_windows.go
@@ -290,6 +290,7 @@
 //sys	FindNextVolumeMountPoint(findVolumeMountPoint Handle, volumeMountPoint *uint16, bufferLength uint32) (err error) = FindNextVolumeMountPointW
 //sys	FindVolumeClose(findVolume Handle) (err error)
 //sys	FindVolumeMountPointClose(findVolumeMountPoint Handle) (err error)
+//sys	GetDiskFreeSpaceEx(directoryName *uint16, freeBytesAvailableToCaller *uint64, totalNumberOfBytes *uint64, totalNumberOfFreeBytes *uint64) (err error) = GetDiskFreeSpaceExW
 //sys	GetDriveType(rootPathName *uint16) (driveType uint32) = GetDriveTypeW
 //sys	GetLogicalDrives() (drivesBitMask uint32, err error) [failretval==0]
 //sys	GetLogicalDriveStrings(bufferLength uint32, buffer *uint16) (n uint32, err error) [failretval==0] = GetLogicalDriveStringsW
diff --git a/windows/syscall_windows_test.go b/windows/syscall_windows_test.go
index a63df60..7f23216 100644
--- a/windows/syscall_windows_test.go
+++ b/windows/syscall_windows_test.go
@@ -356,3 +356,24 @@
 		t.Fatalf("SD = %q; want %q", got, want)
 	}
 }
+
+func TestGetDiskFreeSpaceEx(t *testing.T) {
+	cwd, err := windows.UTF16PtrFromString(".")
+	if err != nil {
+		t.Fatalf(`failed to call UTF16PtrFromString("."): %v`, err)
+	}
+	var freeBytesAvailableToCaller, totalNumberOfBytes, totalNumberOfFreeBytes uint64
+	if err := windows.GetDiskFreeSpaceEx(cwd, &freeBytesAvailableToCaller, &totalNumberOfBytes, &totalNumberOfFreeBytes); err != nil {
+		t.Fatalf("failed to call GetDiskFreeSpaceEx: %v", err)
+	}
+
+	if freeBytesAvailableToCaller == 0 {
+		t.Errorf("freeBytesAvailableToCaller: got 0; want > 0")
+	}
+	if totalNumberOfBytes == 0 {
+		t.Errorf("totalNumberOfBytes: got 0; want > 0")
+	}
+	if totalNumberOfFreeBytes == 0 {
+		t.Errorf("totalNumberOfFreeBytes: got 0; want > 0")
+	}
+}
diff --git a/windows/zsyscall_windows.go b/windows/zsyscall_windows.go
index ace2c19..74d721e 100644
--- a/windows/zsyscall_windows.go
+++ b/windows/zsyscall_windows.go
@@ -224,6 +224,7 @@
 	procFindNextVolumeMountPointW                            = modkernel32.NewProc("FindNextVolumeMountPointW")
 	procFindVolumeClose                                      = modkernel32.NewProc("FindVolumeClose")
 	procFindVolumeMountPointClose                            = modkernel32.NewProc("FindVolumeMountPointClose")
+	procGetDiskFreeSpaceExW                                  = modkernel32.NewProc("GetDiskFreeSpaceExW")
 	procGetDriveTypeW                                        = modkernel32.NewProc("GetDriveTypeW")
 	procGetLogicalDrives                                     = modkernel32.NewProc("GetLogicalDrives")
 	procGetLogicalDriveStringsW                              = modkernel32.NewProc("GetLogicalDriveStringsW")
@@ -2503,6 +2504,18 @@
 	return
 }
 
+func GetDiskFreeSpaceEx(directoryName *uint16, freeBytesAvailableToCaller *uint64, totalNumberOfBytes *uint64, totalNumberOfFreeBytes *uint64) (err error) {
+	r1, _, e1 := syscall.Syscall6(procGetDiskFreeSpaceExW.Addr(), 4, uintptr(unsafe.Pointer(directoryName)), uintptr(unsafe.Pointer(freeBytesAvailableToCaller)), uintptr(unsafe.Pointer(totalNumberOfBytes)), uintptr(unsafe.Pointer(totalNumberOfFreeBytes)), 0, 0)
+	if r1 == 0 {
+		if e1 != 0 {
+			err = errnoErr(e1)
+		} else {
+			err = syscall.EINVAL
+		}
+	}
+	return
+}
+
 func GetDriveType(rootPathName *uint16) (driveType uint32) {
 	r0, _, _ := syscall.Syscall(procGetDriveTypeW.Addr(), 1, uintptr(unsafe.Pointer(rootPathName)), 0, 0)
 	driveType = uint32(r0)