diff options
Diffstat (limited to 'src/pkg/syscall/fs_nacl.go')
-rw-r--r-- | src/pkg/syscall/fs_nacl.go | 815 |
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 +} |