• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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