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.
Этот коммит содержится в:
родитель
3fa7d6cc40
коммит
641a7e5cb9
20 изменённых файлов: 972 добавлений и 67 удалений
127
src/os/dir.go
Обычный файл
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
Обычный файл
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
Обычный файл
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
Обычный файл
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
Обычный файл
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
Обычный файл
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
Обычный файл
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
Обычный файл
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
Обычный файл
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))
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче