// Copyright 2013 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.
// File descriptor support for Native Client.
// We want to provide access to a broader range of (simulated) files than
// Native Client allows, so we maintain our own file descriptor table exposed
// to higher-level packages.
package syscall
import (
"io"
"sync"
)
// files is the table indexed by a file descriptor.
var files struct {
sync.RWMutex
tab []*file
}
// A file is an open file, something with a file descriptor.
// A particular *file may appear in files multiple times, due to use of Dup or Dup2.
type file struct {
fdref int // uses in files.tab
impl fileImpl // underlying implementation
}
// A fileImpl is the implementation of something that can be a file.
type fileImpl interface {
// Standard operations.
// These can be called concurrently from multiple goroutines.
stat(*Stat_t) error
read([]byte) (int, error)
write([]byte) (int, error)
seek(int64, int) (int64, error)
pread([]byte, int64) (int, error)
pwrite([]byte, int64) (int, error)
// Close is called when the last reference to a *file is removed
// from the file descriptor table. It may be called concurrently
// with active operations such as blocked read or write calls.
close() error
}
// newFD adds impl to the file descriptor table,
// returning the new file descriptor.
// Like Unix, it uses the lowest available descriptor.
func newFD(impl fileImpl) int {
files.Lock()
defer files.Unlock()
f := &file{impl: impl, fdref: 1}
for fd, oldf := range files.tab {
if oldf == nil {
files.tab[fd] = f
return fd
}
}
fd := len(files.tab)
files.tab = append(files.tab, f)
return fd
}
// Install Native Client stdin, stdout, stderr.
func init() {
newFD(&naclFile{naclFD: 0})
newFD(&naclFile{naclFD: 1})
newFD(&naclFile{naclFD: 2})
}
// fdToFile retrieves the *file corresponding to a file descriptor.
func fdToFile(fd int) (*file, error) {
files.Lock()
defer files.Unlock()
if fd < 0 || fd >= len(files.tab) || files.tab[fd] == nil {
return nil, EBADF
}
return files.tab[fd], nil
}
func Close(fd int) error {
files.Lock()
if fd < 0 || fd >= len(files.tab) || files.tab[fd] == nil {
files.Unlock()
return EBADF
}
f := files.tab[fd]
files.tab[fd] = nil
f.fdref--
fdref := f.fdref
files.Unlock()
if fdref > 0 {
return nil
}
return f.impl.close()
}
func CloseOnExec(fd int) {
// nothing to do - no exec
}
func Dup(fd int) (int, error) {
files.Lock()
defer files.Unlock()
if fd < 0 || fd >= len(files.tab) || files.tab[fd] == nil {
return -1, EBADF
}
f := files.tab[fd]
f.fdref++
for newfd, oldf := range files.tab {
if oldf == nil {
files.tab[newfd] = f
return newfd, nil
}
}
newfd := len(files.tab)
files.tab = append(files.tab, f)
return newfd, nil
}
func Dup2(fd, newfd int) error {
files.Lock()
defer files.Unlock()
if fd < 0 || fd >= len(files.tab) || files.tab[fd] == nil || newfd < 0 || newfd >= len(files.tab)+100 {
files.Unlock()
return EBADF
}
f := files.tab[fd]
f.fdref++
for cap(files.tab) <= newfd {
files.tab = append(files.tab[:cap(files.tab)], nil)
}
oldf := files.tab[newfd]
var oldfdref int
if oldf != nil {
oldf.fdref--
oldfdref = oldf.fdref
}
files.tab[newfd] = f
files.Unlock()
if oldf != nil {
if oldfdref == 0 {
oldf.impl.close()
}
}
return nil
}
func Fstat(fd int, st *Stat_t) error {
f, err := fdToFile(fd)
if err != nil {
return err
}
return f.impl.stat(st)
}
func Read(fd int, b []byte) (int, error) {
f, err := fdToFile(fd)
if err != nil {
return 0, err
}
return f.impl.read(b)
}
var zerobuf [0]byte
func Write(fd int, b []byte) (int, error) {
if b == nil {
// avoid nil in syscalls; nacl doesn't like that.
b = zerobuf[:]
}
f, err := fdToFile(fd)
if err != nil {
return 0, err
}
return f.impl.write(b)
}
func Pread(fd int, b []byte, offset int64) (int, error) {
f, err := fdToFile(fd)
if err != nil {
return 0, err
}
return f.impl.pread(b, offset)
}
func Pwrite(fd int, b []byte, offset int64) (int, error) {
f, err := fdToFile(fd)
if err != nil {
return 0, err
}
return f.impl.pwrite(b, offset)
}
func Seek(fd int, offset int64, whence int) (int64, error) {
f, err := fdToFile(fd)
if err != nil {
return 0, err
}
return f.impl.seek(offset, whence)
}
// defaulFileImpl implements fileImpl.
// It can be embedded to complete a partial fileImpl implementation.
type defaultFileImpl struct{}
func (*defaultFileImpl) close() error { return nil }
func (*defaultFileImpl) stat(*Stat_t) error { return ENOSYS }
func (*defaultFileImpl) read([]byte) (int, error) { return 0, ENOSYS }
func (*defaultFileImpl) write([]byte) (int, error) { return 0, ENOSYS }
func (*defaultFileImpl) seek(int64, int) (int64, error) { return 0, ENOSYS }
func (*defaultFileImpl) pread([]byte, int64) (int, error) { return 0, ENOSYS }
func (*defaultFileImpl) pwrite([]byte, int64) (int, error) { return 0, ENOSYS }
// naclFile is the fileImpl implementation for a Native Client file descriptor.
type naclFile struct {
defaultFileImpl
naclFD int
}
func (f *naclFile) stat(st *Stat_t) error {
return naclFstat(f.naclFD, st)
}
func (f *naclFile) read(b []byte) (int, error) {
n, err := naclRead(f.naclFD, b)
if err != nil {
n = 0
}
return n, err
}
// implemented in package runtime, to add time header on playground
func naclWrite(fd int, b []byte) int
func (f *naclFile) write(b []byte) (int, error) {
n := naclWrite(f.naclFD, b)
if n < 0 {
return 0, Errno(-n)
}
return n, nil
}
func (f *naclFile) seek(off int64, whence int) (int64, error) {
old := off
err := naclSeek(f.naclFD, &off, whence)
if err != nil {
return old, err
}
return off, nil
}
func (f *naclFile) prw(b []byte, offset int64, rw func([]byte) (int, error)) (int, error) {
// NaCl has no pread; simulate with seek and hope for no races.
old, err := f.seek(0, io.SeekCurrent)
if err != nil {
return 0, err
}
if _, err := f.seek(offset, io.SeekStart); err != nil {
return 0, err
}
n, err := rw(b)
f.seek(old, io.SeekStart)
return n, err
}
func (f *naclFile) pread(b []byte, offset int64) (int, error) {
return f.prw(b, offset, f.read)
}
func (f *naclFile) pwrite(b []byte, offset int64) (int, error) {
return f.prw(b, offset, f.write)
}
func (f *naclFile) close() error {
err := naclClose(f.naclFD)
f.naclFD = -1
return err
}
// A pipeFile is an in-memory implementation of a pipe.
// The byteq implementation is in net_nacl.go.
type pipeFile struct {
defaultFileImpl
rd *byteq
wr *byteq
}
func (f *pipeFile) close() error {
if f.rd != nil {
f.rd.close()
}
if f.wr != nil {
f.wr.close()
}
return nil
}
func (f *pipeFile) read(b []byte) (int, error) {
if f.rd == nil {
return 0, EINVAL
}
n, err := f.rd.read(b, 0)
if err == EAGAIN {
err = nil
}
return n, err
}
func (f *pipeFile) write(b []byte) (int, error) {
if f.wr == nil {
return 0, EINVAL
}
n, err := f.wr.write(b, 0)
if err == EAGAIN {
err = EPIPE
}
return n, err
}
func Pipe(fd []int) error {
q := newByteq()
fd[0] = newFD(&pipeFile{rd: q})
fd[1] = newFD(&pipeFile{wr: q})
return nil
}