| /* |
| * 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, ' ')) { |
| line = util::TrimWhitespace(line); |
| if (!line.empty()) { |
| out_arglist->emplace_back(line); |
| } |
| } |
| 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, ' ')) { |
| line = util::TrimWhitespace(line); |
| if (!line.empty()) { |
| out_argset->emplace(line); |
| } |
| } |
| 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 |