1// Copyright 2017 Google Inc. All rights reserved.
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 build
16
17import (
18	"compress/gzip"
19	"fmt"
20	"io"
21	"os"
22	"path/filepath"
23	"strings"
24)
25
26func absPath(ctx Context, p string) string {
27	ret, err := filepath.Abs(p)
28	if err != nil {
29		ctx.Fatalf("Failed to get absolute path: %v", err)
30	}
31	return ret
32}
33
34// indexList finds the index of a string in a []string
35func indexList(s string, list []string) int {
36	for i, l := range list {
37		if l == s {
38			return i
39		}
40	}
41
42	return -1
43}
44
45// inList determines whether a string is in a []string
46func inList(s string, list []string) bool {
47	return indexList(s, list) != -1
48}
49
50// removeFromlist removes all occurrences of the string in list.
51func removeFromList(s string, list []string) []string {
52	filteredList := make([]string, 0, len(list))
53	for _, ls := range list {
54		if s != ls {
55			filteredList = append(filteredList, ls)
56		}
57	}
58	return filteredList
59}
60
61// ensureDirectoriesExist is a shortcut to os.MkdirAll, sending errors to the ctx logger.
62func ensureDirectoriesExist(ctx Context, dirs ...string) {
63	for _, dir := range dirs {
64		err := os.MkdirAll(dir, 0777)
65		if err != nil {
66			ctx.Fatalf("Error creating %s: %q\n", dir, err)
67		}
68	}
69}
70
71// ensureEmptyDirectoriesExist ensures that the given directories exist and are empty
72func ensureEmptyDirectoriesExist(ctx Context, dirs ...string) {
73	// remove all the directories
74	for _, dir := range dirs {
75		seenErr := map[string]bool{}
76		for {
77			err := os.RemoveAll(dir)
78			if err == nil {
79				break
80			}
81
82			if pathErr, ok := err.(*os.PathError); !ok ||
83				dir == pathErr.Path || seenErr[pathErr.Path] {
84
85				ctx.Fatalf("Error removing %s: %q\n", dir, err)
86			} else {
87				seenErr[pathErr.Path] = true
88				err = os.Chmod(filepath.Dir(pathErr.Path), 0700)
89				if err != nil {
90					ctx.Fatal(err)
91				}
92			}
93		}
94	}
95	// recreate all the directories
96	ensureDirectoriesExist(ctx, dirs...)
97}
98
99// ensureEmptyFileExists ensures that the containing directory exists, and the
100// specified file exists. If it doesn't exist, it will write an empty file.
101func ensureEmptyFileExists(ctx Context, file string) {
102	ensureDirectoriesExist(ctx, filepath.Dir(file))
103	if _, err := os.Stat(file); os.IsNotExist(err) {
104		f, err := os.Create(file)
105		if err != nil {
106			ctx.Fatalf("Error creating %s: %q\n", file, err)
107		}
108		f.Close()
109	} else if err != nil {
110		ctx.Fatalf("Error checking %s: %q\n", file, err)
111	}
112}
113
114// singleUnquote is similar to strconv.Unquote, but can handle multi-character strings inside single quotes.
115func singleUnquote(str string) (string, bool) {
116	if len(str) < 2 || str[0] != '\'' || str[len(str)-1] != '\'' {
117		return "", false
118	}
119	return str[1 : len(str)-1], true
120}
121
122// decodeKeyValue decodes a key=value string
123func decodeKeyValue(str string) (string, string, bool) {
124	idx := strings.IndexRune(str, '=')
125	if idx == -1 {
126		return "", "", false
127	}
128	return str[:idx], str[idx+1:], true
129}
130
131// copyFile copies a file from src to dst. filepath.Dir(dst) must exist.
132func copyFile(src, dst string) (int64, error) {
133	source, err := os.Open(src)
134	if err != nil {
135		return 0, err
136	}
137	defer source.Close()
138
139	destination, err := os.Create(dst)
140	if err != nil {
141		return 0, err
142	}
143	defer destination.Close()
144
145	return io.Copy(destination, source)
146}
147
148// gzipFileToDir writes a compressed copy of src to destDir with the suffix ".gz".
149func gzipFileToDir(src, destDir string) error {
150	in, err := os.Open(src)
151	if err != nil {
152		return fmt.Errorf("failed to open %s: %s", src, err.Error())
153	}
154	defer in.Close()
155
156	dest := filepath.Join(destDir, filepath.Base(src)+".gz")
157
158	out, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY, 0666)
159	if err != nil {
160		return fmt.Errorf("failed to open %s: %s", dest, err.Error())
161	}
162	defer out.Close()
163	gz := gzip.NewWriter(out)
164	defer gz.Close()
165
166	_, err = io.Copy(gz, in)
167	if err != nil {
168		return fmt.Errorf("failed to gzip %s: %s", dest, err.Error())
169	}
170
171	return nil
172}
173