1// Copyright 2021 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package selinux
16
17import (
18	"fmt"
19	"path/filepath"
20	"sort"
21	"strings"
22
23	"android/soong/android"
24)
25
26func init() {
27	android.RegisterModuleType("se_build_files", buildFilesFactory)
28}
29
30// se_build_files gathers policy files from sepolicy dirs, and acts like a filegroup. A tag with
31// partition(plat, system_ext, product) and scope(public, private) is used to select directories.
32// Supported tags are: "plat", "plat_public", "system_ext", "system_ext_public", "product",
33// "product_public", and "reqd_mask".
34func buildFilesFactory() android.Module {
35	module := &buildFiles{}
36	module.AddProperties(&module.properties)
37	android.InitAndroidModule(module)
38	return module
39}
40
41type buildFilesProperties struct {
42	// list of source file suffixes used to collect selinux policy files.
43	// Source files will be looked up in the following local directories:
44	// system/sepolicy/{public, private, vendor, reqd_mask}
45	// and directories specified by following config variables:
46	// BOARD_SEPOLICY_DIRS, BOARD_ODM_SEPOLICY_DIRS
47	// SYSTEM_EXT_PUBLIC_SEPOLICY_DIR, SYSTEM_EXT_PRIVATE_SEPOLICY_DIR
48	Srcs []string
49}
50
51type buildFiles struct {
52	android.ModuleBase
53	properties buildFilesProperties
54
55	srcs map[string]android.Paths
56}
57
58func (b *buildFiles) findSrcsInDirs(ctx android.ModuleContext, dirs ...string) android.Paths {
59	result := android.Paths{}
60	for _, file := range b.properties.Srcs {
61		for _, dir := range dirs {
62			path := filepath.Join(dir, file)
63			files, err := ctx.GlobWithDeps(path, nil)
64			if err != nil {
65				ctx.ModuleErrorf("glob: %s", err.Error())
66			}
67			for _, f := range files {
68				result = append(result, android.PathForSource(ctx, f))
69			}
70		}
71	}
72	return result
73}
74
75func (b *buildFiles) DepsMutator(ctx android.BottomUpMutatorContext) {
76	// do nothing
77}
78
79func (b *buildFiles) OutputFiles(tag string) (android.Paths, error) {
80	if paths, ok := b.srcs[tag]; ok {
81		return paths, nil
82	}
83
84	return nil, fmt.Errorf("unknown tag %q. Supported tags are: %q", tag, strings.Join(android.SortedStringKeys(b.srcs), " "))
85}
86
87var _ android.OutputFileProducer = (*buildFiles)(nil)
88
89type partition int
90
91const (
92	system partition = iota
93	system_ext
94	product
95)
96
97type scope int
98
99const (
100	public scope = iota
101	private
102)
103
104type sepolicyDir struct {
105	partition partition
106	scope     scope
107	paths     []string
108}
109
110func (p partition) String() string {
111	switch p {
112	case system:
113		return "plat"
114	case system_ext:
115		return "system_ext"
116	case product:
117		return "product"
118	default:
119		panic(fmt.Sprintf("Unknown partition %#v", p))
120	}
121}
122
123func (b *buildFiles) GenerateAndroidBuildActions(ctx android.ModuleContext) {
124	// Sepolicy directories should be included in the following order.
125	//   - system_public
126	//   - system_private
127	//   - system_ext_public
128	//   - system_ext_private
129	//   - product_public
130	//   - product_private
131	dirs := []sepolicyDir{
132		sepolicyDir{partition: system, scope: public, paths: []string{filepath.Join(ctx.ModuleDir(), "public")}},
133		sepolicyDir{partition: system, scope: private, paths: []string{filepath.Join(ctx.ModuleDir(), "private")}},
134		sepolicyDir{partition: system_ext, scope: public, paths: ctx.DeviceConfig().SystemExtPublicSepolicyDirs()},
135		sepolicyDir{partition: system_ext, scope: private, paths: ctx.DeviceConfig().SystemExtPrivateSepolicyDirs()},
136		sepolicyDir{partition: product, scope: public, paths: ctx.Config().ProductPublicSepolicyDirs()},
137		sepolicyDir{partition: product, scope: private, paths: ctx.Config().ProductPrivateSepolicyDirs()},
138	}
139
140	if !sort.SliceIsSorted(dirs, func(i, j int) bool {
141		if dirs[i].partition != dirs[j].partition {
142			return dirs[i].partition < dirs[j].partition
143		}
144
145		return dirs[i].scope < dirs[j].scope
146	}) {
147		panic("dirs is not sorted")
148	}
149
150	// Exported cil policy files are built with the following policies.
151	//
152	//   - plat_pub_policy.cil: exported 'system'
153	//   - system_ext_pub_policy.cil: exported 'system' and 'system_ext'
154	//   - pub_policy.cil: exported 'system', 'system_ext', and 'product'
155	//
156	// cil policy files are built with the following policies.
157	//
158	//   - plat_policy.cil: 'system', including private
159	//   - system_ext_policy.cil: 'system_ext', including private
160	//   - product_sepolicy.cil: 'product', including private
161	//
162	// gatherDirsFor collects all needed directories for given partition and scope. For example,
163	//
164	//   - gatherDirsFor(system_ext, private) will return system + system_ext (including private)
165	//   - gatherDirsFor(product, public) will return system + system_ext + product (public only)
166	//
167	// "dirs" should be sorted before calling this.
168	gatherDirsFor := func(p partition, s scope) []string {
169		var ret []string
170
171		for _, d := range dirs {
172			if d.partition <= p && d.scope <= s {
173				ret = append(ret, d.paths...)
174			}
175		}
176
177		return ret
178	}
179
180	reqdMaskDir := filepath.Join(ctx.ModuleDir(), "reqd_mask")
181
182	b.srcs = make(map[string]android.Paths)
183	b.srcs[".reqd_mask"] = b.findSrcsInDirs(ctx, reqdMaskDir)
184
185	for _, p := range []partition{system, system_ext, product} {
186		b.srcs["."+p.String()] = b.findSrcsInDirs(ctx, gatherDirsFor(p, private)...)
187
188		// reqd_mask is needed for public policies
189		b.srcs["."+p.String()+"_public"] = b.findSrcsInDirs(ctx, append(gatherDirsFor(p, public), reqdMaskDir)...)
190	}
191
192	// A special tag, "plat_vendor", includes minimized vendor policies required to boot.
193	//   - system/sepolicy/public
194	//   - system/sepolicy/reqd_mask
195	//   - system/sepolicy/vendor
196	// This is for minimized vendor partition, e.g. microdroid's vendor
197	platVendorDir := filepath.Join(ctx.ModuleDir(), "vendor")
198	b.srcs[".plat_vendor"] = b.findSrcsInDirs(ctx, append(gatherDirsFor(system, public), reqdMaskDir, platVendorDir)...)
199}
200