blob: 5162d6fcbd93a190e756548a1af3cba4332b1883 [file] [log] [blame]
// 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"
"fmt"
"math/rand"
"net/url"
"go.fuchsia.dev/fuchsia/src/sys/pkg/bin/pm/build"
"go.fuchsia.dev/fuchsia/src/testing/host-target-testing/util"
"go.fuchsia.dev/fuchsia/tools/lib/logger"
)
type UpdateImages struct {
repo *Repository
images util.ImagesManifest
zbiPackage Package
// The zbiUrl and vbmetaUrl only differ on the fragment.
zbiUrl *url.URL
vbmetaUrl *url.URL
}
func newUpdateImages(
ctx context.Context,
repo *Repository,
images util.ImagesManifest,
) (*UpdateImages, error) {
// Create a new zbi+vbmeta package that points at the system image package.
zbiUrl, zbiMerkle, err := images.GetPartition("fuchsia", "zbi")
if err != nil {
return nil, fmt.Errorf("failed to read zbi from images manifest: %w", err)
}
if zbiUrl.Fragment == "" {
return nil, fmt.Errorf("url must have fragment for zbi: %q", zbiUrl)
}
vbmetaUrl, _, err := images.GetPartition("fuchsia", "vbmeta")
if err != nil {
return nil, fmt.Errorf("failed to read vbmeta from images manifest: %w", err)
}
if vbmetaUrl.Fragment == "" {
return nil, fmt.Errorf("url must have fragment for vbmeta: %q", vbmetaUrl)
}
// TODO(http://b/309019440): While it's possible to produce an `images.json`
// file that has the `zbi` and `vbmeta` files in separate packages, we only
// support these files being in the same package (which happens to be what
// the fuchsia build produces). That's because when we use `avbtool.py` to
// generate the new `vbmeta`, it'll open the `zbi` with `r+`. It's a lot
// easier for us if both of these files are in the same package so we don't
// need to copy files around or iteratively generate packages.
//
// Because of that, require that these urls are the same, other than the
// fragment.
zbiU := *zbiUrl
vbmetaU := *vbmetaUrl
zbiU.Fragment = ""
vbmetaU.Fragment = ""
if zbiU.String() != vbmetaU.String() {
return nil, fmt.Errorf(
"zbi and vbmeta package URLs should only differ on fragment: %s != %s",
zbiUrl,
vbmetaUrl,
)
}
zbiPath := zbiUrl.Path[1:]
zbiPackage, err := newPackage(ctx, repo, zbiPath, zbiMerkle)
if err != nil {
return nil, err
}
return &UpdateImages{
repo: repo,
images: images,
zbiUrl: zbiUrl,
vbmetaUrl: vbmetaUrl,
zbiPackage: zbiPackage,
}, nil
}
func (u *UpdateImages) replaceZbiPackage(
ctx context.Context,
dstZbi Package,
) (*UpdateImages, error) {
images := u.images.Clone()
// Update the zbi partition entry.
dstZbiUrlString := fmt.Sprintf(
"fuchsia-pkg://%s/%s?hash=%s#%s",
u.zbiUrl.Host,
dstZbi.Path(),
dstZbi.Merkle(),
u.zbiUrl.Fragment,
)
dstZbiUrl, err := url.Parse(dstZbiUrlString)
if err != nil {
return nil, fmt.Errorf("failed to parse url %s: %w", dstZbiUrlString, err)
}
logger.Infof(ctx, "updating update image zbi package url to %s", dstZbiUrl)
zbiBlobPath, err := dstZbi.FilePath(ctx, u.zbiUrl.Fragment)
if err != nil {
return nil, fmt.Errorf("failed to find zbi blob path for %s: %w", u.zbiUrl.Fragment, err)
}
if err := images.SetPartition("fuchsia", "zbi", dstZbiUrl.String(), zbiBlobPath); err != nil {
return nil, fmt.Errorf("failed to set zbi partition: %w", err)
}
dstVbmetaUrlString := fmt.Sprintf(
"fuchsia-pkg://%s/%s?hash=%s#%s",
u.vbmetaUrl.Host,
dstZbi.Path(),
dstZbi.Merkle(),
u.vbmetaUrl.Fragment,
)
dstVbmetaUrl, err := url.Parse(dstVbmetaUrlString)
if err != nil {
return nil, fmt.Errorf("failed to parse url %s: %w", dstVbmetaUrlString, err)
}
logger.Infof(ctx, "updating update image vbmeta package url to %s", dstVbmetaUrl)
vbmetaBlobPath, err := dstZbi.FilePath(ctx, u.vbmetaUrl.Fragment)
if err != nil {
return nil, fmt.Errorf("failed to find zbi blob path for %s: %w", u.vbmetaUrl.Fragment, err)
}
if err := images.SetPartition("fuchsia", "vbmeta", dstVbmetaUrl.String(), vbmetaBlobPath); err != nil {
return nil, fmt.Errorf("failed to set vbmeta partition: %w", err)
}
return newUpdateImages(ctx, u.repo, images)
}
func (u *UpdateImages) zbiPath() string {
return u.zbiUrl.Fragment
}
func (u *UpdateImages) vbmetaPath() string {
return u.vbmetaUrl.Fragment
}
func (u *UpdateImages) Rehost(newHostname string) error {
return u.images.Rehost(newHostname)
}
// Create a new zbi+vbmeta package
func (u *UpdateImages) EditZbiAndVbmetaPackage(
ctx context.Context,
editFunc func(p Package, zbiName string, vbmetaName string) (Package, error),
) (*UpdateImages, error) {
dstZbi, err := editFunc(u.zbiPackage, u.zbiUrl.Fragment, u.vbmetaUrl.Fragment)
if err != nil {
return nil, err
}
return u.replaceZbiPackage(ctx, dstZbi)
}
func (u *UpdateImages) EditZbiAndVbmetaContents(
ctx context.Context,
dstPath string,
editFunc func(tempDir string, zbiName string, vbmetaName string) error,
) (*UpdateImages, error) {
return u.EditZbiAndVbmetaPackage(
ctx,
func(p Package, zbiName string, vbmetaName string) (Package, error) {
return p.EditContents(ctx, dstPath, func(tempDir string) error {
return editFunc(tempDir, zbiName, vbmetaName)
})
},
)
}
func (u *UpdateImages) UpdateImagesAlignedBlobSize(ctx context.Context) (uint64, error) {
blobs, err := u.UpdateImagesBlobs(ctx)
if err != nil {
return 0, err
}
return u.repo.sumAlignedBlobSizes(ctx, blobs)
}
func (u *UpdateImages) UpdateImagesBlobs(ctx context.Context) (map[build.MerkleRoot]struct{}, error) {
return u.updateImagesBlobs(ctx, false)
}
func (u *UpdateImages) updateImagesBlobs(
ctx context.Context,
ignoreZbi bool,
) (map[build.MerkleRoot]struct{}, error) {
visitedPackages := make(map[build.MerkleRoot]struct{})
blobs := make(map[build.MerkleRoot]struct{})
for _, partition := range u.images.Contents.Partitions {
url, merkle, err := util.ParsePackageUrl(partition.Url)
if err != nil {
return nil, err
}
if ignoreZbi && u.zbiPackage.Merkle() == merkle {
continue
}
pkg, err := newPackage(ctx, u.repo, url.Path[1:], merkle)
if err != nil {
return nil, err
}
if err := pkg.transitiveBlobs(ctx, visitedPackages, blobs); err != nil {
return nil, err
}
}
for _, firmware := range u.images.Contents.Firmware {
url, merkle, err := util.ParsePackageUrl(firmware.Url)
if err != nil {
return nil, err
}
pkg, err := newPackage(ctx, u.repo, url.Path[1:], merkle)
if err != nil {
return nil, err
}
if err := pkg.transitiveBlobs(ctx, visitedPackages, blobs); err != nil {
return nil, err
}
}
return blobs, nil
}
func (u *UpdateImages) AddRandomFilesWithUpperBound(
ctx context.Context,
rand *rand.Rand,
dstUpdateImagesPath string,
maxSize uint64,
) (*UpdateImages, error) {
additionalBlobs, err := u.updateImagesBlobs(ctx, true)
if err != nil {
return nil, err
}
return u.EditZbiAndVbmetaPackage(
ctx,
func(p Package, zbiName string, vbmetaName string) (Package, error) {
return p.addRandomFilesWithUpperBound(
ctx,
rand,
dstUpdateImagesPath,
maxSize,
additionalBlobs,
)
},
)
}