summaryrefslogtreecommitdiff
path: root/src/pkg/os/stat_windows.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/os/stat_windows.go')
-rw-r--r--src/pkg/os/stat_windows.go258
1 files changed, 235 insertions, 23 deletions
diff --git a/src/pkg/os/stat_windows.go b/src/pkg/os/stat_windows.go
index 11088436a..75351c805 100644
--- a/src/pkg/os/stat_windows.go
+++ b/src/pkg/os/stat_windows.go
@@ -4,43 +4,255 @@
package os
-import "syscall"
+import (
+ "sync"
+ "syscall"
+ "time"
+ "unsafe"
+)
-func fileInfoFromStat(name string, fi *FileInfo, lstat, stat *syscall.Stat_t) *FileInfo {
- return fileInfoFromWin32finddata(fi, &stat.Windata)
+// Stat returns the FileInfo structure describing file.
+// If there is an error, it will be of type *PathError.
+func (file *File) Stat() (fi FileInfo, err error) {
+ if file == nil || file.fd < 0 {
+ return nil, syscall.EINVAL
+ }
+ if file.isdir() {
+ // I don't know any better way to do that for directory
+ return Stat(file.name)
+ }
+ if file.name == DevNull {
+ return statDevNull()
+ }
+ var d syscall.ByHandleFileInformation
+ e := syscall.GetFileInformationByHandle(syscall.Handle(file.fd), &d)
+ if e != nil {
+ return nil, &PathError{"GetFileInformationByHandle", file.name, e}
+ }
+ return &fileStat{
+ name: basename(file.name),
+ size: mkSize(d.FileSizeHigh, d.FileSizeLow),
+ modTime: mkModTime(d.LastWriteTime),
+ mode: mkMode(d.FileAttributes),
+ sys: mkSysFromFI(&d),
+ }, nil
}
-func fileInfoFromWin32finddata(fi *FileInfo, d *syscall.Win32finddata) *FileInfo {
- return setFileInfo(fi, string(syscall.UTF16ToString(d.FileName[0:])), d.FileAttributes, d.FileSizeHigh, d.FileSizeLow, d.CreationTime, d.LastAccessTime, d.LastWriteTime)
+// Stat returns a FileInfo structure describing the named file.
+// If there is an error, it will be of type *PathError.
+func Stat(name string) (fi FileInfo, err error) {
+ if len(name) == 0 {
+ return nil, &PathError{"Stat", name, syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)}
+ }
+ if name == DevNull {
+ return statDevNull()
+ }
+ var d syscall.Win32FileAttributeData
+ e := syscall.GetFileAttributesEx(syscall.StringToUTF16Ptr(name), syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&d)))
+ if e != nil {
+ return nil, &PathError{"GetFileAttributesEx", name, e}
+ }
+ path := name
+ if !isAbs(path) {
+ cwd, _ := Getwd()
+ path = cwd + `\` + path
+ }
+ return &fileStat{
+ name: basename(name),
+ size: mkSize(d.FileSizeHigh, d.FileSizeLow),
+ modTime: mkModTime(d.LastWriteTime),
+ mode: mkMode(d.FileAttributes),
+ sys: mkSys(path, d.LastAccessTime, d.CreationTime),
+ }, nil
+}
+
+// Lstat returns the FileInfo structure describing the named file.
+// If the file is a symbolic link, the returned FileInfo
+// describes the symbolic link. Lstat makes no attempt to follow the link.
+// If there is an error, it will be of type *PathError.
+func Lstat(name string) (fi FileInfo, err error) {
+ // No links on Windows
+ return Stat(name)
}
-func fileInfoFromByHandleInfo(fi *FileInfo, name string, d *syscall.ByHandleFileInformation) *FileInfo {
- for i := len(name) - 1; i >= 0; i-- {
+// statDevNull return FileInfo structure describing DevNull file ("NUL").
+// It creates invented data, since none of windows api will return
+// that information.
+func statDevNull() (fi FileInfo, err error) {
+ return &fileStat{
+ name: DevNull,
+ mode: ModeDevice | ModeCharDevice | 0666,
+ sys: &winSys{
+ // hopefully this will work for SameFile
+ vol: 0,
+ idxhi: 0,
+ idxlo: 0,
+ },
+ }, nil
+}
+
+// basename removes trailing slashes and the leading
+// directory name and drive letter from path name.
+func basename(name string) string {
+ // Remove drive letter
+ if len(name) == 2 && name[1] == ':' {
+ name = "."
+ } else if len(name) > 2 && name[1] == ':' {
+ name = name[2:]
+ }
+ i := len(name) - 1
+ // Remove trailing slashes
+ for ; i > 0 && (name[i] == '/' || name[i] == '\\'); i-- {
+ name = name[:i]
+ }
+ // Remove leading directory name
+ for i--; i >= 0; i-- {
if name[i] == '/' || name[i] == '\\' {
name = name[i+1:]
break
}
}
- return setFileInfo(fi, name, d.FileAttributes, d.FileSizeHigh, d.FileSizeLow, d.CreationTime, d.LastAccessTime, d.LastWriteTime)
+ return name
+}
+
+func isSlash(c uint8) bool {
+ return c == '\\' || c == '/'
+}
+
+func isAbs(path string) (b bool) {
+ v := volumeName(path)
+ if v == "" {
+ return false
+ }
+ path = path[len(v):]
+ if path == "" {
+ return false
+ }
+ return isSlash(path[0])
+}
+
+func volumeName(path string) (v string) {
+ if len(path) < 2 {
+ return ""
+ }
+ // with drive letter
+ c := path[0]
+ if path[1] == ':' &&
+ ('0' <= c && c <= '9' || 'a' <= c && c <= 'z' ||
+ 'A' <= c && c <= 'Z') {
+ return path[:2]
+ }
+ // is it UNC
+ if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) &&
+ !isSlash(path[2]) && path[2] != '.' {
+ // first, leading `\\` and next shouldn't be `\`. its server name.
+ for n := 3; n < l-1; n++ {
+ // second, next '\' shouldn't be repeated.
+ if isSlash(path[n]) {
+ n++
+ // third, following something characters. its share name.
+ if !isSlash(path[n]) {
+ if path[n] == '.' {
+ break
+ }
+ for ; n < l; n++ {
+ if isSlash(path[n]) {
+ break
+ }
+ }
+ return path[:n]
+ }
+ break
+ }
+ }
+ }
+ return ""
}
-func setFileInfo(fi *FileInfo, name string, fa, sizehi, sizelo uint32, ctime, atime, wtime syscall.Filetime) *FileInfo {
- fi.Mode = 0
+type winSys struct {
+ sync.Mutex
+ path string
+ atime, ctime syscall.Filetime
+ vol, idxhi, idxlo uint32
+}
+
+func mkSize(hi, lo uint32) int64 {
+ return int64(hi)<<32 + int64(lo)
+}
+
+func mkModTime(mtime syscall.Filetime) time.Time {
+ return time.Unix(0, mtime.Nanoseconds())
+}
+
+func mkMode(fa uint32) (m FileMode) {
if fa&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
- fi.Mode = fi.Mode | syscall.S_IFDIR
- } else {
- fi.Mode = fi.Mode | syscall.S_IFREG
+ m |= ModeDir
}
if fa&syscall.FILE_ATTRIBUTE_READONLY != 0 {
- fi.Mode = fi.Mode | 0444
+ m |= 0444
} else {
- fi.Mode = fi.Mode | 0666
- }
- fi.Size = int64(sizehi)<<32 + int64(sizelo)
- fi.Name = name
- fi.FollowedSymlink = false
- fi.Atime_ns = atime.Nanoseconds()
- fi.Mtime_ns = wtime.Nanoseconds()
- fi.Ctime_ns = ctime.Nanoseconds()
- return fi
+ m |= 0666
+ }
+ return m
+}
+
+func mkSys(path string, atime, ctime syscall.Filetime) *winSys {
+ return &winSys{
+ path: path,
+ atime: atime,
+ ctime: ctime,
+ }
+}
+
+func mkSysFromFI(i *syscall.ByHandleFileInformation) *winSys {
+ return &winSys{
+ atime: i.LastAccessTime,
+ ctime: i.CreationTime,
+ vol: i.VolumeSerialNumber,
+ idxhi: i.FileIndexHigh,
+ idxlo: i.FileIndexLow,
+ }
+}
+
+func (s *winSys) loadFileId() error {
+ if s.path == "" {
+ // already done
+ return nil
+ }
+ s.Lock()
+ defer s.Unlock()
+ h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(s.path), 0, 0, nil, syscall.OPEN_EXISTING, syscall.FILE_FLAG_BACKUP_SEMANTICS, 0)
+ if e != nil {
+ return e
+ }
+ defer syscall.CloseHandle(h)
+ var i syscall.ByHandleFileInformation
+ e = syscall.GetFileInformationByHandle(syscall.Handle(h), &i)
+ if e != nil {
+ return e
+ }
+ s.path = ""
+ s.vol = i.VolumeSerialNumber
+ s.idxhi = i.FileIndexHigh
+ s.idxlo = i.FileIndexLow
+ return nil
+}
+
+func sameFile(sys1, sys2 interface{}) bool {
+ s1 := sys1.(*winSys)
+ s2 := sys2.(*winSys)
+ e := s1.loadFileId()
+ if e != nil {
+ panic(e)
+ }
+ e = s2.loadFileId()
+ if e != nil {
+ panic(e)
+ }
+ return s1.vol == s2.vol && s1.idxhi == s2.idxhi && s1.idxlo == s2.idxlo
+}
+
+// For testing.
+func atime(fi FileInfo) time.Time {
+ return time.Unix(0, fi.Sys().(*winSys).atime.Nanoseconds())
}