1 | //===- FuzzerIOWindows.cpp - IO utils for Windows. ------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // IO functions implementation for Windows. |
9 | //===----------------------------------------------------------------------===// |
10 | #include "FuzzerPlatform.h" |
11 | #if LIBFUZZER_WINDOWS |
12 | |
13 | #include "FuzzerExtFunctions.h" |
14 | #include "FuzzerIO.h" |
15 | #include <cstdarg> |
16 | #include <cstdio> |
17 | #include <fstream> |
18 | #include <io.h> |
19 | #include <iterator> |
20 | #include <sys/stat.h> |
21 | #include <sys/types.h> |
22 | #include <windows.h> |
23 | |
24 | namespace fuzzer { |
25 | |
26 | static bool IsFile(const std::string &Path, const DWORD &FileAttributes) { |
27 | |
28 | if (FileAttributes & FILE_ATTRIBUTE_NORMAL) |
29 | return true; |
30 | |
31 | if (FileAttributes & FILE_ATTRIBUTE_DIRECTORY) |
32 | return false; |
33 | |
34 | HANDLE FileHandle( |
35 | CreateFileA(Path.c_str(), 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, |
36 | FILE_FLAG_BACKUP_SEMANTICS, 0)); |
37 | |
38 | if (FileHandle == INVALID_HANDLE_VALUE) { |
39 | Printf("CreateFileA() failed for \"%s\" (Error code: %lu).\n" , Path.c_str(), |
40 | GetLastError()); |
41 | return false; |
42 | } |
43 | |
44 | DWORD FileType = GetFileType(FileHandle); |
45 | |
46 | if (FileType == FILE_TYPE_UNKNOWN) { |
47 | Printf("GetFileType() failed for \"%s\" (Error code: %lu).\n" , Path.c_str(), |
48 | GetLastError()); |
49 | CloseHandle(FileHandle); |
50 | return false; |
51 | } |
52 | |
53 | if (FileType != FILE_TYPE_DISK) { |
54 | CloseHandle(FileHandle); |
55 | return false; |
56 | } |
57 | |
58 | CloseHandle(FileHandle); |
59 | return true; |
60 | } |
61 | |
62 | bool IsFile(const std::string &Path) { |
63 | DWORD Att = GetFileAttributesA(Path.c_str()); |
64 | |
65 | if (Att == INVALID_FILE_ATTRIBUTES) { |
66 | Printf("GetFileAttributesA() failed for \"%s\" (Error code: %lu).\n" , |
67 | Path.c_str(), GetLastError()); |
68 | return false; |
69 | } |
70 | |
71 | return IsFile(Path, Att); |
72 | } |
73 | |
74 | static bool IsDir(DWORD FileAttrs) { |
75 | if (FileAttrs == INVALID_FILE_ATTRIBUTES) return false; |
76 | return FileAttrs & FILE_ATTRIBUTE_DIRECTORY; |
77 | } |
78 | |
79 | bool IsDirectory(const std::string &Path) { |
80 | DWORD Att = GetFileAttributesA(Path.c_str()); |
81 | |
82 | if (Att == INVALID_FILE_ATTRIBUTES) { |
83 | Printf("GetFileAttributesA() failed for \"%s\" (Error code: %lu).\n" , |
84 | Path.c_str(), GetLastError()); |
85 | return false; |
86 | } |
87 | |
88 | return IsDir(Att); |
89 | } |
90 | |
91 | std::string Basename(const std::string &Path) { |
92 | size_t Pos = Path.find_last_of("/\\" ); |
93 | if (Pos == std::string::npos) return Path; |
94 | assert(Pos < Path.size()); |
95 | return Path.substr(Pos + 1); |
96 | } |
97 | |
98 | size_t FileSize(const std::string &Path) { |
99 | WIN32_FILE_ATTRIBUTE_DATA attr; |
100 | if (!GetFileAttributesExA(Path.c_str(), GetFileExInfoStandard, &attr)) { |
101 | DWORD LastError = GetLastError(); |
102 | if (LastError != ERROR_FILE_NOT_FOUND) |
103 | Printf("GetFileAttributesExA() failed for \"%s\" (Error code: %lu).\n" , |
104 | Path.c_str(), LastError); |
105 | return 0; |
106 | } |
107 | ULARGE_INTEGER size; |
108 | size.HighPart = attr.nFileSizeHigh; |
109 | size.LowPart = attr.nFileSizeLow; |
110 | return size.QuadPart; |
111 | } |
112 | |
113 | void ListFilesInDirRecursive(const std::string &Dir, long *Epoch, |
114 | std::vector<std::string> *V, bool TopDir) { |
115 | auto E = GetEpoch(Dir); |
116 | if (Epoch) |
117 | if (E && *Epoch >= E) return; |
118 | |
119 | std::string Path(Dir); |
120 | assert(!Path.empty()); |
121 | if (Path.back() != '\\') |
122 | Path.push_back('\\'); |
123 | Path.push_back('*'); |
124 | |
125 | // Get the first directory entry. |
126 | WIN32_FIND_DATAA FindInfo; |
127 | HANDLE FindHandle(FindFirstFileA(Path.c_str(), &FindInfo)); |
128 | if (FindHandle == INVALID_HANDLE_VALUE) |
129 | { |
130 | if (GetLastError() == ERROR_FILE_NOT_FOUND) |
131 | return; |
132 | Printf("No such file or directory: %s; exiting\n" , Dir.c_str()); |
133 | exit(1); |
134 | } |
135 | |
136 | do { |
137 | std::string FileName = DirPlusFile(Dir, FindInfo.cFileName); |
138 | |
139 | if (FindInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { |
140 | size_t FilenameLen = strlen(FindInfo.cFileName); |
141 | if ((FilenameLen == 1 && FindInfo.cFileName[0] == '.') || |
142 | (FilenameLen == 2 && FindInfo.cFileName[0] == '.' && |
143 | FindInfo.cFileName[1] == '.')) |
144 | continue; |
145 | |
146 | ListFilesInDirRecursive(FileName, Epoch, V, false); |
147 | } |
148 | else if (IsFile(FileName, FindInfo.dwFileAttributes)) |
149 | V->push_back(FileName); |
150 | } while (FindNextFileA(FindHandle, &FindInfo)); |
151 | |
152 | DWORD LastError = GetLastError(); |
153 | if (LastError != ERROR_NO_MORE_FILES) |
154 | Printf("FindNextFileA failed (Error code: %lu).\n" , LastError); |
155 | |
156 | FindClose(FindHandle); |
157 | |
158 | if (Epoch && TopDir) |
159 | *Epoch = E; |
160 | } |
161 | |
162 | void IterateDirRecursive(const std::string &Dir, |
163 | void (*DirPreCallback)(const std::string &Dir), |
164 | void (*DirPostCallback)(const std::string &Dir), |
165 | void (*FileCallback)(const std::string &Dir)) { |
166 | // TODO(metzman): Implement ListFilesInDirRecursive via this function. |
167 | DirPreCallback(Dir); |
168 | |
169 | DWORD DirAttrs = GetFileAttributesA(Dir.c_str()); |
170 | if (!IsDir(DirAttrs)) return; |
171 | |
172 | std::string TargetDir(Dir); |
173 | assert(!TargetDir.empty()); |
174 | if (TargetDir.back() != '\\') TargetDir.push_back('\\'); |
175 | TargetDir.push_back('*'); |
176 | |
177 | WIN32_FIND_DATAA FindInfo; |
178 | // Find the directory's first file. |
179 | HANDLE FindHandle = FindFirstFileA(TargetDir.c_str(), &FindInfo); |
180 | if (FindHandle == INVALID_HANDLE_VALUE) { |
181 | DWORD LastError = GetLastError(); |
182 | if (LastError != ERROR_FILE_NOT_FOUND) { |
183 | // If the directory isn't empty, then something abnormal is going on. |
184 | Printf("FindFirstFileA failed for %s (Error code: %lu).\n" , Dir.c_str(), |
185 | LastError); |
186 | } |
187 | return; |
188 | } |
189 | |
190 | do { |
191 | std::string Path = DirPlusFile(Dir, FindInfo.cFileName); |
192 | DWORD PathAttrs = FindInfo.dwFileAttributes; |
193 | if (IsDir(PathAttrs)) { |
194 | // Is Path the current directory (".") or the parent ("..")? |
195 | if (strcmp(FindInfo.cFileName, "." ) == 0 || |
196 | strcmp(FindInfo.cFileName, ".." ) == 0) |
197 | continue; |
198 | IterateDirRecursive(Path, DirPreCallback, DirPostCallback, FileCallback); |
199 | } else if (PathAttrs != INVALID_FILE_ATTRIBUTES) { |
200 | FileCallback(Path); |
201 | } |
202 | } while (FindNextFileA(FindHandle, &FindInfo)); |
203 | |
204 | DWORD LastError = GetLastError(); |
205 | if (LastError != ERROR_NO_MORE_FILES) |
206 | Printf("FindNextFileA failed for %s (Error code: %lu).\n" , Dir.c_str(), |
207 | LastError); |
208 | |
209 | FindClose(FindHandle); |
210 | DirPostCallback(Dir); |
211 | } |
212 | |
213 | char GetSeparator() { |
214 | return '\\'; |
215 | } |
216 | |
217 | FILE* OpenFile(int Fd, const char* Mode) { |
218 | return _fdopen(Fd, Mode); |
219 | } |
220 | |
221 | int CloseFile(int Fd) { |
222 | return _close(Fd); |
223 | } |
224 | |
225 | int DuplicateFile(int Fd) { |
226 | return _dup(Fd); |
227 | } |
228 | |
229 | void RemoveFile(const std::string &Path) { |
230 | _unlink(Path.c_str()); |
231 | } |
232 | |
233 | void RenameFile(const std::string &OldPath, const std::string &NewPath) { |
234 | rename(OldPath.c_str(), NewPath.c_str()); |
235 | } |
236 | |
237 | intptr_t GetHandleFromFd(int fd) { |
238 | return _get_osfhandle(fd); |
239 | } |
240 | |
241 | bool IsSeparator(char C) { |
242 | return C == '\\' || C == '/'; |
243 | } |
244 | |
245 | // Parse disk designators, like "C:\". If Relative == true, also accepts: "C:". |
246 | // Returns number of characters considered if successful. |
247 | static size_t ParseDrive(const std::string &FileName, const size_t Offset, |
248 | bool Relative = true) { |
249 | if (Offset + 1 >= FileName.size() || FileName[Offset + 1] != ':') |
250 | return 0; |
251 | if (Offset + 2 >= FileName.size() || !IsSeparator(FileName[Offset + 2])) { |
252 | if (!Relative) // Accept relative path? |
253 | return 0; |
254 | else |
255 | return 2; |
256 | } |
257 | return 3; |
258 | } |
259 | |
260 | // Parse a file name, like: SomeFile.txt |
261 | // Returns number of characters considered if successful. |
262 | static size_t ParseFileName(const std::string &FileName, const size_t Offset) { |
263 | size_t Pos = Offset; |
264 | const size_t End = FileName.size(); |
265 | for(; Pos < End && !IsSeparator(FileName[Pos]); ++Pos) |
266 | ; |
267 | return Pos - Offset; |
268 | } |
269 | |
270 | // Parse a directory ending in separator, like: `SomeDir\` |
271 | // Returns number of characters considered if successful. |
272 | static size_t ParseDir(const std::string &FileName, const size_t Offset) { |
273 | size_t Pos = Offset; |
274 | const size_t End = FileName.size(); |
275 | if (Pos >= End || IsSeparator(FileName[Pos])) |
276 | return 0; |
277 | for(; Pos < End && !IsSeparator(FileName[Pos]); ++Pos) |
278 | ; |
279 | if (Pos >= End) |
280 | return 0; |
281 | ++Pos; // Include separator. |
282 | return Pos - Offset; |
283 | } |
284 | |
285 | // Parse a servername and share, like: `SomeServer\SomeShare\` |
286 | // Returns number of characters considered if successful. |
287 | static size_t ParseServerAndShare(const std::string &FileName, |
288 | const size_t Offset) { |
289 | size_t Pos = Offset, Res; |
290 | if (!(Res = ParseDir(FileName, Pos))) |
291 | return 0; |
292 | Pos += Res; |
293 | if (!(Res = ParseDir(FileName, Pos))) |
294 | return 0; |
295 | Pos += Res; |
296 | return Pos - Offset; |
297 | } |
298 | |
299 | // Parse the given Ref string from the position Offset, to exactly match the |
300 | // given string Patt. Returns number of characters considered if successful. |
301 | static size_t ParseCustomString(const std::string &Ref, size_t Offset, |
302 | const char *Patt) { |
303 | size_t Len = strlen(Patt); |
304 | if (Offset + Len > Ref.size()) |
305 | return 0; |
306 | return Ref.compare(Offset, Len, Patt) == 0 ? Len : 0; |
307 | } |
308 | |
309 | // Parse a location, like: |
310 | // \\?\UNC\Server\Share\ \\?\C:\ \\Server\Share\ \ C:\ C: |
311 | // Returns number of characters considered if successful. |
312 | static size_t ParseLocation(const std::string &FileName) { |
313 | size_t Pos = 0, Res; |
314 | |
315 | if ((Res = ParseCustomString(FileName, Pos, R"(\\?\)" ))) { |
316 | Pos += Res; |
317 | if ((Res = ParseCustomString(FileName, Pos, R"(UNC\)" ))) { |
318 | Pos += Res; |
319 | if ((Res = ParseServerAndShare(FileName, Pos))) |
320 | return Pos + Res; |
321 | return 0; |
322 | } |
323 | if ((Res = ParseDrive(FileName, Pos, false))) |
324 | return Pos + Res; |
325 | return 0; |
326 | } |
327 | |
328 | if (Pos < FileName.size() && IsSeparator(FileName[Pos])) { |
329 | ++Pos; |
330 | if (Pos < FileName.size() && IsSeparator(FileName[Pos])) { |
331 | ++Pos; |
332 | if ((Res = ParseServerAndShare(FileName, Pos))) |
333 | return Pos + Res; |
334 | return 0; |
335 | } |
336 | return Pos; |
337 | } |
338 | |
339 | if ((Res = ParseDrive(FileName, Pos))) |
340 | return Pos + Res; |
341 | |
342 | return Pos; |
343 | } |
344 | |
345 | std::string DirName(const std::string &FileName) { |
346 | size_t LocationLen = ParseLocation(FileName); |
347 | size_t DirLen = 0, Res; |
348 | while ((Res = ParseDir(FileName, LocationLen + DirLen))) |
349 | DirLen += Res; |
350 | size_t FileLen = ParseFileName(FileName, LocationLen + DirLen); |
351 | |
352 | if (LocationLen + DirLen + FileLen != FileName.size()) { |
353 | Printf("DirName() failed for \"%s\", invalid path.\n" , FileName.c_str()); |
354 | exit(1); |
355 | } |
356 | |
357 | if (DirLen) { |
358 | --DirLen; // Remove trailing separator. |
359 | if (!FileLen) { // Path ended in separator. |
360 | assert(DirLen); |
361 | // Remove file name from Dir. |
362 | while (DirLen && !IsSeparator(FileName[LocationLen + DirLen - 1])) |
363 | --DirLen; |
364 | if (DirLen) // Remove trailing separator. |
365 | --DirLen; |
366 | } |
367 | } |
368 | |
369 | if (!LocationLen) { // Relative path. |
370 | if (!DirLen) |
371 | return "." ; |
372 | return std::string(".\\" ).append(FileName, 0, DirLen); |
373 | } |
374 | |
375 | return FileName.substr(0, LocationLen + DirLen); |
376 | } |
377 | |
378 | std::string TmpDir() { |
379 | std::string Tmp; |
380 | Tmp.resize(MAX_PATH + 1); |
381 | DWORD Size = GetTempPathA(Tmp.size(), &Tmp[0]); |
382 | if (Size == 0) { |
383 | Printf("Couldn't get Tmp path.\n" ); |
384 | exit(1); |
385 | } |
386 | Tmp.resize(Size); |
387 | return Tmp; |
388 | } |
389 | |
390 | bool IsInterestingCoverageFile(const std::string &FileName) { |
391 | if (FileName.find("Program Files" ) != std::string::npos) |
392 | return false; |
393 | if (FileName.find("compiler-rt\\lib\\" ) != std::string::npos) |
394 | return false; // sanitizer internal. |
395 | if (FileName == "<null>" ) |
396 | return false; |
397 | return true; |
398 | } |
399 | |
400 | void RawPrint(const char *Str) { |
401 | _write(2, Str, strlen(Str)); |
402 | } |
403 | |
404 | void MkDir(const std::string &Path) { |
405 | if (CreateDirectoryA(Path.c_str(), nullptr)) return; |
406 | Printf("CreateDirectoryA failed for %s (Error code: %lu).\n" , Path.c_str(), |
407 | GetLastError()); |
408 | } |
409 | |
410 | void RmDir(const std::string &Path) { |
411 | if (RemoveDirectoryA(Path.c_str())) return; |
412 | Printf("RemoveDirectoryA failed for %s (Error code: %lu).\n" , Path.c_str(), |
413 | GetLastError()); |
414 | } |
415 | |
416 | const std::string &getDevNull() { |
417 | static const std::string devNull = "NUL" ; |
418 | return devNull; |
419 | } |
420 | |
421 | } // namespace fuzzer |
422 | |
423 | #endif // LIBFUZZER_WINDOWS |
424 | |