| // Copyright 2011 Google Inc. All Rights Reserved. |
| // |
| // 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 "disk_interface.h" |
| |
| #include <algorithm> |
| |
| #include <errno.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #ifdef _WIN32 |
| #include <sstream> |
| #include <windows.h> |
| #include <direct.h> // _mkdir |
| #endif |
| |
| #include "metrics.h" |
| #include "util.h" |
| |
| namespace { |
| |
| string DirName(const string& path) { |
| #ifdef _WIN32 |
| static const char kPathSeparators[] = "\\/"; |
| #else |
| static const char kPathSeparators[] = "/"; |
| #endif |
| static const char* const kEnd = kPathSeparators + sizeof(kPathSeparators) - 1; |
| |
| string::size_type slash_pos = path.find_last_of(kPathSeparators); |
| if (slash_pos == string::npos) |
| return string(); // Nothing to do. |
| while (slash_pos > 0 && |
| std::find(kPathSeparators, kEnd, path[slash_pos - 1]) != kEnd) |
| --slash_pos; |
| return path.substr(0, slash_pos); |
| } |
| |
| int MakeDir(const string& path) { |
| #ifdef _WIN32 |
| return _mkdir(path.c_str()); |
| #else |
| return mkdir(path.c_str(), 0777); |
| #endif |
| } |
| |
| #ifdef _WIN32 |
| TimeStamp TimeStampFromFileTime(const FILETIME& filetime) { |
| // FILETIME is in 100-nanosecond increments since the Windows epoch. |
| // We don't much care about epoch correctness but we do want the |
| // resulting value to fit in a 64-bit integer. |
| uint64_t mtime = ((uint64_t)filetime.dwHighDateTime << 32) | |
| ((uint64_t)filetime.dwLowDateTime); |
| // 1600 epoch -> 2000 epoch (subtract 400 years). |
| return (TimeStamp)mtime - 12622770400LL * (1000000000LL / 100); |
| } |
| |
| TimeStamp StatSingleFile(const string& path, string* err) { |
| WIN32_FILE_ATTRIBUTE_DATA attrs; |
| if (!GetFileAttributesExA(path.c_str(), GetFileExInfoStandard, &attrs)) { |
| DWORD win_err = GetLastError(); |
| if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) |
| return 0; |
| *err = "GetFileAttributesEx(" + path + "): " + GetLastErrorString(); |
| return -1; |
| } |
| return TimeStampFromFileTime(attrs.ftLastWriteTime); |
| } |
| |
| bool IsWindows7OrLater() { |
| OSVERSIONINFOEX version_info = |
| { sizeof(OSVERSIONINFOEX), 6, 1, 0, 0, {0}, 0, 0, 0, 0, 0}; |
| DWORDLONG comparison = 0; |
| VER_SET_CONDITION(comparison, VER_MAJORVERSION, VER_GREATER_EQUAL); |
| VER_SET_CONDITION(comparison, VER_MINORVERSION, VER_GREATER_EQUAL); |
| return VerifyVersionInfo( |
| &version_info, VER_MAJORVERSION | VER_MINORVERSION, comparison); |
| } |
| |
| bool StatAllFilesInDir(const string& dir, map<string, TimeStamp>* stamps, |
| string* err) { |
| // FindExInfoBasic is 30% faster than FindExInfoStandard. |
| static bool can_use_basic_info = IsWindows7OrLater(); |
| // This is not in earlier SDKs. |
| const FINDEX_INFO_LEVELS kFindExInfoBasic = |
| static_cast<FINDEX_INFO_LEVELS>(1); |
| FINDEX_INFO_LEVELS level = |
| can_use_basic_info ? kFindExInfoBasic : FindExInfoStandard; |
| WIN32_FIND_DATAA ffd; |
| HANDLE find_handle = FindFirstFileExA((dir + "\\*").c_str(), level, &ffd, |
| FindExSearchNameMatch, NULL, 0); |
| |
| if (find_handle == INVALID_HANDLE_VALUE) { |
| DWORD win_err = GetLastError(); |
| if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) |
| return true; |
| *err = "FindFirstFileExA(" + dir + "): " + GetLastErrorString(); |
| return false; |
| } |
| do { |
| string lowername = ffd.cFileName; |
| if (lowername == "..") { |
| // Seems to just copy the timestamp for ".." from ".", which is wrong. |
| // This is the case at least on NTFS under Windows 7. |
| continue; |
| } |
| transform(lowername.begin(), lowername.end(), lowername.begin(), ::tolower); |
| stamps->insert(make_pair(lowername, |
| TimeStampFromFileTime(ffd.ftLastWriteTime))); |
| } while (FindNextFileA(find_handle, &ffd)); |
| FindClose(find_handle); |
| return true; |
| } |
| #endif // _WIN32 |
| |
| } // namespace |
| |
| // DiskInterface --------------------------------------------------------------- |
| |
| bool DiskInterface::MakeDirs(const string& path) { |
| string dir = DirName(path); |
| if (dir.empty()) |
| return true; // Reached root; assume it's there. |
| string err; |
| TimeStamp mtime = Stat(dir, &err); |
| if (mtime < 0) { |
| Error("%s", err.c_str()); |
| return false; |
| } |
| if (mtime > 0) |
| return true; // Exists already; we're done. |
| |
| // Directory doesn't exist. Try creating its parent first. |
| bool success = MakeDirs(dir); |
| if (!success) |
| return false; |
| return MakeDir(dir); |
| } |
| |
| // RealDiskInterface ----------------------------------------------------------- |
| |
| #ifndef _WIN32 |
| static TimeStamp StatTimestamp(const struct stat& st) { |
| // Some users (Flatpak) set mtime to 0, this should be harmless |
| // and avoids conflicting with our return value of 0 meaning |
| // that it doesn't exist. |
| if (st.st_mtime == 0) |
| return 1; |
| #if defined(_AIX) |
| return (int64_t)st.st_mtime * 1000000000LL + st.st_mtime_n; |
| #elif defined(__APPLE__) |
| return ((int64_t)st.st_mtimespec.tv_sec * 1000000000LL + |
| st.st_mtimespec.tv_nsec); |
| #elif defined(st_mtime) // A macro, so we're likely on modern POSIX. |
| return (int64_t)st.st_mtim.tv_sec * 1000000000LL + st.st_mtim.tv_nsec; |
| #else |
| return (int64_t)st.st_mtime * 1000000000LL + st.st_mtimensec; |
| #endif |
| } |
| #endif |
| |
| /// This function is thread-safe on Unix but not on Windows. See |
| /// RealDiskInterface::IsStatThreadSafe. |
| TimeStamp RealDiskInterface::Stat(const string& path, string* err) const { |
| METRIC_RECORD("node stat"); |
| #ifdef _WIN32 |
| // MSDN: "Naming Files, Paths, and Namespaces" |
| // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx |
| if (!path.empty() && path[0] != '\\' && path.size() > MAX_PATH) { |
| ostringstream err_stream; |
| err_stream << "Stat(" << path << "): Filename longer than " << MAX_PATH |
| << " characters"; |
| *err = err_stream.str(); |
| return -1; |
| } |
| if (!use_cache_) |
| return StatSingleFile(path, err); |
| |
| string dir = DirName(path); |
| string base(path.substr(dir.size() ? dir.size() + 1 : 0)); |
| if (base == "..") { |
| // StatAllFilesInDir does not report any information for base = "..". |
| base = "."; |
| dir = path; |
| } |
| |
| transform(dir.begin(), dir.end(), dir.begin(), ::tolower); |
| transform(base.begin(), base.end(), base.begin(), ::tolower); |
| |
| Cache::iterator ci = cache_.find(dir); |
| if (ci == cache_.end()) { |
| ci = cache_.insert(make_pair(dir, DirCache())).first; |
| if (!StatAllFilesInDir(dir.empty() ? "." : dir, &ci->second, err)) { |
| cache_.erase(ci); |
| return -1; |
| } |
| } |
| DirCache::iterator di = ci->second.find(base); |
| return di != ci->second.end() ? di->second : 0; |
| #else |
| struct stat st; |
| if (stat(path.c_str(), &st) < 0) { |
| if (errno == ENOENT || errno == ENOTDIR) |
| return 0; |
| *err = "stat(" + path + "): " + strerror(errno); |
| return -1; |
| } |
| return StatTimestamp(st); |
| #endif |
| } |
| |
| TimeStamp RealDiskInterface::LStat( |
| const string& path, bool* is_dir, bool* is_symlink, string* err) const { |
| METRIC_RECORD("node lstat"); |
| #ifdef _WIN32 |
| #error unimplemented |
| #else |
| struct stat st; |
| if (lstat(path.c_str(), &st) < 0) { |
| if (errno == ENOENT || errno == ENOTDIR) |
| return 0; |
| *err = "lstat(" + path + "): " + strerror(errno); |
| return -1; |
| } |
| if (is_dir != nullptr) { |
| *is_dir = S_ISDIR(st.st_mode); |
| } |
| |
| if (is_symlink != nullptr) { |
| *is_symlink = S_ISLNK(st.st_mode); |
| } |
| return StatTimestamp(st); |
| #endif |
| } |
| |
| bool RealDiskInterface::IsStatThreadSafe() const { |
| #ifdef _WIN32 |
| return false; |
| #else |
| return true; |
| #endif |
| } |
| |
| bool RealDiskInterface::WriteFile(const string& path, const string& contents) { |
| FILE* fp = fopen(path.c_str(), "w"); |
| if (fp == NULL) { |
| Error("WriteFile(%s): Unable to create file. %s", |
| path.c_str(), strerror(errno)); |
| return false; |
| } |
| |
| if (fwrite(contents.data(), 1, contents.length(), fp) < contents.length()) { |
| Error("WriteFile(%s): Unable to write to the file. %s", |
| path.c_str(), strerror(errno)); |
| fclose(fp); |
| return false; |
| } |
| |
| if (fclose(fp) == EOF) { |
| Error("WriteFile(%s): Unable to close the file. %s", |
| path.c_str(), strerror(errno)); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool RealDiskInterface::MakeDir(const string& path) { |
| if (::MakeDir(path) < 0) { |
| if (errno == EEXIST) { |
| return true; |
| } |
| Error("mkdir(%s): %s", path.c_str(), strerror(errno)); |
| return false; |
| } |
| return true; |
| } |
| |
| FileReader::Status RealDiskInterface::ReadFile(const string& path, |
| string* contents, |
| string* err) { |
| switch (::ReadFile(path, contents, err)) { |
| case 0: return Okay; |
| case -ENOENT: return NotFound; |
| default: return OtherError; |
| } |
| } |
| |
| FileReader::Status RealDiskInterface::LoadFile(const std::string& path, |
| std::unique_ptr<LoadedFile>* result, |
| std::string* err) { |
| #ifdef _WIN32 |
| #error "LoadFile is not implemented yet on Windows" |
| #else |
| struct MappedFile : LoadedFile { |
| MappedFile(const std::string& filename, std::unique_ptr<Mapping> mapping) |
| : mapping_(std::move(mapping)) { |
| filename_ = filename; |
| content_with_nul_ = { mapping_->base(), mapping_->file_size() + 1 }; |
| } |
| std::unique_ptr<Mapping> mapping_; |
| }; |
| |
| std::unique_ptr<Mapping> mapping; |
| switch (::MapFile(path, &mapping, err)) { |
| case 0: |
| *result = std::unique_ptr<MappedFile>(new MappedFile(path, |
| std::move(mapping))); |
| return Okay; |
| case -ENOENT: |
| return NotFound; |
| default: |
| return OtherError; |
| } |
| #endif |
| } |
| |
| int RealDiskInterface::RemoveFile(const string& path) { |
| if (remove(path.c_str()) < 0) { |
| switch (errno) { |
| case ENOENT: |
| return 1; |
| default: |
| Error("remove(%s): %s", path.c_str(), strerror(errno)); |
| return -1; |
| } |
| } else { |
| return 0; |
| } |
| } |
| |
| void RealDiskInterface::AllowStatCache(bool allow) { |
| #ifdef _WIN32 |
| use_cache_ = allow; |
| if (!use_cache_) |
| cache_.clear(); |
| #endif |
| } |