blob: 4f6ac8761e9fc4f82c6b8151b683724d6d35faaf [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
package pkgfs
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"syscall"
"syscall/zx"
"syscall/zx/fdio"
zxio "syscall/zx/io"
"testing"
"fuchsia.googlesource.com/pm/build"
"fuchsia.googlesource.com/pmd/iou"
"fuchsia.googlesource.com/pmd/ramdisk"
)
// 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
)
// 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
blobDir, err = ramdisk.New(10 * 1024 * 1024)
panicerr(err)
panicerr(blobDir.StartBlobfs())
defer blobDir.Destroy()
cfg := build.TestConfig()
defer os.RemoveAll(filepath.Dir(cfg.TempDir))
build.TestPackage(cfg)
build.BuildTestPackage(cfg)
bi, err := cfg.BlobInfo()
panicerr(err)
// Install the blobs to blobfs directly
for _, b := range bi {
src, err := os.Open(b.SourcePath)
panicerr(err)
dst, err := blobDir.Open(b.Merkle.String(), os.O_WRONLY|os.O_CREATE, 0777)
panicerr(err)
panicerr(dst.Truncate(int64(b.Size)))
_, err = io.Copy(dst, src)
panicerr(err)
panicerr(src.Close())
panicerr(dst.Close())
}
d, err := ioutil.TempDir("", "pkgfs-test-mount")
panicerr(err)
defer os.RemoveAll(d)
blobd, err := blobDir.Open(".", os.O_RDWR|syscall.O_DIRECTORY, 0777)
panicerr(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)
panicerr(err)
pkgfs.static.LoadFrom(strings.NewReader(
fmt.Sprintf("static-package/0=%s\n", bi[0].Merkle.String())))
nc, sc, err := zx.NewChannel(0)
panicerr(err)
pkgfsDir = fdio.NewDirectoryWithCtx(&zxio.DirectoryAdminWithCtxInterface{Channel: nc})
go func() {
panicerr(pkgfs.Serve(sc))
}()
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()
panicerr(err)
merkleroot := bi[0].Merkle.String()
dst, err := iou.OpenFrom(pkgfsDir, filepath.Join("install/pkg", merkleroot), os.O_RDWR|os.O_CREATE, 0777)
panicerr(err)
panicerr(dst.Truncate(int64(bi[0].Size)))
src, err := os.Open(bi[0].SourcePath)
panicerr(err)
if _, err := io.Copy(dst, src); err != nil {
src.Close()
dst.Close()
t.Fatal(err)
}
panicerr(src.Close())
panicerr(dst.Close())
// Opening it again gives EEXIST
_, err = iou.OpenFrom(pkgfsDir, filepath.Join("install/pkg", merkleroot), os.O_RDWR|os.O_CREATE, 0777)
if !os.IsExist(err) {
panicerr(err)
}
d, err := blobDir.Open(merkleroot, syscall.O_PATH, 0777)
panicerr(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, 0777)
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, 0777); 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, 0777)
needsPkgs, err := f.Readdirnames(256)
panicerr(f.Close())
panicerr(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, 0777)
if os.IsExist(err) {
continue
}
panicerr(err)
panicerr(dst.Truncate(int64(b.Size)))
src, err := os.Open(b.SourcePath)
panicerr(err)
_, err = io.Copy(dst, src)
panicerr(err)
panicerr(src.Close())
panicerr(dst.Close())
}
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))
panicerr(err)
want, err := ioutil.ReadFile(b.SourcePath)
panicerr(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"))
panicerr(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, 0777)
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, 0777)
if err != nil {
return nil, err
}
defer f.Close()
return f.Stat()
}
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, 0777)
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, 0777)
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, 0777)
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, 0777)
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, 0777)
if err != nil {
t.Fatal(err)
}
names, err := f.Readdirnames(-1)
f.Close()
if err != nil {
t.Fatal(err)
}
want := []string{"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, 0777)
if err != nil {
t.Fatal(err)
}
if err = d.Sync(); err != nil {
t.Fatal(err)
}
d.Close()
}
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, 0777)
if err != nil {
return err
}
return d.Unlink(path)
}
// /pkgfs/garbage no longer exists
if err := unlink("garbage"); err == nil {
t.Fatal("expected error, got nil")
}
// unlinking garbage triggers a GC but doesn't remove the file.
if err := unlink("ctl/garbage"); err != nil {
t.Fatal(err)
}
if err := unlink("ctl/garbage"); err != nil {
t.Fatal(err)
}
}
func TestVersions(t *testing.T) {
f, err := iou.OpenFrom(pkgfsDir, "versions", os.O_RDONLY|syscall.O_DIRECTORY, 0777)
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)
}
}
}
func panicerr(err error) {
if err != nil {
panic(err)
}
}