| // 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" |
| ) |
| |
| 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) { |
| 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().Rename(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 |
| } |
| |
| // 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(context.Background(), name); err != nil { |
| return err |
| } else if zx.Status(status) != zx.ErrOk { |
| return &zx.Error{Status: zx.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"} |
| } |