diff options
Diffstat (limited to 'filesystem/filesystem.go')
-rw-r--r-- | filesystem/filesystem.go | 344 |
1 files changed, 321 insertions, 23 deletions
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go index c6181bc15..cf9871701 100644 --- a/filesystem/filesystem.go +++ b/filesystem/filesystem.go @@ -16,24 +16,70 @@ package filesystem import ( "fmt" + "path/filepath" + "strings" "android/soong/android" "github.com/google/blueprint" + "github.com/google/blueprint/proptools" ) func init() { - android.RegisterModuleType("android_filesystem", filesystemFactory) + registerBuildComponents(android.InitRegistrationContext) +} + +func registerBuildComponents(ctx android.RegistrationContext) { + ctx.RegisterModuleType("android_filesystem", filesystemFactory) } type filesystem struct { android.ModuleBase android.PackagingBase + properties filesystemProperties + output android.OutputPath installDir android.InstallPath } +type symlinkDefinition struct { + Target *string + Name *string +} + +type filesystemProperties struct { + // When set to true, sign the image with avbtool. Default is false. + Use_avb *bool + + // Path to the private key that avbtool will use to sign this filesystem image. + // TODO(jiyong): allow apex_key to be specified here + Avb_private_key *string `android:"path"` + + // Hash and signing algorithm for avbtool. Default is SHA256_RSA4096. + Avb_algorithm *string + + // Name of the partition stored in vbmeta desc. Defaults to the name of this module. + Partition_name *string + + // Type of the filesystem. Currently, ext4, cpio, and compressed_cpio are supported. Default + // is ext4. + Type *string + + // file_contexts file to make image. Currently, only ext4 is supported. + File_contexts *string `android:"path"` + + // Base directory relative to root, to which deps are installed, e.g. "system". Default is "." + // (root). + Base_dir *string + + // Directories to be created under root. e.g. /dev, /proc, etc. + Dirs []string + + // Symbolic links to be created under root with "ln -sf <target> <name>". + Symlinks []symlinkDefinition +} + // android_filesystem packages a set of modules and their transitive dependencies into a filesystem // image. The filesystem images are expected to be mounted in the target device, which means the // modules in the filesystem image are built for the target device (i.e. Android, not Linux host). @@ -41,17 +87,45 @@ type filesystem struct { // partitions like system.img. For example, cc_library modules are placed under ./lib[64] directory. func filesystemFactory() android.Module { module := &filesystem{} + module.AddProperties(&module.properties) android.InitPackageModule(module) android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon) return module } -var dependencyTag = struct{ blueprint.BaseDependencyTag }{} +var dependencyTag = struct { + blueprint.BaseDependencyTag + android.PackagingItemAlwaysDepTag +}{} func (f *filesystem) DepsMutator(ctx android.BottomUpMutatorContext) { f.AddDeps(ctx, dependencyTag) } +type fsType int + +const ( + ext4Type fsType = iota + compressedCpioType + cpioType // uncompressed + unknown +) + +func (f *filesystem) fsType(ctx android.ModuleContext) fsType { + typeStr := proptools.StringDefault(f.properties.Type, "ext4") + switch typeStr { + case "ext4": + return ext4Type + case "compressed_cpio": + return compressedCpioType + case "cpio": + return cpioType + default: + ctx.PropertyErrorf("type", "%q not supported", typeStr) + return unknown + } +} + func (f *filesystem) installFileName() string { return f.BaseModuleName() + ".img" } @@ -59,39 +133,229 @@ func (f *filesystem) installFileName() string { var pctx = android.NewPackageContext("android/soong/filesystem") func (f *filesystem) GenerateAndroidBuildActions(ctx android.ModuleContext) { - zipFile := android.PathForModuleOut(ctx, "temp.zip").OutputPath - f.CopyDepsToZip(ctx, zipFile) + switch f.fsType(ctx) { + case ext4Type: + f.output = f.buildImageUsingBuildImage(ctx) + case compressedCpioType: + f.output = f.buildCpioImage(ctx, true) + case cpioType: + f.output = f.buildCpioImage(ctx, false) + default: + return + } - rootDir := android.PathForModuleOut(ctx, "root").OutputPath + f.installDir = android.PathForModuleInstall(ctx, "etc") + ctx.InstallFile(f.installDir, f.installFileName(), f.output) +} + +// root zip will contain stuffs like dirs or symlinks. +func (f *filesystem) buildRootZip(ctx android.ModuleContext) android.OutputPath { + rootDir := android.PathForModuleGen(ctx, "root").OutputPath builder := android.NewRuleBuilder(pctx, ctx) + builder.Command().Text("rm -rf").Text(rootDir.String()) + builder.Command().Text("mkdir -p").Text(rootDir.String()) + + // create dirs and symlinks + for _, dir := range f.properties.Dirs { + // OutputPath.Join verifies dir + builder.Command().Text("mkdir -p").Text(rootDir.Join(ctx, dir).String()) + } + + for _, symlink := range f.properties.Symlinks { + name := strings.TrimSpace(proptools.String(symlink.Name)) + target := strings.TrimSpace(proptools.String(symlink.Target)) + + if name == "" { + ctx.PropertyErrorf("symlinks", "Name can't be empty") + continue + } + + if target == "" { + ctx.PropertyErrorf("symlinks", "Target can't be empty") + continue + } + + // OutputPath.Join verifies name. don't need to verify target. + dst := rootDir.Join(ctx, name) + + builder.Command().Text("mkdir -p").Text(filepath.Dir(dst.String())) + builder.Command().Text("ln -sf").Text(proptools.ShellEscape(target)).Text(dst.String()) + } + + zipOut := android.PathForModuleGen(ctx, "root.zip").OutputPath + + builder.Command(). + BuiltTool("soong_zip"). + FlagWithOutput("-o ", zipOut). + FlagWithArg("-C ", rootDir.String()). + Flag("-L 0"). // no compression because this will be unzipped soon + FlagWithArg("-D ", rootDir.String()). + Flag("-d") // include empty directories + builder.Command().Text("rm -rf").Text(rootDir.String()) + + builder.Build("zip_root", fmt.Sprintf("zipping root contents for %s", ctx.ModuleName())) + return zipOut +} + +func (f *filesystem) buildImageUsingBuildImage(ctx android.ModuleContext) android.OutputPath { + depsZipFile := android.PathForModuleOut(ctx, "deps.zip").OutputPath + f.CopyDepsToZip(ctx, depsZipFile) + + builder := android.NewRuleBuilder(pctx, ctx) + depsBase := proptools.StringDefault(f.properties.Base_dir, ".") + rebasedDepsZip := android.PathForModuleOut(ctx, "rebased_deps.zip").OutputPath + builder.Command(). + BuiltTool("zip2zip"). + FlagWithInput("-i ", depsZipFile). + FlagWithOutput("-o ", rebasedDepsZip). + Text("**/*:" + proptools.ShellEscape(depsBase)) // zip2zip verifies depsBase + + rootDir := android.PathForModuleOut(ctx, "root").OutputPath + rootZip := f.buildRootZip(ctx) builder.Command(). BuiltTool("zipsync"). FlagWithArg("-d ", rootDir.String()). // zipsync wipes this. No need to clear. - Input(zipFile) - - mkuserimg := ctx.Config().HostToolPath(ctx, "mkuserimg_mke2fs") - propFile := android.PathForModuleOut(ctx, "prop").OutputPath - // TODO(jiyong): support more filesystem types other than ext4 - propsText := fmt.Sprintf(`mount_point=system\n`+ - `fs_type=ext4\n`+ - `use_dynamic_partition_size=true\n`+ - `ext_mkuserimg=%s\n`, mkuserimg.String()) - builder.Command().Text("echo").Flag("-e").Flag(`"` + propsText + `"`). - Text(">").Output(propFile). - Implicit(mkuserimg) - - f.output = android.PathForModuleOut(ctx, "filesystem.img").OutputPath + Input(rootZip). + Input(rebasedDepsZip) + + propFile, toolDeps := f.buildPropFile(ctx) + output := android.PathForModuleOut(ctx, f.installFileName()).OutputPath builder.Command().BuiltTool("build_image"). Text(rootDir.String()). // input directory Input(propFile). - Output(f.output). + Implicits(toolDeps). + Output(output). Text(rootDir.String()) // directory where to find fs_config_files|dirs // rootDir is not deleted. Might be useful for quick inspection. builder.Build("build_filesystem_image", fmt.Sprintf("Creating filesystem %s", f.BaseModuleName())) - f.installDir = android.PathForModuleInstall(ctx, "etc") - ctx.InstallFile(f.installDir, f.installFileName(), f.output) + return output +} + +func (f *filesystem) buildFileContexts(ctx android.ModuleContext) android.OutputPath { + builder := android.NewRuleBuilder(pctx, ctx) + fcBin := android.PathForModuleOut(ctx, "file_contexts.bin") + builder.Command().BuiltTool("sefcontext_compile"). + FlagWithOutput("-o ", fcBin). + Input(android.PathForModuleSrc(ctx, proptools.String(f.properties.File_contexts))) + builder.Build("build_filesystem_file_contexts", fmt.Sprintf("Creating filesystem file contexts for %s", f.BaseModuleName())) + return fcBin.OutputPath +} + +func (f *filesystem) buildPropFile(ctx android.ModuleContext) (propFile android.OutputPath, toolDeps android.Paths) { + type prop struct { + name string + value string + } + + var props []prop + var deps android.Paths + addStr := func(name string, value string) { + props = append(props, prop{name, value}) + } + addPath := func(name string, path android.Path) { + props = append(props, prop{name, path.String()}) + deps = append(deps, path) + } + + // Type string that build_image.py accepts. + fsTypeStr := func(t fsType) string { + switch t { + // TODO(jiyong): add more types like f2fs, erofs, etc. + case ext4Type: + return "ext4" + } + panic(fmt.Errorf("unsupported fs type %v", t)) + } + + addStr("fs_type", fsTypeStr(f.fsType(ctx))) + addStr("mount_point", "/") + addStr("use_dynamic_partition_size", "true") + addPath("ext_mkuserimg", ctx.Config().HostToolPath(ctx, "mkuserimg_mke2fs")) + // b/177813163 deps of the host tools have to be added. Remove this. + for _, t := range []string{"mke2fs", "e2fsdroid", "tune2fs"} { + deps = append(deps, ctx.Config().HostToolPath(ctx, t)) + } + + if proptools.Bool(f.properties.Use_avb) { + addStr("avb_hashtree_enable", "true") + addPath("avb_avbtool", ctx.Config().HostToolPath(ctx, "avbtool")) + algorithm := proptools.StringDefault(f.properties.Avb_algorithm, "SHA256_RSA4096") + addStr("avb_algorithm", algorithm) + key := android.PathForModuleSrc(ctx, proptools.String(f.properties.Avb_private_key)) + addPath("avb_key_path", key) + addStr("avb_add_hashtree_footer_args", "--do_not_generate_fec") + partitionName := proptools.StringDefault(f.properties.Partition_name, f.Name()) + addStr("partition_name", partitionName) + } + + if proptools.String(f.properties.File_contexts) != "" { + addPath("selinux_fc", f.buildFileContexts(ctx)) + } + + propFile = android.PathForModuleOut(ctx, "prop").OutputPath + builder := android.NewRuleBuilder(pctx, ctx) + builder.Command().Text("rm").Flag("-rf").Output(propFile) + for _, p := range props { + builder.Command(). + Text("echo"). + Flag(`"` + p.name + "=" + p.value + `"`). + Text(">>").Output(propFile) + } + builder.Build("build_filesystem_prop", fmt.Sprintf("Creating filesystem props for %s", f.BaseModuleName())) + return propFile, deps +} + +func (f *filesystem) buildCpioImage(ctx android.ModuleContext, compressed bool) 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.") + } + + if proptools.String(f.properties.File_contexts) != "" { + ctx.PropertyErrorf("file_contexts", "file_contexts is not supported for compressed cpio image.") + } + + depsZipFile := android.PathForModuleOut(ctx, "deps.zip").OutputPath + f.CopyDepsToZip(ctx, depsZipFile) + + builder := android.NewRuleBuilder(pctx, ctx) + depsBase := proptools.StringDefault(f.properties.Base_dir, ".") + rebasedDepsZip := android.PathForModuleOut(ctx, "rebased_deps.zip").OutputPath + builder.Command(). + BuiltTool("zip2zip"). + FlagWithInput("-i ", depsZipFile). + FlagWithOutput("-o ", rebasedDepsZip). + Text("**/*:" + proptools.ShellEscape(depsBase)) // zip2zip verifies depsBase + + rootDir := android.PathForModuleOut(ctx, "root").OutputPath + rootZip := f.buildRootZip(ctx) + builder.Command(). + BuiltTool("zipsync"). + FlagWithArg("-d ", rootDir.String()). // zipsync wipes this. No need to clear. + Input(rootZip). + Input(rebasedDepsZip) + + output := android.PathForModuleOut(ctx, f.installFileName()).OutputPath + cmd := builder.Command(). + BuiltTool("mkbootfs"). + Text(rootDir.String()) // input directory + if compressed { + cmd.Text("|"). + BuiltTool("lz4"). + Flag("--favor-decSpeed"). // for faster boot + Flag("-12"). // maximum compression level + Flag("-l"). // legacy format for kernel + Text(">").Output(output) + } else { + cmd.Text(">").Output(output) + } + + // rootDir is not deleted. Might be useful for quick inspection. + builder.Build("build_cpio_image", fmt.Sprintf("Creating filesystem %s", f.BaseModuleName())) + + return output } var _ android.AndroidMkEntriesProvider = (*filesystem)(nil) @@ -102,10 +366,44 @@ func (f *filesystem) AndroidMkEntries() []android.AndroidMkEntries { Class: "ETC", OutputFile: android.OptionalPathForPath(f.output), ExtraEntries: []android.AndroidMkExtraEntriesFunc{ - func(entries *android.AndroidMkEntries) { + func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) { entries.SetString("LOCAL_MODULE_PATH", f.installDir.ToMakePath().String()) entries.SetString("LOCAL_INSTALLED_MODULE_STEM", f.installFileName()) }, }, }} } + +var _ android.OutputFileProducer = (*filesystem)(nil) + +// Implements android.OutputFileProducer +func (f *filesystem) OutputFiles(tag string) (android.Paths, error) { + if tag == "" { + return []android.Path{f.output}, nil + } + return nil, fmt.Errorf("unsupported module reference tag %q", tag) +} + +// Filesystem is the public interface for the filesystem struct. Currently, it's only for the apex +// package to have access to the output file. +type Filesystem interface { + android.Module + OutputPath() android.Path + + // Returns the output file that is signed by avbtool. If this module is not signed, returns + // nil. + SignedOutputPath() android.Path +} + +var _ Filesystem = (*filesystem)(nil) + +func (f *filesystem) OutputPath() android.Path { + return f.output +} + +func (f *filesystem) SignedOutputPath() android.Path { + if proptools.Bool(f.properties.Use_avb) { + return f.OutputPath() + } + return nil +} |