Merge changes I8c00d625,I7147dcbc into main

* changes:
  Make vendor_ramdisk partition install recovery partition files
  Define a tool and a static rule to calculate the hash of a directory
diff --git a/android/defs.go b/android/defs.go
index 9f3fb1e..c4e3b99 100644
--- a/android/defs.go
+++ b/android/defs.go
@@ -102,6 +102,18 @@
 			Description: "concatenate files to $out",
 		})
 
+	// Calculates the hash of a directory and writes to a file.
+	// Note that the directory to calculate the hash is intentionally not listed as an input,
+	// to prevent adding directory as a ninja dependency. Thus, an implicit dependency to a file
+	// is required.
+	WriteDirectoryHash = pctx.AndroidStaticRule("WriteDirectoryHash",
+		blueprint.RuleParams{
+			Command:     "rm -f $out && ${calculateDirectoryHash} $dir $out",
+			CommandDeps: []string{"${calculateDirectoryHash}"},
+			Description: "Calculates the hash of a directory and writes to $out",
+		}, "dir",
+	)
+
 	// Used only when USE_GOMA=true is set, to restrict non-goma jobs to the local parallelism value
 	localPool = blueprint.NewBuiltinPool("local_pool")
 
@@ -118,4 +130,6 @@
 	pctx.VariableFunc("RBEWrapper", func(ctx PackageVarContext) string {
 		return ctx.Config().RBEWrapper()
 	})
+
+	pctx.HostBinToolVariable("calculateDirectoryHash", "calculate_directory_hash")
 }
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index 2244aff..3f08648 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -155,6 +155,11 @@
 	// Directories to be created under root. e.g. /dev, /proc, etc.
 	Dirs proptools.Configurable[[]string]
 
+	// List of filesystem modules to include in creating the partition. The root directory of
+	// the provided filesystem modules are included in creating the partition.
+	// This is only supported for cpio and compressed cpio filesystem types.
+	Include_files_of []string
+
 	// Symbolic links to be created under root with "ln -sf <target> <name>".
 	Symlinks []SymlinkDefinition
 
@@ -286,6 +291,8 @@
 
 var interPartitionDependencyTag = interPartitionDepTag{}
 
+var interPartitionInstallDependencyTag = interPartitionDepTag{}
+
 var _ android.ExcludeFromVisibilityEnforcementTag = (*depTagWithVisibilityEnforcementBypass)(nil)
 
 func (t depTagWithVisibilityEnforcementBypass) ExcludeFromVisibilityEnforcement() {}
@@ -317,6 +324,9 @@
 	for _, partition := range f.properties.Import_aconfig_flags_from {
 		ctx.AddDependency(ctx.Module(), importAconfigDependencyTag, partition)
 	}
+	for _, partition := range f.properties.Include_files_of {
+		ctx.AddDependency(ctx.Module(), interPartitionInstallDependencyTag, partition)
+	}
 }
 
 type fsType int
@@ -337,6 +347,13 @@
 type FilesystemInfo struct {
 	// A text file containing the list of paths installed on the partition.
 	FileListFile android.Path
+
+	// Root directory of the installed partition
+	Rootdir android.Path
+
+	// A text file containing the hash value of the metadata and the content hashes
+	// of Rootdir
+	DirectoryHashFile android.Path
 }
 
 var FilesystemProvider = blueprint.NewProvider[FilesystemInfo]()
@@ -410,13 +427,19 @@
 	if f.filesystemBuilder.ShouldUseVintfFragmentModuleOnly() {
 		f.validateVintfFragments(ctx)
 	}
+
+	if len(f.properties.Include_files_of) > 0 && !android.InList(f.fsType(ctx), []fsType{compressedCpioType, cpioType}) {
+		ctx.PropertyErrorf("include_files_of", "include_files_of is only supported for cpio and compressed cpio filesystem types.")
+	}
+
+	var rootDir android.OutputPath
 	switch f.fsType(ctx) {
 	case ext4Type, erofsType, f2fsType:
-		f.output = f.buildImageUsingBuildImage(ctx)
+		f.output, rootDir = f.buildImageUsingBuildImage(ctx)
 	case compressedCpioType:
-		f.output = f.buildCpioImage(ctx, true)
+		f.output, rootDir = f.buildCpioImage(ctx, true)
 	case cpioType:
-		f.output = f.buildCpioImage(ctx, false)
+		f.output, rootDir = f.buildCpioImage(ctx, false)
 	default:
 		return
 	}
@@ -425,12 +448,29 @@
 	ctx.InstallFile(f.installDir, f.installFileName(), f.output)
 	ctx.SetOutputFiles([]android.Path{f.output}, "")
 
+	if f.partitionName() == "recovery" {
+		rootDir = rootDir.Join(ctx, "root")
+	}
+
+	rootDirHash := android.PathForModuleOut(ctx, "rootdir-hash.txt")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:     android.WriteDirectoryHash,
+		Output:   rootDirHash,
+		Implicit: f.output,
+		Args: map[string]string{
+			"dir": rootDir.String(),
+		},
+	})
+
 	fileListFile := android.PathForModuleOut(ctx, "fileList")
 	android.WriteFileRule(ctx, fileListFile, f.installedFilesList())
 
 	android.SetProvider(ctx, FilesystemProvider, FilesystemInfo{
-		FileListFile: fileListFile,
+		FileListFile:      fileListFile,
+		Rootdir:           rootDir,
+		DirectoryHashFile: rootDirHash,
 	})
+
 	f.fileListFile = fileListFile
 
 	if proptools.Bool(f.properties.Unchecked_module) {
@@ -576,7 +616,7 @@
 	return f.partitionName()
 }
 
-func (f *filesystem) buildImageUsingBuildImage(ctx android.ModuleContext) android.Path {
+func (f *filesystem) buildImageUsingBuildImage(ctx android.ModuleContext) (android.Path, android.OutputPath) {
 	rootDir := android.PathForModuleOut(ctx, f.rootDirString()).OutputPath
 	rebasedDir := rootDir
 	if f.properties.Base_dir != nil {
@@ -627,7 +667,7 @@
 	// rootDir is not deleted. Might be useful for quick inspection.
 	builder.Build("build_filesystem_image", fmt.Sprintf("Creating filesystem %s", f.BaseModuleName()))
 
-	return output
+	return output, rootDir
 }
 
 func (f *filesystem) buildFileContexts(ctx android.ModuleContext) android.Path {
@@ -789,7 +829,20 @@
 	}
 }
 
-func (f *filesystem) buildCpioImage(ctx android.ModuleContext, compressed bool) android.Path {
+func includeFilesRootDir(ctx android.ModuleContext) (rootDirs android.Paths, hashFiles android.Paths) {
+	ctx.VisitDirectDepsWithTag(interPartitionInstallDependencyTag, func(m android.Module) {
+		if fsProvider, ok := android.OtherModuleProvider(ctx, m, FilesystemProvider); ok {
+			rootDirs = append(rootDirs, fsProvider.Rootdir)
+			hashFiles = append(hashFiles, fsProvider.DirectoryHashFile)
+		} else {
+			ctx.PropertyErrorf("include_files_of", "only filesystem modules can be listed in "+
+				"include_files_of but %s is not a filesystem module", m.Name())
+		}
+	})
+	return rootDirs, hashFiles
+}
+
+func (f *filesystem) buildCpioImage(ctx android.ModuleContext, compressed bool) (android.Path, android.OutputPath) {
 	if proptools.Bool(f.properties.Use_avb) {
 		ctx.PropertyErrorf("use_avb", "signing compresed cpio image using avbtool is not supported."+
 			"Consider adding this to bootimg module and signing the entire boot image.")
@@ -821,10 +874,18 @@
 	f.filesystemBuilder.BuildLinkerConfigFile(ctx, builder, rebasedDir)
 	f.copyFilesToProductOut(ctx, builder, rebasedDir)
 
+	rootDirs, hashFiles := includeFilesRootDir(ctx)
+
 	output := android.PathForModuleOut(ctx, f.installFileName())
 	cmd := builder.Command().
 		BuiltTool("mkbootfs").
 		Text(rootDir.String()) // input directory
+
+	for i := range len(rootDirs) {
+		cmd.Text(rootDirs[i].String())
+	}
+	cmd.Implicits(hashFiles)
+
 	if nodeList := f.properties.Dev_nodes_description_file; nodeList != nil {
 		cmd.FlagWithInput("-n ", android.PathForModuleSrc(ctx, proptools.String(nodeList)))
 	}
@@ -842,7 +903,7 @@
 	// rootDir is not deleted. Might be useful for quick inspection.
 	builder.Build("build_cpio_image", fmt.Sprintf("Creating filesystem %s", f.BaseModuleName()))
 
-	return output
+	return output, rootDir
 }
 
 var validPartitions = []string{
diff --git a/fsgen/filesystem_creator.go b/fsgen/filesystem_creator.go
index 0868561..6474397 100644
--- a/fsgen/filesystem_creator.go
+++ b/fsgen/filesystem_creator.go
@@ -78,7 +78,7 @@
 	return module
 }
 
-func generatedPartitions(ctx android.LoadHookContext) []string {
+func generatedPartitions(ctx android.EarlyModuleContext) []string {
 	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
 	generatedPartitions := []string{"system"}
 	if ctx.DeviceConfig().SystemExtPath() == "system_ext" {
@@ -373,6 +373,10 @@
 		fsProps.Security_patch = proptools.StringPtr(partitionVars.VendorDlkmSecurityPatch)
 	case "odm_dlkm":
 		fsProps.Security_patch = proptools.StringPtr(partitionVars.OdmDlkmSecurityPatch)
+	case "vendor_ramdisk":
+		if android.InList("recovery", generatedPartitions(ctx)) {
+			fsProps.Include_files_of = []string{generatedModuleNameForPartition(ctx.Config(), "recovery")}
+		}
 	}
 }
 
diff --git a/scripts/Android.bp b/scripts/Android.bp
index d39c84a..49e6b73 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -327,3 +327,11 @@
         "rustc_linker.py",
     ],
 }
+
+python_binary_host {
+    name: "calculate_directory_hash",
+    main: "calculate_directory_hash.py",
+    srcs: [
+        "calculate_directory_hash.py",
+    ],
+}
diff --git a/scripts/calculate_directory_hash.py b/scripts/calculate_directory_hash.py
new file mode 100755
index 0000000..d4802d8
--- /dev/null
+++ b/scripts/calculate_directory_hash.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2024 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 tool for calculating the hash of a directory based on file contents and metadata."""
+
+import argparse
+import hashlib
+import os
+import stat
+
+def calculate_hash(directory: str) -> str:
+    """
+    Calculates the hash of a directory, including file contents and metadata.
+
+    Following informations are taken into consideration:
+    * Name: The file or directory name.
+    * File Type: Whether it's a regular file, directory, symbolic link, etc.
+    * Size: The size of the file in bytes.
+    * Permissions: The file's access permissions (read, write, execute).
+    * Content Hash (for files): The SHA-1 hash of the file's content.
+    """
+
+    output = []
+    for root, _, files in os.walk(directory):
+        for file in files:
+            filepath = os.path.join(root, file)
+            file_stat = os.lstat(filepath)
+            stat_info = f"{filepath} {stat.filemode(file_stat.st_mode)} {file_stat.st_size}"
+
+            if os.path.islink(filepath):
+                stat_info += os.readlink(filepath)
+            elif os.path.isfile(filepath):
+                with open(filepath, "rb") as f:
+                    file_hash = hashlib.sha1(f.read()).hexdigest()
+                stat_info += f" {file_hash}"
+
+            output.append(stat_info)
+
+    return hashlib.sha1("\n".join(sorted(output)).encode()).hexdigest()
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="Calculate the hash of a directory.")
+    parser.add_argument("directory", help="Path to the directory")
+    parser.add_argument("output_file", help="Path to the output file")
+    args = parser.parse_args()
+
+    hash_value = calculate_hash(args.directory)
+    with open(args.output_file, "w") as f:
+        f.write(hash_value)