Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/drivers/windows/file_access_windows.cpp
9903 views
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/config/project_settings.h"
36
#include "core/os/os.h"
37
#include "core/string/print_string.h"
38
39
#include <share.h> // _SH_DENYNO
40
#include <shlwapi.h>
41
#define WIN32_LEAN_AND_MEAN
42
#include <windows.h>
43
44
#include <io.h>
45
#include <sys/stat.h>
46
#include <sys/types.h>
47
#include <tchar.h>
48
#include <cerrno>
49
#include <cwchar>
50
51
#ifdef _MSC_VER
52
#define S_ISREG(m) ((m) & _S_IFREG)
53
#endif
54
55
void FileAccessWindows::check_errors(bool p_write) const {
56
ERR_FAIL_NULL(f);
57
58
last_error = OK;
59
if (ferror(f)) {
60
if (p_write) {
61
last_error = ERR_FILE_CANT_WRITE;
62
} else {
63
last_error = ERR_FILE_CANT_READ;
64
}
65
}
66
if (!p_write && feof(f)) {
67
last_error = ERR_FILE_EOF;
68
}
69
}
70
71
bool FileAccessWindows::is_path_invalid(const String &p_path) {
72
// Check for invalid operating system file.
73
String fname = p_path.get_file().to_lower();
74
75
int dot = fname.find_char('.');
76
if (dot != -1) {
77
fname = fname.substr(0, dot);
78
}
79
return invalid_files.has(fname);
80
}
81
82
String FileAccessWindows::fix_path(const String &p_path) const {
83
String r_path = FileAccess::fix_path(p_path);
84
85
if (r_path.is_relative_path()) {
86
Char16String current_dir_name;
87
size_t str_len = GetCurrentDirectoryW(0, nullptr);
88
current_dir_name.resize_uninitialized(str_len + 1);
89
GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw());
90
r_path = String::utf16((const char16_t *)current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace_char('\\', '/').path_join(r_path);
91
}
92
r_path = r_path.simplify_path();
93
r_path = r_path.replace_char('/', '\\');
94
if (!r_path.is_network_share_path() && !r_path.begins_with(R"(\\?\)")) {
95
r_path = R"(\\?\)" + r_path;
96
}
97
return r_path;
98
}
99
100
Error FileAccessWindows::open_internal(const String &p_path, int p_mode_flags) {
101
if (is_path_invalid(p_path)) {
102
#ifdef DEBUG_ENABLED
103
if (p_mode_flags != READ) {
104
WARN_PRINT("The path :" + p_path + " is a reserved Windows system pipe, so it can't be used for creating files.");
105
}
106
#endif
107
return ERR_INVALID_PARAMETER;
108
}
109
110
_close();
111
112
path_src = p_path;
113
path = fix_path(p_path);
114
115
const WCHAR *mode_string;
116
117
if (p_mode_flags == READ) {
118
mode_string = L"rb";
119
} else if (p_mode_flags == WRITE) {
120
mode_string = L"wb";
121
} else if (p_mode_flags == READ_WRITE) {
122
mode_string = L"rb+";
123
} else if (p_mode_flags == WRITE_READ) {
124
mode_string = L"wb+";
125
} else {
126
return ERR_INVALID_PARAMETER;
127
}
128
129
if (path.ends_with(":\\") || path.ends_with(":")) {
130
return ERR_FILE_CANT_OPEN;
131
}
132
DWORD file_attr = GetFileAttributesW((LPCWSTR)(path.utf16().get_data()));
133
if (file_attr != INVALID_FILE_ATTRIBUTES && (file_attr & FILE_ATTRIBUTE_DIRECTORY)) {
134
return ERR_FILE_CANT_OPEN;
135
}
136
137
#ifdef TOOLS_ENABLED
138
// Windows is case insensitive in the default configuration, but other platforms can be sensitive to it
139
// To ease cross-platform development, we issue a warning if users try to access
140
// a file using the wrong case (which *works* on Windows, but won't on other
141
// platforms), we only check for relative paths, or paths in res:// or user://,
142
// other paths aren't likely to be portable anyway.
143
if (p_mode_flags == READ && (p_path.is_relative_path() || get_access_type() != ACCESS_FILESYSTEM)) {
144
String base_path = p_path;
145
String working_path;
146
String proper_path;
147
148
if (get_access_type() == ACCESS_RESOURCES) {
149
if (ProjectSettings::get_singleton()) {
150
working_path = ProjectSettings::get_singleton()->get_resource_path();
151
if (!working_path.is_empty()) {
152
base_path = working_path.path_to_file(base_path);
153
}
154
}
155
proper_path = "res://";
156
} else if (get_access_type() == ACCESS_USERDATA) {
157
working_path = OS::get_singleton()->get_user_data_dir();
158
if (!working_path.is_empty()) {
159
base_path = working_path.path_to_file(base_path);
160
}
161
proper_path = "user://";
162
}
163
working_path = fix_path(working_path);
164
165
WIN32_FIND_DATAW d;
166
Vector<String> parts = base_path.simplify_path().split("/");
167
168
bool mismatch = false;
169
170
for (const String &part : parts) {
171
working_path = working_path + "\\" + part;
172
173
HANDLE fnd = FindFirstFileW((LPCWSTR)(working_path.utf16().get_data()), &d);
174
if (fnd == INVALID_HANDLE_VALUE) {
175
mismatch = false;
176
break;
177
}
178
179
const String fname = String::utf16((const char16_t *)(d.cFileName));
180
181
FindClose(fnd);
182
183
if (!mismatch) {
184
mismatch = (part != fname && part.findn(fname) == 0);
185
}
186
187
proper_path = proper_path.path_join(fname);
188
}
189
190
if (mismatch) {
191
WARN_PRINT("Case mismatch opening requested file '" + p_path + "', stored as '" + proper_path + "' in the filesystem. This file will not open when exported to other case-sensitive platforms.");
192
}
193
}
194
#endif
195
196
if (is_backup_save_enabled() && p_mode_flags == WRITE) {
197
save_path = path;
198
// Create a temporary file in the same directory as the target file.
199
// Note: do not use GetTempFileNameW, it's not long path aware!
200
String tmpfile;
201
uint64_t id = OS::get_singleton()->get_ticks_usec();
202
while (true) {
203
tmpfile = path + itos(id++) + ".tmp";
204
HANDLE handle = CreateFileW((LPCWSTR)tmpfile.utf16().get_data(), GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
205
if (handle != INVALID_HANDLE_VALUE) {
206
CloseHandle(handle);
207
break;
208
}
209
if (GetLastError() != ERROR_FILE_EXISTS && GetLastError() != ERROR_SHARING_VIOLATION) {
210
last_error = ERR_FILE_CANT_WRITE;
211
return FAILED;
212
}
213
}
214
path = tmpfile;
215
}
216
217
f = _wfsopen((LPCWSTR)(path.utf16().get_data()), mode_string, is_backup_save_enabled() ? ((p_mode_flags == READ) ? _SH_DENYWR : _SH_DENYRW) : _SH_DENYNO);
218
219
if (f == nullptr) {
220
switch (errno) {
221
case ENOENT: {
222
last_error = ERR_FILE_NOT_FOUND;
223
} break;
224
default: {
225
last_error = ERR_FILE_CANT_OPEN;
226
} break;
227
}
228
return last_error;
229
} else {
230
last_error = OK;
231
flags = p_mode_flags;
232
return OK;
233
}
234
}
235
236
void FileAccessWindows::_close() {
237
if (!f) {
238
return;
239
}
240
241
fclose(f);
242
f = nullptr;
243
244
if (!save_path.is_empty()) {
245
// This workaround of trying multiple times is added to deal with paranoid Windows
246
// antiviruses that love reading just written files even if they are not executable, thus
247
// locking the file and preventing renaming from happening.
248
249
bool rename_error = true;
250
const Char16String &path_utf16 = path.utf16();
251
const Char16String &save_path_utf16 = save_path.utf16();
252
for (int i = 0; i < 1000; i++) {
253
if (ReplaceFileW((LPCWSTR)(save_path_utf16.get_data()), (LPCWSTR)(path_utf16.get_data()), nullptr, REPLACEFILE_IGNORE_MERGE_ERRORS | REPLACEFILE_IGNORE_ACL_ERRORS, nullptr, nullptr)) {
254
rename_error = false;
255
} else {
256
// Either the target exists and is locked (temporarily, hopefully)
257
// or it doesn't exist; let's assume the latter before re-trying.
258
rename_error = MoveFileW((LPCWSTR)(path_utf16.get_data()), (LPCWSTR)(save_path_utf16.get_data())) == 0;
259
}
260
261
if (!rename_error) {
262
break;
263
}
264
265
OS::get_singleton()->delay_usec(1000);
266
}
267
268
if (rename_error) {
269
if (close_fail_notify) {
270
close_fail_notify(save_path);
271
}
272
}
273
274
save_path = "";
275
276
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.");
277
}
278
}
279
280
String FileAccessWindows::get_path() const {
281
return path_src;
282
}
283
284
String FileAccessWindows::get_path_absolute() const {
285
return path.trim_prefix(R"(\\?\)").replace_char('\\', '/');
286
}
287
288
bool FileAccessWindows::is_open() const {
289
return (f != nullptr);
290
}
291
292
void FileAccessWindows::seek(uint64_t p_position) {
293
ERR_FAIL_NULL(f);
294
295
if (_fseeki64(f, p_position, SEEK_SET)) {
296
check_errors();
297
}
298
prev_op = 0;
299
}
300
301
void FileAccessWindows::seek_end(int64_t p_position) {
302
ERR_FAIL_NULL(f);
303
304
if (_fseeki64(f, p_position, SEEK_END)) {
305
check_errors();
306
}
307
prev_op = 0;
308
}
309
310
uint64_t FileAccessWindows::get_position() const {
311
ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use.");
312
313
int64_t aux_position = _ftelli64(f);
314
if (aux_position < 0) {
315
check_errors();
316
}
317
return aux_position;
318
}
319
320
uint64_t FileAccessWindows::get_length() const {
321
ERR_FAIL_NULL_V(f, 0);
322
323
uint64_t pos = get_position();
324
_fseeki64(f, 0, SEEK_END);
325
uint64_t size = get_position();
326
_fseeki64(f, pos, SEEK_SET);
327
328
return size;
329
}
330
331
bool FileAccessWindows::eof_reached() const {
332
return feof(f);
333
}
334
335
uint64_t FileAccessWindows::get_buffer(uint8_t *p_dst, uint64_t p_length) const {
336
ERR_FAIL_NULL_V(f, -1);
337
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
338
339
if (flags == READ_WRITE || flags == WRITE_READ) {
340
if (prev_op == WRITE) {
341
fflush(f);
342
}
343
prev_op = READ;
344
}
345
346
uint64_t read = fread(p_dst, 1, p_length, f);
347
check_errors();
348
349
return read;
350
}
351
352
Error FileAccessWindows::get_error() const {
353
return last_error;
354
}
355
356
Error FileAccessWindows::resize(int64_t p_length) {
357
ERR_FAIL_NULL_V_MSG(f, FAILED, "File must be opened before use.");
358
errno_t res = _chsize_s(_fileno(f), p_length);
359
switch (res) {
360
case 0:
361
return OK;
362
case EACCES:
363
case EBADF:
364
return ERR_FILE_CANT_OPEN;
365
case ENOSPC:
366
return ERR_OUT_OF_MEMORY;
367
case EINVAL:
368
return ERR_INVALID_PARAMETER;
369
default:
370
return FAILED;
371
}
372
}
373
374
void FileAccessWindows::flush() {
375
ERR_FAIL_NULL(f);
376
377
fflush(f);
378
if (prev_op == WRITE) {
379
prev_op = 0;
380
}
381
}
382
383
bool FileAccessWindows::store_buffer(const uint8_t *p_src, uint64_t p_length) {
384
ERR_FAIL_NULL_V(f, false);
385
ERR_FAIL_COND_V(!p_src && p_length > 0, false);
386
387
if (flags == READ_WRITE || flags == WRITE_READ) {
388
if (prev_op == READ) {
389
if (last_error != ERR_FILE_EOF) {
390
fseek(f, 0, SEEK_CUR);
391
}
392
}
393
prev_op = WRITE;
394
}
395
396
bool res = fwrite(p_src, 1, p_length, f) == (size_t)p_length;
397
check_errors(true);
398
return res;
399
}
400
401
bool FileAccessWindows::file_exists(const String &p_name) {
402
if (is_path_invalid(p_name)) {
403
return false;
404
}
405
406
String filename = fix_path(p_name);
407
DWORD file_attr = GetFileAttributesW((LPCWSTR)(filename.utf16().get_data()));
408
return (file_attr != INVALID_FILE_ATTRIBUTES) && !(file_attr & FILE_ATTRIBUTE_DIRECTORY);
409
}
410
411
uint64_t FileAccessWindows::_get_modified_time(const String &p_file) {
412
if (is_path_invalid(p_file)) {
413
return 0;
414
}
415
416
String file = fix_path(p_file);
417
if (file.ends_with("\\") && file != "\\") {
418
file = file.substr(0, file.length() - 1);
419
}
420
421
HANDLE handle = CreateFileW((LPCWSTR)(file.utf16().get_data()), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
422
423
if (handle != INVALID_HANDLE_VALUE) {
424
FILETIME ft_create, ft_write;
425
426
bool status = GetFileTime(handle, &ft_create, nullptr, &ft_write);
427
428
CloseHandle(handle);
429
430
if (status) {
431
uint64_t ret = 0;
432
433
// If write time is invalid, fallback to creation time.
434
if (ft_write.dwHighDateTime == 0 && ft_write.dwLowDateTime == 0) {
435
ret = ft_create.dwHighDateTime;
436
ret <<= 32;
437
ret |= ft_create.dwLowDateTime;
438
} else {
439
ret = ft_write.dwHighDateTime;
440
ret <<= 32;
441
ret |= ft_write.dwLowDateTime;
442
}
443
444
const uint64_t WINDOWS_TICKS_PER_SECOND = 10000000;
445
const uint64_t TICKS_TO_UNIX_EPOCH = 116444736000000000LL;
446
447
if (ret >= TICKS_TO_UNIX_EPOCH) {
448
return (ret - TICKS_TO_UNIX_EPOCH) / WINDOWS_TICKS_PER_SECOND;
449
}
450
}
451
}
452
453
return 0;
454
}
455
456
uint64_t FileAccessWindows::_get_access_time(const String &p_file) {
457
if (is_path_invalid(p_file)) {
458
return 0;
459
}
460
461
String file = fix_path(p_file);
462
if (file.ends_with("\\") && file != "\\") {
463
file = file.substr(0, file.length() - 1);
464
}
465
466
HANDLE handle = CreateFileW((LPCWSTR)(file.utf16().get_data()), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
467
468
if (handle != INVALID_HANDLE_VALUE) {
469
FILETIME ft_create, ft_access;
470
471
bool status = GetFileTime(handle, &ft_create, &ft_access, nullptr);
472
473
CloseHandle(handle);
474
475
if (status) {
476
uint64_t ret = 0;
477
478
// If access time is invalid, fallback to creation time.
479
if (ft_access.dwHighDateTime == 0 && ft_access.dwLowDateTime == 0) {
480
ret = ft_create.dwHighDateTime;
481
ret <<= 32;
482
ret |= ft_create.dwLowDateTime;
483
} else {
484
ret = ft_access.dwHighDateTime;
485
ret <<= 32;
486
ret |= ft_access.dwLowDateTime;
487
}
488
489
const uint64_t WINDOWS_TICKS_PER_SECOND = 10000000;
490
const uint64_t TICKS_TO_UNIX_EPOCH = 116444736000000000LL;
491
492
if (ret >= TICKS_TO_UNIX_EPOCH) {
493
return (ret - TICKS_TO_UNIX_EPOCH) / WINDOWS_TICKS_PER_SECOND;
494
}
495
}
496
}
497
498
ERR_FAIL_V_MSG(0, "Failed to get access time for: " + p_file + "");
499
}
500
501
int64_t FileAccessWindows::_get_size(const String &p_file) {
502
if (is_path_invalid(p_file)) {
503
return 0;
504
}
505
506
String file = fix_path(p_file);
507
if (file.ends_with("\\") && file != "\\") {
508
file = file.substr(0, file.length() - 1);
509
}
510
511
DWORD file_attr = GetFileAttributesW((LPCWSTR)(file.utf16().get_data()));
512
HANDLE handle = CreateFileW((LPCWSTR)(file.utf16().get_data()), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
513
514
if (handle != INVALID_HANDLE_VALUE && !(file_attr & FILE_ATTRIBUTE_DIRECTORY)) {
515
LARGE_INTEGER fsize;
516
517
bool status = GetFileSizeEx(handle, &fsize);
518
519
CloseHandle(handle);
520
521
if (status) {
522
return (int64_t)fsize.QuadPart;
523
}
524
}
525
ERR_FAIL_V_MSG(-1, "Failed to get size for: " + p_file + "");
526
}
527
528
BitField<FileAccess::UnixPermissionFlags> FileAccessWindows::_get_unix_permissions(const String &p_file) {
529
return 0;
530
}
531
532
Error FileAccessWindows::_set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) {
533
return ERR_UNAVAILABLE;
534
}
535
536
bool FileAccessWindows::_get_hidden_attribute(const String &p_file) {
537
String file = fix_path(p_file);
538
539
DWORD attrib = GetFileAttributesW((LPCWSTR)file.utf16().get_data());
540
ERR_FAIL_COND_V_MSG(attrib == INVALID_FILE_ATTRIBUTES, false, "Failed to get attributes for: " + p_file);
541
return (attrib & FILE_ATTRIBUTE_HIDDEN);
542
}
543
544
Error FileAccessWindows::_set_hidden_attribute(const String &p_file, bool p_hidden) {
545
String file = fix_path(p_file);
546
const Char16String &file_utf16 = file.utf16();
547
548
DWORD attrib = GetFileAttributesW((LPCWSTR)file_utf16.get_data());
549
ERR_FAIL_COND_V_MSG(attrib == INVALID_FILE_ATTRIBUTES, FAILED, "Failed to get attributes for: " + p_file);
550
BOOL ok;
551
if (p_hidden) {
552
ok = SetFileAttributesW((LPCWSTR)file_utf16.get_data(), attrib | FILE_ATTRIBUTE_HIDDEN);
553
} else {
554
ok = SetFileAttributesW((LPCWSTR)file_utf16.get_data(), attrib & ~FILE_ATTRIBUTE_HIDDEN);
555
}
556
ERR_FAIL_COND_V_MSG(!ok, FAILED, "Failed to set attributes for: " + p_file);
557
558
return OK;
559
}
560
561
bool FileAccessWindows::_get_read_only_attribute(const String &p_file) {
562
String file = fix_path(p_file);
563
564
DWORD attrib = GetFileAttributesW((LPCWSTR)file.utf16().get_data());
565
ERR_FAIL_COND_V_MSG(attrib == INVALID_FILE_ATTRIBUTES, false, "Failed to get attributes for: " + p_file);
566
return (attrib & FILE_ATTRIBUTE_READONLY);
567
}
568
569
Error FileAccessWindows::_set_read_only_attribute(const String &p_file, bool p_ro) {
570
String file = fix_path(p_file);
571
const Char16String &file_utf16 = file.utf16();
572
573
DWORD attrib = GetFileAttributesW((LPCWSTR)file_utf16.get_data());
574
ERR_FAIL_COND_V_MSG(attrib == INVALID_FILE_ATTRIBUTES, FAILED, "Failed to get attributes for: " + p_file);
575
BOOL ok;
576
if (p_ro) {
577
ok = SetFileAttributesW((LPCWSTR)file_utf16.get_data(), attrib | FILE_ATTRIBUTE_READONLY);
578
} else {
579
ok = SetFileAttributesW((LPCWSTR)file_utf16.get_data(), attrib & ~FILE_ATTRIBUTE_READONLY);
580
}
581
ERR_FAIL_COND_V_MSG(!ok, FAILED, "Failed to set attributes for: " + p_file);
582
583
return OK;
584
}
585
586
void FileAccessWindows::close() {
587
_close();
588
}
589
590
FileAccessWindows::~FileAccessWindows() {
591
_close();
592
}
593
594
HashSet<String> FileAccessWindows::invalid_files;
595
596
void FileAccessWindows::initialize() {
597
static const char *reserved_files[]{
598
"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
599
};
600
int reserved_file_index = 0;
601
while (reserved_files[reserved_file_index] != nullptr) {
602
invalid_files.insert(reserved_files[reserved_file_index]);
603
reserved_file_index++;
604
}
605
606
_setmaxstdio(8192);
607
print_verbose(vformat("Maximum number of file handles: %d", _getmaxstdio()));
608
}
609
610
void FileAccessWindows::finalize() {
611
invalid_files.clear();
612
}
613
614
#endif // WINDOWS_ENABLED
615
616