diff options
Diffstat (limited to 'src/os')
52 files changed, 982 insertions, 357 deletions
diff --git a/src/os/dir.go b/src/os/dir.go index 1d7ced8061..b56d998459 100644 --- a/src/os/dir.go +++ b/src/os/dir.go @@ -4,6 +4,16 @@ package os +import "io/fs" + +type readdirMode int + +const ( + readdirName readdirMode = iota + readdirDirEntry + readdirFileInfo +) + // Readdir reads the contents of the directory associated with file and // returns a slice of up to n FileInfo values, as would be returned // by Lstat, in directory order. Subsequent calls on the same file will yield @@ -19,11 +29,14 @@ package os // nil error. If it encounters an error before the end of the // directory, Readdir returns the FileInfo read until that point // and a non-nil error. +// +// Most clients are better served by the more efficient ReadDir method. func (f *File) Readdir(n int) ([]FileInfo, error) { if f == nil { return nil, ErrInvalid } - return f.readdir(n) + _, _, infos, err := f.readdir(n, readdirFileInfo) + return infos, err } // Readdirnames reads the contents of the directory associated with file @@ -45,5 +58,32 @@ func (f *File) Readdirnames(n int) (names []string, err error) { if f == nil { return nil, ErrInvalid } - return f.readdirnames(n) + names, _, _, err = f.readdir(n, readdirName) + return names, err } + +// A DirEntry is an entry read from a directory +// (using the ReadDir function or a File's ReadDir method). +type DirEntry = fs.DirEntry + +// ReadDir reads the contents of the directory associated with the file f +// and returns a slice of DirEntry values in directory order. +// Subsequent calls on the same file will yield later DirEntry records in the directory. +// +// If n > 0, ReadDir returns at most n DirEntry records. +// In this case, if ReadDir returns an empty slice, it will return an error explaining why. +// At the end of a directory, the error is io.EOF. +// +// If n <= 0, ReadDir returns all the DirEntry records remaining in the directory. +// When it succeeds, it returns a nil error (not io.EOF). +func (f *File) ReadDir(n int) ([]DirEntry, error) { + if f == nil { + return nil, ErrInvalid + } + _, dirents, _, err := f.readdir(n, readdirDirEntry) + return dirents, err +} + +// testingForceReadDirLstat forces ReadDir to call Lstat, for testing that code path. +// This can be difficult to provoke on some Unix systems otherwise. +var testingForceReadDirLstat bool diff --git a/src/os/dir_darwin.go b/src/os/dir_darwin.go index 476af6862e..deba3eb37f 100644 --- a/src/os/dir_darwin.go +++ b/src/os/dir_darwin.go @@ -24,11 +24,11 @@ func (d *dirInfo) close() { d.dir = 0 } -func (f *File) readdirnames(n int) (names []string, err error) { +func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) { if f.dirinfo == nil { dir, call, errno := f.pfd.OpenDir() if errno != nil { - return nil, &PathError{call, f.name, errno} + return nil, nil, nil, &PathError{Op: call, Path: f.name, Err: errno} } f.dirinfo = &dirInfo{ dir: dir, @@ -42,15 +42,14 @@ func (f *File) readdirnames(n int) (names []string, err error) { n = -1 } - names = make([]string, 0, size) var dirent syscall.Dirent var entptr *syscall.Dirent - for len(names) < size || n == -1 { + for len(names)+len(dirents)+len(infos) < size || n == -1 { if errno := readdir_r(d.dir, &dirent, &entptr); errno != 0 { if errno == syscall.EINTR { continue } - return names, &PathError{"readdir", f.name, errno} + return names, dirents, infos, &PathError{Op: "readdir", Path: f.name, Err: errno} } if entptr == nil { // EOF break @@ -69,13 +68,58 @@ func (f *File) readdirnames(n int) (names []string, err error) { if string(name) == "." || string(name) == ".." { continue } - names = append(names, string(name)) + if mode == readdirName { + names = append(names, string(name)) + } else if mode == readdirDirEntry { + de, err := newUnixDirent(f.name, string(name), dtToType(dirent.Type)) + if IsNotExist(err) { + // File disappeared between readdir and stat. + // Treat as if it didn't exist. + continue + } + if err != nil { + return nil, dirents, nil, err + } + dirents = append(dirents, de) + } else { + info, err := lstat(f.name + "/" + string(name)) + if IsNotExist(err) { + // File disappeared between readdir + stat. + // Treat as if it didn't exist. + continue + } + if err != nil { + return nil, nil, infos, err + } + infos = append(infos, info) + } runtime.KeepAlive(f) } - if n >= 0 && len(names) == 0 { - return names, io.EOF + + if n > 0 && len(names)+len(dirents)+len(infos) == 0 { + return nil, nil, nil, io.EOF + } + return names, dirents, infos, nil +} + +func dtToType(typ uint8) FileMode { + switch typ { + case syscall.DT_BLK: + return ModeDevice + case syscall.DT_CHR: + return ModeDevice | ModeCharDevice + case syscall.DT_DIR: + return ModeDir + case syscall.DT_FIFO: + return ModeNamedPipe + case syscall.DT_LNK: + return ModeSymlink + case syscall.DT_REG: + return 0 + case syscall.DT_SOCK: + return ModeSocket } - return names, nil + return ^FileMode(0) } // Implemented in syscall/syscall_darwin.go. diff --git a/src/os/dir_plan9.go b/src/os/dir_plan9.go index 8195c02a46..8f6b0d6109 100644 --- a/src/os/dir_plan9.go +++ b/src/os/dir_plan9.go @@ -9,7 +9,7 @@ import ( "syscall" ) -func (file *File) readdir(n int) ([]FileInfo, error) { +func (file *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) { // If this file has no dirinfo, create one. if file.dirinfo == nil { file.dirinfo = new(dirInfo) @@ -20,7 +20,6 @@ func (file *File) readdir(n int) ([]FileInfo, error) { size = 100 n = -1 } - fi := make([]FileInfo, 0, size) // Empty with room to grow. for n != 0 { // Refill the buffer if necessary. if d.bufp >= d.nbuf { @@ -33,10 +32,10 @@ func (file *File) readdir(n int) ([]FileInfo, error) { if err == io.EOF { break } - return fi, &PathError{"readdir", file.name, err} + return names, dirents, infos, &PathError{Op: "readdir", Path: file.name, Err: err} } if nb < syscall.STATFIXLEN { - return fi, &PathError{"readdir", file.name, syscall.ErrShortStat} + return names, dirents, infos, &PathError{Op: "readdir", Path: file.name, Err: syscall.ErrShortStat} } } @@ -44,30 +43,39 @@ func (file *File) readdir(n int) ([]FileInfo, error) { b := d.buf[d.bufp:] m := int(uint16(b[0])|uint16(b[1])<<8) + 2 if m < syscall.STATFIXLEN { - return fi, &PathError{"readdir", file.name, syscall.ErrShortStat} + return names, dirents, infos, &PathError{Op: "readdir", Path: file.name, Err: syscall.ErrShortStat} } dir, err := syscall.UnmarshalDir(b[:m]) if err != nil { - return fi, &PathError{"readdir", file.name, err} + return names, dirents, infos, &PathError{Op: "readdir", Path: file.name, Err: err} } - fi = append(fi, fileInfoFromStat(dir)) + if mode == readdirName { + names = append(names, dir.Name) + } else { + f := fileInfoFromStat(dir) + if mode == readdirDirEntry { + dirents = append(dirents, dirEntry{f}) + } else { + infos = append(infos, f) + } + } d.bufp += m n-- } - if n >= 0 && len(fi) == 0 { - return fi, io.EOF + if n > 0 && len(names)+len(dirents)+len(infos) == 0 { + return nil, nil, nil, io.EOF } - return fi, nil + return names, dirents, infos, nil } -func (file *File) readdirnames(n int) (names []string, err error) { - fi, err := file.Readdir(n) - names = make([]string, len(fi)) - for i := range fi { - names[i] = fi[i].Name() - } - return +type dirEntry struct { + fs *fileStat } + +func (de dirEntry) Name() string { return de.fs.Name() } +func (de dirEntry) IsDir() bool { return de.fs.IsDir() } +func (de dirEntry) Type() FileMode { return de.fs.Mode().Type() } +func (de dirEntry) Info() (FileInfo, error) { return de.fs, nil } diff --git a/src/os/dir_unix.go b/src/os/dir_unix.go index 58ec406ab8..3e5a698350 100644 --- a/src/os/dir_unix.go +++ b/src/os/dir_unix.go @@ -10,6 +10,7 @@ import ( "io" "runtime" "syscall" + "unsafe" ) // Auxiliary information if the File describes a directory @@ -26,7 +27,7 @@ const ( func (d *dirInfo) close() {} -func (f *File) readdirnames(n int) (names []string, err error) { +func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) { // If this file has no dirinfo, create one. if f.dirinfo == nil { f.dirinfo = new(dirInfo) @@ -41,7 +42,6 @@ func (f *File) readdirnames(n int) (names []string, err error) { n = -1 } - names = make([]string, 0, size) // Empty with room to grow. for n != 0 { // Refill the buffer if necessary if d.bufp >= d.nbuf { @@ -50,7 +50,7 @@ func (f *File) readdirnames(n int) (names []string, err error) { d.nbuf, errno = f.pfd.ReadDirent(d.buf) runtime.KeepAlive(f) if errno != nil { - return names, &PathError{"readdirent", f.name, errno} + return names, dirents, infos, &PathError{Op: "readdirent", Path: f.name, Err: errno} } if d.nbuf <= 0 { break // EOF @@ -58,13 +58,115 @@ func (f *File) readdirnames(n int) (names []string, err error) { } // Drain the buffer - var nb, nc int - nb, nc, names = syscall.ParseDirent(d.buf[d.bufp:d.nbuf], n, names) - d.bufp += nb - n -= nc + buf := d.buf[d.bufp:d.nbuf] + reclen, ok := direntReclen(buf) + if !ok || reclen > uint64(len(buf)) { + break + } + rec := buf[:reclen] + d.bufp += int(reclen) + ino, ok := direntIno(rec) + if !ok { + break + } + if ino == 0 { + continue + } + const namoff = uint64(unsafe.Offsetof(syscall.Dirent{}.Name)) + namlen, ok := direntNamlen(rec) + if !ok || namoff+namlen > uint64(len(rec)) { + break + } + name := rec[namoff : namoff+namlen] + for i, c := range name { + if c == 0 { + name = name[:i] + break + } + } + // Check for useless names before allocating a string. + if string(name) == "." || string(name) == ".." { + continue + } + n-- + if mode == readdirName { + names = append(names, string(name)) + } else if mode == readdirDirEntry { + de, err := newUnixDirent(f.name, string(name), direntType(rec)) + if IsNotExist(err) { + // File disappeared between readdir and stat. + // Treat as if it didn't exist. + continue + } + if err != nil { + return nil, dirents, nil, err + } + dirents = append(dirents, de) + } else { + info, err := lstat(f.name + "/" + string(name)) + if IsNotExist(err) { + // File disappeared between readdir + stat. + // Treat as if it didn't exist. + continue + } + if err != nil { + return nil, nil, infos, err + } + infos = append(infos, info) + } } - if n >= 0 && len(names) == 0 { - return names, io.EOF + + if n > 0 && len(names)+len(dirents)+len(infos) == 0 { + return nil, nil, nil, io.EOF + } + return names, dirents, infos, nil +} + +// readInt returns the size-bytes unsigned integer in native byte order at offset off. +func readInt(b []byte, off, size uintptr) (u uint64, ok bool) { + if len(b) < int(off+size) { + return 0, false + } + if isBigEndian { + return readIntBE(b[off:], size), true + } + return readIntLE(b[off:], size), true +} + +func readIntBE(b []byte, size uintptr) uint64 { + switch size { + case 1: + return uint64(b[0]) + case 2: + _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808 + return uint64(b[1]) | uint64(b[0])<<8 + case 4: + _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808 + return uint64(b[3]) | uint64(b[2])<<8 | uint64(b[1])<<16 | uint64(b[0])<<24 + case 8: + _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 + return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | + uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56 + default: + panic("syscall: readInt with unsupported size") + } +} + +func readIntLE(b []byte, size uintptr) uint64 { + switch size { + case 1: + return uint64(b[0]) + case 2: + _ = b[1] // bounds check hint to compiler; see golang.org/issue/14808 + return uint64(b[0]) | uint64(b[1])<<8 + case 4: + _ = b[3] // bounds check hint to compiler; see golang.org/issue/14808 + return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 + case 8: + _ = b[7] // bounds check hint to compiler; see golang.org/issue/14808 + return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | + uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 + default: + panic("syscall: readInt with unsupported size") } - return names, nil } diff --git a/src/os/dir_windows.go b/src/os/dir_windows.go index 9e5d6bd505..253adad0b9 100644 --- a/src/os/dir_windows.go +++ b/src/os/dir_windows.go @@ -10,20 +10,14 @@ import ( "syscall" ) -func (file *File) readdir(n int) (fi []FileInfo, err error) { - if file == nil { - return nil, syscall.EINVAL - } +func (file *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) { if !file.isdir() { - return nil, &PathError{"Readdir", file.name, syscall.ENOTDIR} + return nil, nil, nil, &PathError{Op: "readdir", Path: file.name, Err: syscall.ENOTDIR} } wantAll := n <= 0 - size := n if wantAll { n = -1 - size = 100 } - fi = make([]FileInfo, 0, size) // Empty with room to grow. d := &file.dirinfo.data for n != 0 && !file.dirinfo.isempty { if file.dirinfo.needdata { @@ -33,10 +27,7 @@ func (file *File) readdir(n int) (fi []FileInfo, err error) { if e == syscall.ERROR_NO_MORE_FILES { break } else { - err = &PathError{"FindNextFile", file.name, e} - if !wantAll { - fi = nil - } + err = &PathError{Op: "FindNextFile", Path: file.name, Err: e} return } } @@ -46,24 +37,32 @@ func (file *File) readdir(n int) (fi []FileInfo, err error) { if name == "." || name == ".." { // Useless names continue } - f := newFileStatFromWin32finddata(d) - f.name = name - f.path = file.dirinfo.path - f.appendNameToPath = true + if mode == readdirName { + names = append(names, name) + } else { + f := newFileStatFromWin32finddata(d) + f.name = name + f.path = file.dirinfo.path + f.appendNameToPath = true + if mode == readdirDirEntry { + dirents = append(dirents, dirEntry{f}) + } else { + infos = append(infos, f) + } + } n-- - fi = append(fi, f) } - if !wantAll && len(fi) == 0 { - return fi, io.EOF + if !wantAll && len(names)+len(dirents)+len(infos) == 0 { + return nil, nil, nil, io.EOF } - return fi, nil + return names, dirents, infos, nil } -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() - } - return names, err +type dirEntry struct { + fs *fileStat } + +func (de dirEntry) Name() string { return de.fs.Name() } +func (de dirEntry) IsDir() bool { return de.fs.IsDir() } +func (de dirEntry) Type() FileMode { return de.fs.Mode().Type() } +func (de dirEntry) Info() (FileInfo, error) { return de.fs, nil } diff --git a/src/os/dirent_aix.go b/src/os/dirent_aix.go new file mode 100644 index 0000000000..5597b8af20 --- /dev/null +++ b/src/os/dirent_aix.go @@ -0,0 +1,30 @@ +// Copyright 2020 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 + +import ( + "syscall" + "unsafe" +) + +func direntIno(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Ino), unsafe.Sizeof(syscall.Dirent{}.Ino)) +} + +func direntReclen(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Reclen), unsafe.Sizeof(syscall.Dirent{}.Reclen)) +} + +func direntNamlen(buf []byte) (uint64, bool) { + reclen, ok := direntReclen(buf) + if !ok { + return 0, false + } + return reclen - uint64(unsafe.Offsetof(syscall.Dirent{}.Name)), true +} + +func direntType(buf []byte) FileMode { + return ^FileMode(0) // unknown +} diff --git a/src/os/dirent_dragonfly.go b/src/os/dirent_dragonfly.go new file mode 100644 index 0000000000..38cbd61ed3 --- /dev/null +++ b/src/os/dirent_dragonfly.go @@ -0,0 +1,55 @@ +// Copyright 2020 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 + +import ( + "syscall" + "unsafe" +) + +func direntIno(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Fileno), unsafe.Sizeof(syscall.Dirent{}.Fileno)) +} + +func direntReclen(buf []byte) (uint64, bool) { + namlen, ok := direntNamlen(buf) + if !ok { + return 0, false + } + return (16 + namlen + 1 + 7) &^ 7, true +} + +func direntNamlen(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Namlen), unsafe.Sizeof(syscall.Dirent{}.Namlen)) +} + +func direntType(buf []byte) FileMode { + off := unsafe.Offsetof(syscall.Dirent{}.Type) + if off >= uintptr(len(buf)) { + return ^FileMode(0) // unknown + } + typ := buf[off] + switch typ { + case syscall.DT_BLK: + return ModeDevice + case syscall.DT_CHR: + return ModeDevice | ModeCharDevice + case syscall.DT_DBF: + // DT_DBF is "database record file". + // fillFileStatFromSys treats as regular file. + return 0 + case syscall.DT_DIR: + return ModeDir + case syscall.DT_FIFO: + return ModeNamedPipe + case syscall.DT_LNK: + return ModeSymlink + case syscall.DT_REG: + return 0 + case syscall.DT_SOCK: + return ModeSocket + } + return ^FileMode(0) // unknown +} diff --git a/src/os/dirent_freebsd.go b/src/os/dirent_freebsd.go new file mode 100644 index 0000000000..d600837ebb --- /dev/null +++ b/src/os/dirent_freebsd.go @@ -0,0 +1,47 @@ +// Copyright 2020 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 + +import ( + "syscall" + "unsafe" +) + +func direntIno(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Fileno), unsafe.Sizeof(syscall.Dirent{}.Fileno)) +} + +func direntReclen(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Reclen), unsafe.Sizeof(syscall.Dirent{}.Reclen)) +} + +func direntNamlen(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Namlen), unsafe.Sizeof(syscall.Dirent{}.Namlen)) +} + +func direntType(buf []byte) FileMode { + off := unsafe.Offsetof(syscall.Dirent{}.Type) + if off >= uintptr(len(buf)) { + return ^FileMode(0) // unknown + } + typ := buf[off] + switch typ { + case syscall.DT_BLK: + return ModeDevice + case syscall.DT_CHR: + return ModeDevice | ModeCharDevice + case syscall.DT_DIR: + return ModeDir + case syscall.DT_FIFO: + return ModeNamedPipe + case syscall.DT_LNK: + return ModeSymlink + case syscall.DT_REG: + return 0 + case syscall.DT_SOCK: + return ModeSocket + } + return ^FileMode(0) // unknown +} diff --git a/src/os/dirent_js.go b/src/os/dirent_js.go new file mode 100644 index 0000000000..31778c2ad8 --- /dev/null +++ b/src/os/dirent_js.go @@ -0,0 +1,30 @@ +// Copyright 2020 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 + +import ( + "syscall" + "unsafe" +) + +func direntIno(buf []byte) (uint64, bool) { + return 1, true +} + +func direntReclen(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Reclen), unsafe.Sizeof(syscall.Dirent{}.Reclen)) +} + +func direntNamlen(buf []byte) (uint64, bool) { + reclen, ok := direntReclen(buf) + if !ok { + return 0, false + } + return reclen - uint64(unsafe.Offsetof(syscall.Dirent{}.Name)), true +} + +func direntType(buf []byte) FileMode { + return ^FileMode(0) // unknown +} diff --git a/src/os/dirent_linux.go b/src/os/dirent_linux.go new file mode 100644 index 0000000000..74a3431121 --- /dev/null +++ b/src/os/dirent_linux.go @@ -0,0 +1,51 @@ +// Copyright 2020 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 + +import ( + "syscall" + "unsafe" +) + +func direntIno(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Ino), unsafe.Sizeof(syscall.Dirent{}.Ino)) +} + +func direntReclen(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Reclen), unsafe.Sizeof(syscall.Dirent{}.Reclen)) +} + +func direntNamlen(buf []byte) (uint64, bool) { + reclen, ok := direntReclen(buf) + if !ok { + return 0, false + } + return reclen - uint64(unsafe.Offsetof(syscall.Dirent{}.Name)), true +} + +func direntType(buf []byte) FileMode { + off := unsafe.Offsetof(syscall.Dirent{}.Type) + if off >= uintptr(len(buf)) { + return ^FileMode(0) // unknown + } + typ := buf[off] + switch typ { + case syscall.DT_BLK: + return ModeDevice + case syscall.DT_CHR: + return ModeDevice | ModeCharDevice + case syscall.DT_DIR: + return ModeDir + case syscall.DT_FIFO: + return ModeNamedPipe + case syscall.DT_LNK: + return ModeSymlink + case syscall.DT_REG: + return 0 + case syscall.DT_SOCK: + return ModeSocket + } + return ^FileMode(0) // unknown +} diff --git a/src/os/dirent_netbsd.go b/src/os/dirent_netbsd.go new file mode 100644 index 0000000000..d600837ebb --- /dev/null +++ b/src/os/dirent_netbsd.go @@ -0,0 +1,47 @@ +// Copyright 2020 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 + +import ( + "syscall" + "unsafe" +) + +func direntIno(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Fileno), unsafe.Sizeof(syscall.Dirent{}.Fileno)) +} + +func direntReclen(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Reclen), unsafe.Sizeof(syscall.Dirent{}.Reclen)) +} + +func direntNamlen(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Namlen), unsafe.Sizeof(syscall.Dirent{}.Namlen)) +} + +func direntType(buf []byte) FileMode { + off := unsafe.Offsetof(syscall.Dirent{}.Type) + if off >= uintptr(len(buf)) { + return ^FileMode(0) // unknown + } + typ := buf[off] + switch typ { + case syscall.DT_BLK: + return ModeDevice + case syscall.DT_CHR: + return ModeDevice | ModeCharDevice + case syscall.DT_DIR: + return ModeDir + case syscall.DT_FIFO: + return ModeNamedPipe + case syscall.DT_LNK: + return ModeSymlink + case syscall.DT_REG: + return 0 + case syscall.DT_SOCK: + return ModeSocket + } + return ^FileMode(0) // unknown +} diff --git a/src/os/dirent_openbsd.go b/src/os/dirent_openbsd.go new file mode 100644 index 0000000000..d600837ebb --- /dev/null +++ b/src/os/dirent_openbsd.go @@ -0,0 +1,47 @@ +// Copyright 2020 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 + +import ( + "syscall" + "unsafe" +) + +func direntIno(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Fileno), unsafe.Sizeof(syscall.Dirent{}.Fileno)) +} + +func direntReclen(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Reclen), unsafe.Sizeof(syscall.Dirent{}.Reclen)) +} + +func direntNamlen(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Namlen), unsafe.Sizeof(syscall.Dirent{}.Namlen)) +} + +func direntType(buf []byte) FileMode { + off := unsafe.Offsetof(syscall.Dirent{}.Type) + if off >= uintptr(len(buf)) { + return ^FileMode(0) // unknown + } + typ := buf[off] + switch typ { + case syscall.DT_BLK: + return ModeDevice + case syscall.DT_CHR: + return ModeDevice | ModeCharDevice + case syscall.DT_DIR: + return ModeDir + case syscall.DT_FIFO: + return ModeNamedPipe + case syscall.DT_LNK: + return ModeSymlink + case syscall.DT_REG: + return 0 + case syscall.DT_SOCK: + return ModeSocket + } + return ^FileMode(0) // unknown +} diff --git a/src/os/dirent_solaris.go b/src/os/dirent_solaris.go new file mode 100644 index 0000000000..5597b8af20 --- /dev/null +++ b/src/os/dirent_solaris.go @@ -0,0 +1,30 @@ +// Copyright 2020 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 + +import ( + "syscall" + "unsafe" +) + +func direntIno(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Ino), unsafe.Sizeof(syscall.Dirent{}.Ino)) +} + +func direntReclen(buf []byte) (uint64, bool) { + return readInt(buf, unsafe.Offsetof(syscall.Dirent{}.Reclen), unsafe.Sizeof(syscall.Dirent{}.Reclen)) +} + +func direntNamlen(buf []byte) (uint64, bool) { + reclen, ok := direntReclen(buf) + if !ok { + return 0, false + } + return reclen - uint64(unsafe.Offsetof(syscall.Dirent{}.Name)), true +} + +func direntType(buf []byte) FileMode { + return ^FileMode(0) // unknown +} diff --git a/src/os/endian_big.go b/src/os/endian_big.go new file mode 100644 index 0000000000..c98f124782 --- /dev/null +++ b/src/os/endian_big.go @@ -0,0 +1,9 @@ +// Copyright 2016 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 ppc64 s390x mips mips64 + +package os + +const isBigEndian = true diff --git a/src/os/endian_little.go b/src/os/endian_little.go new file mode 100644 index 0000000000..3efc5e0d8d --- /dev/null +++ b/src/os/endian_little.go @@ -0,0 +1,9 @@ +// Copyright 2016 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 386 amd64 arm arm64 ppc64le mips64le mipsle riscv64 wasm + +package os + +const isBigEndian = false diff --git a/src/os/error.go b/src/os/error.go index 875cc9711f..7cd9f22bfb 100644 --- a/src/os/error.go +++ b/src/os/error.go @@ -7,6 +7,7 @@ package os import ( "internal/oserror" "internal/poll" + "io/fs" ) // Portable analogs of some common system call errors. @@ -16,20 +17,17 @@ import ( var ( // ErrInvalid indicates an invalid argument. // Methods on File will return this error when the receiver is nil. - ErrInvalid = errInvalid() // "invalid argument" + ErrInvalid = fs.ErrInvalid // "invalid argument" + + ErrPermission = fs.ErrPermission // "permission denied" + ErrExist = fs.ErrExist // "file already exists" + ErrNotExist = fs.ErrNotExist // "file does not exist" + ErrClosed = fs.ErrClosed // "file already closed" - ErrPermission = errPermission() // "permission denied" - ErrExist = errExist() // "file already exists" - ErrNotExist = errNotExist() // "file does not exist" - ErrClosed = errClosed() // "file already closed" ErrNoDeadline = errNoDeadline() // "file type does not support deadline" ErrDeadlineExceeded = errDeadlineExceeded() // "i/o timeout" ) -func errInvalid() error { return oserror.ErrInvalid } -func errPermission() error { return oserror.ErrPermission } -func errExist() error { return oserror.ErrExist } -func errNotExist() error { return oserror.ErrNotExist } func errClosed() error { return oserror.ErrClosed } func errNoDeadline() error { return poll.ErrNoDeadline } @@ -47,21 +45,7 @@ type timeout interface { } // PathError records an error and the operation and file path that caused it. -type PathError struct { - Op string - Path string - Err error -} - -func (e *PathError) Error() string { return e.Op + " " + e.Path + ": " + e.Err.Error() } - -func (e *PathError) Unwrap() error { return e.Err } - -// Timeout reports whether this error represents a timeout. -func (e *PathError) Timeout() bool { - t, ok := e.Err.(timeout) - return ok && t.Timeout() -} +type PathError = fs.PathError // SyscallError records an error from a specific system call. type SyscallError struct { diff --git a/src/os/error_test.go b/src/os/error_test.go index 3d921578fd..060cf59875 100644 --- a/src/os/error_test.go +++ b/src/os/error_test.go @@ -7,6 +7,7 @@ package os_test import ( "errors" "fmt" + "io/fs" "io/ioutil" "os" "path/filepath" @@ -27,7 +28,7 @@ func TestErrIsExist(t *testing.T) { t.Fatal("Open should have failed") return } - if s := checkErrorPredicate("os.IsExist", os.IsExist, err, os.ErrExist); s != "" { + if s := checkErrorPredicate("os.IsExist", os.IsExist, err, fs.ErrExist); s != "" { t.Fatal(s) return } @@ -39,7 +40,7 @@ func testErrNotExist(name string) string { f.Close() return "Open should have failed" } - if s := checkErrorPredicate("os.IsNotExist", os.IsNotExist, err, os.ErrNotExist); s != "" { + if s := checkErrorPredicate("os.IsNotExist", os.IsNotExist, err, fs.ErrNotExist); s != "" { return s } @@ -47,7 +48,7 @@ func testErrNotExist(name string) string { if err == nil { return "Chdir should have failed" } - if s := checkErrorPredicate("os.IsNotExist", os.IsNotExist, err, os.ErrNotExist); s != "" { + if s := checkErrorPredicate("os.IsNotExist", os.IsNotExist, err, fs.ErrNotExist); s != "" { return s } return "" @@ -91,18 +92,18 @@ type isExistTest struct { } var isExistTests = []isExistTest{ - {&os.PathError{Err: os.ErrInvalid}, false, false}, - {&os.PathError{Err: os.ErrPermission}, false, false}, - {&os.PathError{Err: os.ErrExist}, true, false}, - {&os.PathError{Err: os.ErrNotExist}, false, true}, - {&os.PathError{Err: os.ErrClosed}, false, false}, - {&os.LinkError{Err: os.ErrInvalid}, false, false}, - {&os.LinkError{Err: os.ErrPermission}, false, false}, - {&os.LinkError{Err: os.ErrExist}, true, false}, - {&os.LinkError{Err: os.ErrNotExist}, false, true}, - {&os.LinkError{Err: os.ErrClosed}, false, false}, - {&os.SyscallError{Err: os.ErrNotExist}, false, true}, - {&os.SyscallError{Err: os.ErrExist}, true, false}, + {&fs.PathError{Err: fs.ErrInvalid}, false, false}, + {&fs.PathError{Err: fs.ErrPermission}, false, false}, + {&fs.PathError{Err: fs.ErrExist}, true, false}, + {&fs.PathError{Err: fs.ErrNotExist}, false, true}, + {&fs.PathError{Err: fs.ErrClosed}, false, false}, + {&os.LinkError{Err: fs.ErrInvalid}, false, false}, + {&os.LinkError{Err: fs.ErrPermission}, false, false}, + {&os.LinkError{Err: fs.ErrExist}, true, false}, + {&os.LinkError{Err: fs.ErrNotExist}, false, true}, + {&os.LinkError{Err: fs.ErrClosed}, false, false}, + {&os.SyscallError{Err: fs.ErrNotExist}, false, true}, + {&os.SyscallError{Err: fs.ErrExist}, true, false}, {nil, false, false}, } @@ -111,14 +112,14 @@ func TestIsExist(t *testing.T) { if is := os.IsExist(tt.err); is != tt.is { t.Errorf("os.IsExist(%T %v) = %v, want %v", tt.err, tt.err, is, tt.is) } - if is := errors.Is(tt.err, os.ErrExist); is != tt.is { - t.Errorf("errors.Is(%T %v, os.ErrExist) = %v, want %v", tt.err, tt.err, is, tt.is) + if is := errors.Is(tt.err, fs.ErrExist); is != tt.is { + t.Errorf("errors.Is(%T %v, fs.ErrExist) = %v, want %v", tt.err, tt.err, is, tt.is) } if isnot := os.IsNotExist(tt.err); isnot != tt.isnot { t.Errorf("os.IsNotExist(%T %v) = %v, want %v", tt.err, tt.err, isnot, tt.isnot) } - if isnot := errors.Is(tt.err, os.ErrNotExist); isnot != tt.isnot { - t.Errorf("errors.Is(%T %v, os.ErrNotExist) = %v, want %v", tt.err, tt.err, isnot, tt.isnot) + if isnot := errors.Is(tt.err, fs.ErrNotExist); isnot != tt.isnot { + t.Errorf("errors.Is(%T %v, fs.ErrNotExist) = %v, want %v", tt.err, tt.err, isnot, tt.isnot) } } } @@ -130,8 +131,8 @@ type isPermissionTest struct { var isPermissionTests = []isPermissionTest{ {nil, false}, - {&os.PathError{Err: os.ErrPermission}, true}, - {&os.SyscallError{Err: os.ErrPermission}, true}, + {&fs.PathError{Err: fs.ErrPermission}, true}, + {&os.SyscallError{Err: fs.ErrPermission}, true}, } func TestIsPermission(t *testing.T) { @@ -139,8 +140,8 @@ func TestIsPermission(t *testing.T) { if got := os.IsPermission(tt.err); got != tt.want { t.Errorf("os.IsPermission(%#v) = %v; want %v", tt.err, got, tt.want) } - if got := errors.Is(tt.err, os.ErrPermission); got != tt.want { - t.Errorf("errors.Is(%#v, os.ErrPermission) = %v; want %v", tt.err, got, tt.want) + if got := errors.Is(tt.err, fs.ErrPermission); got != tt.want { + t.Errorf("errors.Is(%#v, fs.ErrPermission) = %v; want %v", tt.err, got, tt.want) } } } @@ -170,8 +171,8 @@ func TestErrPathNUL(t *testing.T) { } func TestPathErrorUnwrap(t *testing.T) { - pe := &os.PathError{Err: os.ErrInvalid} - if !errors.Is(pe, os.ErrInvalid) { + pe := &fs.PathError{Err: fs.ErrInvalid} + if !errors.Is(pe, fs.ErrInvalid) { t.Error("errors.Is failed, wanted success") } } @@ -181,7 +182,7 @@ type myErrorIs struct{ error } func (e myErrorIs) Is(target error) bool { return target == e.error } func TestErrorIsMethods(t *testing.T) { - if os.IsPermission(myErrorIs{os.ErrPermission}) { - t.Error("os.IsPermission(err) = true when err.Is(os.ErrPermission), wanted false") + if os.IsPermission(myErrorIs{fs.ErrPermission}) { + t.Error("os.IsPermission(err) = true when err.Is(fs.ErrPermission), wanted false") } } diff --git a/src/os/error_unix_test.go b/src/os/error_unix_test.go index bfc83c9867..18bcf3f4e4 100644 --- a/src/os/error_unix_test.go +++ b/src/os/error_unix_test.go @@ -7,14 +7,15 @@ package os_test import ( + "io/fs" "os" "syscall" ) func init() { isExistTests = append(isExistTests, - isExistTest{err: &os.PathError{Err: syscall.EEXIST}, is: true, isnot: false}, - isExistTest{err: &os.PathError{Err: syscall.ENOTEMPTY}, is: true, isnot: false}, + isExistTest{err: &fs.PathError{Err: syscall.EEXIST}, is: true, isnot: false}, + isExistTest{err: &fs.PathError{Err: syscall.ENOTEMPTY}, is: true, isnot: false}, isExistTest{err: &os.LinkError{Err: syscall.EEXIST}, is: true, isnot: false}, isExistTest{err: &os.LinkError{Err: syscall.ENOTEMPTY}, is: true, isnot: false}, @@ -23,9 +24,9 @@ func init() { isExistTest{err: &os.SyscallError{Err: syscall.ENOTEMPTY}, is: true, isnot: false}, ) isPermissionTests = append(isPermissionTests, - isPermissionTest{err: &os.PathError{Err: syscall.EACCES}, want: true}, - isPermissionTest{err: &os.PathError{Err: syscall.EPERM}, want: true}, - isPermissionTest{err: &os.PathError{Err: syscall.EEXIST}, want: false}, + isPermissionTest{err: &fs.PathError{Err: syscall.EACCES}, want: true}, + isPermissionTest{err: &fs.PathError{Err: syscall.EPERM}, want: true}, + isPermissionTest{err: &fs.PathError{Err: syscall.EEXIST}, want: false}, isPermissionTest{err: &os.LinkError{Err: syscall.EACCES}, want: true}, isPermissionTest{err: &os.LinkError{Err: syscall.EPERM}, want: true}, diff --git a/src/os/error_windows_test.go b/src/os/error_windows_test.go index 1635c1088e..b8191c5ebc 100644 --- a/src/os/error_windows_test.go +++ b/src/os/error_windows_test.go @@ -7,6 +7,7 @@ package os_test import ( + "io/fs" "os" "syscall" ) @@ -15,24 +16,24 @@ func init() { const _ERROR_BAD_NETPATH = syscall.Errno(53) isExistTests = append(isExistTests, - isExistTest{err: &os.PathError{Err: syscall.ERROR_FILE_NOT_FOUND}, is: false, isnot: true}, + isExistTest{err: &fs.PathError{Err: syscall.ERROR_FILE_NOT_FOUND}, is: false, isnot: true}, isExistTest{err: &os.LinkError{Err: syscall.ERROR_FILE_NOT_FOUND}, is: false, isnot: true}, isExistTest{err: &os.SyscallError{Err: syscall.ERROR_FILE_NOT_FOUND}, is: false, isnot: true}, - isExistTest{err: &os.PathError{Err: _ERROR_BAD_NETPATH}, is: false, isnot: true}, + isExistTest{err: &fs.PathError{Err: _ERROR_BAD_NETPATH}, is: false, isnot: true}, isExistTest{err: &os.LinkError{Err: _ERROR_BAD_NETPATH}, is: false, isnot: true}, isExistTest{err: &os.SyscallError{Err: _ERROR_BAD_NETPATH}, is: false, isnot: true}, - isExistTest{err: &os.PathError{Err: syscall.ERROR_PATH_NOT_FOUND}, is: false, isnot: true}, + isExistTest{err: &fs.PathError{Err: syscall.ERROR_PATH_NOT_FOUND}, is: false, isnot: true}, isExistTest{err: &os.LinkError{Err: syscall.ERROR_PATH_NOT_FOUND}, is: false, isnot: true}, isExistTest{err: &os.SyscallError{Err: syscall.ERROR_PATH_NOT_FOUND}, is: false, isnot: true}, - isExistTest{err: &os.PathError{Err: syscall.ERROR_DIR_NOT_EMPTY}, is: true, isnot: false}, + isExistTest{err: &fs.PathError{Err: syscall.ERROR_DIR_NOT_EMPTY}, is: true, isnot: false}, isExistTest{err: &os.LinkError{Err: syscall.ERROR_DIR_NOT_EMPTY}, is: true, isnot: false}, isExistTest{err: &os.SyscallError{Err: syscall.ERROR_DIR_NOT_EMPTY}, is: true, isnot: false}, ) isPermissionTests = append(isPermissionTests, - isPermissionTest{err: &os.PathError{Err: syscall.ERROR_ACCESS_DENIED}, want: true}, + isPermissionTest{err: &fs.PathError{Err: syscall.ERROR_ACCESS_DENIED}, want: true}, isPermissionTest{err: &os.LinkError{Err: syscall.ERROR_ACCESS_DENIED}, want: true}, isPermissionTest{err: &os.SyscallError{Err: syscall.ERROR_ACCESS_DENIED}, want: true}, ) diff --git a/src/os/example_test.go b/src/os/example_test.go index 822886f70c..fbb277b6f1 100644 --- a/src/os/example_test.go +++ b/src/os/example_test.go @@ -6,6 +6,7 @@ package os_test import ( "fmt" + "io/fs" "log" "os" "time" @@ -62,9 +63,9 @@ func ExampleFileMode() { fmt.Println("regular file") case mode.IsDir(): fmt.Println("directory") - case mode&os.ModeSymlink != 0: + case mode&fs.ModeSymlink != 0: fmt.Println("symbolic link") - case mode&os.ModeNamedPipe != 0: + case mode&fs.ModeNamedPipe != 0: fmt.Println("named pipe") } } diff --git a/src/os/exec/example_test.go b/src/os/exec/example_test.go index 62866fa710..a66890be69 100644 --- a/src/os/exec/example_test.go +++ b/src/os/exec/example_test.go @@ -10,7 +10,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "log" "os" "os/exec" @@ -128,7 +127,7 @@ func ExampleCmd_StderrPipe() { log.Fatal(err) } - slurp, _ := ioutil.ReadAll(stderr) + slurp, _ := io.ReadAll(stderr) fmt.Printf("%s\n", slurp) if err := cmd.Wait(); err != nil { diff --git a/src/os/exec/exec_plan9.go b/src/os/exec/exec_plan9.go index d90bd04399..21ac7b765f 100644 --- a/src/os/exec/exec_plan9.go +++ b/src/os/exec/exec_plan9.go @@ -4,14 +4,14 @@ package exec -import "os" +import "io/fs" func init() { skipStdinCopyError = func(err error) bool { // Ignore hungup errors copying to stdin if the program // completed successfully otherwise. // See Issue 35753. - pe, ok := err.(*os.PathError) + pe, ok := err.(*fs.PathError) return ok && pe.Op == "write" && pe.Path == "|1" && pe.Err.Error() == "i/o on hungup channel" diff --git a/src/os/exec/exec_test.go b/src/os/exec/exec_test.go index dafbc64a17..cd3d759ebc 100644 --- a/src/os/exec/exec_test.go +++ b/src/os/exec/exec_test.go @@ -605,6 +605,10 @@ func TestExtraFiles(t *testing.T) { testenv.MustHaveExec(t) testenv.MustHaveGoBuild(t) + // This test runs with cgo disabled. External linking needs cgo, so + // it doesn't work if external linking is required. + testenv.MustInternalLink(t) + if runtime.GOOS == "windows" { t.Skipf("skipping test on %q", runtime.GOOS) } @@ -633,7 +637,7 @@ func TestExtraFiles(t *testing.T) { // cgo), to make sure none of that potential C code leaks fds. ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) // quiet expected TLS handshake error "remote error: bad certificate" - ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0) + ts.Config.ErrorLog = log.New(io.Discard, "", 0) ts.StartTLS() defer ts.Close() _, err = http.Get(ts.URL) @@ -826,7 +830,7 @@ func TestHelperProcess(*testing.T) { } } case "stdinClose": - b, err := ioutil.ReadAll(os.Stdin) + b, err := io.ReadAll(os.Stdin) if err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) diff --git a/src/os/exec/exec_unix.go b/src/os/exec/exec_unix.go index 9c3e17d23a..51c52427c2 100644 --- a/src/os/exec/exec_unix.go +++ b/src/os/exec/exec_unix.go @@ -7,7 +7,7 @@ package exec import ( - "os" + "io/fs" "syscall" ) @@ -16,7 +16,7 @@ func init() { // Ignore EPIPE errors copying to stdin if the program // completed successfully otherwise. // See Issue 9173. - pe, ok := err.(*os.PathError) + pe, ok := err.(*fs.PathError) return ok && pe.Op == "write" && pe.Path == "|1" && pe.Err == syscall.EPIPE diff --git a/src/os/exec/exec_windows.go b/src/os/exec/exec_windows.go index af8cd97218..bb937f8aed 100644 --- a/src/os/exec/exec_windows.go +++ b/src/os/exec/exec_windows.go @@ -5,7 +5,7 @@ package exec import ( - "os" + "io/fs" "syscall" ) @@ -15,7 +15,7 @@ func init() { // to stdin if the program completed successfully otherwise. // See Issue 20445. const _ERROR_NO_DATA = syscall.Errno(0xe8) - pe, ok := err.(*os.PathError) + pe, ok := err.(*fs.PathError) return ok && pe.Op == "write" && pe.Path == "|1" && (pe.Err == syscall.ERROR_BROKEN_PIPE || pe.Err == _ERROR_NO_DATA) diff --git a/src/os/exec/lp_plan9.go b/src/os/exec/lp_plan9.go index 5860cbca4d..e8826a5083 100644 --- a/src/os/exec/lp_plan9.go +++ b/src/os/exec/lp_plan9.go @@ -6,6 +6,7 @@ package exec import ( "errors" + "io/fs" "os" "path/filepath" "strings" @@ -22,7 +23,7 @@ func findExecutable(file string) error { if m := d.Mode(); !m.IsDir() && m&0111 != 0 { return nil } - return os.ErrPermission + return fs.ErrPermission } // LookPath searches for an executable named file in the diff --git a/src/os/exec/lp_unix.go b/src/os/exec/lp_unix.go index 93793e0eee..66c1df76fb 100644 --- a/src/os/exec/lp_unix.go +++ b/src/os/exec/lp_unix.go @@ -8,6 +8,7 @@ package exec import ( "errors" + "io/fs" "os" "path/filepath" "strings" @@ -24,7 +25,7 @@ func findExecutable(file string) error { if m := d.Mode(); !m.IsDir() && m&0111 != 0 { return nil } - return os.ErrPermission + return fs.ErrPermission } // LookPath searches for an executable named file in the diff --git a/src/os/exec/lp_windows.go b/src/os/exec/lp_windows.go index 9ea3d76575..e7a2cdf142 100644 --- a/src/os/exec/lp_windows.go +++ b/src/os/exec/lp_windows.go @@ -6,6 +6,7 @@ package exec import ( "errors" + "io/fs" "os" "path/filepath" "strings" @@ -20,7 +21,7 @@ func chkStat(file string) error { return err } if d.IsDir() { - return os.ErrPermission + return fs.ErrPermission } return nil } @@ -47,7 +48,7 @@ func findExecutable(file string, exts []string) (string, error) { return f, nil } } - return "", os.ErrNotExist + return "", fs.ErrNotExist } // LookPath searches for an executable named file in the diff --git a/src/os/exec/read3.go b/src/os/exec/read3.go index 25d732a991..8852023e77 100644 --- a/src/os/exec/read3.go +++ b/src/os/exec/read3.go @@ -15,7 +15,7 @@ package main import ( "fmt" "internal/poll" - "io/ioutil" + "io" "os" "os/exec" "runtime" @@ -24,7 +24,7 @@ import ( func main() { fd3 := os.NewFile(3, "fd3") - bs, err := ioutil.ReadAll(fd3) + bs, err := io.ReadAll(fd3) if err != nil { fmt.Printf("ReadAll from fd 3: %v\n", err) os.Exit(1) diff --git a/src/os/exec_plan9.go b/src/os/exec_plan9.go index b0abf743dd..ef8dad11b6 100644 --- a/src/os/exec_plan9.go +++ b/src/os/exec_plan9.go @@ -34,7 +34,7 @@ func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err e pid, h, e := syscall.StartProcess(name, argv, sysattr) if e != nil { - return nil, &PathError{"fork/exec", name, e} + return nil, &PathError{Op: "fork/exec", Path: name, Err: e} } return newProcess(pid, h), nil diff --git a/src/os/exec_posix.go b/src/os/exec_posix.go index 45b47a542d..7ecddaed37 100644 --- a/src/os/exec_posix.go +++ b/src/os/exec_posix.go @@ -56,7 +56,7 @@ func startProcess(name string, argv []string, attr *ProcAttr) (p *Process, err e runtime.KeepAlive(attr) if e != nil { - return nil, &PathError{"fork/exec", name, e} + return nil, &PathError{Op: "fork/exec", Path: name, Err: e} } return newProcess(pid, h), nil diff --git a/src/os/export_test.go b/src/os/export_test.go index 812432cee4..d66264a68f 100644 --- a/src/os/export_test.go +++ b/src/os/export_test.go @@ -9,3 +9,4 @@ package os var Atime = atime var LstatP = &lstat var ErrWriteAtInAppendMode = errWriteAtInAppendMode +var TestingForceReadDirLstat = &testingForceReadDirLstat diff --git a/src/os/file.go b/src/os/file.go index 05d2f83283..835d44ab8c 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -45,6 +45,7 @@ import ( "internal/poll" "internal/testlog" "io" + "io/fs" "runtime" "syscall" "time" @@ -127,7 +128,7 @@ func (f *File) ReadAt(b []byte, off int64) (n int, err error) { } if off < 0 { - return 0, &PathError{"readat", f.name, errors.New("negative offset")} + return 0, &PathError{Op: "readat", Path: f.name, Err: errors.New("negative offset")} } for len(b) > 0 { @@ -203,7 +204,7 @@ func (f *File) WriteAt(b []byte, off int64) (n int, err error) { } if off < 0 { - return 0, &PathError{"writeat", f.name, errors.New("negative offset")} + return 0, &PathError{Op: "writeat", Path: f.name, Err: errors.New("negative offset")} } for len(b) > 0 { @@ -253,7 +254,7 @@ func (f *File) WriteString(s string) (n int, err error) { // If there is an error, it will be of type *PathError. func Mkdir(name string, perm FileMode) error { if runtime.GOOS == "windows" && isWindowsNulName(name) { - return &PathError{"mkdir", name, syscall.ENOTDIR} + return &PathError{Op: "mkdir", Path: name, Err: syscall.ENOTDIR} } longName := fixLongPath(name) e := ignoringEINTR(func() error { @@ -261,7 +262,7 @@ func Mkdir(name string, perm FileMode) error { }) if e != nil { - return &PathError{"mkdir", name, e} + return &PathError{Op: "mkdir", Path: name, Err: e} } // mkdir(2) itself won't handle the sticky bit on *BSD and Solaris @@ -291,7 +292,7 @@ func setStickyBit(name string) error { func Chdir(dir string) error { if e := syscall.Chdir(dir); e != nil { testlog.Open(dir) // observe likely non-existent directory - return &PathError{"chdir", dir, e} + return &PathError{Op: "chdir", Path: dir, Err: e} } if log := testlog.Logger(); log != nil { wd, err := Getwd() @@ -366,7 +367,7 @@ func (f *File) wrapErr(op string, err error) error { if err == poll.ErrFileClosing { err = ErrClosed } - return &PathError{op, f.name, err} + return &PathError{Op: op, Path: f.name, Err: err} } // TempDir returns the default directory to use for temporary files. @@ -608,3 +609,21 @@ func isWindowsNulName(name string) bool { } return true } + +// DirFS returns a file system (an fs.FS) for the tree of files rooted at the directory dir. +func DirFS(dir string) fs.FS { + return dirFS(dir) +} + +type dirFS string + +func (dir dirFS) Open(name string) (fs.File, error) { + if !fs.ValidPath(name) { + return nil, &PathError{Op: "open", Path: name, Err: ErrInvalid} + } + f, err := Open(string(dir) + "/" + name) + if err != nil { + return nil, err // nil fs.File + } + return f, nil +} diff --git a/src/os/file_plan9.go b/src/os/file_plan9.go index a1a51a1c06..bbc732838a 100644 --- a/src/os/file_plan9.go +++ b/src/os/file_plan9.go @@ -119,18 +119,18 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) { if IsNotExist(e) && create { fd, e = syscall.Create(name, flag, syscallMode(perm)) if e != nil { - return nil, &PathError{"create", name, e} + return nil, &PathError{Op: "create", Path: name, Err: e} } } } if e != nil { - return nil, &PathError{"open", name, e} + return nil, &PathError{Op: "open", Path: name, Err: e} } if append { if _, e = syscall.Seek(fd, 0, io.SeekEnd); e != nil { - return nil, &PathError{"seek", name, e} + return nil, &PathError{Op: "seek", Path: name, Err: e} } } @@ -154,7 +154,7 @@ func (file *file) close() error { } var err error if e := syscall.Close(file.fd); e != nil { - err = &PathError{"close", file.name, e} + err = &PathError{Op: "close", Path: file.name, Err: e} } file.fd = badFd // so it can't be closed again @@ -191,10 +191,10 @@ func (f *File) Truncate(size int64) error { var buf [syscall.STATFIXLEN]byte n, err := d.Marshal(buf[:]) if err != nil { - return &PathError{"truncate", f.name, err} + return &PathError{Op: "truncate", Path: f.name, Err: err} } if err = syscall.Fwstat(f.fd, buf[:n]); err != nil { - return &PathError{"truncate", f.name, err} + return &PathError{Op: "truncate", Path: f.name, Err: err} } return nil } @@ -209,7 +209,7 @@ func (f *File) chmod(mode FileMode) error { odir, e := dirstat(f) if e != nil { - return &PathError{"chmod", f.name, e} + return &PathError{Op: "chmod", Path: f.name, Err: e} } d.Null() d.Mode = odir.Mode&^chmodMask | syscallMode(mode)&chmodMask @@ -217,10 +217,10 @@ func (f *File) chmod(mode FileMode) error { var buf [syscall.STATFIXLEN]byte n, err := d.Marshal(buf[:]) if err != nil { - return &PathError{"chmod", f.name, err} + return &PathError{Op: "chmod", Path: f.name, Err: err} } if err = syscall.Fwstat(f.fd, buf[:n]); err != nil { - return &PathError{"chmod", f.name, err} + return &PathError{Op: "chmod", Path: f.name, Err: err} } return nil } @@ -238,10 +238,10 @@ func (f *File) Sync() error { var buf [syscall.STATFIXLEN]byte n, err := d.Marshal(buf[:]) if err != nil { - return &PathError{"sync", f.name, err} + return &PathError{Op: "sync", Path: f.name, Err: err} } if err = syscall.Fwstat(f.fd, buf[:n]); err != nil { - return &PathError{"sync", f.name, err} + return &PathError{Op: "sync", Path: f.name, Err: err} } return nil } @@ -314,10 +314,10 @@ func Truncate(name string, size int64) error { var buf [syscall.STATFIXLEN]byte n, err := d.Marshal(buf[:]) if err != nil { - return &PathError{"truncate", name, err} + return &PathError{Op: "truncate", Path: name, Err: err} } if err = syscall.Wstat(name, buf[:n]); err != nil { - return &PathError{"truncate", name, err} + return &PathError{Op: "truncate", Path: name, Err: err} } return nil } @@ -326,7 +326,7 @@ func Truncate(name string, size int64) error { // If there is an error, it will be of type *PathError. func Remove(name string) error { if e := syscall.Remove(name); e != nil { - return &PathError{"remove", name, e} + return &PathError{Op: "remove", Path: name, Err: e} } return nil } @@ -389,7 +389,7 @@ func chmod(name string, mode FileMode) error { odir, e := dirstat(name) if e != nil { - return &PathError{"chmod", name, e} + return &PathError{Op: "chmod", Path: name, Err: e} } d.Null() d.Mode = odir.Mode&^chmodMask | syscallMode(mode)&chmodMask @@ -397,10 +397,10 @@ func chmod(name string, mode FileMode) error { var buf [syscall.STATFIXLEN]byte n, err := d.Marshal(buf[:]) if err != nil { - return &PathError{"chmod", name, err} + return &PathError{Op: "chmod", Path: name, Err: err} } if err = syscall.Wstat(name, buf[:n]); err != nil { - return &PathError{"chmod", name, err} + return &PathError{Op: "chmod", Path: name, Err: err} } return nil } @@ -421,10 +421,10 @@ func Chtimes(name string, atime time.Time, mtime time.Time) error { var buf [syscall.STATFIXLEN]byte n, err := d.Marshal(buf[:]) if err != nil { - return &PathError{"chtimes", name, err} + return &PathError{Op: "chtimes", Path: name, Err: err} } if err = syscall.Wstat(name, buf[:n]); err != nil { - return &PathError{"chtimes", name, err} + return &PathError{Op: "chtimes", Path: name, Err: err} } return nil } @@ -458,7 +458,7 @@ func Symlink(oldname, newname string) error { // Readlink returns the destination of the named symbolic link. // If there is an error, it will be of type *PathError. func Readlink(name string) (string, error) { - return "", &PathError{"readlink", name, syscall.EPLAN9} + return "", &PathError{Op: "readlink", Path: name, Err: syscall.EPLAN9} } // Chown changes the numeric uid and gid of the named file. @@ -469,14 +469,14 @@ func Readlink(name string) (string, error) { // On Windows or Plan 9, Chown always returns the syscall.EWINDOWS or // EPLAN9 error, wrapped in *PathError. func Chown(name string, uid, gid int) error { - return &PathError{"chown", name, syscall.EPLAN9} + return &PathError{Op: "chown", Path: name, Err: syscall.EPLAN9} } // Lchown changes the numeric uid and gid of the named file. // If the file is a symbolic link, it changes the uid and gid of the link itself. // If there is an error, it will be of type *PathError. func Lchown(name string, uid, gid int) error { - return &PathError{"lchown", name, syscall.EPLAN9} + return &PathError{Op: "lchown", Path: name, Err: syscall.EPLAN9} } // Chown changes the numeric uid and gid of the named file. @@ -485,7 +485,7 @@ func (f *File) Chown(uid, gid int) error { if f == nil { return ErrInvalid } - return &PathError{"chown", f.name, syscall.EPLAN9} + return &PathError{Op: "chown", Path: f.name, Err: syscall.EPLAN9} } func tempDir() string { @@ -505,7 +505,7 @@ func (f *File) Chdir() error { return err } if e := syscall.Fchdir(f.fd); e != nil { - return &PathError{"chdir", f.name, e} + return &PathError{Op: "chdir", Path: f.name, Err: e} } return nil } @@ -541,7 +541,7 @@ func (f *File) checkValid(op string) error { return ErrInvalid } if f.fd == badFd { - return &PathError{op, f.name, ErrClosed} + return &PathError{Op: op, Path: f.name, Err: ErrClosed} } return nil } diff --git a/src/os/file_posix.go b/src/os/file_posix.go index ae23d22d0a..795c547856 100644 --- a/src/os/file_posix.go +++ b/src/os/file_posix.go @@ -81,7 +81,7 @@ func chmod(name string, mode FileMode) error { return syscall.Chmod(longName, syscallMode(mode)) }) if e != nil { - return &PathError{"chmod", name, e} + return &PathError{Op: "chmod", Path: name, Err: e} } return nil } @@ -109,7 +109,7 @@ func Chown(name string, uid, gid int) error { return syscall.Chown(name, uid, gid) }) if e != nil { - return &PathError{"chown", name, e} + return &PathError{Op: "chown", Path: name, Err: e} } return nil } @@ -125,7 +125,7 @@ func Lchown(name string, uid, gid int) error { return syscall.Lchown(name, uid, gid) }) if e != nil { - return &PathError{"lchown", name, e} + return &PathError{Op: "lchown", Path: name, Err: e} } return nil } @@ -182,7 +182,7 @@ func Chtimes(name string, atime time.Time, mtime time.Time) error { utimes[0] = syscall.NsecToTimespec(atime.UnixNano()) utimes[1] = syscall.NsecToTimespec(mtime.UnixNano()) if e := syscall.UtimesNano(fixLongPath(name), utimes[0:]); e != nil { - return &PathError{"chtimes", name, e} + return &PathError{Op: "chtimes", Path: name, Err: e} } return nil } diff --git a/src/os/file_unix.go b/src/os/file_unix.go index e0f16d809d..0dc7a5a0a2 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -9,7 +9,6 @@ package os import ( "internal/poll" "internal/syscall/unix" - "io" "runtime" "syscall" ) @@ -216,7 +215,7 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) { continue } - return nil, &PathError{"open", name, e} + return nil, &PathError{Op: "open", Path: name, Err: e} } // open(2) itself won't handle the sticky bit on *BSD and Solaris @@ -245,7 +244,7 @@ func (file *file) close() error { if e == poll.ErrFileClosing { e = ErrClosed } - err = &PathError{"close", file.name, e} + err = &PathError{Op: "close", Path: file.name, Err: e} } // no need for a finalizer anymore @@ -277,7 +276,7 @@ func Truncate(name string, size int64) error { return syscall.Truncate(name, size) }) if e != nil { - return &PathError{"truncate", name, e} + return &PathError{Op: "truncate", Path: name, Err: e} } return nil } @@ -314,7 +313,7 @@ func Remove(name string) error { if e1 != syscall.ENOTDIR { e = e1 } - return &PathError{"remove", name, e} + return &PathError{Op: "remove", Path: name, Err: e} } func tempDir() string { @@ -353,33 +352,6 @@ func Symlink(oldname, newname string) error { return nil } -func (f *File) readdir(n int) (fi []FileInfo, err error) { - dirname := f.name - if dirname == "" { - dirname = "." - } - names, err := f.Readdirnames(n) - fi = make([]FileInfo, 0, len(names)) - for _, filename := range names { - fip, lerr := lstat(dirname + "/" + filename) - if IsNotExist(lerr) { - // File disappeared between readdir + stat. - // Just treat it as if it didn't exist. - continue - } - if lerr != nil { - return fi, lerr - } - fi = append(fi, fip) - } - if len(fi) == 0 && err == nil && n > 0 { - // Per File.Readdir, the slice must be non-empty or err - // must be non-nil if n > 0. - err = io.EOF - } - return fi, err -} - // Readlink returns the destination of the named symbolic link. // If there is an error, it will be of type *PathError. func Readlink(name string) (string, error) { @@ -400,10 +372,48 @@ func Readlink(name string) (string, error) { continue } if e != nil { - return "", &PathError{"readlink", name, e} + return "", &PathError{Op: "readlink", Path: name, Err: e} } if n < len { return string(b[0:n]), nil } } } + +type unixDirent struct { + parent string + name string + typ FileMode + info FileInfo +} + +func (d *unixDirent) Name() string { return d.name } +func (d *unixDirent) IsDir() bool { return d.typ.IsDir() } +func (d *unixDirent) Type() FileMode { return d.typ } + +func (d *unixDirent) Info() (FileInfo, error) { + if d.info != nil { + return d.info, nil + } + return lstat(d.parent + "/" + d.name) +} + +func newUnixDirent(parent, name string, typ FileMode) (DirEntry, error) { + ude := &unixDirent{ + parent: parent, + name: name, + typ: typ, + } + if typ != ^FileMode(0) && !testingForceReadDirLstat { + return ude, nil + } + + info, err := lstat(parent + "/" + name) + if err != nil { + return nil, err + } + + ude.typ = info.Mode().Type() + ude.info = info + return ude, nil +} diff --git a/src/os/file_windows.go b/src/os/file_windows.go index f744a35023..dfc5fc6ce6 100644 --- a/src/os/file_windows.go +++ b/src/os/file_windows.go @@ -168,7 +168,7 @@ func openDir(name string) (file *File, err error) { // openFileNolog is the Windows implementation of OpenFile. func openFileNolog(name string, flag int, perm FileMode) (*File, error) { if name == "" { - return nil, &PathError{"open", name, syscall.ENOENT} + return nil, &PathError{Op: "open", Path: name, Err: syscall.ENOENT} } r, errf := openFile(name, flag, perm) if errf == nil { @@ -178,11 +178,11 @@ func openFileNolog(name string, flag int, perm FileMode) (*File, error) { if errd == nil { if flag&O_WRONLY != 0 || flag&O_RDWR != 0 { r.Close() - return nil, &PathError{"open", name, syscall.EISDIR} + return nil, &PathError{Op: "open", Path: name, Err: syscall.EISDIR} } return r, nil } - return nil, &PathError{"open", name, errf} + return nil, &PathError{Op: "open", Path: name, Err: errf} } func (file *file) close() error { @@ -198,7 +198,7 @@ func (file *file) close() error { if e == poll.ErrFileClosing { e = ErrClosed } - err = &PathError{"close", file.name, e} + err = &PathError{Op: "close", Path: file.name, Err: e} } // no need for a finalizer anymore @@ -236,7 +236,7 @@ func Truncate(name string, size int64) error { func Remove(name string) error { p, e := syscall.UTF16PtrFromString(fixLongPath(name)) if e != nil { - return &PathError{"remove", name, e} + return &PathError{Op: "remove", Path: name, Err: e} } // Go file interface forces us to know whether @@ -267,7 +267,7 @@ func Remove(name string) error { } } } - return &PathError{"remove", name, e} + return &PathError{Op: "remove", Path: name, Err: e} } func rename(oldname, newname string) error { @@ -493,7 +493,7 @@ func readlink(path string) (string, error) { func Readlink(name string) (string, error) { s, err := readlink(fixLongPath(name)) if err != nil { - return "", &PathError{"readlink", name, err} + return "", &PathError{Op: "readlink", Path: name, Err: err} } return s, nil } diff --git a/src/os/os_test.go b/src/os/os_test.go index 2bb57d866f..378ddf58dd 100644 --- a/src/os/os_test.go +++ b/src/os/os_test.go @@ -23,6 +23,7 @@ import ( "sync" "syscall" "testing" + "testing/fstest" "time" ) @@ -309,20 +310,21 @@ func testReaddirnames(dir string, contents []string, t *testing.T) { defer file.Close() s, err2 := file.Readdirnames(-1) if err2 != nil { - t.Fatalf("readdirnames %q failed: %v", dir, err2) + t.Fatalf("Readdirnames %q failed: %v", dir, err2) } for _, m := range contents { found := false for _, n := range s { if n == "." || n == ".." { - t.Errorf("got %s in directory", n) + t.Errorf("got %q in directory", n) } - if equal(m, n) { - if found { - t.Error("present twice:", m) - } - found = true + if !equal(m, n) { + continue } + if found { + t.Error("present twice:", m) + } + found = true } if !found { t.Error("could not find", m) @@ -338,16 +340,68 @@ func testReaddir(dir string, contents []string, t *testing.T) { defer file.Close() s, err2 := file.Readdir(-1) if err2 != nil { - t.Fatalf("readdir %q failed: %v", dir, err2) + t.Fatalf("Readdir %q failed: %v", dir, err2) } for _, m := range contents { found := false for _, n := range s { - if equal(m, n.Name()) { - if found { - t.Error("present twice:", m) - } - found = true + if n.Name() == "." || n.Name() == ".." { + t.Errorf("got %q in directory", n.Name()) + } + if !equal(m, n.Name()) { + continue + } + if found { + t.Error("present twice:", m) + } + found = true + } + if !found { + t.Error("could not find", m) + } + } +} + +func testReadDir(dir string, contents []string, t *testing.T) { + file, err := Open(dir) + if err != nil { + t.Fatalf("open %q failed: %v", dir, err) + } + defer file.Close() + s, err2 := file.ReadDir(-1) + if err2 != nil { + t.Fatalf("ReadDir %q failed: %v", dir, err2) + } + for _, m := range contents { + found := false + for _, n := range s { + if n.Name() == "." || n.Name() == ".." { + t.Errorf("got %q in directory", n) + } + if !equal(m, n.Name()) { + continue + } + if found { + t.Error("present twice:", m) + } + found = true + lstat, err := Lstat(dir + "/" + m) + if err != nil { + t.Fatal(err) + } + if n.IsDir() != lstat.IsDir() { + t.Errorf("%s: IsDir=%v, want %v", m, n.IsDir(), lstat.IsDir()) + } + if n.Type() != lstat.Mode().Type() { + t.Errorf("%s: IsDir=%v, want %v", m, n.Type(), lstat.Mode().Type()) + } + info, err := n.Info() + if err != nil { + t.Errorf("%s: Info: %v", m, err) + continue + } + if !SameFile(info, lstat) { + t.Errorf("%s: Info: SameFile(info, lstat) = false", m) } } if !found { @@ -366,6 +420,11 @@ func TestReaddir(t *testing.T) { testReaddir(sysdir.name, sysdir.files, t) } +func TestReadDir(t *testing.T) { + testReadDir(".", dot, t) + testReadDir(sysdir.name, sysdir.files, t) +} + func benchmarkReaddirname(path string, b *testing.B) { var nentries int for i := 0; i < b.N; i++ { @@ -400,6 +459,23 @@ func benchmarkReaddir(path string, b *testing.B) { b.Logf("benchmarkReaddir %q: %d entries", path, nentries) } +func benchmarkReadDir(path string, b *testing.B) { + var nentries int + for i := 0; i < b.N; i++ { + f, err := Open(path) + if err != nil { + b.Fatalf("open %q failed: %v", path, err) + } + fs, err := f.ReadDir(-1) + f.Close() + if err != nil { + b.Fatalf("readdir %q failed: %v", path, err) + } + nentries = len(fs) + } + b.Logf("benchmarkReadDir %q: %d entries", path, nentries) +} + func BenchmarkReaddirname(b *testing.B) { benchmarkReaddirname(".", b) } @@ -408,6 +484,10 @@ func BenchmarkReaddir(b *testing.B) { benchmarkReaddir(".", b) } +func BenchmarkReadDir(b *testing.B) { + benchmarkReadDir(".", b) +} + func benchmarkStat(b *testing.B, path string) { b.ResetTimer() for i := 0; i < b.N; i++ { @@ -547,7 +627,8 @@ func TestReaddirNValues(t *testing.T) { } } - readDirExpect := func(n, want int, wantErr error) { + readdirExpect := func(n, want int, wantErr error) { + t.Helper() fi, err := d.Readdir(n) if err != wantErr { t.Fatalf("Readdir of %d got error %v, want %v", n, err, wantErr) @@ -557,7 +638,19 @@ func TestReaddirNValues(t *testing.T) { } } - readDirNamesExpect := func(n, want int, wantErr error) { + readDirExpect := func(n, want int, wantErr error) { + t.Helper() + de, err := d.ReadDir(n) + if err != wantErr { + t.Fatalf("ReadDir of %d got error %v, want %v", n, err, wantErr) + } + if g, e := len(de), want; g != e { + t.Errorf("ReadDir of %d got %d files, want %d", n, g, e) + } + } + + readdirnamesExpect := func(n, want int, wantErr error) { + t.Helper() fi, err := d.Readdirnames(n) if err != wantErr { t.Fatalf("Readdirnames of %d got error %v, want %v", n, err, wantErr) @@ -567,7 +660,7 @@ func TestReaddirNValues(t *testing.T) { } } - for _, fn := range []func(int, int, error){readDirExpect, readDirNamesExpect} { + for _, fn := range []func(int, int, error){readdirExpect, readdirnamesExpect, readDirExpect} { // Test the slurp case openDir() fn(0, 105, nil) @@ -1573,8 +1666,8 @@ func TestOpenError(t *testing.T) { func TestOpenNoName(t *testing.T) { f, err := Open("") if err == nil { - t.Fatal(`Open("") succeeded`) f.Close() + t.Fatal(`Open("") succeeded`) } } @@ -2434,7 +2527,7 @@ func testDoubleCloseError(t *testing.T, path string) { if err := file.Close(); err == nil { t.Error("second Close did not fail") } else if pe, ok := err.(*PathError); !ok { - t.Errorf("second Close returned unexpected error type %T; expected os.PathError", pe) + t.Errorf("second Close returned unexpected error type %T; expected fs.PathError", pe) } else if pe.Err != ErrClosed { t.Errorf("second Close returned %q, wanted %q", err, ErrClosed) } else { @@ -2579,3 +2672,9 @@ func TestOpenFileKeepsPermissions(t *testing.T) { t.Errorf("Stat after OpenFile is %v, should be writable", fi.Mode()) } } + +func TestDirFS(t *testing.T) { + if err := fstest.TestFS(DirFS("./signal"), "signal.go", "internal/pty/pty.go"); err != nil { + t.Fatal(err) + } +} diff --git a/src/os/os_windows_test.go b/src/os/os_windows_test.go index f03ec750d0..e002774844 100644 --- a/src/os/os_windows_test.go +++ b/src/os/os_windows_test.go @@ -12,6 +12,7 @@ import ( "internal/syscall/windows/registry" "internal/testenv" "io" + "io/fs" "io/ioutil" "os" osexec "os/exec" @@ -164,11 +165,11 @@ func testDirLinks(t *testing.T, tests []dirLinkTest) { t.Errorf("failed to lstat link %v: %v", link, err) continue } - if m := fi2.Mode(); m&os.ModeSymlink == 0 { + if m := fi2.Mode(); m&fs.ModeSymlink == 0 { t.Errorf("%q should be a link, but is not (mode=0x%x)", link, uint32(m)) continue } - if m := fi2.Mode(); m&os.ModeDir != 0 { + if m := fi2.Mode(); m&fs.ModeDir != 0 { t.Errorf("%q should be a link, not a directory (mode=0x%x)", link, uint32(m)) continue } @@ -681,7 +682,7 @@ func TestStatSymlinkLoop(t *testing.T) { defer os.Remove("x") _, err = os.Stat("x") - if _, ok := err.(*os.PathError); !ok { + if _, ok := err.(*fs.PathError); !ok { t.Errorf("expected *PathError, got %T: %v\n", err, err) } } @@ -1291,9 +1292,9 @@ func TestWindowsReadlink(t *testing.T) { // os.Mkdir(os.DevNull) fails. func TestMkdirDevNull(t *testing.T) { err := os.Mkdir(os.DevNull, 777) - oserr, ok := err.(*os.PathError) + oserr, ok := err.(*fs.PathError) if !ok { - t.Fatalf("error (%T) is not *os.PathError", err) + t.Fatalf("error (%T) is not *fs.PathError", err) } errno, ok := oserr.Err.(syscall.Errno) if !ok { diff --git a/src/os/path.go b/src/os/path.go index ba43ea3525..df87887b9b 100644 --- a/src/os/path.go +++ b/src/os/path.go @@ -22,7 +22,7 @@ func MkdirAll(path string, perm FileMode) error { if dir.IsDir() { return nil } - return &PathError{"mkdir", path, syscall.ENOTDIR} + return &PathError{Op: "mkdir", Path: path, Err: syscall.ENOTDIR} } // Slow path: make sure parent exists and then call Mkdir for path. diff --git a/src/os/pipe_test.go b/src/os/pipe_test.go index 429bd813c2..0593efec75 100644 --- a/src/os/pipe_test.go +++ b/src/os/pipe_test.go @@ -13,6 +13,7 @@ import ( "fmt" "internal/testenv" "io" + "io/fs" "io/ioutil" "os" osexec "os/exec" @@ -46,7 +47,7 @@ func TestEPIPE(t *testing.T) { if err == nil { t.Fatal("unexpected success of Write to broken pipe") } - if pe, ok := err.(*os.PathError); ok { + if pe, ok := err.(*fs.PathError); ok { err = pe.Err } if se, ok := err.(*os.SyscallError); ok { @@ -202,10 +203,10 @@ func testClosedPipeRace(t *testing.T, read bool) { } if err == nil { t.Error("I/O on closed pipe unexpectedly succeeded") - } else if pe, ok := err.(*os.PathError); !ok { - t.Errorf("I/O on closed pipe returned unexpected error type %T; expected os.PathError", pe) - } else if pe.Err != os.ErrClosed { - t.Errorf("got error %q but expected %q", pe.Err, os.ErrClosed) + } else if pe, ok := err.(*fs.PathError); !ok { + t.Errorf("I/O on closed pipe returned unexpected error type %T; expected fs.PathError", pe) + } else if pe.Err != fs.ErrClosed { + t.Errorf("got error %q but expected %q", pe.Err, fs.ErrClosed) } else { t.Logf("I/O returned expected error %q", err) } @@ -233,7 +234,7 @@ func TestReadNonblockingFd(t *testing.T) { defer syscall.SetNonblock(fd, false) _, err := os.Stdin.Read(make([]byte, 1)) if err != nil { - if perr, ok := err.(*os.PathError); !ok || perr.Err != syscall.EAGAIN { + if perr, ok := err.(*fs.PathError); !ok || perr.Err != syscall.EAGAIN { t.Fatalf("read on nonblocking stdin got %q, should have gotten EAGAIN", err) } } @@ -308,10 +309,10 @@ func testCloseWithBlockingRead(t *testing.T, r, w *os.File) { if err == nil { t.Error("I/O on closed pipe unexpectedly succeeded") } - if pe, ok := err.(*os.PathError); ok { + if pe, ok := err.(*fs.PathError); ok { err = pe.Err } - if err != io.EOF && err != os.ErrClosed { + if err != io.EOF && err != fs.ErrClosed { t.Errorf("got %v, expected EOF or closed", err) } }(c2) diff --git a/src/os/removeall_at.go b/src/os/removeall_at.go index 37bf1b8f2f..c1a1b726af 100644 --- a/src/os/removeall_at.go +++ b/src/os/removeall_at.go @@ -22,7 +22,7 @@ func removeAll(path string) error { // The rmdir system call does not permit removing ".", // so we don't permit it either. if endsWithDot(path) { - return &PathError{"RemoveAll", path, syscall.EINVAL} + return &PathError{Op: "RemoveAll", Path: path, Err: syscall.EINVAL} } // Simple case: if Remove works, we're done. @@ -70,7 +70,7 @@ func removeAllFrom(parent *File, base string) error { // whose contents need to be removed. // Otherwise just return the error. if err != syscall.EISDIR && err != syscall.EPERM && err != syscall.EACCES { - return &PathError{"unlinkat", base, err} + return &PathError{Op: "unlinkat", Path: base, Err: err} } // Is this a directory we need to recurse into? @@ -80,11 +80,11 @@ func removeAllFrom(parent *File, base string) error { if IsNotExist(statErr) { return nil } - return &PathError{"fstatat", base, statErr} + return &PathError{Op: "fstatat", Path: base, Err: statErr} } if statInfo.Mode&syscall.S_IFMT != syscall.S_IFDIR { // Not a directory; return the error from the unix.Unlinkat. - return &PathError{"unlinkat", base, err} + return &PathError{Op: "unlinkat", Path: base, Err: err} } // Remove the directory's entries. @@ -99,7 +99,7 @@ func removeAllFrom(parent *File, base string) error { if IsNotExist(err) { return nil } - recurseErr = &PathError{"openfdat", base, err} + recurseErr = &PathError{Op: "openfdat", Path: base, Err: err} break } @@ -113,7 +113,7 @@ func removeAllFrom(parent *File, base string) error { if IsNotExist(readErr) { return nil } - return &PathError{"readdirnames", base, readErr} + return &PathError{Op: "readdirnames", Path: base, Err: readErr} } respSize = len(names) @@ -159,7 +159,7 @@ func removeAllFrom(parent *File, base string) error { if recurseErr != nil { return recurseErr } - return &PathError{"unlinkat", base, unlinkError} + return &PathError{Op: "unlinkat", Path: base, Err: unlinkError} } // openFdAt opens path relative to the directory in fd. diff --git a/src/os/removeall_noat.go b/src/os/removeall_noat.go index c1b43e3807..7c888baaa9 100644 --- a/src/os/removeall_noat.go +++ b/src/os/removeall_noat.go @@ -23,7 +23,7 @@ func removeAll(path string) error { // so we don't permit it to remain consistent with the // "at" implementation of RemoveAll. if endsWithDot(path) { - return &PathError{"RemoveAll", path, syscall.EINVAL} + return &PathError{Op: "RemoveAll", Path: path, Err: syscall.EINVAL} } // Simple case: if Remove works, we're done. diff --git a/src/os/removeall_test.go b/src/os/removeall_test.go index 1e5c650fe1..bc9c468ce3 100644 --- a/src/os/removeall_test.go +++ b/src/os/removeall_test.go @@ -359,7 +359,7 @@ func TestRemoveAllButReadOnlyAndPathError(t *testing.T) { t.Errorf("got %q, expected pathErr.path %q", g, w) } } else { - t.Errorf("got %T, expected *os.PathError", err) + t.Errorf("got %T, expected *fs.PathError", err) } for _, dir := range dirs { diff --git a/src/os/signal/signal_cgo_test.go b/src/os/signal/signal_cgo_test.go index a117221400..a8a485613f 100644 --- a/src/os/signal/signal_cgo_test.go +++ b/src/os/signal/signal_cgo_test.go @@ -17,6 +17,7 @@ import ( "context" "fmt" "io" + "io/fs" "os" "os/exec" ptypkg "os/signal/internal/pty" @@ -127,7 +128,7 @@ func TestTerminalSignal(t *testing.T) { if len(line) > 0 || len(handled) > 0 { t.Logf("%q", append(handled, line...)) } - if perr, ok := err.(*os.PathError); ok { + if perr, ok := err.(*fs.PathError); ok { err = perr.Err } // EOF means pty is closed. diff --git a/src/os/stat_plan9.go b/src/os/stat_plan9.go index b43339afa4..57ae6fb0bb 100644 --- a/src/os/stat_plan9.go +++ b/src/os/stat_plan9.go @@ -11,7 +11,7 @@ import ( const bitSize16 = 2 -func fileInfoFromStat(d *syscall.Dir) FileInfo { +func fileInfoFromStat(d *syscall.Dir) *fileStat { fs := &fileStat{ name: d.Name, size: d.Length, @@ -65,7 +65,7 @@ func dirstat(arg interface{}) (*syscall.Dir, error) { } if n < bitSize16 { - return nil, &PathError{"stat", name, err} + return nil, &PathError{Op: "stat", Path: name, Err: err} } // Pull the real size out of the stat message. @@ -76,7 +76,7 @@ func dirstat(arg interface{}) (*syscall.Dir, error) { if size <= n { d, err := syscall.UnmarshalDir(buf[:n]) if err != nil { - return nil, &PathError{"stat", name, err} + return nil, &PathError{Op: "stat", Path: name, Err: err} } return d, nil } @@ -87,7 +87,7 @@ func dirstat(arg interface{}) (*syscall.Dir, error) { err = syscall.ErrBadStat } - return nil, &PathError{"stat", name, err} + return nil, &PathError{Op: "stat", Path: name, Err: err} } // statNolog implements Stat for Plan 9. diff --git a/src/os/stat_test.go b/src/os/stat_test.go index 60f3b4c587..88b789080e 100644 --- a/src/os/stat_test.go +++ b/src/os/stat_test.go @@ -6,6 +6,7 @@ package os_test import ( "internal/testenv" + "io/fs" "io/ioutil" "os" "path/filepath" @@ -14,7 +15,7 @@ import ( ) // testStatAndLstat verifies that all os.Stat, os.Lstat os.File.Stat and os.Readdir work. -func testStatAndLstat(t *testing.T, path string, isLink bool, statCheck, lstatCheck func(*testing.T, string, os.FileInfo)) { +func testStatAndLstat(t *testing.T, path string, isLink bool, statCheck, lstatCheck func(*testing.T, string, fs.FileInfo)) { // test os.Stat sfi, err := os.Stat(path) if err != nil { @@ -70,7 +71,7 @@ func testStatAndLstat(t *testing.T, path string, isLink bool, statCheck, lstatCh } } - // test os.FileInfo returned by os.Readdir + // test fs.FileInfo returned by os.Readdir if len(path) > 0 && os.IsPathSeparator(path[len(path)-1]) { // skip os.Readdir test of directories with slash at the end return @@ -88,7 +89,7 @@ func testStatAndLstat(t *testing.T, path string, isLink bool, statCheck, lstatCh t.Error(err) return } - var lsfi2 os.FileInfo + var lsfi2 fs.FileInfo base := filepath.Base(path) for _, fi2 := range fis { if fi2.Name() == base { @@ -108,34 +109,34 @@ func testStatAndLstat(t *testing.T, path string, isLink bool, statCheck, lstatCh } // testIsDir verifies that fi refers to directory. -func testIsDir(t *testing.T, path string, fi os.FileInfo) { +func testIsDir(t *testing.T, path string, fi fs.FileInfo) { t.Helper() if !fi.IsDir() { t.Errorf("%q should be a directory", path) } - if fi.Mode()&os.ModeSymlink != 0 { + if fi.Mode()&fs.ModeSymlink != 0 { t.Errorf("%q should not be a symlink", path) } } // testIsSymlink verifies that fi refers to symlink. -func testIsSymlink(t *testing.T, path string, fi os.FileInfo) { +func testIsSymlink(t *testing.T, path string, fi fs.FileInfo) { t.Helper() if fi.IsDir() { t.Errorf("%q should not be a directory", path) } - if fi.Mode()&os.ModeSymlink == 0 { + if fi.Mode()&fs.ModeSymlink == 0 { t.Errorf("%q should be a symlink", path) } } // testIsFile verifies that fi refers to file. -func testIsFile(t *testing.T, path string, fi os.FileInfo) { +func testIsFile(t *testing.T, path string, fi fs.FileInfo) { t.Helper() if fi.IsDir() { t.Errorf("%q should not be a directory", path) } - if fi.Mode()&os.ModeSymlink != 0 { + if fi.Mode()&fs.ModeSymlink != 0 { t.Errorf("%q should not be a symlink", path) } } diff --git a/src/os/stat_unix.go b/src/os/stat_unix.go index ef74a43758..66c356fc62 100644 --- a/src/os/stat_unix.go +++ b/src/os/stat_unix.go @@ -19,7 +19,7 @@ func (f *File) Stat() (FileInfo, error) { var fs fileStat err := f.pfd.Fstat(&fs.sys) if err != nil { - return nil, &PathError{"stat", f.name, err} + return nil, &PathError{Op: "stat", Path: f.name, Err: err} } fillFileStatFromSys(&fs, f.name) return &fs, nil @@ -32,7 +32,7 @@ func statNolog(name string) (FileInfo, error) { return syscall.Stat(name, &fs.sys) }) if err != nil { - return nil, &PathError{"stat", name, err} + return nil, &PathError{Op: "stat", Path: name, Err: err} } fillFileStatFromSys(&fs, name) return &fs, nil @@ -45,7 +45,7 @@ func lstatNolog(name string) (FileInfo, error) { return syscall.Lstat(name, &fs.sys) }) if err != nil { - return nil, &PathError{"lstat", name, err} + return nil, &PathError{Op: "lstat", Path: name, Err: err} } fillFileStatFromSys(&fs, name) return &fs, nil diff --git a/src/os/stat_windows.go b/src/os/stat_windows.go index 3e0e0a59ed..da4c49090e 100644 --- a/src/os/stat_windows.go +++ b/src/os/stat_windows.go @@ -27,7 +27,7 @@ func (file *File) Stat() (FileInfo, error) { ft, err := file.pfd.GetFileType() if err != nil { - return nil, &PathError{"GetFileType", file.name, err} + return nil, &PathError{Op: "GetFileType", Path: file.name, Err: err} } switch ft { case syscall.FILE_TYPE_PIPE, syscall.FILE_TYPE_CHAR: @@ -45,14 +45,14 @@ func (file *File) Stat() (FileInfo, error) { // stat implements both Stat and Lstat of a file. func stat(funcname, name string, createFileAttrs uint32) (FileInfo, error) { if len(name) == 0 { - return nil, &PathError{funcname, name, syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)} + return nil, &PathError{Op: funcname, Path: name, Err: syscall.Errno(syscall.ERROR_PATH_NOT_FOUND)} } if isWindowsNulName(name) { return &devNullStat, nil } namep, err := syscall.UTF16PtrFromString(fixLongPath(name)) if err != nil { - return nil, &PathError{funcname, name, err} + return nil, &PathError{Op: funcname, Path: name, Err: err} } // Try GetFileAttributesEx first, because it is faster than CreateFile. @@ -80,7 +80,7 @@ func stat(funcname, name string, createFileAttrs uint32) (FileInfo, error) { var fd syscall.Win32finddata sh, err := syscall.FindFirstFile(namep, &fd) if err != nil { - return nil, &PathError{"FindFirstFile", name, err} + return nil, &PathError{Op: "FindFirstFile", Path: name, Err: err} } syscall.FindClose(sh) fs := newFileStatFromWin32finddata(&fd) @@ -94,7 +94,7 @@ func stat(funcname, name string, createFileAttrs uint32) (FileInfo, error) { h, err := syscall.CreateFile(namep, 0, 0, nil, syscall.OPEN_EXISTING, createFileAttrs, 0) if err != nil { - return nil, &PathError{"CreateFile", name, err} + return nil, &PathError{Op: "CreateFile", Path: name, Err: err} } defer syscall.CloseHandle(h) diff --git a/src/os/timeout_test.go b/src/os/timeout_test.go index 99b94c2e4c..d848e41642 100644 --- a/src/os/timeout_test.go +++ b/src/os/timeout_test.go @@ -429,7 +429,7 @@ func testVariousDeadlines(t *testing.T) { if err := r.SetDeadline(t0.Add(timeout)); err != nil { t.Error(err) } - n, err := io.Copy(ioutil.Discard, r) + n, err := io.Copy(io.Discard, r) dt := time.Since(t0) r.Close() actvch <- result{n, err, dt} @@ -565,7 +565,7 @@ func TestRacyWrite(t *testing.T) { var wg sync.WaitGroup defer wg.Wait() - go io.Copy(ioutil.Discard, r) + go io.Copy(io.Discard, r) w.SetWriteDeadline(time.Now().Add(time.Millisecond)) for i := 0; i < 10; i++ { diff --git a/src/os/types.go b/src/os/types.go index 4b6c084838..d8edd98b68 100644 --- a/src/os/types.go +++ b/src/os/types.go @@ -5,8 +5,8 @@ package os import ( + "io/fs" "syscall" - "time" ) // Getpagesize returns the underlying system's memory page size. @@ -18,21 +18,14 @@ type File struct { } // 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 for regular files; system-dependent for others - Mode() FileMode // file mode bits - ModTime() time.Time // modification time - IsDir() bool // abbreviation for Mode().IsDir() - Sys() interface{} // underlying data source (can return nil) -} +type FileInfo = fs.FileInfo // 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 +type FileMode = fs.FileMode // The defined file mode bits are the most significant bits of the FileMode. // The nine least-significant bits are the standard Unix rwxrwxrwx permissions. @@ -42,69 +35,26 @@ type FileMode uint32 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; Plan 9 only - ModeSymlink // L: symbolic link - ModeDevice // D: device file - ModeNamedPipe // p: named pipe (FIFO) - ModeSocket // S: Unix domain socket - ModeSetuid // u: setuid - ModeSetgid // g: setgid - ModeCharDevice // c: Unix character device, when ModeDevice is set - ModeSticky // t: sticky - ModeIrregular // ?: non-regular file; nothing else is known about this file + ModeDir = fs.ModeDir // d: is a directory + ModeAppend = fs.ModeAppend // a: append-only + ModeExclusive = fs.ModeExclusive // l: exclusive use + ModeTemporary = fs.ModeTemporary // T: temporary file; Plan 9 only + ModeSymlink = fs.ModeSymlink // L: symbolic link + ModeDevice = fs.ModeDevice // D: device file + ModeNamedPipe = fs.ModeNamedPipe // p: named pipe (FIFO) + ModeSocket = fs.ModeSocket // S: Unix domain socket + ModeSetuid = fs.ModeSetuid // u: setuid + ModeSetgid = fs.ModeSetgid // g: setgid + ModeCharDevice = fs.ModeCharDevice // c: Unix character device, when ModeDevice is set + ModeSticky = fs.ModeSticky // t: sticky + ModeIrregular = fs.ModeIrregular // ?: non-regular file; nothing else is known about this file // Mask for the type bits. For regular files, none will be set. - ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeCharDevice | ModeIrregular + ModeType = fs.ModeType - ModePerm FileMode = 0777 // Unix permission bits + ModePerm = fs.ModePerm // Unix permission bits, 0o777 ) -func (m FileMode) String() string { - const str = "dalTLDpSugct?" - var buf [32]byte // Mode is uint32. - 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]) -} - -// 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 -} - -// IsRegular reports whether m describes a regular file. -// That is, it tests that no mode type bits are set. -func (m FileMode) IsRegular() bool { - return m&ModeType == 0 -} - -// Perm returns the Unix permission bits in m. -func (m FileMode) Perm() FileMode { - return m & ModePerm -} - func (fs *fileStat) Name() string { return fs.name } func (fs *fileStat) IsDir() bool { return fs.Mode().IsDir() } diff --git a/src/os/types_windows.go b/src/os/types_windows.go index 3d1a6674b1..59bf5ca381 100644 --- a/src/os/types_windows.go +++ b/src/os/types_windows.go @@ -45,7 +45,7 @@ func newFileStatFromGetFileInformationByHandle(path string, h syscall.Handle) (f var d syscall.ByHandleFileInformation err = syscall.GetFileInformationByHandle(h, &d) if err != nil { - return nil, &PathError{"GetFileInformationByHandle", path, err} + return nil, &PathError{Op: "GetFileInformationByHandle", Path: path, Err: err} } var ti windows.FILE_ATTRIBUTE_TAG_INFO @@ -58,7 +58,7 @@ func newFileStatFromGetFileInformationByHandle(path string, h syscall.Handle) (f // instance to indicate no symlinks are possible. ti.ReparseTag = 0 } else { - return nil, &PathError{"GetFileInformationByHandleEx", path, err} + return nil, &PathError{Op: "GetFileInformationByHandleEx", Path: path, Err: err} } } @@ -197,7 +197,7 @@ func (fs *fileStat) saveInfoFromPath(path string) error { var err error fs.path, err = syscall.FullPath(fs.path) if err != nil { - return &PathError{"FullPath", path, err} + return &PathError{Op: "FullPath", Path: path, Err: err} } } fs.name = basename(path) |