Ninja
disk_interface.cc
Go to the documentation of this file.
1 // Copyright 2011 Google Inc. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "disk_interface.h"
16 
17 #include <algorithm>
18 
19 #include <errno.h>
20 #include <stdio.h>
21 #include <string.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 
25 #ifdef _WIN32
26 #include <direct.h> // _mkdir
27 #include <windows.h>
28 
29 #include <sstream>
30 #else
31 #include <unistd.h>
32 #endif
33 
34 #include "metrics.h"
35 #include "util.h"
36 
37 using namespace std;
38 
39 namespace {
40 
41 string DirName(const string& path) {
42 #ifdef _WIN32
43  static const char kPathSeparators[] = "\\/";
44 #else
45  static const char kPathSeparators[] = "/";
46 #endif
47  static const char* const kEnd = kPathSeparators + sizeof(kPathSeparators) - 1;
48 
49  string::size_type slash_pos = path.find_last_of(kPathSeparators);
50  if (slash_pos == string::npos)
51  return string(); // Nothing to do.
52  while (slash_pos > 0 &&
53  std::find(kPathSeparators, kEnd, path[slash_pos - 1]) != kEnd)
54  --slash_pos;
55  return path.substr(0, slash_pos);
56 }
57 
58 int MakeDir(const string& path) {
59 #ifdef _WIN32
60  return _mkdir(path.c_str());
61 #else
62  return mkdir(path.c_str(), 0777);
63 #endif
64 }
65 
66 #ifdef _WIN32
67 TimeStamp TimeStampFromFileTime(const FILETIME& filetime) {
68  // FILETIME is in 100-nanosecond increments since the Windows epoch.
69  // We don't much care about epoch correctness but we do want the
70  // resulting value to fit in a 64-bit integer.
71  uint64_t mtime = ((uint64_t)filetime.dwHighDateTime << 32) |
72  ((uint64_t)filetime.dwLowDateTime);
73  // 1600 epoch -> 2000 epoch (subtract 400 years).
74  return (TimeStamp)mtime - 12622770400LL * (1000000000LL / 100);
75 }
76 
77 TimeStamp StatSingleFile(const string& path, string* err) {
78  WIN32_FILE_ATTRIBUTE_DATA attrs;
79  if (!GetFileAttributesExA(path.c_str(), GetFileExInfoStandard, &attrs)) {
80  DWORD win_err = GetLastError();
81  if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND)
82  return 0;
83  *err = "GetFileAttributesEx(" + path + "): " + GetLastErrorString();
84  return -1;
85  }
86  return TimeStampFromFileTime(attrs.ftLastWriteTime);
87 }
88 
89 bool IsWindows7OrLater() {
90  OSVERSIONINFOEX version_info =
91  { sizeof(OSVERSIONINFOEX), 6, 1, 0, 0, {0}, 0, 0, 0, 0, 0};
92  DWORDLONG comparison = 0;
93  VER_SET_CONDITION(comparison, VER_MAJORVERSION, VER_GREATER_EQUAL);
94  VER_SET_CONDITION(comparison, VER_MINORVERSION, VER_GREATER_EQUAL);
95  return VerifyVersionInfo(
96  &version_info, VER_MAJORVERSION | VER_MINORVERSION, comparison);
97 }
98 
99 bool StatAllFilesInDir(const string& dir, map<string, TimeStamp>* stamps,
100  string* err) {
101  // FindExInfoBasic is 30% faster than FindExInfoStandard.
102  static bool can_use_basic_info = IsWindows7OrLater();
103  // This is not in earlier SDKs.
104  const FINDEX_INFO_LEVELS kFindExInfoBasic =
105  static_cast<FINDEX_INFO_LEVELS>(1);
106  FINDEX_INFO_LEVELS level =
107  can_use_basic_info ? kFindExInfoBasic : FindExInfoStandard;
108  WIN32_FIND_DATAA ffd;
109  HANDLE find_handle = FindFirstFileExA((dir + "\\*").c_str(), level, &ffd,
110  FindExSearchNameMatch, NULL, 0);
111 
112  if (find_handle == INVALID_HANDLE_VALUE) {
113  DWORD win_err = GetLastError();
114  if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND ||
115  win_err == ERROR_DIRECTORY)
116  return true;
117  *err = "FindFirstFileExA(" + dir + "): " + GetLastErrorString();
118  return false;
119  }
120  do {
121  string lowername = ffd.cFileName;
122  if (lowername == "..") {
123  // Seems to just copy the timestamp for ".." from ".", which is wrong.
124  // This is the case at least on NTFS under Windows 7.
125  continue;
126  }
127  transform(lowername.begin(), lowername.end(), lowername.begin(), ::tolower);
128  stamps->insert(make_pair(lowername,
129  TimeStampFromFileTime(ffd.ftLastWriteTime)));
130  } while (FindNextFileA(find_handle, &ffd));
131  FindClose(find_handle);
132  return true;
133 }
134 #endif // _WIN32
135 
136 } // namespace
137 
138 // DiskInterface ---------------------------------------------------------------
139 
140 bool DiskInterface::MakeDirs(const string& path) {
141  string dir = DirName(path);
142  if (dir.empty())
143  return true; // Reached root; assume it's there.
144  string err;
145  TimeStamp mtime = Stat(dir, &err);
146  if (mtime < 0) {
147  Error("%s", err.c_str());
148  return false;
149  }
150  if (mtime > 0)
151  return true; // Exists already; we're done.
152 
153  // Directory doesn't exist. Try creating its parent first.
154  bool success = MakeDirs(dir);
155  if (!success)
156  return false;
157  return MakeDir(dir);
158 }
159 
160 // RealDiskInterface -----------------------------------------------------------
162 #ifdef _WIN32
163 : use_cache_(false), long_paths_enabled_(false) {
164  // Probe ntdll.dll for RtlAreLongPathsEnabled, and call it if it exists.
165  HINSTANCE ntdll_lib = ::GetModuleHandleW(L"ntdll");
166  if (ntdll_lib) {
167  typedef BOOLEAN(WINAPI FunctionType)();
168  auto* func_ptr = FunctionCast<FunctionType*>(
169  ::GetProcAddress(ntdll_lib, "RtlAreLongPathsEnabled"));
170  if (func_ptr) {
171  long_paths_enabled_ = (*func_ptr)();
172  }
173  }
174 }
175 #else
176 {}
177 #endif
178 
179 TimeStamp RealDiskInterface::Stat(const string& path, string* err) const {
180  METRIC_RECORD("node stat");
181 #ifdef _WIN32
182  // MSDN: "Naming Files, Paths, and Namespaces"
183  // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
184  if (!path.empty() && !AreLongPathsEnabled() && path[0] != '\\' &&
185  path.size() > MAX_PATH) {
186  ostringstream err_stream;
187  err_stream << "Stat(" << path << "): Filename longer than " << MAX_PATH
188  << " characters";
189  *err = err_stream.str();
190  return -1;
191  }
192  if (!use_cache_)
193  return StatSingleFile(path, err);
194 
195  string dir = DirName(path);
196  string base(path.substr(dir.size() ? dir.size() + 1 : 0));
197  if (base == "..") {
198  // StatAllFilesInDir does not report any information for base = "..".
199  base = ".";
200  dir = path;
201  }
202 
203  string dir_lowercase = dir;
204  transform(dir.begin(), dir.end(), dir_lowercase.begin(), ::tolower);
205  transform(base.begin(), base.end(), base.begin(), ::tolower);
206 
207  Cache::iterator ci = cache_.find(dir_lowercase);
208  if (ci == cache_.end()) {
209  ci = cache_.insert(make_pair(dir_lowercase, DirCache())).first;
210  if (!StatAllFilesInDir(dir.empty() ? "." : dir, &ci->second, err)) {
211  cache_.erase(ci);
212  return -1;
213  }
214  }
215  DirCache::iterator di = ci->second.find(base);
216  return di != ci->second.end() ? di->second : 0;
217 #else
218 #ifdef __USE_LARGEFILE64
219  struct stat64 st;
220  if (stat64(path.c_str(), &st) < 0) {
221 #else
222  struct stat st;
223  if (stat(path.c_str(), &st) < 0) {
224 #endif
225  if (errno == ENOENT || errno == ENOTDIR)
226  return 0;
227  *err = "stat(" + path + "): " + strerror(errno);
228  return -1;
229  }
230  // Some users (Flatpak) set mtime to 0, this should be harmless
231  // and avoids conflicting with our return value of 0 meaning
232  // that it doesn't exist.
233  if (st.st_mtime == 0)
234  return 1;
235 #if defined(_AIX)
236  return (int64_t)st.st_mtime * 1000000000LL + st.st_mtime_n;
237 #elif defined(__APPLE__)
238  return ((int64_t)st.st_mtimespec.tv_sec * 1000000000LL +
239  st.st_mtimespec.tv_nsec);
240 #elif defined(st_mtime) // A macro, so we're likely on modern POSIX.
241  return (int64_t)st.st_mtim.tv_sec * 1000000000LL + st.st_mtim.tv_nsec;
242 #else
243  return (int64_t)st.st_mtime * 1000000000LL + st.st_mtimensec;
244 #endif
245 #endif
246 }
247 
248 bool RealDiskInterface::WriteFile(const string& path, const string& contents,
249  bool crlf_on_windows) {
250  FILE* fp = fopen(path.c_str(),
251 #ifdef _WIN32
252  crlf_on_windows ? "w" : "wb");
253 #else
254  "wb");
255  (void)crlf_on_windows;
256 #endif
257  if (fp == NULL) {
258  Error("WriteFile(%s): Unable to create file. %s",
259  path.c_str(), strerror(errno));
260  return false;
261  }
262 
263  if (fwrite(contents.data(), 1, contents.length(), fp) < contents.length()) {
264  Error("WriteFile(%s): Unable to write to the file. %s",
265  path.c_str(), strerror(errno));
266  fclose(fp);
267  return false;
268  }
269 
270  if (fclose(fp) == EOF) {
271  Error("WriteFile(%s): Unable to close the file. %s",
272  path.c_str(), strerror(errno));
273  return false;
274  }
275 
276  return true;
277 }
278 
279 bool RealDiskInterface::MakeDir(const string& path) {
280  if (::MakeDir(path) < 0) {
281  if (errno == EEXIST) {
282  return true;
283  }
284  Error("mkdir(%s): %s", path.c_str(), strerror(errno));
285  return false;
286  }
287  return true;
288 }
289 
291  string* contents,
292  string* err) {
293  switch (::ReadFile(path, contents, err)) {
294  case 0: return Okay;
295  case -ENOENT: return NotFound;
296  default: return OtherError;
297  }
298 }
299 
300 int RealDiskInterface::RemoveFile(const string& path) {
301 #ifdef _WIN32
302  DWORD attributes = GetFileAttributesA(path.c_str());
303  if (attributes == INVALID_FILE_ATTRIBUTES) {
304  DWORD win_err = GetLastError();
305  if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) {
306  return 1;
307  }
308  } else if (attributes & FILE_ATTRIBUTE_READONLY) {
309  // On non-Windows systems, remove() will happily delete read-only files.
310  // On Windows Ninja should behave the same:
311  // https://github.com/ninja-build/ninja/issues/1886
312  // Skip error checking. If this fails, accept whatever happens below.
313  SetFileAttributesA(path.c_str(), attributes & ~FILE_ATTRIBUTE_READONLY);
314  }
315  if (attributes & FILE_ATTRIBUTE_DIRECTORY) {
316  // remove() deletes both files and directories. On Windows we have to
317  // select the correct function (DeleteFile will yield Permission Denied when
318  // used on a directory)
319  // This fixes the behavior of ninja -t clean in some cases
320  // https://github.com/ninja-build/ninja/issues/828
321  if (!RemoveDirectoryA(path.c_str())) {
322  DWORD win_err = GetLastError();
323  if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) {
324  return 1;
325  }
326  // Report remove(), not RemoveDirectory(), for cross-platform consistency.
327  Error("remove(%s): %s", path.c_str(), GetLastErrorString().c_str());
328  return -1;
329  }
330  } else {
331  if (!DeleteFileA(path.c_str())) {
332  DWORD win_err = GetLastError();
333  if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) {
334  return 1;
335  }
336  // Report as remove(), not DeleteFile(), for cross-platform consistency.
337  Error("remove(%s): %s", path.c_str(), GetLastErrorString().c_str());
338  return -1;
339  }
340  }
341 #else
342  if (remove(path.c_str()) < 0) {
343  switch (errno) {
344  case ENOENT:
345  return 1;
346  default:
347  Error("remove(%s): %s", path.c_str(), strerror(errno));
348  return -1;
349  }
350  }
351 #endif
352  return 0;
353 }
354 
356 #ifdef _WIN32
357  use_cache_ = allow;
358  if (!use_cache_)
359  cache_.clear();
360 #endif
361 }
362 
363 #ifdef _WIN32
364 bool RealDiskInterface::AreLongPathsEnabled(void) const {
365  return long_paths_enabled_;
366 }
367 #endif
#define METRIC_RECORD(name)
The primary interface to metrics.
Definition: metrics.h:83
Definition: hash_map.h:26
bool MakeDirs(const std::string &path)
Create all the parent directories for path; like mkdir -p basename path.
Status
Result of ReadFile.
Status ReadFile(const std::string &path, std::string *contents, std::string *err) override
Read and store in given string.
void AllowStatCache(bool allow)
Whether stat information can be cached. Only has an effect on Windows.
bool WriteFile(const std::string &path, const std::string &contents, bool crlf_on_windows) override
Create a file, with the specified name and contents If crlf_on_windows is true, will be converted t...
int RemoveFile(const std::string &path) override
Remove the file named path.
TimeStamp Stat(const std::string &path, std::string *err) const override
stat() a file, returning the mtime, or 0 if missing and -1 on other errors.
bool MakeDir(const std::string &path) override
Create a directory, returning false on failure.
int64_t TimeStamp
Definition: timestamp.h:31
void Error(const char *msg, va_list ap)
Definition: util.cc:98
int ReadFile(const string &path, string *contents, string *err)
Definition: util.cc:415
unsigned long long uint64_t
Definition: win32port.h:29
signed long long int64_t
A 64-bit integer type.
Definition: win32port.h:28