Skip to content
Permalink
2197321db1
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
846 lines (761 sloc) 17.2 KB
// 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
}
if uid != -1 {
ip.Uid = uint32(uid)
}
if gid != -1 {
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()
fs.mu.Lock()
defer fs.mu.Unlock()
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
}
_, _, err = fs.dirlookup(dp, elem)
if err == nil {
return EEXIST
}
fs.dirlink(dp, elem, ip)
return nil
}
func Rename(from, to string) error {
fsinit()
fs.mu.Lock()
defer fs.mu.Unlock()
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
}