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