1// Copyright 2021 Google LLC
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 main
16
17import (
18	"bytes"
19	"flag"
20	"fmt"
21	"io"
22	"io/fs"
23	"os"
24	"path/filepath"
25	"sort"
26	"strings"
27
28	"android/soong/response"
29	"android/soong/tools/compliance"
30)
31
32var (
33	failConflicts     = fmt.Errorf("conflicts")
34	failNoneRequested = fmt.Errorf("\nNo metadata files requested")
35	failNoLicenses    = fmt.Errorf("No licenses")
36)
37
38// byError orders conflicts by error string
39type byError []compliance.SourceSharePrivacyConflict
40
41func (l byError) Len() int           { return len(l) }
42func (l byError) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }
43func (l byError) Less(i, j int) bool { return l[i].Error() < l[j].Error() }
44
45func main() {
46	var expandedArgs []string
47	for _, arg := range os.Args[1:] {
48		if strings.HasPrefix(arg, "@") {
49			f, err := os.Open(strings.TrimPrefix(arg, "@"))
50			if err != nil {
51				fmt.Fprintln(os.Stderr, err.Error())
52				os.Exit(1)
53			}
54
55			respArgs, err := response.ReadRspFile(f)
56			f.Close()
57			if err != nil {
58				fmt.Fprintln(os.Stderr, err.Error())
59				os.Exit(1)
60			}
61			expandedArgs = append(expandedArgs, respArgs...)
62		} else {
63			expandedArgs = append(expandedArgs, arg)
64		}
65	}
66
67	flags := flag.NewFlagSet("flags", flag.ExitOnError)
68
69	flags.Usage = func() {
70		fmt.Fprintf(os.Stderr, `Usage: %s {-o outfile} file.meta_lic {file.meta_lic...}
71
72Reports on stderr any targets where policy says that the source both
73must and must not be shared. The error report indicates the target, the
74license condition that has a source privacy policy, and the license
75condition that has a source sharing policy.
76
77Any given target may appear multiple times with different combinations
78of conflicting license conditions.
79
80If all the source code that policy says must be shared may be shared,
81outputs "PASS" to stdout and exits with status 0.
82
83If policy says any source must both be shared and not be shared,
84outputs "FAIL" to stdout and exits with status 1.
85`, filepath.Base(os.Args[0]))
86		flags.PrintDefaults()
87	}
88
89	outputFile := flags.String("o", "-", "Where to write the output. (default stdout)")
90
91	flags.Parse(expandedArgs)
92
93	// Must specify at least one root target.
94	if flags.NArg() == 0 {
95		flags.Usage()
96		os.Exit(2)
97	}
98
99	if len(*outputFile) == 0 {
100		flags.Usage()
101		fmt.Fprintf(os.Stderr, "must specify file for -o; use - for stdout\n")
102		os.Exit(2)
103	} else {
104		dir, err := filepath.Abs(filepath.Dir(*outputFile))
105		if err != nil {
106			fmt.Fprintf(os.Stderr, "cannot determine path to %q: %s\n", *outputFile, err)
107			os.Exit(1)
108		}
109		fi, err := os.Stat(dir)
110		if err != nil {
111			fmt.Fprintf(os.Stderr, "cannot read directory %q of %q: %s\n", dir, *outputFile, err)
112			os.Exit(1)
113		}
114		if !fi.IsDir() {
115			fmt.Fprintf(os.Stderr, "parent %q of %q is not a directory\n", dir, *outputFile)
116			os.Exit(1)
117		}
118	}
119
120	var ofile io.Writer
121	ofile = os.Stdout
122	var obuf *bytes.Buffer
123	if *outputFile != "-" {
124		obuf = &bytes.Buffer{}
125		ofile = obuf
126	}
127
128	err := checkShare(ofile, os.Stderr, compliance.FS, flags.Args()...)
129	if err != nil {
130		if err != failConflicts {
131			if err == failNoneRequested {
132				flags.Usage()
133			}
134			fmt.Fprintf(os.Stderr, "%s\n", err.Error())
135		}
136		os.Exit(1)
137	}
138	if *outputFile != "-" {
139		err := os.WriteFile(*outputFile, obuf.Bytes(), 0666)
140		if err != nil {
141			fmt.Fprintf(os.Stderr, "could not write output to %q from %q: %s\n", *outputFile, os.Getenv("PWD"), err)
142			os.Exit(1)
143		}
144	}
145	os.Exit(0)
146}
147
148// checkShare implements the checkshare utility.
149func checkShare(stdout, stderr io.Writer, rootFS fs.FS, files ...string) error {
150
151	if len(files) < 1 {
152		return failNoneRequested
153	}
154
155	// Read the license graph from the license metadata files (*.meta_lic).
156	licenseGraph, err := compliance.ReadLicenseGraph(rootFS, stderr, files)
157	if err != nil {
158		return fmt.Errorf("Unable to read license metadata file(s) %q from %q: %w\n", files, os.Getenv("PWD"), err)
159	}
160	if licenseGraph == nil {
161		return failNoLicenses
162	}
163
164	// Apply policy to find conflicts and report them to stderr lexicographically ordered.
165	conflicts := compliance.ConflictingSharedPrivateSource(licenseGraph)
166	sort.Sort(byError(conflicts))
167	for _, conflict := range conflicts {
168		fmt.Fprintln(stderr, conflict.Error())
169	}
170
171	// Indicate pass or fail on stdout.
172	if len(conflicts) > 0 {
173		fmt.Fprintln(stdout, "FAIL")
174		return failConflicts
175	}
176	fmt.Fprintln(stdout, "PASS")
177	return nil
178}
179