blob: 7dbf63594dc5d85c8d9cbd17b5441e443e9c0d59 [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.
// +build fuchsia
package fdio
import (
"path"
"strings"
"sync"
"syscall/zx"
"syscall/zx/fidl"
"syscall/zx/io"
)
func splitPath(path string) (name string, remaining string) {
slash := strings.IndexByte(path, '/')
if slash == 0 || slash == len(path)-1 {
return "", ""
} else if slash == -1 {
return path, ""
} else {
return path[:slash], path[slash+1:]
}
}
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 := strings.TrimPrefix(p, node)
rest = strings.TrimPrefix(rest, "/")
return ServiceConnectAt(svcHandle, rest, h)
}
if node == "/" {
break
}
node = path.Dir(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 := splitPath(path)
vn = vn.getOrAddChild(name)
path = remaining
}
if vn.remote.IsValid() {
return nil, zx.Error{Status: zx.ErrAlreadyExists, Text: "fdio.OpenRoot"}
}
obj := (*io.ObjectInterface)(&fidl.Proxy{Channel: zx.Channel(h)})
vn.remote = Directory{Node: Node{Object: Object{obj}}}
}
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 {
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.KModeTypeDirectory) | 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"}
}
// Ioctl implements FDIO for dir.
func (d *dir) Ioctl(op uint32, max uint64, in []byte, handles []zx.Handle) ([]byte, []zx.Handle, error) {
return nil, nil, 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) {
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 := splitPath(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 := path.Split(pathname)
parent, err := d.Open(path.Dir(dirpath), io.KOpenRightReadable, io.KOpenFlagDirectory)
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(oldname, token, newname)
if err != nil {
return err
} else if status != zx.ErrOk {
return zx.Error{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().Rename(oldname, token, newname)
if err != nil {
return err
} else if status != zx.ErrOk {
return zx.Error{Status: status, 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"}
}
if status, err := parentdir.DirectoryInterface().Unlink(name); err != nil {
return err
} else if status != zx.ErrOk {
return zx.Error{Status: status, Text: "fdio.ns.dir"}
}
return nil
}
// ReadDirents implements FDIO for dir.
func (d *dir) ReadDirents(max uint64) ([]byte, error) {
return nil, zx.Error{Status: zx.ErrNotSupported, Text: "fdio.ns.dir"}
}
// Rewind implements FDIO for dir.
func (d *dir) Rewind() error {
return zx.Error{Status: zx.ErrNotSupported, Text: "fdio.ns.dir"}
}