blob: 7841323b52b69261151e4d2648437316755ec5fa [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.
// +build !build_with_native_toolchain
package pkgfs
import (
"bytes"
"encoding/json"
"log"
"os"
"path/filepath"
"strings"
"time"
"go.fuchsia.dev/fuchsia/src/lib/thinfs/fs"
"go.fuchsia.dev/fuchsia/src/sys/pkg/bin/pm/pkg"
"go.fuchsia.dev/fuchsia/src/sys/pkg/lib/far/go"
)
type packagedItem struct {
blobId string
executable bool
}
type packageDir struct {
unsupportedDirectory
fs *Filesystem
merkleroot string
contents map[string]packagedItem
executable bool
// if this packagedir is a subdirectory, then this is the prefix name
subdir *string
}
func newPackageDir(name, version string, filesystem *Filesystem, executable bool) (*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, executable)
}
func isExecutablePath(path string) bool {
// TODO(fxbug.dev/37328): try limiting this to just lib/, bin/, and test/
// prefixes? Or put explicit bits for each file in the manifest.
return true
}
// Initialize a package directory server interface from a package meta.far
func newPackageDirFromBlob(blob string, filesystem *Filesystem, executable bool) (*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("package:" + blob),
merkleroot: blob,
fs: filesystem,
contents: map[string]packagedItem{},
executable: executable,
}
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
}
path := string(parts[0])
pd.contents[path] = packagedItem{
blobId: string(parts[1]),
executable: isExecutablePath(path),
}
}
if err != nil {
return nil, goErrToFSErr(err)
}
pd.contents["meta"] = packagedItem{
blobId: blob,
executable: false,
}
for _, name := range fr.List() {
if !strings.HasPrefix(name, "meta/") {
log.Printf("package:%s illegal file in meta.far: %q", pd.merkleroot, name)
continue
}
pd.contents[name] = packagedItem{
blobId: name,
executable: false,
}
}
return &pd, nil
}
func (d *packageDir) Close() error {
return nil
}
func (d *packageDir) Dup() (fs.Directory, error) {
return d, nil
}
func (d *packageDir) getBlobFor(path string) (string, bool) {
root, ok := d.contents[path]
return root.blobId, ok
}
func (d *packageDir) Open(name string, flags fs.OpenFlags) (fs.File, fs.Directory, *fs.Remote, error) {
name = clean(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() {
return nil, nil, nil, fs.ErrNotSupported
}
if name == "meta" {
mf, err := newMetaFar(d.contents[name].blobId, d.fs)
if err != nil {
return nil, nil, nil, err
}
if flags.File() || (!flags.Directory() && !flags.Path()) {
mff := newMetaFile(mf, flags)
return mff, nil, nil, nil
}
mfd := newMetaFarDir(mf)
return nil, mfd, nil, nil
}
if strings.HasPrefix(name, "meta/") {
mf, err := newMetaFar(d.contents["meta"].blobId, d.fs)
if err != nil {
return nil, nil, nil, err
}
mfd := newMetaFarDir(mf)
return mfd.Open(strings.TrimPrefix(name, "meta"), flags)
}
if root, ok := d.contents[name]; ok {
if flags.Execute() {
if !root.executable {
return nil, nil, nil, fs.ErrPermission
}
// TODO(fxbug.dev/48930) Remove this temporary feature when possible.
if !d.executable && d.fs.enforceNonBaseExecutabilityRestrictions {
log.Printf("pkgfs: attempted executable open of %s. This is not allowed due to executability restrictions in pkgfs. See fxbug.dev/48902", name)
return nil, nil, nil, fs.ErrPermission
}
}
return nil, nil, &fs.Remote{Channel: d.fs.blobfs.Channel(), Path: root.blobId, 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
}
}
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(fxbug.dev/22014): 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) {
// TODO(raggi): forward stat values from the index
return 0, d.fs.mountTime, d.fs.mountTime, nil
}
func (d *packageDir) Blobs() []string {
// TODO(fxbug.dev/22235) 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.blobId)
}
return blobs
}