blob: 8d44d27ca010354f3a24095ab8eee1dbe36d4093 [file] [log] [blame]
// Copyright 2017 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's distribution tools attempt to compile everything; this file
// depends on types that don't compile in not-Fuchsia.
// +build fuchsia
package fdio
import (
"sync"
"syscall/zx"
"syscall/zx/internal/context"
"syscall/zx/io"
"syscall/zx/io2"
)
type NS struct {
mu sync.Mutex
m map[string]zx.Handle
}
func NewNS() *NS {
return &NS{m: make(map[string]zx.Handle)}
}
func NewNSFromMap(m map[string]zx.Handle) (*NS, error) {
if m == nil {
return nil, &zx.Error{Status: zx.ErrInvalidArgs, Text: "namespace.NewFromNSMAP"}
}
ns := &NS{m: make(map[string]zx.Handle)}
for k, v := range m {
ns.m[k] = v
}
return ns, nil
}
func (ns *NS) Bind(p string, h zx.Handle) {
ns.mu.Lock()
ns.m[p] = h
ns.mu.Unlock()
}
func (ns *NS) Connect(p string, h zx.Handle) error {
if p[0] != '/' {
h.Close()
return &zx.Error{Status: zx.ErrNotFound, Text: "namespace.Connect"}
}
node := p
for {
ns.mu.Lock()
svcHandle := ns.m[node]
ns.mu.Unlock()
if svcHandle.IsValid() {
rest := trimPrefix(p, node)
rest = trimPrefix(rest, "/")
return ServiceConnectAt(svcHandle, rest, h)
}
if node == "/" {
break
}
node = pathDir(node)
}
h.Close()
return &zx.Error{Status: zx.ErrNotFound, Text: "namespace.Connect"}
}
func (ns *NS) OpenRoot() (FDIO, error) {
root := &vnode{}
ns.mu.Lock()
defer ns.mu.Unlock()
for path, h := range ns.m {
if path == "" || path[0] != '/' {
return nil, &zx.Error{Status: zx.ErrBadPath, Text: "fdio.OpenRoot"}
}
vn := root
path = path[1:]
for path != "" {
name, remaining := pathSplitForward(path)
vn = vn.getOrAddChild(name)
path = remaining
}
if vn.remote.IsValid() {
return nil, &zx.Error{Status: zx.ErrAlreadyExists, Text: "fdio.OpenRoot"}
}
vn.remote = Directory{Node: Node{NodeWithCtxInterface: &io.NodeWithCtxInterface{Channel: zx.Channel(h)}}}
}
return &dir{root}, nil
}
// vnode is a virtual node in the namespace "filesystem".
type vnode struct {
child *vnode
parent *vnode
next *vnode
remote Directory
name string
}
func (vn *vnode) getOrAddChild(name string) *vnode {
child := vn.getChild(name)
if child == nil {
child = &vnode{
parent: vn,
next: vn.child,
name: name,
}
vn.child = child
}
return child
}
func (vn *vnode) getChild(name string) *vnode {
for vn = vn.child; vn != nil; vn = vn.next {
if vn.name == name {
return vn
}
}
return nil
}
// dir represents a directory in the namespace.
type dir struct {
vn *vnode
}
// Handle returns the underlying handle as an untyped handle.
func (d *dir) Handles() []zx.Handle {
return d.vn.remote.Handles()
}
// Clone makes a clone of the dir, if possible.
func (d *dir) Clone() (FDIO, error) {
if !d.vn.remote.IsValid() {
return nil, &zx.Error{Status: zx.ErrNotSupported, Text: "fdio.ns.dir"}
}
return d.vn.remote.Clone()
}
// Close closes the dir.
func (d *dir) Close() error {
if !d.vn.remote.IsValid() {
return nil
}
return d.vn.remote.Close()
}
// Sync implements FDIO for dir.
func (d *dir) Sync() error {
return &zx.Error{Status: zx.ErrNotSupported, Text: "fdio.ns.dir"}
}
// GetAttr implements FDIO for dir.
func (d *dir) GetAttr() (io.NodeAttributes, error) {
return io.NodeAttributes{
Mode: uint32(io.ModeTypeDirectory) | uint32(VtypeIRUSR),
Id: 1,
LinkCount: 1,
}, nil
}
// SetAttr implements FDIO for dir.
func (d *dir) SetAttr(flags uint32, attr io.NodeAttributes) error {
return &zx.Error{Status: zx.ErrNotSupported, Text: "fdio.ns.dir"}
}
func (d *dir) Read(data []byte) (int, error) {
return 0, &zx.Error{Status: zx.ErrNotSupported, Text: "fdio.dir"}
}
func (d *dir) ReadAt(data []byte, off int64) (int, error) {
return 0, &zx.Error{Status: zx.ErrNotSupported, Text: "fdio.dir"}
}
func (d *dir) Write(data []byte) (n int, err error) {
return 0, &zx.Error{Status: zx.ErrNotSupported, Text: "fdio.dir"}
}
func (d *dir) WriteAt(data []byte, off int64) (int, error) {
return 0, &zx.Error{Status: zx.ErrNotSupported, Text: "fdio.dir"}
}
func (d *dir) Seek(offset int64, whence int) (int64, error) {
if remote := d.vn.remote; remote.IsValid() {
return remote.Seek(offset, whence)
}
if offset == 0 && whence == 0 {
return 0, nil
}
return 0, &zx.Error{Status: zx.ErrNotSupported, Text: "fdio.dir"}
}
// Truncate implements FDIO for dir.
func (d *dir) Truncate(length uint64) error {
return &zx.Error{Status: zx.ErrNotSupported, Text: "fdio.ns.dir"}
}
func (d *dir) getVnode(path string) (vn *vnode, rpath string, isRemote bool, err error) {
vn = d.vn
if path == "." || path == "" {
if vn.remote.IsValid() {
return vn, ".", true, nil
}
return vn, "", false, nil
}
originalPath := path
var savedVn *vnode
var savedPath string
for {
name, remaining := pathSplitForward(path)
if name == "" {
return nil, "", false, &zx.Error{Status: zx.ErrBadPath, Text: path}
}
child := vn.getChild(name)
if child != nil {
if remaining == "" {
// We've resolved all the path segments and found the vnode we're
// looking for.
if child.remote.IsValid() {
return child, ".", true, nil
} else {
return child, "", false, nil
}
} else {
// We've got more path to resolve.
vn = child
path = remaining
// If this child has a remote file system, we might hand off to
// this remote if we don't find anything more specific below
// this vnode.
if child.remote.IsValid() {
savedVn = vn
savedPath = path
}
continue
}
}
// We've reached the end of our local vnode structure with more
// path to resolve.
if vn.remote.IsValid() {
// We've got a remote file system at this vnode. We can hand off
// directly to that file system.
return vn, path, true, nil
}
// We saw a remote file system earlier in our walk that might have this
// path. Let's hand off to it.
if savedVn != nil {
return savedVn, savedPath, true, nil
}
// There is no remote file system to resolve the path against,
// which means we failed to find what we were looking for.
return nil, "", false, &zx.Error{Status: zx.ErrNotFound, Text: originalPath}
}
}
// Open implements FDIO for dir.
func (d *dir) Open(pathname string, flags uint32, mode uint32) (FDIO, error) {
vn, relp, isRemote, err := d.getVnode(pathname)
if err != nil {
return nil, err
}
if isRemote {
return vn.remote.Open(relp, flags, mode)
}
return &dir{vn}, nil
}
func (d *dir) openParent(pathname string) (FDIO, string, error) {
dirpath, name := pathSplit(pathname)
parent, err := d.Open(
pathDir(dirpath),
io.OpenRightReadable|io.OpenFlagDirectory|io.OpenFlagPosix,
S_IFDIR)
if err != nil {
return nil, "", err
}
return parent, name, err
}
// Link implements FDIO for dir.
func (d *dir) Link(oldpath, newpath string) error {
oldparent, oldname, err := d.openParent(oldpath)
if err != nil {
return err
}
defer oldparent.Close()
newparent, newname, err := d.openParent(newpath)
if err != nil {
return err
}
defer newparent.Close()
olddir, ok := oldparent.(*Directory)
if !ok {
return &zx.Error{Status: zx.ErrNotSupported, Text: "fdio.ns.dir"}
}
newdir, ok := newparent.(*Directory)
if !ok {
return &zx.Error{Status: zx.ErrNotSupported, Text: "fdio.ns.dir"}
}
token, err := newdir.getToken()
if err != nil {
return err
}
status, err := olddir.directoryInterface().Link(context.Background(), oldname, token, newname)
if err != nil {
return err
} else if zx.Status(status) != zx.ErrOk {
return &zx.Error{Status: zx.Status(status), Text: "fdio.ns.dir"}
}
return nil
}
// Rename implements FDIO for dir.
func (d *dir) Rename(oldpath, newpath string) error {
oldf, oldname, err := d.openParent(oldpath)
if err != nil {
return err
}
defer oldf.Close()
newf, newname, err := d.openParent(newpath)
if err != nil {
return err
}
defer newf.Close()
olddir, ok := oldf.(*Directory)
if !ok {
return &zx.Error{Status: zx.ErrNotSupported, Text: "fdio.ns.dir"}
}
newdir, ok := newf.(*Directory)
if !ok {
return &zx.Error{Status: zx.ErrNotSupported, Text: "fdio.ns.dir"}
}
token, err := newdir.getToken()
if err != nil {
return err
}
status, err := olddir.directoryInterface().Rename2(
context.Background(), oldname, zx.Event(token), newname)
if err != nil {
return err
} else if status.Which() == io.DirectoryRename2ResultErr {
return &zx.Error{Status: zx.Status(status.Err), Text: "fdio.ns.dir"}
}
return nil
}
// Unlink implements FDIO for dir.
func (d *dir) Unlink(pathname string) error {
parent, name, err := d.openParent(pathname)
if err != nil {
return err
}
defer parent.Close()
parentdir, ok := parent.(*Directory)
if !ok {
return &zx.Error{Status: zx.ErrNotSupported, Text: "fdio.ns.dir"}
}
status, err := parentdir.directoryInterface().Unlink(context.Background(), name, io2.UnlinkOptions{})
return zxStatusToError(status.Err, err, "fdio.ns.dir")
}
// ReadDirents implements FDIO for dir.
func (d *dir) ReadDirents(max uint64) ([]byte, error) {
if remote := d.vn.remote; remote.IsValid() {
return remote.ReadDirents(max)
}
return nil, nil
}
// Rewind implements FDIO for dir.
func (d *dir) Rewind() error {
if remote := d.vn.remote; remote.IsValid() {
return remote.Rewind()
}
return nil
}