| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package context |
| |
| import ( |
| "bytes" |
| "encoding/binary" |
| "io" |
| "runtime" |
| "strings" |
| "syscall" |
| "syscall/zx" |
| "syscall/zx/fdio" |
| "syscall/zx/fidl" |
| "unsafe" |
| |
| fidlio "fidl/fuchsia/io" |
| "fidl/fuchsia/mem" |
| ) |
| |
| func respond(flags uint32, req fidlio.NodeInterfaceRequest, err error, node fidlio.Node) error { |
| if flags&fidlio.OpenFlagDescribe != 0 { |
| proxy := fidlio.NodeEventProxy{Channel: req.Channel} |
| switch err := err.(type) { |
| case nil: |
| info, err := node.Describe() |
| if err != nil { |
| panic(err) |
| } |
| return proxy.OnOpen(int32(zx.ErrOk), &info) |
| case *zx.Error: |
| return proxy.OnOpen(int32(err.Status), nil) |
| default: |
| panic(err) |
| } |
| } |
| return nil |
| } |
| |
| type Node interface { |
| getIO() fidlio.Node |
| addConnection(flags, mode uint32, req fidlio.NodeInterfaceRequest) error |
| } |
| |
| type ServiceFn func(zx.Channel) error |
| |
| var _ Node = (*ServiceFn)(nil) |
| var _ fidlio.Node = (*ServiceFn)(nil) |
| |
| func (s ServiceFn) getIO() fidlio.Node { |
| return s |
| } |
| |
| func (s ServiceFn) addConnection(flags, mode uint32, req fidlio.NodeInterfaceRequest) error { |
| // TODO(ZX-3805): this does not implement the node protocol correctly, |
| // but matches the behaviour of SDK FVS. |
| if flags&fidlio.OpenFlagNodeReference != 0 { |
| b := fidl.Binding{ |
| Stub: &fidlio.NodeStub{Impl: s}, |
| Channel: req.Channel, |
| } |
| return respond(flags, req, b.Init(func(error) { |
| if err := b.Close(); err != nil { |
| panic(err) |
| } |
| }), s) |
| } |
| |
| return respond(flags, req, s(req.Channel), s) |
| } |
| |
| func (s ServiceFn) Clone(flags uint32, req fidlio.NodeInterfaceRequest) error { |
| return s.addConnection(flags, 0, req) |
| } |
| |
| func (s ServiceFn) Close() (int32, error) { |
| return int32(zx.ErrOk), nil |
| } |
| |
| func (s ServiceFn) Describe() (fidlio.NodeInfo, error) { |
| return fidlio.NodeInfo{NodeInfoTag: fidlio.NodeInfoService}, nil |
| } |
| |
| func (s ServiceFn) Sync() (int32, error) { |
| return int32(zx.ErrNotSupported), nil |
| } |
| |
| func (s ServiceFn) GetAttr() (int32, fidlio.NodeAttributes, error) { |
| return int32(zx.ErrOk), fidlio.NodeAttributes{ |
| Mode: fidlio.ModeTypeService, |
| Id: fidlio.InoUnknown, |
| LinkCount: 1, |
| }, nil |
| } |
| |
| func (s ServiceFn) SetAttr(flags uint32, attributes fidlio.NodeAttributes) (int32, error) { |
| return int32(zx.ErrNotSupported), nil |
| } |
| |
| func (s ServiceFn) Ioctl(opcode uint32, maxOut uint64, handles []zx.Handle, in []uint8) (int32, []zx.Handle, []uint8, error) { |
| return int32(zx.ErrNotSupported), nil, nil, nil |
| } |
| |
| type Directory interface { |
| Get(string) (Node, bool) |
| ForEach(func(string, Node)) |
| } |
| |
| var _ Directory = mapDirectory(nil) |
| |
| type mapDirectory map[string]Node |
| |
| func (md mapDirectory) Get(name string) (Node, bool) { |
| node, ok := md[name] |
| return node, ok |
| } |
| |
| func (md mapDirectory) ForEach(fn func(string, Node)) { |
| for name, node := range md { |
| fn(name, node) |
| } |
| } |
| |
| type DirectoryWrapper struct { |
| Directory |
| } |
| |
| var _ Node = (*DirectoryWrapper)(nil) |
| |
| func (dir *DirectoryWrapper) GetDirectory() fidlio.Directory { |
| return &directoryState{DirectoryWrapper: dir} |
| } |
| |
| func (dir *DirectoryWrapper) getIO() fidlio.Node { |
| return dir.GetDirectory() |
| } |
| |
| func (dir *DirectoryWrapper) addConnection(flags, mode uint32, req fidlio.NodeInterfaceRequest) error { |
| ioDir := dir.GetDirectory() |
| b := fidl.Binding{ |
| Stub: &fidlio.DirectoryStub{Impl: ioDir}, |
| Channel: req.Channel, |
| } |
| return respond(flags, req, b.Init(func(error) { |
| if err := b.Close(); err != nil { |
| panic(err) |
| } |
| }), ioDir) |
| } |
| |
| var _ fidlio.Directory = (*directoryState)(nil) |
| |
| type directoryState struct { |
| *DirectoryWrapper |
| |
| reading bool |
| dirents bytes.Buffer |
| } |
| |
| func (dirState *directoryState) Clone(flags uint32, req fidlio.NodeInterfaceRequest) error { |
| return dirState.addConnection(flags, 0, req) |
| } |
| |
| func (dirState *directoryState) Close() (int32, error) { |
| return int32(zx.ErrOk), nil |
| } |
| |
| func (dirState *directoryState) Describe() (fidlio.NodeInfo, error) { |
| return fidlio.NodeInfo{NodeInfoTag: fidlio.NodeInfoDirectory}, nil |
| } |
| |
| func (dirState *directoryState) Sync() (int32, error) { |
| return int32(zx.ErrNotSupported), nil |
| } |
| |
| func (dirState *directoryState) GetAttr() (int32, fidlio.NodeAttributes, error) { |
| return int32(zx.ErrOk), fidlio.NodeAttributes{ |
| Mode: fidlio.ModeTypeDirectory | uint32(fdio.VtypeIRUSR), |
| Id: fidlio.InoUnknown, |
| LinkCount: 1, |
| }, nil |
| } |
| |
| func (dirState *directoryState) SetAttr(flags uint32, attributes fidlio.NodeAttributes) (int32, error) { |
| return int32(zx.ErrNotSupported), nil |
| } |
| |
| func (dirState *directoryState) Ioctl(opcode uint32, maxOut uint64, handles []zx.Handle, in []uint8) (int32, []zx.Handle, []uint8, error) { |
| return int32(zx.ErrNotSupported), nil, nil, nil |
| } |
| |
| const dot = "." |
| |
| func (dirState *directoryState) Open(flags, mode uint32, path string, req fidlio.NodeInterfaceRequest) error { |
| if path == dot { |
| return dirState.addConnection(flags, mode, req) |
| } |
| const slash = "/" |
| if strings.HasSuffix(path, slash) { |
| mode |= fidlio.ModeTypeDirectory |
| path = path[:len(path)-len(slash)] |
| } |
| |
| if i := strings.Index(path, slash); i != -1 { |
| if node, ok := dirState.Get(path[:i]); ok { |
| node := node.getIO() |
| if dir, ok := node.(fidlio.Directory); ok { |
| return dir.Open(flags, mode, path[i+len(slash):], req) |
| } |
| return respond(flags, req, &zx.Error{Status: zx.ErrNotDir}, node) |
| } |
| } else if node, ok := dirState.Get(path); ok { |
| return node.addConnection(flags, mode, req) |
| } |
| |
| return respond(flags, req, &zx.Error{Status: zx.ErrNotFound}, dirState) |
| } |
| |
| func (dirState *directoryState) Unlink(path string) (int32, error) { |
| return int32(zx.ErrNotSupported), nil |
| } |
| |
| func (dirState *directoryState) ReadDirents(maxOut uint64) (int32, []uint8, error) { |
| if !dirState.reading { |
| writeFn := func(name string, node Node) { |
| ioNode := node.getIO() |
| status, attr, err := ioNode.GetAttr() |
| if err != nil { |
| panic(err) |
| } |
| if status := zx.Status(status); status != zx.ErrOk { |
| panic(status) |
| } |
| dirent := syscall.Dirent{ |
| Ino: attr.Id, |
| Size: uint8(len(name)), |
| Type: uint8(attr.Mode & fidlio.ModeTypeMask), |
| } |
| if err := binary.Write(&dirState.dirents, binary.LittleEndian, dirent); err != nil { |
| panic(err) |
| } |
| dirState.dirents.Truncate(dirState.dirents.Len() - int(unsafe.Sizeof(syscall.Dirent{}.Name))) |
| if _, err := dirState.dirents.WriteString(name); err != nil { |
| panic(err) |
| } |
| } |
| writeFn(dot, dirState) |
| dirState.ForEach(writeFn) |
| dirState.reading = true |
| } else if dirState.dirents.Len() == 0 { |
| status, err := dirState.Rewind() |
| if err != nil { |
| panic(err) |
| } |
| if status := zx.Status(status); status != zx.ErrOk { |
| panic(status) |
| } |
| } |
| return int32(zx.ErrOk), dirState.dirents.Next(int(maxOut)), nil |
| } |
| |
| func (dirState *directoryState) Rewind() (int32, error) { |
| dirState.reading = false |
| dirState.dirents.Reset() |
| return int32(zx.ErrOk), nil |
| } |
| |
| func (dirState *directoryState) GetToken() (int32, zx.Handle, error) { |
| return int32(zx.ErrNotSupported), zx.HandleInvalid, nil |
| } |
| |
| func (dirState *directoryState) Rename(src string, dstParentToken zx.Handle, dst string) (int32, error) { |
| return int32(zx.ErrNotSupported), nil |
| } |
| |
| func (dirState *directoryState) Link(src string, dstParentToken zx.Handle, dst string) (int32, error) { |
| return int32(zx.ErrNotSupported), nil |
| } |
| |
| func (dirState *directoryState) Watch(mask uint32, options uint32, watcher zx.Channel) (int32, error) { |
| if err := watcher.Close(); err != nil { |
| _ = err |
| } |
| return int32(zx.ErrNotSupported), nil |
| } |
| |
| type File interface { |
| GetBytes() []byte |
| } |
| |
| type goroutineFile struct{} |
| |
| func (*goroutineFile) GetBytes() []byte { |
| buf := make([]byte, 1024) |
| for { |
| if n := runtime.Stack(buf, true); n < len(buf) { |
| return buf[:n] |
| } |
| buf = make([]byte, 2*len(buf)) |
| } |
| } |
| |
| var _ Node = (*FileWrapper)(nil) |
| |
| type FileWrapper struct { |
| File |
| } |
| |
| func (file *FileWrapper) getFile() fidlio.File { |
| buf := file.GetBytes() |
| fState := fileState{FileWrapper: file} |
| fState.Reset(buf) |
| return &fState |
| } |
| |
| func (file *FileWrapper) getIO() fidlio.Node { |
| return file.getFile() |
| } |
| |
| func (file *FileWrapper) addConnection(flags, mode uint32, req fidlio.NodeInterfaceRequest) error { |
| ioFile := file.getFile() |
| b := fidl.Binding{ |
| Stub: &fidlio.FileStub{Impl: ioFile}, |
| Channel: req.Channel, |
| } |
| return respond(flags, req, b.Init(func(error) { |
| if err := b.Close(); err != nil { |
| panic(err) |
| } |
| }), ioFile) |
| } |
| |
| var _ fidlio.File = (*fileState)(nil) |
| |
| type fileState struct { |
| *FileWrapper |
| bytes.Reader |
| } |
| |
| func (fState *fileState) Clone(flags uint32, req fidlio.NodeInterfaceRequest) error { |
| return fState.addConnection(flags, 0, req) |
| } |
| |
| func (fState *fileState) Close() (int32, error) { |
| return int32(zx.ErrOk), nil |
| } |
| |
| func (fState *fileState) Describe() (fidlio.NodeInfo, error) { |
| return fidlio.NodeInfo{NodeInfoTag: fidlio.NodeInfoFile}, nil |
| } |
| |
| func (fState *fileState) Sync() (int32, error) { |
| return int32(zx.ErrNotSupported), nil |
| } |
| |
| func (fState *fileState) GetAttr() (int32, fidlio.NodeAttributes, error) { |
| return int32(zx.ErrOk), fidlio.NodeAttributes{ |
| Mode: fidlio.ModeTypeFile | uint32(fdio.VtypeIRUSR), |
| Id: fidlio.InoUnknown, |
| ContentSize: uint64(fState.Size()), |
| LinkCount: 1, |
| }, nil |
| } |
| |
| func (fState *fileState) SetAttr(flags uint32, attributes fidlio.NodeAttributes) (int32, error) { |
| return int32(zx.ErrNotSupported), nil |
| } |
| |
| func (fState *fileState) Ioctl(opcode uint32, maxOut uint64, handles []zx.Handle, in []uint8) (int32, []zx.Handle, []uint8, error) { |
| return int32(zx.ErrNotSupported), nil, nil, nil |
| } |
| |
| func (fState *fileState) Read(count uint64) (int32, []uint8, error) { |
| if l := uint64(fState.Len()); l < count { |
| count = l |
| } |
| b := make([]byte, count) |
| n, err := fState.Reader.Read(b) |
| if err != nil && err != io.EOF { |
| return 0, nil, err |
| } |
| b = b[:n] |
| return int32(zx.ErrOk), b, nil |
| } |
| |
| func (fState *fileState) ReadAt(count uint64, offset uint64) (int32, []uint8, error) { |
| if l := uint64(fState.Size()) - offset; l < count { |
| count = l |
| } |
| b := make([]byte, count) |
| n, err := fState.Reader.ReadAt(b, int64(offset)) |
| if err != nil && err != io.EOF { |
| return 0, nil, err |
| } |
| b = b[:n] |
| return int32(zx.ErrOk), b, nil |
| } |
| |
| func (fState *fileState) Write(data []uint8) (int32, uint64, error) { |
| return int32(zx.ErrNotSupported), 0, nil |
| } |
| |
| func (fState *fileState) WriteAt(data []uint8, offset uint64) (int32, uint64, error) { |
| return int32(zx.ErrNotSupported), 0, nil |
| } |
| |
| func (fState *fileState) Seek(offset int64, start fidlio.SeekOrigin) (int32, uint64, error) { |
| n, err := fState.Reader.Seek(offset, int(start)) |
| return int32(zx.ErrOk), uint64(n), err |
| } |
| |
| func (fState *fileState) Truncate(length uint64) (int32, error) { |
| return int32(zx.ErrNotSupported), nil |
| } |
| |
| func (fState *fileState) GetFlags() (int32, uint32, error) { |
| return int32(zx.ErrNotSupported), 0, nil |
| } |
| |
| func (fState *fileState) SetFlags(flags uint32) (int32, error) { |
| return int32(zx.ErrNotSupported), nil |
| } |
| |
| func (fState *fileState) GetBuffer(flags uint32) (int32, *mem.Buffer, error) { |
| return int32(zx.ErrNotSupported), nil, nil |
| } |