blob: fad6a66d60a7f209977e6dcf9b008a3ffdefedaa [file] [log] [blame]
Austin Schuh812d0d12021-11-04 20:16:48 -07001// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5//===----------------------------------------------------------------------===//
6//
7// The LLVM Compiler Infrastructure
8//
9// This file is distributed under the University of Illinois Open Source
10// License. See LICENSE.TXT for details.
11//
12//===----------------------------------------------------------------------===//
13
14#include <cassert>
15
16#ifdef _WIN32
17#include <fcntl.h>
18#include <io.h>
19#include <sys/types.h>
20// Require at least Windows 7 API.
21#define _WIN32_WINNT 0x0601
22#define _WIN32_IE 0x0800 // MinGW at it again. FIXME: verify if still needed.
23#define WIN32_LEAN_AND_MEAN
24#ifndef NOMINMAX
25#define NOMINMAX
26#endif
27
28#define WIN32_NO_STATUS
29#include <windows.h>
30#undef WIN32_NO_STATUS
31#include <winternl.h>
32#include <ntstatus.h>
33
34#include <shellapi.h>
35#include <shlobj.h>
36
37#include "wpi/WindowsError.h"
38
39#else // _WIN32
40
41#include <fcntl.h>
42#include <unistd.h>
43
44#endif // _WIN32
45
46#if defined(__APPLE__)
47#include <Availability.h>
48#endif
49#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) \
50 || (defined(__cplusplus) && __cplusplus >= 201703L)) \
51 && defined(__has_include)
52#if __has_include(<filesystem>) \
53 && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) \
54 || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) \
55 && (defined(__clang__) || !defined(__GNUC__) || __GNUC__ >= 10 \
56 || (__GNUC__ >= 9 && __GNUC_MINOR__ >= 1))
57#define GHC_USE_STD_FS
58#endif
59#endif
60#ifndef GHC_USE_STD_FS
61// #define GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE
62#define GHC_FILESYSTEM_IMPLEMENTATION
63#include "wpi/ghc/filesystem.hpp"
64#endif
65
66#include "wpi/Errno.h"
67#include "wpi/ErrorHandling.h"
68#include "wpi/WindowsError.h"
69#include "wpi/fs.h"
70
71namespace fs {
72
73#ifdef _WIN32
74
75#ifdef _MSC_VER
76#pragma comment(lib, "shell32.lib")
77#pragma comment(lib, "ole32.lib")
78#pragma warning(push)
79#pragma warning(disable : 4244 4267 4146)
80#endif
81
82const file_t kInvalidFile = INVALID_HANDLE_VALUE;
83
84static DWORD nativeDisposition(CreationDisposition Disp, OpenFlags Flags) {
Austin Schuh812d0d12021-11-04 20:16:48 -070085 switch (Disp) {
86 case CD_CreateAlways:
87 return CREATE_ALWAYS;
88 case CD_CreateNew:
89 return CREATE_NEW;
90 case CD_OpenAlways:
91 return OPEN_ALWAYS;
92 case CD_OpenExisting:
93 return OPEN_EXISTING;
94 }
95 wpi_unreachable("unreachable!");
96}
97
98static DWORD nativeAccess(FileAccess Access, OpenFlags Flags) {
99 DWORD Result = 0;
100 if (Access & FA_Read)
101 Result |= GENERIC_READ;
102 if (Access & FA_Write)
103 Result |= GENERIC_WRITE;
104 if (Flags & OF_Delete)
105 Result |= DELETE;
106 if (Flags & OF_UpdateAtime)
107 Result |= FILE_WRITE_ATTRIBUTES;
108 return Result;
109}
110
111static file_t openFileInternal(const path& Path, std::error_code& EC,
112 DWORD Disp, DWORD Access, DWORD Flags,
113 bool Inherit = false) {
114 SECURITY_ATTRIBUTES SA;
115 SA.nLength = sizeof(SA);
116 SA.lpSecurityDescriptor = nullptr;
117 SA.bInheritHandle = Inherit;
118
119 HANDLE H =
120 ::CreateFileW(Path.c_str(), Access,
121 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &SA,
122 Disp, Flags, NULL);
123 if (H == INVALID_HANDLE_VALUE) {
124 DWORD LastError = ::GetLastError();
125 EC = wpi::mapWindowsError(LastError);
126 // Provide a better error message when trying to open directories.
127 // This only runs if we failed to open the file, so there is probably
128 // no performances issues.
129 if (LastError != ERROR_ACCESS_DENIED) {
130 return kInvalidFile;
131 }
132 if (is_directory(Path)) {
133 EC = std::make_error_code(std::errc::is_a_directory);
134 }
135 return kInvalidFile;
136 }
137 EC = std::error_code();
138 return H;
139}
140
141static std::error_code setDeleteDisposition(HANDLE Handle, bool Delete) {
142 FILE_DISPOSITION_INFO Disposition;
143 Disposition.DeleteFile = Delete;
144 if (!::SetFileInformationByHandle(Handle, FileDispositionInfo, &Disposition,
145 sizeof(Disposition)))
146 return wpi::mapWindowsError(::GetLastError());
147 return std::error_code();
148}
149
150file_t OpenFile(const path& Path, std::error_code& EC, CreationDisposition Disp,
151 FileAccess Access, OpenFlags Flags, unsigned Mode) {
152 // Verify that we don't have both "append" and "excl".
153 assert((!(Disp == CD_CreateNew) || !(Flags & OF_Append)) &&
154 "Cannot specify both 'CreateNew' and 'Append' file creation flags!");
155
156 DWORD NativeDisp = nativeDisposition(Disp, Flags);
157 DWORD NativeAccess = nativeAccess(Access, Flags);
158
159 bool Inherit = false;
160 if (Flags & OF_ChildInherit) {
161 Inherit = true;
162 }
163
164 file_t Result = openFileInternal(Path, EC, NativeDisp, NativeAccess,
165 FILE_ATTRIBUTE_NORMAL, Inherit);
166 if (EC) {
167 return Result;
168 }
169
170 if (Flags & OF_UpdateAtime) {
171 FILETIME FileTime;
172 SYSTEMTIME SystemTime;
173 ::GetSystemTime(&SystemTime);
174 if (::SystemTimeToFileTime(&SystemTime, &FileTime) == 0 ||
175 ::SetFileTime(Result, NULL, &FileTime, NULL) == 0) {
176 DWORD LastError = ::GetLastError();
177 ::CloseHandle(Result);
178 EC = wpi::mapWindowsError(LastError);
179 return kInvalidFile;
180 }
181 }
182
183 if (Flags & OF_Delete) {
184 if ((EC = setDeleteDisposition(Result, true))) {
185 ::CloseHandle(Result);
186 return kInvalidFile;
187 }
188 }
189 return Result;
190}
191
192file_t OpenFileForRead(const path& Path, std::error_code& EC, OpenFlags Flags) {
193 return OpenFile(Path, EC, CD_OpenExisting, FA_Read, Flags);
194}
195
196int FileToFd(file_t& F, std::error_code& EC, OpenFlags Flags) {
197 if (F == kInvalidFile) {
198 EC = wpi::mapWindowsError(ERROR_INVALID_HANDLE);
199 return -1;
200 }
201
202 int CrtOpenFlags = 0;
203 if (Flags & OF_Append) {
204 CrtOpenFlags |= _O_APPEND;
205 }
206
207 if (Flags & OF_Text) {
208 CrtOpenFlags |= _O_TEXT;
209 }
210
211 int ResultFD = ::_open_osfhandle(intptr_t(F), CrtOpenFlags);
212 if (ResultFD == -1) {
213 ::CloseHandle(F);
214 EC = wpi::mapWindowsError(ERROR_INVALID_HANDLE);
215 return -1;
216 }
217
218 EC = std::error_code();
219 F = kInvalidFile;
220 return ResultFD;
221}
222
223void CloseFile(file_t& F) {
224 ::CloseHandle(F);
225 F = kInvalidFile;
226}
227
228#else // _WIN32
229
230const file_t kInvalidFile = -1;
231
232static int nativeOpenFlags(CreationDisposition Disp, OpenFlags Flags,
233 FileAccess Access) {
234 int Result = 0;
235 if (Access == FA_Read) {
236 Result |= O_RDONLY;
237 } else if (Access == FA_Write) {
238 Result |= O_WRONLY;
239 } else if (Access == (FA_Read | FA_Write)) {
240 Result |= O_RDWR;
241 }
242
Austin Schuh812d0d12021-11-04 20:16:48 -0700243 if (Disp == CD_CreateNew) {
244 Result |= O_CREAT; // Create if it doesn't exist.
245 Result |= O_EXCL; // Fail if it does.
246 } else if (Disp == CD_CreateAlways) {
247 Result |= O_CREAT; // Create if it doesn't exist.
248 Result |= O_TRUNC; // Truncate if it does.
249 } else if (Disp == CD_OpenAlways) {
250 Result |= O_CREAT; // Create if it doesn't exist.
251 } else if (Disp == CD_OpenExisting) {
252 // Nothing special, just don't add O_CREAT and we get these semantics.
253 }
254
255 if (Flags & F_Append) {
256 Result |= O_APPEND;
257 }
258
259#ifdef O_CLOEXEC
260 if (!(Flags & OF_ChildInherit)) {
261 Result |= O_CLOEXEC;
262 }
263#endif
264
265 return Result;
266}
267
268file_t OpenFile(const path& Path, std::error_code& EC, CreationDisposition Disp,
269 FileAccess Access, OpenFlags Flags, unsigned Mode) {
270 int OpenFlags = nativeOpenFlags(Disp, Flags, Access);
271 file_t ResultFD = kInvalidFile;
272
273 // Call ::open in a lambda to avoid overload resolution in RetryAfterSignal
274 // when open is overloaded, such as in Bionic.
275 auto Open = [&]() { return ::open(Path.c_str(), OpenFlags, Mode); };
276 if ((ResultFD = wpi::sys::RetryAfterSignal(-1, Open)) < 0) {
277 EC = std::error_code(errno, std::generic_category());
278 return kInvalidFile;
279 }
280#ifndef O_CLOEXEC
281 if (!(Flags & OF_ChildInherit)) {
282 int r = fcntl(ResultFD, F_SETFD, FD_CLOEXEC);
283 (void)r;
284 assert(r == 0 && "fcntl(F_SETFD, FD_CLOEXEC) failed");
285 }
286#endif
287 EC = std::error_code();
288 return ResultFD;
289}
290
291file_t OpenFileForRead(const path& Path, std::error_code& EC, OpenFlags Flags) {
292 return OpenFile(Path, EC, CD_OpenExisting, FA_Read, Flags, 0666);
293}
294
295int FileToFd(file_t& F, std::error_code& EC, OpenFlags Flags) {
296 int fd = F;
297 F = kInvalidFile;
298 EC = std::error_code();
299 return fd;
300}
301
302void CloseFile(file_t& F) {
303 ::close(F);
304 F = kInvalidFile;
305}
306
307#endif // _WIN32
308
309} // namespace fs