blob: 34d6ccad070e7d24a5cba4b8507facf44f895031 [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) {
85 // This is a compatibility hack. Really we should respect the creation
86 // disposition, but a lot of old code relied on the implicit assumption that
87 // OF_Append implied it would open an existing file. Since the disposition is
88 // now explicit and defaults to CD_CreateAlways, this assumption would cause
89 // any usage of OF_Append to append to a new file, even if the file already
90 // existed. A better solution might have two new creation dispositions:
91 // CD_AppendAlways and CD_AppendNew. This would also address the problem of
92 // OF_Append being used on a read-only descriptor, which doesn't make sense.
93 if (Flags & OF_Append)
94 return OPEN_ALWAYS;
95
96 switch (Disp) {
97 case CD_CreateAlways:
98 return CREATE_ALWAYS;
99 case CD_CreateNew:
100 return CREATE_NEW;
101 case CD_OpenAlways:
102 return OPEN_ALWAYS;
103 case CD_OpenExisting:
104 return OPEN_EXISTING;
105 }
106 wpi_unreachable("unreachable!");
107}
108
109static DWORD nativeAccess(FileAccess Access, OpenFlags Flags) {
110 DWORD Result = 0;
111 if (Access & FA_Read)
112 Result |= GENERIC_READ;
113 if (Access & FA_Write)
114 Result |= GENERIC_WRITE;
115 if (Flags & OF_Delete)
116 Result |= DELETE;
117 if (Flags & OF_UpdateAtime)
118 Result |= FILE_WRITE_ATTRIBUTES;
119 return Result;
120}
121
122static file_t openFileInternal(const path& Path, std::error_code& EC,
123 DWORD Disp, DWORD Access, DWORD Flags,
124 bool Inherit = false) {
125 SECURITY_ATTRIBUTES SA;
126 SA.nLength = sizeof(SA);
127 SA.lpSecurityDescriptor = nullptr;
128 SA.bInheritHandle = Inherit;
129
130 HANDLE H =
131 ::CreateFileW(Path.c_str(), Access,
132 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &SA,
133 Disp, Flags, NULL);
134 if (H == INVALID_HANDLE_VALUE) {
135 DWORD LastError = ::GetLastError();
136 EC = wpi::mapWindowsError(LastError);
137 // Provide a better error message when trying to open directories.
138 // This only runs if we failed to open the file, so there is probably
139 // no performances issues.
140 if (LastError != ERROR_ACCESS_DENIED) {
141 return kInvalidFile;
142 }
143 if (is_directory(Path)) {
144 EC = std::make_error_code(std::errc::is_a_directory);
145 }
146 return kInvalidFile;
147 }
148 EC = std::error_code();
149 return H;
150}
151
152static std::error_code setDeleteDisposition(HANDLE Handle, bool Delete) {
153 FILE_DISPOSITION_INFO Disposition;
154 Disposition.DeleteFile = Delete;
155 if (!::SetFileInformationByHandle(Handle, FileDispositionInfo, &Disposition,
156 sizeof(Disposition)))
157 return wpi::mapWindowsError(::GetLastError());
158 return std::error_code();
159}
160
161file_t OpenFile(const path& Path, std::error_code& EC, CreationDisposition Disp,
162 FileAccess Access, OpenFlags Flags, unsigned Mode) {
163 // Verify that we don't have both "append" and "excl".
164 assert((!(Disp == CD_CreateNew) || !(Flags & OF_Append)) &&
165 "Cannot specify both 'CreateNew' and 'Append' file creation flags!");
166
167 DWORD NativeDisp = nativeDisposition(Disp, Flags);
168 DWORD NativeAccess = nativeAccess(Access, Flags);
169
170 bool Inherit = false;
171 if (Flags & OF_ChildInherit) {
172 Inherit = true;
173 }
174
175 file_t Result = openFileInternal(Path, EC, NativeDisp, NativeAccess,
176 FILE_ATTRIBUTE_NORMAL, Inherit);
177 if (EC) {
178 return Result;
179 }
180
181 if (Flags & OF_UpdateAtime) {
182 FILETIME FileTime;
183 SYSTEMTIME SystemTime;
184 ::GetSystemTime(&SystemTime);
185 if (::SystemTimeToFileTime(&SystemTime, &FileTime) == 0 ||
186 ::SetFileTime(Result, NULL, &FileTime, NULL) == 0) {
187 DWORD LastError = ::GetLastError();
188 ::CloseHandle(Result);
189 EC = wpi::mapWindowsError(LastError);
190 return kInvalidFile;
191 }
192 }
193
194 if (Flags & OF_Delete) {
195 if ((EC = setDeleteDisposition(Result, true))) {
196 ::CloseHandle(Result);
197 return kInvalidFile;
198 }
199 }
200 return Result;
201}
202
203file_t OpenFileForRead(const path& Path, std::error_code& EC, OpenFlags Flags) {
204 return OpenFile(Path, EC, CD_OpenExisting, FA_Read, Flags);
205}
206
207int FileToFd(file_t& F, std::error_code& EC, OpenFlags Flags) {
208 if (F == kInvalidFile) {
209 EC = wpi::mapWindowsError(ERROR_INVALID_HANDLE);
210 return -1;
211 }
212
213 int CrtOpenFlags = 0;
214 if (Flags & OF_Append) {
215 CrtOpenFlags |= _O_APPEND;
216 }
217
218 if (Flags & OF_Text) {
219 CrtOpenFlags |= _O_TEXT;
220 }
221
222 int ResultFD = ::_open_osfhandle(intptr_t(F), CrtOpenFlags);
223 if (ResultFD == -1) {
224 ::CloseHandle(F);
225 EC = wpi::mapWindowsError(ERROR_INVALID_HANDLE);
226 return -1;
227 }
228
229 EC = std::error_code();
230 F = kInvalidFile;
231 return ResultFD;
232}
233
234void CloseFile(file_t& F) {
235 ::CloseHandle(F);
236 F = kInvalidFile;
237}
238
239#else // _WIN32
240
241const file_t kInvalidFile = -1;
242
243static int nativeOpenFlags(CreationDisposition Disp, OpenFlags Flags,
244 FileAccess Access) {
245 int Result = 0;
246 if (Access == FA_Read) {
247 Result |= O_RDONLY;
248 } else if (Access == FA_Write) {
249 Result |= O_WRONLY;
250 } else if (Access == (FA_Read | FA_Write)) {
251 Result |= O_RDWR;
252 }
253
254 // This is for compatibility with old code that assumed F_Append implied
255 // would open an existing file. See Windows/Path.inc for a longer comment.
256 if (Flags & F_Append) {
257 Disp = CD_OpenAlways;
258 }
259
260 if (Disp == CD_CreateNew) {
261 Result |= O_CREAT; // Create if it doesn't exist.
262 Result |= O_EXCL; // Fail if it does.
263 } else if (Disp == CD_CreateAlways) {
264 Result |= O_CREAT; // Create if it doesn't exist.
265 Result |= O_TRUNC; // Truncate if it does.
266 } else if (Disp == CD_OpenAlways) {
267 Result |= O_CREAT; // Create if it doesn't exist.
268 } else if (Disp == CD_OpenExisting) {
269 // Nothing special, just don't add O_CREAT and we get these semantics.
270 }
271
272 if (Flags & F_Append) {
273 Result |= O_APPEND;
274 }
275
276#ifdef O_CLOEXEC
277 if (!(Flags & OF_ChildInherit)) {
278 Result |= O_CLOEXEC;
279 }
280#endif
281
282 return Result;
283}
284
285file_t OpenFile(const path& Path, std::error_code& EC, CreationDisposition Disp,
286 FileAccess Access, OpenFlags Flags, unsigned Mode) {
287 int OpenFlags = nativeOpenFlags(Disp, Flags, Access);
288 file_t ResultFD = kInvalidFile;
289
290 // Call ::open in a lambda to avoid overload resolution in RetryAfterSignal
291 // when open is overloaded, such as in Bionic.
292 auto Open = [&]() { return ::open(Path.c_str(), OpenFlags, Mode); };
293 if ((ResultFD = wpi::sys::RetryAfterSignal(-1, Open)) < 0) {
294 EC = std::error_code(errno, std::generic_category());
295 return kInvalidFile;
296 }
297#ifndef O_CLOEXEC
298 if (!(Flags & OF_ChildInherit)) {
299 int r = fcntl(ResultFD, F_SETFD, FD_CLOEXEC);
300 (void)r;
301 assert(r == 0 && "fcntl(F_SETFD, FD_CLOEXEC) failed");
302 }
303#endif
304 EC = std::error_code();
305 return ResultFD;
306}
307
308file_t OpenFileForRead(const path& Path, std::error_code& EC, OpenFlags Flags) {
309 return OpenFile(Path, EC, CD_OpenExisting, FA_Read, Flags, 0666);
310}
311
312int FileToFd(file_t& F, std::error_code& EC, OpenFlags Flags) {
313 int fd = F;
314 F = kInvalidFile;
315 EC = std::error_code();
316 return fd;
317}
318
319void CloseFile(file_t& F) {
320 ::close(F);
321 F = kInvalidFile;
322}
323
324#endif // _WIN32
325
326} // namespace fs