/* * 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 #include #include #include #include #include #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 #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(const 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(const 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) { last_dir_sep = c + 1; } } return StringPiece(last_dir_sep, end - last_dir_sep); } StringPiece GetExtension(const 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(const 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&& args) { if (args.empty()) { return ""; } std::string out = args[0].to_string(); for (int i = 1; i < args.size(); i++) { file::AppendPath(&out, args[i]); } return out; } std::string PackageToPath(const StringPiece& package) { std::string out_path; for (const StringPiece& part : util::Tokenize(package, '.')) { AppendPath(&out_path, part); } return out_path; } Maybe 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(const StringPiece& path, std::vector* out_arglist, std::string* out_error) { std::string contents; if (!ReadFileToString(path.to_string(), &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->push_back(line.to_string()); } } return true; } bool AppendSetArgsFromFile(const StringPiece& path, std::unordered_set* out_argset, std::string* out_error) { std::string contents; if(!ReadFileToString(path.to_string(), &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->insert(line.to_string()); } } return true; } bool FileFilter::SetPattern(const 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(DiagMessage() << "skipping " << (type == FileType::kDirectory ? "dir '" : "file '") << filename << "' due to ignore pattern '" << token << "'"); } return false; } } return true; } Maybe> FindFiles(const android::StringPiece& path, IDiagnostics* diag, const FileFilter* filter) { const std::string root_dir = path.to_string(); std::unique_ptr d(opendir(root_dir.data()), closedir); if (!d) { diag->Error(DiagMessage() << SystemErrorCodeToString(errno)); return {}; } std::vector files; std::vector 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); Maybe> 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