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