Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/drivers/windows/dir_access_windows.cpp
9903 views
1
/**************************************************************************/
2
/* dir_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
#if defined(WINDOWS_ENABLED)
32
33
#include "dir_access_windows.h"
34
#include "file_access_windows.h"
35
36
#include "core/config/project_settings.h"
37
#include "core/os/memory.h"
38
#include "core/os/os.h"
39
#include "core/string/print_string.h"
40
41
#include <cstdio>
42
#include <cwchar>
43
#define WIN32_LEAN_AND_MEAN
44
#include <windows.h>
45
46
typedef struct _NT_IO_STATUS_BLOCK {
47
union {
48
LONG Status;
49
PVOID Pointer;
50
} DUMMY;
51
ULONG_PTR Information;
52
} NT_IO_STATUS_BLOCK;
53
54
typedef struct _NT_FILE_CASE_SENSITIVE_INFO {
55
ULONG Flags;
56
} NT_FILE_CASE_SENSITIVE_INFO;
57
58
typedef enum _NT_FILE_INFORMATION_CLASS {
59
FileCaseSensitiveInformation = 71,
60
} NT_FILE_INFORMATION_CLASS;
61
62
#define NT_FILE_CS_FLAG_CASE_SENSITIVE_DIR 0x00000001
63
64
extern "C" NTSYSAPI LONG NTAPI NtQueryInformationFile(HANDLE FileHandle, NT_IO_STATUS_BLOCK *IoStatusBlock, PVOID FileInformation, ULONG Length, NT_FILE_INFORMATION_CLASS FileInformationClass);
65
66
struct DirAccessWindowsPrivate {
67
HANDLE h; // handle for FindFirstFile.
68
WIN32_FIND_DATA f;
69
WIN32_FIND_DATAW fu; // Unicode version.
70
};
71
72
String DirAccessWindows::fix_path(const String &p_path) const {
73
String r_path = DirAccess::fix_path(p_path.trim_prefix(R"(\\?\)").replace_char('\\', '/'));
74
if (r_path.ends_with(":")) {
75
r_path += "/";
76
}
77
if (r_path.is_relative_path()) {
78
r_path = current_dir.trim_prefix(R"(\\?\)").replace_char('\\', '/').path_join(r_path);
79
} else if (r_path == ".") {
80
r_path = current_dir.trim_prefix(R"(\\?\)").replace_char('\\', '/');
81
}
82
r_path = r_path.simplify_path();
83
r_path = r_path.replace_char('/', '\\');
84
if (!r_path.is_network_share_path() && !r_path.begins_with(R"(\\?\)")) {
85
r_path = R"(\\?\)" + r_path;
86
}
87
return r_path;
88
}
89
90
// CreateFolderAsync
91
92
Error DirAccessWindows::list_dir_begin() {
93
_cisdir = false;
94
_cishidden = false;
95
96
list_dir_end();
97
p->h = FindFirstFileExW((LPCWSTR)(String(current_dir + "\\*").utf16().get_data()), FindExInfoStandard, &p->fu, FindExSearchNameMatch, nullptr, 0);
98
99
if (p->h == INVALID_HANDLE_VALUE) {
100
return ERR_CANT_OPEN;
101
}
102
103
return OK;
104
}
105
106
String DirAccessWindows::get_next() {
107
if (p->h == INVALID_HANDLE_VALUE) {
108
return "";
109
}
110
111
_cisdir = (p->fu.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
112
_cishidden = (p->fu.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);
113
114
String name = String::utf16((const char16_t *)(p->fu.cFileName));
115
116
if (FindNextFileW(p->h, &p->fu) == 0) {
117
FindClose(p->h);
118
p->h = INVALID_HANDLE_VALUE;
119
}
120
121
return name;
122
}
123
124
bool DirAccessWindows::current_is_dir() const {
125
return _cisdir;
126
}
127
128
bool DirAccessWindows::current_is_hidden() const {
129
return _cishidden;
130
}
131
132
void DirAccessWindows::list_dir_end() {
133
if (p->h != INVALID_HANDLE_VALUE) {
134
FindClose(p->h);
135
p->h = INVALID_HANDLE_VALUE;
136
}
137
}
138
139
int DirAccessWindows::get_drive_count() {
140
return drive_count;
141
}
142
143
String DirAccessWindows::get_drive(int p_drive) {
144
if (p_drive < 0 || p_drive >= drive_count) {
145
return "";
146
}
147
148
return String::chr(drives[p_drive]) + ":";
149
}
150
151
Error DirAccessWindows::change_dir(String p_dir) {
152
GLOBAL_LOCK_FUNCTION
153
154
String dir = fix_path(p_dir);
155
156
Char16String real_current_dir_name;
157
size_t str_len = GetCurrentDirectoryW(0, nullptr);
158
real_current_dir_name.resize_uninitialized(str_len + 1);
159
GetCurrentDirectoryW(real_current_dir_name.size(), (LPWSTR)real_current_dir_name.ptrw());
160
String prev_dir = String::utf16((const char16_t *)real_current_dir_name.get_data());
161
162
SetCurrentDirectoryW((LPCWSTR)(current_dir.utf16().get_data()));
163
bool worked = (SetCurrentDirectoryW((LPCWSTR)(dir.utf16().get_data())) != 0);
164
165
String base = _get_root_path();
166
if (!base.is_empty()) {
167
str_len = GetCurrentDirectoryW(0, nullptr);
168
real_current_dir_name.resize_uninitialized(str_len + 1);
169
GetCurrentDirectoryW(real_current_dir_name.size(), (LPWSTR)real_current_dir_name.ptrw());
170
String new_dir = String::utf16((const char16_t *)real_current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace_char('\\', '/');
171
if (!new_dir.begins_with(base)) {
172
worked = false;
173
}
174
}
175
176
if (worked) {
177
str_len = GetCurrentDirectoryW(0, nullptr);
178
real_current_dir_name.resize_uninitialized(str_len + 1);
179
GetCurrentDirectoryW(real_current_dir_name.size(), (LPWSTR)real_current_dir_name.ptrw());
180
current_dir = String::utf16((const char16_t *)real_current_dir_name.get_data());
181
}
182
183
SetCurrentDirectoryW((LPCWSTR)(prev_dir.utf16().get_data()));
184
185
return worked ? OK : ERR_INVALID_PARAMETER;
186
}
187
188
Error DirAccessWindows::make_dir(String p_dir) {
189
GLOBAL_LOCK_FUNCTION
190
191
if (FileAccessWindows::is_path_invalid(p_dir)) {
192
#ifdef DEBUG_ENABLED
193
WARN_PRINT("The path :" + p_dir + " is a reserved Windows system pipe, so it can't be used for creating directories.");
194
#endif
195
return ERR_INVALID_PARAMETER;
196
}
197
198
String dir = fix_path(p_dir);
199
200
bool success;
201
int err;
202
203
success = CreateDirectoryW((LPCWSTR)(dir.utf16().get_data()), nullptr);
204
err = GetLastError();
205
206
if (success) {
207
return OK;
208
}
209
210
if (err == ERROR_ALREADY_EXISTS || err == ERROR_ACCESS_DENIED) {
211
return ERR_ALREADY_EXISTS;
212
}
213
214
return ERR_CANT_CREATE;
215
}
216
217
String DirAccessWindows::get_current_dir(bool p_include_drive) const {
218
String cdir = current_dir.trim_prefix(R"(\\?\)").replace_char('\\', '/');
219
String base = _get_root_path();
220
if (!base.is_empty()) {
221
String bd = cdir.replace_first(base, "");
222
if (bd.begins_with("/")) {
223
return _get_root_string() + bd.substr(1);
224
} else {
225
return _get_root_string() + bd;
226
}
227
}
228
229
if (p_include_drive) {
230
return cdir;
231
} else {
232
if (_get_root_string().is_empty()) {
233
int pos = cdir.find_char(':');
234
if (pos != -1) {
235
return cdir.substr(pos + 1);
236
}
237
}
238
return cdir;
239
}
240
}
241
242
bool DirAccessWindows::file_exists(String p_file) {
243
GLOBAL_LOCK_FUNCTION
244
245
String file = fix_path(p_file);
246
247
DWORD fileAttr;
248
fileAttr = GetFileAttributesW((LPCWSTR)(file.utf16().get_data()));
249
if (INVALID_FILE_ATTRIBUTES == fileAttr) {
250
return false;
251
}
252
253
return !(fileAttr & FILE_ATTRIBUTE_DIRECTORY);
254
}
255
256
bool DirAccessWindows::dir_exists(String p_dir) {
257
GLOBAL_LOCK_FUNCTION
258
259
String dir = fix_path(p_dir);
260
261
DWORD fileAttr;
262
fileAttr = GetFileAttributesW((LPCWSTR)(dir.utf16().get_data()));
263
if (INVALID_FILE_ATTRIBUTES == fileAttr) {
264
return false;
265
}
266
return (fileAttr & FILE_ATTRIBUTE_DIRECTORY);
267
}
268
269
Error DirAccessWindows::rename(String p_path, String p_new_path) {
270
String path = fix_path(p_path);
271
String new_path = fix_path(p_new_path);
272
273
// If we're only changing file name case we need to do a little juggling
274
if (path.to_lower() == new_path.to_lower()) {
275
if (dir_exists(path)) {
276
// The path is a dir; just rename
277
return MoveFileW((LPCWSTR)(path.utf16().get_data()), (LPCWSTR)(new_path.utf16().get_data())) != 0 ? OK : FAILED;
278
}
279
// The path is a file; juggle
280
// Note: do not use GetTempFileNameW, it's not long path aware!
281
Char16String tmpfile_utf16;
282
uint64_t id = OS::get_singleton()->get_ticks_usec();
283
while (true) {
284
tmpfile_utf16 = (path + itos(id++) + ".tmp").utf16();
285
HANDLE handle = CreateFileW((LPCWSTR)tmpfile_utf16.get_data(), GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
286
if (handle != INVALID_HANDLE_VALUE) {
287
CloseHandle(handle);
288
break;
289
}
290
if (GetLastError() != ERROR_FILE_EXISTS && GetLastError() != ERROR_SHARING_VIOLATION) {
291
return FAILED;
292
}
293
}
294
295
if (!::ReplaceFileW((LPCWSTR)tmpfile_utf16.get_data(), (LPCWSTR)(path.utf16().get_data()), nullptr, 0, nullptr, nullptr)) {
296
DeleteFileW((LPCWSTR)tmpfile_utf16.get_data());
297
return FAILED;
298
}
299
300
return MoveFileW((LPCWSTR)tmpfile_utf16.get_data(), (LPCWSTR)(new_path.utf16().get_data())) != 0 ? OK : FAILED;
301
302
} else {
303
if (file_exists(new_path)) {
304
if (remove(new_path) != OK) {
305
return FAILED;
306
}
307
}
308
309
return MoveFileW((LPCWSTR)(path.utf16().get_data()), (LPCWSTR)(new_path.utf16().get_data())) != 0 ? OK : FAILED;
310
}
311
}
312
313
Error DirAccessWindows::remove(String p_path) {
314
String path = fix_path(p_path);
315
const Char16String &path_utf16 = path.utf16();
316
317
DWORD fileAttr;
318
319
fileAttr = GetFileAttributesW((LPCWSTR)(path_utf16.get_data()));
320
if (INVALID_FILE_ATTRIBUTES == fileAttr) {
321
return FAILED;
322
}
323
if ((fileAttr & FILE_ATTRIBUTE_DIRECTORY)) {
324
return RemoveDirectoryW((LPCWSTR)(path_utf16.get_data())) != 0 ? OK : FAILED;
325
} else {
326
return DeleteFileW((LPCWSTR)(path_utf16.get_data())) != 0 ? OK : FAILED;
327
}
328
}
329
330
uint64_t DirAccessWindows::get_space_left() {
331
uint64_t bytes = 0;
332
333
String path = fix_path(current_dir);
334
335
if (!path.ends_with("\\")) {
336
path += "\\";
337
}
338
339
if (!GetDiskFreeSpaceExW((LPCWSTR)(path.utf16().get_data()), (PULARGE_INTEGER)&bytes, nullptr, nullptr)) {
340
return 0;
341
}
342
343
// This is either 0 or a value in bytes.
344
return bytes;
345
}
346
347
String DirAccessWindows::get_filesystem_type() const {
348
String path = current_dir.trim_prefix(R"(\\?\)");
349
350
if (path.is_network_share_path()) {
351
return "Network Share";
352
}
353
354
int unit_end = path.find_char(':');
355
ERR_FAIL_COND_V(unit_end == -1, String());
356
String unit = path.substr(0, unit_end + 1) + "\\";
357
358
WCHAR szVolumeName[100];
359
WCHAR szFileSystemName[10];
360
DWORD dwSerialNumber = 0;
361
DWORD dwMaxFileNameLength = 0;
362
DWORD dwFileSystemFlags = 0;
363
364
if (::GetVolumeInformationW((LPCWSTR)(unit.utf16().get_data()),
365
szVolumeName,
366
sizeof(szVolumeName),
367
&dwSerialNumber,
368
&dwMaxFileNameLength,
369
&dwFileSystemFlags,
370
szFileSystemName,
371
sizeof(szFileSystemName)) == TRUE) {
372
return String::utf16((const char16_t *)szFileSystemName).to_upper();
373
}
374
375
ERR_FAIL_V("");
376
}
377
378
bool DirAccessWindows::is_case_sensitive(const String &p_path) const {
379
String f = fix_path(p_path);
380
381
HANDLE h_file = ::CreateFileW((LPCWSTR)(f.utf16().get_data()), 0,
382
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
383
nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
384
385
if (h_file == INVALID_HANDLE_VALUE) {
386
return false;
387
}
388
389
NT_IO_STATUS_BLOCK io_status_block;
390
NT_FILE_CASE_SENSITIVE_INFO file_info;
391
LONG out = NtQueryInformationFile(h_file, &io_status_block, &file_info, sizeof(NT_FILE_CASE_SENSITIVE_INFO), FileCaseSensitiveInformation);
392
::CloseHandle(h_file);
393
394
if (out >= 0) {
395
return file_info.Flags & NT_FILE_CS_FLAG_CASE_SENSITIVE_DIR;
396
} else {
397
return false;
398
}
399
}
400
401
typedef struct {
402
ULONGLONG LowPart;
403
ULONGLONG HighPart;
404
} GD_FILE_ID_128;
405
406
typedef struct {
407
ULONGLONG VolumeSerialNumber;
408
GD_FILE_ID_128 FileId;
409
} GD_FILE_ID_INFO;
410
411
bool DirAccessWindows::is_equivalent(const String &p_path_a, const String &p_path_b) const {
412
String f1 = fix_path(p_path_a);
413
GD_FILE_ID_INFO st1;
414
HANDLE h1 = ::CreateFileW((LPCWSTR)(f1.utf16().get_data()), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
415
if (h1 == INVALID_HANDLE_VALUE) {
416
return DirAccess::is_equivalent(p_path_a, p_path_b);
417
}
418
::GetFileInformationByHandleEx(h1, (FILE_INFO_BY_HANDLE_CLASS)0x12 /*FileIdInfo*/, &st1, sizeof(st1));
419
::CloseHandle(h1);
420
421
String f2 = fix_path(p_path_b);
422
GD_FILE_ID_INFO st2;
423
HANDLE h2 = ::CreateFileW((LPCWSTR)(f2.utf16().get_data()), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
424
if (h2 == INVALID_HANDLE_VALUE) {
425
return DirAccess::is_equivalent(p_path_a, p_path_b);
426
}
427
::GetFileInformationByHandleEx(h2, (FILE_INFO_BY_HANDLE_CLASS)0x12 /*FileIdInfo*/, &st2, sizeof(st2));
428
::CloseHandle(h2);
429
430
return (st1.VolumeSerialNumber == st2.VolumeSerialNumber) && (st1.FileId.LowPart == st2.FileId.LowPart) && (st1.FileId.HighPart == st2.FileId.HighPart);
431
}
432
433
bool DirAccessWindows::is_link(String p_file) {
434
String f = fix_path(p_file);
435
436
DWORD attr = GetFileAttributesW((LPCWSTR)(f.utf16().get_data()));
437
if (attr == INVALID_FILE_ATTRIBUTES) {
438
return false;
439
}
440
441
return (attr & FILE_ATTRIBUTE_REPARSE_POINT);
442
}
443
444
String DirAccessWindows::read_link(String p_file) {
445
String f = fix_path(p_file);
446
447
HANDLE hfile = CreateFileW((LPCWSTR)(f.utf16().get_data()), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
448
if (hfile == INVALID_HANDLE_VALUE) {
449
return f;
450
}
451
452
DWORD ret = GetFinalPathNameByHandleW(hfile, nullptr, 0, VOLUME_NAME_DOS | FILE_NAME_NORMALIZED);
453
if (ret == 0) {
454
return f;
455
}
456
Char16String cs;
457
cs.resize_uninitialized(ret + 1);
458
GetFinalPathNameByHandleW(hfile, (LPWSTR)cs.ptrw(), ret, VOLUME_NAME_DOS | FILE_NAME_NORMALIZED);
459
CloseHandle(hfile);
460
461
return String::utf16((const char16_t *)cs.ptr(), ret).trim_prefix(R"(\\?\)").replace_char('\\', '/');
462
}
463
464
Error DirAccessWindows::create_link(String p_source, String p_target) {
465
String source = fix_path(p_source);
466
String target = fix_path(p_target);
467
468
DWORD file_attr = GetFileAttributesW((LPCWSTR)(source.utf16().get_data()));
469
bool is_dir = (file_attr & FILE_ATTRIBUTE_DIRECTORY);
470
471
DWORD flags = ((is_dir) ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0) | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
472
if (CreateSymbolicLinkW((LPCWSTR)target.utf16().get_data(), (LPCWSTR)source.utf16().get_data(), flags) != 0) {
473
return OK;
474
} else {
475
return FAILED;
476
}
477
}
478
479
DirAccessWindows::DirAccessWindows() {
480
p = memnew(DirAccessWindowsPrivate);
481
p->h = INVALID_HANDLE_VALUE;
482
483
Char16String real_current_dir_name;
484
size_t str_len = GetCurrentDirectoryW(0, nullptr);
485
real_current_dir_name.resize_uninitialized(str_len + 1);
486
GetCurrentDirectoryW(real_current_dir_name.size(), (LPWSTR)real_current_dir_name.ptrw());
487
current_dir = String::utf16((const char16_t *)real_current_dir_name.get_data());
488
489
DWORD mask = GetLogicalDrives();
490
491
for (int i = 0; i < MAX_DRIVES; i++) {
492
if (mask & (1 << i)) { //DRIVE EXISTS
493
494
drives[drive_count] = 'A' + i;
495
drive_count++;
496
}
497
}
498
499
change_dir(".");
500
}
501
502
DirAccessWindows::~DirAccessWindows() {
503
list_dir_end();
504
505
memdelete(p);
506
}
507
508
#endif // WINDOWS_ENABLED
509
510