| // Copyright 2016 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. |
| |
| // `go mod` ignores file names for the purpose of resolving |
| // dependencies, and fdio doesn't build on not-Fuchsia. |
| //go:build fuchsia |
| |
| package syscall |
| |
| import ( |
| "errors" |
| "internal/itoa" |
| "io" |
| "path" |
| "strings" |
| "sync" |
| "unsafe" |
| |
| "syscall/zx" |
| "syscall/zx/fdio" |
| fidlIo "syscall/zx/io" |
| ) |
| |
| var ( |
| Stdin = 0 |
| Stdout = 1 |
| Stderr = 2 |
| ) |
| |
| const ( |
| FsRightReadable = 0x00000001 |
| FsRightWritable = 0x00000002 |
| FsRightAdmin = 0x00000004 |
| FsRightExecutable = 0x00000008 |
| FsRights = FsRightReadable | FsRightWritable | FsRightAdmin | FsRightExecutable |
| FsRightSpace = 0x0000FFFF |
| FsFlagCreate = 0x00010000 |
| FsFlagExclusive = 0x00020000 |
| FsFlagTruncate = 0x00040000 |
| FsFlagDirectory = 0x00080000 |
| FsFlagAppend = 0x00100000 |
| FsFlagNoRemote = 0x00200000 |
| FsFlagPath = 0x00400000 |
| FsFlagDescribe = 0x00800000 |
| FsFlagNotDirectory = 0x02000000 |
| FsFlagCloneSameRights = 0x04000000 |
| FsFlagPosixWritable = 0x08000000 |
| FsFlagPosixExecutable = 0x10000000 |
| |
| FsFlagsAllowedWithPath = FsFlagPath | FsFlagDirectory | |
| FsFlagNotDirectory | FsFlagDescribe |
| ) |
| |
| const ( |
| O_RDONLY = 0x0 |
| O_WRONLY = 0x1 |
| O_RDWR = 0x2 |
| |
| // Flags which align with ZXIO_FS_* |
| O_ADMIN = FsRightAdmin |
| O_CREAT = FsFlagCreate |
| O_EXCL = FsFlagExclusive |
| O_TRUNC = FsFlagTruncate |
| O_DIRECTORY = FsFlagDirectory |
| O_APPEND = FsFlagAppend |
| O_NOREMOTE = FsFlagNoRemote |
| O_PATH = FsFlagPath |
| |
| FdioAlignedFlags = O_ADMIN | O_CREAT | O_EXCL | O_TRUNC | O_DIRECTORY | O_APPEND | |
| O_NOREMOTE | O_PATH |
| |
| // Flags which do not align with ZXIO_FS_* |
| O_NONBLOCK = 0x00000010 |
| O_DSYNC = 0x00000020 |
| O_SYNC = 0x00000040 | O_DSYNC |
| O_RSYNC = O_SYNC |
| O_NOFOLLOW = 0x00000080 |
| O_CLOEXEC = 0x00000100 |
| O_NOCTTY = 0x00000200 |
| O_ASYNC = 0x00000400 |
| O_DIRECT = 0x00000800 |
| O_LARGEFILE = 0x00001000 |
| O_NOATIME = 0x00002000 |
| O_TMPFILE = 0x00004000 |
| ) |
| |
| // This function must translate the flags exactly as the function fdio_flags_to_zxio |
| // in unistd.cc (sdk/lib/fdio/unistd.cc) does. |
| func FdioFlagsToZxio(flags uint32, mode uint32) uint32 { |
| var zflags uint32 |
| perms := uint32(O_RDONLY | O_WRONLY | O_RDWR) |
| switch flags & perms { |
| case O_RDONLY: |
| zflags |= FsRightReadable |
| case O_WRONLY: |
| zflags |= FsRightWritable |
| case O_RDWR: |
| zflags |= FsRightReadable | FsRightWritable |
| } |
| |
| zflags |= FsFlagDescribe |
| zflags |= (flags & FdioAlignedFlags) |
| if (zflags & FsFlagPath) == 0 { |
| // Since this is not opening a Vnode reference, we must specify the exact |
| // rights we wish to inherit for POSIX compatibility. |
| zflags |= FsFlagPosixWritable | FsFlagPosixExecutable |
| } else { |
| zflags &= FsFlagsAllowedWithPath |
| } |
| if mode&S_IFDIR != 0 { |
| zflags |= FsFlagDirectory |
| } |
| return zflags |
| } |
| |
| const ( |
| S_IFMT = fdio.S_IFMT |
| S_IFDIR = fdio.S_IFDIR |
| S_IFREG = fdio.S_IFREG |
| S_IFIFO = fdio.S_IFIFO |
| ) |
| |
| // TODO(crawshaw): generate a zerrors file once cgo is working |
| const ( |
| EPERM = Errno(0x01) |
| ENOENT = Errno(0x02) |
| EINTR = Errno(0x04) |
| EIO = Errno(0x05) |
| EBADF = Errno(0x09) |
| EACCES = Errno(0x0d) |
| EEXIST = Errno(0x11) |
| ENOTDIR = Errno(0x14) |
| EISDIR = Errno(0x15) |
| EINVAL = Errno(0x16) |
| EMFILE = Errno(0x18) |
| ESPIPE = Errno(0x1d) |
| ERANGE = Errno(0x22) |
| ENAMETOOLONG = Errno(0x24) |
| ENOSYS = Errno(0x26) |
| ENOTEMPTY = Errno(0x27) |
| ENOPROTOOPT = Errno(0x5c) |
| EOPNOTSUPP = Errno(0x5f) |
| ENOTSUP = EOPNOTSUPP |
| EADDRINUSE = Errno(0x62) |
| EADDRNOTAVAIL = Errno(0x63) |
| ETIMEDOUT = Errno(0x6e) |
| ECONNABORTED = Errno(0x67) |
| EHOSTUNREACH = Errno(0x71) |
| EFUCHSIA = Errno(0xfa) |
| ) |
| |
| var EPIPE error = Errno(0x20) |
| |
| const ( |
| SOMAXCONN = 0x80 |
| ) |
| |
| const ( |
| PathMax = 4096 |
| ) |
| |
| type Stat_t struct { |
| Dev uint64 |
| Ino uint64 |
| Size uint64 |
| CreateTime uint64 |
| ModifyTime uint64 |
| Mode uint64 |
| } |
| |
| func Getpid() (pid int) { |
| var info zx.InfoHandleBasic |
| if err := zx.ProcHandle.GetInfo(zx.ObjectInfoHandleBasic, unsafe.Pointer(&info), uint(unsafe.Sizeof(info))); err != nil { |
| return 0 |
| } |
| return int(info.Koid) |
| } |
| |
| func Getppid() (ppid int) { |
| return 0 |
| } |
| |
| func Getuid() (uid int) { |
| return 0 |
| } |
| |
| func Geteuid() (uid int) { |
| return 0 |
| } |
| |
| func Getgid() (uid int) { |
| return 0 |
| } |
| |
| func Getegid() (uid int) { |
| return 0 |
| } |
| |
| func Getgroups() (gids []int, err error) { |
| return nil, errors.New("Getgroups unimplemented") |
| } |
| |
| //func Exit(code int) { |
| //zx.Sys_process_exit(int64(code)) |
| //} |
| |
| const ImplementsGetwd = true |
| |
| func Getwd() (wd string, err error) { |
| cwdMu.Lock() |
| wd = cwdStr |
| cwdMu.Unlock() |
| return wd, nil |
| } |
| |
| func Chdir(dir string) (err error) { |
| // TODO: if cgo enabled, change the working directory there too. |
| f, err := OpenPath(dir, 0, 0) |
| if err != nil { |
| return err |
| } |
| cwdMu.Lock() |
| var old fdio.FDIO |
| old, cwd = cwd, f |
| // cwd starts as a reference to root, which should remain open. |
| if old != nil && old != root { |
| defer old.Close() |
| } |
| if path.IsAbs(dir) { |
| cwdStr = path.Clean(dir) |
| } else { |
| cwdStr = path.Join(cwdStr, dir) |
| } |
| cwdMu.Unlock() |
| return nil |
| } |
| |
| func Rmdir(path string) error { |
| return Unlink(path) |
| } |
| |
| func Unlink(p string) error { |
| dir, relp := fdioPath(path.Clean(p)) |
| return dir.Unlink(relp) |
| } |
| |
| func makeAbs(paths ...string) []string { |
| cwdMu.Lock() |
| dir := cwdStr |
| cwdMu.Unlock() |
| for i := range paths { |
| if !path.IsAbs(paths[i]) { |
| paths[i] = path.Join(dir, paths[i]) |
| } |
| } |
| return paths |
| } |
| |
| func Rename(oldpath, newpath string) error { |
| abs := makeAbs(oldpath, newpath) |
| rootMu.Lock() |
| r := root |
| rootMu.Unlock() |
| return r.Rename(abs[0][1:], abs[1][1:]) |
| } |
| |
| func Link(oldpath, newpath string) error { |
| abs := makeAbs(oldpath, newpath) |
| rootMu.Lock() |
| r := root |
| rootMu.Unlock() |
| return r.Link(abs[0][1:], abs[1][1:]) |
| } |
| |
| func Fsync(fd int) error { |
| fdsMu.Lock() |
| f := fds[fd-fdsOff] |
| fdsMu.Unlock() |
| return f.Sync() |
| } |
| |
| func Fchdir(fd int) (err error) { |
| return errors.New("Fchdir unimplemented") |
| } |
| |
| func CloseOnExec(fd int) { |
| // nothing to do - no exec |
| } |
| |
| func SetNonblock(fd int, nonblocking bool) (err error) { |
| if nonblocking { |
| return errors.New("syscall.SetNonblock: non-blocking not supported on fuchsia") |
| } |
| return nil |
| } |
| |
| var ( |
| rootMu sync.Mutex |
| root fdio.FDIO |
| ) |
| |
| var ( |
| cwdMu sync.Mutex |
| cwd fdio.FDIO |
| cwdStr string |
| ) |
| |
| var ( |
| nsMu sync.Mutex |
| ns *fdio.NS |
| ) |
| |
| var ( |
| stdioMu sync.Mutex |
| stdin fdio.FDIO |
| stdout fdio.FDIO |
| stderr fdio.FDIO |
| ) |
| |
| const fdsOff = 1e6 // move outside the fd range of unistd.c FDIO |
| |
| var ( |
| fdsMu sync.Mutex |
| fds []fdio.FDIO |
| ) |
| |
| func Read(fd int, p []byte) (n int, err error) { |
| if fd == Stdin { |
| stdioMu.Lock() |
| n, err = stdin.Read(p) |
| stdioMu.Unlock() |
| } else { |
| if fd < fdsOff { |
| return 0, EINVAL |
| } |
| fdsMu.Lock() |
| f := fds[fd-fdsOff] |
| fdsMu.Unlock() |
| |
| n, err = f.Read(p) |
| } |
| // An io.Reader can return n < len(p) and io.EOF. |
| // But this is the POSIX syscall read(3), which |
| // returns nil on eof. |
| if err == io.EOF { |
| err = nil |
| } |
| return n, err |
| } |
| |
| func init() { |
| EPIPE = zx.EPIPE |
| |
| var err error |
| |
| nsMu.Lock() |
| ns, err = fdio.NewNSFromMap(zx.RootNSMap) |
| nsMu.Unlock() |
| if err != nil { |
| println("syscall: failed to create namespace: ", err.Error()) |
| } |
| |
| rootMu.Lock() |
| root, err = ns.OpenRoot() |
| rootMu.Unlock() |
| if err != nil { |
| println("syscall: failed to create root directory from namespace: ", err.Error()) |
| } |
| |
| cwdMu.Lock() |
| var found bool |
| cwdStr, found = Getenv("PWD") |
| if !found { |
| cwdStr = "/" |
| } else { |
| // Ensure the incoming cwd is absolute and cleaned |
| cwdStr = path.Join("/", cwdStr) |
| } |
| cwd = root |
| if cwdStr != "/" { |
| cwd, err = OpenPath(cwdStr, 0, 0) |
| } |
| cwdMu.Unlock() |
| if err != nil { |
| println("syscall: failed to create fdio cwd: ", err.Error()) |
| } |
| |
| initStdio := func(i int) (fdio.FDIO, error) { |
| switch zx.StdioHandleTypes[i] { |
| case 0: |
| return nil, EINVAL |
| case fdio.HandleTypeRemote, fdio.HandleTypeSocket, fdio.HandleTypeLogger, fdio.HandleTypeFileDescriptor: |
| info, err := zx.StdioHandles[i].GetInfoHandleBasic() |
| if err != nil { |
| println("syscall: GetInfoHandleBasic failed", err) |
| return nil, EINVAL |
| } |
| switch info.Type { |
| case zx.ObjTypeChannel: |
| return fdio.NewFileWithCtx(&fidlIo.FileWithCtxInterface{Channel: zx.Channel(zx.StdioHandles[i])}, 0), nil |
| case zx.ObjTypeSocket: |
| return fdio.NewPipe(zx.Socket(zx.StdioHandles[i])), nil |
| case zx.ObjTypeLog: |
| return fdio.NewLogger(zx.Log(zx.StdioHandles[i])), nil |
| default: |
| println("syscall: unknown object type for stdio: ", info.Type) |
| return nil, EINVAL |
| } |
| default: |
| println("syscall: unknown handle type for stdio: " + itoa.Itoa(zx.StdioHandleTypes[i])) |
| return nil, EINVAL |
| } |
| } |
| stdioMu.Lock() |
| stdin, err = initStdio(0) |
| if err != nil { |
| println("syscall: failed to create fdio stdin: ", err.Error()) |
| } |
| stdout, err = initStdio(1) |
| if err != nil { |
| println("syscall: failed to create fdio stdout: ", err.Error()) |
| } |
| stderr, err = initStdio(2) |
| if err != nil { |
| println("syscall: failed to create fdio stderr: ", err.Error()) |
| } |
| stdioMu.Unlock() |
| } |
| |
| func Write(fd int, p []byte) (n int, err error) { |
| switch fd { |
| case Stdout: |
| stdioMu.Lock() |
| n, err := stdout.Write(p) |
| stdioMu.Unlock() |
| return n, err |
| case Stderr: |
| stdioMu.Lock() |
| n, err := stderr.Write(p) |
| stdioMu.Unlock() |
| return n, err |
| default: |
| fdsMu.Lock() |
| f := fds[fd-fdsOff] |
| fdsMu.Unlock() |
| return f.Write(p) |
| } |
| } |
| |
| func Seek(fd int, offset int64, whence int) (int64, error) { |
| fdsMu.Lock() |
| f := fds[fd-fdsOff] |
| fdsMu.Unlock() |
| off, err := f.Seek(offset, whence) |
| if err, ok := err.(*zx.Error); ok && err.Status == zx.ErrNotSupported { |
| if _, ok := f.(*fdio.Pipe); ok { |
| return off, ESPIPE |
| } |
| } |
| return off, err |
| } |
| |
| func Close(fd int) (err error) { |
| if fd < fdsOff { |
| return EINVAL |
| } |
| f, err := func() (fdio.FDIO, error) { |
| i := fd - fdsOff |
| fdsMu.Lock() |
| defer fdsMu.Unlock() |
| if i >= len(fds) { |
| return nil, EINVAL |
| } |
| f := fds[i] |
| fds[i] = nil |
| return f, nil |
| }() |
| if err != nil { |
| return err |
| } |
| return f.Close() |
| } |
| |
| func fdioPath(p string) (fdio.FDIO, string) { |
| if path.IsAbs(p) { |
| rootMu.Lock() |
| r := root |
| rootMu.Unlock() |
| return r, p[1:] |
| } |
| cwdMu.Lock() |
| c := cwd |
| cwdMu.Unlock() |
| return c, p |
| } |
| |
| func injectNotDirectoryFlag(flags uint32) uint32 { |
| if flags&FsFlagDirectory == 0 && flags&(FsRightWritable|FsFlagCreate) != 0 { |
| flags |= FsFlagNotDirectory |
| } |
| return flags |
| } |
| |
| func OpenPath(p string, flags int, mode uint32) (fdio.FDIO, error) { |
| if strings.Contains(p, "\x00") { |
| return nil, EINVAL |
| } |
| dir, relp := fdioPath(path.Clean(p)) |
| zflags := injectNotDirectoryFlag(FdioFlagsToZxio(uint32(flags), mode)) |
| return dir.Open(relp, fidlIo.OpenFlags(zflags)) |
| } |
| |
| // Mkdir is implemented as an Open call on Fuchsia. The difference from the regular OpenPath call |
| // is that Mkdir does not add the FsFlagNotDirectory flag, and uses O_RDWR as the access mode. |
| func Mkdir(p string, mode uint32) error { |
| if strings.Contains(p, "\x00") { |
| return EINVAL |
| } |
| dir, relp := fdioPath(path.Clean(p)) |
| zflags := FdioFlagsToZxio(uint32(O_CREAT|O_EXCL|O_RDWR), mode&0777|S_IFDIR) |
| f, err := dir.Open(relp, fidlIo.OpenFlags(zflags)) |
| if err != nil { |
| return err |
| } |
| f.Close() |
| return nil |
| } |
| |
| func Open(path string, flags int, mode uint32) (fd int, err error) { |
| if path == "" { |
| return -1, EINVAL |
| } |
| f, err := OpenPath(path, flags, mode) |
| if err != nil { |
| return -1, err |
| } |
| return OpenFDIO(f), nil |
| } |
| |
| func OpenAt(fdParent int, path string, flags int, mode uint32) (fd int, err error) { |
| parent := FDIOForFD(fdParent) |
| if parent == nil { |
| return -1, EBADF |
| } |
| zflags := injectNotDirectoryFlag(FdioFlagsToZxio(uint32(flags), mode)) |
| f, err := parent.Open(path, fidlIo.OpenFlags(zflags)) |
| if err != nil { |
| return -1, err |
| } |
| return OpenFDIO(f), nil |
| } |
| |
| func FDIOForFD(fd int) fdio.FDIO { |
| switch fd { |
| case Stdin: |
| return stdin |
| case Stdout: |
| return stdout |
| case Stderr: |
| return stderr |
| } |
| if fd < fdsOff { |
| return nil |
| } |
| fdsMu.Lock() |
| if fd-fdsOff > len(fds) { |
| fdsMu.Unlock() |
| return nil |
| } |
| f := fds[fd-fdsOff] |
| fdsMu.Unlock() |
| return f |
| } |
| |
| func OpenFDIO(f fdio.FDIO) (fd int) { |
| fdsMu.Lock() |
| i := -1 |
| for i = 0; i < len(fds); i++ { |
| if fds[i] == nil { |
| fds[i] = f |
| break |
| } |
| } |
| if i == len(fds) { |
| fds = append(fds, f) |
| } |
| fdsMu.Unlock() |
| |
| return i + fdsOff |
| } |
| |
| func Fstat(fd int, stat *Stat_t) error { |
| if fd < fdsOff { |
| return EINVAL |
| } |
| f, err := func() (fdio.FDIO, error) { |
| i := fd - fdsOff |
| fdsMu.Lock() |
| defer fdsMu.Unlock() |
| if i >= len(fds) { |
| return nil, EINVAL |
| } |
| return fds[i], nil |
| }() |
| if err != nil { |
| return err |
| } |
| |
| attr, err := f.GetAttr() |
| if err != nil { |
| return err |
| } |
| stat.Dev = uint64(attr.Mode) |
| stat.Ino = attr.Id |
| stat.Size = attr.ContentSize |
| stat.CreateTime = attr.CreationTime |
| stat.ModifyTime = attr.ModificationTime |
| return nil |
| } |
| |
| func Chown(path string, uid int, gid int) error { |
| var stat Stat_t |
| return Stat(path, &stat) |
| } |
| |
| func Fchown(fd int, uid int, gid int) error { |
| var stat Stat_t |
| return Fstat(fd, &stat) |
| } |
| |
| func Lchown(path string, uid int, gid int) error { |
| var stat Stat_t |
| return Lstat(path, &stat) |
| } |
| |
| func Chmod(path string, mode uint32) error { |
| var stat Stat_t |
| return Stat(path, &stat) |
| } |
| |
| func Fchmod(fd int, mode uint32) error { |
| var stat Stat_t |
| return Fstat(fd, &stat) |
| } |
| |
| func Shutdown(fd int, how int) error { |
| panic("TODO shutdown") |
| } |
| |
| func Stat(path string, stat *Stat_t) (err error) { |
| fd, err := Open(path, O_RDONLY|O_PATH, 0) |
| if err != nil { |
| return err |
| } |
| err = Fstat(fd, stat) |
| if err2 := Close(fd); err == nil { |
| err = err2 |
| } |
| return err |
| } |
| |
| func Lstat(path string, stat *Stat_t) (err error) { |
| // TODO: adjust when there are symlinks |
| return Stat(path, stat) |
| } |
| |
| func Pread(fd int, p []byte, offset int64) (n int, err error) { |
| if fd == Stdout || fd == Stderr { |
| return 0, ESPIPE |
| } |
| |
| fdsMu.Lock() |
| f := fds[fd-fdsOff] |
| fdsMu.Unlock() |
| |
| // An io.Reader can return n < len(p) and io.EOF. |
| // But this is the POSIX syscall read(3), which |
| // returns nil on eof. |
| n, err = f.ReadAt(p, offset) |
| if err == io.EOF { |
| err = nil |
| } |
| return n, err |
| } |
| |
| func Pwrite(fd int, p []byte, offset int64) (n int, err error) { |
| if fd == Stdout || fd == Stderr { |
| return 0, ESPIPE |
| } |
| |
| fdsMu.Lock() |
| f := fds[fd-fdsOff] |
| fdsMu.Unlock() |
| return f.WriteAt(p, offset) |
| } |
| |
| func Ftruncate(fd int, length int64) (err error) { |
| fdsMu.Lock() |
| f := fds[fd-fdsOff] |
| fdsMu.Unlock() |
| return f.Resize(uint64(length)) |
| } |
| |
| func Truncate(path string, length int64) (err error) { |
| fd, err := Open(path, O_WRONLY, 0) |
| if err != nil { |
| return err |
| } |
| err = Ftruncate(fd, length) |
| if err2 := Close(fd); err == nil { |
| err = err2 |
| } |
| return err |
| } |
| |
| func Readlink(path string, buf []byte) (n int, err error) { |
| return 0, EOPNOTSUPP // no fuchsia support yet |
| } |
| |
| func Symlink(oldpath string, newpath string) (err error) { |
| return EOPNOTSUPP // no fuchsia support yet |
| } |
| |
| func UtimesNano(path string, ts []Timespec) (err error) { |
| f, err := OpenPath(path, 0, 0) |
| if err != nil { |
| return err |
| } |
| defer f.Close() |
| return f.SetAttr(uint32(fidlIo.NodeAttributeFlagsModificationTime), fidlIo.NodeAttributes{ |
| ModificationTime: uint64(TimespecToNsec(ts[1])), |
| }) |
| } |
| |
| func clen(n []byte) int { |
| for i := 0; i < len(n); i++ { |
| if n[i] == 0 { |
| return i |
| } |
| } |
| return len(n) |
| } |
| |
| func ReadDirent(fd int, buf []byte) (n int, err error) { |
| fdsMu.Lock() |
| f := fds[fd-fdsOff] |
| fdsMu.Unlock() |
| dirent, err := f.ReadDirents(uint64(len(buf))) |
| if err != nil { |
| return 0, err |
| } |
| return copy(buf, dirent), nil |
| } |
| |
| func direntIno(buf []byte) (uint64, bool) { |
| return readInt(buf, unsafe.Offsetof(Dirent{}.Ino), unsafe.Sizeof(Dirent{}.Ino)) |
| } |
| |
| func direntReclen(buf []byte) (uint64, bool) { |
| if namelen, ok := direntNamlen(buf); ok { |
| return namelen + uint64(direntSize), true |
| } else { |
| return 0, false |
| } |
| } |
| |
| func direntNamlen(buf []byte) (uint64, bool) { |
| return readInt(buf, unsafe.Offsetof(Dirent{}.Size), unsafe.Sizeof(Dirent{}.Size)) |
| } |
| |
| type Dirent struct { |
| Ino uint64 |
| Size uint8 |
| Type uint8 |
| Name [1]byte |
| } |
| |
| const direntSize = int(unsafe.Offsetof(Dirent{}.Name)) |