1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "util/Files.h"
18 #include "util/Util.h"
19 
20 #include <algorithm>
21 #include <android-base/file.h>
22 #include <cerrno>
23 #include <cstdio>
24 #include <dirent.h>
25 #include <string>
26 #include <sys/stat.h>
27 
28 #ifdef _WIN32
29 // Windows includes.
30 #include <direct.h>
31 #endif
32 
33 namespace aapt {
34 namespace file {
35 
getFileType(const StringPiece & path)36 FileType getFileType(const StringPiece& path) {
37     struct stat sb;
38     if (stat(path.data(), &sb) < 0) {
39         if (errno == ENOENT || errno == ENOTDIR) {
40             return FileType::kNonexistant;
41         }
42         return FileType::kUnknown;
43     }
44 
45     if (S_ISREG(sb.st_mode)) {
46         return FileType::kRegular;
47     } else if (S_ISDIR(sb.st_mode)) {
48         return FileType::kDirectory;
49     } else if (S_ISCHR(sb.st_mode)) {
50         return FileType::kCharDev;
51     } else if (S_ISBLK(sb.st_mode)) {
52         return FileType::kBlockDev;
53     } else if (S_ISFIFO(sb.st_mode)) {
54         return FileType::kFifo;
55 #if defined(S_ISLNK)
56     } else if (S_ISLNK(sb.st_mode)) {
57         return FileType::kSymlink;
58 #endif
59 #if defined(S_ISSOCK)
60     } else if (S_ISSOCK(sb.st_mode)) {
61         return FileType::kSocket;
62 #endif
63     } else {
64         return FileType::kUnknown;
65     }
66 }
67 
listFiles(const StringPiece & root,std::string * outError)68 std::vector<std::string> listFiles(const StringPiece& root, std::string* outError) {
69     DIR* dir = opendir(root.data());
70     if (dir == nullptr) {
71         if (outError) {
72             std::stringstream errorStr;
73             errorStr << "unable to open file: " << strerror(errno);
74             *outError = errorStr.str();
75             return {};
76         }
77     }
78 
79     std::vector<std::string> files;
80     dirent* entry;
81     while ((entry = readdir(dir))) {
82         files.emplace_back(entry->d_name);
83     }
84 
85     closedir(dir);
86     return files;
87 }
88 
mkdirImpl(const StringPiece & path)89 inline static int mkdirImpl(const StringPiece& path) {
90 #ifdef _WIN32
91     return _mkdir(path.toString().c_str());
92 #else
93     return mkdir(path.toString().c_str(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP);
94 #endif
95 }
96 
mkdirs(const StringPiece & path)97 bool mkdirs(const StringPiece& path) {
98     const char* start = path.begin();
99     const char* end = path.end();
100     for (const char* current = start; current != end; ++current) {
101         if (*current == sDirSep && current != start) {
102             StringPiece parentPath(start, current - start);
103             int result = mkdirImpl(parentPath);
104             if (result < 0 && errno != EEXIST) {
105                 return false;
106             }
107         }
108     }
109     return mkdirImpl(path) == 0 || errno == EEXIST;
110 }
111 
getStem(const StringPiece & path)112 StringPiece getStem(const StringPiece& path) {
113     const char* start = path.begin();
114     const char* end = path.end();
115     for (const char* current = end - 1; current != start - 1; --current) {
116         if (*current == sDirSep) {
117             return StringPiece(start, current - start);
118         }
119     }
120     return {};
121 }
122 
getFilename(const StringPiece & path)123 StringPiece getFilename(const StringPiece& path) {
124     const char* end = path.end();
125     const char* lastDirSep = path.begin();
126     for (const char* c = path.begin(); c != end; ++c) {
127         if (*c == sDirSep) {
128             lastDirSep = c + 1;
129         }
130     }
131     return StringPiece(lastDirSep, end - lastDirSep);
132 }
133 
getExtension(const StringPiece & path)134 StringPiece getExtension(const StringPiece& path) {
135     StringPiece filename = getFilename(path);
136     const char* const end = filename.end();
137     const char* c = std::find(filename.begin(), end, '.');
138     if (c != end) {
139         return StringPiece(c, end - c);
140     }
141     return {};
142 }
143 
appendPath(std::string * base,StringPiece part)144 void appendPath(std::string* base, StringPiece part) {
145     assert(base);
146     const bool baseHasTrailingSep = (!base->empty() && *(base->end() - 1) == sDirSep);
147     const bool partHasLeadingSep = (!part.empty() && *(part.begin()) == sDirSep);
148     if (baseHasTrailingSep && partHasLeadingSep) {
149         // Remove the part's leading sep
150         part = part.substr(1, part.size() - 1);
151     } else if (!baseHasTrailingSep && !partHasLeadingSep) {
152         // None of the pieces has a separator.
153         *base += sDirSep;
154     }
155     base->append(part.data(), part.size());
156 }
157 
packageToPath(const StringPiece & package)158 std::string packageToPath(const StringPiece& package) {
159     std::string outPath;
160     for (StringPiece part : util::tokenize<char>(package, '.')) {
161         appendPath(&outPath, part);
162     }
163     return outPath;
164 }
165 
mmapPath(const StringPiece & path,std::string * outError)166 Maybe<android::FileMap> mmapPath(const StringPiece& path, std::string* outError) {
167     std::unique_ptr<FILE, decltype(fclose)*> f = { fopen(path.data(), "rb"), fclose };
168     if (!f) {
169         if (outError) *outError = strerror(errno);
170         return {};
171     }
172 
173     int fd = fileno(f.get());
174 
175     struct stat fileStats = {};
176     if (fstat(fd, &fileStats) != 0) {
177         if (outError) *outError = strerror(errno);
178         return {};
179     }
180 
181     android::FileMap fileMap;
182     if (fileStats.st_size == 0) {
183         // mmap doesn't like a length of 0. Instead we return an empty FileMap.
184         return std::move(fileMap);
185     }
186 
187     if (!fileMap.create(path.data(), fd, 0, fileStats.st_size, true)) {
188         if (outError) *outError = strerror(errno);
189         return {};
190     }
191     return std::move(fileMap);
192 }
193 
appendArgsFromFile(const StringPiece & path,std::vector<std::string> * outArgList,std::string * outError)194 bool appendArgsFromFile(const StringPiece& path, std::vector<std::string>* outArgList,
195                         std::string* outError) {
196     std::string contents;
197     if (!android::base::ReadFileToString(path.toString(), &contents)) {
198         if (outError) *outError = "failed to read argument-list file";
199         return false;
200     }
201 
202     for (StringPiece line : util::tokenize<char>(contents, ' ')) {
203         line = util::trimWhitespace(line);
204         if (!line.empty()) {
205             outArgList->push_back(line.toString());
206         }
207     }
208     return true;
209 }
210 
setPattern(const StringPiece & pattern)211 bool FileFilter::setPattern(const StringPiece& pattern) {
212     mPatternTokens = util::splitAndLowercase(pattern, ':');
213     return true;
214 }
215 
operator ()(const std::string & filename,FileType type) const216 bool FileFilter::operator()(const std::string& filename, FileType type) const {
217     if (filename == "." || filename == "..") {
218         return false;
219     }
220 
221     const char kDir[] = "dir";
222     const char kFile[] = "file";
223     const size_t filenameLen = filename.length();
224     bool chatty = true;
225     for (const std::string& token : mPatternTokens) {
226         const char* tokenStr = token.c_str();
227         if (*tokenStr == '!') {
228             chatty = false;
229             tokenStr++;
230         }
231 
232         if (strncasecmp(tokenStr, kDir, sizeof(kDir)) == 0) {
233             if (type != FileType::kDirectory) {
234                 continue;
235             }
236             tokenStr += sizeof(kDir);
237         }
238 
239         if (strncasecmp(tokenStr, kFile, sizeof(kFile)) == 0) {
240             if (type != FileType::kRegular) {
241                 continue;
242             }
243             tokenStr += sizeof(kFile);
244         }
245 
246         bool ignore = false;
247         size_t n = strlen(tokenStr);
248         if (*tokenStr == '*') {
249             // Math suffix.
250             tokenStr++;
251             n--;
252             if (n <= filenameLen) {
253                 ignore = strncasecmp(tokenStr, filename.c_str() + filenameLen - n, n) == 0;
254             }
255         } else if (n > 1 && tokenStr[n - 1] == '*') {
256             // Match prefix.
257             ignore = strncasecmp(tokenStr, filename.c_str(), n - 1) == 0;
258         } else {
259             ignore = strcasecmp(tokenStr, filename.c_str()) == 0;
260         }
261 
262         if (ignore) {
263             if (chatty) {
264                 mDiag->warn(DiagMessage() << "skipping "
265                             << (type == FileType::kDirectory ? "dir '" : "file '")
266                             << filename << "' due to ignore pattern '"
267                             << token << "'");
268             }
269             return false;
270         }
271     }
272     return true;
273 }
274 
275 } // namespace file
276 } // namespace aapt
277