blob: 35df5e23349246e8d02fd625267254c06801e7e5 [file] [edit]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package cros
import (
"context"
"fmt"
"strings"
"time"
"go.chromium.org/luci/common/errors"
"go.chromium.org/infra/cros/recovery/internal/components"
)
// ReadFRID reads the FRID from an android-based device.
func ReadFRID(ctx context.Context, timeout time.Duration, run components.Runner) (string, error) {
productSku, err := run(ctx, timeout, "getprop", "ro.boot.vendor.product.sku")
if err != nil {
return "", errors.Annotate(err, "get vendor product sku from output")
}
frid := normalizeVendorProductSku(productSku)
if err := validateFRID(frid); err != nil {
return "", err
}
return frid, nil
}
// normalizeVendorProductSku normalizes the output of getprop ro.boot.vendor.product.sku for further use in futility.
//
// sample output: spiffy_######
func normalizeVendorProductSku(output string) string {
lowercased := strings.ToLower(output)
l := strings.LastIndex(output, "_")
if l == -1 {
return lowercased
}
return fmt.Sprintf("google_%s", output[:l])
}
// validateFRID validates a frid right before we modify the ChromeOs object on the exec info. It is our last line of defense
func validateFRID(frid string) error {
if frid == "" {
return errors.New("frid cannot be empty")
}
if frid != strings.ToLower(frid) {
return fmt.Errorf("frid %q is not exclusively lowercase", frid)
}
if !strings.HasPrefix(frid, "google_") {
return fmt.Errorf("frid %q is missing google_ prefix", frid)
}
return nil
}