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