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