diff options
Diffstat (limited to 'src/pkg/os')
| -rw-r--r-- | src/pkg/os/dir_windows.go | 2 | ||||
| -rw-r--r-- | src/pkg/os/exec/lp_unix.go | 2 | ||||
| -rw-r--r-- | src/pkg/os/exec/lp_windows.go | 6 | ||||
| -rw-r--r-- | src/pkg/os/export_test.go | 10 | ||||
| -rw-r--r-- | src/pkg/os/file_unix.go | 53 | ||||
| -rw-r--r-- | src/pkg/os/file_windows.go | 6 | ||||
| -rw-r--r-- | src/pkg/os/getwd.go | 12 | ||||
| -rw-r--r-- | src/pkg/os/os_test.go | 136 | ||||
| -rw-r--r-- | src/pkg/os/os_unix_test.go | 75 | ||||
| -rw-r--r-- | src/pkg/os/path.go | 6 | ||||
| -rw-r--r-- | src/pkg/os/stat_darwin.go | 57 | ||||
| -rw-r--r-- | src/pkg/os/stat_freebsd.go | 57 | ||||
| -rw-r--r-- | src/pkg/os/stat_linux.go | 57 | ||||
| -rw-r--r-- | src/pkg/os/stat_openbsd.go | 57 | ||||
| -rw-r--r-- | src/pkg/os/stat_windows.go | 38 | ||||
| -rw-r--r-- | src/pkg/os/types.go | 124 | ||||
| -rw-r--r-- | src/pkg/os/user/user_test.go | 4 |
17 files changed, 421 insertions, 281 deletions
diff --git a/src/pkg/os/dir_windows.go b/src/pkg/os/dir_windows.go index ec06d24168..6f999c0b0d 100644 --- a/src/pkg/os/dir_windows.go +++ b/src/pkg/os/dir_windows.go @@ -8,7 +8,7 @@ func (file *File) Readdirnames(n int) (names []string, err error) { fis, err := file.Readdir(n) names = make([]string, len(fis)) for i, fi := range fis { - names[i] = fi.Name + names[i] = fi.Name() } return names, err } diff --git a/src/pkg/os/exec/lp_unix.go b/src/pkg/os/exec/lp_unix.go index d234641acc..9665ea8f41 100644 --- a/src/pkg/os/exec/lp_unix.go +++ b/src/pkg/os/exec/lp_unix.go @@ -20,7 +20,7 @@ func findExecutable(file string) error { if err != nil { return err } - if d.IsRegular() && d.Permission()&0111 != 0 { + if m := d.Mode(); !m.IsDir() && m&0111 != 0 { return nil } return os.EPERM diff --git a/src/pkg/os/exec/lp_windows.go b/src/pkg/os/exec/lp_windows.go index db326236ee..ef5bd92166 100644 --- a/src/pkg/os/exec/lp_windows.go +++ b/src/pkg/os/exec/lp_windows.go @@ -18,10 +18,10 @@ func chkStat(file string) error { if err != nil { return err } - if d.IsRegular() { - return nil + if d.IsDir() { + return os.EPERM } - return os.EPERM + return nil } func findExecutable(file string, exts []string) (string, error) { diff --git a/src/pkg/os/export_test.go b/src/pkg/os/export_test.go new file mode 100644 index 0000000000..29f051162a --- /dev/null +++ b/src/pkg/os/export_test.go @@ -0,0 +1,10 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package os + +// Export for testing. + +var TimespecToTime = timespecToTime +var Atime = atime diff --git a/src/pkg/os/file_unix.go b/src/pkg/os/file_unix.go index f3e0d1f9be..6e08eb6134 100644 --- a/src/pkg/os/file_unix.go +++ b/src/pkg/os/file_unix.go @@ -99,50 +99,43 @@ func (file *file) close() error { // Stat returns the FileInfo structure describing file. // It returns the FileInfo and an error, if any. -func (file *File) Stat() (fi *FileInfo, err error) { +func (file *File) Stat() (fi FileInfo, err error) { var stat syscall.Stat_t - e := syscall.Fstat(file.fd, &stat) - if e != nil { - return nil, &PathError{"stat", file.name, e} + err = syscall.Fstat(file.fd, &stat) + if err != nil { + return nil, &PathError{"stat", file.name, err} } - return fileInfoFromStat(file.name, new(FileInfo), &stat, &stat), nil + return fileInfoFromStat(&stat, file.name), nil } -// Stat returns a FileInfo structure describing the named file and an error, if any. +// Stat returns a FileInfo describing the named file and an error, if any. // If name names a valid symbolic link, the returned FileInfo describes // the file pointed at by the link and has fi.FollowedSymlink set to true. // If name names an invalid symbolic link, the returned FileInfo describes // the link itself and has fi.FollowedSymlink set to false. -func Stat(name string) (fi *FileInfo, err error) { - var lstat, stat syscall.Stat_t - e := syscall.Lstat(name, &lstat) - if e != nil { - return nil, &PathError{"stat", name, e} - } - statp := &lstat - if lstat.Mode&syscall.S_IFMT == syscall.S_IFLNK { - e := syscall.Stat(name, &stat) - if e == nil { - statp = &stat - } +func Stat(name string) (fi FileInfo, err error) { + var stat syscall.Stat_t + err = syscall.Stat(name, &stat) + if err != nil { + return nil, &PathError{"stat", name, err} } - return fileInfoFromStat(name, new(FileInfo), &lstat, statp), nil + return fileInfoFromStat(&stat, name), nil } -// Lstat returns the FileInfo structure describing the named file and an +// Lstat returns a FileInfo describing the named file and an // error, if any. If the file is a symbolic link, the returned FileInfo // describes the symbolic link. Lstat makes no attempt to follow the link. -func Lstat(name string) (fi *FileInfo, err error) { +func Lstat(name string) (fi FileInfo, err error) { var stat syscall.Stat_t - e := syscall.Lstat(name, &stat) - if e != nil { - return nil, &PathError{"lstat", name, e} + err = syscall.Lstat(name, &stat) + if err != nil { + return nil, &PathError{"lstat", name, err} } - return fileInfoFromStat(name, new(FileInfo), &stat, &stat), nil + return fileInfoFromStat(&stat, name), nil } // Readdir reads the contents of the directory associated with file and -// returns an array of up to n FileInfo structures, as would be returned +// returns an array of up to n FileInfo values, as would be returned // by Lstat, in directory order. Subsequent calls on the same file will yield // further FileInfos. // @@ -166,13 +159,13 @@ func (file *File) Readdir(n int) (fi []FileInfo, err error) { fi = make([]FileInfo, len(names)) for i, filename := range names { fip, err := Lstat(dirname + filename) - if fip == nil || err != nil { - fi[i].Name = filename // rest is already zeroed out + if err == nil { + fi[i] = fip } else { - fi[i] = *fip + fi[i] = &FileStat{name: filename} } } - return + return fi, err } // read reads up to len(b) bytes from the File. diff --git a/src/pkg/os/file_windows.go b/src/pkg/os/file_windows.go index 81fdbe3051..bdb5d1d29c 100644 --- a/src/pkg/os/file_windows.go +++ b/src/pkg/os/file_windows.go @@ -180,12 +180,12 @@ func (file *File) Readdir(n int) (fi []FileInfo, err error) { } } } - var f FileInfo - setFileInfo(&f, string(syscall.UTF16ToString(d.FileName[0:])), d.FileAttributes, d.FileSizeHigh, d.FileSizeLow, d.CreationTime, d.LastAccessTime, d.LastWriteTime) file.dirinfo.needdata = true - if f.Name == "." || f.Name == ".." { // Useless names + name := string(syscall.UTF16ToString(d.FileName[0:])) + if name == "." || name == ".." { // Useless names continue } + f := toFileInfo(name, d.FileAttributes, d.FileSizeHigh, d.FileSizeLow, d.CreationTime, d.LastAccessTime, d.LastWriteTime) n-- fi = append(fi, f) } diff --git a/src/pkg/os/getwd.go b/src/pkg/os/getwd.go index d5f5ae6bed..a0d3c99a50 100644 --- a/src/pkg/os/getwd.go +++ b/src/pkg/os/getwd.go @@ -12,7 +12,7 @@ import ( // current directory. If the current directory can be // reached via multiple paths (due to symbolic links), // Getwd may return any one of them. -func Getwd() (string, error) { +func Getwd() (pwd string, err error) { // If the operating system provides a Getwd call, use it. if syscall.ImplementsGetwd { s, e := syscall.Getwd() @@ -27,10 +27,10 @@ func Getwd() (string, error) { // Clumsy but widespread kludge: // if $PWD is set and matches ".", use it. - pwd := Getenv("PWD") + pwd = Getenv("PWD") if len(pwd) > 0 && pwd[0] == '/' { d, err := Stat(pwd) - if err == nil && d.Dev == dot.Dev && d.Ino == dot.Ino { + if err == nil && dot.(*FileStat).SameFile(d.(*FileStat)) { return pwd, nil } } @@ -42,7 +42,7 @@ func Getwd() (string, error) { // Can't stat root - no hope. return "", err } - if root.Dev == dot.Dev && root.Ino == dot.Ino { + if root.(*FileStat).SameFile(dot.(*FileStat)) { return "/", nil } @@ -67,7 +67,7 @@ func Getwd() (string, error) { } for _, name := range names { d, _ := Lstat(parent + "/" + name) - if d.Dev == dot.Dev && d.Ino == dot.Ino { + if d.(*FileStat).SameFile(dot.(*FileStat)) { pwd = "/" + name + pwd goto Found } @@ -82,7 +82,7 @@ func Getwd() (string, error) { return "", err } fd.Close() - if pd.Dev == root.Dev && pd.Ino == root.Ino { + if pd.(*FileStat).SameFile(root.(*FileStat)) { break } // Set up for next round. diff --git a/src/pkg/os/os_test.go b/src/pkg/os/os_test.go index 2439f03348..d107020449 100644 --- a/src/pkg/os/os_test.go +++ b/src/pkg/os/os_test.go @@ -122,12 +122,12 @@ func TestStat(t *testing.T) { if err != nil { t.Fatal("stat failed:", err) } - if !equal(sfname, dir.Name) { - t.Error("name should be ", sfname, "; is", dir.Name) + if !equal(sfname, dir.Name()) { + t.Error("name should be ", sfname, "; is", dir.Name()) } filesize := size(path, t) - if dir.Size != filesize { - t.Error("size should be", filesize, "; is", dir.Size) + if dir.Size() != filesize { + t.Error("size should be", filesize, "; is", dir.Size()) } } @@ -142,12 +142,12 @@ func TestFstat(t *testing.T) { if err2 != nil { t.Fatal("fstat failed:", err2) } - if !equal(sfname, dir.Name) { - t.Error("name should be ", sfname, "; is", dir.Name) + if !equal(sfname, dir.Name()) { + t.Error("name should be ", sfname, "; is", dir.Name()) } filesize := size(path, t) - if dir.Size != filesize { - t.Error("size should be", filesize, "; is", dir.Size) + if dir.Size() != filesize { + t.Error("size should be", filesize, "; is", dir.Size()) } } @@ -157,12 +157,12 @@ func TestLstat(t *testing.T) { if err != nil { t.Fatal("lstat failed:", err) } - if !equal(sfname, dir.Name) { - t.Error("name should be ", sfname, "; is", dir.Name) + if !equal(sfname, dir.Name()) { + t.Error("name should be ", sfname, "; is", dir.Name()) } filesize := size(path, t) - if dir.Size != filesize { - t.Error("size should be", filesize, "; is", dir.Size) + if dir.Size() != filesize { + t.Error("size should be", filesize, "; is", dir.Size()) } } @@ -229,7 +229,7 @@ func testReaddir(dir string, contents []string, t *testing.T) { for _, m := range contents { found := false for _, n := range s { - if equal(m, n.Name) { + if equal(m, n.Name()) { if found { t.Error("present twice:", m) } @@ -408,7 +408,7 @@ func TestHardLink(t *testing.T) { if err != nil { t.Fatalf("stat %q failed: %v", from, err) } - if tostat.Dev != fromstat.Dev || tostat.Ino != fromstat.Ino { + if !tostat.(*FileStat).SameFile(fromstat.(*FileStat)) { t.Errorf("link %q, %q did not create hard link", to, from) } } @@ -433,32 +433,32 @@ func TestSymLink(t *testing.T) { t.Fatalf("symlink %q, %q failed: %v", to, from, err) } defer Remove(from) - tostat, err := Stat(to) + tostat, err := Lstat(to) if err != nil { t.Fatalf("stat %q failed: %v", to, err) } - if tostat.FollowedSymlink { - t.Fatalf("stat %q claims to have followed a symlink", to) + if tostat.Mode()&ModeSymlink != 0 { + t.Fatalf("stat %q claims to have found a symlink", to) } fromstat, err := Stat(from) if err != nil { t.Fatalf("stat %q failed: %v", from, err) } - if tostat.Dev != fromstat.Dev || tostat.Ino != fromstat.Ino { + if !tostat.(*FileStat).SameFile(fromstat.(*FileStat)) { t.Errorf("symlink %q, %q did not create symlink", to, from) } fromstat, err = Lstat(from) if err != nil { t.Fatalf("lstat %q failed: %v", from, err) } - if !fromstat.IsSymlink() { + if fromstat.Mode()&ModeSymlink == 0 { t.Fatalf("symlink %q, %q did not create symlink", to, from) } fromstat, err = Stat(from) if err != nil { t.Fatalf("stat %q failed: %v", from, err) } - if !fromstat.FollowedSymlink { + if fromstat.Mode()&ModeSymlink != 0 { t.Fatalf("stat %q did not follow symlink", from) } s, err := Readlink(from) @@ -566,13 +566,13 @@ func TestStartProcess(t *testing.T) { exec(t, cmddir, cmdbase, args, filepath.Clean(cmddir)+le) } -func checkMode(t *testing.T, path string, mode uint32) { +func checkMode(t *testing.T, path string, mode FileMode) { dir, err := Stat(path) if err != nil { t.Fatalf("Stat %q (looking for mode %#o): %s", path, mode, err) } - if dir.Mode&0777 != mode { - t.Errorf("Stat %q: mode %#o want %#o", path, dir.Mode, mode) + if dir.Mode()&0777 != mode { + t.Errorf("Stat %q: mode %#o want %#o", path, dir.Mode(), mode) } } @@ -596,73 +596,13 @@ func TestChmod(t *testing.T) { checkMode(t, f.Name(), 0123) } -func checkUidGid(t *testing.T, path string, uid, gid int) { - dir, err := Stat(path) - if err != nil { - t.Fatalf("Stat %q (looking for uid/gid %d/%d): %s", path, uid, gid, err) - } - if dir.Uid != uid { - t.Errorf("Stat %q: uid %d want %d", path, dir.Uid, uid) - } - if dir.Gid != gid { - t.Errorf("Stat %q: gid %d want %d", path, dir.Gid, gid) - } -} - -func TestChown(t *testing.T) { - // Chown is not supported under windows or Plan 9. - // Plan9 provides a native ChownPlan9 version instead. - if syscall.OS == "windows" || syscall.OS == "plan9" { - return - } - // Use TempDir() to make sure we're on a local file system, - // so that the group ids returned by Getgroups will be allowed - // on the file. On NFS, the Getgroups groups are - // basically useless. - f := newFile("TestChown", t) - defer Remove(f.Name()) - defer f.Close() - dir, err := f.Stat() - if err != nil { - t.Fatalf("stat %s: %s", f.Name(), err) - } - - // Can't change uid unless root, but can try - // changing the group id. First try our current group. - gid := Getgid() - t.Log("gid:", gid) - if err = Chown(f.Name(), -1, gid); err != nil { - t.Fatalf("chown %s -1 %d: %s", f.Name(), gid, err) - } - checkUidGid(t, f.Name(), dir.Uid, gid) - - // Then try all the auxiliary groups. - groups, err := Getgroups() - if err != nil { - t.Fatalf("getgroups: %s", err) - } - t.Log("groups: ", groups) - for _, g := range groups { - if err = Chown(f.Name(), -1, g); err != nil { - t.Fatalf("chown %s -1 %d: %s", f.Name(), g, err) - } - checkUidGid(t, f.Name(), dir.Uid, g) - - // change back to gid to test fd.Chown - if err = f.Chown(-1, gid); err != nil { - t.Fatalf("fchown %s -1 %d: %s", f.Name(), gid, err) - } - checkUidGid(t, f.Name(), dir.Uid, gid) - } -} - func checkSize(t *testing.T, f *File, size int64) { dir, err := f.Stat() if err != nil { t.Fatalf("Stat %q (looking for size %d): %s", f.Name(), size, err) } - if dir.Size != size { - t.Errorf("Stat %q: size %d want %d", f.Name(), dir.Size, size) + if dir.Size() != size { + t.Errorf("Stat %q: size %d want %d", f.Name(), dir.Size(), size) } } @@ -714,36 +654,38 @@ func TestChtimes(t *testing.T) { f.Write([]byte("hello, world\n")) f.Close() - preStat, err := Stat(f.Name()) + st, err := Stat(f.Name()) if err != nil { t.Fatalf("Stat %s: %s", f.Name(), err) } + preStat := st.(*FileStat) // Move access and modification time back a second - err = Chtimes(f.Name(), preStat.AccessTime.Add(-time.Second), preStat.ModTime.Add(-time.Second)) + at := Atime(preStat) + mt := preStat.ModTime() + err = Chtimes(f.Name(), at.Add(-time.Second), mt.Add(-time.Second)) if err != nil { t.Fatalf("Chtimes %s: %s", f.Name(), err) } - postStat, err := Stat(f.Name()) + st, err = Stat(f.Name()) if err != nil { t.Fatalf("second Stat %s: %s", f.Name(), err) } + postStat := st.(*FileStat) /* Plan 9: Mtime is the time of the last change of content. Similarly, atime is set whenever the contents are accessed; also, it is set whenever mtime is set. */ - if !postStat.AccessTime.Before(preStat.AccessTime) && syscall.OS != "plan9" { - t.Errorf("AccessTime didn't go backwards; was=%d, after=%d", - preStat.AccessTime, - postStat.AccessTime) + pat := Atime(postStat) + pmt := postStat.ModTime() + if !pat.Before(at) && syscall.OS != "plan9" { + t.Errorf("AccessTime didn't go backwards; was=%d, after=%d", at, pat) } - if !postStat.ModTime.Before(preStat.ModTime) { - t.Errorf("ModTime didn't go backwards; was=%d, after=%d", - preStat.ModTime, - postStat.ModTime) + if !pmt.Before(mt) { + t.Errorf("ModTime didn't go backwards; was=%d, after=%d", mt, pmt) } } @@ -885,7 +827,7 @@ func TestOpenError(t *testing.T) { } perr, ok := err.(*PathError) if !ok { - t.Errorf("Open(%q, %d) returns error of %T type; want *os.PathError", tt.path, tt.mode, err) + t.Errorf("Open(%q, %d) returns error of %T type; want *PathError", tt.path, tt.mode, err) } if perr.Err != tt.error { if syscall.OS == "plan9" { diff --git a/src/pkg/os/os_unix_test.go b/src/pkg/os/os_unix_test.go new file mode 100644 index 0000000000..3109a8171a --- /dev/null +++ b/src/pkg/os/os_unix_test.go @@ -0,0 +1,75 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin freebsd linux openbsd + +package os_test + +import ( + . "os" + "syscall" + "testing" +) + +func checkUidGid(t *testing.T, path string, uid, gid int) { + dir, err := Stat(path) + if err != nil { + t.Fatalf("Stat %q (looking for uid/gid %d/%d): %s", path, uid, gid, err) + } + sys := dir.(*FileStat).Sys.(*syscall.Stat_t) + if int(sys.Uid) != uid { + t.Errorf("Stat %q: uid %d want %d", path, sys.Uid, uid) + } + if int(sys.Gid) != gid { + t.Errorf("Stat %q: gid %d want %d", path, sys.Gid, gid) + } +} + +func TestChown(t *testing.T) { + // Chown is not supported under windows or Plan 9. + // Plan9 provides a native ChownPlan9 version instead. + if syscall.OS == "windows" || syscall.OS == "plan9" { + return + } + // Use TempDir() to make sure we're on a local file system, + // so that the group ids returned by Getgroups will be allowed + // on the file. On NFS, the Getgroups groups are + // basically useless. + f := newFile("TestChown", t) + defer Remove(f.Name()) + defer f.Close() + dir, err := f.Stat() + if err != nil { + t.Fatalf("stat %s: %s", f.Name(), err) + } + + // Can't change uid unless root, but can try + // changing the group id. First try our current group. + gid := Getgid() + t.Log("gid:", gid) + if err = Chown(f.Name(), -1, gid); err != nil { + t.Fatalf("chown %s -1 %d: %s", f.Name(), gid, err) + } + sys := dir.(*FileStat).Sys.(*syscall.Stat_t) + checkUidGid(t, f.Name(), int(sys.Uid), gid) + + // Then try all the auxiliary groups. + groups, err := Getgroups() + if err != nil { + t.Fatalf("getgroups: %s", err) + } + t.Log("groups: ", groups) + for _, g := range groups { + if err = Chown(f.Name(), -1, g); err != nil { + t.Fatalf("chown %s -1 %d: %s", f.Name(), g, err) + } + checkUidGid(t, f.Name(), int(sys.Uid), g) + + // change back to gid to test fd.Chown + if err = f.Chown(-1, gid); err != nil { + t.Fatalf("fchown %s -1 %d: %s", f.Name(), gid, err) + } + checkUidGid(t, f.Name(), int(sys.Uid), gid) + } +} diff --git a/src/pkg/os/path.go b/src/pkg/os/path.go index 82fade63ae..bc14a78318 100644 --- a/src/pkg/os/path.go +++ b/src/pkg/os/path.go @@ -17,7 +17,7 @@ func MkdirAll(path string, perm uint32) error { // If path exists, stop with success or error. dir, err := Stat(path) if err == nil { - if dir.IsDirectory() { + if dir.IsDir() { return nil } return &PathError{"mkdir", path, ENOTDIR} @@ -48,7 +48,7 @@ func MkdirAll(path string, perm uint32) error { // Handle arguments like "foo/." by // double-checking that directory doesn't exist. dir, err1 := Lstat(path) - if err1 == nil && dir.IsDirectory() { + if err1 == nil && dir.IsDir() { return nil } return err @@ -75,7 +75,7 @@ func RemoveAll(path string) error { } return serr } - if !dir.IsDirectory() { + if !dir.IsDir() { // Not a directory; return the error from Remove. return err } diff --git a/src/pkg/os/stat_darwin.go b/src/pkg/os/stat_darwin.go index 00bf612746..81d6cd57c7 100644 --- a/src/pkg/os/stat_darwin.go +++ b/src/pkg/os/stat_darwin.go @@ -9,31 +9,48 @@ import ( "time" ) -func isSymlink(stat *syscall.Stat_t) bool { - return stat.Mode&syscall.S_IFMT == syscall.S_IFLNK +func sameFile(fs1, fs2 *FileStat) bool { + sys1 := fs1.Sys.(*syscall.Stat_t) + sys2 := fs2.Sys.(*syscall.Stat_t) + return sys1.Dev == sys2.Dev && sys1.Ino == sys2.Ino } -func fileInfoFromStat(name string, fi *FileInfo, lstat, stat *syscall.Stat_t) *FileInfo { - fi.Dev = uint64(stat.Dev) - fi.Ino = stat.Ino - fi.Nlink = uint64(stat.Nlink) - fi.Mode = uint32(stat.Mode) - fi.Uid = int(stat.Uid) - fi.Gid = int(stat.Gid) - fi.Rdev = uint64(stat.Rdev) - fi.Size = stat.Size - fi.Blksize = int64(stat.Blksize) - fi.Blocks = stat.Blocks - fi.AccessTime = timespecToTime(stat.Atimespec) - fi.ModTime = timespecToTime(stat.Mtimespec) - fi.ChangeTime = timespecToTime(stat.Ctimespec) - fi.Name = basename(name) - if isSymlink(lstat) && !isSymlink(stat) { - fi.FollowedSymlink = true +func fileInfoFromStat(st *syscall.Stat_t, name string) FileInfo { + fs := &FileStat{ + name: basename(name), + size: int64(st.Size), + modTime: timespecToTime(st.Mtimespec), + Sys: st, } - return fi + fs.mode = FileMode(st.Mode & 0777) + switch st.Mode & syscall.S_IFMT { + case syscall.S_IFBLK, syscall.S_IFCHR, syscall.S_IFWHT: + fs.mode |= ModeDevice + case syscall.S_IFDIR: + fs.mode |= ModeDir + case syscall.S_IFIFO: + fs.mode |= ModeNamedPipe + case syscall.S_IFLNK: + fs.mode |= ModeSymlink + case syscall.S_IFREG: + // nothing to do + case syscall.S_IFSOCK: + fs.mode |= ModeSocket + } + if st.Mode&syscall.S_ISGID != 0 { + fs.mode |= ModeSetgid + } + if st.Mode&syscall.S_ISUID != 0 { + fs.mode |= ModeSetuid + } + return fs } func timespecToTime(ts syscall.Timespec) time.Time { return time.Unix(int64(ts.Sec), int64(ts.Nsec)) } + +// For testing. +func atime(fi FileInfo) time.Time { + return timespecToTime(fi.(*FileStat).Sys.(*syscall.Stat_t).Atimespec) +} diff --git a/src/pkg/os/stat_freebsd.go b/src/pkg/os/stat_freebsd.go index a82a0b7bb1..c142edffab 100644 --- a/src/pkg/os/stat_freebsd.go +++ b/src/pkg/os/stat_freebsd.go @@ -9,31 +9,48 @@ import ( "time" ) -func isSymlink(stat *syscall.Stat_t) bool { - return stat.Mode&syscall.S_IFMT == syscall.S_IFLNK +func sameFile(fs1, fs2 *FileStat) bool { + sys1 := fs1.Sys.(*syscall.Stat_t) + sys2 := fs2.Sys.(*syscall.Stat_t) + return sys1.Dev == sys2.Dev && sys1.Ino == sys2.Ino } -func fileInfoFromStat(name string, fi *FileInfo, lstat, stat *syscall.Stat_t) *FileInfo { - fi.Dev = uint64(stat.Dev) - fi.Ino = uint64(stat.Ino) - fi.Nlink = uint64(stat.Nlink) - fi.Mode = uint32(stat.Mode) - fi.Uid = int(stat.Uid) - fi.Gid = int(stat.Gid) - fi.Rdev = uint64(stat.Rdev) - fi.Size = int64(stat.Size) - fi.Blksize = int64(stat.Blksize) - fi.Blocks = stat.Blocks - fi.AccessTime = timespecToTime(stat.Atimespec) - fi.ModTime = timespecToTime(stat.Mtimespec) - fi.ChangeTime = timespecToTime(stat.Ctimespec) - fi.Name = basename(name) - if isSymlink(lstat) && !isSymlink(stat) { - fi.FollowedSymlink = true +func fileInfoFromStat(st *syscall.Stat_t, name string) FileInfo { + fs := &FileStat{ + name: basename(name), + size: int64(st.Size), + modTime: timespecToTime(st.Mtimespec), + Sys: st, } - return fi + fs.mode = FileMode(st.Mode & 0777) + switch st.Mode & syscall.S_IFMT { + case syscall.S_IFBLK, syscall.S_IFCHR: + fs.mode |= ModeDevice + case syscall.S_IFDIR: + fs.mode |= ModeDir + case syscall.S_IFIFO: + fs.mode |= ModeNamedPipe + case syscall.S_IFLNK: + fs.mode |= ModeSymlink + case syscall.S_IFREG: + // nothing to do + case syscall.S_IFSOCK: + fs.mode |= ModeSocket + } + if st.Mode&syscall.S_ISGID != 0 { + fs.mode |= ModeSetgid + } + if st.Mode&syscall.S_ISUID != 0 { + fs.mode |= ModeSetuid + } + return fs } func timespecToTime(ts syscall.Timespec) time.Time { return time.Unix(int64(ts.Sec), int64(ts.Nsec)) } + +// For testing. +func atime(fi FileInfo) time.Time { + return timespecToTime(fi.(*FileStat).Sys.(*syscall.Stat_t).Atimespec) +} diff --git a/src/pkg/os/stat_linux.go b/src/pkg/os/stat_linux.go index 5f9c115e29..66189a6b9b 100644 --- a/src/pkg/os/stat_linux.go +++ b/src/pkg/os/stat_linux.go @@ -9,31 +9,48 @@ import ( "time" ) -func isSymlink(stat *syscall.Stat_t) bool { - return stat.Mode&syscall.S_IFMT == syscall.S_IFLNK +func sameFile(fs1, fs2 *FileStat) bool { + sys1 := fs1.Sys.(*syscall.Stat_t) + sys2 := fs2.Sys.(*syscall.Stat_t) + return sys1.Dev == sys2.Dev && sys1.Ino == sys2.Ino } -func fileInfoFromStat(name string, fi *FileInfo, lstat, stat *syscall.Stat_t) *FileInfo { - fi.Dev = stat.Dev - fi.Ino = stat.Ino - fi.Nlink = uint64(stat.Nlink) - fi.Mode = stat.Mode - fi.Uid = int(stat.Uid) - fi.Gid = int(stat.Gid) - fi.Rdev = stat.Rdev - fi.Size = stat.Size - fi.Blksize = int64(stat.Blksize) - fi.Blocks = stat.Blocks - fi.AccessTime = timespecToTime(stat.Atim) - fi.ModTime = timespecToTime(stat.Mtim) - fi.ChangeTime = timespecToTime(stat.Ctim) - fi.Name = basename(name) - if isSymlink(lstat) && !isSymlink(stat) { - fi.FollowedSymlink = true +func fileInfoFromStat(st *syscall.Stat_t, name string) FileInfo { + fs := &FileStat{ + name: basename(name), + size: int64(st.Size), + modTime: timespecToTime(st.Mtim), + Sys: st, } - return fi + fs.mode = FileMode(st.Mode & 0777) + switch st.Mode & syscall.S_IFMT { + case syscall.S_IFBLK, syscall.S_IFCHR: + fs.mode |= ModeDevice + case syscall.S_IFDIR: + fs.mode |= ModeDir + case syscall.S_IFIFO: + fs.mode |= ModeNamedPipe + case syscall.S_IFLNK: + fs.mode |= ModeSymlink + case syscall.S_IFREG: + // nothing to do + case syscall.S_IFSOCK: + fs.mode |= ModeSocket + } + if st.Mode&syscall.S_ISGID != 0 { + fs.mode |= ModeSetgid + } + if st.Mode&syscall.S_ISUID != 0 { + fs.mode |= ModeSetuid + } + return fs } func timespecToTime(ts syscall.Timespec) time.Time { return time.Unix(int64(ts.Sec), int64(ts.Nsec)) } + +// For testing. +func atime(fi FileInfo) time.Time { + return timespecToTime(fi.(*FileStat).Sys.(*syscall.Stat_t).Atim) +} diff --git a/src/pkg/os/stat_openbsd.go b/src/pkg/os/stat_openbsd.go index 943d34c402..66189a6b9b 100644 --- a/src/pkg/os/stat_openbsd.go +++ b/src/pkg/os/stat_openbsd.go @@ -9,31 +9,48 @@ import ( "time" ) -func isSymlink(stat *syscall.Stat_t) bool { - return stat.Mode&syscall.S_IFMT == syscall.S_IFLNK +func sameFile(fs1, fs2 *FileStat) bool { + sys1 := fs1.Sys.(*syscall.Stat_t) + sys2 := fs2.Sys.(*syscall.Stat_t) + return sys1.Dev == sys2.Dev && sys1.Ino == sys2.Ino } -func fileInfoFromStat(name string, fi *FileInfo, lstat, stat *syscall.Stat_t) *FileInfo { - fi.Dev = uint64(stat.Dev) - fi.Ino = uint64(stat.Ino) - fi.Nlink = uint64(stat.Nlink) - fi.Mode = uint32(stat.Mode) - fi.Uid = int(stat.Uid) - fi.Gid = int(stat.Gid) - fi.Rdev = uint64(stat.Rdev) - fi.Size = int64(stat.Size) - fi.Blksize = int64(stat.Blksize) - fi.Blocks = stat.Blocks - fi.AccessTime = timespecToTime(stat.Atim) - fi.ModTime = timespecToTime(stat.Mtim) - fi.ChangeTime = timespecToTime(stat.Ctim) - fi.Name = basename(name) - if isSymlink(lstat) && !isSymlink(stat) { - fi.FollowedSymlink = true +func fileInfoFromStat(st *syscall.Stat_t, name string) FileInfo { + fs := &FileStat{ + name: basename(name), + size: int64(st.Size), + modTime: timespecToTime(st.Mtim), + Sys: st, } - return fi + fs.mode = FileMode(st.Mode & 0777) + switch st.Mode & syscall.S_IFMT { + case syscall.S_IFBLK, syscall.S_IFCHR: + fs.mode |= ModeDevice + case syscall.S_IFDIR: + fs.mode |= ModeDir + case syscall.S_IFIFO: + fs.mode |= ModeNamedPipe + case syscall.S_IFLNK: + fs.mode |= ModeSymlink + case syscall.S_IFREG: + // nothing to do + case syscall.S_IFSOCK: + fs.mode |= ModeSocket + } + if st.Mode&syscall.S_ISGID != 0 { + fs.mode |= ModeSetgid + } + if st.Mode&syscall.S_ISUID != 0 { + fs.mode |= ModeSetuid + } + return fs } func timespecToTime(ts syscall.Timespec) time.Time { return time.Unix(int64(ts.Sec), int64(ts.Nsec)) } + +// For testing. +func atime(fi FileInfo) time.Time { + return timespecToTime(fi.(*FileStat).Sys.(*syscall.Stat_t).Atim) +} diff --git a/src/pkg/os/stat_windows.go b/src/pkg/os/stat_windows.go index b226b2913b..d024915ee0 100644 --- a/src/pkg/os/stat_windows.go +++ b/src/pkg/os/stat_windows.go @@ -12,7 +12,7 @@ import ( // Stat returns the FileInfo structure describing file. // It returns the FileInfo and an error, if any. -func (file *File) Stat() (fi *FileInfo, err error) { +func (file *File) Stat() (fi FileInfo, err error) { if file == nil || file.fd < 0 { return nil, EINVAL } @@ -25,7 +25,7 @@ func (file *File) Stat() (fi *FileInfo, err error) { if e != nil { return nil, &PathError{"GetFileInformationByHandle", file.name, e} } - return setFileInfo(new(FileInfo), basename(file.name), d.FileAttributes, d.FileSizeHigh, d.FileSizeLow, d.CreationTime, d.LastAccessTime, d.LastWriteTime), nil + return toFileInfo(basename(file.name), d.FileAttributes, d.FileSizeHigh, d.FileSizeLow, d.CreationTime, d.LastAccessTime, d.LastWriteTime), nil } // Stat returns a FileInfo structure describing the named file and an error, if any. @@ -33,7 +33,7 @@ func (file *File) Stat() (fi *FileInfo, err error) { // the file pointed at by the link and has fi.FollowedSymlink set to true. // If name names an invalid symbolic link, the returned FileInfo describes // the link itself and has fi.FollowedSymlink set to false. -func Stat(name string) (fi *FileInfo, err error) { +func Stat(name string) (fi FileInfo, err error) { if len(name) == 0 { return nil, &PathError{"Stat", name, syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)} } @@ -42,13 +42,13 @@ func Stat(name string) (fi *FileInfo, err error) { if e != nil { return nil, &PathError{"GetFileAttributesEx", name, e} } - return setFileInfo(new(FileInfo), basename(name), d.FileAttributes, d.FileSizeHigh, d.FileSizeLow, d.CreationTime, d.LastAccessTime, d.LastWriteTime), nil + return toFileInfo(basename(name), d.FileAttributes, d.FileSizeHigh, d.FileSizeLow, d.CreationTime, d.LastAccessTime, d.LastWriteTime), nil } // Lstat returns the FileInfo structure describing the named file and an // error, if any. If the file is a symbolic link, the returned FileInfo // describes the symbolic link. Lstat makes no attempt to follow the link. -func Lstat(name string) (fi *FileInfo, err error) { +func Lstat(name string) (fi FileInfo, err error) { // No links on Windows return Stat(name) } @@ -77,23 +77,23 @@ func basename(name string) string { return name } -func setFileInfo(fi *FileInfo, name string, fa, sizehi, sizelo uint32, ctime, atime, wtime syscall.Filetime) *FileInfo { - fi.Mode = 0 +func toFileInfo(name string, fa, sizehi, sizelo uint32, ctime, atime, wtime syscall.Filetime) FileInfo { + fs := new(FileStat) + fs.mode = 0 if fa&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { - fi.Mode = fi.Mode | syscall.S_IFDIR - } else { - fi.Mode = fi.Mode | syscall.S_IFREG + fs.mode |= ModeDir } if fa&syscall.FILE_ATTRIBUTE_READONLY != 0 { - fi.Mode = fi.Mode | 0444 + fs.mode |= 0444 } else { - fi.Mode = fi.Mode | 0666 + fs.mode |= 0666 } - fi.Size = int64(sizehi)<<32 + int64(sizelo) - fi.Name = name - fi.FollowedSymlink = false - fi.AccessTime = time.Unix(0, atime.Nanoseconds()) - fi.ModTime = time.Unix(0, wtime.Nanoseconds()) - fi.ChangeTime = time.Unix(0, ctime.Nanoseconds()) - return fi + fs.size = int64(sizehi)<<32 + int64(sizelo) + fs.name = name + fs.modTime = time.Unix(0, wtime.Nanoseconds()) + return fs +} + +func sameFile(fs1, fs2 *FileStat) bool { + return false } diff --git a/src/pkg/os/types.go b/src/pkg/os/types.go index 3f8ac78350..dff7090cdb 100644 --- a/src/pkg/os/types.go +++ b/src/pkg/os/types.go @@ -9,51 +9,103 @@ import ( "time" ) -// An operating-system independent representation of Unix data structures. -// OS-specific routines in this directory convert the OS-local versions to these. - // Getpagesize returns the underlying system's memory page size. func Getpagesize() int { return syscall.Getpagesize() } -// A FileInfo describes a file and is returned by Stat, Fstat, and Lstat -type FileInfo struct { - Dev uint64 // device number of file system holding file. - Ino uint64 // inode number. - Nlink uint64 // number of hard links. - Mode uint32 // permission and mode bits. - Uid int // user id of owner. - Gid int // group id of owner. - Rdev uint64 // device type for special file. - Size int64 // length in bytes. - Blksize int64 // size of blocks, in bytes. - Blocks int64 // number of blocks allocated for file. - AccessTime time.Time // access time - ModTime time.Time // modification time - ChangeTime time.Time // status change time - Name string // base name of the file name provided in Open, Stat, etc. - FollowedSymlink bool // followed a symlink to get this information +// A FileInfo describes a file and is returned by Stat and Lstat +type FileInfo interface { + Name() string // base name of the file + Size() int64 // length in bytes + Mode() FileMode // file mode bits + ModTime() time.Time // modification time + IsDir() bool // abbreviation for Mode().IsDir() } -// IsFifo reports whether the FileInfo describes a FIFO file. -func (f *FileInfo) IsFifo() bool { return (f.Mode & syscall.S_IFMT) == syscall.S_IFIFO } +// A FileMode represents a file's mode and permission bits. +// The bits have the same definition on all systems, so that +// information about files can be moved from one system +// to another portably. Not all bits apply to all systems. +// The only required bit is ModeDir for directories. +type FileMode uint32 -// IsChar reports whether the FileInfo describes a character special file. -func (f *FileInfo) IsChar() bool { return (f.Mode & syscall.S_IFMT) == syscall.S_IFCHR } +// The defined file mode bits are the most significant bits of the FileMode. +// The nine least-significant bits are the standard Unix rwxrwxrwx permissions. +const ( + // The single letters are the abbreviations + // used by the String method's formatting. + ModeDir FileMode = 1 << (32 - 1 - iota) // d: is a directory + ModeAppend // a: append-only + ModeExclusive // l: exclusive use + ModeTemporary // t: temporary file (not backed up) + ModeSymlink // L: symbolic link + ModeDevice // D: device file + ModeNamedPipe // p: named pipe (FIFO) + ModeSocket // S: Unix domain socket + ModeSetuid // u: setuid + ModeSetgid // g: setgid -// IsDirectory reports whether the FileInfo describes a directory. -func (f *FileInfo) IsDirectory() bool { return (f.Mode & syscall.S_IFMT) == syscall.S_IFDIR } + ModePerm FileMode = 0777 // permission bits +) -// IsBlock reports whether the FileInfo describes a block special file. -func (f *FileInfo) IsBlock() bool { return (f.Mode & syscall.S_IFMT) == syscall.S_IFBLK } +func (m FileMode) String() string { + const str = "daltLDpSug" + var buf [20]byte + w := 0 + for i, c := range str { + if m&(1<<uint(32-1-i)) != 0 { + buf[w] = byte(c) + w++ + } + } + if w == 0 { + buf[w] = '-' + w++ + } + const rwx = "rwxrwxrwx" + for i, c := range rwx { + if m&(1<<uint(9-1-i)) != 0 { + buf[w] = byte(c) + } else { + buf[w] = '-' + } + w++ + } + return string(buf[:w]) +} -// IsRegular reports whether the FileInfo describes a regular file. -func (f *FileInfo) IsRegular() bool { return (f.Mode & syscall.S_IFMT) == syscall.S_IFREG } +// IsDir reports whether m describes a directory. +// That is, it tests for the ModeDir bit being set in m. +func (m FileMode) IsDir() bool { + return m&ModeDir != 0 +} -// IsSymlink reports whether the FileInfo describes a symbolic link. -func (f *FileInfo) IsSymlink() bool { return (f.Mode & syscall.S_IFMT) == syscall.S_IFLNK } +// Perm returns the Unix permission bits in m. +func (m FileMode) Perm() FileMode { + return m & ModePerm +} -// IsSocket reports whether the FileInfo describes a socket. -func (f *FileInfo) IsSocket() bool { return (f.Mode & syscall.S_IFMT) == syscall.S_IFSOCK } +// A FileStat is the implementation of FileInfo returned by Stat and Lstat. +// Clients that need access to the underlying system-specific stat information +// can test for *os.FileStat and then consult the Sys field. +type FileStat struct { + name string + size int64 + mode FileMode + modTime time.Time -// Permission returns the file permission bits. -func (f *FileInfo) Permission() uint32 { return f.Mode & 0777 } + Sys interface{} +} + +func (fs *FileStat) Name() string { return fs.name } +func (fs *FileStat) Size() int64 { return fs.size } +func (fs *FileStat) Mode() FileMode { return fs.mode } +func (fs *FileStat) ModTime() time.Time { return fs.modTime } +func (fs *FileStat) IsDir() bool { return fs.mode.IsDir() } + +// SameFile reports whether fs and other describe the same file. +// For example, on Unix this means that the device and inode fields +// of the two underlying structures are identical; on other systems +// the decision may be based on the path names. +func (fs *FileStat) SameFile(other *FileStat) bool { + return sameFile(fs, other) +} diff --git a/src/pkg/os/user/user_test.go b/src/pkg/os/user/user_test.go index 59f15e4c67..f9f44af8a9 100644 --- a/src/pkg/os/user/user_test.go +++ b/src/pkg/os/user/user_test.go @@ -41,8 +41,8 @@ func TestLookup(t *testing.T) { t.Errorf("expected Uid of %d; got %d", e, g) } fi, err := os.Stat(u.HomeDir) - if err != nil || !fi.IsDirectory() { - t.Errorf("expected a valid HomeDir; stat(%q): err=%v, IsDirectory=%v", u.HomeDir, err, fi.IsDirectory()) + if err != nil || !fi.IsDir() { + t.Errorf("expected a valid HomeDir; stat(%q): err=%v, IsDir=%v", u.HomeDir, err, fi.IsDir()) } if u.Username == "" { t.Fatalf("didn't get a username") |
