1/**************************************************************************/
2/* file_access_windows.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#ifdef WINDOWS_ENABLED
32
33#include "file_access_windows.h"
34
35#include "core/os/os.h"
36#include "core/string/print_string.h"
37#include <share.h> // _SH_DENYNO
38#include <shlwapi.h>
39#define WIN32_LEAN_AND_MEAN
40#include <windows.h>
41
42#include <errno.h>
43#include <sys/stat.h>
44#include <sys/types.h>
45#include <tchar.h>
46#include <wchar.h>
47
48#ifdef _MSC_VER
49#define S_ISREG(m) ((m)&_S_IFREG)
50#endif
51
52void FileAccessWindows::check_errors() const {
53 ERR_FAIL_NULL(f);
54
55 if (feof(f)) {
56 last_error = ERR_FILE_EOF;
57 }
58}
59
60bool FileAccessWindows::is_path_invalid(const String &p_path) {
61 // Check for invalid operating system file.
62 String fname = p_path;
63 int dot = fname.find(".");
64 if (dot != -1) {
65 fname = fname.substr(0, dot);
66 }
67 fname = fname.to_lower();
68 return invalid_files.has(fname);
69}
70
71String FileAccessWindows::fix_path(const String &p_path) const {
72 String r_path = FileAccess::fix_path(p_path);
73 if (r_path.is_absolute_path() && !r_path.is_network_share_path() && r_path.length() > MAX_PATH) {
74 r_path = "\\\\?\\" + r_path.replace("/", "\\");
75 }
76 return r_path;
77}
78
79Error FileAccessWindows::open_internal(const String &p_path, int p_mode_flags) {
80 if (is_path_invalid(p_path)) {
81#ifdef DEBUG_ENABLED
82 if (p_mode_flags != READ) {
83 WARN_PRINT("The path :" + p_path + " is a reserved Windows system pipe, so it can't be used for creating files.");
84 }
85#endif
86 return ERR_INVALID_PARAMETER;
87 }
88
89 _close();
90
91 path_src = p_path;
92 path = fix_path(p_path);
93
94 const WCHAR *mode_string;
95
96 if (p_mode_flags == READ) {
97 mode_string = L"rb";
98 } else if (p_mode_flags == WRITE) {
99 mode_string = L"wb";
100 } else if (p_mode_flags == READ_WRITE) {
101 mode_string = L"rb+";
102 } else if (p_mode_flags == WRITE_READ) {
103 mode_string = L"wb+";
104 } else {
105 return ERR_INVALID_PARAMETER;
106 }
107
108 /* Pretty much every implementation that uses fopen as primary
109 backend supports utf8 encoding. */
110
111 struct _stat st;
112 if (_wstat((LPCWSTR)(path.utf16().get_data()), &st) == 0) {
113 if (!S_ISREG(st.st_mode)) {
114 return ERR_FILE_CANT_OPEN;
115 }
116 }
117
118#ifdef TOOLS_ENABLED
119 // Windows is case insensitive, but all other platforms are sensitive to it
120 // To ease cross-platform development, we issue a warning if users try to access
121 // a file using the wrong case (which *works* on Windows, but won't on other
122 // platforms).
123 if (p_mode_flags == READ) {
124 WIN32_FIND_DATAW d;
125 HANDLE fnd = FindFirstFileW((LPCWSTR)(path.utf16().get_data()), &d);
126 if (fnd != INVALID_HANDLE_VALUE) {
127 String fname = String::utf16((const char16_t *)(d.cFileName));
128 if (!fname.is_empty()) {
129 String base_file = path.get_file();
130 if (base_file != fname && base_file.findn(fname) == 0) {
131 WARN_PRINT("Case mismatch opening requested file '" + base_file + "', stored as '" + fname + "' in the filesystem. This file will not open when exported to other case-sensitive platforms.");
132 }
133 }
134 FindClose(fnd);
135 }
136 }
137#endif
138
139 if (is_backup_save_enabled() && p_mode_flags == WRITE) {
140 save_path = path;
141 // Create a temporary file in the same directory as the target file.
142 WCHAR tmpFileName[MAX_PATH];
143 if (GetTempFileNameW((LPCWSTR)(path.get_base_dir().utf16().get_data()), (LPCWSTR)(path.get_file().utf16().get_data()), 0, tmpFileName) == 0) {
144 last_error = ERR_FILE_CANT_OPEN;
145 return last_error;
146 }
147 path = tmpFileName;
148 }
149
150 f = _wfsopen((LPCWSTR)(path.utf16().get_data()), mode_string, is_backup_save_enabled() ? _SH_SECURE : _SH_DENYNO);
151
152 if (f == nullptr) {
153 switch (errno) {
154 case ENOENT: {
155 last_error = ERR_FILE_NOT_FOUND;
156 } break;
157 default: {
158 last_error = ERR_FILE_CANT_OPEN;
159 } break;
160 }
161 return last_error;
162 } else {
163 last_error = OK;
164 flags = p_mode_flags;
165 return OK;
166 }
167}
168
169void FileAccessWindows::_close() {
170 if (!f) {
171 return;
172 }
173
174 fclose(f);
175 f = nullptr;
176
177 if (!save_path.is_empty()) {
178 // This workaround of trying multiple times is added to deal with paranoid Windows
179 // antiviruses that love reading just written files even if they are not executable, thus
180 // locking the file and preventing renaming from happening.
181
182 bool rename_error = true;
183 const Char16String &path_utf16 = path.utf16();
184 const Char16String &save_path_utf16 = save_path.utf16();
185 for (int i = 0; i < 1000; i++) {
186 if (ReplaceFileW((LPCWSTR)(save_path_utf16.get_data()), (LPCWSTR)(path_utf16.get_data()), nullptr, REPLACEFILE_IGNORE_MERGE_ERRORS | REPLACEFILE_IGNORE_ACL_ERRORS, nullptr, nullptr)) {
187 rename_error = false;
188 } else {
189 // Either the target exists and is locked (temporarily, hopefully)
190 // or it doesn't exist; let's assume the latter before re-trying.
191 rename_error = _wrename((LPCWSTR)(path_utf16.get_data()), (LPCWSTR)(save_path_utf16.get_data())) != 0;
192 }
193
194 if (!rename_error) {
195 break;
196 }
197
198 OS::get_singleton()->delay_usec(1000);
199 }
200
201 if (rename_error) {
202 if (close_fail_notify) {
203 close_fail_notify(save_path);
204 }
205 }
206
207 save_path = "";
208
209 ERR_FAIL_COND_MSG(rename_error, "Safe save failed. This may be a permissions problem, but also may happen because you are running a paranoid antivirus. If this is the case, please switch to Windows Defender or disable the 'safe save' option in editor settings. This makes it work, but increases the risk of file corruption in a crash.");
210 }
211}
212
213String FileAccessWindows::get_path() const {
214 return path_src;
215}
216
217String FileAccessWindows::get_path_absolute() const {
218 return path;
219}
220
221bool FileAccessWindows::is_open() const {
222 return (f != nullptr);
223}
224
225void FileAccessWindows::seek(uint64_t p_position) {
226 ERR_FAIL_NULL(f);
227
228 last_error = OK;
229 if (_fseeki64(f, p_position, SEEK_SET)) {
230 check_errors();
231 }
232 prev_op = 0;
233}
234
235void FileAccessWindows::seek_end(int64_t p_position) {
236 ERR_FAIL_NULL(f);
237
238 if (_fseeki64(f, p_position, SEEK_END)) {
239 check_errors();
240 }
241 prev_op = 0;
242}
243
244uint64_t FileAccessWindows::get_position() const {
245 int64_t aux_position = _ftelli64(f);
246 if (aux_position < 0) {
247 check_errors();
248 }
249 return aux_position;
250}
251
252uint64_t FileAccessWindows::get_length() const {
253 ERR_FAIL_NULL_V(f, 0);
254
255 uint64_t pos = get_position();
256 _fseeki64(f, 0, SEEK_END);
257 uint64_t size = get_position();
258 _fseeki64(f, pos, SEEK_SET);
259
260 return size;
261}
262
263bool FileAccessWindows::eof_reached() const {
264 check_errors();
265 return last_error == ERR_FILE_EOF;
266}
267
268uint8_t FileAccessWindows::get_8() const {
269 ERR_FAIL_NULL_V(f, 0);
270
271 if (flags == READ_WRITE || flags == WRITE_READ) {
272 if (prev_op == WRITE) {
273 fflush(f);
274 }
275 prev_op = READ;
276 }
277 uint8_t b;
278 if (fread(&b, 1, 1, f) == 0) {
279 check_errors();
280 b = '\0';
281 }
282
283 return b;
284}
285
286uint64_t FileAccessWindows::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
287 ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
288 ERR_FAIL_NULL_V(f, -1);
289
290 if (flags == READ_WRITE || flags == WRITE_READ) {
291 if (prev_op == WRITE) {
292 fflush(f);
293 }
294 prev_op = READ;
295 }
296 uint64_t read = fread(p_dst, 1, p_length, f);
297 check_errors();
298 return read;
299}
300
301Error FileAccessWindows::get_error() const {
302 return last_error;
303}
304
305void FileAccessWindows::flush() {
306 ERR_FAIL_NULL(f);
307
308 fflush(f);
309 if (prev_op == WRITE) {
310 prev_op = 0;
311 }
312}
313
314void FileAccessWindows::store_8(uint8_t p_dest) {
315 ERR_FAIL_NULL(f);
316
317 if (flags == READ_WRITE || flags == WRITE_READ) {
318 if (prev_op == READ) {
319 if (last_error != ERR_FILE_EOF) {
320 fseek(f, 0, SEEK_CUR);
321 }
322 }
323 prev_op = WRITE;
324 }
325 fwrite(&p_dest, 1, 1, f);
326}
327
328void FileAccessWindows::store_buffer(const uint8_t *p_src, uint64_t p_length) {
329 ERR_FAIL_NULL(f);
330 ERR_FAIL_COND(!p_src && p_length > 0);
331
332 if (flags == READ_WRITE || flags == WRITE_READ) {
333 if (prev_op == READ) {
334 if (last_error != ERR_FILE_EOF) {
335 fseek(f, 0, SEEK_CUR);
336 }
337 }
338 prev_op = WRITE;
339 }
340 ERR_FAIL_COND(fwrite(p_src, 1, p_length, f) != (size_t)p_length);
341}
342
343bool FileAccessWindows::file_exists(const String &p_name) {
344 if (is_path_invalid(p_name)) {
345 return false;
346 }
347
348 String filename = fix_path(p_name);
349 FILE *g = _wfsopen((LPCWSTR)(filename.utf16().get_data()), L"rb", _SH_DENYNO);
350 if (g == nullptr) {
351 return false;
352 } else {
353 fclose(g);
354 return true;
355 }
356}
357
358uint64_t FileAccessWindows::_get_modified_time(const String &p_file) {
359 if (is_path_invalid(p_file)) {
360 return 0;
361 }
362
363 String file = fix_path(p_file);
364 if (file.ends_with("/") && file != "/") {
365 file = file.substr(0, file.length() - 1);
366 }
367
368 struct _stat st;
369 int rv = _wstat((LPCWSTR)(file.utf16().get_data()), &st);
370
371 if (rv == 0) {
372 return st.st_mtime;
373 } else {
374 print_verbose("Failed to get modified time for: " + p_file + "");
375 return 0;
376 }
377}
378
379BitField<FileAccess::UnixPermissionFlags> FileAccessWindows::_get_unix_permissions(const String &p_file) {
380 return 0;
381}
382
383Error FileAccessWindows::_set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) {
384 return ERR_UNAVAILABLE;
385}
386
387bool FileAccessWindows::_get_hidden_attribute(const String &p_file) {
388 String file = fix_path(p_file);
389
390 DWORD attrib = GetFileAttributesW((LPCWSTR)file.utf16().get_data());
391 ERR_FAIL_COND_V_MSG(attrib == INVALID_FILE_ATTRIBUTES, false, "Failed to get attributes for: " + p_file);
392 return (attrib & FILE_ATTRIBUTE_HIDDEN);
393}
394
395Error FileAccessWindows::_set_hidden_attribute(const String &p_file, bool p_hidden) {
396 String file = fix_path(p_file);
397
398 DWORD attrib = GetFileAttributesW((LPCWSTR)file.utf16().get_data());
399 ERR_FAIL_COND_V_MSG(attrib == INVALID_FILE_ATTRIBUTES, FAILED, "Failed to get attributes for: " + p_file);
400 BOOL ok;
401 if (p_hidden) {
402 ok = SetFileAttributesW((LPCWSTR)file.utf16().get_data(), attrib | FILE_ATTRIBUTE_HIDDEN);
403 } else {
404 ok = SetFileAttributesW((LPCWSTR)file.utf16().get_data(), attrib & ~FILE_ATTRIBUTE_HIDDEN);
405 }
406 ERR_FAIL_COND_V_MSG(!ok, FAILED, "Failed to set attributes for: " + p_file);
407
408 return OK;
409}
410
411bool FileAccessWindows::_get_read_only_attribute(const String &p_file) {
412 String file = fix_path(p_file);
413
414 DWORD attrib = GetFileAttributesW((LPCWSTR)file.utf16().get_data());
415 ERR_FAIL_COND_V_MSG(attrib == INVALID_FILE_ATTRIBUTES, false, "Failed to get attributes for: " + p_file);
416 return (attrib & FILE_ATTRIBUTE_READONLY);
417}
418
419Error FileAccessWindows::_set_read_only_attribute(const String &p_file, bool p_ro) {
420 String file = fix_path(p_file);
421
422 DWORD attrib = GetFileAttributesW((LPCWSTR)file.utf16().get_data());
423 ERR_FAIL_COND_V_MSG(attrib == INVALID_FILE_ATTRIBUTES, FAILED, "Failed to get attributes for: " + p_file);
424 BOOL ok;
425 if (p_ro) {
426 ok = SetFileAttributesW((LPCWSTR)file.utf16().get_data(), attrib | FILE_ATTRIBUTE_READONLY);
427 } else {
428 ok = SetFileAttributesW((LPCWSTR)file.utf16().get_data(), attrib & ~FILE_ATTRIBUTE_READONLY);
429 }
430 ERR_FAIL_COND_V_MSG(!ok, FAILED, "Failed to set attributes for: " + p_file);
431
432 return OK;
433}
434
435void FileAccessWindows::close() {
436 _close();
437}
438
439FileAccessWindows::~FileAccessWindows() {
440 _close();
441}
442
443HashSet<String> FileAccessWindows::invalid_files;
444
445void FileAccessWindows::initialize() {
446 static const char *reserved_files[]{
447 "con", "prn", "aux", "nul", "com0", "com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9", "lpt0", "lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", nullptr
448 };
449 int reserved_file_index = 0;
450 while (reserved_files[reserved_file_index] != nullptr) {
451 invalid_files.insert(reserved_files[reserved_file_index]);
452 reserved_file_index++;
453 }
454}
455
456void FileAccessWindows::finalize() {
457 invalid_files.clear();
458}
459
460#endif // WINDOWS_ENABLED
461