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
DirName(const string & path)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
MakeDir(const string & path)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
TimeStampFromFileTime(const FILETIME & filetime)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
StatSingleFile(const string & path,string * err)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
IsWindows7OrLater()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
StatAllFilesInDir(const string & dir,map<string,TimeStamp> * stamps,string * err)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
MakeDirs(const string & path)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 -----------------------------------------------------------
RealDiskInterface()161 RealDiskInterface::RealDiskInterface()
162 #ifdef _WIN32
163 : use_cache_(false), long_paths_enabled_(false) {
164 setlocale(LC_ALL, "");
165
166 // Probe ntdll.dll for RtlAreLongPathsEnabled, and call it if it exists.
167 HINSTANCE ntdll_lib = ::GetModuleHandleW(L"ntdll");
168 if (ntdll_lib) {
169 typedef BOOLEAN(WINAPI FunctionType)();
170 auto* func_ptr = reinterpret_cast<FunctionType*>(
171 ::GetProcAddress(ntdll_lib, "RtlAreLongPathsEnabled"));
172 if (func_ptr) {
173 long_paths_enabled_ = (*func_ptr)();
174 }
175 }
176 }
177 #else
178 {}
179 #endif
180
Stat(const string & path,string * err) const181 TimeStamp RealDiskInterface::Stat(const string& path, string* err) const {
182 METRIC_RECORD("node stat");
183 #ifdef _WIN32
184 // MSDN: "Naming Files, Paths, and Namespaces"
185 // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
186 if (!path.empty() && !AreLongPathsEnabled() && path[0] != '\\' &&
187 path.size() > MAX_PATH) {
188 ostringstream err_stream;
189 err_stream << "Stat(" << path << "): Filename longer than " << MAX_PATH
190 << " characters";
191 *err = err_stream.str();
192 return -1;
193 }
194 if (!use_cache_)
195 return StatSingleFile(path, err);
196
197 string dir = DirName(path);
198 string base(path.substr(dir.size() ? dir.size() + 1 : 0));
199 if (base == "..") {
200 // StatAllFilesInDir does not report any information for base = "..".
201 base = ".";
202 dir = path;
203 }
204
205 string dir_lowercase = dir;
206 transform(dir.begin(), dir.end(), dir_lowercase.begin(), ::tolower);
207 transform(base.begin(), base.end(), base.begin(), ::tolower);
208
209 Cache::iterator ci = cache_.find(dir_lowercase);
210 if (ci == cache_.end()) {
211 ci = cache_.insert(make_pair(dir_lowercase, DirCache())).first;
212 if (!StatAllFilesInDir(dir.empty() ? "." : dir, &ci->second, err)) {
213 cache_.erase(ci);
214 return -1;
215 }
216 }
217 DirCache::iterator di = ci->second.find(base);
218 return di != ci->second.end() ? di->second : 0;
219 #else
220 #ifdef __USE_LARGEFILE64
221 struct stat64 st;
222 if (stat64(path.c_str(), &st) < 0) {
223 #else
224 struct stat st;
225 if (stat(path.c_str(), &st) < 0) {
226 #endif
227 if (errno == ENOENT || errno == ENOTDIR)
228 return 0;
229 *err = "stat(" + path + "): " + strerror(errno);
230 return -1;
231 }
232 // Some users (Flatpak) set mtime to 0, this should be harmless
233 // and avoids conflicting with our return value of 0 meaning
234 // that it doesn't exist.
235 if (st.st_mtime == 0)
236 return 1;
237 #if defined(_AIX)
238 return (int64_t)st.st_mtime * 1000000000LL + st.st_mtime_n;
239 #elif defined(__APPLE__)
240 return ((int64_t)st.st_mtimespec.tv_sec * 1000000000LL +
241 st.st_mtimespec.tv_nsec);
242 #elif defined(st_mtime) // A macro, so we're likely on modern POSIX.
243 return (int64_t)st.st_mtim.tv_sec * 1000000000LL + st.st_mtim.tv_nsec;
244 #else
245 return (int64_t)st.st_mtime * 1000000000LL + st.st_mtimensec;
246 #endif
247 #endif
248 }
249
250 bool RealDiskInterface::WriteFile(const string& path, const string& contents) {
251 FILE* fp = fopen(path.c_str(), "w");
252 if (fp == NULL) {
253 Error("WriteFile(%s): Unable to create file. %s",
254 path.c_str(), strerror(errno));
255 return false;
256 }
257
258 if (fwrite(contents.data(), 1, contents.length(), fp) < contents.length()) {
259 Error("WriteFile(%s): Unable to write to the file. %s",
260 path.c_str(), strerror(errno));
261 fclose(fp);
262 return false;
263 }
264
265 if (fclose(fp) == EOF) {
266 Error("WriteFile(%s): Unable to close the file. %s",
267 path.c_str(), strerror(errno));
268 return false;
269 }
270
271 return true;
272 }
273
274 bool RealDiskInterface::MakeDir(const string& path) {
275 if (::MakeDir(path) < 0) {
276 if (errno == EEXIST) {
277 return true;
278 }
279 Error("mkdir(%s): %s", path.c_str(), strerror(errno));
280 return false;
281 }
282 return true;
283 }
284
285 FileReader::Status RealDiskInterface::ReadFile(const string& path,
286 string* contents,
287 string* err) {
288 switch (::ReadFile(path, contents, err)) {
289 case 0: return Okay;
290 case -ENOENT: return NotFound;
291 default: return OtherError;
292 }
293 }
294
295 int RealDiskInterface::RemoveFile(const string& path) {
296 #ifdef _WIN32
297 DWORD attributes = GetFileAttributesA(path.c_str());
298 if (attributes == INVALID_FILE_ATTRIBUTES) {
299 DWORD win_err = GetLastError();
300 if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) {
301 return 1;
302 }
303 } else if (attributes & FILE_ATTRIBUTE_READONLY) {
304 // On non-Windows systems, remove() will happily delete read-only files.
305 // On Windows Ninja should behave the same:
306 // https://github.com/ninja-build/ninja/issues/1886
307 // Skip error checking. If this fails, accept whatever happens below.
308 SetFileAttributesA(path.c_str(), attributes & ~FILE_ATTRIBUTE_READONLY);
309 }
310 if (attributes & FILE_ATTRIBUTE_DIRECTORY) {
311 // remove() deletes both files and directories. On Windows we have to
312 // select the correct function (DeleteFile will yield Permission Denied when
313 // used on a directory)
314 // This fixes the behavior of ninja -t clean in some cases
315 // https://github.com/ninja-build/ninja/issues/828
316 if (!RemoveDirectoryA(path.c_str())) {
317 DWORD win_err = GetLastError();
318 if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) {
319 return 1;
320 }
321 // Report remove(), not RemoveDirectory(), for cross-platform consistency.
322 Error("remove(%s): %s", path.c_str(), GetLastErrorString().c_str());
323 return -1;
324 }
325 } else {
326 if (!DeleteFileA(path.c_str())) {
327 DWORD win_err = GetLastError();
328 if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) {
329 return 1;
330 }
331 // Report as remove(), not DeleteFile(), for cross-platform consistency.
332 Error("remove(%s): %s", path.c_str(), GetLastErrorString().c_str());
333 return -1;
334 }
335 }
336 #else
337 if (remove(path.c_str()) < 0) {
338 switch (errno) {
339 case ENOENT:
340 return 1;
341 default:
342 Error("remove(%s): %s", path.c_str(), strerror(errno));
343 return -1;
344 }
345 }
346 #endif
347 return 0;
348 }
349
350 void RealDiskInterface::AllowStatCache(bool allow) {
351 #ifdef _WIN32
352 use_cache_ = allow;
353 if (!use_cache_)
354 cache_.clear();
355 #endif
356 }
357
358 #ifdef _WIN32
359 bool RealDiskInterface::AreLongPathsEnabled(void) const {
360 return long_paths_enabled_;
361 }
362 #endif
363