blob: 2ba08ac3058006f13ec40fd0bdb49627a5e9744b [file] [log] [blame]
// Copyright 2020 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 zbi
import (
"context"
"fmt"
"io"
"io/fs"
"os"
"os/exec"
"path/filepath"
"strings"
"go.fuchsia.dev/fuchsia/src/sys/pkg/bin/pm/build"
"go.fuchsia.dev/fuchsia/tools/lib/logger"
)
const basePackagePrefix = "zircon.system.pkgfs.cmd=bin/pkgsvr+"
type ZBITool struct {
zbiToolPath string
stdout io.Writer
}
func NewZBITool(zbiToolPath string) (*ZBITool, error) {
return NewZBIToolWithStdout(zbiToolPath, nil)
}
func NewZBIToolWithStdout(zbiToolPath string, stdout io.Writer) (*ZBITool, error) {
if _, err := os.Stat(zbiToolPath); err != nil {
return nil, err
}
return &ZBITool{
zbiToolPath: zbiToolPath,
stdout: stdout,
}, nil
}
func (z *ZBITool) MakeImageArgsZbi(ctx context.Context, destPath string, imageArgs map[string]string) error {
imageArgsFile, err := os.CreateTemp("", "")
if err != nil {
return err
}
defer os.Remove(imageArgsFile.Name())
for key, value := range imageArgs {
if _, err := imageArgsFile.WriteString(fmt.Sprintf("%s=%s\n", key, value)); err != nil {
return err
}
}
args := []string{
"--output",
destPath,
"--type",
"IMAGE_ARGS",
imageArgsFile.Name(),
}
return z.RunZbiCommand(ctx, args)
}
// Create new ZBI with the system image merkle provided:
// * extract zbi from tempdir
// * extract bootfs from the zbi
// * overwrite bootfs with new system image merkle
// * create zbi manifest to generate new bootfs and zbi
// * generate new bootfs
// * generate new zbi under tempDir
func (z *ZBITool) UpdateZBIWithNewSystemImageMerkle(
ctx context.Context,
systemImageMerkle build.MerkleRoot,
srcZbiPath string,
dstZbiPath string,
bootfsCompression string,
) error {
// Create zbitemp directory to store the overwritten zbi
tempDir, err := os.MkdirTemp("", "")
if err != nil {
return fmt.Errorf("failed to create temp directory for zbi: %q", err)
}
defer os.RemoveAll(tempDir)
// Extract zbi from the source update package
pathToZbiDir := filepath.Join(tempDir, "src")
if err := os.Mkdir(pathToZbiDir, 0700); err != nil {
return err
}
args := []string{
"--extract-items",
"--output-dir",
pathToZbiDir,
srcZbiPath,
}
if err := z.RunZbiCommand(ctx, args); err != nil {
return fmt.Errorf("failed to extract zbi from %s package: %w", srcZbiPath, err)
}
// Extract bootfs from the zbi extractted from step above
zbiFiles, err := os.ReadDir(pathToZbiDir)
if err != nil {
return fmt.Errorf("failed to read zbi directory from %s: %w", pathToZbiDir, err)
}
pathToZbiBootfs := ""
for _, file := range zbiFiles {
if strings.HasSuffix(file.Name(), ".bootfs.zbi") {
pathToZbiBootfs = filepath.Join(pathToZbiDir, file.Name())
break
}
}
if pathToZbiBootfs == "" {
return fmt.Errorf("failed to find bootfs image in zbi from %s", pathToZbiBootfs)
}
pathToBootfs := filepath.Join(tempDir, "bootfs")
args = []string{
"--extract",
"--output-dir",
pathToBootfs,
pathToZbiBootfs,
}
if err := z.RunZbiCommand(ctx, args); err != nil {
return fmt.Errorf("failed to extract bootfs from %s: %q", pathToZbiBootfs, err)
}
// Overwrite system image merkle
devMgrPath := filepath.Join(pathToBootfs, "config", "additional_boot_args")
content, err := os.ReadFile(devMgrPath)
if err != nil {
return err
}
logger.Infof(ctx, "updating the additional boot args config with new system_image_merkle %q", systemImageMerkle)
lines := strings.Split(string(content), "\n")
for i, line := range lines {
if strings.Contains(line, "zircon.system.pkgfs.cmd") {
lines[i] = basePackagePrefix + systemImageMerkle.String()
}
}
output := strings.Join(lines, "\n")
if err := os.WriteFile(devMgrPath, []byte(output), 0644); err != nil {
return fmt.Errorf("failed to update additional boot args: %q", err)
}
// Create new zbi manifest file to generate new zbi
zbiManifest := filepath.Join(tempDir, "manifest")
zbiManifestFile, err := os.OpenFile(zbiManifest, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return fmt.Errorf("failed to create zbi manifest file %q", err)
}
defer zbiManifestFile.Close()
err = filepath.Walk(pathToBootfs, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("failed to access a path %q: %v", path, err)
}
if !info.IsDir() {
key, err := filepath.Rel(pathToBootfs, path)
if err != nil {
return fmt.Errorf("failed to get realtive paths for %s: %q", pathToBootfs, err)
}
if _, err := fmt.Fprintf(zbiManifestFile, "%s=%s\n", key, path); err != nil {
return fmt.Errorf("failed to generate zbi manifest entries %q", err)
}
}
return nil
})
if err != nil {
return fmt.Errorf("failed to create zbi manifest file: %q", err)
}
// Create new bootfs from the zbi manifest
args = []string{
"--output",
dstZbiPath,
"--compressed=" + bootfsCompression,
}
for _, file := range zbiFiles {
if !strings.HasSuffix(file.Name(), ".bootfs.zbi") {
args = append(args, "--type=container", filepath.Join(pathToZbiDir, file.Name()))
} else {
args = append(args, "--files", zbiManifest)
}
}
if err := z.RunZbiCommand(ctx, args); err != nil {
return fmt.Errorf("failed to extract zbi %q", err)
}
return nil
}
func (z *ZBITool) RunZbiCommand(ctx context.Context, args []string) error {
path, err := exec.LookPath(z.zbiToolPath)
if err != nil {
return err
}
logger.Infof(ctx, "running: %s %q", path, args)
cmd := exec.CommandContext(ctx, path, args...)
if z.stdout != nil {
cmd.Stdout = z.stdout
} else {
cmd.Stdout = os.Stdout
}
cmd.Stderr = os.Stderr
return cmd.Run()
}