| // Copyright (C) 2021 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. | 
 |  | 
 | package filesystem | 
 |  | 
 | import ( | 
 | 	"fmt" | 
 | 	"strconv" | 
 |  | 
 | 	"github.com/google/blueprint/proptools" | 
 |  | 
 | 	"android/soong/android" | 
 | ) | 
 |  | 
 | func init() { | 
 | 	android.RegisterModuleType("logical_partition", logicalPartitionFactory) | 
 | } | 
 |  | 
 | type logicalPartition struct { | 
 | 	android.ModuleBase | 
 |  | 
 | 	properties logicalPartitionProperties | 
 |  | 
 | 	output     android.OutputPath | 
 | 	installDir android.InstallPath | 
 | } | 
 |  | 
 | type logicalPartitionProperties struct { | 
 | 	// Set the name of the output. Defaults to <module_name>.img. | 
 | 	Stem *string | 
 |  | 
 | 	// Total size of the logical partition. If set to "auto", total size is automatically | 
 | 	// calcaulted as minimum. | 
 | 	Size *string | 
 |  | 
 | 	// List of partitions for default group. Default group has no size limit and automatically | 
 | 	// minimized when creating an image. | 
 | 	Default_group []partitionProperties | 
 |  | 
 | 	// List of groups. A group defines a fixed sized region. It can host one or more logical | 
 | 	// partitions and their total size is limited by the size of the group they are in. | 
 | 	Groups []groupProperties | 
 |  | 
 | 	// Whether the output is a sparse image or not. Default is false. | 
 | 	Sparse *bool | 
 | } | 
 |  | 
 | type groupProperties struct { | 
 | 	// Name of the partition group. Can't be "default"; use default_group instead. | 
 | 	Name *string | 
 |  | 
 | 	// Size of the partition group | 
 | 	Size *string | 
 |  | 
 | 	// List of logical partitions in this group | 
 | 	Partitions []partitionProperties | 
 | } | 
 |  | 
 | type partitionProperties struct { | 
 | 	// Name of the partition | 
 | 	Name *string | 
 |  | 
 | 	// Filesystem that is placed on the partition | 
 | 	Filesystem *string `android:"path"` | 
 | } | 
 |  | 
 | // logical_partition is a partition image which has one or more logical partitions in it. | 
 | func logicalPartitionFactory() android.Module { | 
 | 	module := &logicalPartition{} | 
 | 	module.AddProperties(&module.properties) | 
 | 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst) | 
 | 	return module | 
 | } | 
 |  | 
 | func (l *logicalPartition) DepsMutator(ctx android.BottomUpMutatorContext) { | 
 | 	// do nothing | 
 | } | 
 |  | 
 | func (l *logicalPartition) installFileName() string { | 
 | 	return proptools.StringDefault(l.properties.Stem, l.BaseModuleName()+".img") | 
 | } | 
 |  | 
 | func (l *logicalPartition) GenerateAndroidBuildActions(ctx android.ModuleContext) { | 
 | 	builder := android.NewRuleBuilder(pctx, ctx) | 
 |  | 
 | 	// Sparse the filesystem images and calculate their sizes | 
 | 	sparseImages := make(map[string]android.OutputPath) | 
 | 	sparseImageSizes := make(map[string]android.OutputPath) | 
 |  | 
 | 	sparsePartitions := func(partitions []partitionProperties) { | 
 | 		for _, part := range partitions { | 
 | 			sparseImg, sizeTxt := sparseFilesystem(ctx, part, builder) | 
 | 			pName := proptools.String(part.Name) | 
 | 			sparseImages[pName] = sparseImg | 
 | 			sparseImageSizes[pName] = sizeTxt | 
 | 		} | 
 | 	} | 
 |  | 
 | 	for _, group := range l.properties.Groups { | 
 | 		sparsePartitions(group.Partitions) | 
 | 	} | 
 |  | 
 | 	sparsePartitions(l.properties.Default_group) | 
 |  | 
 | 	cmd := builder.Command().BuiltTool("lpmake") | 
 |  | 
 | 	size := proptools.String(l.properties.Size) | 
 | 	if size == "" { | 
 | 		ctx.PropertyErrorf("size", "must be set") | 
 | 	} else if _, err := strconv.Atoi(size); err != nil && size != "auto" { | 
 | 		ctx.PropertyErrorf("size", `must be a number or "auto"`) | 
 | 	} | 
 | 	cmd.FlagWithArg("--device-size=", size) | 
 |  | 
 | 	// TODO(jiyong): consider supporting A/B devices. Then we need to adjust num of slots. | 
 | 	cmd.FlagWithArg("--metadata-slots=", "2") | 
 | 	cmd.FlagWithArg("--metadata-size=", "65536") | 
 |  | 
 | 	if proptools.Bool(l.properties.Sparse) { | 
 | 		cmd.Flag("--sparse") | 
 | 	} | 
 |  | 
 | 	groupNames := make(map[string]bool) | 
 | 	partitionNames := make(map[string]bool) | 
 |  | 
 | 	addPartitionsToGroup := func(partitions []partitionProperties, gName string) { | 
 | 		for _, part := range partitions { | 
 | 			pName := proptools.String(part.Name) | 
 | 			if pName == "" { | 
 | 				ctx.PropertyErrorf("groups.partitions.name", "must be set") | 
 | 			} | 
 | 			if _, ok := partitionNames[pName]; ok { | 
 | 				ctx.PropertyErrorf("groups.partitions.name", "already exists") | 
 | 			} else { | 
 | 				partitionNames[pName] = true | 
 | 			} | 
 | 			// Get size of the partition by reading the -size.txt file | 
 | 			var pSize string | 
 | 			if size, hasSize := sparseImageSizes[pName]; hasSize { | 
 | 				pSize = fmt.Sprintf("$(cat %s)", size) | 
 | 			} else { | 
 | 				pSize = "0" | 
 | 			} | 
 | 			cmd.FlagWithArg("--partition=", fmt.Sprintf("%s:readonly:%s:%s", pName, pSize, gName)) | 
 | 			if image, hasImage := sparseImages[pName]; hasImage { | 
 | 				cmd.FlagWithInput("--image="+pName+"=", image) | 
 | 			} | 
 | 		} | 
 | 	} | 
 |  | 
 | 	addPartitionsToGroup(l.properties.Default_group, "default") | 
 |  | 
 | 	for _, group := range l.properties.Groups { | 
 | 		gName := proptools.String(group.Name) | 
 | 		if gName == "" { | 
 | 			ctx.PropertyErrorf("groups.name", "must be set") | 
 | 		} else if gName == "default" { | 
 | 			ctx.PropertyErrorf("groups.name", `can't use "default" as a group name. Use default_group instead`) | 
 | 		} | 
 | 		if _, ok := groupNames[gName]; ok { | 
 | 			ctx.PropertyErrorf("group.name", "already exists") | 
 | 		} else { | 
 | 			groupNames[gName] = true | 
 | 		} | 
 | 		gSize := proptools.String(group.Size) | 
 | 		if gSize == "" { | 
 | 			ctx.PropertyErrorf("groups.size", "must be set") | 
 | 		} | 
 | 		if _, err := strconv.Atoi(gSize); err != nil { | 
 | 			ctx.PropertyErrorf("groups.size", "must be a number") | 
 | 		} | 
 | 		cmd.FlagWithArg("--group=", gName+":"+gSize) | 
 |  | 
 | 		addPartitionsToGroup(group.Partitions, gName) | 
 | 	} | 
 |  | 
 | 	l.output = android.PathForModuleOut(ctx, l.installFileName()).OutputPath | 
 | 	cmd.FlagWithOutput("--output=", l.output) | 
 |  | 
 | 	builder.Build("build_logical_partition", fmt.Sprintf("Creating %s", l.BaseModuleName())) | 
 |  | 
 | 	l.installDir = android.PathForModuleInstall(ctx, "etc") | 
 | 	ctx.InstallFile(l.installDir, l.installFileName(), l.output) | 
 |  | 
 | 	ctx.SetOutputFiles([]android.Path{l.output}, "") | 
 | } | 
 |  | 
 | // Add a rule that converts the filesystem for the given partition to the given rule builder. The | 
 | // path to the sparse file and the text file having the size of the partition are returned. | 
 | func sparseFilesystem(ctx android.ModuleContext, p partitionProperties, builder *android.RuleBuilder) (sparseImg android.OutputPath, sizeTxt android.OutputPath) { | 
 | 	if p.Filesystem == nil { | 
 | 		return | 
 | 	} | 
 | 	img := android.PathForModuleSrc(ctx, proptools.String(p.Filesystem)) | 
 | 	name := proptools.String(p.Name) | 
 | 	sparseImg = android.PathForModuleOut(ctx, name+".img").OutputPath | 
 |  | 
 | 	builder.Temporary(sparseImg) | 
 | 	builder.Command().BuiltTool("img2simg").Input(img).Output(sparseImg) | 
 |  | 
 | 	sizeTxt = android.PathForModuleOut(ctx, name+"-size.txt").OutputPath | 
 | 	builder.Temporary(sizeTxt) | 
 | 	builder.Command().BuiltTool("sparse_img").Flag("--get_partition_size").Input(sparseImg). | 
 | 		Text("| ").Text("tr").FlagWithArg("-d ", "'\n'"). | 
 | 		Text("> ").Output(sizeTxt) | 
 |  | 
 | 	return sparseImg, sizeTxt | 
 | } | 
 |  | 
 | var _ android.AndroidMkEntriesProvider = (*logicalPartition)(nil) | 
 |  | 
 | // Implements android.AndroidMkEntriesProvider | 
 | func (l *logicalPartition) AndroidMkEntries() []android.AndroidMkEntries { | 
 | 	return []android.AndroidMkEntries{android.AndroidMkEntries{ | 
 | 		Class:      "ETC", | 
 | 		OutputFile: android.OptionalPathForPath(l.output), | 
 | 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{ | 
 | 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) { | 
 | 				entries.SetString("LOCAL_MODULE_PATH", l.installDir.String()) | 
 | 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", l.installFileName()) | 
 | 			}, | 
 | 		}, | 
 | 	}} | 
 | } | 
 |  | 
 | var _ Filesystem = (*logicalPartition)(nil) | 
 |  | 
 | func (l *logicalPartition) OutputPath() android.Path { | 
 | 	return l.output | 
 | } | 
 |  | 
 | func (l *logicalPartition) SignedOutputPath() android.Path { | 
 | 	return nil // logical partition is not signed by itself | 
 | } |