diff options
Diffstat (limited to 'src/pkg/os/stat_windows.go')
| -rw-r--r-- | src/pkg/os/stat_windows.go | 258 |
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()) } |
