xref: /aosp_15_r20/build/soong/filesystem/filesystem.go (revision 333d2b3687b3a337dbcca9d65000bca186795e39)
1// Copyright (C) 2020 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	"crypto/sha256"
19	"fmt"
20	"io"
21	"path/filepath"
22	"slices"
23	"strconv"
24	"strings"
25
26	"android/soong/android"
27	"android/soong/cc"
28	"android/soong/java"
29	"android/soong/linkerconfig"
30
31	"github.com/google/blueprint"
32	"github.com/google/blueprint/proptools"
33)
34
35func init() {
36	registerBuildComponents(android.InitRegistrationContext)
37	registerMutators(android.InitRegistrationContext)
38}
39
40func registerBuildComponents(ctx android.RegistrationContext) {
41	ctx.RegisterModuleType("android_filesystem", FilesystemFactory)
42	ctx.RegisterModuleType("android_filesystem_defaults", filesystemDefaultsFactory)
43	ctx.RegisterModuleType("android_system_image", SystemImageFactory)
44	ctx.RegisterModuleType("avb_add_hash_footer", avbAddHashFooterFactory)
45	ctx.RegisterModuleType("avb_add_hash_footer_defaults", avbAddHashFooterDefaultsFactory)
46	ctx.RegisterModuleType("avb_gen_vbmeta_image", avbGenVbmetaImageFactory)
47	ctx.RegisterModuleType("avb_gen_vbmeta_image_defaults", avbGenVbmetaImageDefaultsFactory)
48}
49
50func registerMutators(ctx android.RegistrationContext) {
51	ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
52		ctx.BottomUp("add_autogenerated_rro_deps", addAutogeneratedRroDeps)
53	})
54}
55
56// Remember to add referenced files to implicits!
57var textFileProcessorRule = pctx.AndroidStaticRule("text_file_processing", blueprint.RuleParams{
58	Command:     "build/soong/scripts/text_file_processor.py $in $out",
59	CommandDeps: []string{"build/soong/scripts/text_file_processor.py"},
60})
61
62type filesystem struct {
63	android.ModuleBase
64	android.PackagingBase
65	android.DefaultableModuleBase
66
67	properties FilesystemProperties
68
69	output     android.Path
70	installDir android.InstallPath
71
72	fileListFile android.Path
73
74	// Keeps the entries installed from this filesystem
75	entries []string
76
77	filesystemBuilder filesystemBuilder
78}
79
80type filesystemBuilder interface {
81	BuildLinkerConfigFile(ctx android.ModuleContext, builder *android.RuleBuilder, rebasedDir android.OutputPath)
82	// Function that filters PackagingSpec in PackagingBase.GatherPackagingSpecs()
83	FilterPackagingSpec(spec android.PackagingSpec) bool
84	// Function that modifies PackagingSpec in PackagingBase.GatherPackagingSpecs() to customize.
85	// For example, GSI system.img contains system_ext and product artifacts and their
86	// relPathInPackage need to be rebased to system/system_ext and system/system_product.
87	ModifyPackagingSpec(spec *android.PackagingSpec)
88
89	// Function to check if the filesystem should not use `vintf_fragments` property,
90	// but use `vintf_fragment` module type instead
91	ShouldUseVintfFragmentModuleOnly() bool
92}
93
94var _ filesystemBuilder = (*filesystem)(nil)
95
96type SymlinkDefinition struct {
97	Target *string
98	Name   *string
99}
100
101type FilesystemProperties struct {
102	// When set to true, sign the image with avbtool. Default is false.
103	Use_avb *bool
104
105	// Path to the private key that avbtool will use to sign this filesystem image.
106	// TODO(jiyong): allow apex_key to be specified here
107	Avb_private_key *string `android:"path"`
108
109	// Signing algorithm for avbtool. Default is SHA256_RSA4096.
110	Avb_algorithm *string
111
112	// Hash algorithm used for avbtool (for descriptors). This is passed as hash_algorithm to
113	// avbtool. Default used by avbtool is sha1.
114	Avb_hash_algorithm *string
115
116	// Whether or not to use forward-error-correction codes when signing with AVB. Defaults to true.
117	Use_fec *bool
118
119	// The index used to prevent rollback of the image. Only used if use_avb is true.
120	Rollback_index *int64
121
122	// Name of the partition stored in vbmeta desc. Defaults to the name of this module.
123	Partition_name *string
124
125	// Type of the filesystem. Currently, ext4, erofs, cpio, and compressed_cpio are supported. Default
126	// is ext4.
127	Type *string
128
129	// Identifies which partition this is for //visibility:any_system_image (and others) visibility
130	// checks, and will be used in the future for API surface checks.
131	Partition_type *string
132
133	// file_contexts file to make image. Currently, only ext4 is supported. These file contexts
134	// will be compiled with sefcontext_compile
135	File_contexts *string `android:"path"`
136
137	// The selinux file contexts, after having already run them through sefcontext_compile
138	Precompiled_file_contexts *string `android:"path"`
139
140	// Base directory relative to root, to which deps are installed, e.g. "system". Default is "."
141	// (root).
142	Base_dir *string
143
144	// Directories to be created under root. e.g. /dev, /proc, etc.
145	Dirs proptools.Configurable[[]string]
146
147	// Symbolic links to be created under root with "ln -sf <target> <name>".
148	Symlinks []SymlinkDefinition
149
150	// Seconds since unix epoch to override timestamps of file entries
151	Fake_timestamp *string
152
153	// When set, passed to mkuserimg_mke2fs --mke2fs_uuid & --mke2fs_hash_seed.
154	// Otherwise, they'll be set as random which might cause indeterministic build output.
155	Uuid *string
156
157	// Mount point for this image. Default is "/"
158	Mount_point *string
159
160	// If set to the name of a partition ("system", "vendor", etc), this filesystem module
161	// will also include the contents of the make-built staging directories. If any soong
162	// modules would be installed to the same location as a make module, they will overwrite
163	// the make version.
164	Include_make_built_files string
165
166	// When set, builds etc/event-log-tags file by merging logtags from all dependencies.
167	// Default is false
168	Build_logtags *bool
169
170	// Install aconfig_flags.pb file for the modules installed in this partition.
171	Gen_aconfig_flags_pb *bool
172
173	Fsverity fsverityProperties
174
175	// If this property is set to true, the filesystem will call ctx.UncheckedModule(), causing
176	// it to not be built on checkbuilds. Used for the automatic migration from make to soong
177	// build modules, where we want to emit some not-yet-working filesystems and we don't want them
178	// to be built.
179	Unchecked_module *bool `blueprint:"mutated"`
180
181	Erofs ErofsProperties
182
183	F2fs F2fsProperties
184
185	Linker_config LinkerConfigProperties
186
187	// Determines if the module is auto-generated from Soong or not. If the module is
188	// auto-generated, its deps are exempted from visibility enforcement.
189	Is_auto_generated *bool
190
191	// Path to the dev nodes description file. This is only needed for building the ramdisk
192	// partition and should not be explicitly specified.
193	Dev_nodes_description_file *string `android:"path" blueprint:"mutated"`
194
195	// Additional dependencies used for building android products
196	Android_filesystem_deps AndroidFilesystemDeps
197}
198
199type AndroidFilesystemDeps struct {
200	System     *string
201	System_ext *string
202}
203
204// Additional properties required to generate erofs FS partitions.
205type ErofsProperties struct {
206	// Compressor and Compression level passed to mkfs.erofs. e.g. (lz4hc,9)
207	// Please see external/erofs-utils/README for complete documentation.
208	Compressor *string
209
210	// Used as --compress-hints for mkfs.erofs
211	Compress_hints *string `android:"path"`
212
213	Sparse *bool
214}
215
216// Additional properties required to generate f2fs FS partitions.
217type F2fsProperties struct {
218	Sparse *bool
219}
220
221type LinkerConfigProperties struct {
222
223	// Build a linker.config.pb file
224	Gen_linker_config *bool
225
226	// List of files (in .json format) that will be converted to a linker config file (in .pb format).
227	// The linker config file be installed in the filesystem at /etc/linker.config.pb
228	Linker_config_srcs []string `android:"path"`
229}
230
231// android_filesystem packages a set of modules and their transitive dependencies into a filesystem
232// image. The filesystem images are expected to be mounted in the target device, which means the
233// modules in the filesystem image are built for the target device (i.e. Android, not Linux host).
234// The modules are placed in the filesystem image just like they are installed to the ordinary
235// partitions like system.img. For example, cc_library modules are placed under ./lib[64] directory.
236func FilesystemFactory() android.Module {
237	module := &filesystem{}
238	module.filesystemBuilder = module
239	initFilesystemModule(module, module)
240	return module
241}
242
243func initFilesystemModule(module android.DefaultableModule, filesystemModule *filesystem) {
244	module.AddProperties(&filesystemModule.properties)
245	android.InitPackageModule(filesystemModule)
246	filesystemModule.PackagingBase.DepsCollectFirstTargetOnly = true
247	filesystemModule.PackagingBase.AllowHighPriorityDeps = true
248	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
249	android.InitDefaultableModule(module)
250
251	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
252		filesystemModule.setDevNodesDescriptionProp()
253	})
254}
255
256type depTag struct {
257	blueprint.BaseDependencyTag
258	android.PackagingItemAlwaysDepTag
259}
260
261var dependencyTag = depTag{}
262
263type depTagWithVisibilityEnforcementBypass struct {
264	depTag
265}
266
267type interPartitionDepTag struct {
268	blueprint.BaseDependencyTag
269}
270
271var interPartitionDependencyTag = interPartitionDepTag{}
272
273var _ android.ExcludeFromVisibilityEnforcementTag = (*depTagWithVisibilityEnforcementBypass)(nil)
274
275func (t depTagWithVisibilityEnforcementBypass) ExcludeFromVisibilityEnforcement() {}
276
277var dependencyTagWithVisibilityEnforcementBypass = depTagWithVisibilityEnforcementBypass{}
278
279// ramdiskDevNodesDescription is the name of the filegroup module that provides the file that
280// contains the description of dev nodes added to the CPIO archive for the ramdisk partition.
281const ramdiskDevNodesDescription = "ramdisk_node_list"
282
283func (f *filesystem) setDevNodesDescriptionProp() {
284	if proptools.String(f.properties.Partition_name) == "ramdisk" {
285		f.properties.Dev_nodes_description_file = proptools.StringPtr(":" + ramdiskDevNodesDescription)
286	}
287}
288
289func (f *filesystem) DepsMutator(ctx android.BottomUpMutatorContext) {
290	if proptools.Bool(f.properties.Is_auto_generated) {
291		f.AddDeps(ctx, dependencyTagWithVisibilityEnforcementBypass)
292	} else {
293		f.AddDeps(ctx, dependencyTag)
294	}
295	if f.properties.Android_filesystem_deps.System != nil {
296		ctx.AddDependency(ctx.Module(), interPartitionDependencyTag, proptools.String(f.properties.Android_filesystem_deps.System))
297	}
298	if f.properties.Android_filesystem_deps.System_ext != nil {
299		ctx.AddDependency(ctx.Module(), interPartitionDependencyTag, proptools.String(f.properties.Android_filesystem_deps.System_ext))
300	}
301}
302
303type fsType int
304
305const (
306	ext4Type fsType = iota
307	erofsType
308	f2fsType
309	compressedCpioType
310	cpioType // uncompressed
311	unknown
312)
313
314func (fs fsType) IsUnknown() bool {
315	return fs == unknown
316}
317
318type FilesystemInfo struct {
319	// A text file containing the list of paths installed on the partition.
320	FileListFile android.Path
321}
322
323var FilesystemProvider = blueprint.NewProvider[FilesystemInfo]()
324
325func GetFsTypeFromString(ctx android.EarlyModuleContext, typeStr string) fsType {
326	switch typeStr {
327	case "ext4":
328		return ext4Type
329	case "erofs":
330		return erofsType
331	case "f2fs":
332		return f2fsType
333	case "compressed_cpio":
334		return compressedCpioType
335	case "cpio":
336		return cpioType
337	default:
338		return unknown
339	}
340}
341
342func (f *filesystem) fsType(ctx android.ModuleContext) fsType {
343	typeStr := proptools.StringDefault(f.properties.Type, "ext4")
344	fsType := GetFsTypeFromString(ctx, typeStr)
345	if fsType == unknown {
346		ctx.PropertyErrorf("type", "%q not supported", typeStr)
347	}
348	return fsType
349}
350
351func (f *filesystem) installFileName() string {
352	return f.BaseModuleName() + ".img"
353}
354
355func (f *filesystem) partitionName() string {
356	return proptools.StringDefault(f.properties.Partition_name, f.Name())
357}
358
359func (f *filesystem) FilterPackagingSpec(ps android.PackagingSpec) bool {
360	// Filesystem module respects the installation semantic. A PackagingSpec from a module with
361	// IsSkipInstall() is skipped.
362	if ps.SkipInstall() {
363		return false
364	}
365	if proptools.Bool(f.properties.Is_auto_generated) { // TODO (spandandas): Remove this.
366		pt := f.PartitionType()
367		return ps.Partition() == pt || strings.HasPrefix(ps.Partition(), pt+"/")
368	}
369	return true
370}
371
372func (f *filesystem) ModifyPackagingSpec(ps *android.PackagingSpec) {
373	// Sometimes, android.modulePartition() returns a path with >1 path components.
374	// This makes the partition field of packagingSpecs have multiple components, like
375	// "system/product". Right now, the filesystem module doesn't look at the partition field
376	// when deciding what path to install the file under, only the RelPathInPackage field, so
377	// we move the later path components from partition to relPathInPackage. This should probably
378	// be revisited in the future.
379	prefix := f.PartitionType() + "/"
380	if strings.HasPrefix(ps.Partition(), prefix) {
381		subPartition := strings.TrimPrefix(ps.Partition(), prefix)
382		ps.SetPartition(f.PartitionType())
383		ps.SetRelPathInPackage(filepath.Join(subPartition, ps.RelPathInPackage()))
384	}
385}
386
387var pctx = android.NewPackageContext("android/soong/filesystem")
388
389func (f *filesystem) GenerateAndroidBuildActions(ctx android.ModuleContext) {
390	validatePartitionType(ctx, f)
391	if f.filesystemBuilder.ShouldUseVintfFragmentModuleOnly() {
392		f.validateVintfFragments(ctx)
393	}
394	switch f.fsType(ctx) {
395	case ext4Type, erofsType, f2fsType:
396		f.output = f.buildImageUsingBuildImage(ctx)
397	case compressedCpioType:
398		f.output = f.buildCpioImage(ctx, true)
399	case cpioType:
400		f.output = f.buildCpioImage(ctx, false)
401	default:
402		return
403	}
404
405	f.installDir = android.PathForModuleInstall(ctx, "etc")
406	ctx.InstallFile(f.installDir, f.installFileName(), f.output)
407	ctx.SetOutputFiles([]android.Path{f.output}, "")
408
409	fileListFile := android.PathForModuleOut(ctx, "fileList")
410	android.WriteFileRule(ctx, fileListFile, f.installedFilesList())
411
412	android.SetProvider(ctx, FilesystemProvider, FilesystemInfo{
413		FileListFile: fileListFile,
414	})
415	f.fileListFile = fileListFile
416
417	if proptools.Bool(f.properties.Unchecked_module) {
418		ctx.UncheckedModule()
419	}
420}
421
422func (f *filesystem) validateVintfFragments(ctx android.ModuleContext) {
423	visitedModule := map[string]bool{}
424	packagingSpecs := f.gatherFilteredPackagingSpecs(ctx)
425
426	moduleInFileSystem := func(mod android.Module) bool {
427		for _, ps := range android.OtherModuleProviderOrDefault(
428			ctx, mod, android.InstallFilesProvider).PackagingSpecs {
429			if _, ok := packagingSpecs[ps.RelPathInPackage()]; ok {
430				return true
431			}
432		}
433		return false
434	}
435
436	ctx.WalkDeps(func(child, parent android.Module) bool {
437		if visitedModule[child.Name()] {
438			return false
439		}
440		if !moduleInFileSystem(child) {
441			visitedModule[child.Name()] = true
442			return true
443		}
444		if vintfFragments := child.VintfFragments(ctx); vintfFragments != nil {
445			ctx.PropertyErrorf(
446				"vintf_fragments",
447				"Module %s is referenced by soong-defined filesystem %s with property vintf_fragments(%s) in use."+
448					" Use vintf_fragment_modules property instead.",
449				child.Name(),
450				f.BaseModuleName(),
451				strings.Join(vintfFragments, ", "),
452			)
453		}
454		visitedModule[child.Name()] = true
455		return true
456	})
457}
458
459func (f *filesystem) appendToEntry(ctx android.ModuleContext, installedFile android.Path) {
460	partitionBaseDir := android.PathForModuleOut(ctx, "root", proptools.String(f.properties.Base_dir)).String() + "/"
461
462	relPath, inTargetPartition := strings.CutPrefix(installedFile.String(), partitionBaseDir)
463	if inTargetPartition {
464		f.entries = append(f.entries, relPath)
465	}
466}
467
468func (f *filesystem) installedFilesList() string {
469	installedFilePaths := android.FirstUniqueStrings(f.entries)
470	slices.Sort(installedFilePaths)
471
472	return strings.Join(installedFilePaths, "\n")
473}
474
475func validatePartitionType(ctx android.ModuleContext, p partition) {
476	if !android.InList(p.PartitionType(), validPartitions) {
477		ctx.PropertyErrorf("partition_type", "partition_type must be one of %s, found: %s", validPartitions, p.PartitionType())
478	}
479
480	ctx.VisitDirectDepsWithTag(android.DefaultsDepTag, func(m android.Module) {
481		if fdm, ok := m.(*filesystemDefaults); ok {
482			if p.PartitionType() != fdm.PartitionType() {
483				ctx.PropertyErrorf("partition_type",
484					"%s doesn't match with the partition type %s of the filesystem default module %s",
485					p.PartitionType(), fdm.PartitionType(), m.Name())
486			}
487		}
488	})
489}
490
491// Copy extra files/dirs that are not from the `deps` property to `rootDir`, checking for conflicts with files
492// already in `rootDir`.
493func (f *filesystem) buildNonDepsFiles(ctx android.ModuleContext, builder *android.RuleBuilder, rootDir android.OutputPath) {
494	// create dirs and symlinks
495	for _, dir := range f.properties.Dirs.GetOrDefault(ctx, nil) {
496		// OutputPath.Join verifies dir
497		builder.Command().Text("mkdir -p").Text(rootDir.Join(ctx, dir).String())
498	}
499
500	for _, symlink := range f.properties.Symlinks {
501		name := strings.TrimSpace(proptools.String(symlink.Name))
502		target := strings.TrimSpace(proptools.String(symlink.Target))
503
504		if name == "" {
505			ctx.PropertyErrorf("symlinks", "Name can't be empty")
506			continue
507		}
508
509		if target == "" {
510			ctx.PropertyErrorf("symlinks", "Target can't be empty")
511			continue
512		}
513
514		// OutputPath.Join verifies name. don't need to verify target.
515		dst := rootDir.Join(ctx, name)
516		builder.Command().Textf("(! [ -e %s -o -L %s ] || (echo \"%s already exists from an earlier stage of the build\" && exit 1))", dst, dst, dst)
517		builder.Command().Text("mkdir -p").Text(filepath.Dir(dst.String()))
518		builder.Command().Text("ln -sf").Text(proptools.ShellEscape(target)).Text(dst.String())
519		f.appendToEntry(ctx, dst)
520	}
521}
522
523func (f *filesystem) copyPackagingSpecs(ctx android.ModuleContext, builder *android.RuleBuilder, specs map[string]android.PackagingSpec, rootDir, rebasedDir android.WritablePath) []string {
524	rootDirSpecs := make(map[string]android.PackagingSpec)
525	rebasedDirSpecs := make(map[string]android.PackagingSpec)
526
527	for rel, spec := range specs {
528		if spec.Partition() == "root" {
529			rootDirSpecs[rel] = spec
530		} else {
531			rebasedDirSpecs[rel] = spec
532		}
533	}
534
535	dirsToSpecs := make(map[android.WritablePath]map[string]android.PackagingSpec)
536	dirsToSpecs[rootDir] = rootDirSpecs
537	dirsToSpecs[rebasedDir] = rebasedDirSpecs
538
539	return f.CopySpecsToDirs(ctx, builder, dirsToSpecs)
540}
541
542func (f *filesystem) copyFilesToProductOut(ctx android.ModuleContext, builder *android.RuleBuilder, rebasedDir android.OutputPath) {
543	if f.Name() != ctx.Config().SoongDefinedSystemImage() {
544		return
545	}
546	installPath := android.PathForModuleInPartitionInstall(ctx, f.partitionName())
547	builder.Command().Textf("cp -prf %s/* %s", rebasedDir, installPath)
548}
549
550func (f *filesystem) buildImageUsingBuildImage(ctx android.ModuleContext) android.Path {
551	rootDir := android.PathForModuleOut(ctx, "root").OutputPath
552	rebasedDir := rootDir
553	if f.properties.Base_dir != nil {
554		rebasedDir = rootDir.Join(ctx, *f.properties.Base_dir)
555	}
556	builder := android.NewRuleBuilder(pctx, ctx)
557	// Wipe the root dir to get rid of leftover files from prior builds
558	builder.Command().Textf("rm -rf %s && mkdir -p %s", rootDir, rootDir)
559	specs := f.gatherFilteredPackagingSpecs(ctx)
560	f.entries = f.copyPackagingSpecs(ctx, builder, specs, rootDir, rebasedDir)
561
562	f.buildNonDepsFiles(ctx, builder, rootDir)
563	f.addMakeBuiltFiles(ctx, builder, rootDir)
564	f.buildFsverityMetadataFiles(ctx, builder, specs, rootDir, rebasedDir)
565	f.buildEventLogtagsFile(ctx, builder, rebasedDir)
566	f.buildAconfigFlagsFiles(ctx, builder, specs, rebasedDir)
567	f.filesystemBuilder.BuildLinkerConfigFile(ctx, builder, rebasedDir)
568	f.copyFilesToProductOut(ctx, builder, rebasedDir)
569
570	// run host_init_verifier
571	// Ideally we should have a concept of pluggable linters that verify the generated image.
572	// While such concept is not implement this will do.
573	// TODO(b/263574231): substitute with pluggable linter.
574	builder.Command().
575		BuiltTool("host_init_verifier").
576		FlagWithArg("--out_system=", rootDir.String()+"/system")
577
578	propFile, toolDeps := f.buildPropFile(ctx)
579
580	// Most of the time, if build_image were to call a host tool, it accepts the path to the
581	// host tool in a field in the prop file. However, it doesn't have that option for fec, which
582	// it expects to just be on the PATH. Add fec to the PATH.
583	fec := ctx.Config().HostToolPath(ctx, "fec")
584	pathToolDirs := []string{filepath.Dir(fec.String())}
585
586	output := android.PathForModuleOut(ctx, f.installFileName())
587	builder.Command().
588		Textf("PATH=%s:$PATH", strings.Join(pathToolDirs, ":")).
589		BuiltTool("build_image").
590		Text(rootDir.String()). // input directory
591		Input(propFile).
592		Implicits(toolDeps).
593		Implicit(fec).
594		Output(output).
595		Text(rootDir.String()) // directory where to find fs_config_files|dirs
596
597	// rootDir is not deleted. Might be useful for quick inspection.
598	builder.Build("build_filesystem_image", fmt.Sprintf("Creating filesystem %s", f.BaseModuleName()))
599
600	return output
601}
602
603func (f *filesystem) buildFileContexts(ctx android.ModuleContext) android.Path {
604	builder := android.NewRuleBuilder(pctx, ctx)
605	fcBin := android.PathForModuleOut(ctx, "file_contexts.bin")
606	builder.Command().BuiltTool("sefcontext_compile").
607		FlagWithOutput("-o ", fcBin).
608		Input(android.PathForModuleSrc(ctx, proptools.String(f.properties.File_contexts)))
609	builder.Build("build_filesystem_file_contexts", fmt.Sprintf("Creating filesystem file contexts for %s", f.BaseModuleName()))
610	return fcBin
611}
612
613// Calculates avb_salt from entry list (sorted) for deterministic output.
614func (f *filesystem) salt() string {
615	return sha1sum(f.entries)
616}
617
618func (f *filesystem) buildPropFile(ctx android.ModuleContext) (android.Path, android.Paths) {
619	var deps android.Paths
620	var propFileString strings.Builder
621	addStr := func(name string, value string) {
622		propFileString.WriteString(name)
623		propFileString.WriteRune('=')
624		propFileString.WriteString(value)
625		propFileString.WriteRune('\n')
626	}
627	addPath := func(name string, path android.Path) {
628		addStr(name, path.String())
629		deps = append(deps, path)
630	}
631
632	// Type string that build_image.py accepts.
633	fsTypeStr := func(t fsType) string {
634		switch t {
635		// TODO(372522486): add more types like f2fs, erofs, etc.
636		case ext4Type:
637			return "ext4"
638		case erofsType:
639			return "erofs"
640		case f2fsType:
641			return "f2fs"
642		}
643		panic(fmt.Errorf("unsupported fs type %v", t))
644	}
645
646	addStr("fs_type", fsTypeStr(f.fsType(ctx)))
647	addStr("mount_point", proptools.StringDefault(f.properties.Mount_point, "/"))
648	addStr("use_dynamic_partition_size", "true")
649	addPath("ext_mkuserimg", ctx.Config().HostToolPath(ctx, "mkuserimg_mke2fs"))
650	// b/177813163 deps of the host tools have to be added. Remove this.
651	for _, t := range []string{"mke2fs", "e2fsdroid", "tune2fs"} {
652		deps = append(deps, ctx.Config().HostToolPath(ctx, t))
653	}
654
655	if proptools.Bool(f.properties.Use_avb) {
656		addStr("avb_hashtree_enable", "true")
657		addPath("avb_avbtool", ctx.Config().HostToolPath(ctx, "avbtool"))
658		algorithm := proptools.StringDefault(f.properties.Avb_algorithm, "SHA256_RSA4096")
659		addStr("avb_algorithm", algorithm)
660		if f.properties.Avb_private_key != nil {
661			key := android.PathForModuleSrc(ctx, *f.properties.Avb_private_key)
662			addPath("avb_key_path", key)
663		}
664		addStr("partition_name", f.partitionName())
665		avb_add_hashtree_footer_args := ""
666		if !proptools.BoolDefault(f.properties.Use_fec, true) {
667			avb_add_hashtree_footer_args += " --do_not_generate_fec"
668		}
669		if hashAlgorithm := proptools.String(f.properties.Avb_hash_algorithm); hashAlgorithm != "" {
670			avb_add_hashtree_footer_args += " --hash_algorithm " + hashAlgorithm
671		}
672		if f.properties.Rollback_index != nil {
673			rollbackIndex := proptools.Int(f.properties.Rollback_index)
674			if rollbackIndex < 0 {
675				ctx.PropertyErrorf("rollback_index", "Rollback index must be non-negative")
676			}
677			avb_add_hashtree_footer_args += " --rollback_index " + strconv.Itoa(rollbackIndex)
678		}
679		avb_add_hashtree_footer_args += fmt.Sprintf(" --prop com.android.build.%s.os_version:%s", f.partitionName(), ctx.Config().PlatformVersionLastStable())
680		avb_add_hashtree_footer_args += fmt.Sprintf(" --prop com.android.build.%s.fingerprint:{CONTENTS_OF:%s}", f.partitionName(), ctx.Config().BuildFingerprintFile(ctx))
681		avb_add_hashtree_footer_args += fmt.Sprintf(" --prop com.android.build.%s.security_patch:%s", f.partitionName(), ctx.Config().PlatformSecurityPatch())
682		addStr("avb_add_hashtree_footer_args", avb_add_hashtree_footer_args)
683		addStr("avb_salt", f.salt())
684	}
685
686	if f.properties.File_contexts != nil && f.properties.Precompiled_file_contexts != nil {
687		ctx.ModuleErrorf("file_contexts and precompiled_file_contexts cannot both be set")
688	} else if f.properties.File_contexts != nil {
689		addPath("selinux_fc", f.buildFileContexts(ctx))
690	} else if f.properties.Precompiled_file_contexts != nil {
691		src := android.PathForModuleSrc(ctx, *f.properties.Precompiled_file_contexts)
692		if src != nil {
693			addPath("selinux_fc", src)
694		}
695	}
696	if timestamp := proptools.String(f.properties.Fake_timestamp); timestamp != "" {
697		addStr("timestamp", timestamp)
698	}
699	if uuid := proptools.String(f.properties.Uuid); uuid != "" {
700		addStr("uuid", uuid)
701		addStr("hash_seed", uuid)
702	}
703
704	// TODO(b/381120092): This should only be added if none of the size-related properties are set,
705	// but currently soong built partitions don't have size properties. Make code:
706	// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=2262;drc=39cd33701c9278db0e7e481a090605f428d5b12d
707	// Make uses system_disable_sparse but disable_sparse has the same effect, and we shouldn't need
708	// to qualify it because each partition gets its own property file built.
709	addStr("disable_sparse", "true")
710
711	fst := f.fsType(ctx)
712	switch fst {
713	case erofsType:
714		// Add erofs properties
715		addStr("erofs_default_compressor", proptools.StringDefault(f.properties.Erofs.Compressor, "lz4hc,9"))
716		if f.properties.Erofs.Compress_hints != nil {
717			src := android.PathForModuleSrc(ctx, *f.properties.Erofs.Compress_hints)
718			addPath("erofs_default_compress_hints", src)
719		}
720		if proptools.BoolDefault(f.properties.Erofs.Sparse, true) {
721			// https://source.corp.google.com/h/googleplex-android/platform/build/+/88b1c67239ca545b11580237242774b411f2fed9:core/Makefile;l=2292;bpv=1;bpt=0;drc=ea8f34bc1d6e63656b4ec32f2391e9d54b3ebb6b
722			addStr("erofs_sparse_flag", "-s")
723		}
724	case f2fsType:
725		if proptools.BoolDefault(f.properties.F2fs.Sparse, true) {
726			// https://source.corp.google.com/h/googleplex-android/platform/build/+/88b1c67239ca545b11580237242774b411f2fed9:core/Makefile;l=2294;drc=ea8f34bc1d6e63656b4ec32f2391e9d54b3ebb6b;bpv=1;bpt=0
727			addStr("f2fs_sparse_flag", "-S")
728		}
729	}
730	f.checkFsTypePropertyError(ctx, fst, fsTypeStr(fst))
731
732	propFilePreProcessing := android.PathForModuleOut(ctx, "prop_pre_processing")
733	android.WriteFileRuleVerbatim(ctx, propFilePreProcessing, propFileString.String())
734	propFile := android.PathForModuleOut(ctx, "prop")
735	ctx.Build(pctx, android.BuildParams{
736		Rule:     textFileProcessorRule,
737		Input:    propFilePreProcessing,
738		Output:   propFile,
739		Implicit: ctx.Config().BuildFingerprintFile(ctx),
740	})
741	return propFile, deps
742}
743
744// This method checks if there is any property set for the fstype(s) other than
745// the current fstype.
746func (f *filesystem) checkFsTypePropertyError(ctx android.ModuleContext, t fsType, fs string) {
747	raiseError := func(otherFsType, currentFsType string) {
748		errMsg := fmt.Sprintf("%s is non-empty, but FS type is %s\n. Please delete %s properties if this partition should use %s\n", otherFsType, currentFsType, otherFsType, currentFsType)
749		ctx.PropertyErrorf(otherFsType, errMsg)
750	}
751
752	if t != erofsType {
753		if f.properties.Erofs.Compressor != nil || f.properties.Erofs.Compress_hints != nil || f.properties.Erofs.Sparse != nil {
754			raiseError("erofs", fs)
755		}
756	}
757	if t != f2fsType {
758		if f.properties.F2fs.Sparse != nil {
759			raiseError("f2fs", fs)
760		}
761	}
762}
763
764func (f *filesystem) buildCpioImage(ctx android.ModuleContext, compressed bool) android.Path {
765	if proptools.Bool(f.properties.Use_avb) {
766		ctx.PropertyErrorf("use_avb", "signing compresed cpio image using avbtool is not supported."+
767			"Consider adding this to bootimg module and signing the entire boot image.")
768	}
769
770	if proptools.String(f.properties.File_contexts) != "" {
771		ctx.PropertyErrorf("file_contexts", "file_contexts is not supported for compressed cpio image.")
772	}
773
774	if f.properties.Include_make_built_files != "" {
775		ctx.PropertyErrorf("include_make_built_files", "include_make_built_files is not supported for compressed cpio image.")
776	}
777
778	rootDir := android.PathForModuleOut(ctx, "root").OutputPath
779	rebasedDir := rootDir
780	if f.properties.Base_dir != nil {
781		rebasedDir = rootDir.Join(ctx, *f.properties.Base_dir)
782	}
783	builder := android.NewRuleBuilder(pctx, ctx)
784	// Wipe the root dir to get rid of leftover files from prior builds
785	builder.Command().Textf("rm -rf %s && mkdir -p %s", rootDir, rootDir)
786	specs := f.gatherFilteredPackagingSpecs(ctx)
787	f.entries = f.copyPackagingSpecs(ctx, builder, specs, rootDir, rebasedDir)
788
789	f.buildNonDepsFiles(ctx, builder, rootDir)
790	f.buildFsverityMetadataFiles(ctx, builder, specs, rootDir, rebasedDir)
791	f.buildEventLogtagsFile(ctx, builder, rebasedDir)
792	f.buildAconfigFlagsFiles(ctx, builder, specs, rebasedDir)
793	f.filesystemBuilder.BuildLinkerConfigFile(ctx, builder, rebasedDir)
794	f.copyFilesToProductOut(ctx, builder, rebasedDir)
795
796	output := android.PathForModuleOut(ctx, f.installFileName())
797	cmd := builder.Command().
798		BuiltTool("mkbootfs").
799		Text(rootDir.String()) // input directory
800	if nodeList := f.properties.Dev_nodes_description_file; nodeList != nil {
801		cmd.FlagWithInput("-n ", android.PathForModuleSrc(ctx, proptools.String(nodeList)))
802	}
803	if compressed {
804		cmd.Text("|").
805			BuiltTool("lz4").
806			Flag("--favor-decSpeed"). // for faster boot
807			Flag("-12").              // maximum compression level
808			Flag("-l").               // legacy format for kernel
809			Text(">").Output(output)
810	} else {
811		cmd.Text(">").Output(output)
812	}
813
814	// rootDir is not deleted. Might be useful for quick inspection.
815	builder.Build("build_cpio_image", fmt.Sprintf("Creating filesystem %s", f.BaseModuleName()))
816
817	return output
818}
819
820var validPartitions = []string{
821	"system",
822	"userdata",
823	"cache",
824	"system_other",
825	"vendor",
826	"product",
827	"system_ext",
828	"odm",
829	"vendor_dlkm",
830	"odm_dlkm",
831	"system_dlkm",
832	"ramdisk",
833	"vendor_ramdisk",
834	"recovery",
835}
836
837func (f *filesystem) addMakeBuiltFiles(ctx android.ModuleContext, builder *android.RuleBuilder, rootDir android.Path) {
838	partition := f.properties.Include_make_built_files
839	if partition == "" {
840		return
841	}
842	if !slices.Contains(validPartitions, partition) {
843		ctx.PropertyErrorf("include_make_built_files", "Expected one of %#v, found %q", validPartitions, partition)
844		return
845	}
846	stampFile := fmt.Sprintf("target/product/%s/obj/PACKAGING/%s_intermediates/staging_dir.stamp", ctx.Config().DeviceName(), partition)
847	fileListFile := fmt.Sprintf("target/product/%s/obj/PACKAGING/%s_intermediates/file_list.txt", ctx.Config().DeviceName(), partition)
848	stagingDir := fmt.Sprintf("target/product/%s/%s", ctx.Config().DeviceName(), partition)
849
850	builder.Command().BuiltTool("merge_directories").
851		Implicit(android.PathForArbitraryOutput(ctx, stampFile)).
852		Text("--ignore-duplicates").
853		FlagWithInput("--file-list", android.PathForArbitraryOutput(ctx, fileListFile)).
854		Text(rootDir.String()).
855		Text(android.PathForArbitraryOutput(ctx, stagingDir).String())
856}
857
858func (f *filesystem) buildEventLogtagsFile(ctx android.ModuleContext, builder *android.RuleBuilder, rebasedDir android.OutputPath) {
859	if !proptools.Bool(f.properties.Build_logtags) {
860		return
861	}
862
863	logtagsFilePaths := make(map[string]bool)
864	ctx.WalkDeps(func(child, parent android.Module) bool {
865		if logtagsInfo, ok := android.OtherModuleProvider(ctx, child, android.LogtagsProviderKey); ok {
866			for _, path := range logtagsInfo.Logtags {
867				logtagsFilePaths[path.String()] = true
868			}
869		}
870		return true
871	})
872
873	if len(logtagsFilePaths) == 0 {
874		return
875	}
876
877	etcPath := rebasedDir.Join(ctx, "etc")
878	eventLogtagsPath := etcPath.Join(ctx, "event-log-tags")
879	builder.Command().Text("mkdir").Flag("-p").Text(etcPath.String())
880	cmd := builder.Command().BuiltTool("merge-event-log-tags").
881		FlagWithArg("-o ", eventLogtagsPath.String()).
882		FlagWithInput("-m ", android.MergedLogtagsPath(ctx))
883
884	for _, path := range android.SortedKeys(logtagsFilePaths) {
885		cmd.Text(path)
886	}
887
888	f.appendToEntry(ctx, eventLogtagsPath)
889}
890
891func (f *filesystem) BuildLinkerConfigFile(ctx android.ModuleContext, builder *android.RuleBuilder, rebasedDir android.OutputPath) {
892	if !proptools.Bool(f.properties.Linker_config.Gen_linker_config) {
893		return
894	}
895
896	provideModules, _ := f.getLibsForLinkerConfig(ctx)
897	output := rebasedDir.Join(ctx, "etc", "linker.config.pb")
898	linkerconfig.BuildLinkerConfig(ctx, builder, android.PathsForModuleSrc(ctx, f.properties.Linker_config.Linker_config_srcs), provideModules, nil, output)
899
900	f.appendToEntry(ctx, output)
901}
902
903func (f *filesystem) ShouldUseVintfFragmentModuleOnly() bool {
904	return false
905}
906
907type partition interface {
908	PartitionType() string
909}
910
911func (f *filesystem) PartitionType() string {
912	return proptools.StringDefault(f.properties.Partition_type, "system")
913}
914
915var _ partition = (*filesystem)(nil)
916
917var _ android.AndroidMkEntriesProvider = (*filesystem)(nil)
918
919// Implements android.AndroidMkEntriesProvider
920func (f *filesystem) AndroidMkEntries() []android.AndroidMkEntries {
921	return []android.AndroidMkEntries{android.AndroidMkEntries{
922		Class:      "ETC",
923		OutputFile: android.OptionalPathForPath(f.output),
924		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
925			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
926				entries.SetString("LOCAL_MODULE_PATH", f.installDir.String())
927				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", f.installFileName())
928				entries.SetString("LOCAL_FILESYSTEM_FILELIST", f.fileListFile.String())
929			},
930		},
931	}}
932}
933
934// Filesystem is the public interface for the filesystem struct. Currently, it's only for the apex
935// package to have access to the output file.
936type Filesystem interface {
937	android.Module
938	OutputPath() android.Path
939
940	// Returns the output file that is signed by avbtool. If this module is not signed, returns
941	// nil.
942	SignedOutputPath() android.Path
943}
944
945var _ Filesystem = (*filesystem)(nil)
946
947func (f *filesystem) OutputPath() android.Path {
948	return f.output
949}
950
951func (f *filesystem) SignedOutputPath() android.Path {
952	if proptools.Bool(f.properties.Use_avb) {
953		return f.OutputPath()
954	}
955	return nil
956}
957
958// Filter the result of GatherPackagingSpecs to discard items targeting outside "system" partition.
959// Note that "apex" module installs its contents to "apex"(fake partition) as well
960// for symbol lookup by imitating "activated" paths.
961func (f *filesystem) gatherFilteredPackagingSpecs(ctx android.ModuleContext) map[string]android.PackagingSpec {
962	specs := f.PackagingBase.GatherPackagingSpecsWithFilterAndModifier(ctx, f.filesystemBuilder.FilterPackagingSpec, f.filesystemBuilder.ModifyPackagingSpec)
963	return specs
964}
965
966func sha1sum(values []string) string {
967	h := sha256.New()
968	for _, value := range values {
969		io.WriteString(h, value)
970	}
971	return fmt.Sprintf("%x", h.Sum(nil))
972}
973
974// Base cc.UseCoverage
975
976var _ cc.UseCoverage = (*filesystem)(nil)
977
978func (*filesystem) IsNativeCoverageNeeded(ctx cc.IsNativeCoverageNeededContext) bool {
979	return ctx.Device() && ctx.DeviceConfig().NativeCoverageEnabled()
980}
981
982// android_filesystem_defaults
983
984type filesystemDefaults struct {
985	android.ModuleBase
986	android.DefaultsModuleBase
987
988	properties FilesystemProperties
989}
990
991// android_filesystem_defaults is a default module for android_filesystem and android_system_image
992func filesystemDefaultsFactory() android.Module {
993	module := &filesystemDefaults{}
994	module.AddProperties(&module.properties)
995	module.AddProperties(&android.PackagingProperties{})
996	android.InitDefaultsModule(module)
997	return module
998}
999
1000func (f *filesystemDefaults) PartitionType() string {
1001	return proptools.StringDefault(f.properties.Partition_type, "system")
1002}
1003
1004var _ partition = (*filesystemDefaults)(nil)
1005
1006func (f *filesystemDefaults) GenerateAndroidBuildActions(ctx android.ModuleContext) {
1007	validatePartitionType(ctx, f)
1008}
1009
1010// getLibsForLinkerConfig returns
1011// 1. A list of libraries installed in this filesystem
1012// 2. A list of dep libraries _not_ installed in this filesystem
1013//
1014// `linkerconfig.BuildLinkerConfig` will convert these two to a linker.config.pb for the filesystem
1015// (1) will be added to --provideLibs if they are C libraries with a stable interface (has stubs)
1016// (2) will be added to --requireLibs if they are C libraries with a stable interface (has stubs)
1017func (f *filesystem) getLibsForLinkerConfig(ctx android.ModuleContext) ([]android.Module, []android.Module) {
1018	// we need "Module"s for packaging items
1019	modulesInPackageByModule := make(map[android.Module]bool)
1020	modulesInPackageByName := make(map[string]bool)
1021
1022	deps := f.gatherFilteredPackagingSpecs(ctx)
1023	ctx.WalkDeps(func(child, parent android.Module) bool {
1024		for _, ps := range android.OtherModuleProviderOrDefault(
1025			ctx, child, android.InstallFilesProvider).PackagingSpecs {
1026			if _, ok := deps[ps.RelPathInPackage()]; ok && ps.Partition() == f.PartitionType() {
1027				modulesInPackageByModule[child] = true
1028				modulesInPackageByName[child.Name()] = true
1029				return true
1030			}
1031		}
1032		return true
1033	})
1034
1035	provideModules := make([]android.Module, 0, len(modulesInPackageByModule))
1036	for mod := range modulesInPackageByModule {
1037		provideModules = append(provideModules, mod)
1038	}
1039
1040	var requireModules []android.Module
1041	ctx.WalkDeps(func(child, parent android.Module) bool {
1042		_, parentInPackage := modulesInPackageByModule[parent]
1043		_, childInPackageName := modulesInPackageByName[child.Name()]
1044
1045		// When parent is in the package, and child (or its variant) is not, this can be from an interface.
1046		if parentInPackage && !childInPackageName {
1047			requireModules = append(requireModules, child)
1048		}
1049		return true
1050	})
1051
1052	return provideModules, requireModules
1053}
1054
1055// Checks that the given file doesn't exceed the given size, and will also print a warning
1056// if it's nearing the maximum size. Equivalent to assert-max-image-size in make:
1057// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/definitions.mk;l=3455;drc=993c4de29a02a6accd60ceaaee153307e1a18d10
1058func assertMaxImageSize(builder *android.RuleBuilder, image android.Path, maxSize int64, addAvbLater bool) {
1059	if addAvbLater {
1060		// The value 69632 is derived from MAX_VBMETA_SIZE + MAX_FOOTER_SIZE in avbtool.
1061		// Logic copied from make:
1062		// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=228;drc=a6a0007ef24e16c0b79f439beac4a118416717e6
1063		maxSize -= 69632
1064	}
1065	cmd := builder.Command()
1066	cmd.Textf(`file="%s"; maxsize="%d";`+
1067		`total=$(stat -c "%%s" "$file" | tr -d '\n');`+
1068		`if [ "$total" -gt "$maxsize" ]; then `+
1069		`  echo "error: $file too large ($total > $maxsize)";`+
1070		`  false;`+
1071		`elif [ "$total" -gt $((maxsize - 32768)) ]; then `+
1072		`  echo "WARNING: $file approaching size limit ($total now; limit $maxsize)";`+
1073		`fi`,
1074		image, maxSize)
1075	cmd.Implicit(image)
1076}
1077
1078// addAutogeneratedRroDeps walks the transitive closure of vendor and product partitions.
1079// It visits apps installed in system and system_ext partitions, and adds the autogenerated
1080// RRO modules to its own deps.
1081func addAutogeneratedRroDeps(ctx android.BottomUpMutatorContext) {
1082	f, ok := ctx.Module().(*filesystem)
1083	if !ok {
1084		return
1085	}
1086	thisPartition := f.PartitionType()
1087	if thisPartition != "vendor" && thisPartition != "product" {
1088		return
1089	}
1090	ctx.WalkDeps(func(child, parent android.Module) bool {
1091		depTag := ctx.OtherModuleDependencyTag(child)
1092		if parent.Name() == f.Name() && depTag != interPartitionDependencyTag {
1093			return false // This is a module listed in deps of vendor/product filesystem
1094		}
1095		if vendorOverlay := java.AutogeneratedRroModuleName(ctx, child.Name(), "vendor"); ctx.OtherModuleExists(vendorOverlay) && thisPartition == "vendor" {
1096			ctx.AddFarVariationDependencies(nil, dependencyTagWithVisibilityEnforcementBypass, vendorOverlay)
1097		}
1098		if productOverlay := java.AutogeneratedRroModuleName(ctx, child.Name(), "product"); ctx.OtherModuleExists(productOverlay) && thisPartition == "product" {
1099			ctx.AddFarVariationDependencies(nil, dependencyTagWithVisibilityEnforcementBypass, productOverlay)
1100		}
1101		return true
1102	})
1103}
1104