summaryrefslogtreecommitdiff
path: root/src/pkg/syscall/fs_nacl.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/syscall/fs_nacl.go')
-rw-r--r--src/pkg/syscall/fs_nacl.go815
1 files changed, 815 insertions, 0 deletions
diff --git a/src/pkg/syscall/fs_nacl.go b/src/pkg/syscall/fs_nacl.go
new file mode 100644
index 000000000..ac9239483
--- /dev/null
+++ b/src/pkg/syscall/fs_nacl.go
@@ -0,0 +1,815 @@
+// 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 (
+ "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()
+
+func init() {
+ 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 false, 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 1:
+ offset += f.offset
+ case 2:
+ 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) {
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ 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
+ }
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ fs.mu.Lock()
+ fs.mu.Unlock()
+ 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 {
+ naclFD int
+}
+
+func openRandom() (devFile, error) {
+ fd, err := openNamedService("SecureRandom", O_RDONLY)
+ if err != nil {
+ return nil, err
+ }
+ return &randomFile{naclFD: fd}, nil
+}
+
+func (f *randomFile) close() error {
+ naclClose(f.naclFD)
+ f.naclFD = -1
+ return nil
+}
+
+func (f *randomFile) pread(b []byte, offset int64) (int, error) {
+ return naclRead(f.naclFD, b)
+}
+
+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()
+ fs.mu.Unlock()
+ f, err := fs.open(name, O_CREATE|O_EXCL, mode)
+ if err != nil {
+ 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
+}