// Copyright (C) 2018 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 selinux

// This file contains "se_cil_compat_map" module type used to build and install
// sepolicy backwards compatibility mapping files.

import (
	"android/soong/android"

	"github.com/google/blueprint"
	"github.com/google/blueprint/proptools"
)

var (
	combine_maps    = pctx.HostBinToolVariable("combine_maps", "combine_maps")
	combineMapsCmd  = "${combine_maps} -t ${topHalf} -b ${bottomHalf} -o $out"
	combineMapsRule = pctx.StaticRule(
		"combineMapsRule",
		blueprint.RuleParams{
			Command:     combineMapsCmd,
			CommandDeps: []string{"${combine_maps}"},
		},
		"topHalf",
		"bottomHalf",
	)

	String        = proptools.String
	TopHalfDepTag = dependencyTag{name: "top"}
)

func init() {
	android.RegisterModuleType("se_cil_compat_map", cilCompatMapFactory)
	pctx.Import("android/soong/android")
}

func cilCompatMapFactory() android.Module {
	c := &cilCompatMap{}
	c.AddProperties(&c.properties)
	android.InitAndroidArchModule(c, android.DeviceSupported, android.MultilibCommon)
	return c
}

type cilCompatMapProperties struct {
	// se_cil_compat_map module representing a compatibility mapping file for
	// platform versions (x->y). Bottom half represents a mapping (y->z).
	// Together the halves are used to generate a (x->z) mapping.
	Top_half *string `android:"path"`
	// list of source (.cil) files used to build an the bottom half of sepolicy
	// compatibility mapping file. bottom_half may reference the outputs of
	// other modules that produce source files like genrule or filegroup using
	// the syntax ":module". srcs has to be non-empty.
	Bottom_half []string `android:"path"`
	// name of the output
	Stem *string
	// Target version that this module supports. This module will be ignored if platform sepolicy
	// version is same as this module's version.
	Version *string
}

type cilCompatMap struct {
	android.ModuleBase
	properties cilCompatMapProperties
	// (.intermediate) module output path as installation source.
	installSource android.OptionalPath
	installPath   android.InstallPath
}

type CilCompatMapGeneratorInfo struct {
	GeneratedMapFile android.OptionalPath
}

var CilCompatMapGeneratorInfoProvider = blueprint.NewProvider[CilCompatMapGeneratorInfo]()

func expandTopHalf(ctx android.ModuleContext) android.OptionalPath {
	var topHalf android.OptionalPath
	ctx.VisitDirectDepsProxy(func(dep android.ModuleProxy) {
		depTag := ctx.OtherModuleDependencyTag(dep)
		switch depTag {
		case TopHalfDepTag:
			info := android.OtherModuleProviderOrDefault(ctx, dep, CilCompatMapGeneratorInfoProvider)
			topHalf = info.GeneratedMapFile
		}
	})
	return topHalf
}

func expandSeSources(ctx android.ModuleContext, srcFiles []string) android.Paths {
	return android.PathsForModuleSrc(ctx, srcFiles)
}

func (c *cilCompatMap) shouldSkipBuild(ctx android.ModuleContext) bool {
	return proptools.String(c.properties.Version) == ctx.DeviceConfig().PlatformSepolicyVersion()
}

func (c *cilCompatMap) stem() string {
	return proptools.StringDefault(c.properties.Stem, c.Name())
}

func (c *cilCompatMap) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	if c.shouldSkipBuild(ctx) {
		return
	}

	c.installPath = android.PathForModuleInstall(ctx, "etc", "selinux", "mapping")

	srcFiles := expandSeSources(ctx, c.properties.Bottom_half)

	for _, src := range srcFiles {
		if src.Ext() != ".cil" {
			ctx.PropertyErrorf("bottom_half", "%s has to be a .cil file.", src.String())
		}
	}

	bottomHalf := android.PathForModuleGen(ctx, "bottom_half")
	ctx.Build(pctx, android.BuildParams{
		Rule:   android.Cat,
		Output: bottomHalf,
		Inputs: srcFiles,
	})

	topHalf := expandTopHalf(ctx)
	if topHalf.Valid() {
		out := android.PathForModuleGen(ctx, c.Name())
		ctx.Build(pctx, android.BuildParams{
			Rule:   combineMapsRule,
			Output: out,
			Implicits: []android.Path{
				topHalf.Path(),
				bottomHalf,
			},
			Args: map[string]string{
				"topHalf":    topHalf.String(),
				"bottomHalf": bottomHalf.String(),
			},
		})
		c.installSource = android.OptionalPathForPath(out)
	} else {
		c.installSource = android.OptionalPathForPath(bottomHalf)
	}
	ctx.InstallFile(c.installPath, c.stem(), c.installSource.Path())

	if c.installSource.Valid() {
		ctx.SetOutputFiles(android.Paths{c.installSource.Path()}, "")
	}

	android.SetProvider(ctx, CilCompatMapGeneratorInfoProvider, CilCompatMapGeneratorInfo{
		GeneratedMapFile: c.installSource,
	})

	moduleInfoJSON := ctx.ModuleInfoJSON()
	moduleInfoJSON.Class = []string{"ETC"}
	moduleInfoJSON.SystemSharedLibs = []string{"none"}
}

func (c *cilCompatMap) DepsMutator(ctx android.BottomUpMutatorContext) {
	if c.properties.Top_half != nil {
		ctx.AddDependency(c, TopHalfDepTag, String(c.properties.Top_half))
	}
}

func (c *cilCompatMap) AndroidMkEntries() []android.AndroidMkEntries {
	if !c.installSource.Valid() {
		return nil
	}
	return []android.AndroidMkEntries{android.AndroidMkEntries{
		Class:      "ETC",
		OutputFile: c.installSource,
		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
				entries.SetPath("LOCAL_MODULE_PATH", c.installPath)
				if c.properties.Stem != nil {
					entries.SetString("LOCAL_INSTALLED_MODULE_STEM", String(c.properties.Stem))
				}
			},
		},
	}}
}
