|  | /* | 
|  | * Copyright (C) 2015 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | #include "util/Files.h" | 
|  |  | 
|  | #include <dirent.h> | 
|  | #include <sys/stat.h> | 
|  |  | 
|  | #include <algorithm> | 
|  | #include <cerrno> | 
|  | #include <cstdio> | 
|  | #include <string> | 
|  |  | 
|  | #include "android-base/errors.h" | 
|  | #include "android-base/file.h" | 
|  | #include "android-base/logging.h" | 
|  | #include "android-base/unique_fd.h" | 
|  | #include "android-base/utf8.h" | 
|  |  | 
|  | #include "util/Util.h" | 
|  |  | 
|  | #ifdef _WIN32 | 
|  | // Windows includes. | 
|  | #include <windows.h> | 
|  | #endif | 
|  |  | 
|  | using ::android::FileMap; | 
|  | using ::android::StringPiece; | 
|  | using ::android::base::ReadFileToString; | 
|  | using ::android::base::SystemErrorCodeToString; | 
|  | using ::android::base::unique_fd; | 
|  |  | 
|  | namespace aapt { | 
|  | namespace file { | 
|  |  | 
|  | #ifdef _WIN32 | 
|  | FileType GetFileType(const std::string& path) { | 
|  | std::wstring path_utf16; | 
|  | if (!::android::base::UTF8PathToWindowsLongPath(path.c_str(), &path_utf16)) { | 
|  | return FileType::kNonExistant; | 
|  | } | 
|  |  | 
|  | DWORD result = GetFileAttributesW(path_utf16.c_str()); | 
|  | if (result == INVALID_FILE_ATTRIBUTES) { | 
|  | return FileType::kNonExistant; | 
|  | } | 
|  |  | 
|  | if (result & FILE_ATTRIBUTE_DIRECTORY) { | 
|  | return FileType::kDirectory; | 
|  | } | 
|  |  | 
|  | // Too many types to consider, just let open fail later. | 
|  | return FileType::kRegular; | 
|  | } | 
|  | #else | 
|  | FileType GetFileType(const std::string& path) { | 
|  | struct stat sb; | 
|  | int result = stat(path.c_str(), &sb); | 
|  |  | 
|  | if (result == -1) { | 
|  | if (errno == ENOENT || errno == ENOTDIR) { | 
|  | return FileType::kNonExistant; | 
|  | } | 
|  | return FileType::kUnknown; | 
|  | } | 
|  |  | 
|  | if (S_ISREG(sb.st_mode)) { | 
|  | return FileType::kRegular; | 
|  | } else if (S_ISDIR(sb.st_mode)) { | 
|  | return FileType::kDirectory; | 
|  | } else if (S_ISCHR(sb.st_mode)) { | 
|  | return FileType::kCharDev; | 
|  | } else if (S_ISBLK(sb.st_mode)) { | 
|  | return FileType::kBlockDev; | 
|  | } else if (S_ISFIFO(sb.st_mode)) { | 
|  | return FileType::kFifo; | 
|  | #if defined(S_ISLNK) | 
|  | } else if (S_ISLNK(sb.st_mode)) { | 
|  | return FileType::kSymlink; | 
|  | #endif | 
|  | #if defined(S_ISSOCK) | 
|  | } else if (S_ISSOCK(sb.st_mode)) { | 
|  | return FileType::kSocket; | 
|  | #endif | 
|  | } else { | 
|  | return FileType::kUnknown; | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  | bool mkdirs(const std::string& path) { | 
|  | #ifdef _WIN32 | 
|  | // Start after the long path prefix if present. | 
|  | bool require_drive = false; | 
|  | size_t current_pos = 0u; | 
|  | if (util::StartsWith(path, R"(\\?\)")) { | 
|  | require_drive = true; | 
|  | current_pos = 4u; | 
|  | } | 
|  |  | 
|  | // Start after the drive path if present. | 
|  | if (path.size() >= 3 && path[current_pos + 1] == ':' && | 
|  | (path[current_pos + 2] == '\\' || path[current_pos + 2] == '/')) { | 
|  | current_pos += 3u; | 
|  | } else if (require_drive) { | 
|  | return false; | 
|  | } | 
|  | #else | 
|  | // Start after the first character so that we don't consume the root '/'. | 
|  | // This is safe to do with unicode because '/' will never match with a continuation character. | 
|  | size_t current_pos = 1u; | 
|  | #endif | 
|  | constexpr const mode_t mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP; | 
|  | while ((current_pos = path.find(sDirSep, current_pos)) != std::string::npos) { | 
|  | std::string parent_path = path.substr(0, current_pos); | 
|  | if (parent_path.empty()) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | int result = ::android::base::utf8::mkdir(parent_path.c_str(), mode); | 
|  | if (result < 0 && errno != EEXIST) { | 
|  | return false; | 
|  | } | 
|  | current_pos += 1; | 
|  | } | 
|  | return ::android::base::utf8::mkdir(path.c_str(), mode) == 0 || errno == EEXIST; | 
|  | } | 
|  |  | 
|  | StringPiece GetStem(StringPiece path) { | 
|  | const char* start = path.begin(); | 
|  | const char* end = path.end(); | 
|  | for (const char* current = end - 1; current != start - 1; --current) { | 
|  | if (*current == sDirSep) { | 
|  | return StringPiece(start, current - start); | 
|  | } | 
|  | } | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | StringPiece GetFilename(StringPiece path) { | 
|  | const char* end = path.end(); | 
|  | const char* last_dir_sep = path.begin(); | 
|  | for (const char* c = path.begin(); c != end; ++c) { | 
|  | if (*c == sDirSep || *c == sInvariantDirSep) { | 
|  | last_dir_sep = c + 1; | 
|  | } | 
|  | } | 
|  | return StringPiece(last_dir_sep, end - last_dir_sep); | 
|  | } | 
|  |  | 
|  | StringPiece GetExtension(StringPiece path) { | 
|  | StringPiece filename = GetFilename(path); | 
|  | const char* const end = filename.end(); | 
|  | const char* c = std::find(filename.begin(), end, '.'); | 
|  | if (c != end) { | 
|  | return StringPiece(c, end - c); | 
|  | } | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | bool IsHidden(android::StringPiece path) { | 
|  | return util::StartsWith(GetFilename(path), "."); | 
|  | } | 
|  |  | 
|  | void AppendPath(std::string* base, StringPiece part) { | 
|  | CHECK(base != nullptr); | 
|  | const bool base_has_trailing_sep = (!base->empty() && *(base->end() - 1) == sDirSep); | 
|  | const bool part_has_leading_sep = (!part.empty() && *(part.begin()) == sDirSep); | 
|  | if (base_has_trailing_sep && part_has_leading_sep) { | 
|  | // Remove the part's leading sep | 
|  | part = part.substr(1, part.size() - 1); | 
|  | } else if (!base_has_trailing_sep && !part_has_leading_sep) { | 
|  | // None of the pieces has a separator. | 
|  | *base += sDirSep; | 
|  | } | 
|  | base->append(part.data(), part.size()); | 
|  | } | 
|  |  | 
|  | std::string BuildPath(std::vector<const StringPiece>&& args) { | 
|  | if (args.empty()) { | 
|  | return ""; | 
|  | } | 
|  | std::string out{args[0]}; | 
|  | for (int i = 1; i < args.size(); i++) { | 
|  | file::AppendPath(&out, args[i]); | 
|  | } | 
|  | return out; | 
|  | } | 
|  |  | 
|  | std::string PackageToPath(StringPiece package) { | 
|  | std::string out_path; | 
|  | for (StringPiece part : util::Tokenize(package, '.')) { | 
|  | AppendPath(&out_path, part); | 
|  | } | 
|  | return out_path; | 
|  | } | 
|  |  | 
|  | std::optional<FileMap> MmapPath(const std::string& path, std::string* out_error) { | 
|  | int flags = O_RDONLY | O_CLOEXEC | O_BINARY; | 
|  | unique_fd fd(TEMP_FAILURE_RETRY(::android::base::utf8::open(path.c_str(), flags))); | 
|  | if (fd == -1) { | 
|  | if (out_error) { | 
|  | *out_error = SystemErrorCodeToString(errno); | 
|  | } | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | struct stat filestats = {}; | 
|  | if (fstat(fd, &filestats) != 0) { | 
|  | if (out_error) { | 
|  | *out_error = SystemErrorCodeToString(errno); | 
|  | } | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | FileMap filemap; | 
|  | if (filestats.st_size == 0) { | 
|  | // mmap doesn't like a length of 0. Instead we return an empty FileMap. | 
|  | return std::move(filemap); | 
|  | } | 
|  |  | 
|  | if (!filemap.create(path.c_str(), fd, 0, filestats.st_size, true)) { | 
|  | if (out_error) { | 
|  | *out_error = SystemErrorCodeToString(errno); | 
|  | } | 
|  | return {}; | 
|  | } | 
|  | return std::move(filemap); | 
|  | } | 
|  |  | 
|  | bool AppendArgsFromFile(StringPiece path, std::vector<std::string>* out_arglist, | 
|  | std::string* out_error) { | 
|  | std::string contents; | 
|  | if (!ReadFileToString(std::string(path), &contents, true /*follow_symlinks*/)) { | 
|  | if (out_error) { | 
|  | *out_error = "failed to read argument-list file"; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | for (StringPiece line : util::Tokenize(contents, '\n')) { | 
|  | line = util::TrimWhitespace(line); | 
|  | for (StringPiece arg : util::Tokenize(line, ' ')) { | 
|  | arg = util::TrimWhitespace(arg); | 
|  | if (!arg.empty()) { | 
|  | out_arglist->emplace_back(arg); | 
|  | } | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool AppendSetArgsFromFile(StringPiece path, std::unordered_set<std::string>* out_argset, | 
|  | std::string* out_error) { | 
|  | std::string contents; | 
|  | if (!ReadFileToString(std::string(path), &contents, true /*follow_symlinks*/)) { | 
|  | if (out_error) { | 
|  | *out_error = "failed to read argument-list file"; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | for (StringPiece line : util::Tokenize(contents, '\n')) { | 
|  | line = util::TrimWhitespace(line); | 
|  | for (StringPiece arg : util::Tokenize(line, ' ')) { | 
|  | arg = util::TrimWhitespace(arg); | 
|  | if (!arg.empty()) { | 
|  | out_argset->emplace(arg); | 
|  | } | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool FileFilter::SetPattern(StringPiece pattern) { | 
|  | pattern_tokens_ = util::SplitAndLowercase(pattern, ':'); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool FileFilter::operator()(const std::string& filename, FileType type) const { | 
|  | if (filename == "." || filename == "..") { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const char kDir[] = "dir"; | 
|  | const char kFile[] = "file"; | 
|  | const size_t filename_len = filename.length(); | 
|  | bool chatty = true; | 
|  | for (const std::string& token : pattern_tokens_) { | 
|  | const char* token_str = token.c_str(); | 
|  | if (*token_str == '!') { | 
|  | chatty = false; | 
|  | token_str++; | 
|  | } | 
|  |  | 
|  | if (strncasecmp(token_str, kDir, sizeof(kDir)) == 0) { | 
|  | if (type != FileType::kDirectory) { | 
|  | continue; | 
|  | } | 
|  | token_str += sizeof(kDir); | 
|  | } | 
|  |  | 
|  | if (strncasecmp(token_str, kFile, sizeof(kFile)) == 0) { | 
|  | if (type != FileType::kRegular) { | 
|  | continue; | 
|  | } | 
|  | token_str += sizeof(kFile); | 
|  | } | 
|  |  | 
|  | bool ignore = false; | 
|  | size_t n = strlen(token_str); | 
|  | if (*token_str == '*') { | 
|  | // Math suffix. | 
|  | token_str++; | 
|  | n--; | 
|  | if (n <= filename_len) { | 
|  | ignore = | 
|  | strncasecmp(token_str, filename.c_str() + filename_len - n, n) == 0; | 
|  | } | 
|  | } else if (n > 1 && token_str[n - 1] == '*') { | 
|  | // Match prefix. | 
|  | ignore = strncasecmp(token_str, filename.c_str(), n - 1) == 0; | 
|  | } else { | 
|  | ignore = strcasecmp(token_str, filename.c_str()) == 0; | 
|  | } | 
|  |  | 
|  | if (ignore) { | 
|  | if (chatty) { | 
|  | diag_->Warn(android::DiagMessage() | 
|  | << "skipping " << (type == FileType::kDirectory ? "dir '" : "file '") | 
|  | << filename << "' due to ignore pattern '" << token << "'"); | 
|  | } | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | std::optional<std::vector<std::string>> FindFiles(android::StringPiece path, | 
|  | android::IDiagnostics* diag, | 
|  | const FileFilter* filter) { | 
|  | const auto& root_dir = path; | 
|  | std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir); | 
|  | if (!d) { | 
|  | diag->Error(android::DiagMessage() << SystemErrorCodeToString(errno) << ": " << root_dir); | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | std::vector<std::string> files; | 
|  | std::vector<std::string> subdirs; | 
|  | while (struct dirent* entry = readdir(d.get())) { | 
|  | if (util::StartsWith(entry->d_name, ".")) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | std::string file_name = entry->d_name; | 
|  | std::string full_path{root_dir}; | 
|  | AppendPath(&full_path, file_name); | 
|  | const FileType file_type = GetFileType(full_path); | 
|  |  | 
|  | if (filter != nullptr) { | 
|  | if (!(*filter)(file_name, file_type)) { | 
|  | continue; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (file_type == file::FileType::kDirectory) { | 
|  | subdirs.push_back(std::move(file_name)); | 
|  | } else { | 
|  | files.push_back(std::move(file_name)); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Now process subdirs. | 
|  | for (const std::string& subdir : subdirs) { | 
|  | std::string full_subdir{root_dir}; | 
|  | AppendPath(&full_subdir, subdir); | 
|  | std::optional<std::vector<std::string>> subfiles = FindFiles(full_subdir, diag, filter); | 
|  | if (!subfiles) { | 
|  | return {}; | 
|  | } | 
|  |  | 
|  | for (const std::string& subfile : subfiles.value()) { | 
|  | std::string new_file = subdir; | 
|  | AppendPath(&new_file, subfile); | 
|  | files.push_back(new_file); | 
|  | } | 
|  | } | 
|  | return files; | 
|  | } | 
|  |  | 
|  | }  // namespace file | 
|  | }  // namespace aapt |