|  | // Copyright 2020 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 rpc | 
|  |  | 
|  | import ( | 
|  | "context" | 
|  | "fmt" | 
|  | "reflect" | 
|  | "syscall" | 
|  | "syscall/zx" | 
|  | "testing" | 
|  | "time" | 
|  | "unsafe" | 
|  |  | 
|  | "fidl/fuchsia/io" | 
|  |  | 
|  | "go.fuchsia.dev/fuchsia/src/lib/thinfs/fs" | 
|  | ) | 
|  |  | 
|  | type dummyFs struct { | 
|  | rootDir fs.Directory | 
|  | } | 
|  |  | 
|  | func (d *dummyFs) Blockcount() int64           { return 0 } | 
|  | func (d *dummyFs) Blocksize() int64            { return 0 } | 
|  | func (d *dummyFs) Size() int64                 { return 0 } | 
|  | func (d *dummyFs) Close() error                { return nil } | 
|  | func (d *dummyFs) RootDirectory() fs.Directory { return d.rootDir } | 
|  | func (d *dummyFs) Type() string                { return "dummy" } | 
|  | func (d *dummyFs) FreeSize() int64             { return 0 } | 
|  |  | 
|  | func TestCookies(t *testing.T) { | 
|  | c1, c2, err := zx.NewChannel(0) | 
|  | if err != nil { | 
|  | t.Fatalf("failed to create channel: %v", err) | 
|  | } | 
|  | defer c1.Close() | 
|  | defer c2.Close() | 
|  |  | 
|  | vfs, err := NewServer(&dummyFs{rootDir: nil}, c2) | 
|  | if err != nil { | 
|  | t.Fatalf("failed to create server: %v", err) | 
|  | } | 
|  | defer vfs.fs.Close() | 
|  |  | 
|  | if len(vfs.dirs) != 1 { | 
|  | t.Fatalf("Unexpected number of directories. Want %d, got %d", 1, len(vfs.dirs)) | 
|  | } | 
|  |  | 
|  | for _, dir := range vfs.dirs { | 
|  | res, h, err := dir.GetToken(nil) | 
|  | if err != nil { | 
|  | t.Fatalf("GetToken(nil) failed: %v", err) | 
|  | } | 
|  | if zx.Status(res) != zx.ErrOk { | 
|  | t.Fatalf("GetToken(nil) returned wrong value. Want %v, got %v", zx.ErrOk, zx.Status(res)) | 
|  | } | 
|  |  | 
|  | dir.setCookie(32) | 
|  | if dir.getCookie(h) != 32 { | 
|  | t.Fatalf("Wrong Cookie retrieved. Want %d, got %d", 32, dir.getCookie(h)) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | type dummyDirectory struct { | 
|  | dirents []fs.Dirent | 
|  | } | 
|  |  | 
|  | func (d *dummyDirectory) Close() error                                         { return nil } | 
|  | func (d *dummyDirectory) Touch(lastAccess, lastModified time.Time) error       { return nil } | 
|  | func (d *dummyDirectory) Dup() (fs.Directory, error)                           { return d, nil } | 
|  | func (d *dummyDirectory) Read() ([]fs.Dirent, error)                           { return d.dirents, nil } | 
|  | func (d *dummyDirectory) Rename(dstparent fs.Directory, src, dst string) error { return nil } | 
|  | func (d *dummyDirectory) Sync() error                                          { return nil } | 
|  | func (d *dummyDirectory) Unlink(target string) error                           { return nil } | 
|  | func (d *dummyDirectory) Stat() (int64, time.Time, time.Time, error) { | 
|  | return 0, time.Unix(0, 0), time.Unix(0, 0), nil | 
|  | } | 
|  | func (d *dummyDirectory) Open(name string, flags fs.OpenFlags) (fs.File, fs.Directory, *fs.Remote, error) { | 
|  | return nil, nil, nil, nil | 
|  | } | 
|  |  | 
|  | type fileDirEnt string | 
|  |  | 
|  | func (d fileDirEnt) GetType() fs.FileType { return fs.FileTypeRegularFile } | 
|  | func (d fileDirEnt) GetIno() uint64       { return io.InoUnknown } | 
|  | func (d fileDirEnt) GetName() string      { return string(d) } | 
|  |  | 
|  | func parseDirectoryEntryNames(t *testing.T, bytes []byte) []string { | 
|  | const direntNameSizeOffset = int(unsafe.Offsetof(syscall.Dirent{}.Size)) | 
|  |  | 
|  | names := []string{} | 
|  |  | 
|  | for len(bytes) != 0 { | 
|  | nameSize := int(uint8(bytes[direntNameSizeOffset])) | 
|  | name := string(bytes[direntSize : direntSize+nameSize]) | 
|  |  | 
|  | names = append(names, name) | 
|  |  | 
|  | bytes = bytes[direntSize+nameSize:] | 
|  | } | 
|  |  | 
|  | return names | 
|  | } | 
|  |  | 
|  | func readDirectoryEntryNames(t *testing.T, proxy *io.DirectoryWithCtxInterface) []string { | 
|  | res, err := proxy.Rewind(context.Background()) | 
|  | if err != nil { | 
|  | t.Fatalf("Rewind() failed: %v", err) | 
|  | } | 
|  | if zx.Status(res) != zx.ErrOk { | 
|  | t.Fatalf("Rewind() returned wrong value. Want %v, got %v", zx.ErrOk, zx.Status(res)) | 
|  | } | 
|  |  | 
|  | names := []string{} | 
|  |  | 
|  | for { | 
|  | // Use an arbitrary yet small buffer size to avoid having to use too | 
|  | // many entries for testing chunking edge cases. | 
|  | res, bytes, err := proxy.ReadDirents(context.Background(), 256) | 
|  | if err != nil { | 
|  | t.Fatalf("ReadDirents() failed: %v", err) | 
|  | } | 
|  | if zx.Status(res) != zx.ErrOk { | 
|  | t.Fatalf("ReadDirents() returned wrong value. Want %v, got %v", zx.ErrOk, zx.Status(res)) | 
|  | } | 
|  |  | 
|  | if len(bytes) == 0 { | 
|  | break | 
|  | } | 
|  |  | 
|  | names = append(names, parseDirectoryEntryNames(t, bytes)...) | 
|  | } | 
|  |  | 
|  | return names | 
|  | } | 
|  |  | 
|  | func TestReadDirents(t *testing.T) { | 
|  | c1, c2, err := zx.NewChannel(0) | 
|  | if err != nil { | 
|  | t.Fatalf("failed to create channel: %v", err) | 
|  | } | 
|  | defer c1.Close() | 
|  | defer c2.Close() | 
|  |  | 
|  | proxy := &io.DirectoryWithCtxInterface{Channel: c1} | 
|  |  | 
|  | rootDirectory := &dummyDirectory{ | 
|  | dirents: []fs.Dirent{}, | 
|  | } | 
|  | vfs, err := NewServer(&dummyFs{rootDir: rootDirectory}, c2) | 
|  | if err != nil { | 
|  | t.Fatalf("failed to create server: %v", err) | 
|  | } | 
|  | defer vfs.fs.Close() | 
|  |  | 
|  | expectedNames := []string{} | 
|  |  | 
|  | for i := 0; i < 50; i++ { | 
|  | actualNames := readDirectoryEntryNames(t, proxy) | 
|  | if !reflect.DeepEqual(actualNames, expectedNames) { | 
|  | t.Errorf("Readdir() returned incorrect entries. got %v, want %v", actualNames, expectedNames) | 
|  | } | 
|  |  | 
|  | name := fmt.Sprintf("entry%v", i) | 
|  | expectedNames = append(expectedNames, name) | 
|  | rootDirectory.dirents = append(rootDirectory.dirents, fileDirEnt(name)) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestFailingToSendOnOpenEventDoesNotCloseParentDir(t *testing.T) { | 
|  | c11, c12, err := zx.NewChannel(0) | 
|  | if err != nil { | 
|  | t.Fatalf("failed to create channel: %v", err) | 
|  | } | 
|  | defer c11.Close() | 
|  | defer c12.Close() | 
|  |  | 
|  | proxy := &io.DirectoryWithCtxInterface{Channel: c11} | 
|  |  | 
|  | rootDirectory := &dummyDirectory{ | 
|  | dirents: []fs.Dirent{}, | 
|  | } | 
|  | vfs, err := NewServer(&dummyFs{rootDir: rootDirectory}, c12) | 
|  | if err != nil { | 
|  | t.Fatalf("failed to create server: %v", err) | 
|  | } | 
|  | defer vfs.fs.Close() | 
|  |  | 
|  | c21, c22, err := zx.NewChannel(0) | 
|  | if err != nil { | 
|  | t.Fatalf("failed to create channel: %v", err) | 
|  | } | 
|  | c21.Close() | 
|  | defer c22.Close() | 
|  |  | 
|  | nodeReq := io.NodeWithCtxInterfaceRequest{Channel: c22} | 
|  | err = proxy.Open(context.Background(), io.OpenFlagDescribe, 0, "", nodeReq) | 
|  | if err != nil { | 
|  | t.Fatalf("failed to open child node: %v", err) | 
|  | } | 
|  |  | 
|  | _, err = proxy.Describe(context.Background()) | 
|  | if err != nil { | 
|  | t.Fatalf("failed to describe parent node: %v", err) | 
|  | } | 
|  | } |