blob: 0f009dc97a08e9e46238232a0a2ca9eea2ef1cc4 [file] [log] [blame]
// Copyright 2018 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
import (
"bytes"
"encoding/json"
"log"
"os"
"path/filepath"
"strings"
"thinfs/fs"
"time"
"fuchsia.googlesource.com/far"
"fuchsia.googlesource.com/pm/pkg"
)
type packageDir struct {
unsupportedDirectory
fs *Filesystem
name, version string
contents map[string]string
// if this packagedir is a subdirectory, then this is the prefix name
subdir *string
}
func newPackageDir(name, version string, filesystem *Filesystem) (*packageDir, error) {
var merkleroot string
var foundInStatic bool
p := pkg.Package{Name: name, Version: version}
if filesystem.static != nil {
merkleroot, foundInStatic = filesystem.static.Get(p)
}
if !foundInStatic {
var found bool
merkleroot, found = filesystem.index.Get(p)
if !found {
return nil, fs.ErrNotFound
}
}
return newPackageDirFromBlob(merkleroot, filesystem)
}
// Initialize a package directory server interface from a package meta.far
func newPackageDirFromBlob(blob string, filesystem *Filesystem) (*packageDir, error) {
f, err := filesystem.blobfs.Open(blob)
if err != nil {
if !os.IsNotExist(err) {
log.Printf("pkgfs: failed to open package contents at %q: %s", blob, err)
}
return nil, goErrToFSErr(err)
}
defer f.Close()
fr, err := far.NewReader(f)
if err != nil {
log.Printf("pkgfs: failed to read meta.far at %q: %s", blob, err)
return nil, goErrToFSErr(err)
}
buf, err := fr.ReadFile("meta/package")
if err != nil {
log.Printf("pkgfs: failed to read meta/package from %q: %s", blob, err)
return nil, goErrToFSErr(err)
}
var p pkg.Package
if err := json.Unmarshal(buf, &p); err != nil {
log.Printf("pkgfs: failed to parse meta/package from %q: %s", blob, err)
return nil, goErrToFSErr(err)
}
buf, err = fr.ReadFile("meta/contents")
if err != nil {
log.Printf("pkgfs: failed to read meta/contents from %q: %s", blob, err)
return nil, goErrToFSErr(err)
}
pd := packageDir{
unsupportedDirectory: unsupportedDirectory(filepath.Join("/packages", p.Name, p.Version)),
name: p.Name,
version: p.Version,
fs: filesystem,
contents: map[string]string{},
}
lines := bytes.Split(buf, []byte("\n"))
for _, line := range lines {
line = bytes.TrimSpace(line)
if len(line) == 0 {
continue
}
parts := bytes.SplitN(line, []byte("="), 2)
if len(parts) != 2 {
log.Printf("pkgfs: bad contents line: %v", line)
continue
}
pd.contents[string(parts[0])] = string(parts[1])
}
if err != nil {
return nil, goErrToFSErr(err)
}
pd.contents["meta"] = blob
for _, name := range fr.List() {
if !strings.HasPrefix(name, "meta/") {
log.Printf("pkgfs:packageDir:new %q/%q illegal file in meta.far: %q", pd.name, pd.version, name)
continue
}
pd.contents[name] = name
}
return &pd, nil
}
func (d *packageDir) Close() error {
debugLog("pkgfs:packageDir:close %q/%q", d.name, d.version)
return nil
}
func (d *packageDir) Dup() (fs.Directory, error) {
return d, nil
}
func (d *packageDir) Reopen(flags fs.OpenFlags) (fs.Directory, error) {
return d, nil
}
func (d *packageDir) getBlobFor(path string) (string, bool) {
root, ok := d.contents[path]
return root, ok
}
func (d *packageDir) Open(name string, flags fs.OpenFlags) (fs.File, fs.Directory, *fs.Remote, error) {
name = clean(name)
debugLog("pkgfs:packagedir:open %q", name)
if d.subdir != nil {
name = filepath.Join(*d.subdir, name)
}
if name == "" {
return nil, d, nil, nil
}
if flags.Create() || flags.Truncate() || flags.Write() || flags.Append() {
debugLog("pkgfs:packagedir:open %q unsupported flags", name)
return nil, nil, nil, fs.ErrNotSupported
}
if name == "meta" {
if flags.File() || (!flags.Directory() && !flags.Path()) {
mff := newMetaFile(d.name, d.version, d.contents[name], d.fs, flags)
return mff, nil, nil, nil
}
mfd := newMetaFarDir(d.name, d.version, d.contents[name], d.fs)
return nil, mfd, nil, nil
}
if strings.HasPrefix(name, "meta/") {
if _, found := d.contents[name]; !found {
return nil, nil, nil, fs.ErrNotFound
}
mfd := newMetaFarDir(d.name, d.version, d.contents["meta"], d.fs)
return mfd.Open(strings.TrimPrefix(name, "meta"), flags)
}
if root, ok := d.contents[name]; ok {
return nil, nil, &fs.Remote{Channel: d.fs.blobfs.Channel(), Path: root, Flags: flags}, nil
}
dirname := name + "/"
for k := range d.contents {
if strings.HasPrefix(k, dirname) {
// subdir is a copy of d, but with subdir set
subdir := *d
subdir.subdir = &dirname
return nil, &subdir, nil, nil
}
}
debugLog("pkgfs:packagedir:open %q not found", name)
return nil, nil, nil, fs.ErrNotFound
}
func (d *packageDir) Read() ([]fs.Dirent, error) {
// TODO(raggi): improve efficiency
dirs := map[string]struct{}{}
dents := []fs.Dirent{}
dents = append(dents, dirDirEnt("."))
if d.subdir == nil {
dirs["meta"] = struct{}{}
dents = append(dents, dirDirEnt("meta"))
}
for name := range d.contents {
if d.subdir != nil {
if !strings.HasPrefix(name, *d.subdir) {
continue
}
name = strings.TrimPrefix(name, *d.subdir)
}
parts := strings.SplitN(name, "/", 2)
if len(parts) == 2 {
if _, ok := dirs[parts[0]]; !ok {
dirs[parts[0]] = struct{}{}
dents = append(dents, dirDirEnt(parts[0]))
}
} else {
// TODO(PKG-44): fix the potential for discrepancies here
// most of the time there are no pointers in contents for dirs, but the
// exception is the meta pointer which this would mistake for a file, so we
// must check for a name collision here too.
if _, ok := dirs[parts[0]]; !ok {
dents = append(dents, fileDirEnt(parts[0]))
}
}
}
return dents, nil
}
func (d *packageDir) Stat() (int64, time.Time, time.Time, error) {
debugLog("pkgfs:packagedir:stat %q/%q", d.name, d.version)
// TODO(raggi): forward stat values from the index
return 0, d.fs.mountTime, d.fs.mountTime, nil
}
func (d *packageDir) Blobs() []string {
// TODO(PKG-273) consider preallocation which would over-allocate, but cause less thrash
blobs := []string{}
for path, blob := range d.contents {
if strings.HasPrefix(path, "meta/") {
continue
}
blobs = append(blobs, blob)
}
return blobs
}