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