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 <sstream> |
27 | #include <windows.h> |
28 | #include <direct.h> // _mkdir |
29 | #else |
30 | #include <unistd.h> |
31 | #endif |
32 | |
33 | #include "metrics.h" |
34 | #include "util.h" |
35 | |
36 | using namespace std; |
37 | |
38 | namespace { |
39 | |
40 | string DirName(const string& path) { |
41 | #ifdef _WIN32 |
42 | static const char kPathSeparators[] = "\\/" ; |
43 | #else |
44 | static const char kPathSeparators[] = "/" ; |
45 | #endif |
46 | static const char* const kEnd = kPathSeparators + sizeof(kPathSeparators) - 1; |
47 | |
48 | string::size_type slash_pos = path.find_last_of(kPathSeparators); |
49 | if (slash_pos == string::npos) |
50 | return string(); // Nothing to do. |
51 | while (slash_pos > 0 && |
52 | std::find(kPathSeparators, kEnd, path[slash_pos - 1]) != kEnd) |
53 | --slash_pos; |
54 | return path.substr(0, slash_pos); |
55 | } |
56 | |
57 | int MakeDir(const string& path) { |
58 | #ifdef _WIN32 |
59 | return _mkdir(path.c_str()); |
60 | #else |
61 | return mkdir(path.c_str(), 0777); |
62 | #endif |
63 | } |
64 | |
65 | #ifdef _WIN32 |
66 | TimeStamp TimeStampFromFileTime(const FILETIME& filetime) { |
67 | // FILETIME is in 100-nanosecond increments since the Windows epoch. |
68 | // We don't much care about epoch correctness but we do want the |
69 | // resulting value to fit in a 64-bit integer. |
70 | uint64_t mtime = ((uint64_t)filetime.dwHighDateTime << 32) | |
71 | ((uint64_t)filetime.dwLowDateTime); |
72 | // 1600 epoch -> 2000 epoch (subtract 400 years). |
73 | return (TimeStamp)mtime - 12622770400LL * (1000000000LL / 100); |
74 | } |
75 | |
76 | TimeStamp StatSingleFile(const string& path, string* err) { |
77 | WIN32_FILE_ATTRIBUTE_DATA attrs; |
78 | if (!GetFileAttributesExA(path.c_str(), GetFileExInfoStandard, &attrs)) { |
79 | DWORD win_err = GetLastError(); |
80 | if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) |
81 | return 0; |
82 | *err = "GetFileAttributesEx(" + path + "): " + GetLastErrorString(); |
83 | return -1; |
84 | } |
85 | return TimeStampFromFileTime(attrs.ftLastWriteTime); |
86 | } |
87 | |
88 | bool IsWindows7OrLater() { |
89 | OSVERSIONINFOEX version_info = |
90 | { sizeof(OSVERSIONINFOEX), 6, 1, 0, 0, {0}, 0, 0, 0, 0, 0}; |
91 | DWORDLONG comparison = 0; |
92 | VER_SET_CONDITION(comparison, VER_MAJORVERSION, VER_GREATER_EQUAL); |
93 | VER_SET_CONDITION(comparison, VER_MINORVERSION, VER_GREATER_EQUAL); |
94 | return VerifyVersionInfo( |
95 | &version_info, VER_MAJORVERSION | VER_MINORVERSION, comparison); |
96 | } |
97 | |
98 | bool StatAllFilesInDir(const string& dir, map<string, TimeStamp>* stamps, |
99 | string* err) { |
100 | // FindExInfoBasic is 30% faster than FindExInfoStandard. |
101 | static bool can_use_basic_info = IsWindows7OrLater(); |
102 | // This is not in earlier SDKs. |
103 | const FINDEX_INFO_LEVELS kFindExInfoBasic = |
104 | static_cast<FINDEX_INFO_LEVELS>(1); |
105 | FINDEX_INFO_LEVELS level = |
106 | can_use_basic_info ? kFindExInfoBasic : FindExInfoStandard; |
107 | WIN32_FIND_DATAA ffd; |
108 | HANDLE find_handle = FindFirstFileExA((dir + "\\*" ).c_str(), level, &ffd, |
109 | FindExSearchNameMatch, NULL, 0); |
110 | |
111 | if (find_handle == INVALID_HANDLE_VALUE) { |
112 | DWORD win_err = GetLastError(); |
113 | if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) |
114 | return true; |
115 | *err = "FindFirstFileExA(" + dir + "): " + GetLastErrorString(); |
116 | return false; |
117 | } |
118 | do { |
119 | string lowername = ffd.cFileName; |
120 | if (lowername == ".." ) { |
121 | // Seems to just copy the timestamp for ".." from ".", which is wrong. |
122 | // This is the case at least on NTFS under Windows 7. |
123 | continue; |
124 | } |
125 | transform(lowername.begin(), lowername.end(), lowername.begin(), ::tolower); |
126 | stamps->insert(make_pair(lowername, |
127 | TimeStampFromFileTime(ffd.ftLastWriteTime))); |
128 | } while (FindNextFileA(find_handle, &ffd)); |
129 | FindClose(find_handle); |
130 | return true; |
131 | } |
132 | #endif // _WIN32 |
133 | |
134 | } // namespace |
135 | |
136 | // DiskInterface --------------------------------------------------------------- |
137 | |
138 | bool DiskInterface::MakeDirs(const string& path) { |
139 | string dir = DirName(path); |
140 | if (dir.empty()) |
141 | return true; // Reached root; assume it's there. |
142 | string err; |
143 | TimeStamp mtime = Stat(dir, &err); |
144 | if (mtime < 0) { |
145 | Error("%s" , err.c_str()); |
146 | return false; |
147 | } |
148 | if (mtime > 0) |
149 | return true; // Exists already; we're done. |
150 | |
151 | // Directory doesn't exist. Try creating its parent first. |
152 | bool success = MakeDirs(dir); |
153 | if (!success) |
154 | return false; |
155 | return MakeDir(dir); |
156 | } |
157 | |
158 | // RealDiskInterface ----------------------------------------------------------- |
159 | |
160 | TimeStamp RealDiskInterface::Stat(const string& path, string* err) const { |
161 | METRIC_RECORD("node stat" ); |
162 | #ifdef _WIN32 |
163 | // MSDN: "Naming Files, Paths, and Namespaces" |
164 | // http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx |
165 | if (!path.empty() && path[0] != '\\' && path.size() > MAX_PATH) { |
166 | ostringstream err_stream; |
167 | err_stream << "Stat(" << path << "): Filename longer than " << MAX_PATH |
168 | << " characters" ; |
169 | *err = err_stream.str(); |
170 | return -1; |
171 | } |
172 | if (!use_cache_) |
173 | return StatSingleFile(path, err); |
174 | |
175 | string dir = DirName(path); |
176 | string base(path.substr(dir.size() ? dir.size() + 1 : 0)); |
177 | if (base == ".." ) { |
178 | // StatAllFilesInDir does not report any information for base = "..". |
179 | base = "." ; |
180 | dir = path; |
181 | } |
182 | |
183 | string dir_lowercase = dir; |
184 | transform(dir.begin(), dir.end(), dir_lowercase.begin(), ::tolower); |
185 | transform(base.begin(), base.end(), base.begin(), ::tolower); |
186 | |
187 | Cache::iterator ci = cache_.find(dir_lowercase); |
188 | if (ci == cache_.end()) { |
189 | ci = cache_.insert(make_pair(dir_lowercase, DirCache())).first; |
190 | if (!StatAllFilesInDir(dir.empty() ? "." : dir, &ci->second, err)) { |
191 | cache_.erase(ci); |
192 | return -1; |
193 | } |
194 | } |
195 | DirCache::iterator di = ci->second.find(base); |
196 | return di != ci->second.end() ? di->second : 0; |
197 | #else |
198 | #ifdef __USE_LARGEFILE64 |
199 | struct stat64 st; |
200 | if (stat64(path.c_str(), &st) < 0) { |
201 | #else |
202 | struct stat st; |
203 | if (stat(path.c_str(), &st) < 0) { |
204 | #endif |
205 | if (errno == ENOENT || errno == ENOTDIR) |
206 | return 0; |
207 | *err = "stat(" + path + "): " + strerror(errno); |
208 | return -1; |
209 | } |
210 | // Some users (Flatpak) set mtime to 0, this should be harmless |
211 | // and avoids conflicting with our return value of 0 meaning |
212 | // that it doesn't exist. |
213 | if (st.st_mtime == 0) |
214 | return 1; |
215 | #if defined(_AIX) |
216 | return (int64_t)st.st_mtime * 1000000000LL + st.st_mtime_n; |
217 | #elif defined(__APPLE__) |
218 | return ((int64_t)st.st_mtimespec.tv_sec * 1000000000LL + |
219 | st.st_mtimespec.tv_nsec); |
220 | #elif defined(st_mtime) // A macro, so we're likely on modern POSIX. |
221 | return (int64_t)st.st_mtim.tv_sec * 1000000000LL + st.st_mtim.tv_nsec; |
222 | #else |
223 | return (int64_t)st.st_mtime * 1000000000LL + st.st_mtimensec; |
224 | #endif |
225 | #endif |
226 | } |
227 | |
228 | bool RealDiskInterface::WriteFile(const string& path, const string& contents) { |
229 | FILE* fp = fopen(path.c_str(), "w" ); |
230 | if (fp == NULL) { |
231 | Error("WriteFile(%s): Unable to create file. %s" , |
232 | path.c_str(), strerror(errno)); |
233 | return false; |
234 | } |
235 | |
236 | if (fwrite(contents.data(), 1, contents.length(), fp) < contents.length()) { |
237 | Error("WriteFile(%s): Unable to write to the file. %s" , |
238 | path.c_str(), strerror(errno)); |
239 | fclose(fp); |
240 | return false; |
241 | } |
242 | |
243 | if (fclose(fp) == EOF) { |
244 | Error("WriteFile(%s): Unable to close the file. %s" , |
245 | path.c_str(), strerror(errno)); |
246 | return false; |
247 | } |
248 | |
249 | return true; |
250 | } |
251 | |
252 | bool RealDiskInterface::MakeDir(const string& path) { |
253 | if (::MakeDir(path) < 0) { |
254 | if (errno == EEXIST) { |
255 | return true; |
256 | } |
257 | Error("mkdir(%s): %s" , path.c_str(), strerror(errno)); |
258 | return false; |
259 | } |
260 | return true; |
261 | } |
262 | |
263 | FileReader::Status RealDiskInterface::ReadFile(const string& path, |
264 | string* contents, |
265 | string* err) { |
266 | switch (::ReadFile(path, contents, err)) { |
267 | case 0: return Okay; |
268 | case -ENOENT: return NotFound; |
269 | default: return OtherError; |
270 | } |
271 | } |
272 | |
273 | int RealDiskInterface::RemoveFile(const string& path) { |
274 | #ifdef _WIN32 |
275 | DWORD attributes = GetFileAttributesA(path.c_str()); |
276 | if (attributes == INVALID_FILE_ATTRIBUTES) { |
277 | DWORD win_err = GetLastError(); |
278 | if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) { |
279 | return 1; |
280 | } |
281 | } else if (attributes & FILE_ATTRIBUTE_READONLY) { |
282 | // On non-Windows systems, remove() will happily delete read-only files. |
283 | // On Windows Ninja should behave the same: |
284 | // https://github.com/ninja-build/ninja/issues/1886 |
285 | // Skip error checking. If this fails, accept whatever happens below. |
286 | SetFileAttributesA(path.c_str(), attributes & ~FILE_ATTRIBUTE_READONLY); |
287 | } |
288 | if (attributes & FILE_ATTRIBUTE_DIRECTORY) { |
289 | // remove() deletes both files and directories. On Windows we have to |
290 | // select the correct function (DeleteFile will yield Permission Denied when |
291 | // used on a directory) |
292 | // This fixes the behavior of ninja -t clean in some cases |
293 | // https://github.com/ninja-build/ninja/issues/828 |
294 | if (!RemoveDirectoryA(path.c_str())) { |
295 | DWORD win_err = GetLastError(); |
296 | if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) { |
297 | return 1; |
298 | } |
299 | // Report remove(), not RemoveDirectory(), for cross-platform consistency. |
300 | Error("remove(%s): %s" , path.c_str(), GetLastErrorString().c_str()); |
301 | return -1; |
302 | } |
303 | } else { |
304 | if (!DeleteFileA(path.c_str())) { |
305 | DWORD win_err = GetLastError(); |
306 | if (win_err == ERROR_FILE_NOT_FOUND || win_err == ERROR_PATH_NOT_FOUND) { |
307 | return 1; |
308 | } |
309 | // Report as remove(), not DeleteFile(), for cross-platform consistency. |
310 | Error("remove(%s): %s" , path.c_str(), GetLastErrorString().c_str()); |
311 | return -1; |
312 | } |
313 | } |
314 | #else |
315 | if (remove(path.c_str()) < 0) { |
316 | switch (errno) { |
317 | case ENOENT: |
318 | return 1; |
319 | default: |
320 | Error("remove(%s): %s" , path.c_str(), strerror(errno)); |
321 | return -1; |
322 | } |
323 | } |
324 | #endif |
325 | return 0; |
326 | } |
327 | |
328 | void RealDiskInterface::AllowStatCache(bool allow) { |
329 | #ifdef _WIN32 |
330 | use_cache_ = allow; |
331 | if (!use_cache_) |
332 | cache_.clear(); |
333 | #endif |
334 | } |
335 | |