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