[botanist] Add support for fastboot booting zedboot.

Bug: 124946
Change-Id: I954eb8c28336a9bd1d1b37317d6671fcd773e8a4
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/832821
Reviewed-by: Oliver Newman <olivernewman@google.com>
Commit-Queue: Ina Huh <ihuh@google.com>
(cherry picked from commit 5d8304448c910d9168336a3e6671e5847e249ded)
diff --git a/tools/botanist/cmd/run.go b/tools/botanist/cmd/run.go
index c631215..66aa64b 100644
--- a/tools/botanist/cmd/run.go
+++ b/tools/botanist/cmd/run.go
@@ -430,8 +430,10 @@
 			return err
 		}
 	}
-	// Some preflight commands can cause side effects that take up to 30s.
-	time.Sleep(30 * time.Second)
+	if len(cmds.Commands) > 0 {
+		// Some preflight commands can cause side effects that take up to 30s.
+		time.Sleep(30 * time.Second)
+	}
 	logger.Debugf(ctx, "done running preflights")
 	return nil
 }
diff --git a/tools/botanist/targets/device.go b/tools/botanist/targets/device.go
index 40de2ef..e70b1a5 100644
--- a/tools/botanist/targets/device.go
+++ b/tools/botanist/targets/device.go
@@ -13,6 +13,7 @@
 	"net"
 	"os"
 	"os/exec"
+	"path/filepath"
 	"sync/atomic"
 	"time"
 
@@ -39,6 +40,10 @@
 	// String to look for in serial log that indicates system booted. From
 	// https://cs.opensource.google/fuchsia/fuchsia/+/main:zircon/kernel/top/main.cc;l=116;drc=6a0fd696cde68b7c65033da57ab911ee5db75064
 	bootedLogSignature = "welcome to Zircon"
+
+	// Whether we should place the device in Zedboot if idling in fastboot.
+	// TODO(fxbug.dev/124946): Remove once release branches no longer need this.
+	mustLoadThroughZedboot = true
 )
 
 // DeviceConfig contains the static properties of a target device.
@@ -186,29 +191,13 @@
 	return t.sshClient(&net.IPAddr{IP: addr})
 }
 
+func (t *DeviceTarget) mustLoadThroughZedboot() bool {
+	return mustLoadThroughZedboot || t.config.FastbootSernum == "" || !t.UseFFXExperimental(1)
+}
+
 // Start starts the device target.
 func (t *DeviceTarget) Start(ctx context.Context, images []bootserver.Image, args []string) error {
 	serialSocketPath := t.SerialSocketPath()
-	// Initialize the tftp client if:
-	// 1. It is currently uninitialized.
-	// 2. The device cannot be accessed via fastboot.
-	if t.config.FastbootSernum == "" && t.tftp == nil {
-		// Discover the node on the network and initialize a tftp client to
-		// talk to it.
-		addr, err := netutil.GetNodeAddress(ctx, t.Nodename())
-		if err != nil {
-			return err
-		}
-		tftpClient, err := tftp.NewClient(&net.UDPAddr{
-			IP:   addr.IP,
-			Port: tftp.ClientPort,
-			Zone: addr.Zone,
-		}, 0, 0)
-		if err != nil {
-			return err
-		}
-		t.tftp = tftpClient
-	}
 
 	// Set up log listener and dump kernel output to stdout.
 	l, err := netboot.NewLogListener(t.Nodename())
@@ -266,7 +255,7 @@
 		}
 		var imgs []bootserver.Image
 		var ffxFlashDeps []string
-		if t.UseFFXExperimental(1) && !t.opts.Netboot {
+		if t.UseFFXExperimental(1) && !t.opts.Netboot && !t.mustLoadThroughZedboot() {
 			ffxFlashDeps, err = ffxutil.GetFlashDeps(wd, "fuchsia")
 			if err != nil {
 				return err
@@ -296,7 +285,11 @@
 			}
 		}
 
-		if t.opts.Netboot {
+		if t.mustLoadThroughZedboot() {
+			if err := t.bootZedboot(ctx, imgs); err != nil {
+				return err
+			}
+		} else if t.opts.Netboot {
 			if err := t.ramBoot(ctx, imgs); err != nil {
 				return err
 			}
@@ -305,7 +298,28 @@
 				return err
 			}
 		}
-	} else {
+	}
+	if t.mustLoadThroughZedboot() {
+		// Initialize the tftp client if:
+		// 1. It is currently uninitialized.
+		// 2. The device has been placed in Zedboot.
+		if t.tftp == nil {
+			// Discover the node on the network and initialize a tftp client to
+			// talk to it.
+			addr, err := netutil.GetNodeAddress(ctx, t.Nodename())
+			if err != nil {
+				return err
+			}
+			tftpClient, err := tftp.NewClient(&net.UDPAddr{
+				IP:   addr.IP,
+				Port: tftp.ClientPort,
+				Zone: addr.Zone,
+			}, 0, 0)
+			if err != nil {
+				return err
+			}
+			t.tftp = tftpClient
+		}
 		var imgs []bootserver.Image
 		for _, img := range images {
 			if t.imageOverrides.IsEmpty() {
@@ -349,6 +363,38 @@
 	return nil
 }
 
+func (t *DeviceTarget) bootZedboot(ctx context.Context, images []bootserver.Image) error {
+	fastboot := getImageByName(images, "exe.linux-x64_fastboot")
+	if fastboot == nil {
+		return errors.New("fastboot not found")
+	}
+	zbi := getImageByName(images, "zbi_zircon-r")
+	vbmeta := getImageByName(images, "vbmeta_zircon-r")
+	logger.Debugf(ctx, "zbi: %s, vbmeta: %s", zbi.Path, vbmeta.Path)
+	zbiContents, err := os.ReadFile(zbi.Path)
+	if err != nil {
+		return err
+	}
+	vbmetaContents, err := os.ReadFile(vbmeta.Path)
+	if err != nil {
+		return err
+	}
+	combinedZBIVBMeta := filepath.Join(filepath.Dir(zbi.Path), "zedboot.combined")
+	err = os.WriteFile(combinedZBIVBMeta, append(zbiContents, vbmetaContents...), 0666)
+	if err != nil {
+		return err
+	}
+	cmd := exec.CommandContext(ctx, fastboot.Path, "-s", t.config.FastbootSernum, "boot", combinedZBIVBMeta)
+	stdout, stderr, flush := botanist.NewStdioWriters(ctx)
+	defer flush()
+	cmd.Stdout = stdout
+	cmd.Stderr = stderr
+	logger.Debugf(ctx, "starting: %v", cmd.Args)
+	err = cmd.Run()
+	logger.Debugf(ctx, "done booting zedboot")
+	return err
+}
+
 func (t *DeviceTarget) ramBoot(ctx context.Context, images []bootserver.Image) error {
 	// TODO(fxbug.dev/91352): Remove experimental condition once stable.
 	if t.UseFFXExperimental(1) {
@@ -480,7 +526,7 @@
 
 	// If we have specified image overrides, then we are only looking for image
 	// among those specifications in the case of flashing.
-	if t.imageOverrides.ZBI != "" || t.imageOverrides.VBMeta != "" {
+	if !t.mustLoadThroughZedboot() && (t.imageOverrides.ZBI != "" || t.imageOverrides.VBMeta != "") {
 		return img.Label == t.imageOverrides.ZBI || img.Label == t.imageOverrides.VBMeta
 	}
 
diff --git a/tools/integration/testsharder/images.go b/tools/integration/testsharder/images.go
index 021ec56..d69c507 100644
--- a/tools/integration/testsharder/images.go
+++ b/tools/integration/testsharder/images.go
@@ -57,7 +57,9 @@
 		}
 
 		// TODO(fxbug.dev/47531): Remove zedboot images once we switch to flashing.
-		return !s.Env.TargetsEmulator() && len(image.PaveZedbootArgs) != 0
+		// TODO(fxbug.dev/124946): Remove flashing deps once we no longer need to fastboot boot zedboot
+		// for release branches.
+		return !s.Env.TargetsEmulator() && (len(image.PaveZedbootArgs) != 0 || isFlashingDep(image))
 	}
 
 	if s.Env.TargetsEmulator() {