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