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
37using namespace std;
38
39namespace {
40
41string 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
58int 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
67TimeStamp 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
77TimeStamp 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
89bool 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
99bool 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
140bool 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
179TimeStamp 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
248bool 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
279bool 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
300int 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
364bool 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
virtual bool MakeDir(const std::string &path)=0
Create a directory, returning false on failure.
bool MakeDirs(const std::string &path)
Create all the parent directories for path; like mkdir -p basename path.
virtual TimeStamp Stat(const std::string &path, std::string *err) const =0
stat() a file, returning the mtime, or 0 if missing and -1 on other errors.
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
unsigned long long uint64_t
Definition win32port.h:29
signed long long int64_t
A 64-bit integer type.
Definition win32port.h:28