From 641a7e5cb9eb41c2bbc1448b49c4f8e5c469dc42 Mon Sep 17 00:00:00 2001 From: Dan Kegel Date: Sat, 8 Jan 2022 12:28:32 -0800 Subject: [PATCH] os: implement readdir for darwin and linux readdir is disabled on linux for 386 and arm until syscall.seek is implemented there. windows is hard, so leaving that for later. File src/os/dir_other_go115.go can be deleted when we drop support for go 1.15. Also adds TestReadNonDir, which was helpful while debugging. --- src/os/dir.go | 127 +++++++++++++++++++ src/os/dir_darwin.go | 156 +++++++++++++++++++++++ src/os/dir_other.go | 18 +++ src/os/dir_other_go115.go | 11 ++ src/os/dir_test.go | 181 ++++++++++++++++++++++++++ src/os/dir_unix.go | 195 +++++++++++++++++++++++++++++ src/os/dirent_linux.go | 53 ++++++++ src/os/endian_little.go | 9 ++ src/os/file.go | 21 +--- src/os/file_anyos.go | 6 +- src/os/file_go_116.go | 9 -- src/os/file_other.go | 19 ++- src/os/file_unix.go | 62 +++++++-- src/os/file_windows.go | 25 ++-- src/os/os_anyos_test.go | 11 ++ src/os/types.go | 21 +--- src/os/types_anyos.go | 25 ++++ src/syscall/syscall_libc.go | 12 ++ src/syscall/syscall_libc_darwin.go | 53 +++++++- src/syscall/syscall_libc_wasi.go | 25 ++++ 20 files changed, 972 insertions(+), 67 deletions(-) create mode 100644 src/os/dir.go create mode 100644 src/os/dir_darwin.go create mode 100644 src/os/dir_other.go create mode 100644 src/os/dir_other_go115.go create mode 100644 src/os/dir_test.go create mode 100644 src/os/dir_unix.go create mode 100644 src/os/dirent_linux.go create mode 100644 src/os/endian_little.go create mode 100644 src/os/types_anyos.go diff --git a/src/os/dir.go b/src/os/dir.go new file mode 100644 index 00000000..72bd2c26 --- /dev/null +++ b/src/os/dir.go @@ -0,0 +1,127 @@ +// +build go1.16 + +// 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. + +package os + +import ( + "io/fs" + "sort" +) + +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 +// further FileInfos. +// +// If n > 0, Readdir returns at most n FileInfo structures. In this case, if +// Readdir returns an empty slice, it will return a non-nil error +// explaining why. At the end of a directory, the error is io.EOF. +// +// If n <= 0, Readdir returns all the FileInfo from the directory in +// a single slice. In this case, if Readdir succeeds (reads all +// the way to the end of the directory), it returns the slice and a +// 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 + } + _, _, infos, err := f.readdir(n, readdirFileInfo) + if infos == nil { + // Readdir has historically always returned a non-nil empty slice, never nil, + // even on error (except misuse with nil receiver above). + // Keep it that way to avoid breaking overly sensitive callers. + infos = []FileInfo{} + } + return infos, err +} + +// Readdirnames reads the contents of the directory associated with file +// and returns a slice of up to n names of files in the directory, +// in directory order. Subsequent calls on the same file will yield +// further names. +// +// If n > 0, Readdirnames returns at most n names. In this case, if +// Readdirnames returns an empty slice, it will return a non-nil error +// explaining why. At the end of a directory, the error is io.EOF. +// +// If n <= 0, Readdirnames returns all the names from the directory in +// a single slice. In this case, if Readdirnames succeeds (reads all +// the way to the end of the directory), it returns the slice and a +// nil error. If it encounters an error before the end of the +// directory, Readdirnames returns the names read until that point and +// a non-nil error. +func (f *File) Readdirnames(n int) (names []string, err error) { + if f == nil { + return nil, ErrInvalid + } + names, _, _, err = f.readdir(n, readdirName) + if names == nil { + // Readdirnames has historically always returned a non-nil empty slice, never nil, + // even on error (except misuse with nil receiver above). + // Keep it that way to avoid breaking overly sensitive callers. + names = []string{} + } + 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) + if dirents == nil { + // Match Readdir and Readdirnames: don't return nil slices. + dirents = []DirEntry{} + } + 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 + +// ReadDir reads the named directory, +// returning all its directory entries sorted by filename. +// If an error occurs reading the directory, +// ReadDir returns the entries it was able to read before the error, +// along with the error. +func ReadDir(name string) ([]DirEntry, error) { + f, err := Open(name) + if err != nil { + return nil, err + } + defer f.Close() + + dirs, err := f.ReadDir(-1) + sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() }) + return dirs, err +} diff --git a/src/os/dir_darwin.go b/src/os/dir_darwin.go new file mode 100644 index 00000000..0578d0b6 --- /dev/null +++ b/src/os/dir_darwin.go @@ -0,0 +1,156 @@ +// 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. + +package os + +import ( + "io" + "runtime" + "syscall" + "unsafe" +) + +// Auxiliary information if the File describes a directory +type dirInfo struct { + dir uintptr // Pointer to DIR structure from dirent.h +} + +func (d *dirInfo) close() { + if d.dir == 0 { + return + } + closedir(d.dir) + d.dir = 0 +} + +func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) { + if f.dirinfo == nil { + dir, call, errno := darwinOpenDir(syscallFd(f.handle.(unixFileHandle))) + if errno != nil { + return nil, nil, nil, &PathError{Op: call, Path: f.name, Err: errno} + } + f.dirinfo = &dirInfo{ + dir: dir, + } + } + d := f.dirinfo + + size := n + if size <= 0 { + size = 100 + n = -1 + } + + var dirent syscall.Dirent + var entptr *syscall.Dirent + 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, dirents, infos, &PathError{Op: "readdir", Path: f.name, Err: errno} + } + if entptr == nil { // EOF + break + } + if dirent.Ino == 0 { + continue + } + name := (*[len(syscall.Dirent{}.Name)]byte)(unsafe.Pointer(&dirent.Name))[:] + 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 + } + 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)+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 ^FileMode(0) +} + +// darwinOpenDir returns a pointer to a DIR structure suitable for +// ReadDir. In case of an error, the name of the failed +// syscall is returned along with a syscall.Errno. +// Borrowed from upstream's internal/poll/fd_opendir_darwin.go +func darwinOpenDir(fd syscallFd) (uintptr, string, error) { + // fdopendir(3) takes control of the file descriptor, + // so use a dup. + fd2, err := syscall.Dup(fd) + if err != nil { + return 0, "dup", err + } + var dir uintptr + for { + dir, err = syscall.Fdopendir(fd2) + if err != syscall.EINTR { + break + } + } + if err != nil { + syscall.Close(fd2) + return 0, "fdopendir", err + } + return dir, "", nil +} + +// Implemented in syscall/syscall_darwin.go. + +//go:linkname closedir syscall.closedir +func closedir(dir uintptr) (err error) + +//go:linkname readdir_r syscall.readdir_r +func readdir_r(dir uintptr, entry *syscall.Dirent, result **syscall.Dirent) (res syscall.Errno) diff --git a/src/os/dir_other.go b/src/os/dir_other.go new file mode 100644 index 00000000..fea6fe06 --- /dev/null +++ b/src/os/dir_other.go @@ -0,0 +1,18 @@ +// +build go1.16,baremetal go1.16,js go1.16,wasi go1.16,windows + +// 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. + +package os + +import ( + "syscall" +) + +type dirInfo struct { +} + +func (f *File) readdir(n int, mode readdirMode) (names []string, dirents []DirEntry, infos []FileInfo, err error) { + return nil, nil, nil, &PathError{Op: "readdir unimplemented", Err: syscall.ENOTDIR} +} diff --git a/src/os/dir_other_go115.go b/src/os/dir_other_go115.go new file mode 100644 index 00000000..8bc4036d --- /dev/null +++ b/src/os/dir_other_go115.go @@ -0,0 +1,11 @@ +// +build !go1.16 + +// 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. + +package os + +func (f *File) Readdirnames(n int) (names []string, err error) { + return nil, ErrInvalid +} diff --git a/src/os/dir_test.go b/src/os/dir_test.go new file mode 100644 index 00000000..9ff799ca --- /dev/null +++ b/src/os/dir_test.go @@ -0,0 +1,181 @@ +// +build darwin linux,!baremetal,!js,!wasi,!386,!arm + +package os_test + +import ( + . "os" + "testing" +) + +func testReaddirnames(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.Readdirnames(-1) + if err2 != nil { + t.Fatalf("Readdirnames %q failed: %v", dir, err2) + } + for _, m := range contents { + found := false + for _, n := range s { + if n == "." || n == ".." { + t.Errorf("got %q in directory", n) + } + if !equal(m, n) { + continue + } + if found { + t.Error("present twice:", m) + } + found = true + } + if !found { + t.Error("could not find", m) + } + } + if s == nil { + t.Error("Readdirnames returned nil instead of empty slice") + } +} + +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.Name()) + } + if !equal(m, n.Name()) { + continue + } + if found { + t.Error("present twice:", m) + } + found = true + } + if !found { + t.Error("could not find", m) + } + } + if s == nil { + t.Error("Readdir returned nil instead of empty slice") + } +} + +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 { + t.Error("could not find", m) + } + } + if s == nil { + t.Error("ReadDir returned nil instead of empty slice") + } +} + +func TestFileReaddirnames(t *testing.T) { + testReaddirnames(".", dot, t) + testReaddirnames(TempDir(), nil, t) +} + +func TestFileReaddir(t *testing.T) { + testReaddir(".", dot, t) + testReaddir(TempDir(), nil, t) +} + +func TestFileReadDir(t *testing.T) { + testReadDir(".", dot, t) + testReadDir(TempDir(), nil, t) +} + +func TestReadDir(t *testing.T) { + dirname := "rumpelstilzchen" + _, err := ReadDir(dirname) + if err == nil { + t.Fatalf("ReadDir %s: error expected, none found", dirname) + } + + dirname = "testdata" + list, err := ReadDir(dirname) + if err != nil { + t.Fatalf("ReadDir %s: %v", dirname, err) + } + + foundFile := false + foundSubDir := false + for _, dir := range list { + switch { + case !dir.IsDir() && dir.Name() == "hello": + foundFile = true + case dir.IsDir() && dir.Name() == "issue37161": + foundSubDir = true + } + } + if !foundFile { + t.Fatalf("ReadDir %s: hello file not found", dirname) + } + if !foundSubDir { + t.Fatalf("ReadDir %s: testdata directory not found", dirname) + } +} + +// TestReadNonDir just verifies that opening a non-directory returns an error. +func TestReadNonDir(t *testing.T) { + // Use filename of this source file; it is known to exist, and go tests run in source tree. + dirname := "dir_test.go" + _, err := ReadDir(dirname) + if err == nil { + t.Fatalf("ReadDir %s: error on non-dir expected, none found", dirname) + } +} diff --git a/src/os/dir_unix.go b/src/os/dir_unix.go new file mode 100644 index 00000000..9bcecb37 --- /dev/null +++ b/src/os/dir_unix.go @@ -0,0 +1,195 @@ +// 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 linux,!baremetal,!wasi + +package os + +import ( + "io" + "sync" + "syscall" + "unsafe" +) + +// Auxiliary information if the File describes a directory +type dirInfo struct { + buf *[]byte // buffer for directory I/O + nbuf int // length of buf; return value from Getdirentries + bufp int // location of next record in buf. +} + +const ( + // More than 5760 to work around https://golang.org/issue/24015. + blockSize = 8192 +) + +var dirBufPool = sync.Pool{ + New: func() interface{} { + // The buffer must be at least a block long. + buf := make([]byte, blockSize) + return &buf + }, +} + +func (d *dirInfo) close() { + if d.buf != nil { + dirBufPool.Put(d.buf) + d.buf = nil + } +} + +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) + f.dirinfo.buf = dirBufPool.Get().(*[]byte) + } + d := f.dirinfo + + // Change the meaning of n for the implementation below. + // + // The n above was for the public interface of "if n <= 0, + // Readdir returns all the FileInfo from the directory in a + // single slice". + // + // But below, we use only negative to mean looping until the + // end and positive to mean bounded, with positive + // terminating at 0. + if n == 0 { + n = -1 + } + + for n != 0 { + // Refill the buffer if necessary + if d.bufp >= d.nbuf { + d.bufp = 0 + var errno error + d.nbuf, errno = syscall.ReadDirent(syscallFd(f.handle.(unixFileHandle)), *d.buf) + if d.nbuf < 0 { + errno = handleSyscallError(errno) + } + if errno != nil { + return names, dirents, infos, &PathError{Op: "readdirent", Path: f.name, Err: errno} + } + if d.nbuf <= 0 { + break // EOF + } + } + + // Drain the buffer + 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 + } + if n > 0 { // see 'n == 0' comment above + 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)+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") + } +} diff --git a/src/os/dirent_linux.go b/src/os/dirent_linux.go new file mode 100644 index 00000000..564703e8 --- /dev/null +++ b/src/os/dirent_linux.go @@ -0,0 +1,53 @@ +// +build !baremetal,!js,!wasi + +// 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/endian_little.go b/src/os/endian_little.go new file mode 100644 index 00000000..40ce292c --- /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. + +// TODO: exclude big endian targets here and include them in endian_big.go, once tinygo supports one + +package os + +const isBigEndian = false diff --git a/src/os/file.go b/src/os/file.go index 3589c377..10f77f45 100644 --- a/src/os/file.go +++ b/src/os/file.go @@ -24,6 +24,9 @@ const ( SEEK_END int = io.SeekEnd ) +// lstat is overridden in tests. +var lstat = Lstat + // Mkdir creates a directory. If the operation fails, it will return an error of // type *PathError. func Mkdir(path string, perm FileMode) error { @@ -66,12 +69,6 @@ func RemoveAll(path string) error { return ErrNotImplemented } -// File represents an open file descriptor. -type File struct { - handle FileHandle - name string -} - // Name returns the name of the file with which it was opened. func (f *File) Name() string { return f.name @@ -88,7 +85,7 @@ func OpenFile(name string, flag int, perm FileMode) (*File, error) { if err != nil { return nil, &PathError{"open", name, err} } - return &File{name: name, handle: handle}, nil + return NewFile(handle, name), nil } // Open opens the file named for reading. @@ -170,16 +167,6 @@ func (f *File) Close() (err error) { return } -// Readdir is a stub, not yet implemented -func (f *File) Readdir(n int) ([]FileInfo, error) { - return nil, &PathError{"readdir", f.name, ErrNotImplemented} -} - -// Readdirnames is a stub, not yet implemented -func (f *File) Readdirnames(n int) (names []string, err error) { - return nil, &PathError{"readdirnames", f.name, ErrNotImplemented} -} - // Seek sets the offset for the next Read or Write on file to offset, interpreted // according to whence: 0 means relative to the origin of the file, 1 means // relative to the current offset, and 2 means relative to the end. diff --git a/src/os/file_anyos.go b/src/os/file_anyos.go index ea48e9af..2ec4ebd1 100644 --- a/src/os/file_anyos.go +++ b/src/os/file_anyos.go @@ -21,9 +21,9 @@ func init() { // Stdin, Stdout, and Stderr are open Files pointing to the standard input, // standard output, and standard error file descriptors. var ( - Stdin = &File{unixFileHandle(syscall.Stdin), "/dev/stdin"} - Stdout = &File{unixFileHandle(syscall.Stdout), "/dev/stdout"} - Stderr = &File{unixFileHandle(syscall.Stderr), "/dev/stderr"} + Stdin = NewFile(unixFileHandle(syscall.Stdin), "/dev/stdin") + Stdout = NewFile(unixFileHandle(syscall.Stdout), "/dev/stdout") + Stderr = NewFile(unixFileHandle(syscall.Stderr), "/dev/stderr") ) const DevNull = "/dev/null" diff --git a/src/os/file_go_116.go b/src/os/file_go_116.go index 7049d37e..807c2598 100644 --- a/src/os/file_go_116.go +++ b/src/os/file_go_116.go @@ -8,19 +8,10 @@ import ( ) type ( - DirEntry = fs.DirEntry FileMode = fs.FileMode FileInfo = fs.FileInfo ) -func (f *File) ReadDir(n int) ([]DirEntry, error) { - return nil, &PathError{"ReadDir", f.name, ErrNotImplemented} -} - -func ReadDir(name string) ([]DirEntry, error) { - return nil, &PathError{"ReadDir", name, ErrNotImplemented} -} - // The followings are copied from Go 1.16 official implementation: // https://github.com/golang/go/blob/go1.16/src/os/file.go diff --git a/src/os/file_other.go b/src/os/file_other.go index 7a0ebb89..b97d249d 100644 --- a/src/os/file_other.go +++ b/src/os/file_other.go @@ -9,9 +9,9 @@ import ( // Stdin, Stdout, and Stderr are open Files pointing to the standard input, // standard output, and standard error file descriptors. var ( - Stdin = &File{stdioFileHandle(0), "/dev/stdin"} - Stdout = &File{stdioFileHandle(1), "/dev/stdout"} - Stderr = &File{stdioFileHandle(2), "/dev/stderr"} + Stdin = NewFile(stdioFileHandle(0), "/dev/stdin") + Stdout = NewFile(stdioFileHandle(1), "/dev/stdout") + Stderr = NewFile(stdioFileHandle(2), "/dev/stderr") ) // isOS indicates whether we're running on a real operating system with @@ -22,6 +22,19 @@ const isOS = false // number. It implements the FileHandle interface. type stdioFileHandle uint8 +// file is the real representation of *File. +// The extra level of indirection ensures that no clients of os +// can overwrite this data, which could cause the finalizer +// to close the wrong file descriptor. +type file struct { + handle FileHandle + name string +} + +func NewFile(fd FileHandle, name string) *File { + return &File{&file{fd, name}} +} + // Read is unsupported on this system. func (f stdioFileHandle) Read(b []byte) (n int, err error) { return 0, ErrUnsupported diff --git a/src/os/file_unix.go b/src/os/file_unix.go index 90c0c7b2..76cc3ca7 100644 --- a/src/os/file_unix.go +++ b/src/os/file_unix.go @@ -27,20 +27,28 @@ func rename(oldname, newname string) error { return nil } +// file is the real representation of *File. +// The extra level of indirection ensures that no clients of os +// can overwrite this data, which could cause the finalizer +// to close the wrong file descriptor. +type file struct { + handle FileHandle + name string + dirinfo *dirInfo // nil unless directory being read +} + +func NewFile(fd FileHandle, name string) *File { + return &File{&file{fd, name, nil}} +} + func Pipe() (r *File, w *File, err error) { var p [2]int err = handleSyscallError(syscall.Pipe2(p[:], syscall.O_CLOEXEC)) if err != nil { return } - r = &File{ - handle: unixFileHandle(p[0]), - name: "|0", - } - w = &File{ - handle: unixFileHandle(p[1]), - name: "|1", - } + r = NewFile(unixFileHandle(p[0]), "|0") + w = NewFile(unixFileHandle(p[1]), "|1") return } @@ -108,3 +116,41 @@ func (f unixFileHandle) Seek(offset int64, whence int) (int64, error) { newoffset, err := syscall.Seek(syscallFd(f), offset, whence) return newoffset, handleSyscallError(err) } + +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 3e1c63b9..ef5db96b 100644 --- a/src/os/file_windows.go +++ b/src/os/file_windows.go @@ -33,20 +33,29 @@ func rename(oldname, newname string) error { return nil } +type file struct { + handle FileHandle + name string +} + +func NewFile(fd FileHandle, name string) *File { + return &File{&file{fd, name}} +} + func Pipe() (r *File, w *File, err error) { var p [2]syscall.Handle e := handleSyscallError(syscall.Pipe(p[:])) if e != nil { return nil, nil, err } - r = &File{ - handle: unixFileHandle(p[0]), - name: "|0", - } - w = &File{ - handle: unixFileHandle(p[1]), - name: "|1", - } + r = NewFile( + unixFileHandle(p[0]), + "|0", + ) + w = NewFile( + unixFileHandle(p[1]), + "|1", + ) return } diff --git a/src/os/os_anyos_test.go b/src/os/os_anyos_test.go index 9a587968..ed0d9daf 100644 --- a/src/os/os_anyos_test.go +++ b/src/os/os_anyos_test.go @@ -13,6 +13,17 @@ import ( "time" ) +var dot = []string{ + "dir.go", + "env.go", + "errors.go", + "file.go", + "os_test.go", + "types.go", + "stat_darwin.go", + "stat_linux.go", +} + func randomName() string { // fastrand() does not seem available here, so fake it ns := time.Now().Nanosecond() diff --git a/src/os/types.go b/src/os/types.go index 4cf02ff3..7e3345a2 100644 --- a/src/os/types.go +++ b/src/os/types.go @@ -1,25 +1,10 @@ -// +build !baremetal,!js - // 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. package os -func (fs *fileStat) Name() string { return fs.name } -func (fs *fileStat) IsDir() bool { return fs.Mode().IsDir() } - -// SameFile reports whether fi1 and fi2 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. -// SameFile only applies to results returned by this package's Stat. -// It returns false in other cases. -func SameFile(fi1, fi2 FileInfo) bool { - fs1, ok1 := fi1.(*fileStat) - fs2, ok2 := fi2.(*fileStat) - if !ok1 || !ok2 { - return false - } - return sameFile(fs1, fs2) +// File represents an open file descriptor. +type File struct { + *file // os specific } diff --git a/src/os/types_anyos.go b/src/os/types_anyos.go new file mode 100644 index 00000000..4cf02ff3 --- /dev/null +++ b/src/os/types_anyos.go @@ -0,0 +1,25 @@ +// +build !baremetal,!js + +// 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. + +package os + +func (fs *fileStat) Name() string { return fs.name } +func (fs *fileStat) IsDir() bool { return fs.Mode().IsDir() } + +// SameFile reports whether fi1 and fi2 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. +// SameFile only applies to results returned by this package's Stat. +// It returns false in other cases. +func SameFile(fi1, fi2 FileInfo) bool { + fs1, ok1 := fi1.(*fileStat) + fs2, ok2 := fi2.(*fileStat) + if !ok1 || !ok2 { + return false + } + return sameFile(fs1, fs2) +} diff --git a/src/syscall/syscall_libc.go b/src/syscall/syscall_libc.go index 27c95886..496044f2 100644 --- a/src/syscall/syscall_libc.go +++ b/src/syscall/syscall_libc.go @@ -19,6 +19,14 @@ func Close(fd int) (err error) { return } +func Dup(fd int) (fd2 int, err error) { + fd2 = int(libc_dup(int32(fd))) + if fd2 < 0 { + err = getErrno() + } + return +} + func Write(fd int, p []byte) (n int, err error) { buf, count := splitSlice(p) n = libc_write(int32(fd), buf, uint(count)) @@ -297,6 +305,10 @@ func libc_open(pathname *byte, flags int32, mode uint32) int32 //export close func libc_close(fd int32) int32 +// int dup(int fd) +//export dup +func libc_dup(fd int32) int32 + // void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); //export mmap func libc_mmap(addr unsafe.Pointer, length uintptr, prot, flags, fd int32, offset uintptr) unsafe.Pointer diff --git a/src/syscall/syscall_libc_darwin.go b/src/syscall/syscall_libc_darwin.go index c19ddba4..93201fc7 100644 --- a/src/syscall/syscall_libc_darwin.go +++ b/src/syscall/syscall_libc_darwin.go @@ -40,6 +40,19 @@ func (e Errno) Is(target error) bool { return false } +// Source: upstream zerrors_darwin_amd64.go +const ( + DT_BLK = 0x6 + DT_CHR = 0x2 + DT_DIR = 0x4 + DT_FIFO = 0x1 + DT_LNK = 0xa + DT_REG = 0x8 + DT_SOCK = 0xc + DT_UNKNOWN = 0x0 + DT_WHT = 0xe +) + // Source: https://opensource.apple.com/source/xnu/xnu-7195.81.3/bsd/sys/errno.h.auto.html const ( EPERM Errno = 1 @@ -107,6 +120,17 @@ type Timespec struct { Nsec int64 } +// Source: upstream ztypes_darwin_amd64.go +type Dirent struct { + Ino uint64 + Seekoff uint64 + Reclen uint16 + Namlen uint16 + Type uint8 + Name [1024]int8 + Pad_cgo_0 [3]byte +} + // Go chose Linux's field names for Stat_t, see https://github.com/golang/go/issues/31735 type Stat_t struct { Dev int32 @@ -190,9 +214,36 @@ func Lstat(path string, p *Stat_t) (err error) { return } -// The odd $INODE64 suffix is an Apple compatibility feature, see https://assert.cc/posts/darwin_use_64_bit_inode_vs_ctypes/ +func Fdopendir(fd int) (dir uintptr, err error) { + r0 := libc_fdopendir(int32(fd)) + dir = uintptr(r0) + if dir == 0 { + err = getErrno() + } + return +} + +func readdir_r(dir uintptr, entry *Dirent, result **Dirent) (err error) { + e1 := libc_readdir_r(unsafe.Pointer(dir), unsafe.Pointer(entry), unsafe.Pointer(result)) + if e1 != 0 { + err = getErrno() + } + return +} + +// The odd $INODE64 suffix is an Apple compatibility feature, see +// https://assert.cc/posts/darwin_use_64_bit_inode_vs_ctypes/ +// and https://github.com/golang/go/issues/35269 // Without it, you get the old, smaller struct stat from mac os 10.2 or so. +// struct DIR * buf fdopendir(int fd); +//export fdopendir$INODE64 +func libc_fdopendir(fd int32) unsafe.Pointer + +// int readdir_r(struct DIR * buf, struct dirent *entry, struct dirent **result); +//export readdir_r$INODE64 +func libc_readdir_r(unsafe.Pointer, unsafe.Pointer, unsafe.Pointer) int32 + // int stat(const char *path, struct stat * buf); //export stat$INODE64 func libc_stat(pathname *byte, ptr unsafe.Pointer) int32 diff --git a/src/syscall/syscall_libc_wasi.go b/src/syscall/syscall_libc_wasi.go index 92b4601a..ef3a7fa2 100644 --- a/src/syscall/syscall_libc_wasi.go +++ b/src/syscall/syscall_libc_wasi.go @@ -202,6 +202,31 @@ const ( S_IXUSR = 0x40 ) +// dummy +const ( + DT_BLK = 0x6 + DT_CHR = 0x2 + DT_DIR = 0x4 + DT_FIFO = 0x1 + DT_LNK = 0xa + DT_REG = 0x8 + DT_SOCK = 0xc + DT_UNKNOWN = 0x0 + DT_WHT = 0xe +) + +// dummy +type Dirent struct { + Ino uint64 + Reclen uint16 + Type uint8 + Name [1024]int8 +} + +func ReadDirent(fd int, buf []byte) (n int, err error) { + return -1, ENOSYS +} + func Stat(path string, p *Stat_t) (err error) { data := cstring(path) n := libc_stat(&data[0], unsafe.Pointer(p))