...
Run Format

Source file src/syscall/fs_nacl.go

Documentation: syscall

  // 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.
  
  // A simulated Unix-like file system for use within NaCl.
  //
  // The simulation is not particularly tied to NaCl other than the reuse
  // of NaCl's definition for the Stat_t structure.
  //
  // The file system need never be written to disk, so it is represented as
  // in-memory Go data structures, never in a serialized form.
  //
  // TODO: Perhaps support symlinks, although they muck everything up.
  
  package syscall
  
  import (
  	"io"
  	"sync"
  	"unsafe"
  )
  
  // Provided by package runtime.
  func now() (sec int64, nsec int32)
  
  // An fsys is a file system.
  // Since there is no I/O (everything is in memory),
  // the global lock mu protects the whole file system state,
  // and that's okay.
  type fsys struct {
  	mu   sync.Mutex
  	root *inode                    // root directory
  	cwd  *inode                    // process current directory
  	inum uint64                    // number of inodes created
  	dev  []func() (devFile, error) // table for opening devices
  }
  
  // A devFile is the implementation required of device files
  // like /dev/null or /dev/random.
  type devFile interface {
  	pread([]byte, int64) (int, error)
  	pwrite([]byte, int64) (int, error)
  }
  
  // An inode is a (possibly special) file in the file system.
  type inode struct {
  	Stat_t
  	data []byte
  	dir  []dirent
  }
  
  // A dirent describes a single directory entry.
  type dirent struct {
  	name  string
  	inode *inode
  }
  
  // An fsysFile is the fileImpl implementation backed by the file system.
  type fsysFile struct {
  	defaultFileImpl
  	fsys     *fsys
  	inode    *inode
  	openmode int
  	offset   int64
  	dev      devFile
  }
  
  // newFsys creates a new file system.
  func newFsys() *fsys {
  	fs := &fsys{}
  	fs.mu.Lock()
  	defer fs.mu.Unlock()
  	ip := fs.newInode()
  	ip.Mode = 0555 | S_IFDIR
  	fs.dirlink(ip, ".", ip)
  	fs.dirlink(ip, "..", ip)
  	fs.cwd = ip
  	fs.root = ip
  	return fs
  }
  
  var fs = newFsys()
  var fsinit = func() {}
  
  func init() {
  	// do not trigger loading of zipped file system here
  	oldFsinit := fsinit
  	defer func() { fsinit = oldFsinit }()
  	fsinit = func() {}
  	Mkdir("/dev", 0555)
  	Mkdir("/tmp", 0777)
  	mkdev("/dev/null", 0666, openNull)
  	mkdev("/dev/random", 0444, openRandom)
  	mkdev("/dev/urandom", 0444, openRandom)
  	mkdev("/dev/zero", 0666, openZero)
  	chdirEnv()
  }
  
  func chdirEnv() {
  	pwd, ok := Getenv("NACLPWD")
  	if ok {
  		chdir(pwd)
  	}
  }
  
  // Except where indicated otherwise, unexported methods on fsys
  // expect fs.mu to have been locked by the caller.
  
  // newInode creates a new inode.
  func (fs *fsys) newInode() *inode {
  	fs.inum++
  	ip := &inode{
  		Stat_t: Stat_t{
  			Ino:     fs.inum,
  			Blksize: 512,
  		},
  	}
  	return ip
  }
  
  // atime sets ip.Atime to the current time.
  func (fs *fsys) atime(ip *inode) {
  	sec, nsec := now()
  	ip.Atime, ip.AtimeNsec = sec, int64(nsec)
  }
  
  // mtime sets ip.Mtime to the current time.
  func (fs *fsys) mtime(ip *inode) {
  	sec, nsec := now()
  	ip.Mtime, ip.MtimeNsec = sec, int64(nsec)
  }
  
  // dirlookup looks for an entry in the directory dp with the given name.
  // It returns the directory entry and its index within the directory.
  func (fs *fsys) dirlookup(dp *inode, name string) (de *dirent, index int, err error) {
  	fs.atime(dp)
  	for i := range dp.dir {
  		de := &dp.dir[i]
  		if de.name == name {
  			fs.atime(de.inode)
  			return de, i, nil
  		}
  	}
  	return nil, 0, ENOENT
  }
  
  // dirlink adds to the directory dp an entry for name pointing at the inode ip.
  // If dp already contains an entry for name, that entry is overwritten.
  func (fs *fsys) dirlink(dp *inode, name string, ip *inode) {
  	fs.mtime(dp)
  	fs.atime(ip)
  	ip.Nlink++
  	for i := range dp.dir {
  		if dp.dir[i].name == name {
  			dp.dir[i] = dirent{name, ip}
  			return
  		}
  	}
  	dp.dir = append(dp.dir, dirent{name, ip})
  	dp.dirSize()
  }
  
  func (dp *inode) dirSize() {
  	dp.Size = int64(len(dp.dir)) * (8 + 8 + 2 + 256) // Dirent
  }
  
  // skipelem splits path into the first element and the remainder.
  // the returned first element contains no slashes, and the returned
  // remainder does not begin with a slash.
  func skipelem(path string) (elem, rest string) {
  	for len(path) > 0 && path[0] == '/' {
  		path = path[1:]
  	}
  	if len(path) == 0 {
  		return "", ""
  	}
  	i := 0
  	for i < len(path) && path[i] != '/' {
  		i++
  	}
  	elem, path = path[:i], path[i:]
  	for len(path) > 0 && path[0] == '/' {
  		path = path[1:]
  	}
  	return elem, path
  }
  
  // namei translates a file system path name into an inode.
  // If parent is false, the returned ip corresponds to the given name, and elem is the empty string.
  // If parent is true, the walk stops at the next-to-last element in the name,
  // so that ip is the parent directory and elem is the final element in the path.
  func (fs *fsys) namei(path string, parent bool) (ip *inode, elem string, err error) {
  	// Reject NUL in name.
  	for i := 0; i < len(path); i++ {
  		if path[i] == '\x00' {
  			return nil, "", EINVAL
  		}
  	}
  
  	// Reject empty name.
  	if path == "" {
  		return nil, "", EINVAL
  	}
  
  	if path[0] == '/' {
  		ip = fs.root
  	} else {
  		ip = fs.cwd
  	}
  
  	for len(path) > 0 && path[len(path)-1] == '/' {
  		path = path[:len(path)-1]
  	}
  
  	for {
  		elem, rest := skipelem(path)
  		if elem == "" {
  			if parent && ip.Mode&S_IFMT == S_IFDIR {
  				return ip, ".", nil
  			}
  			break
  		}
  		if ip.Mode&S_IFMT != S_IFDIR {
  			return nil, "", ENOTDIR
  		}
  		if len(elem) >= 256 {
  			return nil, "", ENAMETOOLONG
  		}
  		if parent && rest == "" {
  			// Stop one level early.
  			return ip, elem, nil
  		}
  		de, _, err := fs.dirlookup(ip, elem)
  		if err != nil {
  			return nil, "", err
  		}
  		ip = de.inode
  		path = rest
  	}
  	if parent {
  		return nil, "", ENOTDIR
  	}
  	return ip, "", nil
  }
  
  // open opens or creates a file with the given name, open mode,
  // and permission mode bits.
  func (fs *fsys) open(name string, openmode int, mode uint32) (fileImpl, error) {
  	dp, elem, err := fs.namei(name, true)
  	if err != nil {
  		return nil, err
  	}
  	var (
  		ip  *inode
  		dev devFile
  	)
  	de, _, err := fs.dirlookup(dp, elem)
  	if err != nil {
  		if openmode&O_CREATE == 0 {
  			return nil, err
  		}
  		ip = fs.newInode()
  		ip.Mode = mode
  		fs.dirlink(dp, elem, ip)
  		if ip.Mode&S_IFMT == S_IFDIR {
  			fs.dirlink(ip, ".", ip)
  			fs.dirlink(ip, "..", dp)
  		}
  	} else {
  		ip = de.inode
  		if openmode&(O_CREATE|O_EXCL) == O_CREATE|O_EXCL {
  			return nil, EEXIST
  		}
  		if openmode&O_TRUNC != 0 {
  			if ip.Mode&S_IFMT == S_IFDIR {
  				return nil, EISDIR
  			}
  			ip.data = nil
  		}
  		if ip.Mode&S_IFMT == S_IFCHR {
  			if ip.Rdev < 0 || ip.Rdev >= int64(len(fs.dev)) || fs.dev[ip.Rdev] == nil {
  				return nil, ENODEV
  			}
  			dev, err = fs.dev[ip.Rdev]()
  			if err != nil {
  				return nil, err
  			}
  		}
  	}
  
  	switch openmode & O_ACCMODE {
  	case O_WRONLY, O_RDWR:
  		if ip.Mode&S_IFMT == S_IFDIR {
  			return nil, EISDIR
  		}
  	}
  
  	switch ip.Mode & S_IFMT {
  	case S_IFDIR:
  		if openmode&O_ACCMODE != O_RDONLY {
  			return nil, EISDIR
  		}
  
  	case S_IFREG:
  		// ok
  
  	case S_IFCHR:
  		// handled above
  
  	default:
  		// TODO: some kind of special file
  		return nil, EPERM
  	}
  
  	f := &fsysFile{
  		fsys:     fs,
  		inode:    ip,
  		openmode: openmode,
  		dev:      dev,
  	}
  	if openmode&O_APPEND != 0 {
  		f.offset = ip.Size
  	}
  	return f, nil
  }
  
  // fsysFile methods to implement fileImpl.
  
  func (f *fsysFile) stat(st *Stat_t) error {
  	f.fsys.mu.Lock()
  	defer f.fsys.mu.Unlock()
  	*st = f.inode.Stat_t
  	return nil
  }
  
  func (f *fsysFile) read(b []byte) (int, error) {
  	f.fsys.mu.Lock()
  	defer f.fsys.mu.Unlock()
  	n, err := f.preadLocked(b, f.offset)
  	f.offset += int64(n)
  	return n, err
  }
  
  func ReadDirent(fd int, buf []byte) (int, error) {
  	f, err := fdToFsysFile(fd)
  	if err != nil {
  		return 0, err
  	}
  	f.fsys.mu.Lock()
  	defer f.fsys.mu.Unlock()
  	if f.inode.Mode&S_IFMT != S_IFDIR {
  		return 0, EINVAL
  	}
  	n, err := f.preadLocked(buf, f.offset)
  	f.offset += int64(n)
  	return n, err
  }
  
  func (f *fsysFile) write(b []byte) (int, error) {
  	f.fsys.mu.Lock()
  	defer f.fsys.mu.Unlock()
  	n, err := f.pwriteLocked(b, f.offset)
  	f.offset += int64(n)
  	return n, err
  }
  
  func (f *fsysFile) seek(offset int64, whence int) (int64, error) {
  	f.fsys.mu.Lock()
  	defer f.fsys.mu.Unlock()
  	switch whence {
  	case io.SeekCurrent:
  		offset += f.offset
  	case io.SeekEnd:
  		offset += f.inode.Size
  	}
  	if offset < 0 {
  		return 0, EINVAL
  	}
  	if offset > f.inode.Size {
  		return 0, EINVAL
  	}
  	f.offset = offset
  	return offset, nil
  }
  
  func (f *fsysFile) pread(b []byte, offset int64) (int, error) {
  	f.fsys.mu.Lock()
  	defer f.fsys.mu.Unlock()
  	return f.preadLocked(b, offset)
  }
  
  func (f *fsysFile) pwrite(b []byte, offset int64) (int, error) {
  	f.fsys.mu.Lock()
  	defer f.fsys.mu.Unlock()
  	return f.pwriteLocked(b, offset)
  }
  
  func (f *fsysFile) preadLocked(b []byte, offset int64) (int, error) {
  	if f.openmode&O_ACCMODE == O_WRONLY {
  		return 0, EINVAL
  	}
  	if offset < 0 {
  		return 0, EINVAL
  	}
  	if f.dev != nil {
  		f.fsys.atime(f.inode)
  		f.fsys.mu.Unlock()
  		defer f.fsys.mu.Lock()
  		return f.dev.pread(b, offset)
  	}
  	if offset > f.inode.Size {
  		return 0, nil
  	}
  	if int64(len(b)) > f.inode.Size-offset {
  		b = b[:f.inode.Size-offset]
  	}
  
  	if f.inode.Mode&S_IFMT == S_IFDIR {
  		if offset%direntSize != 0 || len(b) != 0 && len(b) < direntSize {
  			return 0, EINVAL
  		}
  		fs.atime(f.inode)
  		n := 0
  		for len(b) >= direntSize {
  			src := f.inode.dir[int(offset/direntSize)]
  			dst := (*Dirent)(unsafe.Pointer(&b[0]))
  			dst.Ino = int64(src.inode.Ino)
  			dst.Off = offset
  			dst.Reclen = direntSize
  			for i := range dst.Name {
  				dst.Name[i] = 0
  			}
  			copy(dst.Name[:], src.name)
  			n += direntSize
  			offset += direntSize
  			b = b[direntSize:]
  		}
  		return n, nil
  	}
  
  	fs.atime(f.inode)
  	n := copy(b, f.inode.data[offset:])
  	return n, nil
  }
  
  func (f *fsysFile) pwriteLocked(b []byte, offset int64) (int, error) {
  	if f.openmode&O_ACCMODE == O_RDONLY {
  		return 0, EINVAL
  	}
  	if offset < 0 {
  		return 0, EINVAL
  	}
  	if f.dev != nil {
  		f.fsys.atime(f.inode)
  		f.fsys.mu.Unlock()
  		defer f.fsys.mu.Lock()
  		return f.dev.pwrite(b, offset)
  	}
  	if offset > f.inode.Size {
  		return 0, EINVAL
  	}
  	f.fsys.mtime(f.inode)
  	n := copy(f.inode.data[offset:], b)
  	if n < len(b) {
  		f.inode.data = append(f.inode.data, b[n:]...)
  		f.inode.Size = int64(len(f.inode.data))
  	}
  	return len(b), nil
  }
  
  // Standard Unix system calls.
  
  func Open(path string, openmode int, perm uint32) (fd int, err error) {
  	fsinit()
  	fs.mu.Lock()
  	defer fs.mu.Unlock()
  	f, err := fs.open(path, openmode, perm&0777|S_IFREG)
  	if err != nil {
  		return -1, err
  	}
  	return newFD(f), nil
  }
  
  func Mkdir(path string, perm uint32) error {
  	fs.mu.Lock()
  	defer fs.mu.Unlock()
  	_, err := fs.open(path, O_CREATE|O_EXCL, perm&0777|S_IFDIR)
  	return err
  }
  
  func Getcwd(buf []byte) (n int, err error) {
  	// Force package os to default to the old algorithm using .. and directory reads.
  	return 0, ENOSYS
  }
  
  func Stat(path string, st *Stat_t) error {
  	fsinit()
  	fs.mu.Lock()
  	defer fs.mu.Unlock()
  	ip, _, err := fs.namei(path, false)
  	if err != nil {
  		return err
  	}
  	*st = ip.Stat_t
  	return nil
  }
  
  func Lstat(path string, st *Stat_t) error {
  	return Stat(path, st)
  }
  
  func unlink(path string, isdir bool) error {
  	fsinit()
  	fs.mu.Lock()
  	defer fs.mu.Unlock()
  	dp, elem, err := fs.namei(path, true)
  	if err != nil {
  		return err
  	}
  	if elem == "." || elem == ".." {
  		return EINVAL
  	}
  	de, _, err := fs.dirlookup(dp, elem)
  	if err != nil {
  		return err
  	}
  	if isdir {
  		if de.inode.Mode&S_IFMT != S_IFDIR {
  			return ENOTDIR
  		}
  		if len(de.inode.dir) != 2 {
  			return ENOTEMPTY
  		}
  	} else {
  		if de.inode.Mode&S_IFMT == S_IFDIR {
  			return EISDIR
  		}
  	}
  	de.inode.Nlink--
  	*de = dp.dir[len(dp.dir)-1]
  	dp.dir = dp.dir[:len(dp.dir)-1]
  	dp.dirSize()
  	return nil
  }
  
  func Unlink(path string) error {
  	return unlink(path, false)
  }
  
  func Rmdir(path string) error {
  	return unlink(path, true)
  }
  
  func Chmod(path string, mode uint32) error {
  	fsinit()
  	fs.mu.Lock()
  	defer fs.mu.Unlock()
  	ip, _, err := fs.namei(path, false)
  	if err != nil {
  		return err
  	}
  	ip.Mode = ip.Mode&^0777 | mode&0777
  	return nil
  }
  
  func Fchmod(fd int, mode uint32) error {
  	f, err := fdToFsysFile(fd)
  	if err != nil {
  		return err
  	}
  	f.fsys.mu.Lock()
  	defer f.fsys.mu.Unlock()
  	f.inode.Mode = f.inode.Mode&^0777 | mode&0777
  	return nil
  }
  
  func Chown(path string, uid, gid int) error {
  	fsinit()
  	fs.mu.Lock()
  	defer fs.mu.Unlock()
  	ip, _, err := fs.namei(path, false)
  	if err != nil {
  		return err
  	}
  	ip.Uid = uint32(uid)
  	ip.Gid = uint32(gid)
  	return nil
  }
  
  func Fchown(fd int, uid, gid int) error {
  	fs.mu.Lock()
  	defer fs.mu.Unlock()
  	f, err := fdToFsysFile(fd)
  	if err != nil {
  		return err
  	}
  	f.fsys.mu.Lock()
  	defer f.fsys.mu.Unlock()
  	f.inode.Uid = uint32(uid)
  	f.inode.Gid = uint32(gid)
  	return nil
  }
  
  func Lchown(path string, uid, gid int) error {
  	return Chown(path, uid, gid)
  }
  
  func UtimesNano(path string, ts []Timespec) error {
  	if len(ts) != 2 {
  		return EINVAL
  	}
  	fsinit()
  	fs.mu.Lock()
  	defer fs.mu.Unlock()
  	ip, _, err := fs.namei(path, false)
  	if err != nil {
  		return err
  	}
  	ip.Atime = ts[0].Sec
  	ip.AtimeNsec = int64(ts[0].Nsec)
  	ip.Mtime = ts[1].Sec
  	ip.MtimeNsec = int64(ts[1].Nsec)
  	return nil
  }
  
  func Link(path, link string) error {
  	fsinit()
  	ip, _, err := fs.namei(path, false)
  	if err != nil {
  		return err
  	}
  	dp, elem, err := fs.namei(link, true)
  	if err != nil {
  		return err
  	}
  	if ip.Mode&S_IFMT == S_IFDIR {
  		return EPERM
  	}
  	fs.dirlink(dp, elem, ip)
  	return nil
  }
  
  func Rename(from, to string) error {
  	fsinit()
  	fdp, felem, err := fs.namei(from, true)
  	if err != nil {
  		return err
  	}
  	fde, _, err := fs.dirlookup(fdp, felem)
  	if err != nil {
  		return err
  	}
  	tdp, telem, err := fs.namei(to, true)
  	if err != nil {
  		return err
  	}
  	fs.dirlink(tdp, telem, fde.inode)
  	fde.inode.Nlink--
  	*fde = fdp.dir[len(fdp.dir)-1]
  	fdp.dir = fdp.dir[:len(fdp.dir)-1]
  	fdp.dirSize()
  	return nil
  }
  
  func (fs *fsys) truncate(ip *inode, length int64) error {
  	if length > 1e9 || ip.Mode&S_IFMT != S_IFREG {
  		return EINVAL
  	}
  	if length < int64(len(ip.data)) {
  		ip.data = ip.data[:length]
  	} else {
  		data := make([]byte, length)
  		copy(data, ip.data)
  		ip.data = data
  	}
  	ip.Size = int64(len(ip.data))
  	return nil
  }
  
  func Truncate(path string, length int64) error {
  	fsinit()
  	fs.mu.Lock()
  	defer fs.mu.Unlock()
  	ip, _, err := fs.namei(path, false)
  	if err != nil {
  		return err
  	}
  	return fs.truncate(ip, length)
  }
  
  func Ftruncate(fd int, length int64) error {
  	f, err := fdToFsysFile(fd)
  	if err != nil {
  		return err
  	}
  	f.fsys.mu.Lock()
  	defer f.fsys.mu.Unlock()
  	return f.fsys.truncate(f.inode, length)
  }
  
  func Chdir(path string) error {
  	fsinit()
  	return chdir(path)
  }
  
  func chdir(path string) error {
  	fs.mu.Lock()
  	defer fs.mu.Unlock()
  	ip, _, err := fs.namei(path, false)
  	if err != nil {
  		return err
  	}
  	fs.cwd = ip
  	return nil
  }
  
  func Fchdir(fd int) error {
  	f, err := fdToFsysFile(fd)
  	if err != nil {
  		return err
  	}
  	f.fsys.mu.Lock()
  	defer f.fsys.mu.Unlock()
  	if f.inode.Mode&S_IFMT != S_IFDIR {
  		return ENOTDIR
  	}
  	fs.cwd = f.inode
  	return nil
  }
  
  func Readlink(path string, buf []byte) (n int, err error) {
  	return 0, ENOSYS
  }
  
  func Symlink(path, link string) error {
  	return ENOSYS
  }
  
  func Fsync(fd int) error {
  	return nil
  }
  
  // Special devices.
  
  func mkdev(path string, mode uint32, open func() (devFile, error)) error {
  	f, err := fs.open(path, O_CREATE|O_RDONLY|O_EXCL, S_IFCHR|mode)
  	if err != nil {
  		return err
  	}
  	ip := f.(*fsysFile).inode
  	ip.Rdev = int64(len(fs.dev))
  	fs.dev = append(fs.dev, open)
  	return nil
  }
  
  type nullFile struct{}
  
  func openNull() (devFile, error)                               { return &nullFile{}, nil }
  func (f *nullFile) close() error                               { return nil }
  func (f *nullFile) pread(b []byte, offset int64) (int, error)  { return 0, nil }
  func (f *nullFile) pwrite(b []byte, offset int64) (int, error) { return len(b), nil }
  
  type zeroFile struct{}
  
  func openZero() (devFile, error)                               { return &zeroFile{}, nil }
  func (f *zeroFile) close() error                               { return nil }
  func (f *zeroFile) pwrite(b []byte, offset int64) (int, error) { return len(b), nil }
  
  func (f *zeroFile) pread(b []byte, offset int64) (int, error) {
  	for i := range b {
  		b[i] = 0
  	}
  	return len(b), nil
  }
  
  type randomFile struct{}
  
  func openRandom() (devFile, error) {
  	return randomFile{}, nil
  }
  
  func (f randomFile) close() error {
  	return nil
  }
  
  func (f randomFile) pread(b []byte, offset int64) (int, error) {
  	if err := naclGetRandomBytes(b); err != nil {
  		return 0, err
  	}
  	return len(b), nil
  }
  
  func (f randomFile) pwrite(b []byte, offset int64) (int, error) {
  	return 0, EPERM
  }
  
  func fdToFsysFile(fd int) (*fsysFile, error) {
  	f, err := fdToFile(fd)
  	if err != nil {
  		return nil, err
  	}
  	impl := f.impl
  	fsysf, ok := impl.(*fsysFile)
  	if !ok {
  		return nil, EINVAL
  	}
  	return fsysf, nil
  }
  
  // create creates a file in the file system with the given name, mode, time, and data.
  // It is meant to be called when initializing the file system image.
  func create(name string, mode uint32, sec int64, data []byte) error {
  	fs.mu.Lock()
  	defer fs.mu.Unlock()
  	f, err := fs.open(name, O_CREATE|O_EXCL, mode)
  	if err != nil {
  		if mode&S_IFMT == S_IFDIR {
  			ip, _, err := fs.namei(name, false)
  			if err == nil && (ip.Mode&S_IFMT) == S_IFDIR {
  				return nil // directory already exists
  			}
  		}
  		return err
  	}
  	ip := f.(*fsysFile).inode
  	ip.Atime = sec
  	ip.Mtime = sec
  	ip.Ctime = sec
  	if len(data) > 0 {
  		ip.Size = int64(len(data))
  		ip.data = data
  	}
  	return nil
  }
  

View as plain text