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 "fmt" 19 "path/filepath" 20 "regexp" 21 "slices" 22 "strconv" 23 "strings" 24 25 "android/soong/android" 26 27 "github.com/google/blueprint" 28 "github.com/google/blueprint/proptools" 29) 30 31func init() { 32 android.RegisterModuleType("super_image", SuperImageFactory) 33} 34 35type superImage struct { 36 android.ModuleBase 37 38 properties SuperImageProperties 39 partitionProps SuperImagePartitionNameProperties 40 41 installDir android.InstallPath 42} 43 44type SuperImageProperties struct { 45 // the size of the super partition 46 Size *int64 47 // the block device where metadata for dynamic partitions is stored 48 Metadata_device *string 49 // the super partition block device list 50 Block_devices []string 51 // whether A/B updater is used 52 Ab_update *bool 53 // whether dynamic partitions is enabled on devices that were launched without this support 54 Retrofit *bool 55 // whether the output is a sparse image 56 Sparse *bool 57 // information about how partitions within the super partition are grouped together 58 Partition_groups []PartitionGroupsInfo 59 // Name of the system_other partition filesystem module. This module will be installed to 60 // the "b" slot of the system partition in a/b partition builds. 61 System_other_partition *string 62 // whether dynamic partitions is used 63 Use_dynamic_partitions *bool 64 Virtual_ab struct { 65 // whether virtual A/B seamless update is enabled 66 Enable *bool 67 // whether retrofitting virtual A/B seamless update is enabled 68 Retrofit *bool 69 // If set, device uses virtual A/B Compression 70 Compression *bool 71 // This value controls the compression algorithm used for VABC. 72 // Valid options are defined in system/core/fs_mgr/libsnapshot/cow_writer.cpp 73 // e.g. "none", "gz", "brotli" 74 Compression_method *string 75 // Specifies maximum bytes to be compressed at once during ota. Options: 4096, 8192, 16384, 32768, 65536, 131072, 262144. 76 Compression_factor *int64 77 // Specifies COW version to be used by update_engine and libsnapshot. If this value is not 78 // specified we default to COW version 2 in update_engine for backwards compatibility 79 Cow_version *int64 80 } 81 // Whether the super image will be disted in the update package 82 Super_image_in_update_package *bool 83 // Whether a super_empty.img should be created 84 Create_super_empty *bool 85} 86 87type PartitionGroupsInfo struct { 88 Name string 89 GroupSize string 90 PartitionList []string 91} 92 93type SuperImagePartitionNameProperties struct { 94 // Name of the System partition filesystem module 95 System_partition *string 96 // Name of the System_ext partition filesystem module 97 System_ext_partition *string 98 // Name of the System_dlkm partition filesystem module 99 System_dlkm_partition *string 100 // Name of the System_other partition filesystem module 101 System_other_partition *string 102 // Name of the Product partition filesystem module 103 Product_partition *string 104 // Name of the Vendor partition filesystem module 105 Vendor_partition *string 106 // Name of the Vendor_dlkm partition filesystem module 107 Vendor_dlkm_partition *string 108 // Name of the Odm partition filesystem module 109 Odm_partition *string 110 // Name of the Odm_dlkm partition filesystem module 111 Odm_dlkm_partition *string 112} 113 114type SuperImageInfo struct { 115 // The built super.img file, which contains the sub-partitions 116 SuperImage android.Path 117 118 // Mapping from the sub-partition type to its re-exported FileSystemInfo providers from the 119 // sub-partitions. 120 SubImageInfo map[string]FilesystemInfo 121 122 DynamicPartitionsInfo android.Path 123 124 SuperEmptyImage android.Path 125 126 AbUpdate bool 127} 128 129var SuperImageProvider = blueprint.NewProvider[SuperImageInfo]() 130 131func SuperImageFactory() android.Module { 132 module := &superImage{} 133 module.AddProperties(&module.properties, &module.partitionProps) 134 android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon) 135 return module 136} 137 138type superImageDepTagType struct { 139 blueprint.BaseDependencyTag 140} 141 142var subImageDepTag superImageDepTagType 143 144type systemOtherDepTagType struct { 145 blueprint.BaseDependencyTag 146} 147 148var systemOtherDepTag systemOtherDepTagType 149 150func (s *superImage) DepsMutator(ctx android.BottomUpMutatorContext) { 151 addDependencyIfDefined := func(dep *string) { 152 if dep != nil { 153 ctx.AddDependency(ctx.Module(), subImageDepTag, proptools.String(dep)) 154 } 155 } 156 157 addDependencyIfDefined(s.partitionProps.System_partition) 158 addDependencyIfDefined(s.partitionProps.System_ext_partition) 159 addDependencyIfDefined(s.partitionProps.System_dlkm_partition) 160 addDependencyIfDefined(s.partitionProps.System_other_partition) 161 addDependencyIfDefined(s.partitionProps.Product_partition) 162 addDependencyIfDefined(s.partitionProps.Vendor_partition) 163 addDependencyIfDefined(s.partitionProps.Vendor_dlkm_partition) 164 addDependencyIfDefined(s.partitionProps.Odm_partition) 165 addDependencyIfDefined(s.partitionProps.Odm_dlkm_partition) 166 if s.properties.System_other_partition != nil { 167 ctx.AddDependency(ctx.Module(), systemOtherDepTag, *s.properties.System_other_partition) 168 } 169} 170 171func (s *superImage) GenerateAndroidBuildActions(ctx android.ModuleContext) { 172 miscInfo, deps, subImageInfos := s.buildMiscInfo(ctx, false) 173 builder := android.NewRuleBuilder(pctx, ctx) 174 output := android.PathForModuleOut(ctx, s.installFileName()) 175 lpMake := ctx.Config().HostToolPath(ctx, "lpmake") 176 lpMakeDir := filepath.Dir(lpMake.String()) 177 deps = append(deps, lpMake) 178 builder.Command().Textf("PATH=%s:\\$PATH", lpMakeDir). 179 BuiltTool("build_super_image"). 180 Text("-v"). 181 Input(miscInfo). 182 Implicits(deps). 183 Output(output) 184 builder.Build("build_super_image", fmt.Sprintf("Creating super image %s", s.BaseModuleName())) 185 var superEmptyImage android.WritablePath 186 if proptools.Bool(s.properties.Create_super_empty) { 187 superEmptyImageBuilder := android.NewRuleBuilder(pctx, ctx) 188 superEmptyImage = android.PathForModuleOut(ctx, "super_empty.img") 189 superEmptyMiscInfo, superEmptyDeps, _ := s.buildMiscInfo(ctx, true) 190 if superEmptyDeps != nil { 191 ctx.ModuleErrorf("TODO: Handle additional deps when building super_empty.img") 192 } 193 superEmptyImageBuilder.Command().Textf("PATH=%s:\\$PATH", lpMakeDir). 194 BuiltTool("build_super_image"). 195 Text("-v"). 196 Input(superEmptyMiscInfo). 197 Implicit(lpMake). 198 Output(superEmptyImage) 199 superEmptyImageBuilder.Build("build_super_empty_image", fmt.Sprintf("Creating super empty image %s", s.BaseModuleName())) 200 } 201 android.SetProvider(ctx, SuperImageProvider, SuperImageInfo{ 202 SuperImage: output, 203 SubImageInfo: subImageInfos, 204 DynamicPartitionsInfo: s.generateDynamicPartitionsInfo(ctx), 205 SuperEmptyImage: superEmptyImage, 206 AbUpdate: proptools.Bool(s.properties.Ab_update), 207 }) 208 ctx.SetOutputFiles([]android.Path{output}, "") 209 ctx.CheckbuildFile(output) 210 211 buildComplianceMetadata(ctx, subImageDepTag) 212} 213 214func (s *superImage) installFileName() string { 215 return "super.img" 216} 217 218func (s *superImage) buildMiscInfo(ctx android.ModuleContext, superEmpty bool) (android.Path, android.Paths, map[string]FilesystemInfo) { 219 var miscInfoString strings.Builder 220 partitionList := s.dumpDynamicPartitionInfo(ctx, &miscInfoString) 221 addStr := func(name string, value string) { 222 miscInfoString.WriteString(name) 223 miscInfoString.WriteRune('=') 224 miscInfoString.WriteString(value) 225 miscInfoString.WriteRune('\n') 226 } 227 addStr("ab_update", strconv.FormatBool(proptools.Bool(s.properties.Ab_update))) 228 if superEmpty { 229 miscInfo := android.PathForModuleOut(ctx, "misc_info_super_empty.txt") 230 android.WriteFileRule(ctx, miscInfo, miscInfoString.String()) 231 return miscInfo, nil, nil 232 } 233 234 subImageInfo := make(map[string]FilesystemInfo) 235 var deps android.Paths 236 237 missingPartitionErrorMessage := "" 238 handleSubPartition := func(partitionType string, name *string) { 239 if proptools.String(name) == "" { 240 missingPartitionErrorMessage += fmt.Sprintf("%s image listed in partition groups, but its module was not specified. ", partitionType) 241 return 242 } 243 mod := ctx.GetDirectDepWithTag(*name, subImageDepTag) 244 if mod == nil { 245 ctx.ModuleErrorf("Could not get dep %q", *name) 246 return 247 } 248 info, ok := android.OtherModuleProvider(ctx, mod, FilesystemProvider) 249 if !ok { 250 ctx.ModuleErrorf("Expected dep %q to provide FilesystemInfo", *name) 251 return 252 } 253 addStr(partitionType+"_image", info.Output.String()) 254 deps = append(deps, info.Output) 255 if _, ok := subImageInfo[partitionType]; ok { 256 ctx.ModuleErrorf("Already set subimageInfo for %q", partitionType) 257 } 258 subImageInfo[partitionType] = info 259 } 260 261 // Build partitionToImagePath, because system partition may need system_other 262 // partition image path 263 for _, p := range partitionList { 264 switch p { 265 case "system": 266 handleSubPartition("system", s.partitionProps.System_partition) 267 case "system_dlkm": 268 handleSubPartition("system_dlkm", s.partitionProps.System_dlkm_partition) 269 case "system_ext": 270 handleSubPartition("system_ext", s.partitionProps.System_ext_partition) 271 case "product": 272 handleSubPartition("product", s.partitionProps.Product_partition) 273 case "vendor": 274 handleSubPartition("vendor", s.partitionProps.Vendor_partition) 275 case "vendor_dlkm": 276 handleSubPartition("vendor_dlkm", s.partitionProps.Vendor_dlkm_partition) 277 case "odm": 278 handleSubPartition("odm", s.partitionProps.Odm_partition) 279 case "odm_dlkm": 280 handleSubPartition("odm_dlkm", s.partitionProps.Odm_dlkm_partition) 281 default: 282 ctx.ModuleErrorf("partition %q is not a super image supported partition", p) 283 } 284 } 285 286 if s.properties.System_other_partition != nil { 287 if !slices.Contains(partitionList, "system") { 288 ctx.PropertyErrorf("system_other_partition", "Must have a system partition to use a system_other partition") 289 } 290 systemOther := ctx.GetDirectDepProxyWithTag(*s.properties.System_other_partition, systemOtherDepTag) 291 systemOtherFiles := android.OutputFilesForModule(ctx, systemOther, "") 292 if len(systemOtherFiles) != 1 { 293 ctx.PropertyErrorf("system_other_partition", "Expected 1 output file from module %q", *&s.properties.System_other_partition) 294 } else { 295 handleSubPartition("system_other", s.partitionProps.System_other_partition) 296 } 297 } 298 299 // Delay the error message until execution time because on aosp-main-future-without-vendor, 300 // BUILDING_VENDOR_IMAGE is false so we don't get the vendor image, but it's still listed in 301 // BOARD_GOOGLE_DYNAMIC_PARTITIONS_PARTITION_LIST. 302 missingPartitionErrorMessageFile := android.PathForModuleOut(ctx, "missing_partition_error.txt") 303 if missingPartitionErrorMessage != "" { 304 ctx.Build(pctx, android.BuildParams{ 305 Rule: android.ErrorRule, 306 Output: missingPartitionErrorMessageFile, 307 Args: map[string]string{ 308 "error": missingPartitionErrorMessage, 309 }, 310 }) 311 } else { 312 ctx.Build(pctx, android.BuildParams{ 313 Rule: android.Touch, 314 Output: missingPartitionErrorMessageFile, 315 }) 316 } 317 318 miscInfo := android.PathForModuleOut(ctx, "misc_info.txt") 319 android.WriteFileRule(ctx, miscInfo, miscInfoString.String(), missingPartitionErrorMessageFile) 320 return miscInfo, deps, subImageInfo 321} 322 323func (s *superImage) dumpDynamicPartitionInfo(ctx android.ModuleContext, sb *strings.Builder) []string { 324 addStr := func(name string, value string) { 325 sb.WriteString(name) 326 sb.WriteRune('=') 327 sb.WriteString(value) 328 sb.WriteRune('\n') 329 } 330 331 addStr("use_dynamic_partitions", strconv.FormatBool(proptools.Bool(s.properties.Use_dynamic_partitions))) 332 if proptools.Bool(s.properties.Retrofit) { 333 addStr("dynamic_partition_retrofit", "true") 334 } 335 addStr("lpmake", "lpmake") 336 addStr("build_super_partition", "true") 337 if proptools.Bool(s.properties.Create_super_empty) { 338 addStr("build_super_empty_partition", "true") 339 } 340 addStr("super_metadata_device", proptools.String(s.properties.Metadata_device)) 341 if len(s.properties.Block_devices) > 0 { 342 addStr("super_block_devices", strings.Join(s.properties.Block_devices, " ")) 343 } 344 // TODO: In make, there's more complicated logic than just this surrounding super_*_device_size 345 addStr("super_super_device_size", strconv.Itoa(proptools.Int(s.properties.Size))) 346 var groups, partitionList []string 347 for _, groupInfo := range s.properties.Partition_groups { 348 groups = append(groups, groupInfo.Name) 349 partitionList = append(partitionList, groupInfo.PartitionList...) 350 } 351 addStr("dynamic_partition_list", strings.Join(android.SortedUniqueStrings(partitionList), " ")) 352 addStr("super_partition_groups", strings.Join(groups, " ")) 353 initialPartitionListLen := len(partitionList) 354 partitionList = android.SortedUniqueStrings(partitionList) 355 if len(partitionList) != initialPartitionListLen { 356 ctx.ModuleErrorf("Duplicate partitions found in the partition_groups property") 357 } 358 // Add Partition group info after adding `super_partition_groups` and `dynamic_partition_list` 359 for _, groupInfo := range s.properties.Partition_groups { 360 addStr("super_"+groupInfo.Name+"_group_size", groupInfo.GroupSize) 361 addStr("super_"+groupInfo.Name+"_partition_list", strings.Join(groupInfo.PartitionList, " ")) 362 } 363 364 if proptools.Bool(s.properties.Super_image_in_update_package) { 365 addStr("super_image_in_update_package", "true") 366 } 367 addStr("super_partition_size", strconv.Itoa(proptools.Int(s.properties.Size))) 368 369 if proptools.Bool(s.properties.Virtual_ab.Enable) { 370 addStr("virtual_ab", "true") 371 if proptools.Bool(s.properties.Virtual_ab.Retrofit) { 372 addStr("virtual_ab_retrofit", "true") 373 } 374 addStr("virtual_ab_compression", strconv.FormatBool(proptools.Bool(s.properties.Virtual_ab.Compression))) 375 if s.properties.Virtual_ab.Compression_method != nil { 376 matched, _ := regexp.MatchString("^[a-zA-Z0-9_-]+$", *s.properties.Virtual_ab.Compression_method) 377 if !matched { 378 ctx.PropertyErrorf("virtual_ab.compression_method", "compression_method cannot have special characters") 379 } 380 addStr("virtual_ab_compression_method", *s.properties.Virtual_ab.Compression_method) 381 } 382 if s.properties.Virtual_ab.Cow_version != nil { 383 addStr("virtual_ab_cow_version", strconv.FormatInt(*s.properties.Virtual_ab.Cow_version, 10)) 384 } 385 if s.properties.Virtual_ab.Compression_factor != nil { 386 addStr("virtual_ab_compression_factor", strconv.FormatInt(*s.properties.Virtual_ab.Compression_factor, 10)) 387 } 388 389 } else { 390 if s.properties.Virtual_ab.Retrofit != nil { 391 ctx.PropertyErrorf("virtual_ab.retrofit", "This property cannot be set when virtual_ab is disabled") 392 } 393 if s.properties.Virtual_ab.Compression != nil { 394 ctx.PropertyErrorf("virtual_ab.compression", "This property cannot be set when virtual_ab is disabled") 395 } 396 if s.properties.Virtual_ab.Compression_method != nil { 397 ctx.PropertyErrorf("virtual_ab.compression_method", "This property cannot be set when virtual_ab is disabled") 398 } 399 if s.properties.Virtual_ab.Compression_factor != nil { 400 ctx.PropertyErrorf("virtual_ab.compression_factor", "This property cannot be set when virtual_ab is disabled") 401 } 402 } 403 404 return partitionList 405} 406 407func (s *superImage) generateDynamicPartitionsInfo(ctx android.ModuleContext) android.Path { 408 var contents strings.Builder 409 s.dumpDynamicPartitionInfo(ctx, &contents) 410 dynamicPartitionsInfo := android.PathForModuleOut(ctx, "dynamic_partitions_info.txt") 411 android.WriteFileRuleVerbatim(ctx, dynamicPartitionsInfo, contents.String()) 412 return dynamicPartitionsInfo 413} 414