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