Generate $partition_filesystem_config.txt for target_files.zip

These files will be packaged into META subdir of target_files.zip

Some details
- This CL adds the filesystem_config.txt files for system, vendor, ...
  Some other partitions like system_other are currently missing
  (different module type)
- There is a subtle difference between the make and soong build rule. In
  the make rule, it uses $TARGET_OUT (all partitions) as the -D in `fs_config`. In the
  soong rule, this implementation passes the staging directory of the
  filesystem. This difference does not seem to have a difference on the
  final result.
- Some filesystem_config are currently missing/not bit-identical. These
  include boot, init_boot. For these, we actually want to use the
  configs from their ramdisk filesystems. I will do this in a followup
  CL

Test: diff'd target_files.zip
The following files are identical except NOTICE.xml.gz entry
- odm_dlkm, odm, product, system_dlkm, system_ext, vendor_dlkm, vendor

The following files are _not_ identical in names and/or contents
- boot_filesystem_config.txt
- root_filesystem_config.txt
- system_other_filesystem_config.txt
- vendor_boot_filesystem_config.txt

Bug: 388633394
Change-Id: I3a3997c0ffd8100c44b4a50a63bf0709a61d4472
diff --git a/filesystem/android_device.go b/filesystem/android_device.go
index 62c8658..38bfe6b 100644
--- a/filesystem/android_device.go
+++ b/filesystem/android_device.go
@@ -463,6 +463,29 @@
 			builder.Command().Textf("cp").Input(a.getFsInfos(ctx)["system"].SelinuxFc).Textf(" %s/META/file_contexts.bin", targetFilesDir.String())
 		}
 	}
+	// Copy $partition_filesystem_config.txt
+	fsInfos := a.getFsInfos(ctx)
+	for _, partition := range android.SortedKeys(fsInfos) {
+		if fsInfos[partition].FilesystemConfig == nil {
+			continue
+		}
+		if android.InList(partition, []string{"userdata"}) {
+			continue
+		}
+		builder.Command().Textf("cp").Input(fsInfos[partition].FilesystemConfig).Textf(" %s/META/%s", targetFilesDir.String(), a.filesystemConfigNameForTargetFiles(partition))
+	}
+}
+
+// Filenames for the partition specific fs_config files.
+// Hardcode the ramdisk files to their boot image prefix
+func (a *androidDevice) filesystemConfigNameForTargetFiles(partition string) string {
+	name := partition + "_filesystem_config.txt"
+	if partition == "system" {
+		name = "filesystem_config.txt"
+	} else if partition == "ramdisk" {
+		name = "init_boot_filesystem_config.txt"
+	}
+	return name
 }
 
 func (a *androidDevice) getFilesystemInfo(ctx android.ModuleContext, depName string) FilesystemInfo {
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index 0381951..dd48c5d 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -37,6 +37,7 @@
 	registerBuildComponents(android.InitRegistrationContext)
 	registerMutators(android.InitRegistrationContext)
 	pctx.HostBinToolVariable("fileslist", "fileslist")
+	pctx.HostBinToolVariable("fs_config", "fs_config")
 }
 
 func registerBuildComponents(ctx android.RegistrationContext) {
@@ -72,6 +73,10 @@
 		Command:     `build/make/tools/fileslist_util.py -c ${in} > ${out}`,
 		CommandDeps: []string{"build/make/tools/fileslist_util.py"},
 	})
+	fsConfigRule = pctx.AndroidStaticRule("fs_config_rule", blueprint.RuleParams{
+		Command:     `(cd ${rootDir}; find . -type d | sed 's,$$,/,'; find . \! -type d) | cut -c 3- | sort | sed 's,^,${prefix},' | ${fs_config} -C -D ${rootDir} -R "${prefix}" > ${out}`,
+		CommandDeps: []string{"${fs_config}"},
+	}, "rootDir", "prefix")
 )
 
 type filesystem struct {
@@ -421,6 +426,8 @@
 	ErofsCompressHints android.Path
 
 	SelinuxFc android.Path
+
+	FilesystemConfig android.Path
 }
 
 // FullInstallPathInfo contains information about the "full install" paths of all the files
@@ -665,6 +672,7 @@
 		},
 		ErofsCompressHints: erofsCompressHints,
 		SelinuxFc:          f.selinuxFc,
+		FilesystemConfig:   f.generateFilesystemConfig(ctx, rootDir, rebasedDir),
 	}
 
 	android.SetProvider(ctx, FilesystemProvider, fsInfo)
@@ -682,6 +690,30 @@
 	return android.PathForModuleOut(ctx, "staging_dir.timestamp")
 }
 
+func (f *filesystem) generateFilesystemConfig(ctx android.ModuleContext, rootDir android.Path, rebasedDir android.Path) android.Path {
+	rootDirString := rootDir.String()
+	prefix := f.partitionName() + "/"
+	if f.partitionName() == "system" {
+		rootDirString = rebasedDir.String()
+	}
+	if f.partitionName() == "ramdisk" || f.partitionName() == "recovery" {
+		// Hardcoded to match make behavior.
+		// https://cs.android.com/android/_/android/platform/build/+/2a0ef42a432d4da00201e8eb7697dcaa68fd2389:core/Makefile;l=6957-6962;drc=9ea8ad9232cef4d0a24d70133b1b9d2ce2defe5f;bpv=1;bpt=0
+		prefix = ""
+	}
+	out := android.PathForModuleOut(ctx, "filesystem_config.txt")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   fsConfigRule,
+		Input:  f.fileystemStagingDirTimestamp(ctx), // assemble the staging directory
+		Output: out,
+		Args: map[string]string{
+			"rootDir": rootDirString,
+			"prefix":  prefix,
+		},
+	})
+	return out
+}
+
 func (f *filesystem) setVbmetaPartitionProvider(ctx android.ModuleContext) {
 	var extractedPublicKey android.ModuleOutPath
 	if f.properties.Avb_private_key != nil {