1// Copyright (C) 2024 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package filesystem 16 17import ( 18 "android/soong/android" 19 "fmt" 20 "path/filepath" 21 "sort" 22 "strings" 23 "time" 24 25 "github.com/google/blueprint" 26 "github.com/google/blueprint/proptools" 27) 28 29var ( 30 systemOtherPropFileTweaks = pctx.AndroidStaticRule("system_other_prop_file_tweaks", blueprint.RuleParams{ 31 Command: `rm -rf $out && sed -e 's@^mount_point=/$$@mount_point=system_other@g' -e 's@^partition_name=system$$@partition_name=system_other@g' $in > $out`, 32 }) 33) 34 35type SystemOtherImageProperties struct { 36 // The system_other image always requires a reference to the system image. The system_other 37 // partition gets built into the system partition's "b" slot in a/b partition builds. Thus, it 38 // copies most of its configuration from the system image, such as filesystem type, avb signing 39 // info, etc. Including it here does not automatically mean that it will pick up the system 40 // image's dexpropt files, it must also be listed in Preinstall_dexpreopt_files_from for that. 41 System_image *string 42 43 // This system_other partition will include all the dexpreopt files from the apps on these 44 // partitions. 45 Preinstall_dexpreopt_files_from []string 46} 47 48type systemOtherImage struct { 49 android.ModuleBase 50 android.DefaultableModuleBase 51 properties SystemOtherImageProperties 52} 53 54// The system_other image is the default contents of the "b" slot of the system image. 55// It contains the dexpreopt files of all the apps on the device, for a faster first boot. 56// Afterwards, at runtime, it will be used as a regular b slot for OTA updates, and the initial 57// dexpreopt files will be deleted. 58func SystemOtherImageFactory() android.Module { 59 module := &systemOtherImage{} 60 module.AddProperties(&module.properties) 61 android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon) 62 android.InitDefaultableModule(module) 63 return module 64} 65 66type systemImageDeptag struct { 67 blueprint.BaseDependencyTag 68} 69 70var systemImageDependencyTag = systemImageDeptag{} 71 72type dexpreoptDeptag struct { 73 blueprint.BaseDependencyTag 74} 75 76var dexpreoptDependencyTag = dexpreoptDeptag{} 77 78func (m *systemOtherImage) DepsMutator(ctx android.BottomUpMutatorContext) { 79 if proptools.String(m.properties.System_image) == "" { 80 ctx.ModuleErrorf("system_image property must be set") 81 return 82 } 83 ctx.AddDependency(ctx.Module(), systemImageDependencyTag, *m.properties.System_image) 84 ctx.AddDependency(ctx.Module(), dexpreoptDependencyTag, m.properties.Preinstall_dexpreopt_files_from...) 85} 86 87func (m *systemOtherImage) GenerateAndroidBuildActions(ctx android.ModuleContext) { 88 systemImage := ctx.GetDirectDepProxyWithTag(*m.properties.System_image, systemImageDependencyTag) 89 systemInfo, ok := android.OtherModuleProvider(ctx, systemImage, FilesystemProvider) 90 if !ok { 91 ctx.PropertyErrorf("system_image", "Expected system_image module to provide FilesystemProvider") 92 return 93 } 94 95 output := android.PathForModuleOut(ctx, "system_other.img") 96 stagingDir := android.PathForModuleOut(ctx, "staging_dir") 97 stagingDirTimestamp := android.PathForModuleOut(ctx, "staging_dir.timestamp") 98 99 builder := android.NewRuleBuilder(pctx, ctx) 100 builder.Command().Textf("rm -rf %s && mkdir -p %s", stagingDir, stagingDir) 101 102 specs := make(map[string]android.PackagingSpec) 103 for _, otherPartition := range m.properties.Preinstall_dexpreopt_files_from { 104 dexModule := ctx.GetDirectDepProxyWithTag(otherPartition, dexpreoptDependencyTag) 105 fsInfo, ok := android.OtherModuleProvider(ctx, dexModule, FilesystemProvider) 106 if !ok { 107 ctx.PropertyErrorf("preinstall_dexpreopt_files_from", "Expected module %q to provide FilesystemProvider", otherPartition) 108 return 109 } 110 // Merge all the packaging specs into 1 map 111 for k := range fsInfo.SpecsForSystemOther { 112 if _, ok := specs[k]; ok { 113 ctx.ModuleErrorf("Packaging spec %s given by two different partitions", k) 114 continue 115 } 116 specs[k] = fsInfo.SpecsForSystemOther[k] 117 } 118 } 119 120 // TOOD: CopySpecsToDir only exists on PackagingBase, but doesn't use any fields from it. Clean this up. 121 (&android.PackagingBase{}).CopySpecsToDir(ctx, builder, specs, stagingDir) 122 123 fullInstallPaths := []string{} 124 if len(m.properties.Preinstall_dexpreopt_files_from) > 0 { 125 builder.Command().Textf("touch %s", filepath.Join(stagingDir.String(), "system-other-odex-marker")) 126 installPath := android.PathForModuleInPartitionInstall(ctx, "system_other", "system-other-odex-marker") 127 fullInstallPaths = append(fullInstallPaths, installPath.String()) 128 } 129 builder.Command().Textf("touch").Output(stagingDirTimestamp) 130 builder.Build("assemble_filesystem_staging_dir", "Assemble filesystem staging dir") 131 132 // Most of the time, if build_image were to call a host tool, it accepts the path to the 133 // host tool in a field in the prop file. However, it doesn't have that option for fec, which 134 // it expects to just be on the PATH. Add fec to the PATH. 135 fec := ctx.Config().HostToolPath(ctx, "fec") 136 pathToolDirs := []string{filepath.Dir(fec.String())} 137 138 // In make, the exact same prop file is used for both system and system_other. However, I 139 // believe make goes through a different build_image code path that is based on the name of 140 // the output file. So it sees the output file is named system_other.img and makes some changes. 141 // We don't use that codepath, so make the changes manually to the prop file. 142 propFile := android.PathForModuleOut(ctx, "prop") 143 ctx.Build(pctx, android.BuildParams{ 144 Rule: systemOtherPropFileTweaks, 145 Input: systemInfo.BuildImagePropFile, 146 Output: propFile, 147 }) 148 149 builder = android.NewRuleBuilder(pctx, ctx) 150 builder.Command(). 151 Textf("PATH=%s:$PATH", strings.Join(pathToolDirs, ":")). 152 BuiltTool("build_image"). 153 Text(stagingDir.String()). // input directory 154 Input(propFile). 155 Implicits(systemInfo.BuildImagePropFileDeps). 156 Implicit(fec). 157 Implicit(stagingDirTimestamp). 158 Output(output). 159 Text(stagingDir.String()) 160 161 builder.Build("build_system_other", "build system other") 162 163 // Create a hermetic system_other.img with pinned timestamps 164 builder = android.NewRuleBuilder(pctx, ctx) 165 outputHermetic := android.PathForModuleOut(ctx, "for_target_files", "system_other.img") 166 outputHermeticPropFile := m.propFileForHermeticImg(ctx, builder, propFile) 167 builder.Command(). 168 Textf("PATH=%s:$PATH", strings.Join(pathToolDirs, ":")). 169 BuiltTool("build_image"). 170 Text(stagingDir.String()). // input directory 171 Input(outputHermeticPropFile). 172 Implicits(systemInfo.BuildImagePropFileDeps). 173 Implicit(fec). 174 Implicit(stagingDirTimestamp). 175 Output(outputHermetic). 176 Text(stagingDir.String()) 177 178 builder.Build("build_system_other_hermetic", "build system other") 179 180 fsInfo := FilesystemInfo{ 181 Output: output, 182 OutputHermetic: outputHermetic, 183 RootDir: stagingDir, 184 FilesystemConfig: m.generateFilesystemConfig(ctx, stagingDir, stagingDirTimestamp), 185 PropFileForMiscInfo: m.buildPropFileForMiscInfo(ctx), 186 } 187 188 android.SetProvider(ctx, FilesystemProvider, fsInfo) 189 190 ctx.SetOutputFiles(android.Paths{output}, "") 191 ctx.CheckbuildFile(output) 192 193 // Dump compliance metadata 194 complianceMetadataInfo := ctx.ComplianceMetadataInfo() 195 complianceMetadataInfo.SetFilesContained(fullInstallPaths) 196} 197 198func (s *systemOtherImage) generateFilesystemConfig(ctx android.ModuleContext, stagingDir, stagingDirTimestamp android.Path) android.Path { 199 out := android.PathForModuleOut(ctx, "filesystem_config.txt") 200 ctx.Build(pctx, android.BuildParams{ 201 Rule: fsConfigRule, 202 Input: stagingDirTimestamp, // assemble the staging directory 203 Output: out, 204 Args: map[string]string{ 205 "rootDir": stagingDir.String(), 206 "prefix": "system/", 207 }, 208 }) 209 return out 210} 211 212func (f *systemOtherImage) propFileForHermeticImg(ctx android.ModuleContext, builder *android.RuleBuilder, inputPropFile android.Path) android.Path { 213 propFilePinnedTimestamp := android.PathForModuleOut(ctx, "for_target_files", "prop") 214 builder.Command().Textf("cat").Input(inputPropFile).Flag(">").Output(propFilePinnedTimestamp). 215 Textf(" && echo use_fixed_timestamp=true >> %s", propFilePinnedTimestamp) 216 return propFilePinnedTimestamp 217} 218 219func (f *systemOtherImage) buildPropFileForMiscInfo(ctx android.ModuleContext) android.Path { 220 var lines []string 221 addStr := func(name string, value string) { 222 lines = append(lines, fmt.Sprintf("%s=%s", name, value)) 223 } 224 225 addStr("building_system_other_image", "true") 226 227 systemImage := ctx.GetDirectDepProxyWithTag(*f.properties.System_image, systemImageDependencyTag) 228 systemInfo, ok := android.OtherModuleProvider(ctx, systemImage, FilesystemProvider) 229 if !ok { 230 ctx.PropertyErrorf("system_image", "Expected system_image module to provide FilesystemProvider") 231 return nil 232 } 233 if systemInfo.PartitionSize == nil { 234 addStr("system_other_disable_sparse", "true") 235 } 236 if systemInfo.UseAvb { 237 addStr("avb_system_other_hashtree_enable", "true") 238 addStr("avb_system_other_algorithm", systemInfo.AvbAlgorithm) 239 footerArgs := fmt.Sprintf("--hash_algorithm %s", systemInfo.AvbHashAlgorithm) 240 if rollbackIndex, err := f.avbRollbackIndex(ctx); err == nil { 241 footerArgs += fmt.Sprintf(" --rollback_index %d", rollbackIndex) 242 } else { 243 ctx.ModuleErrorf("Could not determine rollback_index %s\n", err) 244 } 245 addStr("avb_system_other_add_hashtree_footer_args", footerArgs) 246 if systemInfo.AvbKey != nil { 247 addStr("avb_system_other_key_path", systemInfo.AvbKey.String()) 248 } 249 } 250 251 sort.Strings(lines) 252 253 propFile := android.PathForModuleOut(ctx, "prop_file") 254 android.WriteFileRule(ctx, propFile, strings.Join(lines, "\n")) 255 return propFile 256} 257 258// Use the default: PlatformSecurityPatch 259// TODO: Get this value from vbmeta_system 260func (f *systemOtherImage) avbRollbackIndex(ctx android.ModuleContext) (int64, error) { 261 t, err := time.Parse(time.DateOnly, ctx.Config().PlatformSecurityPatch()) 262 if err != nil { 263 return -1, err 264 } 265 return t.Unix(), err 266} 267