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 
19 #include <dirent.h>
20 #include <sys/stat.h>
21 
22 #include <algorithm>
23 #include <cerrno>
24 #include <cstdio>
25 #include <string>
26 
27 #include "android-base/errors.h"
28 #include "android-base/file.h"
29 #include "android-base/logging.h"
30 
31 #include "util/Util.h"
32 
33 #ifdef _WIN32
34 // Windows includes.
35 #include <direct.h>
36 #endif
37 
38 using android::StringPiece;
39 
40 namespace aapt {
41 namespace file {
42 
GetFileType(const StringPiece & path)43 FileType GetFileType(const StringPiece& path) {
44   struct stat sb;
45   if (stat(path.data(), &sb) < 0) {
46     if (errno == ENOENT || errno == ENOTDIR) {
47       return FileType::kNonexistant;
48     }
49     return FileType::kUnknown;
50   }
51 
52   if (S_ISREG(sb.st_mode)) {
53     return FileType::kRegular;
54   } else if (S_ISDIR(sb.st_mode)) {
55     return FileType::kDirectory;
56   } else if (S_ISCHR(sb.st_mode)) {
57     return FileType::kCharDev;
58   } else if (S_ISBLK(sb.st_mode)) {
59     return FileType::kBlockDev;
60   } else if (S_ISFIFO(sb.st_mode)) {
61     return FileType::kFifo;
62 #if defined(S_ISLNK)
63   } else if (S_ISLNK(sb.st_mode)) {
64     return FileType::kSymlink;
65 #endif
66 #if defined(S_ISSOCK)
67   } else if (S_ISSOCK(sb.st_mode)) {
68     return FileType::kSocket;
69 #endif
70   } else {
71     return FileType::kUnknown;
72   }
73 }
74 
MkdirImpl(const StringPiece & path)75 inline static int MkdirImpl(const StringPiece& path) {
76 #ifdef _WIN32
77   return _mkdir(path.to_string().c_str());
78 #else
79   return mkdir(path.to_string().c_str(), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP);
80 #endif
81 }
82 
mkdirs(const StringPiece & path)83 bool mkdirs(const StringPiece& path) {
84   const char* start = path.begin();
85   const char* end = path.end();
86   for (const char* current = start; current != end; ++current) {
87     if (*current == sDirSep && current != start) {
88       StringPiece parent_path(start, current - start);
89       int result = MkdirImpl(parent_path);
90       if (result < 0 && errno != EEXIST) {
91         return false;
92       }
93     }
94   }
95   return MkdirImpl(path) == 0 || errno == EEXIST;
96 }
97 
GetStem(const StringPiece & path)98 StringPiece GetStem(const StringPiece& path) {
99   const char* start = path.begin();
100   const char* end = path.end();
101   for (const char* current = end - 1; current != start - 1; --current) {
102     if (*current == sDirSep) {
103       return StringPiece(start, current - start);
104     }
105   }
106   return {};
107 }
108 
GetFilename(const StringPiece & path)109 StringPiece GetFilename(const StringPiece& path) {
110   const char* end = path.end();
111   const char* last_dir_sep = path.begin();
112   for (const char* c = path.begin(); c != end; ++c) {
113     if (*c == sDirSep) {
114       last_dir_sep = c + 1;
115     }
116   }
117   return StringPiece(last_dir_sep, end - last_dir_sep);
118 }
119 
GetExtension(const StringPiece & path)120 StringPiece GetExtension(const StringPiece& path) {
121   StringPiece filename = GetFilename(path);
122   const char* const end = filename.end();
123   const char* c = std::find(filename.begin(), end, '.');
124   if (c != end) {
125     return StringPiece(c, end - c);
126   }
127   return {};
128 }
129 
AppendPath(std::string * base,StringPiece part)130 void AppendPath(std::string* base, StringPiece part) {
131   CHECK(base != nullptr);
132   const bool base_has_trailing_sep =
133       (!base->empty() && *(base->end() - 1) == sDirSep);
134   const bool part_has_leading_sep =
135       (!part.empty() && *(part.begin()) == sDirSep);
136   if (base_has_trailing_sep && part_has_leading_sep) {
137     // Remove the part's leading sep
138     part = part.substr(1, part.size() - 1);
139   } else if (!base_has_trailing_sep && !part_has_leading_sep) {
140     // None of the pieces has a separator.
141     *base += sDirSep;
142   }
143   base->append(part.data(), part.size());
144 }
145 
PackageToPath(const StringPiece & package)146 std::string PackageToPath(const StringPiece& package) {
147   std::string out_path;
148   for (StringPiece part : util::Tokenize(package, '.')) {
149     AppendPath(&out_path, part);
150   }
151   return out_path;
152 }
153 
MmapPath(const StringPiece & path,std::string * out_error)154 Maybe<android::FileMap> MmapPath(const StringPiece& path,
155                                  std::string* out_error) {
156   std::unique_ptr<FILE, decltype(fclose)*> f = {fopen(path.data(), "rb"),
157                                                 fclose};
158   if (!f) {
159     if (out_error) *out_error = android::base::SystemErrorCodeToString(errno);
160     return {};
161   }
162 
163   int fd = fileno(f.get());
164 
165   struct stat filestats = {};
166   if (fstat(fd, &filestats) != 0) {
167     if (out_error) *out_error = android::base::SystemErrorCodeToString(errno);
168     return {};
169   }
170 
171   android::FileMap filemap;
172   if (filestats.st_size == 0) {
173     // mmap doesn't like a length of 0. Instead we return an empty FileMap.
174     return std::move(filemap);
175   }
176 
177   if (!filemap.create(path.data(), fd, 0, filestats.st_size, true)) {
178     if (out_error) *out_error = android::base::SystemErrorCodeToString(errno);
179     return {};
180   }
181   return std::move(filemap);
182 }
183 
AppendArgsFromFile(const StringPiece & path,std::vector<std::string> * out_arglist,std::string * out_error)184 bool AppendArgsFromFile(const StringPiece& path, std::vector<std::string>* out_arglist,
185                         std::string* out_error) {
186   std::string contents;
187   if (!android::base::ReadFileToString(path.to_string(), &contents, true /*follow_symlinks*/)) {
188     if (out_error) {
189       *out_error = "failed to read argument-list file";
190     }
191     return false;
192   }
193 
194   for (StringPiece line : util::Tokenize(contents, ' ')) {
195     line = util::TrimWhitespace(line);
196     if (!line.empty()) {
197       out_arglist->push_back(line.to_string());
198     }
199   }
200   return true;
201 }
202 
SetPattern(const StringPiece & pattern)203 bool FileFilter::SetPattern(const StringPiece& pattern) {
204   pattern_tokens_ = util::SplitAndLowercase(pattern, ':');
205   return true;
206 }
207 
operator ()(const std::string & filename,FileType type) const208 bool FileFilter::operator()(const std::string& filename, FileType type) const {
209   if (filename == "." || filename == "..") {
210     return false;
211   }
212 
213   const char kDir[] = "dir";
214   const char kFile[] = "file";
215   const size_t filename_len = filename.length();
216   bool chatty = true;
217   for (const std::string& token : pattern_tokens_) {
218     const char* token_str = token.c_str();
219     if (*token_str == '!') {
220       chatty = false;
221       token_str++;
222     }
223 
224     if (strncasecmp(token_str, kDir, sizeof(kDir)) == 0) {
225       if (type != FileType::kDirectory) {
226         continue;
227       }
228       token_str += sizeof(kDir);
229     }
230 
231     if (strncasecmp(token_str, kFile, sizeof(kFile)) == 0) {
232       if (type != FileType::kRegular) {
233         continue;
234       }
235       token_str += sizeof(kFile);
236     }
237 
238     bool ignore = false;
239     size_t n = strlen(token_str);
240     if (*token_str == '*') {
241       // Math suffix.
242       token_str++;
243       n--;
244       if (n <= filename_len) {
245         ignore =
246             strncasecmp(token_str, filename.c_str() + filename_len - n, n) == 0;
247       }
248     } else if (n > 1 && token_str[n - 1] == '*') {
249       // Match prefix.
250       ignore = strncasecmp(token_str, filename.c_str(), n - 1) == 0;
251     } else {
252       ignore = strcasecmp(token_str, filename.c_str()) == 0;
253     }
254 
255     if (ignore) {
256       if (chatty) {
257         diag_->Warn(DiagMessage()
258                     << "skipping "
259                     << (type == FileType::kDirectory ? "dir '" : "file '")
260                     << filename << "' due to ignore pattern '" << token << "'");
261       }
262       return false;
263     }
264   }
265   return true;
266 }
267 
FindFiles(const android::StringPiece & path,IDiagnostics * diag,const FileFilter * filter)268 Maybe<std::vector<std::string>> FindFiles(const android::StringPiece& path, IDiagnostics* diag,
269                                           const FileFilter* filter) {
270   const std::string root_dir = path.to_string();
271   std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir);
272   if (!d) {
273     diag->Error(DiagMessage() << android::base::SystemErrorCodeToString(errno));
274     return {};
275   }
276 
277   std::vector<std::string> files;
278   std::vector<std::string> subdirs;
279   while (struct dirent* entry = readdir(d.get())) {
280     if (util::StartsWith(entry->d_name, ".")) {
281       continue;
282     }
283 
284     std::string file_name = entry->d_name;
285     std::string full_path = root_dir;
286     AppendPath(&full_path, file_name);
287     const FileType file_type = GetFileType(full_path);
288 
289     if (filter != nullptr) {
290       if (!(*filter)(file_name, file_type)) {
291         continue;
292       }
293     }
294 
295     if (file_type == file::FileType::kDirectory) {
296       subdirs.push_back(std::move(file_name));
297     } else {
298       files.push_back(std::move(file_name));
299     }
300   }
301 
302   // Now process subdirs.
303   for (const std::string& subdir : subdirs) {
304     std::string full_subdir = root_dir;
305     AppendPath(&full_subdir, subdir);
306     Maybe<std::vector<std::string>> subfiles = FindFiles(full_subdir, diag, filter);
307     if (!subfiles) {
308       return {};
309     }
310 
311     for (const std::string& subfile : subfiles.value()) {
312       std::string new_file = subdir;
313       AppendPath(&new_file, subfile);
314       files.push_back(new_file);
315     }
316   }
317   return files;
318 }
319 
320 }  // namespace file
321 }  // namespace aapt
322