blob: 15abdf2cd98c37cb91a8842b63d87cd006846ee8 [file] [log] [blame]
// 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.
// +build !build_with_native_toolchain
package component
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"log"
"runtime/pprof"
"strings"
"syscall"
"syscall/zx"
"syscall/zx/fdio"
"syscall/zx/fidl"
"unsafe"
fidlio "fidl/fuchsia/io"
"fidl/fuchsia/mem"
)
func respond(ctx fidl.Context, flags uint32, req fidlio.NodeWithCtxInterfaceRequest, err error, node fidlio.NodeWithCtx) error {
if err != nil {
defer func() {
_ = req.Close()
}()
}
if flags&fidlio.OpenFlagDescribe != 0 {
proxy := fidlio.NodeEventProxy{Channel: req.Channel}
switch err := err.(type) {
case nil:
info, err := node.Describe(ctx)
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
}
func logError(err error) {
log.Print(err)
}
type Node interface {
getIO() fidlio.NodeWithCtx
addConnection(ctx fidl.Context, flags, mode uint32, req fidlio.NodeWithCtxInterfaceRequest) error
}
type addFn func(fidl.Context, zx.Channel) error
// TODO(fxbug.dev/37419): Remove TransitionalBase after methods landed.
type Service struct {
*fidlio.NodeWithCtxTransitionalBase
AddFn addFn
}
var _ Node = (*Service)(nil)
var _ fidlio.NodeWithCtx = (*Service)(nil)
func (s *Service) getIO() fidlio.NodeWithCtx {
return s
}
func (s *Service) addConnection(ctx fidl.Context, flags, mode uint32, req fidlio.NodeWithCtxInterfaceRequest) error {
// TODO(fxbug.dev/33595): this does not implement the node protocol correctly,
// but matches the behaviour of SDK VFS.
if flags&fidlio.OpenFlagNodeReference != 0 {
stub := fidlio.NodeWithCtxStub{Impl: s}
go ServeExclusive(ctx, &stub, req.Channel, logError)
return respond(ctx, flags, req, nil, s)
}
return respond(ctx, flags, req, s.AddFn(ctx, req.Channel), s)
}
func (s *Service) Clone(ctx fidl.Context, flags uint32, req fidlio.NodeWithCtxInterfaceRequest) error {
return s.addConnection(ctx, flags, 0, req)
}
func (s *Service) Close(fidl.Context) (int32, error) {
return int32(zx.ErrOk), nil
}
func (s *Service) Describe(fidl.Context) (fidlio.NodeInfo, error) {
var nodeInfo fidlio.NodeInfo
nodeInfo.SetService(fidlio.Service{})
return nodeInfo, nil
}
func (s *Service) Sync(fidl.Context) (int32, error) {
return int32(zx.ErrNotSupported), nil
}
func (s *Service) GetAttr(fidl.Context) (int32, fidlio.NodeAttributes, error) {
return int32(zx.ErrOk), fidlio.NodeAttributes{
Mode: fidlio.ModeTypeService,
Id: fidlio.InoUnknown,
LinkCount: 1,
}, nil
}
func (s *Service) SetAttr(_ fidl.Context, flags uint32, attributes fidlio.NodeAttributes) (int32, error) {
return int32(zx.ErrNotSupported), 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)
}
}
var _ Directory = (*pprofDirectory)(nil)
type pprofDirectory struct{}
func (*pprofDirectory) Get(name string) (Node, bool) {
if p := pprof.Lookup(name); p != nil {
return &FileWrapper{
File: &pprofFile{
p: p,
},
}, true
}
return nil, false
}
func (*pprofDirectory) ForEach(fn func(string, Node)) {
for _, p := range pprof.Profiles() {
fn(p.Name(), &FileWrapper{
File: &pprofFile{
p: p,
},
})
}
}
type DirectoryWrapper struct {
Directory Directory
}
var _ Node = (*DirectoryWrapper)(nil)
func (dir *DirectoryWrapper) GetDirectory() fidlio.DirectoryWithCtx {
return &directoryState{DirectoryWrapper: dir}
}
func (dir *DirectoryWrapper) getIO() fidlio.NodeWithCtx {
return dir.GetDirectory()
}
func (dir *DirectoryWrapper) addConnection(ctx fidl.Context, flags, mode uint32, req fidlio.NodeWithCtxInterfaceRequest) error {
ioDir := dir.GetDirectory()
stub := fidlio.DirectoryWithCtxStub{Impl: ioDir}
go ServeExclusive(ctx, &stub, req.Channel, logError)
return respond(ctx, flags, req, nil, ioDir)
}
var _ fidlio.DirectoryWithCtx = (*directoryState)(nil)
// TODO(fxbug.dev/37419): Remove TransitionalBase after methods landed.
type directoryState struct {
*fidlio.DirectoryWithCtxTransitionalBase
*DirectoryWrapper
reading bool
dirents bytes.Buffer
}
func (dirState *directoryState) Clone(ctx fidl.Context, flags uint32, req fidlio.NodeWithCtxInterfaceRequest) error {
return dirState.addConnection(ctx, flags, 0, req)
}
func (dirState *directoryState) Close(fidl.Context) (int32, error) {
return int32(zx.ErrOk), nil
}
func (dirState *directoryState) Describe(fidl.Context) (fidlio.NodeInfo, error) {
var nodeInfo fidlio.NodeInfo
nodeInfo.SetDirectory(fidlio.DirectoryObject{})
return nodeInfo, nil
}
func (dirState *directoryState) Sync(fidl.Context) (int32, error) {
return int32(zx.ErrNotSupported), nil
}
func (dirState *directoryState) GetAttr(fidl.Context) (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(_ fidl.Context, flags uint32, attributes fidlio.NodeAttributes) (int32, error) {
return int32(zx.ErrNotSupported), nil
}
const dot = "."
func (dirState *directoryState) Open(ctx fidl.Context, flags, mode uint32, path string, req fidlio.NodeWithCtxInterfaceRequest) error {
if path == dot {
return dirState.addConnection(ctx, 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.Directory.Get(path[:i]); ok {
node := node.getIO()
if dir, ok := node.(fidlio.DirectoryWithCtx); ok {
return dir.Open(ctx, flags, mode, path[i+len(slash):], req)
}
return respond(ctx, flags, req, &zx.Error{Status: zx.ErrNotDir}, node)
}
} else if node, ok := dirState.Directory.Get(path); ok {
return node.addConnection(ctx, flags, mode, req)
}
return respond(ctx, flags, req, &zx.Error{Status: zx.ErrNotFound}, dirState)
}
func (dirState *directoryState) Unlink(_ fidl.Context, path string) (int32, error) {
return int32(zx.ErrNotSupported), nil
}
func (dirState *directoryState) ReadDirents(ctx fidl.Context, maxOut uint64) (int32, []uint8, error) {
if !dirState.reading {
writeFn := func(name string, node Node) {
ioNode := node.getIO()
status, attr, err := ioNode.GetAttr(ctx)
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)),
}
switch modeType := attr.Mode & fidlio.ModeTypeMask; modeType {
case fidlio.ModeTypeDirectory:
dirent.Type = fidlio.DirentTypeDirectory
case fidlio.ModeTypeFile:
dirent.Type = fidlio.DirentTypeFile
case fidlio.ModeTypeService:
dirent.Type = fidlio.DirentTypeService
default:
panic(fmt.Sprintf("unknown mode type: %b", modeType))
}
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.Directory.ForEach(writeFn)
dirState.reading = true
} else if dirState.dirents.Len() == 0 {
status, err := dirState.Rewind(ctx)
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(fidl.Context) (int32, error) {
dirState.reading = false
dirState.dirents.Reset()
return int32(zx.ErrOk), nil
}
func (dirState *directoryState) GetToken(fidl.Context) (int32, zx.Handle, error) {
return int32(zx.ErrNotSupported), zx.HandleInvalid, nil
}
func (dirState *directoryState) Rename(_ fidl.Context, src string, dstParentToken zx.Handle, dst string) (int32, error) {
return int32(zx.ErrNotSupported), nil
}
func (dirState *directoryState) Link(_ fidl.Context, src string, dstParentToken zx.Handle, dst string) (int32, error) {
return int32(zx.ErrNotSupported), nil
}
func (dirState *directoryState) Watch(_ fidl.Context, mask uint32, options uint32, watcher zx.Channel) (int32, error) {
if err := watcher.Close(); err != nil {
logError(err)
}
return int32(zx.ErrNotSupported), nil
}
type File interface {
GetReader() (Reader, uint64)
GetVMO() zx.VMO
}
var _ File = (*pprofFile)(nil)
type pprofFile struct {
p *pprof.Profile
}
func (p *pprofFile) GetReader() (Reader, uint64) {
var b bytes.Buffer
if err := p.p.WriteTo(&b, 0); err != nil {
panic(err)
}
return bytes.NewReader(b.Bytes()), uint64(b.Len())
}
func (*pprofFile) GetVMO() zx.VMO {
return zx.VMO(zx.HandleInvalid)
}
var _ Node = (*FileWrapper)(nil)
type FileWrapper struct {
File File
}
func (file *FileWrapper) getFile() fidlio.FileWithCtx {
reader, size := file.File.GetReader()
return &fileState{
FileWrapper: file,
reader: reader,
size: size,
vmo: file.File.GetVMO(),
}
}
func (file *FileWrapper) getIO() fidlio.NodeWithCtx {
return file.getFile()
}
func (file *FileWrapper) addConnection(ctx fidl.Context, flags, mode uint32, req fidlio.NodeWithCtxInterfaceRequest) error {
ioFile := file.getFile()
stub := fidlio.FileWithCtxStub{Impl: ioFile}
go ServeExclusive(ctx, &stub, req.Channel, logError)
return respond(ctx, flags, req, nil, ioFile)
}
var _ fidlio.FileWithCtx = (*fileState)(nil)
type Reader interface {
io.Reader
io.ReaderAt
io.Seeker
}
// TODO(fxbug.dev/37419): Remove TransitionalBase after methods landed.
type fileState struct {
*fidlio.FileWithCtxTransitionalBase
*FileWrapper
reader Reader
size uint64
vmo zx.VMO
}
func (fState *fileState) Clone(ctx fidl.Context, flags uint32, req fidlio.NodeWithCtxInterfaceRequest) error {
return fState.addConnection(ctx, flags, 0, req)
}
func (fState *fileState) Close(fidl.Context) (int32, error) {
return int32(zx.ErrOk), nil
}
func (fState *fileState) Describe(fidl.Context) (fidlio.NodeInfo, error) {
var nodeInfo fidlio.NodeInfo
if fState.vmo.Handle().IsValid() {
h, err := fState.vmo.Handle().Duplicate(zx.RightSameRights)
if err != nil {
return nodeInfo, err
}
nodeInfo.SetVmofile(fidlio.Vmofile{
Vmo: zx.VMO(h),
Offset: 0,
Length: fState.size,
})
} else {
nodeInfo.SetFile(fidlio.FileObject{})
}
return nodeInfo, nil
}
func (fState *fileState) Sync(fidl.Context) (int32, error) {
return int32(zx.ErrNotSupported), nil
}
func (fState *fileState) GetAttr(fidl.Context) (int32, fidlio.NodeAttributes, error) {
return int32(zx.ErrOk), fidlio.NodeAttributes{
Mode: fidlio.ModeTypeFile | uint32(fdio.VtypeIRUSR),
Id: fidlio.InoUnknown,
ContentSize: fState.size,
LinkCount: 1,
}, nil
}
func (fState *fileState) SetAttr(_ fidl.Context, flags uint32, attributes fidlio.NodeAttributes) (int32, error) {
return int32(zx.ErrNotSupported), nil
}
func (fState *fileState) Read(_ fidl.Context, count uint64) (int32, []uint8, error) {
if l := fState.size; 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(_ fidl.Context, count uint64, offset uint64) (int32, []uint8, error) {
if l := 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(_ fidl.Context, data []uint8) (int32, uint64, error) {
return int32(zx.ErrNotSupported), 0, nil
}
func (fState *fileState) WriteAt(_ fidl.Context, data []uint8, offset uint64) (int32, uint64, error) {
return int32(zx.ErrNotSupported), 0, nil
}
func (fState *fileState) Seek(_ fidl.Context, 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(_ fidl.Context, length uint64) (int32, error) {
return int32(zx.ErrNotSupported), nil
}
func (fState *fileState) GetFlags(fidl.Context) (int32, uint32, error) {
return int32(zx.ErrNotSupported), 0, nil
}
func (fState *fileState) SetFlags(_ fidl.Context, flags uint32) (int32, error) {
return int32(zx.ErrNotSupported), nil
}
func (fState *fileState) GetBuffer(_ fidl.Context, flags uint32) (int32, *mem.Buffer, error) {
return int32(zx.ErrNotSupported), nil, nil
}