| /* |
| * 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 "util/Util.h" |
| |
| #include <cerrno> |
| #include <cstdio> |
| #include <dirent.h> |
| #include <string> |
| #include <sys/stat.h> |
| |
| #ifdef _WIN32 |
| // Windows includes. |
| #include <direct.h> |
| #endif |
| |
| namespace aapt { |
| namespace file { |
| |
| FileType getFileType(const StringPiece& path) { |
| struct stat sb; |
| if (stat(path.data(), &sb) < 0) { |
| 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; |
| } |
| } |
| |
| std::vector<std::string> listFiles(const StringPiece& root, std::string* outError) { |
| DIR* dir = opendir(root.data()); |
| if (dir == nullptr) { |
| if (outError) { |
| std::stringstream errorStr; |
| errorStr << "unable to open file: " << strerror(errno); |
| *outError = errorStr.str(); |
| return {}; |
| } |
| } |
| |
| std::vector<std::string> files; |
| dirent* entry; |
| while ((entry = readdir(dir))) { |
| files.emplace_back(entry->d_name); |
| } |
| |
| closedir(dir); |
| return files; |
| } |
| |
| inline static int mkdirImpl(const StringPiece& path) { |
| #ifdef _WIN32 |
| return _mkdir(path.toString().c_str()); |
| #else |
| return mkdir(path.toString().c_str(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP); |
| #endif |
| } |
| |
| bool mkdirs(const StringPiece& path) { |
| const char* start = path.begin(); |
| const char* end = path.end(); |
| for (const char* current = start; current != end; ++current) { |
| if (*current == sDirSep && current != start) { |
| StringPiece parentPath(start, current - start); |
| int result = mkdirImpl(parentPath); |
| if (result < 0 && errno != EEXIST) { |
| return false; |
| } |
| } |
| } |
| return mkdirImpl(path) == 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* lastDirSep = path.begin(); |
| for (const char* c = path.begin(); c != end; ++c) { |
| if (*c == sDirSep) { |
| lastDirSep = c + 1; |
| } |
| } |
| return StringPiece(lastDirSep, end - lastDirSep); |
| } |
| |
| 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 {}; |
| } |
| |
| void appendPath(std::string* base, StringPiece part) { |
| assert(base); |
| const bool baseHasTrailingSep = (!base->empty() && *(base->end() - 1) == sDirSep); |
| const bool partHasLeadingSep = (!part.empty() && *(part.begin()) == sDirSep); |
| if (baseHasTrailingSep && partHasLeadingSep) { |
| // Remove the part's leading sep |
| part = part.substr(1, part.size() - 1); |
| } else if (!baseHasTrailingSep && !partHasLeadingSep) { |
| // None of the pieces has a separator. |
| *base += sDirSep; |
| } |
| base->append(part.data(), part.size()); |
| } |
| |
| std::string packageToPath(const StringPiece& package) { |
| std::string outPath; |
| for (StringPiece part : util::tokenize<char>(package, '.')) { |
| appendPath(&outPath, part); |
| } |
| return outPath; |
| } |
| |
| Maybe<android::FileMap> mmapPath(const StringPiece& path, std::string* outError) { |
| std::unique_ptr<FILE, decltype(fclose)*> f = { fopen(path.data(), "rb"), fclose }; |
| if (!f) { |
| if (outError) *outError = strerror(errno); |
| return {}; |
| } |
| |
| int fd = fileno(f.get()); |
| |
| struct stat fileStats = {}; |
| if (fstat(fd, &fileStats) != 0) { |
| if (outError) *outError = strerror(errno); |
| return {}; |
| } |
| |
| android::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.data(), fd, 0, fileStats.st_size, true)) { |
| if (outError) *outError = strerror(errno); |
| return {}; |
| } |
| return std::move(fileMap); |
| } |
| |
| bool FileFilter::setPattern(const StringPiece& pattern) { |
| mPatternTokens = 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 filenameLen = filename.length(); |
| bool chatty = true; |
| for (const std::string& token : mPatternTokens) { |
| const char* tokenStr = token.c_str(); |
| if (*tokenStr == '!') { |
| chatty = false; |
| tokenStr++; |
| } |
| |
| if (strncasecmp(tokenStr, kDir, sizeof(kDir)) == 0) { |
| if (type != FileType::kDirectory) { |
| continue; |
| } |
| tokenStr += sizeof(kDir); |
| } |
| |
| if (strncasecmp(tokenStr, kFile, sizeof(kFile)) == 0) { |
| if (type != FileType::kRegular) { |
| continue; |
| } |
| tokenStr += sizeof(kFile); |
| } |
| |
| bool ignore = false; |
| size_t n = strlen(tokenStr); |
| if (*tokenStr == '*') { |
| // Math suffix. |
| tokenStr++; |
| n--; |
| if (n <= filenameLen) { |
| ignore = strncasecmp(tokenStr, filename.c_str() + filenameLen - n, n) == 0; |
| } |
| } else if (n > 1 && tokenStr[n - 1] == '*') { |
| // Match prefix. |
| ignore = strncasecmp(tokenStr, filename.c_str(), n - 1) == 0; |
| } else { |
| ignore = strcasecmp(tokenStr, filename.c_str()) == 0; |
| } |
| |
| if (ignore) { |
| if (chatty) { |
| mDiag->warn(DiagMessage() << "skipping " |
| << (type == FileType::kDirectory ? "dir '" : "file '") |
| << filename << "' due to ignore pattern '" |
| << token << "'"); |
| } |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| } // namespace file |
| } // namespace aapt |