From: Alex Brainman Date: Mon, 27 Feb 2012 01:29:33 +0000 (+1100) Subject: os: implement sameFile on windows X-Git-Tag: weekly.2012-03-04~142 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=c7482b919619b459cb68e3a0c681afa1c3425dc4;p=gostls13.git os: implement sameFile on windows Fixes #2511. R=golang-dev, rsc CC=golang-dev https://golang.org/cl/5687072 --- diff --git a/src/pkg/os/file_windows.go b/src/pkg/os/file_windows.go index 82c7429945..88fa77bb84 100644 --- a/src/pkg/os/file_windows.go +++ b/src/pkg/os/file_windows.go @@ -52,6 +52,7 @@ func NewFile(fd uintptr, name string) *File { type dirInfo struct { data syscall.Win32finddata needdata bool + path string } const DevNull = "NUL" @@ -79,6 +80,11 @@ func openDir(name string) (file *File, err error) { if e != nil { return nil, &PathError{"open", name, e} } + d.path = name + if !isAbs(d.path) { + cwd, _ := Getwd() + d.path = cwd + `\` + d.path + } f := NewFile(uintptr(r), name) f.dirinfo = d return f, nil @@ -171,7 +177,13 @@ func (file *File) readdir(n int) (fi []FileInfo, err error) { if name == "." || name == ".." { // Useless names continue } - f := toFileInfo(name, d.FileAttributes, d.FileSizeHigh, d.FileSizeLow, d.CreationTime, d.LastAccessTime, d.LastWriteTime) + f := &fileStat{ + name: name, + size: mkSize(d.FileSizeHigh, d.FileSizeLow), + modTime: mkModTime(d.LastWriteTime), + mode: mkMode(d.FileAttributes), + sys: mkSys(file.dirinfo.path+`\`+name, d.LastAccessTime, d.CreationTime), + } n-- fi = append(fi, f) } diff --git a/src/pkg/os/os_test.go b/src/pkg/os/os_test.go index 21e2f374ca..02f75b2a73 100644 --- a/src/pkg/os/os_test.go +++ b/src/pkg/os/os_test.go @@ -1014,3 +1014,38 @@ func TestNilProcessStateString(t *testing.T) { t.Errorf("(*ProcessState)(nil).String() = %q, want %q", s, "") } } + +func TestSameFile(t *testing.T) { + fa, err := Create("a") + if err != nil { + t.Fatalf("Create(a): %v", err) + } + defer Remove(fa.Name()) + fa.Close() + fb, err := Create("b") + if err != nil { + t.Fatalf("Create(b): %v", err) + } + defer Remove(fb.Name()) + fb.Close() + + ia1, err := Stat("a") + if err != nil { + t.Fatalf("Stat(a): %v", err) + } + ia2, err := Stat("a") + if err != nil { + t.Fatalf("Stat(a): %v", err) + } + if !SameFile(ia1, ia2) { + t.Errorf("files should be same") + } + + ib, err := Stat("b") + if err != nil { + t.Fatalf("Stat(b): %v", err) + } + if SameFile(ia1, ib) { + t.Errorf("files should be different") + } +} diff --git a/src/pkg/os/stat_windows.go b/src/pkg/os/stat_windows.go index ffb679f417..19e215e935 100644 --- a/src/pkg/os/stat_windows.go +++ b/src/pkg/os/stat_windows.go @@ -5,6 +5,7 @@ package os import ( + "sync" "syscall" "time" "unsafe" @@ -25,7 +26,13 @@ func (file *File) Stat() (fi FileInfo, err error) { if e != nil { return nil, &PathError{"GetFileInformationByHandle", file.name, e} } - return toFileInfo(basename(file.name), d.FileAttributes, d.FileSizeHigh, d.FileSizeLow, d.CreationTime, d.LastAccessTime, d.LastWriteTime), nil + return &fileStat{ + name: basename(file.name), + size: mkSize(d.FileSizeHigh, d.FileSizeLow), + modTime: mkModTime(d.LastWriteTime), + mode: mkMode(d.FileAttributes), + sys: mkSysFromFI(&d), + }, nil } // Stat returns a FileInfo structure describing the named file. @@ -39,7 +46,18 @@ func Stat(name string) (fi FileInfo, err error) { if e != nil { return nil, &PathError{"GetFileAttributesEx", name, e} } - return toFileInfo(basename(name), d.FileAttributes, d.FileSizeHigh, d.FileSizeLow, d.CreationTime, d.LastAccessTime, d.LastWriteTime), nil + 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. @@ -75,37 +93,144 @@ func basename(name string) string { return name } -type winTimes struct { - atime, ctime syscall.Filetime +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 toFileInfo(name string, fa, sizehi, sizelo uint32, ctime, atime, mtime syscall.Filetime) FileInfo { - fs := &fileStat{ - name: name, - size: int64(sizehi)<<32 + int64(sizelo), - modTime: time.Unix(0, mtime.Nanoseconds()), - sys: &winTimes{atime, ctime}, +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 "" +} + +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 { - fs.mode |= ModeDir + m |= ModeDir } if fa&syscall.FILE_ATTRIBUTE_READONLY != 0 { - fs.mode |= 0444 + m |= 0444 } else { - fs.mode |= 0666 + m |= 0666 } - return fs + 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), syscall.GENERIC_READ, syscall.FILE_SHARE_READ, nil, syscall.OPEN_EXISTING, 0, 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 { - // TODO(rsc): Do better than this, but this matches what - // used to happen when code compared .Dev and .Ino, - // which were both always zero. Obviously not all files - // are the same. - return true + 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().(*winTimes).atime.Nanoseconds()) + return time.Unix(0, fi.Sys().(*winSys).atime.Nanoseconds()) }