| // 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 |
| } |