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 "FuzzerDefs.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 std::string Basename(const std::string &Path) {
75   size_t Pos = Path.find_last_of("/\\");
76   if (Pos == std::string::npos) return Path;
77   assert(Pos < Path.size());
78   return Path.substr(Pos + 1);
79 }
80 
81 size_t FileSize(const std::string &Path) {
82   WIN32_FILE_ATTRIBUTE_DATA attr;
83   if (!GetFileAttributesExA(Path.c_str(), GetFileExInfoStandard, &attr)) {
84     Printf("GetFileAttributesExA() failed for \"%s\" (Error code: %lu).\n",
85            Path.c_str(), GetLastError());
86     return 0;
87   }
88   ULARGE_INTEGER size;
89   size.HighPart = attr.nFileSizeHigh;
90   size.LowPart = attr.nFileSizeLow;
91   return size.QuadPart;
92 }
93 
94 void ListFilesInDirRecursive(const std::string &Dir, long *Epoch,
95                              Vector<std::string> *V, bool TopDir) {
96   auto E = GetEpoch(Dir);
97   if (Epoch)
98     if (E && *Epoch >= E) return;
99 
100   std::string Path(Dir);
101   assert(!Path.empty());
102   if (Path.back() != '\\')
103       Path.push_back('\\');
104   Path.push_back('*');
105 
106   // Get the first directory entry.
107   WIN32_FIND_DATAA FindInfo;
108   HANDLE FindHandle(FindFirstFileA(Path.c_str(), &FindInfo));
109   if (FindHandle == INVALID_HANDLE_VALUE)
110   {
111     if (GetLastError() == ERROR_FILE_NOT_FOUND)
112       return;
113     Printf("No such file or directory: %s; exiting\n", Dir.c_str());
114     exit(1);
115   }
116 
117   do {
118     std::string FileName = DirPlusFile(Dir, FindInfo.cFileName);
119 
120     if (FindInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
121       size_t FilenameLen = strlen(FindInfo.cFileName);
122       if ((FilenameLen == 1 && FindInfo.cFileName[0] == '.') ||
123           (FilenameLen == 2 && FindInfo.cFileName[0] == '.' &&
124                                FindInfo.cFileName[1] == '.'))
125         continue;
126 
127       ListFilesInDirRecursive(FileName, Epoch, V, false);
128     }
129     else if (IsFile(FileName, FindInfo.dwFileAttributes))
130       V->push_back(FileName);
131   } while (FindNextFileA(FindHandle, &FindInfo));
132 
133   DWORD LastError = GetLastError();
134   if (LastError != ERROR_NO_MORE_FILES)
135     Printf("FindNextFileA failed (Error code: %lu).\n", LastError);
136 
137   FindClose(FindHandle);
138 
139   if (Epoch && TopDir)
140     *Epoch = E;
141 }
142 
143 char GetSeparator() {
144   return '\\';
145 }
146 
147 FILE* OpenFile(int Fd, const char* Mode) {
148   return _fdopen(Fd, Mode);
149 }
150 
151 int CloseFile(int Fd) {
152   return _close(Fd);
153 }
154 
155 int DuplicateFile(int Fd) {
156   return _dup(Fd);
157 }
158 
159 void RemoveFile(const std::string &Path) {
160   _unlink(Path.c_str());
161 }
162 
163 void DiscardOutput(int Fd) {
164   FILE* Temp = fopen("nul", "w");
165   if (!Temp)
166     return;
167   _dup2(_fileno(Temp), Fd);
168   fclose(Temp);
169 }
170 
171 intptr_t GetHandleFromFd(int fd) {
172   return _get_osfhandle(fd);
173 }
174 
175 static bool IsSeparator(char C) {
176   return C == '\\' || C == '/';
177 }
178 
179 // Parse disk designators, like "C:\". If Relative == true, also accepts: "C:".
180 // Returns number of characters considered if successful.
181 static size_t ParseDrive(const std::string &FileName, const size_t Offset,
182                          bool Relative = true) {
183   if (Offset + 1 >= FileName.size() || FileName[Offset + 1] != ':')
184     return 0;
185   if (Offset + 2 >= FileName.size() || !IsSeparator(FileName[Offset + 2])) {
186     if (!Relative) // Accept relative path?
187       return 0;
188     else
189       return 2;
190   }
191   return 3;
192 }
193 
194 // Parse a file name, like: SomeFile.txt
195 // Returns number of characters considered if successful.
196 static size_t ParseFileName(const std::string &FileName, const size_t Offset) {
197   size_t Pos = Offset;
198   const size_t End = FileName.size();
199   for(; Pos < End && !IsSeparator(FileName[Pos]); ++Pos)
200     ;
201   return Pos - Offset;
202 }
203 
204 // Parse a directory ending in separator, like: `SomeDir\`
205 // Returns number of characters considered if successful.
206 static size_t ParseDir(const std::string &FileName, const size_t Offset) {
207   size_t Pos = Offset;
208   const size_t End = FileName.size();
209   if (Pos >= End || IsSeparator(FileName[Pos]))
210     return 0;
211   for(; Pos < End && !IsSeparator(FileName[Pos]); ++Pos)
212     ;
213   if (Pos >= End)
214     return 0;
215   ++Pos; // Include separator.
216   return Pos - Offset;
217 }
218 
219 // Parse a servername and share, like: `SomeServer\SomeShare\`
220 // Returns number of characters considered if successful.
221 static size_t ParseServerAndShare(const std::string &FileName,
222                                   const size_t Offset) {
223   size_t Pos = Offset, Res;
224   if (!(Res = ParseDir(FileName, Pos)))
225     return 0;
226   Pos += Res;
227   if (!(Res = ParseDir(FileName, Pos)))
228     return 0;
229   Pos += Res;
230   return Pos - Offset;
231 }
232 
233 // Parse the given Ref string from the position Offset, to exactly match the given
234 // string Patt.
235 // Returns number of characters considered if successful.
236 static size_t ParseCustomString(const std::string &Ref, size_t Offset,
237                                 const char *Patt) {
238   size_t Len = strlen(Patt);
239   if (Offset + Len > Ref.size())
240     return 0;
241   return Ref.compare(Offset, Len, Patt) == 0 ? Len : 0;
242 }
243 
244 // Parse a location, like:
245 // \\?\UNC\Server\Share\  \\?\C:\  \\Server\Share\  \  C:\  C:
246 // Returns number of characters considered if successful.
247 static size_t ParseLocation(const std::string &FileName) {
248   size_t Pos = 0, Res;
249 
250   if ((Res = ParseCustomString(FileName, Pos, R"(\\?\)"))) {
251     Pos += Res;
252     if ((Res = ParseCustomString(FileName, Pos, R"(UNC\)"))) {
253       Pos += Res;
254       if ((Res = ParseServerAndShare(FileName, Pos)))
255         return Pos + Res;
256       return 0;
257     }
258     if ((Res = ParseDrive(FileName, Pos, false)))
259       return Pos + Res;
260     return 0;
261   }
262 
263   if (Pos < FileName.size() && IsSeparator(FileName[Pos])) {
264     ++Pos;
265     if (Pos < FileName.size() && IsSeparator(FileName[Pos])) {
266       ++Pos;
267       if ((Res = ParseServerAndShare(FileName, Pos)))
268         return Pos + Res;
269       return 0;
270     }
271     return Pos;
272   }
273 
274   if ((Res = ParseDrive(FileName, Pos)))
275     return Pos + Res;
276 
277   return Pos;
278 }
279 
280 std::string DirName(const std::string &FileName) {
281   size_t LocationLen = ParseLocation(FileName);
282   size_t DirLen = 0, Res;
283   while ((Res = ParseDir(FileName, LocationLen + DirLen)))
284     DirLen += Res;
285   size_t FileLen = ParseFileName(FileName, LocationLen + DirLen);
286 
287   if (LocationLen + DirLen + FileLen != FileName.size()) {
288     Printf("DirName() failed for \"%s\", invalid path.\n", FileName.c_str());
289     exit(1);
290   }
291 
292   if (DirLen) {
293     --DirLen; // Remove trailing separator.
294     if (!FileLen) { // Path ended in separator.
295       assert(DirLen);
296       // Remove file name from Dir.
297       while (DirLen && !IsSeparator(FileName[LocationLen + DirLen - 1]))
298         --DirLen;
299       if (DirLen) // Remove trailing separator.
300         --DirLen;
301     }
302   }
303 
304   if (!LocationLen) { // Relative path.
305     if (!DirLen)
306       return ".";
307     return std::string(".\\").append(FileName, 0, DirLen);
308   }
309 
310   return FileName.substr(0, LocationLen + DirLen);
311 }
312 
313 std::string TmpDir() {
314   std::string Tmp;
315   Tmp.resize(MAX_PATH + 1);
316   DWORD Size = GetTempPathA(Tmp.size(), &Tmp[0]);
317   if (Size == 0) {
318     Printf("Couldn't get Tmp path.\n");
319     exit(1);
320   }
321   Tmp.resize(Size);
322   return Tmp;
323 }
324 
325 bool IsInterestingCoverageFile(const std::string &FileName) {
326   if (FileName.find("Program Files") != std::string::npos)
327     return false;
328   if (FileName.find("compiler-rt\\lib\\") != std::string::npos)
329     return false; // sanitizer internal.
330   if (FileName == "<null>")
331     return false;
332   return true;
333 }
334 
335 void RawPrint(const char *Str) {
336   // Not tested, may or may not work. Fix if needed.
337   write(2, Str, strlen(Str));
338 }
339 
340 }  // namespace fuzzer
341 
342 #endif // LIBFUZZER_WINDOWS
343