blob: 7675e9d777cfc6e7440db660f27b648b54db8e12 [file] [log] [blame]
// 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.
package system_updater
import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall/zx"
"app/context"
fuchsiaio "fidl/fuchsia/io"
"fidl/fuchsia/pkg"
"syslog/logger"
)
type Package struct {
namever string
merkle string
}
func ConnectToPackageResolver() (*pkg.PackageResolverInterface, error) {
context := context.CreateFromStartupInfo()
req, pxy, err := pkg.NewPackageResolverInterfaceRequest()
if err != nil {
logger.Errorf("control interface could not be acquired: %s", err)
return nil, err
}
context.ConnectToEnvService(req)
return pxy, nil
}
func ParseRequirements(pkgSrc io.ReadCloser, imgSrc io.ReadCloser) ([]*Package, []string, error) {
imgs := []string{}
pkgs := []*Package{}
rdr := bufio.NewReader(pkgSrc)
for {
l, err := rdr.ReadString('\n')
s := strings.TrimSpace(l)
if (err == nil || err == io.EOF) && len(s) > 0 {
entry := strings.Split(s, "=")
if len(entry) != 2 {
return nil, nil, fmt.Errorf("parser: entry format %q", s)
} else {
pkgs = append(pkgs, &Package{namever: entry[0], merkle: entry[1]})
}
}
if err != nil {
if err != io.EOF {
return nil, nil, fmt.Errorf("parser: got error reading packages file %s", err)
}
break
}
}
rdr = bufio.NewReader(imgSrc)
for {
l, err := rdr.ReadString('\n')
s := strings.TrimSpace(l)
if (err == nil || err == io.EOF) && len(s) > 0 {
imgs = append(imgs, s)
}
if err != nil {
if err != io.EOF {
return nil, nil, fmt.Errorf("parser: got error reading images file %s", err)
}
break
}
}
return pkgs, imgs, nil
}
func FetchPackages(pkgs []*Package, resolver *pkg.PackageResolverInterface) error {
var errCount int
for _, pkg := range pkgs {
if err := fetchPackage(pkg, resolver); err != nil {
logger.Errorf("fetch error: %s", err)
errCount++
}
}
if errCount > 0 {
return fmt.Errorf("system update failed, %d packages had errors", errCount)
}
return nil
}
func fetchPackage(p *Package, resolver *pkg.PackageResolverInterface) error {
b, err := ioutil.ReadFile(filepath.Join("/pkgfs/packages", p.namever, "meta"))
if err == nil {
// package is already installed, skip
if string(b) == p.merkle {
return nil
}
}
pkgUri := fmt.Sprintf("fuchsia-pkg://fuchsia.com/%s?hash=%s", p.namever, p.merkle)
selectors := []string{}
updatePolicy := pkg.UpdatePolicy{}
dirReq, dirPxy, err := fuchsiaio.NewDirectoryInterfaceRequest()
defer dirPxy.Close()
logger.Infof("requesting %s from update system", pkgUri)
status, err := resolver.Resolve(pkgUri, selectors, updatePolicy, dirReq)
if err != nil {
return fmt.Errorf("fetch: Resolve error: %s", err)
}
statusErr := zx.Status(status)
if statusErr != zx.ErrOk {
return fmt.Errorf("fetch: Resolve status: %s", statusErr)
}
return nil
}
var diskImagerPath = filepath.Join("/pkg", "bin", "install-disk-image")
func ValidateImgs(imgs []string, imgsPath string) error {
boardPath := filepath.Join(imgsPath, "board")
actual, err := ioutil.ReadFile(boardPath)
if err != nil {
if os.IsNotExist(err) {
return nil
} else {
return err
}
}
expected, err := ioutil.ReadFile("/config/build-info/board")
if err != nil {
return err
}
if !bytes.Equal(actual, expected) {
return fmt.Errorf("parser: expected board name %s found %s", expected, actual)
}
return nil
}
func WriteImgs(imgs []string, imgsPath string) error {
logger.Infof("Writing images %+v from %q", imgs, imgsPath)
for _, img := range imgs {
imgPath := filepath.Join(imgsPath, img)
if fi, err := os.Stat(imgPath); err != nil || fi.Size() == 0 {
logger.Warnf("img_writer: %q image not found or zero length, skipping", img)
continue
}
var c *exec.Cmd
switch img {
case "zbi", "zbi.signed":
c = exec.Command(diskImagerPath, "install-zircona")
case "zedboot", "zedboot.signed":
c = exec.Command(diskImagerPath, "install-zirconr")
// TODO(ZX-2689): remove once the bootloader is booting zirconr as recovery.
if img == "zedboot.signed" {
c = exec.Command(diskImagerPath, "install-zirconb")
}
case "bootloader":
c = exec.Command(diskImagerPath, "install-bootloader")
case "board":
continue
default:
return fmt.Errorf("unrecognized image %q", img)
}
logger.Infof("img_writer: writing %q from %q", img, imgPath)
out, err := writeImg(c, imgPath)
if len(out) != 0 {
logger.Infof("img_writer: %s", string(out))
}
if err != nil {
logger.Errorf("img_writer: error writing %q from %q: %s", img, imgPath, err)
if len(out) != 0 {
logger.Errorf("img_writer: %s", string(out))
}
return err
}
logger.Infof("img_writer: wrote %q successfully from %q", img, imgPath)
}
return nil
}
func writeImg(c *exec.Cmd, path string) ([]byte, error) {
info, err := os.Stat(path)
if err != nil {
return nil, err
}
if info.Size() == 0 {
return nil, fmt.Errorf("img_writer: image file is empty!")
}
imgFile, err := os.Open(path)
if err != nil {
return nil, err
}
defer imgFile.Close()
c.Stdin = imgFile
return c.CombinedOutput()
}
// UpdateCurrentChannel persists the update channel info for a successful update
func UpdateCurrentChannel() error {
targetPath := "/misc/ota/target_channel.json"
contents, err := ioutil.ReadFile(targetPath)
if err != nil {
return fmt.Errorf("no target channel recorded in %v: %v", targetPath, err)
}
currentPath := "/misc/ota/current_channel.json"
partPath := currentPath + ".part"
f, err := os.Create(partPath)
if err != nil {
return fmt.Errorf("unable to write current channel to %v: %v", partPath, err)
}
defer f.Close()
buf := bytes.NewBuffer(contents)
_, err = buf.WriteTo(f)
if err != nil {
return fmt.Errorf("unable to write current channel to %v: %v", currentPath, err)
}
f.Sync()
f.Close()
if err := os.Rename(partPath, currentPath); err != nil {
return fmt.Errorf("error moving %v to %v: %v", partPath, currentPath, err)
}
return nil
}