Snap for 7110675 from 5d40d747e919bc803e77544c6d9e5879f4fe0ae8 to sdk-release
Change-Id: Ibdf9d271dabae3de3b11fc69598017cd071e1d24
diff --git a/Android.bp b/Android.bp
index dd0e560..c3f7c44 100644
--- a/Android.bp
+++ b/Android.bp
@@ -15,10 +15,15 @@
//
python_defaults {
- name: "ota_from_raw_image_defaults",
+ name: "gki_python_defaults",
libs: [
"releasetools_ota_from_target_files",
],
+ version: {
+ py3: {
+ embedded_launcher: true,
+ },
+ },
target: {
darwin: {
// required module "brillo_update_payload" is disabled on darwin
@@ -29,34 +34,21 @@
python_binary_host {
name: "ota_from_raw_image",
- defaults: ["ota_from_raw_image_defaults"],
+ defaults: ["gki_python_defaults"],
srcs: ["ota_from_raw_image.py"],
required: [
"brillo_update_payload",
],
- version: {
- py3: {
- embedded_launcher: true,
- },
- },
}
-python_test_host {
- name: "ota_from_raw_image_test",
- defaults: ["ota_from_raw_image_defaults"],
- test_suites: ["general-tests"],
- srcs: [
- "ota_from_raw_image.py",
- "ota_from_raw_image_test.py",
+python_binary_host {
+ name: "extract_img_from_apex",
+ defaults: ["gki_python_defaults"],
+ srcs: ["extract_img_from_apex.py"],
+ required: [
+ "debugfs",
+ "delta_generator",
],
- version: {
- py3: {
- // When using embedded launcher, atest will try (but may fail) to load libc++.so from
- // host, because the test executable won't be able to find the needed libs via its
- // runpath.
- embedded_launcher: false,
- },
- },
}
apex_key {
@@ -65,23 +57,25 @@
private_key: "com.android.gki.pem",
}
-sh_binary {
+// Use cc_prebuilt_binary because sh_binary does not support product_specific.
+// TODO(b/169954965): Change to sh_binary when product_specific is supported.
+cc_prebuilt_binary {
name: "com.android.gki.preinstall",
- src: "preinstall.sh",
-}
-
-sh_binary {
- name: "com.android.gki.postinstall",
- src: "postinstall.sh",
+ product_specific: true,
+ srcs: ["preinstall.sh"],
+ apex_available: ["com.android.gki.*"],
+ strip: {
+ none: true,
+ },
}
// Common defaults for all GKI APEXes.
apex_defaults {
name: "com.android.gki_defaults",
+ product_specific: true,
binaries: [
"update_engine_stable_client",
"com.android.gki.preinstall",
- "com.android.gki.postinstall",
],
file_contexts: ":com.android.gki-file_contexts",
// Key to sign apex_payload.img
@@ -108,3 +102,51 @@
"-Werror",
],
}
+
+// Build GKI APEX 5.4-android12-0 from $(PRODUCT_OUT)/boot.img.
+// Also generate test packages.
+gki_apex {
+ name: "com.android.gki.kmi_5_4_android12_0",
+ installable: true,
+ kmi_version: "5.4-android12-0",
+ product_out_path: "boot.img",
+ gen_test: true,
+}
+
+// Build GKI APEX 5.10-android12-0 from $(PRODUCT_OUT)/boot.img.
+// Also generate test packages.
+gki_apex {
+ name: "com.android.gki.kmi_5_10_android12_0",
+ installable: true,
+ kmi_version: "5.10-android12-0",
+ product_out_path: "boot.img",
+ gen_test: true,
+}
+
+// Build GKI APEX 5.4-android12-0 from $(PRODUCT_OUT)/boot-5.4.img
+gki_apex {
+ name: "com.android.gki.kmi_5_4_android12_0_boot-5.4",
+ installable: false,
+ kmi_version: "5.4-android12-0",
+ product_out_path: "boot-5.4.img",
+}
+
+// Build GKI APEX 5.4-android12-0 from $(PRODUCT_OUT)/boot-5.10.img
+gki_apex {
+ name: "com.android.gki.kmi_5_10_android12_0_boot-5.10",
+ installable: false,
+ kmi_version: "5.10-android12-0",
+ product_out_path: "boot-5.10.img",
+}
+
+// List of all test APEXes for GkiInstallTest. Append "_test_high" and "_test_low" for each
+// gki_apex with gen_test:true.
+filegroup {
+ name: "gki_install_test_files",
+ srcs: [
+ ":com.android.gki.kmi_5_4_android12_0_test_high",
+ ":com.android.gki.kmi_5_4_android12_0_test_low",
+ ":com.android.gki.kmi_5_10_android12_0_test_high",
+ ":com.android.gki.kmi_5_10_android12_0_test_low",
+ ],
+}
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 1479c90..e3e017d 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -1,8 +1,14 @@
{
"presubmit": [
{
- "name": "ota_from_raw_image_test",
+ "name": "libkver_test"
+ },
+ {
+ "name": "libkver_test",
"host": true
+ },
+ {
+ "name": "GkiInstallTest"
}
]
}
diff --git a/build/Android.bp b/build/Android.bp
new file mode 100644
index 0000000..a991224
--- /dev/null
+++ b/build/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+bootstrap_go_package {
+ name: "gki-soong-rules",
+ pkgPath: "android/soong/gki",
+ pluginFor: ["soong_build"],
+ deps: [
+ "blueprint",
+ "blueprint-proptools",
+ "soong",
+ "soong-android",
+ "soong-apex",
+ "soong-phony",
+ ],
+ srcs: [
+ "gki.go",
+ "kmi.go",
+ "prebuilt.go",
+ "properties.go",
+ "raw_img_ota.go",
+ ],
+ testSrcs: [
+ "kmi_test.go",
+ ],
+}
diff --git a/build/gki.go b/build/gki.go
new file mode 100644
index 0000000..208aaf8
--- /dev/null
+++ b/build/gki.go
@@ -0,0 +1,311 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package gki
+
+import (
+ "path/filepath"
+ "strings"
+
+ "android/soong/android"
+ "android/soong/apex"
+ "android/soong/etc"
+ "android/soong/genrule"
+
+ "github.com/google/blueprint/proptools"
+)
+
+type gkiApexProperties struct {
+ // Path relative to $(PRODUCT_OUT) that points to the boot image. This is
+ // passed to the generated makefile_goal.
+ // Exactly one of [factory, product_out_path] must be set.
+ Product_out_path *string
+
+ // Declared KMI version of the boot image. Example: "5.4-android12-0"
+ Kmi_version *string
+
+ // The certificate to sign the OTA payload.
+ // The name of a certificate in the default certificate directory, blank to
+ // use the default product certificate,
+ // or an android_app_certificate module name in the form ":module".
+ Ota_payload_certificate *string
+
+ // Whether test APEXes are generated. Test APEXes are named with
+ // ${name}_test_high and ${name}_test_low, respectively.
+ Gen_test *bool
+
+ // Whether this APEX is installable to one of the partitions. Default:
+ // see apex.installable.
+ Installable *bool
+
+ // Whether modules should be enabled according to board variables.
+ ModulesEnabled bool `blueprint:"mutated"`
+ // APEX package name that will be declared in the APEX manifest.
+ // e.g. com.android.gki.kmi_5_4_android12_0
+ ApexName *string `blueprint:"mutated"`
+}
+
+type gkiApex struct {
+ android.ModuleBase
+ properties gkiApexProperties
+}
+
+func init() {
+ android.RegisterModuleType("gki_apex", gkiApexFactory)
+}
+
+// Declare a GKI APEX. Generate a set of modules to define an apex with name
+// "com.android.gki" + sanitized(kmi_version).
+func gkiApexFactory() android.Module {
+ g := &gkiApex{}
+ g.AddProperties(&g.properties)
+ android.InitAndroidModule(g)
+ android.AddLoadHook(g, func(ctx android.LoadHookContext) { gkiApexMutator(ctx, g) })
+ return g
+}
+
+func gkiApexMutator(mctx android.LoadHookContext, g *gkiApex) {
+ g.validateAndSetMutableProperties(mctx)
+ g.createModulesRealApexes(mctx)
+}
+
+func (g *gkiApex) validateAndSetMutableProperties(mctx android.LoadHookContext) {
+ // Parse kmi_version property to find APEX name.
+ apexName, err := kmiVersionToApexName(proptools.String(g.properties.Kmi_version))
+ if err != nil {
+ mctx.PropertyErrorf("kmi_version", err.Error())
+ return
+ }
+
+ // Set mutable properties.
+ g.properties.ModulesEnabled = g.bootImgHasRules(mctx) && g.boardDefinesKmiVersion(mctx)
+ g.properties.ApexName = proptools.StringPtr(apexName)
+}
+
+func testApexBundleFactory() android.Module {
+ return apex.ApexBundleFactory(true /* testApex */, false /* art */)
+}
+
+// Create modules for a real APEX package that contains an OTA payload.
+func (g *gkiApex) createModulesRealApexes(mctx android.LoadHookContext) {
+ // Import $(PRODUCT_OUT)/boot.img to Soong
+ bootImage := g.moduleName() + "_bootimage"
+ mctx.CreateModule(android.MakefileGoalFactory, &moduleCommonProperties{
+ Name: proptools.StringPtr(bootImage),
+ Enabled: proptools.BoolPtr(g.properties.ModulesEnabled),
+ }, &makefileGoalProperties{
+ Product_out_path: g.properties.Product_out_path,
+ })
+ // boot.img -> kernel_release.txt
+ mctx.CreateModule(genrule.GenRuleFactory, &moduleCommonProperties{
+ Name: proptools.StringPtr(g.kernelReleaseFileName()),
+ Enabled: proptools.BoolPtr(g.properties.ModulesEnabled),
+ }, &genRuleProperties{
+ Defaults: []string{"extract_kernel_release_defaults"},
+ Srcs: []string{":" + bootImage},
+ })
+ // boot.img -> payload.bin and payload_properties.txt
+ otaPayloadGen := g.moduleName() + "_ota_payload_gen"
+ mctx.CreateModule(rawImageOtaFactory, &moduleCommonProperties{
+ Name: proptools.StringPtr(otaPayloadGen),
+ Enabled: proptools.BoolPtr(g.properties.ModulesEnabled),
+ }, &rawImageOtaProperties{
+ Certificate: g.properties.Ota_payload_certificate,
+ Image_goals: []string{"boot:" + bootImage},
+ })
+ // copy payload.bin to <apex>/etc/ota
+ mctx.CreateModule(etc.PrebuiltEtcFactory, &moduleCommonProperties{
+ Name: proptools.StringPtr(g.otaPayloadName()),
+ Enabled: proptools.BoolPtr(g.properties.ModulesEnabled),
+ }, &prebuiltEtcProperties{
+ Src: proptools.StringPtr(":" + otaPayloadGen + "{" + payloadTag + "}"),
+ Filename_from_src: proptools.BoolPtr(true),
+ Relative_install_path: proptools.StringPtr("ota"),
+ Installable: proptools.BoolPtr(false),
+ })
+ // copy payload_properties.txt to <apex>/etc/ota
+ mctx.CreateModule(etc.PrebuiltEtcFactory, &moduleCommonProperties{
+ Name: proptools.StringPtr(g.otaPropertiesName()),
+ Enabled: proptools.BoolPtr(g.properties.ModulesEnabled),
+ }, &prebuiltEtcProperties{
+ Src: proptools.StringPtr(":" + otaPayloadGen + "{" + payloadPropertiesTag + "}"),
+ Filename_from_src: proptools.BoolPtr(true),
+ Relative_install_path: proptools.StringPtr("ota"),
+ Installable: proptools.BoolPtr(false),
+ })
+ // Create the APEX module with name g.moduleName(). Use factory APEX version.
+ g.createModulesRealApex(mctx, g.moduleName(), false, "")
+
+ // Create test APEX modules if gen_test. Test packages are not installable.
+ // Use hard-coded APEX version.
+ if proptools.Bool(g.properties.Gen_test) {
+ g.createModulesRealApex(mctx, g.moduleName()+"_test_high", true, "1000000000")
+ g.createModulesRealApex(mctx, g.moduleName()+"_test_low", true, "1")
+ }
+}
+
+func (g *gkiApex) createModulesRealApex(mctx android.LoadHookContext,
+ moduleName string,
+ isTestApex bool,
+ overrideApexVersion string) {
+ // Check kmi_version property against kernel_release.txt, then
+ // kernel_release.txt -> apex_manifest.json.
+ apexManifest := moduleName + "_apex_manifest"
+ mctx.CreateModule(genrule.GenRuleFactory, &moduleCommonProperties{
+ Name: proptools.StringPtr(apexManifest),
+ Enabled: proptools.BoolPtr(g.properties.ModulesEnabled),
+ }, &genRuleProperties{
+ Tools: []string{"build_gki_apex_manifest"},
+ Out: []string{"apex_manifest.json"},
+ Srcs: []string{":" + g.kernelReleaseFileName()},
+ Cmd: proptools.StringPtr(g.createApexManifestCmd(overrideApexVersion)),
+ })
+
+ // The APEX module.
+
+ // For test APEXes, if module is not enabled because KMI version is not
+ // compatible with the device, create a stub module that produces an empty
+ // file. This is so that the module name can be used in tests.
+ if isTestApex && !g.properties.ModulesEnabled {
+ mctx.CreateModule(genrule.GenRuleFactory, &moduleCommonProperties{
+ Name: proptools.StringPtr(moduleName),
+ }, &genRuleProperties{
+ Out: []string{moduleName + ".apex"},
+ Cmd: proptools.StringPtr(`touch $(out)`),
+ })
+ return
+ }
+
+ // For test APEXes, if module is enabled, build an apex_test with installable: false.
+ // For installed APEXes, build apex, respecting installable and enabled.
+ apexFactory := apex.BundleFactory
+ overrideInstallable := g.properties.Installable
+ if isTestApex {
+ apexFactory = testApexBundleFactory
+ overrideInstallable = proptools.BoolPtr(false)
+ }
+
+ mctx.CreateModule(apexFactory, &moduleCommonProperties{
+ Name: proptools.StringPtr(moduleName),
+ Enabled: proptools.BoolPtr(g.properties.ModulesEnabled),
+ }, &apexProperties{
+ Apex_name: g.properties.ApexName,
+ Manifest: proptools.StringPtr(":" + apexManifest),
+ Defaults: []string{"com.android.gki_defaults"},
+ // A real GKI APEX cannot be preinstalled to the device.
+ // It can only be provided as an update.
+ Installable: overrideInstallable,
+ Prebuilts: []string{
+ g.otaPayloadName(),
+ g.otaPropertiesName(),
+ },
+ })
+}
+
+// Original module name as specified by the "name" property.
+// This is also the APEX module name, i.e. the file name of the APEX file.
+// This is also the prefix of names of all generated modules that the phony module depends on.
+// e.g. com.android.gki.kmi_5_4_android12_0_boot
+func (g *gkiApex) moduleName() string {
+ return g.BaseModuleName()
+}
+
+// The appeared name of this gkiApex object. Exposed to Soong to avoid conflicting with
+// the generated APEX module with name moduleName().
+// e.g. com.android.gki.kmi_5_4_android12_0_boot_all
+func (g *gkiApex) Name() string {
+ return g.moduleName() + "_all"
+}
+
+// Names for intermediate modules.
+func (g *gkiApex) kernelReleaseFileName() string {
+ return g.moduleName() + "_bootimage_kernel_release_file"
+}
+
+func (g *gkiApex) otaPayloadName() string {
+ return g.moduleName() + "_ota_payload"
+}
+
+func (g *gkiApex) otaPropertiesName() string {
+ return g.moduleName() + "_ota_payload_properties"
+}
+
+// If the boot image pointed at product_out_path has no rule to be generated, do not generate any
+// build rules for this gki_apex module. For example, if this gki_apex module is:
+// { name: "foo", product_out_path: "boot-bar.img" }
+// But there is no rule to generate boot-bar.img, then
+// - `m foo` fails with `unknown target 'foo'`
+// - checkbuild is still successful. The module foo doesn't even exist, so there
+// is no dependency on boot-bar.img
+//
+// There is a rule to generate "boot-foo.img" if "kernel-foo" is in BOARD_KERNEL_BINARIES.
+// As a special case, there is a rule to generate "boot.img" if BOARD_KERNEL_BINARIES is empty,
+// or "kernel" is in BOARD_KERNEL_BINARIES.
+func (g *gkiApex) bootImgHasRules(mctx android.EarlyModuleContext) bool {
+ kernelNames := mctx.DeviceConfig().BoardKernelBinaries()
+ if len(kernelNames) == 0 {
+ return proptools.String(g.properties.Product_out_path) == "boot.img"
+ }
+ for _, kernelName := range kernelNames {
+ validBootImagePath := strings.Replace(kernelName, "kernel", "boot", -1) + ".img"
+ if proptools.String(g.properties.Product_out_path) == validBootImagePath {
+ return true
+ }
+ }
+ return false
+}
+
+// Only generate if this module's kmi_version property is in BOARD_KERNEL_MODULE_INTERFACE_VERSIONS.
+// Otherwise, this board does not support GKI APEXes, so no modules are generated at all.
+// This function also avoids building invalid modules in checkbuild. For example, if these
+// gki_apex modules are defined:
+// gki_apex { name: "boot-kmi-1", kmi_version: "1", product_out_path: "boot.img" }
+// gki_apex { name: "boot-kmi-2", kmi_version: "2", product_out_path: "boot.img" }
+// But a given device's $PRODUCT_OUT/boot.img can only support at most one KMI version.
+// Disable some modules accordingly to make sure checkbuild still works.
+func boardDefinesKmiVersion(mctx android.EarlyModuleContext, kmiVersion string) bool {
+ kmiVersions := mctx.DeviceConfig().BoardKernelModuleInterfaceVersions()
+ return android.InList(kmiVersion, kmiVersions)
+}
+
+func (g *gkiApex) boardDefinesKmiVersion(mctx android.EarlyModuleContext) bool {
+ return boardDefinesKmiVersion(mctx, proptools.String(g.properties.Kmi_version))
+}
+
+// Transform kernel release file in $(in) to KMI version + sublevel.
+// e.g. 5.4.42-android12-0 => name: "com.android.gki.kmi_5_4_android12_0", version: "300000000"
+// Finally, write APEX manifest JSON to $(out).
+func (g *gkiApex) createApexManifestCmd(apexVersion string) string {
+ ret := `$(location build_gki_apex_manifest) ` +
+ `--kmi_version "` + proptools.String(g.properties.Kmi_version) + `" ` +
+ `--apex_manifest $(out) --kernel_release_file $(in)`
+ // Override version field if set.
+ if apexVersion != "" {
+ ret += ` --apex_version ` + apexVersion
+ }
+ return ret
+}
+
+func (g *gkiApex) DepsMutator(ctx android.BottomUpMutatorContext) {
+}
+
+func (g *gkiApex) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+}
+
+// OTA payload binary is signed with default_system_dev_certificate, which is equivalent to
+// DefaultAppCertificate().
+func getDefaultCertificate(ctx android.EarlyModuleContext) string {
+ pem, _ := ctx.Config().DefaultAppCertificate(ctx)
+ return strings.TrimSuffix(pem.String(), filepath.Ext(pem.String()))
+}
diff --git a/build/kmi.go b/build/kmi.go
new file mode 100644
index 0000000..27b00bf
--- /dev/null
+++ b/build/kmi.go
@@ -0,0 +1,45 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Minimum support of KMI version in Go. Keep in sync with libkver.
+
+package gki
+
+import (
+ "fmt"
+ "regexp"
+)
+
+var digits = "([0-9]+)"
+var reKmi = regexp.MustCompile("^([0-9]+)[.]([0-9]+)-(android[0-9]+)-([0-9]+)$")
+
+// Input is a valid KMI version, e.g. 5.4-android12-0.
+// Return a sanitized string to be used as a suffix of APEX package name
+// com.android.gki.kmi_5_4_android12_0
+// Keep in sync with libkver.
+func kmiVersionToApexName(s string) (string, error) {
+ matches := reKmi.FindAllStringSubmatch(s, 4)
+
+ if matches == nil {
+ return "", fmt.Errorf("Poorly formed KMI version: %q must match regex %q", s, reKmi)
+ }
+
+ version := matches[0][1]
+ patchLevel := matches[0][2]
+ androidRelease := matches[0][3]
+ kmiGeneration := matches[0][4]
+
+ return fmt.Sprintf("com.android.gki.kmi_%s_%s_%s_%s",
+ version, patchLevel, androidRelease, kmiGeneration), nil
+}
diff --git a/build/kmi_test.go b/build/kmi_test.go
new file mode 100644
index 0000000..de7642b
--- /dev/null
+++ b/build/kmi_test.go
@@ -0,0 +1,48 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Minimum support of KMI version in Go. Keep in sync with libkver.
+
+package gki
+
+import (
+ "testing"
+)
+
+func expectValid(t *testing.T, kmi string, expectedApexName string) {
+ t.Helper()
+ got, e := kmiVersionToApexName(kmi)
+ if e != nil {
+ t.Errorf("Expected no error when parsing %q, got %q", kmi, e)
+ }
+ if got != expectedApexName {
+ t.Errorf("Expected kmiVersionToApexName(%q) == %q, got %q", kmi, expectedApexName, got)
+ }
+}
+
+func expectInvalid(t *testing.T, kmi string) {
+ t.Helper()
+ got, e := kmiVersionToApexName(kmi)
+ if e == nil {
+ t.Errorf("Expected error when parsing %q, got no error with result %q", kmi, got)
+ }
+}
+
+func TestParse(t *testing.T) {
+ expectInvalid(t, "")
+ expectInvalid(t, "foobar")
+ expectInvalid(t, "1")
+ expectValid(t, "5.4-android12-0", "com.android.gki.kmi_5_4_android12_0")
+ expectValid(t, "5.4-android12-42", "com.android.gki.kmi_5_4_android12_42")
+}
diff --git a/build/prebuilt.go b/build/prebuilt.go
new file mode 100644
index 0000000..da8a123
--- /dev/null
+++ b/build/prebuilt.go
@@ -0,0 +1,125 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package gki
+
+import (
+ "fmt"
+ "io"
+
+ "android/soong/android"
+ "android/soong/apex"
+ "github.com/google/blueprint/proptools"
+)
+
+var (
+ prebuiltApexTag = dependencyTag{name: "prebuilt_apex"}
+)
+
+type prebuiltGkiApexProperties struct {
+ apex.PrebuiltProperties
+
+ // Declared KMI version of the boot image. Example: "5.4-android12-0"
+ Kmi_version *string
+}
+
+type prebuiltGkiApex struct {
+ android.ModuleBase
+ properties prebuiltGkiApexProperties
+
+ extractedBootImage android.WritablePath
+}
+
+func init() {
+ android.RegisterModuleType("prebuilt_gki_apex", prebuiltGkiApexFactory)
+}
+
+// Declare a prebuilt GKI APEX. When installed, the boot image is extracted from
+// the module.
+func prebuiltGkiApexFactory() android.Module {
+ g := &prebuiltGkiApex{}
+ g.AddProperties(&g.properties)
+ android.InitAndroidModule(g)
+ android.AddLoadHook(g, func(ctx android.LoadHookContext) { prebuiltGkiApexMutator(ctx, g) })
+ return g
+}
+func prebuiltGkiApexMutator(mctx android.LoadHookContext, g *prebuiltGkiApex) {
+ // Whether modules should be enabled according to board variables.
+ enabled := boardDefinesKmiVersion(mctx, proptools.String(g.properties.Kmi_version))
+ if !enabled {
+ g.Disable()
+ }
+
+ // The prebuilt_apex module.
+ mctx.CreateModule(apex.PrebuiltFactory, &moduleCommonProperties{
+ Name: proptools.StringPtr(g.BaseModuleName()),
+ Enabled: proptools.BoolPtr(enabled),
+ Product_specific: proptools.BoolPtr(true),
+ }, &g.properties.PrebuiltProperties)
+}
+
+// The appeared name of this prebuiltGkiApex object. Exposed to Soong to avoid conflicting with
+// the generated prebuilt_apex module with name BaseModuleName().
+func (g *prebuiltGkiApex) Name() string {
+ return g.BaseModuleName() + "_boot_img"
+}
+
+func (g *prebuiltGkiApex) DepsMutator(ctx android.BottomUpMutatorContext) {
+ ctx.AddDependency(ctx.Module(), prebuiltApexTag, g.BaseModuleName())
+}
+
+func (g *prebuiltGkiApex) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ var apexFile android.OptionalPath
+ ctx.VisitDirectDepsWithTag(prebuiltApexTag, func(m android.Module) {
+ if prebuiltApex, ok := m.(*apex.Prebuilt); ok {
+ srcFiles, err := prebuiltApex.OutputFiles("")
+ if err != nil {
+ ctx.ModuleErrorf("Cannot get output files from %q: %s", ctx.OtherModuleName(m), err)
+ } else if len(srcFiles) != 1 {
+ ctx.ModuleErrorf("%q generated %d files", ctx.OtherModuleName(m), len(srcFiles))
+ } else {
+ apexFile = android.OptionalPathForPath(srcFiles[0])
+ }
+ } else {
+ ctx.ModuleErrorf("%q is not a prebuilt_apex", ctx.OtherModuleName(m))
+ }
+ })
+ if !apexFile.Valid() {
+ ctx.ModuleErrorf("Can't determine the prebuilt APEX file")
+ return
+ }
+
+ genDir := android.PathForModuleOut(ctx, "extracted")
+ g.extractedBootImage = genDir.Join(ctx, "boot.img")
+
+ rule := android.NewRuleBuilder(pctx, ctx)
+ rule.Command().
+ ImplicitOutput(g.extractedBootImage).
+ BuiltTool("extract_img_from_apex").
+ Flag("--tool").BuiltTool("debugfs").
+ Flag("--tool").BuiltTool("delta_generator").
+ Input(apexFile.Path()).
+ Text(genDir.String())
+ rule.Build("extractImgFromApex", "Extract boot image from prebuilt GKI APEX")
+
+ ctx.Phony(g.BaseModuleName(), g.extractedBootImage)
+}
+
+func (g *prebuiltGkiApex) AndroidMk() android.AndroidMkData {
+ return android.AndroidMkData{
+ Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
+ fmt.Fprintf(w, "ALL_MODULES.%s.EXTRACTED_BOOT_IMAGE := %s\n", name, g.extractedBootImage)
+ },
+ }
+}
diff --git a/build/properties.go b/build/properties.go
new file mode 100644
index 0000000..af68446
--- /dev/null
+++ b/build/properties.go
@@ -0,0 +1,50 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package gki
+
+type moduleCommonProperties struct {
+ Name *string
+ Enabled *bool
+ Required []string
+ Product_specific *bool
+}
+
+type makefileGoalProperties struct {
+ Product_out_path *string
+}
+
+type genRuleProperties struct {
+ Cmd *string
+ Defaults []string
+ Out []string
+ Srcs []string
+ Tools []string
+}
+
+type prebuiltEtcProperties struct {
+ Src *string
+ Filename_from_src *bool
+ Relative_install_path *string
+ Installable *bool
+}
+
+type apexProperties struct {
+ Apex_name *string
+ Manifest *string
+ Defaults []string
+ Installable *bool
+ Prebuilts []string
+ Overrides []string
+}
diff --git a/build/raw_img_ota.go b/build/raw_img_ota.go
new file mode 100644
index 0000000..2f29e2e
--- /dev/null
+++ b/build/raw_img_ota.go
@@ -0,0 +1,250 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// A special genrule that creates OTA payload and payload_properties from a raw
+// image. This rule is created so that the two outputs, payload and
+// payload_properties, can be distinguished with tags.
+
+package gki
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+
+ "android/soong/android"
+ "android/soong/java"
+
+ "github.com/google/blueprint"
+ "github.com/google/blueprint/proptools"
+)
+
+type dependencyTag struct {
+ blueprint.BaseDependencyTag
+ name string
+}
+
+// {"foo": "fooVal", "bar": "barVal"} -> ["${foo}", "${bar}"]
+func keysToVars(deps map[string]string) []string {
+ var ret []string
+ for dep := range deps {
+ ret = append(ret, fmt.Sprintf("${%s}", dep))
+ }
+ sort.Strings(ret)
+ return ret
+}
+
+var (
+ certificateTag = dependencyTag{name: "certificate"}
+ rawImageTag = dependencyTag{name: "raw_image"}
+
+ pctx = android.NewPackageContext("android/gki")
+
+ otaFromRawImageDeps = map[string]string{
+ "ota_from_raw_image": "ota_from_raw_image",
+
+ // Needed by ota_from_target_files
+ "brillo_update_payload": "brillo_update_payload",
+
+ // Needed by brillo_update_payload
+ "delta_generator": "delta_generator",
+ // b/171581299: shflags isn't built to the path where HostBinToolVariable
+ // points to without explicitly declaring it, even if it is stated as
+ // required by brillo_update_payload.
+ "shflags": "lib/shflags/shflags",
+
+ // Needed by GetBootImageTimestamp
+ "lz4": "lz4",
+ "toybox": "toybox",
+ "unpack_bootimg": "unpack_bootimg",
+ }
+
+ otaFromRawImageVarDeps = keysToVars(otaFromRawImageDeps)
+
+ otaFromRawImageRule = pctx.AndroidStaticRule("ota_from_raw_image", blueprint.RuleParams{
+ Command: `${ota_from_raw_image} --tools ` + strings.Join(otaFromRawImageVarDeps, " ") +
+ ` ${kwargs} --out ${outDir} -- ${inputArg}`,
+ CommandDeps: otaFromRawImageVarDeps,
+ Description: "ota_from_raw_image ${outDir}",
+ }, "kwargs", "outDir", "inputArg")
+
+ // Tags to OutputFiles
+ payloadTag = "payload"
+ payloadPropertiesTag = "properties"
+)
+
+func init() {
+ for dep := range otaFromRawImageDeps {
+ pctx.HostBinToolVariable(dep, otaFromRawImageDeps[dep])
+ }
+ // Intentionally not register this module so that it can only be constructed by gki_apex.
+}
+
+type rawImageOtaProperties struct {
+ // The name of a certificate in the default certificate directory, blank to use the default product certificate,
+ // or an android_app_certificate module name in the form ":module".
+ Certificate *string
+
+ // A set of images and their related modules. Must be in this form
+ // IMAGE_NAME:MODULE, where IMAGE_NAME is an image name like "boot", and
+ // MODULE is the name of a makefile_goal.
+ Image_goals []string
+}
+
+type rawImageOta struct {
+ android.ModuleBase
+ properties rawImageOtaProperties
+
+ pem android.Path
+ key android.Path
+
+ outPayload android.WritablePath
+ outProperties android.WritablePath
+}
+
+// Declare a rule that generates a signed OTA payload from a raw image. This
+// includes payload.bin and payload_properties.txt.
+func rawImageOtaFactory() android.Module {
+ r := &rawImageOta{}
+ r.AddProperties(&r.properties)
+ android.InitAndroidModule(r)
+ return r
+}
+
+func (r *rawImageOta) OutputFiles(tag string) (android.Paths, error) {
+ switch tag {
+ case "":
+ return android.Paths{r.outPayload, r.outProperties}, nil
+ case payloadTag:
+ return android.Paths{r.outPayload}, nil
+ case payloadPropertiesTag:
+ return android.Paths{r.outProperties}, nil
+ default:
+ return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+ }
+}
+
+var _ android.OutputFileProducer = (*rawImageOta)(nil)
+
+func (r *rawImageOta) getCertString(ctx android.BaseModuleContext) string {
+ moduleName := ctx.ModuleName()
+ certificate, overridden := ctx.DeviceConfig().OverrideCertificateFor(moduleName)
+ if overridden {
+ return ":" + certificate
+ }
+ return proptools.String(r.properties.Certificate)
+}
+
+// Returns module->image_name mapping, e.g. "bootimage_soong"->"boot"
+func (r *rawImageOta) goalToImage(ctx android.EarlyModuleContext) map[string]string {
+ ret := map[string]string{}
+ for _, imageGoal := range r.properties.Image_goals {
+ lst := strings.Split(imageGoal, ":")
+ if len(lst) != 2 {
+ ctx.PropertyErrorf("image_goals", "Must be in the form IMAGE_NAME:MODULE")
+ return map[string]string{}
+ }
+ ret[lst[1]] = lst[0]
+ }
+ return ret
+}
+
+func (r *rawImageOta) DepsMutator(ctx android.BottomUpMutatorContext) {
+ // Add dependency to modules in image_goals
+ for module, _ := range r.goalToImage(ctx) {
+ ctx.AddVariationDependencies(nil, rawImageTag, module)
+ }
+ // Add dependency to certificate module, if any.
+ cert := android.SrcIsModule(r.getCertString(ctx))
+ if cert != "" {
+ ctx.AddVariationDependencies(nil, certificateTag, cert)
+ }
+}
+
+func (r *rawImageOta) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+ inputArg := []string{}
+ kwargs := []string{}
+ implicits := android.Paths{}
+
+ // Handle image_goals
+ goalToImage := r.goalToImage(ctx)
+ ctx.VisitDirectDepsWithTag(rawImageTag, func(module android.Module) {
+ depName := ctx.OtherModuleName(module)
+ imgPath := android.OutputFileForModule(ctx, module, "")
+ if imgPath != nil {
+ implicits = append(implicits, imgPath)
+ inputArg = append(inputArg, goalToImage[depName]+":"+imgPath.String())
+ } else {
+ ctx.ModuleErrorf("image dependency %q does not generate any output", depName)
+ }
+ })
+
+ // Handle certificate
+ ctx.VisitDirectDepsWithTag(certificateTag, func(module android.Module) {
+ depName := ctx.OtherModuleName(module)
+ if cert, ok := module.(*java.AndroidAppCertificate); ok {
+ r.pem = cert.Certificate.Pem
+ r.key = cert.Certificate.Key
+ } else {
+ ctx.ModuleErrorf("certificate dependency %q must be an android_app_certificate module", depName)
+ }
+ })
+ r.setCertificateAndPrivateKey(ctx)
+ keyName, keyError := removeCertExt(r.pem)
+ if keyError != nil {
+ ctx.ModuleErrorf("Cannot get certificate to sign the OTA payload binary: " + keyError.Error())
+ }
+ implicits = append(implicits, r.pem, r.key)
+ kwargs = append(kwargs, "--key "+proptools.String(keyName))
+
+ // Set outputs
+ outDir := android.PathForModuleGen(ctx, "payload_files")
+ r.outPayload = outDir.Join(ctx, "payload.bin")
+ r.outProperties = outDir.Join(ctx, "payload_properties.txt")
+
+ ctx.Build(pctx, android.BuildParams{
+ Rule: otaFromRawImageRule,
+ Description: "Generate OTA from raw image",
+ Implicits: implicits,
+ Outputs: android.WritablePaths{r.outPayload, r.outProperties},
+ Args: map[string]string{
+ "kwargs": strings.Join(kwargs, " "),
+ "outDir": outDir.String(),
+ "inputArg": strings.Join(inputArg, " "),
+ },
+ })
+}
+
+func (r *rawImageOta) setCertificateAndPrivateKey(ctx android.ModuleContext) {
+ if r.pem == nil {
+ cert := proptools.String(r.properties.Certificate)
+ if cert == "" {
+ pem, key := ctx.Config().DefaultAppCertificate(ctx)
+ r.pem = pem
+ r.key = key
+ } else {
+ defaultDir := ctx.Config().DefaultAppCertificateDir(ctx)
+ r.pem = defaultDir.Join(ctx, cert+".x509.pem")
+ r.key = defaultDir.Join(ctx, cert+".pk8")
+ }
+ }
+}
+
+func removeCertExt(path android.Path) (*string, error) {
+ s := path.String()
+ if strings.HasSuffix(s, ".x509.pem") {
+ return proptools.StringPtr(strings.TrimSuffix(s, ".x509.pem")), nil
+ }
+ return nil, fmt.Errorf("Path %q does not end with .x509.pem", s)
+}
diff --git a/build_gki_apex_manifest.cpp b/build_gki_apex_manifest.cpp
index 659afcc..796f1ad 100644
--- a/build_gki_apex_manifest.cpp
+++ b/build_gki_apex_manifest.cpp
@@ -30,16 +30,11 @@
namespace {
-int CheckKmi(const KernelRelease& kernel_release, const std::string& kmi_version) {
- const auto& actual_kmi_version = kernel_release.kmi_version().string();
+int CheckKmi(const KernelRelease& kernel_release, const KmiVersion& kmi_version) {
+ const auto& actual_kmi_version = kernel_release.kmi_version();
if (actual_kmi_version != kmi_version) {
- LOG(ERROR) << "KMI version does not match. Actual: " << actual_kmi_version
- << ", expected: " << kmi_version;
- return EX_SOFTWARE;
- }
- if (kernel_release.sub_level() == GetFactoryApexVersion()) {
- LOG(ERROR) << "Kernel release is " << kernel_release.string() << ". Sub-level "
- << GetFactoryApexVersion() << " is reserved for factory GKI APEX.";
+ LOG(ERROR) << "KMI version does not match. Actual: " << actual_kmi_version.string()
+ << ", expected: " << kmi_version.string();
return EX_SOFTWARE;
}
return EX_OK;
@@ -51,7 +46,6 @@
root["name"] = apex_name;
root["version"] = apex_version;
root["preInstallHook"] = "bin/com.android.gki.preinstall";
- root["postInstallHook"] = "bin/com.android.gki.postinstall";
std::string json_string = Json::StyledWriter().write(root);
if (!android::base::WriteStringToFile(json_string, out_file)) {
PLOG(ERROR) << "Cannot write to " << out_file;
@@ -70,6 +64,8 @@
"--factory must be set.");
DEFINE_string(kmi_version, "", "Declared KMI version for this APEX.");
DEFINE_string(apex_manifest, "", "Output APEX manifest JSON file.");
+DEFINE_uint64(apex_version, GetFactoryApexVersion(),
+ "Override APEX version in APEX manifest. Use factory APEX version if unspecified.");
int main(int argc, char** argv) {
gflags::ParseCommandLineFlags(&argc, &argv, true);
@@ -89,12 +85,7 @@
return EX_SOFTWARE;
}
- std::string apex_name;
- uint64_t apex_version;
- if (FLAGS_factory) {
- apex_name = GetApexName(*kmi_version);
- apex_version = GetFactoryApexVersion();
- } else {
+ if (!FLAGS_kernel_release_file.empty()) {
std::string kernel_release_string;
if (!android::base::ReadFileToString(FLAGS_kernel_release_file, &kernel_release_string)) {
PLOG(ERROR) << "Cannot read " << FLAGS_kernel_release_file;
@@ -105,13 +96,13 @@
LOG(ERROR) << kernel_release_string << " is not a valid GKI kernel release string";
return EX_SOFTWARE;
}
- int res = CheckKmi(*kernel_release, FLAGS_kmi_version);
+ int res = CheckKmi(*kernel_release, *kmi_version);
if (res != EX_OK) return res;
-
- apex_name = GetApexName(kernel_release->kmi_version());
- apex_version = GetApexVersion(*kernel_release);
}
+ std::string apex_name = GetApexName(*kmi_version);
+ uint64_t apex_version = FLAGS_apex_version;
+
if (FLAGS_apex_manifest.empty()) {
LOG(WARNING) << "Skip writing APEX manifest because --apex_manifest is not set.";
} else {
diff --git a/download_boot_prebuilt.py b/download_boot_prebuilt.py
new file mode 100755
index 0000000..bd665d7
--- /dev/null
+++ b/download_boot_prebuilt.py
@@ -0,0 +1,140 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2019-2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import argparse
+import collections
+import functools
+import glob
+import json
+import logging
+import os
+import pathlib
+import re
+import shlex
+import shutil
+import subprocess
+import sys
+import tempfile
+import urllib.request
+
+from concurrent import futures
+from pathlib import Path
+
+BASE_URL = "https://ci.android.com/builds/submitted/{build_id}/{target}/latest/raw"
+SUPPORTED_ARCHS = ["arm64"]
+VARIANTS = ["userdebug"]
+BOOT_PREBUILT_REL_DIR = "packages/modules/BootPrebuilt"
+ANDROID_BUILD_TOP = os.environ["ANDROID_BUILD_TOP"]
+
+logger = logging.getLogger(__name__)
+logging.basicConfig(level=logging.INFO)
+
+def parse_args():
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "build_id",
+ type=int,
+ help="the build id to download the build for, e.g. 6148204")
+ parser.add_argument(
+ "--bug",
+ type=str,
+ default=None,
+ help="optional bug number for git commit.")
+
+ return parser.parse_args()
+
+
+def download_file(url, dest_filename):
+ logger.info("Downloading %s -> %s", url, dest_filename)
+ urllib.request.urlretrieve(url, dest_filename)
+
+
+def get_artifact_download_spec(build_id, device, variant, dest_dir):
+ target = "{}-{}".format(device, variant)
+ url_base = BASE_URL.format(build_id=build_id, target=target)
+ filename = "{}-img-{}.zip".format(device, build_id)
+ url = os.path.join(url_base, filename)
+ dest_filename = os.path.join(dest_dir, filename)
+ return url, dest_filename
+
+
+def update_prebuilt(build_id, boot_prebuilt, ver, arch, variant, bug):
+ device = "aosp_" + arch
+ arch_dir = os.path.join(boot_prebuilt, ver, arch)
+ variant_dir = os.path.join(arch_dir, variant)
+ boot_img_name = "boot-{}.img".format(ver)
+ stored_img_name = "boot-{}.img".format(variant)
+ try:
+ subprocess.check_call(["repo", "start", "boot-prebuilt-{}".format(build_id)], cwd=arch_dir)
+
+ os.makedirs(variant_dir)
+ url, dest_filename = get_artifact_download_spec(build_id, device, variant, variant_dir)
+ download_file(url, dest_filename)
+ args = ["unzip", "-d", variant_dir, dest_filename, boot_img_name]
+ logger.info("Calling: %s", " ".join(args))
+ subprocess.check_call(args)
+ shutil.move(os.path.join(variant_dir, boot_img_name), os.path.join(arch_dir, stored_img_name))
+
+ finally:
+ shutil.rmtree(variant_dir)
+
+ message = """Update prebuilts to {build_id}.
+
+Test: Treehugger
+Bug: {bug}
+""".format(build_id=build_id, bug=bug or "N/A")
+
+ logger.info("Creating commit for %s", arch_dir)
+ subprocess.check_call(["git", "add", "."], cwd=arch_dir)
+ subprocess.check_call(["git", "commit", "-m", message], cwd=arch_dir)
+
+
+def main():
+ args = parse_args()
+ with futures.ThreadPoolExecutor(max_workers=10) as pool:
+ fs = []
+ boot_prebuilt = os.path.join(ANDROID_BUILD_TOP, BOOT_PREBUILT_REL_DIR)
+ for ver in os.listdir(boot_prebuilt):
+ if not re.match(r'\d+[.]\d+', ver):
+ continue
+ for arch in os.listdir(os.path.join(boot_prebuilt, ver)):
+ if arch not in SUPPORTED_ARCHS:
+ continue
+ for variant in VARIANTS:
+ fs.append((ver, arch, pool.submit(update_prebuilt, args.build_id, boot_prebuilt, ver,
+ arch, variant, args.bug)))
+
+ futures.wait([f for ver, arch, f in fs])
+ success_dirs = []
+ logger.info("===============")
+ logger.info("Summary:")
+ for ver, arch, future in fs:
+ if future.exception():
+ logger.error("%s/%s: %s", ver, arch, future.exception())
+ else:
+ logger.info("%s/%s: Updated.", ver, arch)
+ success_dirs.append(os.path.join(BOOT_PREBUILT_REL_DIR, ver, arch))
+
+ if success_dirs:
+ args = ["repo", "upload", "--verify", "--hashtag-branch", "--br",
+ "boot-prebuilt-{}".format(args.build_id)] + success_dirs
+ logger.info(" ".join(args))
+ subprocess.check_call(args, cwd=ANDROID_BUILD_TOP)
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/extract_img_from_apex.py b/extract_img_from_apex.py
new file mode 100644
index 0000000..12eb987
--- /dev/null
+++ b/extract_img_from_apex.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Extract boot.img from a GKI APEX.
+
+Usage: extract_img_from_apex [--tool host_tool [--tool ...]] \
+ input_gki.apex out_dir
+"""
+
+import logging
+import os
+import sys
+
+import common
+
+APEX_PAYLOAD_FILE = "apex_payload.img"
+APEX_PAYLOAD_BIN_PATH = "/etc/ota/payload.bin"
+
+logger = logging.getLogger(__name__)
+OPTIONS = common.OPTIONS
+
+
+def ExtractImagesFromApex(apex, out_dir):
+ if not os.path.isdir(out_dir):
+ os.makedirs(out_dir)
+ apex_dir = common.UnzipTemp(apex, [APEX_PAYLOAD_FILE])
+ apex_payload = os.path.join(apex_dir, APEX_PAYLOAD_FILE)
+
+ payload_bin = common.MakeTempFile("payload_", ".bin")
+ debugfs_command = ['debugfs', '-R', "dump {} {}".format(
+ APEX_PAYLOAD_BIN_PATH, payload_bin), apex_payload]
+
+ common.RunAndCheckOutput(debugfs_command)
+ assert os.path.getsize(payload_bin) > 0, payload_bin + " is an empty file!"
+
+ boot_img = os.path.join(out_dir, 'boot.img')
+ with open(boot_img, 'w') as _:
+ pass
+
+ delta_generator_command = [
+ 'delta_generator',
+ '--is_partial_update=true',
+ '--in_file=' + payload_bin,
+ '--partition_names=boot',
+ '--new_partitions=' + boot_img
+ ]
+ common.RunAndCheckOutput(delta_generator_command)
+
+
+def main(argv):
+ def option_handler(o, a):
+ if o == "--tool":
+ name = os.path.basename(a)
+ common.SetHostToolLocation(name, a)
+ else:
+ return False
+ return True
+
+ args = common.ParseOptions(
+ argv, __doc__,
+ extra_long_opts=["tool="],
+ extra_option_handler=option_handler)
+
+ if len(args) != 2:
+ common.Usage(__doc__)
+ sys.exit(1)
+
+ common.InitLogging()
+
+ ExtractImagesFromApex(args[0], args[1])
+ logger.info("done.")
+
+
+if __name__ == '__main__':
+ try:
+ common.CloseInheritedPipes()
+ main(sys.argv[1:])
+ except common.ExternalError:
+ logger.exception("\n ERROR:\n")
+ sys.exit(1)
+ finally:
+ common.Cleanup()
diff --git a/libkver/TEST_MAPPING b/libkver/TEST_MAPPING
deleted file mode 100644
index b3fe1e6..0000000
--- a/libkver/TEST_MAPPING
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "presubmit": [
- {
- "name": "libkver_test"
- },
- {
- "name": "libkver_test",
- "host": true
- }
- ]
-}
diff --git a/libkver/include/kver/kmi_version.h b/libkver/include/kver/kmi_version.h
index 71e0e8c..7066f10 100644
--- a/libkver/include/kver/kmi_version.h
+++ b/libkver/include/kver/kmi_version.h
@@ -55,4 +55,12 @@
uint64_t gen_ = 0;
};
+inline bool operator==(const KmiVersion& left, const KmiVersion& right) {
+ return left.tuple() == right.tuple();
+}
+
+inline bool operator!=(const KmiVersion& left, const KmiVersion& right) {
+ return left.tuple() != right.tuple();
+}
+
} // namespace android::kver
diff --git a/libkver/include/kver/utils.h b/libkver/include/kver/utils.h
index 2ae3c19..26e0707 100644
--- a/libkver/include/kver/utils.h
+++ b/libkver/include/kver/utils.h
@@ -35,10 +35,7 @@
// e.g. "com.android.gki.kmi_5_4_android12_0"
std::string GetApexName(const KmiVersion& kmi_version);
-// Defines how kernel release is mapped to APEX versions.
-uint64_t GetApexVersion(const KernelRelease& kernel_release);
-
-// Return the APEX version for a factory GKI APEX.
+// Return the APEX version for a GKI APEX built from source.
uint64_t GetFactoryApexVersion();
} // namespace android::kver
diff --git a/libkver/utils.cpp b/libkver/utils.cpp
index 0c6beb8..a1d2c13 100644
--- a/libkver/utils.cpp
+++ b/libkver/utils.cpp
@@ -78,14 +78,8 @@
kmi_version.android_release(), kmi_version.generation());
}
-uint64_t GetApexVersion(const KernelRelease& kernel_release) {
- // TODO(b/168255100): define APEX version
- return kernel_release.sub_level();
-}
-
uint64_t GetFactoryApexVersion() {
- // TODO(b/168255100): define APEX version
- return 1;
+ return 300000000;
}
} // namespace android::kver
diff --git a/ota_from_raw_image.py b/ota_from_raw_image.py
index c6b4ea0..acd0697 100644
--- a/ota_from_raw_image.py
+++ b/ota_from_raw_image.py
@@ -40,9 +40,6 @@
parser.add_argument("--key", type=str,
help="Key to use to sign the package. If unspecified, script does not sign "
"the package and payload_properties.txt is not generated.")
- parser.add_argument("--kernel-release-file", type=str,
- help="If boot is in input, a file containing the kernel release of the boot "
- "image. Create the file with `extract_kernel --output-release`.")
parser.add_argument("--out", type=str, required=True,
help="Required output directory to payload.bin and payload_properties.txt")
parser.add_argument("input", metavar="NAME:IMAGE", nargs="+",
@@ -58,32 +55,13 @@
return
for path in args.tools:
name = os.path.basename(path)
- common.SetHostToolLocation(name, path)
+ # Use absolute path because GetBootImageTimestamp changes cwd when running some tools.
+ common.SetHostToolLocation(name, os.path.abspath(path))
# brillo_update_payload is a shell script that depends on this environment variable.
if name == "delta_generator":
os.environ["GENERATOR"] = path
-def _GetKernelRelease(line):
- """
- Get GKI kernel release string from the given line.
- """
- PATTERN = r"^(\d+[.]\d+[.]\d+-android\d+-\d+).*$"
- mo = re.match(PATTERN, line)
- assert mo, "Kernel release '{}' does not match regex r'{}'".format(line, PATTERN)
- return mo.group(1)
-
-
-def _GetKernelReleaseFromFile(filename):
- """
- Get GKI kernel release string from the given text file.
- """
- assert filename, "--kernel-release-file must be specified if boot is in input"
- with open(filename) as f:
- line = f.read().strip()
- return _GetKernelRelease(line)
-
-
def CreateOtaFromRawImages(args):
_PrepareEnvironment(args)
@@ -98,8 +76,10 @@
zip.write(img_path, arcname=os.path.join("IMAGES", name + ".img"))
names.append(name)
if name == "boot":
+ timestamp = common.GetBootImageTimestamp(img_path)
+ assert timestamp is not None, "Cannot extract timestamp from boot image"
payload_additional_args += ["--partition_timestamps",
- "boot:" + _GetKernelReleaseFromFile(args.kernel_release_file)]
+ "boot:" + str(timestamp)]
zip.writestr("META/ab_partitions.txt", "\n".join(names) + "\n")
zip.writestr("META/dynamic_partitions_info.txt", """
diff --git a/ota_from_raw_image_test.py b/ota_from_raw_image_test.py
deleted file mode 100644
index 0151547..0000000
--- a/ota_from_raw_image_test.py
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/usr/bin/env python3
-
-# Copyright (C) 2020 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import unittest
-from ota_from_raw_image import _GetKernelRelease
-
-
-class OtaFromRawImageTest(unittest.TestCase):
- def test_get_kernel_release_trivial(self):
- self.assertEqual("5.4.42-android12-15", _GetKernelRelease("5.4.42-android12-15"))
-
- def test_get_kernel_release_suffix(self):
- self.assertEqual("5.4.42-android12-15", _GetKernelRelease("5.4.42-android12-15-something"))
-
- def test_get_kernel_release_invalid(self):
- with self.assertRaises(AssertionError):
- _GetKernelRelease("5.4-android12-15")
-
-
-if __name__ == '__main__':
- # atest needs a verbosity level of >= 2 to correctly parse the result.
- unittest.main(verbosity=2)
diff --git a/postinstall.sh b/postinstall.sh
deleted file mode 100644
index d29e65b..0000000
--- a/postinstall.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/system/bin/sh
-
-log -p i -t gki "GKI APEX postinstall hook starting."
diff --git a/sysprop/api/gkiprops-current.txt b/sysprop/api/gkiprops-current.txt
index 38edfea..e69de29 100644
--- a/sysprop/api/gkiprops-current.txt
+++ b/sysprop/api/gkiprops-current.txt
@@ -1,13 +0,0 @@
-props {
- module: "android.sysprop.GkiProperties"
- prop {
- api_name: "prevent_downgrade_spl"
- scope: Internal
- prop_name: "ro.build.ab_update.gki.prevent_downgrade_spl"
- }
- prop {
- api_name: "prevent_downgrade_version"
- scope: Internal
- prop_name: "ro.build.ab_update.gki.prevent_downgrade_version"
- }
-}
diff --git a/test/Android.bp b/test/Android.bp
new file mode 100644
index 0000000..528a3eb
--- /dev/null
+++ b/test/Android.bp
@@ -0,0 +1,51 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// A list of names of test APEXes for GkiInstallTest to instantiate test cases.
+genrule {
+ name: "gki_install_test_file_list",
+ srcs: [":gki_install_test_files"],
+ out: ["gki_install_test_file_list.txt"],
+ cmd: "for file in $(in); do basename $${file} >> $(out); done",
+}
+
+java_test_host {
+ name: "GkiInstallTest",
+ srcs: [
+ "src/**/*.java",
+ ":gki_install_test_file_list",
+ ],
+ test_suites: [
+ "device-tests",
+ ],
+ libs: [
+ "compatibility-tradefed",
+ "tradefed",
+ ],
+ static_libs: [
+ "cts-host-utils",
+ "hamcrest-library",
+ ],
+ data: [":gki_install_test_files"],
+ java_resources: [":gki_install_test_file_list"],
+}
+
+// Prebuilts
+
+prebuilt_gki_apex {
+ name: "com.android.gki.kmi_5_4_android12_0_test_prebuilt",
+ installable: false,
+ kmi_version: "5.4-android12-0",
+ src: "com.android.gki.kmi_5_4_android12_0_test_prebuilt.apex",
+}
diff --git a/test/AndroidTest.xml b/test/AndroidTest.xml
new file mode 100644
index 0000000..5eceeb2
--- /dev/null
+++ b/test/AndroidTest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<configuration description="GKI Install test">
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="jar" value="GkiInstallTest.jar" />
+ </test>
+</configuration>
diff --git a/test/com.android.gki.kmi_5_4_android12_0_test_prebuilt.apex b/test/com.android.gki.kmi_5_4_android12_0_test_prebuilt.apex
new file mode 100644
index 0000000..1c01dd9
--- /dev/null
+++ b/test/com.android.gki.kmi_5_4_android12_0_test_prebuilt.apex
Binary files differ
diff --git a/test/src/com/android/gki/tests/GkiInstallTest.java b/test/src/com/android/gki/tests/GkiInstallTest.java
new file mode 100644
index 0000000..9abc5f9
--- /dev/null
+++ b/test/src/com/android/gki/tests/GkiInstallTest.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gki.tests;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.everyItem;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.isIn;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.io.FileMatchers.aFileWithSize;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeThat;
+import static org.junit.Assert.fail;
+
+import static java.util.stream.Collectors.toList;
+
+import android.cts.host.utils.DeviceJUnit4ClassRunnerWithParameters;
+import android.cts.host.utils.DeviceJUnit4Parameterized;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.ITestDevice.ApexInfo;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.runners.Parameterized.UseParametersRunnerFactory;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Scanner;
+import java.util.Set;
+
+@RunWith(DeviceJUnit4Parameterized.class)
+@UseParametersRunnerFactory(DeviceJUnit4ClassRunnerWithParameters.RunnerFactory.class)
+public class GkiInstallTest extends BaseHostJUnit4Test {
+
+ // Keep in sync with gki.go.
+ private static final String HIGH_SUFFIX = "_test_high.apex";
+ private static final String LOW_SUFFIX = "_test_low.apex";
+ private static final long TEST_HIGH_VERSION = 1000000000L;
+
+ // Timeout between device online for adb commands and boot completed flag is set.
+ private static final long DEVICE_AVAIL_TIMEOUT_MS = 180000; // 3mins
+ // Timeout for `adb install`.
+ private static final long INSTALL_TIMEOUT_MS = 600000; // 10mins
+
+ @Parameter
+ public String mFileName;
+
+ private String mPackageName;
+ private File mApexFile;
+ private boolean mExpectInstallSuccess;
+ private final Set<String> mOverlayfs = new HashSet();
+
+ @Parameters(name = "{0}")
+ public static Iterable<String> getTestFileNames() {
+ try (Scanner scanner = new Scanner(
+ GkiInstallTest.class.getClassLoader().getResourceAsStream(
+ "gki_install_test_file_list.txt"))) {
+ List<String> list = new ArrayList<>();
+ scanner.forEachRemaining(list::add);
+ return list;
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ inferPackageName();
+ skipTestIfPackageNotInstalled();
+ findTestApexFile();
+ prepareOverlayfs();
+ }
+
+ /** Set mPackageName and mExpectInstallSuccess according to mFileName. */
+ private void inferPackageName() throws Exception {
+ if (mFileName.endsWith(HIGH_SUFFIX)) {
+ mPackageName = mFileName.substring(0, mFileName.length() - HIGH_SUFFIX.length());
+ mExpectInstallSuccess = true;
+ } else if (mFileName.endsWith(LOW_SUFFIX)) {
+ mPackageName = mFileName.substring(0, mFileName.length() - LOW_SUFFIX.length());
+ mExpectInstallSuccess = false;
+ } else {
+ fail("Unrecognized test data file: " + mFileName);
+ }
+ }
+
+ /** Skip the test if mPackageName is not installed on the device. */
+ private void skipTestIfPackageNotInstalled() throws Exception {
+ CLog.i("Wait for device to be available for %d ms...", DEVICE_AVAIL_TIMEOUT_MS);
+ getDevice().waitForDeviceAvailable(DEVICE_AVAIL_TIMEOUT_MS);
+ CLog.i("Device is available after %d ms", DEVICE_AVAIL_TIMEOUT_MS);
+
+ // Skip if the device does not support this APEX package.
+ CLog.i("Checking if %s is installed on the device.", mPackageName);
+ ApexInfo oldApexInfo = getApexInfo(getDevice(), mPackageName);
+ assumeThat(oldApexInfo, is(notNullValue()));
+ assumeThat(oldApexInfo.name, is(mPackageName));
+ }
+
+ /** Find the corresponding APEX test file with mFileName. */
+ private void findTestApexFile() throws Exception {
+ // Find the APEX file.
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+ mApexFile = buildHelper.getTestFile(mFileName);
+
+ // There may be empty .apex files in the directory for disabled APEXes. But if the device
+ // is known to install the package, the test must be built with non-empty APEXes for this
+ // particular package.
+ assertThat("Test is not built properly. It does not contain a non-empty " + mFileName,
+ mApexFile, is(aFileWithSize(greaterThan(0L))));
+ }
+
+ /**
+ * Record what partitions have overlayfs set up. Then, tear down overlayfs because it may
+ * make OTA fail.
+ *
+ * Usually, the test does not require root to run, but if the device has overlayfs set up,
+ * the test assumes that the device has root functionality, and attempts to tear down
+ * overlayfs before the test starts.
+ * Note that this function immediately reboots after enabling adb root to ensure the test runs
+ * with the same permission before it is called.
+ */
+ private void prepareOverlayfs() throws Exception {
+ mOverlayfs.addAll(getOverlayfsState(getDevice()));
+
+ if (!mOverlayfs.isEmpty()) {
+ getDevice().enableAdbRoot();
+ getDevice().executeAdbCommand("enable-verity");
+ rebootUntilAvailable(getDevice(), DEVICE_AVAIL_TIMEOUT_MS);
+ }
+ }
+
+ @Test
+ public void testInstallAndReboot() throws Exception {
+ CLog.i("Installing %s with %d ms timeout", mApexFile, INSTALL_TIMEOUT_MS);
+ String result = getDevice().installPackage(mApexFile, false,
+ "--staged-ready-timeout", String.valueOf(INSTALL_TIMEOUT_MS));
+ if (!mExpectInstallSuccess) {
+ assertNotNull("Should not be able to install downgrade package", result);
+ assertThat(result, containsString("Downgrade of APEX package " + mPackageName +
+ " is not allowed."));
+ return;
+ }
+
+ assertNull("Installation failed with " + result, result);
+ rebootUntilAvailable(getDevice(), DEVICE_AVAIL_TIMEOUT_MS);
+
+ ApexInfo newApexInfo = getApexInfo(getDevice(), mPackageName);
+ assertNotNull(newApexInfo);
+ assertThat(newApexInfo.versionCode, is(TEST_HIGH_VERSION));
+ }
+
+ /**
+ * Restore overlayfs on partitions.
+ *
+ * Usually, tearDown() does not require root to run, but if the device had overlayfs set up
+ * before the test has started,
+ * the test assumes that the device has root functionality, and attempts to re-set up
+ * overlayfs after the test ends.
+ * Note that tearDown() immediately reboots after enabling adb root to ensure the test ends up
+ * with the same permission before the test has started.
+ */
+ @After
+ public void tearDown() throws Exception {
+ // Restore overlayfs for partitions that the test knows of.
+ CLog.i("Test ends, now restoring overlayfs partitions %s.", mOverlayfs);
+ if (mOverlayfs.contains("system")) {
+ getDevice().enableAdbRoot();
+ getDevice().remountSystemWritable();
+ }
+ if (mOverlayfs.contains("vendor")) {
+ getDevice().enableAdbRoot();
+ getDevice().remountVendorWritable();
+ }
+ CLog.i("Restoring overlayfs partition ends, now rebooting.");
+
+ // Reboot device no matter what to avoid interference.
+ rebootUntilAvailable(getDevice(), DEVICE_AVAIL_TIMEOUT_MS);
+
+ // remount*Writable should have enabled overlayfs for all necessary partitions. If not,
+ // throw an error.
+ Set<String> newOverlayfsState = getOverlayfsState(getDevice());
+ assertThat("Some partitions did not restore overlayfs properly. Before test: " + mOverlayfs
+ + ", after test: " + newOverlayfsState, mOverlayfs,
+ everyItem(isIn(newOverlayfsState)));
+ CLog.i("All overlayfs states are restored.");
+ }
+
+ /**
+ * @param device the device under test
+ * @param packageName the package name to look for
+ * @return The {@link ApexInfo} of the APEX named {@code packageName} on the
+ * {@code device}, or {@code null} if the device does not have the APEX installed.
+ * @throws Exception an error has occurred.
+ */
+ private static ApexInfo getApexInfo(ITestDevice device, String packageName)
+ throws Exception {
+ assertNotNull(packageName);
+ List<ApexInfo> list = device.getActiveApexes().stream().filter(
+ apexInfo -> packageName.equals(apexInfo.name)).collect(toList());
+ if (list.isEmpty()) return null;
+ assertThat(list.size(), is(1));
+ return list.get(0);
+ }
+
+ /**
+ * Similar to device.reboot(), but with a timeout on waitForDeviceAvailable. Note that
+ * the timeout does not include the rebootUntilOnline() call.
+ *
+ * @param device the device under test
+ * @param timeoutMs timeout for waitForDeviceAvailable() call
+ * @throws Exception an error has occurred.
+ */
+ private static void rebootUntilAvailable(ITestDevice device, long timeoutMs)
+ throws Exception {
+ CLog.i("Reboot and waiting for device to be online");
+ device.rebootUntilOnline();
+ CLog.i("Device online, wait for device to be available for %d ms...", timeoutMs);
+ device.waitForDeviceAvailable(timeoutMs);
+ CLog.i("Device is available after %d ms", timeoutMs);
+ }
+
+ /**
+ * Get all partitions that have overlayfs setup. Parse /proc/mounts and if it finds lines like:
+ * {@code overlayfs /vendor ...}, then put {@code vendor} in the returned set.
+ * @param device the device under test
+ * @return a list of partitions like {@code system}, {@code vendor} that has overlayfs set up
+ * @throws Exception an error has occurred.
+ */
+ private static Set<String> getOverlayfsState(ITestDevice device) throws Exception {
+ Set<String> ret = new HashSet();
+ File mounts = device.pullFile("/proc/mounts");
+ try (Scanner scanner = new Scanner(mounts)) {
+ while (scanner.hasNextLine()) {
+ String line = scanner.nextLine();
+ String[] tokens = line.split("\\s");
+ if (tokens.length < 2) continue;
+ if (!"overlay".equals(tokens[0])) continue;
+ Path path = Paths.get(tokens[1]);
+ if (path.getNameCount() == 0) continue;
+ ret.add(path.getName(0).toString());
+ }
+ }
+ CLog.i("Device has overlayfs set up on partitions %s", ret);
+ return ret;
+ }
+}