Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
go/src/syscall/fd_nacl.go
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
326 lines (288 sloc)
7.08 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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() | |
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 | |
} |