blob: eb7b983025da60f0a4f7a7599e2c03ef08327037 [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.
// Package pkgfs hosts a filesystem for interacting with packages that are
// stored on a host. It presents a tree of packages that are locally available
// and a tree that enables a user to add new packages and/or package content to
// the host.
package pkgfs
import (
"fmt"
"io"
"log"
"os"
"path"
"path/filepath"
"runtime"
"sync"
"syscall"
"syscall/zx"
"syscall/zx/fidl"
zxio "syscall/zx/io"
"time"
"thinfs/fs"
"thinfs/zircon/rpc"
"fuchsia.googlesource.com/pm/pkg"
"fuchsia.googlesource.com/pmd/blobfs"
"fuchsia.googlesource.com/pmd/index"
)
// Filesystem is the top level container for a pkgfs server
type Filesystem struct {
root *rootDirectory
static *index.StaticIndex
index *index.DynamicIndex
blobfs *blobfs.Manager
mountInfo mountInfo
mountTime time.Time
}
// New initializes a new pkgfs filesystem server
func New(indexDir string, blobDir string) (*Filesystem, error) {
bm, err := blobfs.New(blobDir)
if err != nil {
return nil, fmt.Errorf("pkgfs: open blobfs: %s", err)
}
static := index.NewStatic()
f := &Filesystem{
static: static,
index: index.NewDynamic(indexDir, static),
blobfs: bm,
mountInfo: mountInfo{
parentFd: -1,
},
}
f.root = &rootDirectory{
unsupportedDirectory: unsupportedDirectory("/"),
fs: f,
dirs: map[string]fs.Directory{
"install": &installDir{
unsupportedDirectory: unsupportedDirectory("/install"),
fs: f,
},
"needs": &needsRoot{
unsupportedDirectory: unsupportedDirectory("/needs"),
fs: f,
},
"garbage": unsupportedDirectory("/garbage"),
"packages": &packagesRoot{
unsupportedDirectory: unsupportedDirectory("/packages"),
fs: f,
},
"versions": &versionsDirectory{
unsupportedDirectory: unsupportedDirectory("/versions"),
fs: f,
},
"system": unsupportedDirectory("/system"),
"validation": &validationDir{unsupportedDirectory: unsupportedDirectory("/validation"),
fs: f,
},
},
}
return f, nil
}
// staticIndexPath is the path inside the system package directory that contains the static packages for that system version.
const staticIndexPath = "data/static_packages"
// loadStaticIndex loads the blob specified by root from blobfs. A non-nil
// *StaticIndex is always returned. If an error is returned that indicates a
// problem reading the index content from disk and therefore the StaticIndex
// returned may be empty.
func loadStaticIndex(static *index.StaticIndex, blobfs *blobfs.Manager, root string) error {
indexFile, err := blobfs.Open(root)
if err != nil {
return fmt.Errorf("pkgfs: could not load static index from blob %s: %s", root, err)
}
defer indexFile.Close()
return static.LoadFrom(indexFile)
}
func readBlobfs(blobfs *blobfs.Manager) (map[string]struct{}, error) {
dnames, err := readDir(blobfs.Root)
if err != nil {
log.Printf("pkgfs: error reading(%q): %s", blobfs.Root, err)
// Note: translates to zx.ErrBadState
return nil, fs.ErrFailedPrecondition
}
names := make(map[string]struct{})
for _, name := range dnames {
names[name] = struct{}{}
}
return names, nil
}
func readDir(path string) ([]string, error) {
d, err := os.Open(path)
if err != nil {
return nil, err
}
defer d.Close()
return d.Readdirnames(-1)
}
// SetSystemRoot sets/updates the merkleroot (and static index) that backs the /system partition and static package index.
func (f *Filesystem) SetSystemRoot(merkleroot string) error {
pd, err := newPackageDirFromBlob(merkleroot, f)
if err != nil {
return err
}
f.root.setDir("system", pd)
blob, ok := pd.getBlobFor(staticIndexPath)
if !ok {
return fmt.Errorf("pkgfs: new system root set, but new static index %q not found in %q", staticIndexPath, merkleroot)
}
err = loadStaticIndex(f.static, f.blobfs, blob)
// Ensure that the "system_image" package is also indexed
f.static.Set(
pkg.Package{
Name: pd.name,
Version: pd.version,
},
merkleroot,
)
return err
}
func (f *Filesystem) Blockcount() int64 {
// TODO(raggi): sum up all packages?
// TODO(raggi): delegate to blobfs?
debugLog("fs blockcount")
return 0
}
func (f *Filesystem) Blocksize() int64 {
// TODO(raggi): sum up all packages?
// TODO(raggi): delegate to blobfs?
debugLog("fs blocksize")
return 0
}
func (f *Filesystem) Size() int64 {
debugLog("fs size")
// TODO(raggi): delegate to blobfs?
return 0
}
func (f *Filesystem) Close() error {
f.Unmount()
return nil
}
func (f *Filesystem) RootDirectory() fs.Directory {
return f.root
}
func (f *Filesystem) Type() string {
return "pkgfs"
}
func (f *Filesystem) FreeSize() int64 {
return 0
}
func (f *Filesystem) DevicePath() string {
return ""
}
// Serve starts a Directory protocol RPC server on the given channel.
func (f *Filesystem) Serve(c zx.Channel) error {
// rpc.NewServer takes ownership of the Handle and will close it on error.
vfs, err := rpc.NewServer(f, zx.Handle(c))
if err != nil {
return fmt.Errorf("vfs server creation: %s", err)
}
f.mountInfo.serveChannel = c
// TODO(raggi): serve has no quit/shutdown path.
for i := runtime.NumCPU(); i > 1; i-- {
go vfs.Serve()
}
vfs.Serve()
return nil
}
// Mount attaches the filesystem host to the given path. If an error occurs
// during setup, this method returns that error. If an error occurs after
// serving has started, the error causes a log.Fatal. If the given path does not
// exist, it is created before mounting.
func (f *Filesystem) Mount(path string) error {
err := os.MkdirAll(path, os.ModePerm)
if err != nil {
return err
}
f.mountInfo.parentFd, err = syscall.Open(path, syscall.O_ADMIN|syscall.O_DIRECTORY, 0777)
if err != nil {
return err
}
var rpcChan, mountChan zx.Channel
rpcChan, mountChan, err = zx.NewChannel(0)
if err != nil {
syscall.Close(f.mountInfo.parentFd)
f.mountInfo.parentFd = -1
return fmt.Errorf("channel creation: %s", err)
}
remote := zxio.DirectoryInterface(fidl.InterfaceRequest{Channel: mountChan})
dirChan := zx.Channel(syscall.FDIOForFD(f.mountInfo.parentFd).Handles()[0])
dir := zxio.DirectoryAdminInterface(fidl.InterfaceRequest{Channel: dirChan})
if status, err := dir.Mount(remote); err != nil || zx.Status(status) != zx.ErrOk {
rpcChan.Close()
syscall.Close(f.mountInfo.parentFd)
f.mountInfo.parentFd = -1
return fmt.Errorf("mount failure: %s", err)
}
return f.Serve(rpcChan)
}
// Unmount detaches the filesystem from a previously mounted path. If mount was not previously called or successful, this will panic.
func (f *Filesystem) Unmount() {
f.mountInfo.unmountOnce.Do(func() {
// parentFd is -1 in the case where f was just Serve()'d instead of Mount()'d
if f.mountInfo.parentFd != -1 {
dirChan := zx.Channel(syscall.FDIOForFD(f.mountInfo.parentFd).Handles()[0])
dir := zxio.DirectoryAdminInterface(fidl.InterfaceRequest{Channel: dirChan})
dir.UnmountNode()
syscall.Close(f.mountInfo.parentFd)
f.mountInfo.parentFd = -1
}
f.mountInfo.serveChannel.Close()
f.mountInfo.serveChannel = 0
})
}
var _ fs.FileSystem = (*Filesystem)(nil)
// clean canonicalizes a path and returns a path that is relative to an assumed root.
// as a result of this cleaning operation, an open of '/' or '.' or '' all return ''.
// TODO(raggi): speed this up/reduce allocation overhead.
func clean(path string) string {
return filepath.Clean("/" + path)[1:]
}
type mountInfo struct {
unmountOnce sync.Once
serveChannel zx.Channel
parentFd int
}
func goErrToFSErr(err error) error {
switch e := err.(type) {
case nil:
return nil
case *os.PathError:
return goErrToFSErr(e.Err)
case *zx.Error:
switch e.Status {
case zx.ErrNotFound:
return fs.ErrNotFound
case zx.ErrNoSpace:
return fs.ErrNoSpace
case zx.ErrNotSupported:
return fs.ErrNotSupported
case zx.ErrInvalidArgs:
return fs.ErrInvalidArgs
default:
debugLog("pkgfs: unmapped zx status to fs err: %s", e.Status)
return err
}
}
switch err {
case os.ErrInvalid:
return fs.ErrInvalidArgs
case os.ErrPermission:
return fs.ErrPermission
case os.ErrExist:
return fs.ErrAlreadyExists
case os.ErrNotExist:
return fs.ErrNotFound
case os.ErrClosed:
return fs.ErrNotOpen
case io.EOF:
return fs.ErrEOF
default:
debugLog("pkgfs: unmapped os err to fs err: %T %v", err, err)
return err
}
}
// GC examines the static and dynamic indexes, collects all the blobs that
// belong to packages in these indexes. It then reads blobfs for its entire
// list of blobs. Anything in blobfs that does not appear in the indexes is
// removed.
func (fs *Filesystem) GC() error {
log.Println("GC: start")
start := time.Now()
defer func() {
// this process produces a lot of garbage, so try to free that up (removes
// ~1.5mb of heap from a common (small) build target).
runtime.GC()
log.Printf("GC: completed in %.3fs", time.Since(start).Seconds())
}()
// read the list of installed blobs first, as there may be installations in
// progress, we want to not create orphans later, this list must be equal to or
// a subset of the set we intersect.
installedBlobs, err := readBlobfs(fs.blobfs)
if err != nil {
return fmt.Errorf("GC: unable to list blobfs: %s", err)
}
log.Printf("GC: %d blobs in blobfs", len(installedBlobs))
allPackageBlobs := fs.index.AllPackageBlobs()
// access the meta FAR blob of the system package
if pd, ok := fs.root.dir("system").(*packageDir); ok {
allPackageBlobs = append(allPackageBlobs, pd.contents["meta"])
} else {
return fmt.Errorf("GC: gc aborted, system directory is of unknown type")
}
// Walk the list of all packages and collate all involved blobs, both the fars
// themselves, and the contents on which they depend.
allBlobs := make(map[string]struct{})
for _, pkgRoot := range allPackageBlobs {
allBlobs[pkgRoot] = struct{}{}
pDir, err := newPackageDirFromBlob(pkgRoot, fs)
if err != nil {
log.Printf("GC: failed getting package from blob %s: %s", pkgRoot, err)
continue
}
for _, m := range pDir.Blobs() {
allBlobs[m] = struct{}{}
}
pDir.Close()
}
log.Printf("GC: %d blobs referenced by %d packages", len(allBlobs), len(allPackageBlobs))
for m := range allBlobs {
delete(installedBlobs, m)
}
// remove all the blobs we no longer need
log.Printf("GC: removing %d blobs from blobfs", len(installedBlobs))
for m := range installedBlobs {
e := os.Remove(path.Join("/blob", m))
if e != nil {
log.Printf("GC: error removing %s from blobfs: %s", m, e)
}
}
return nil
}
// ValidateStaticIndex compares the contents of the static index against what
// blobs are available in blobfs. It returns the ids of the blobs that are
// present and those that are missing or any error encountered trying to do the
// validation.
func (fs *Filesystem) ValidateStaticIndex() (map[string]struct{}, map[string]struct{}, error) {
installedBlobs, err := readBlobfs(fs.blobfs)
if err != nil {
return nil, nil, fmt.Errorf("pmd_validate: unable to list blobfs: %s", err)
}
present := make(map[string]struct{})
missing := make(map[string]struct{})
staticPkgs := fs.static.StaticPackageBlobs()
if pd, ok := fs.root.dir("system").(*packageDir); ok {
staticPkgs = append(staticPkgs, pd.contents["meta"])
}
for _, pkgRoot := range staticPkgs {
if _, ok := installedBlobs[pkgRoot]; ok {
present[pkgRoot] = struct{}{}
} else {
log.Printf("pmd_validate: %q root is missing", pkgRoot)
missing[pkgRoot] = struct{}{}
}
pDir, err := newPackageDirFromBlob(pkgRoot, fs)
if err != nil {
log.Printf("pmd_validate: failed getting package from blob %s: %s", pkgRoot, err)
pDir.Close()
return nil, nil, err
}
for _, m := range pDir.Blobs() {
if _, ok := installedBlobs[m]; ok {
present[m] = struct{}{}
} else {
log.Printf("pmd_validate: %q is missing from %q", m, pkgRoot)
missing[m] = struct{}{}
}
}
pDir.Close()
}
return present, missing, nil
}