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.
Этот коммит содержится в:
Dan Kegel 2022-01-08 12:28:32 -08:00 коммит произвёл Ron Evans
родитель 3fa7d6cc40
коммит 641a7e5cb9
20 изменённых файлов: 972 добавлений и 67 удалений

127
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
}

156
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)

18
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}
}

11
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
}

181
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)
}
}

195
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")
}
}

53
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
}

9
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

Просмотреть файл

@ -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.

Просмотреть файл

@ -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"

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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
}

Просмотреть файл

@ -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
}

Просмотреть файл

@ -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()

Просмотреть файл

@ -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
}

25
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)
}

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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))