blob: 6ac99018a592ffbfeb141a2a962ddfe8753a4558 [file] [log] [blame]
// Copyright 2017 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 fuchsia
// +build !build_with_native_toolchain
package pkgfs
import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"syscall"
"syscall/zx"
"syscall/zx/fdio"
"testing"
zxio "syscall/zx/io"
"go.fuchsia.dev/fuchsia/src/sys/pkg/bin/pkgfs/iou"
"go.fuchsia.dev/fuchsia/src/sys/pkg/bin/pkgfs/ramdisk"
"go.fuchsia.dev/fuchsia/src/sys/pkg/bin/pm/build"
"go.fuchsia.dev/fuchsia/src/sys/pkg/bin/pm/pkg"
)
// Adding a file to /in writes the file to blobfs
// Adding a file that is a meta.far to /in creates the package in the package filesystem
// If not all of a packages contents are available, opening the package directory should fail
// A package directory should contain all files from meta.far and listed by meta/contents
var (
pkgfsDir *fdio.Directory
blobDir *ramdisk.Ramdisk
)
func installTestPackage(installJustMetaFar bool) string {
cfg := build.TestConfig()
defer os.RemoveAll(filepath.Dir(cfg.TempDir))
build.TestPackage(cfg)
build.BuildTestPackage(cfg)
bi, err := cfg.BlobInfo()
if err != nil {
panic(fmt.Errorf("Creating BlobInfo: %s", err))
}
// Install the blobs to blobfs directly.
for _, b := range bi {
src, err := os.Open(b.SourcePath)
if err != nil {
panic(err)
}
dst, err := blobDir.Open(b.Merkle.String(), os.O_WRONLY|os.O_CREATE, 0o700)
if err != nil {
panic(fmt.Errorf("Opening blob dst: %s", err))
}
if err := dst.Truncate(int64(b.Size)); err != nil {
panic(err)
}
if _, err = io.Copy(dst, src); err != nil {
panic(err)
}
if err := src.Close(); err != nil {
panic(err)
}
if err := dst.Close(); err != nil {
panic(err)
}
if installJustMetaFar {
return bi[0].Merkle.String()
}
}
return bi[0].Merkle.String()
}
var testPackageMerkle string
// tmain exists for the defer convenience, so that defers are run before os.Exit gets called.
func tmain(m *testing.M) int {
// Undo the defaults that print to the system log...
log.SetOutput(os.Stdout)
var err error
if blobDir, err = ramdisk.New(10 * 1024 * 1024); err != nil {
panic(fmt.Errorf("Creating blobfs ramdisk: %s", err))
}
if err := blobDir.StartBlobfs(); err != nil {
panic(fmt.Errorf("Starting blobfs: %s", err))
}
defer blobDir.Destroy()
testPackageMerkle = installTestPackage(false)
systemImageMerkle := installTestPackage(true)
d, err := ioutil.TempDir("", "pkgfs-test-mount")
if err != nil {
panic(err)
}
defer os.RemoveAll(d)
blobd, err := blobDir.Open(".", os.O_RDWR|syscall.O_DIRECTORY, 0o700)
if err != nil {
panic(err)
}
defer func() {
// The Go syscall API doesn't provide any way to detatch the underlying
// channel from the *File wrapper, so once the GC runs, then blobd will be
// closed and then pkgfs can't access the blobfs anymore, so we have to keep
// it alive for at least the runtime of the tests.
runtime.KeepAlive(blobd)
}()
pkgfs, err := New(syscall.FDIOForFD(int(blobd.Fd())).(*fdio.Directory), false, false)
if err != nil {
panic(err)
}
systemImagePackage := pkg.Package{
Name: "system_image",
Version: "0",
}
pkgfs.static.LoadFrom(strings.NewReader(
fmt.Sprintf("static-package/0=%s\n", testPackageMerkle)), systemImagePackage, systemImageMerkle)
nc, sc, err := zx.NewChannel(0)
if err != nil {
panic(err)
}
pkgfsDir = fdio.NewDirectoryWithCtx(&zxio.DirectoryAdminWithCtxInterface{Channel: nc})
if err = pkgfs.Serve(sc); err != nil {
panic(err)
}
return m.Run()
}
func TestMain(m *testing.M) {
os.Exit(tmain(m))
}
func TestAddPackage(t *testing.T) {
cfg := build.TestConfig()
defer os.RemoveAll(filepath.Dir(cfg.TempDir))
cfg.PkgName = t.Name()
build.BuildTestPackage(cfg)
bi, err := cfg.BlobInfo()
if err != nil {
t.Fatal(err)
}
merkleroot := bi[0].Merkle.String()
dst, err := iou.OpenFrom(pkgfsDir, filepath.Join("install/pkg", merkleroot), os.O_RDWR|os.O_CREATE, 0o700)
if err != nil {
t.Fatal(err)
}
if err := dst.Truncate(int64(bi[0].Size)); err != nil {
t.Fatal(err)
}
src, err := os.Open(bi[0].SourcePath)
if err != nil {
t.Fatal(err)
}
if _, err := io.Copy(dst, src); err != nil {
src.Close()
dst.Close()
t.Fatal(err)
}
if src.Close(); err != nil {
t.Fatal(err)
}
if dst.Close(); err != nil {
t.Fatal(err)
}
// Opening it again gives EEXIST
_, err = iou.OpenFrom(pkgfsDir, filepath.Join("install/pkg", merkleroot), os.O_RDWR|os.O_CREATE, 0o700)
if !os.IsExist(err) {
t.Fatal(err)
}
d, err := blobDir.Open(merkleroot, syscall.O_PATH, 0o700)
if err != nil {
t.Fatal(err)
}
_, err = d.Stat()
d.Close()
if err != nil {
t.Fatalf("package blob missing after package write: %s", err)
}
f, err := iou.OpenFrom(pkgfsDir, filepath.Join("packages", cfg.PkgName, cfg.PkgVersion), os.O_RDONLY|syscall.O_DIRECTORY, 0o700)
if err == nil {
f.Close()
t.Error("package appeared in the pkgfs package tree before needs fulfilled")
}
expectedNeeds := []string{}
for _, b := range bi {
if _, err := blobDir.Open(b.Merkle.String(), syscall.O_PATH, 0o700); os.IsNotExist(err) {
expectedNeeds = append(expectedNeeds, b.Merkle.String())
}
}
sort.Strings(expectedNeeds)
f, err = iou.OpenFrom(pkgfsDir, filepath.Join("needs", "packages", merkleroot), os.O_RDONLY|syscall.O_DIRECTORY, 0o700)
needsPkgs, err := f.Readdirnames(256)
if err != nil {
t.Error(err)
}
if err := f.Close(); err != nil {
t.Fatal(err)
}
if got, want := len(needsPkgs), len(expectedNeeds); got != want {
t.Errorf("needs/packages/{root}/* count: got %d, want %d", got, want)
}
sort.Strings(needsPkgs)
for i := range expectedNeeds {
if got, want := filepath.Base(needsPkgs[i]), expectedNeeds[i]; got != want {
t.Errorf("needs/packages/{root}/{file} got %q, want %q", got, want)
}
}
// install the blobs of the package
for _, b := range bi[1:] {
root := b.Merkle.String()
idx := sort.SearchStrings(needsPkgs, root)
if idx == len(needsPkgs) {
continue
}
dst, err := iou.OpenFrom(pkgfsDir, filepath.Join("install/blob", root), os.O_RDWR|os.O_CREATE, 0o700)
if os.IsExist(err) {
continue
}
if err != nil {
t.Fatal(err)
}
if err := dst.Truncate(int64(b.Size)); err != nil {
t.Fatal(err)
}
src, err := os.Open(b.SourcePath)
if err != nil {
t.Fatal(err)
}
if _, err = io.Copy(dst, src); err != nil {
t.Fatal(err)
}
if src.Close(); err != nil {
t.Fatal(err)
}
if dst.Close(); err != nil {
t.Fatal(err)
}
}
var info os.FileInfo
if info, err = pkgfsStat(filepath.Join("packages", cfg.PkgName)); err != nil {
t.Fatalf("package did not appear in the pkgfs package tree: %s", err)
}
if !info.IsDir() {
t.Errorf("os.Stat on package directory says it's not a directory")
}
if info, err = pkgfsStat(filepath.Join("packages", cfg.PkgName, cfg.PkgVersion)); err != nil {
t.Fatalf("package version did not appear in the pkgfs package tree: %s", err)
}
if !info.IsDir() {
t.Errorf("os.Stat on package version directory says it's not a directory")
}
for _, b := range bi[1:] {
got, err := pkgfsReadFile(filepath.Join("packages", cfg.PkgName, cfg.PkgVersion, b.Path))
if err != nil {
t.Fatal(err)
}
want, err := ioutil.ReadFile(b.SourcePath)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(got, want) {
t.Errorf("got %x, want %x", got, want)
}
}
// assert that the dynamically added package appears in /versions
metaMerkle, err := pkgfsReadFile(filepath.Join("versions", merkleroot, "meta"))
if err != nil {
t.Fatal(err)
}
if got, want := string(metaMerkle), merkleroot; got != want {
t.Errorf("add dynamic package, bad version: got %q, want %q", got, want)
}
}
func pkgfsReadFile(path string) ([]byte, error) {
f, err := iou.OpenFrom(pkgfsDir, path, os.O_RDONLY, 0o700)
if err != nil {
return nil, err
}
defer f.Close()
buf := bytes.Buffer{}
if _, err := io.Copy(&buf, f); err != io.EOF && err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func pkgfsStat(path string) (os.FileInfo, error) {
f, err := iou.OpenFrom(pkgfsDir, path, os.O_RDONLY|syscall.O_PATH, 0o700)
if err != nil {
return nil, err
}
defer f.Close()
return f.Stat()
}
func TestMetaFarRootDuality(t *testing.T) {
path := "packages/static-package/0/meta"
t.Run("meta is a file containing the merkleroot", func(t *testing.T) {
f, err := iou.OpenFrom(pkgfsDir, path, 0, 0o700)
if err != nil {
t.Fatal(err)
}
defer f.Close()
b, err := ioutil.ReadAll(f)
if err != nil {
t.Fatal(err)
}
if len(b) != 64 && string(b) == testPackageMerkle {
t.Fatalf("expected 64 byte merkleroot of %q, got %q", testPackageMerkle, string(b))
}
})
t.Run("meta is a directory containing files", func(t *testing.T) {
f, err := iou.OpenFrom(pkgfsDir, path, syscall.O_DIRECTORY, 0o700)
if err != nil {
t.Fatal(err)
}
defer f.Close()
list, err := f.Readdirnames(-1)
if err != nil {
t.Fatal(err)
}
found := false
for _, item := range list {
if item == "contents" {
found = true
break
}
}
if !found {
t.Fatalf("did not find 'contents' file among meta/ readdir: %v", list)
}
contents, err := iou.OpenFrom(pkgfsDir, filepath.Join(path, "contents"), 0, 0o700)
if err != nil {
t.Fatal(err)
}
defer contents.Close()
fi, err := contents.Stat()
if err != nil {
t.Fatal(err)
}
offset, err := contents.Seek(17, io.SeekStart)
if err != nil {
t.Fatal(err)
}
if offset != 17 {
t.Fatalf("Tried to seek to 17 but got %d", offset)
}
offset, err = contents.Seek(-7, io.SeekCurrent)
if err != nil {
t.Fatal(err)
}
if offset != 10 {
t.Fatalf("Tried to seek to 17-7 but got %d", offset)
}
offset, err = contents.Seek(0, io.SeekEnd)
if err != nil {
t.Fatal(err)
}
if offset == 0 {
t.Fatalf("Tried to seek to end but got %d", offset)
}
if offset != fi.Size() {
t.Fatalf("Seek to end arrived at %d but expected %d size", offset, fi.Size())
}
})
t.Run("meta subdirectories are openable and listable", func(t *testing.T) {
f, err := iou.OpenFrom(pkgfsDir, "packages/static-package/0/meta/foo", 0, 0o700)
if err != nil {
t.Fatal(err)
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
t.Fatal(err)
}
if !fi.IsDir() {
t.Fatal("expected static-package/0/meta/foo to be a directory, not a file")
}
list, err := f.Readdirnames(-1)
if err != nil {
t.Fatal(err)
}
if len(list) != 1 && list[0] == "one" {
t.Fatalf("expected list to contain one file, got %v", list)
}
})
t.Run("meta subdirectories do not have file/directory duality", func(t *testing.T) {
// protect against regression of name prefix fixup in metafar.go,
// wherein at time of test authorship, a "." open would open meta/
// instead.
d, err := pkgfsDir.Open("packages/static-package/0/meta/foo", syscall.O_DIRECTORY, 0o700)
if err != nil {
t.Fatal(err)
}
defer d.Close()
f, err := iou.OpenFrom(d.(*fdio.Directory), "", syscall.O_RDONLY, 0o700)
if err != nil {
t.Fatal(err)
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
t.Fatal(err)
}
if !fi.IsDir() {
t.Fatal("expected static-package/0/meta/foo to be a directory, not a file")
}
})
}
func TestExecutability(t *testing.T) {
// packages/static-package/0/meta/contents should not be openable
// executable, because meta/* is never executable
path := "packages/static-package/0/meta/contents"
f, err := pkgfsDir.Open(path, syscall.FsRightReadable|syscall.FsRightExecutable, 0o700)
if f != nil || err == nil {
t.Fatal(err)
}
// packages/static-package/0/a should be openable executable, because
// files from packages are executable.
path = "packages/static-package/0/a"
f, err = pkgfsDir.Open(path, syscall.FsRightReadable|syscall.FsRightExecutable, 0o700)
if err != nil {
t.Fatal(err)
}
f.Close()
}
func TestListContainsStatic(t *testing.T) {
//names, err := filepath.Glob(filepath.Join(pkgfsMount, "packages", "*", "*"))
f, err := iou.OpenFrom(pkgfsDir, "packages/static-package/0", os.O_RDONLY|syscall.O_DIRECTORY, 0o700)
if err != nil {
t.Fatal(err)
}
names, err := f.Readdirnames(-1)
f.Close()
if err != nil {
t.Fatal(err)
}
if len(names) <= 0 {
t.Errorf("static-package appears to be empty or missing")
}
}
func TestListRoot(t *testing.T) {
f, err := iou.OpenFrom(pkgfsDir, ".", os.O_RDONLY|syscall.O_DIRECTORY, 0o700)
if err != nil {
t.Fatal(err)
}
names, err := f.Readdirnames(-1)
f.Close()
if err != nil {
t.Fatal(err)
}
want := []string{"install", "needs", "packages", "system", "versions", "ctl"}
sort.Strings(names)
sort.Strings(want)
if len(names) != len(want) {
t.Fatalf("got %v, want %v", names, want)
}
for i, name := range names {
got := filepath.Base(name)
if want := want[i]; got != want {
t.Errorf("got %q, want %q", got, want)
}
}
}
func TestListCtl(t *testing.T) {
f, err := iou.OpenFrom(pkgfsDir, "ctl", os.O_RDONLY|syscall.O_DIRECTORY, 0o700)
if err != nil {
t.Fatal(err)
}
names, err := f.Readdirnames(-1)
f.Close()
if err != nil {
t.Fatal(err)
}
want := []string{"do-not-use-this-garbage", "validation"}
sort.Strings(names)
sort.Strings(want)
if len(names) != len(want) {
t.Fatalf("got %v, want %v", names, want)
}
for i, name := range names {
got := filepath.Base(name)
if want := want[i]; got != want {
t.Errorf("got %q, want %q", got, want)
}
}
}
func TestSync(t *testing.T) {
d, err := iou.OpenFrom(pkgfsDir, "ctl", os.O_RDONLY|syscall.O_DIRECTORY, 0o700)
if err != nil {
t.Fatal(err)
}
if err = d.Sync(); err != nil {
t.Fatal(err)
}
d.Close()
}
func TestMetaFileGetFlags(t *testing.T) {
path := "packages/static-package/0/meta/contents"
f, err := pkgfsDir.Open(path, syscall.FsRightReadable, 0o700)
if err != nil {
t.Fatal(err)
}
defer f.Close()
fdioFile, ok := f.(*fdio.File)
if !ok {
t.Fatal("File is not an fdio.File")
}
ioFile := (*zxio.FileWithCtxInterface)(fdioFile.Node.NodeWithCtxInterface)
status, flags, err := ioFile.GetFlags(context.Background())
if err != nil || status != int32(zx.ErrOk) {
t.Fatal("Could not get flags:", err, status)
}
if flags != syscall.FsRightReadable {
t.Fatalf("got %v, want %v", flags, syscall.FsRightReadable)
}
}
func TestMapFileForRead(t *testing.T) {
path := "packages/static-package/0/meta/contents"
f, err := pkgfsDir.Open(path, syscall.FsRightReadable, 0o700)
if err != nil {
t.Fatal(err)
}
defer f.Close()
fdioFile, ok := f.(*fdio.File)
if !ok {
t.Fatal("File is not an fdio.File")
}
defer fdioFile.Close()
flags := zxio.VmoFlagRead
status, buffer, err := fdioFile.GetBuffer(flags)
if err != nil || status != int32(zx.ErrOk) {
t.Fatal("Could not get buffer:", err, status)
}
if buffer.Size == 0 {
t.Fatal("Buffer has zero size")
}
size := buffer.Size
buf := make([]byte, size)
offset := uint64(0)
err = buffer.Vmo.Read(buf, offset)
if err != nil {
t.Fatal("Error reading data from VMO")
}
buffer.Vmo.Close()
}
func getKoid(h *zx.Handle) (uint64, error) {
info, err := h.GetInfoHandleBasic()
if err != nil {
return 0, err
}
return info.Koid, nil
}
func TestMapFileForReadPrivate(t *testing.T) {
path := "packages/static-package/0/meta/contents"
f, err := pkgfsDir.Open(path, syscall.FsRightReadable, 0o700)
if err != nil {
t.Fatal(err)
}
defer f.Close()
fdioFile, ok := f.(*fdio.File)
if !ok {
t.Fatal("File is not an fdio.File")
}
defer fdioFile.Close()
flags := zxio.VmoFlagRead | zxio.VmoFlagPrivate
// We want to test that we're receiving our own clone each time we invoke
// GetBuffer() with the VmoFlagPrivate field set
status, buffer, err := fdioFile.GetBuffer(flags)
if err != nil || status != int32(zx.ErrOk) {
t.Fatal("Could not get buffer:", err, status)
}
firstVmo := buffer.Vmo
defer firstVmo.Close()
status, buffer, err = fdioFile.GetBuffer(flags)
if err != nil || status != int32(zx.ErrOk) {
t.Fatal("Could not get buffer:", err, status)
}
secondVmo := buffer.Vmo
defer secondVmo.Close()
firstKoid, err := getKoid(firstVmo.Handle())
if err != nil {
t.Fatal("Could not retrieve koid of handle: ", err)
}
secondKoid, err := getKoid(secondVmo.Handle())
if err != nil {
t.Fatal("Could not retrieve koid of handle: ", err)
}
if firstKoid == secondKoid {
t.Fatal("Two GetBuffer calls with VmoFlagPrivate produced handles to the same object")
}
}
func TestMapFileForReadExact(t *testing.T) {
path := "packages/static-package/0/meta/contents"
f, err := pkgfsDir.Open(path, syscall.FsRightReadable, 0o700)
if err != nil {
t.Fatal(err)
}
defer f.Close()
fdioFile, ok := f.(*fdio.File)
if !ok {
t.Fatal("File is not an fdio.File")
}
// Exact flag is not supported in pkgfs
flags := zxio.VmoFlagExact
_, _, err = fdioFile.GetBuffer(flags)
if err == nil {
t.Fatal("Attempt to map with VmoFlagExact should fail")
}
}
func TestMapFilePrivateAndExact(t *testing.T) {
path := "packages/static-package/0/meta/contents"
f, err := pkgfsDir.Open(path, syscall.FsRightReadable, 0o700)
if err != nil {
t.Fatal(err)
}
defer f.Close()
fdioFile, ok := f.(*fdio.File)
if !ok {
t.Fatal("File is not an fdio.File")
}
// This combination is invalid according to the fuchsia.io protocol definition.
flags := zxio.VmoFlagPrivate | zxio.VmoFlagExact
_, _, err = fdioFile.GetBuffer(flags)
if err == nil {
t.Fatal("Attempt to specify VmoFlagPrivate and VmoFlagExact should fail")
}
}
func TestMapFileForWrite(t *testing.T) {
path := "packages/static-package/0/meta/contents"
f, err := pkgfsDir.Open(path, syscall.FsRightReadable, 0o700)
if err != nil {
t.Fatal(err)
}
defer f.Close()
fdioFile, ok := f.(*fdio.File)
if !ok {
t.Fatal("File is not an fdio.File")
}
// Files in a meta directory are read-only, creating a writable mapping
// should fail.
flags := zxio.VmoFlagWrite
_, _, err = fdioFile.GetBuffer(flags)
if err == nil {
t.Fatal("Attempt to get a writable buffer should fail")
}
}
func TestMapFileForExec(t *testing.T) {
path := "packages/static-package/0/meta/contents"
f, err := pkgfsDir.Open(path, syscall.FsRightReadable, 0o700)
if err != nil {
t.Fatal(err)
}
defer f.Close()
fdioFile, ok := f.(*fdio.File)
if !ok {
t.Fatal("File is not an fdio.File")
}
flags := zxio.VmoFlagExec
_, _, err = fdioFile.GetBuffer(flags)
if err == nil {
t.Fatal("Attempt to get executable buffer should fail")
}
}
func TestTriggerGC(t *testing.T) {
// always perform the operation on a dedicated channel, so that pkgfsDir is not
// closed.
unlink := func(path string) error {
d, err := pkgfsDir.Open(".", zxio.OpenFlagDirectory|zxio.OpenRightReadable|zxio.OpenFlagPosix, 0o700)
if err != nil {
return err
}
return d.Unlink(path)
}
// /pkgfs/do-not-use-this-garbage no longer exists
if err := unlink("do-not-use-this-garbage"); err == nil {
t.Fatal("expected error, got nil")
}
// unlinking do-not-use-this-garbage triggers a GC but doesn't remove the file.
if err := unlink("ctl/do-not-use-this-garbage"); err != nil {
t.Fatal(err)
}
if err := unlink("ctl/do-not-use-this-garbage"); err != nil {
t.Fatal(err)
}
}
func TestVersions(t *testing.T) {
f, err := iou.OpenFrom(pkgfsDir, "versions", os.O_RDONLY|syscall.O_DIRECTORY, 0o700)
if err != nil {
t.Fatal(err)
}
names, err := f.Readdirnames(-1)
f.Close()
if err != nil {
t.Fatal(err)
}
if len(names) == 0 {
t.Fatal("observed no versions")
}
for _, name := range names {
if !merklePat.MatchString(filepath.Base(name)) {
t.Errorf("got non-merkle version: %q", name)
continue
}
b, err := pkgfsReadFile(filepath.Join("versions", name, "meta"))
if err != nil {
t.Fatal(err)
}
if got, want := string(b), filepath.Base(name); got != want {
t.Errorf("got %q, want %q", got, want)
}
}
}