| // Copyright 2023 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 packages |
| |
| import ( |
| "context" |
| "encoding/json" |
| "errors" |
| "fmt" |
| "math/rand" |
| "os" |
| |
| "go.fuchsia.dev/fuchsia/src/sys/pkg/bin/pm/build" |
| "go.fuchsia.dev/fuchsia/src/testing/host-target-testing/util" |
| ) |
| |
| const ( |
| staticPackagesPath string = "data/static_packages" |
| cachePackagesPath string = "data/cache_packages.json" |
| ) |
| |
| type SystemImagePackage struct { |
| p Package |
| packages map[string]build.MerkleRoot |
| } |
| |
| func newSystemImagePackage(ctx context.Context, p Package) (*SystemImagePackage, error) { |
| packages := make(map[string]build.MerkleRoot) |
| |
| // Parse the base packages list. |
| if f, err := p.Open(ctx, staticPackagesPath); err == nil { |
| defer f.Close() |
| |
| if contents, err := build.ParseMetaContents(f); err == nil { |
| for path, merkle := range contents { |
| packages[path] = merkle |
| } |
| } else { |
| return nil, err |
| } |
| } else { |
| return nil, fmt.Errorf("failed to open system image's base packages: %w", err) |
| } |
| |
| // Parse the cache packages list. |
| if b, err := p.ReadFile(ctx, cachePackagesPath); err == nil { |
| contents, err := parseCachePackages(b) |
| if err != nil { |
| return nil, err |
| } |
| |
| for path, merkle := range contents { |
| packages[path] = merkle |
| } |
| } else if !errors.Is(err, os.ErrNotExist) { |
| return nil, fmt.Errorf("failed to open system image's cache packages: %w", err) |
| } |
| |
| return &SystemImagePackage{ |
| p: p, |
| packages: packages, |
| }, nil |
| } |
| |
| func (u *SystemImagePackage) Repository() *Repository { |
| return u.p.Repository() |
| } |
| |
| func (u *SystemImagePackage) String() string { |
| return u.p.String() |
| } |
| |
| func (u *SystemImagePackage) Path() string { |
| return u.p.Path() |
| } |
| |
| func (u *SystemImagePackage) Merkle() build.MerkleRoot { |
| return u.p.Merkle() |
| } |
| |
| func (u *SystemImagePackage) EditContents( |
| ctx context.Context, |
| dstSystemImagePath string, |
| editFunc func(tempDir string) error, |
| ) (*SystemImagePackage, error) { |
| return u.EditPackage(ctx, func(p Package) (Package, error) { |
| return p.EditContents(ctx, dstSystemImagePath, editFunc) |
| }) |
| } |
| |
| func (u *SystemImagePackage) EditPackage( |
| ctx context.Context, |
| editFunc func(p Package) (Package, error), |
| ) (*SystemImagePackage, error) { |
| p, err := editFunc(u.p) |
| if err != nil { |
| return nil, err |
| } |
| |
| return &SystemImagePackage{ |
| p: p, |
| packages: u.packages, |
| }, nil |
| } |
| |
| // SystemImageSize returns the transitive space needed to store all the blobs |
| // in the system image. It does not include the update image package |
| // blobs, since those are garbage collected during the OTA. |
| func (u *SystemImagePackage) SystemImageAlignedBlobSize(ctx context.Context) (uint64, error) { |
| blobs, err := u.SystemImageBlobs(ctx) |
| if err != nil { |
| return 0, err |
| } |
| |
| return u.p.repo.sumAlignedBlobSizes(ctx, blobs) |
| } |
| |
| // SystemImageBlobs returns the transitive blobs in the system image and the |
| // available set package blobs contained in the system image. |
| func (u *SystemImagePackage) SystemImageBlobs(ctx context.Context) (map[build.MerkleRoot]struct{}, error) { |
| visitedPackages := make(map[build.MerkleRoot]struct{}) |
| blobs := make(map[build.MerkleRoot]struct{}) |
| |
| // This will contain all the blobs in the system image packages, and |
| // any of its subpackages. |
| if err := u.p.transitiveBlobs(ctx, visitedPackages, blobs); err != nil { |
| return nil, err |
| } |
| |
| if err := u.systemImagePackageBlobs(ctx, visitedPackages, blobs); err != nil { |
| return nil, err |
| } |
| |
| return blobs, nil |
| } |
| |
| func (u *SystemImagePackage) systemImagePackageBlobs( |
| ctx context.Context, |
| visitedPackages map[build.MerkleRoot]struct{}, |
| blobs map[build.MerkleRoot]struct{}, |
| ) error { |
| for path, merkle := range u.packages { |
| pkg, err := newPackage(ctx, u.p.repo, path, merkle) |
| if err != nil { |
| return err |
| } |
| |
| if err := pkg.transitiveBlobs(ctx, visitedPackages, blobs); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| // AddRandomFiles will extend the system image package with extra files that add |
| // up to `additionalBytes` |
| // the total size of the system image package blobs and the available set |
| // package blobs is less than or equal to `maxSize`. |
| func (u *SystemImagePackage) AddRandomFilesWithAdditionalBytes( |
| ctx context.Context, |
| rand *rand.Rand, |
| dstSystemImagePath string, |
| bytesToAdd uint64, |
| ) (*SystemImagePackage, error) { |
| return u.EditPackage(ctx, func(p Package) (Package, error) { |
| return p.AddRandomFilesWithAdditionalBytes( |
| ctx, |
| rand, |
| dstSystemImagePath, |
| bytesToAdd, |
| ) |
| }) |
| } |
| |
| // AddRandomFilesWithUpperBound will extend the system image package such that |
| // the total size of the system image package blobs and the available set |
| // package blobs is less than or equal to `maxSize`. |
| func (u *SystemImagePackage) AddRandomFilesWithUpperBound( |
| ctx context.Context, |
| rand *rand.Rand, |
| dstSystemImagePath string, |
| maxSize uint64, |
| ) (*SystemImagePackage, error) { |
| visitedPackages := make(map[build.MerkleRoot]struct{}) |
| packageBlobs := make(map[build.MerkleRoot]struct{}) |
| |
| if err := u.systemImagePackageBlobs(ctx, visitedPackages, packageBlobs); err != nil { |
| return nil, err |
| } |
| |
| return u.EditPackage(ctx, func(p Package) (Package, error) { |
| return p.addRandomFilesWithUpperBound( |
| ctx, |
| rand, |
| dstSystemImagePath, |
| maxSize, |
| packageBlobs, |
| ) |
| }) |
| } |
| |
| func parseCachePackages(b []byte) (map[string]build.MerkleRoot, error) { |
| contents := make(map[string]build.MerkleRoot) |
| |
| // Treat an empty file as an empty map. |
| if len(b) == 0 { |
| return contents, nil |
| } |
| |
| // Otherwise parse the json. |
| var cachePackages struct { |
| Content []string `json:"content"` |
| Version string `json:"version"` |
| } |
| |
| if err := json.Unmarshal(b, &cachePackages); err != nil { |
| return nil, fmt.Errorf("failed to parse cache packages: %w", err) |
| } |
| |
| if cachePackages.Version != "1" { |
| return nil, fmt.Errorf( |
| "do not know how to parse %s version %s", |
| cachePackagesPath, |
| cachePackages.Version, |
| ) |
| } |
| |
| for _, s := range cachePackages.Content { |
| url, merkle, err := util.ParsePackageUrl(s) |
| if err != nil { |
| return nil, fmt.Errorf("failed to parse %s: %w", s, err) |
| } |
| |
| contents[url.Path[1:]] = merkle |
| } |
| |
| return contents, nil |
| } |