Path: blob/master/drivers/windows/dir_access_windows.cpp
9903 views
/**************************************************************************/1/* dir_access_windows.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#if defined(WINDOWS_ENABLED)3132#include "dir_access_windows.h"33#include "file_access_windows.h"3435#include "core/config/project_settings.h"36#include "core/os/memory.h"37#include "core/os/os.h"38#include "core/string/print_string.h"3940#include <cstdio>41#include <cwchar>42#define WIN32_LEAN_AND_MEAN43#include <windows.h>4445typedef struct _NT_IO_STATUS_BLOCK {46union {47LONG Status;48PVOID Pointer;49} DUMMY;50ULONG_PTR Information;51} NT_IO_STATUS_BLOCK;5253typedef struct _NT_FILE_CASE_SENSITIVE_INFO {54ULONG Flags;55} NT_FILE_CASE_SENSITIVE_INFO;5657typedef enum _NT_FILE_INFORMATION_CLASS {58FileCaseSensitiveInformation = 71,59} NT_FILE_INFORMATION_CLASS;6061#define NT_FILE_CS_FLAG_CASE_SENSITIVE_DIR 0x000000016263extern "C" NTSYSAPI LONG NTAPI NtQueryInformationFile(HANDLE FileHandle, NT_IO_STATUS_BLOCK *IoStatusBlock, PVOID FileInformation, ULONG Length, NT_FILE_INFORMATION_CLASS FileInformationClass);6465struct DirAccessWindowsPrivate {66HANDLE h; // handle for FindFirstFile.67WIN32_FIND_DATA f;68WIN32_FIND_DATAW fu; // Unicode version.69};7071String DirAccessWindows::fix_path(const String &p_path) const {72String r_path = DirAccess::fix_path(p_path.trim_prefix(R"(\\?\)").replace_char('\\', '/'));73if (r_path.ends_with(":")) {74r_path += "/";75}76if (r_path.is_relative_path()) {77r_path = current_dir.trim_prefix(R"(\\?\)").replace_char('\\', '/').path_join(r_path);78} else if (r_path == ".") {79r_path = current_dir.trim_prefix(R"(\\?\)").replace_char('\\', '/');80}81r_path = r_path.simplify_path();82r_path = r_path.replace_char('/', '\\');83if (!r_path.is_network_share_path() && !r_path.begins_with(R"(\\?\)")) {84r_path = R"(\\?\)" + r_path;85}86return r_path;87}8889// CreateFolderAsync9091Error DirAccessWindows::list_dir_begin() {92_cisdir = false;93_cishidden = false;9495list_dir_end();96p->h = FindFirstFileExW((LPCWSTR)(String(current_dir + "\\*").utf16().get_data()), FindExInfoStandard, &p->fu, FindExSearchNameMatch, nullptr, 0);9798if (p->h == INVALID_HANDLE_VALUE) {99return ERR_CANT_OPEN;100}101102return OK;103}104105String DirAccessWindows::get_next() {106if (p->h == INVALID_HANDLE_VALUE) {107return "";108}109110_cisdir = (p->fu.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);111_cishidden = (p->fu.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN);112113String name = String::utf16((const char16_t *)(p->fu.cFileName));114115if (FindNextFileW(p->h, &p->fu) == 0) {116FindClose(p->h);117p->h = INVALID_HANDLE_VALUE;118}119120return name;121}122123bool DirAccessWindows::current_is_dir() const {124return _cisdir;125}126127bool DirAccessWindows::current_is_hidden() const {128return _cishidden;129}130131void DirAccessWindows::list_dir_end() {132if (p->h != INVALID_HANDLE_VALUE) {133FindClose(p->h);134p->h = INVALID_HANDLE_VALUE;135}136}137138int DirAccessWindows::get_drive_count() {139return drive_count;140}141142String DirAccessWindows::get_drive(int p_drive) {143if (p_drive < 0 || p_drive >= drive_count) {144return "";145}146147return String::chr(drives[p_drive]) + ":";148}149150Error DirAccessWindows::change_dir(String p_dir) {151GLOBAL_LOCK_FUNCTION152153String dir = fix_path(p_dir);154155Char16String real_current_dir_name;156size_t str_len = GetCurrentDirectoryW(0, nullptr);157real_current_dir_name.resize_uninitialized(str_len + 1);158GetCurrentDirectoryW(real_current_dir_name.size(), (LPWSTR)real_current_dir_name.ptrw());159String prev_dir = String::utf16((const char16_t *)real_current_dir_name.get_data());160161SetCurrentDirectoryW((LPCWSTR)(current_dir.utf16().get_data()));162bool worked = (SetCurrentDirectoryW((LPCWSTR)(dir.utf16().get_data())) != 0);163164String base = _get_root_path();165if (!base.is_empty()) {166str_len = GetCurrentDirectoryW(0, nullptr);167real_current_dir_name.resize_uninitialized(str_len + 1);168GetCurrentDirectoryW(real_current_dir_name.size(), (LPWSTR)real_current_dir_name.ptrw());169String new_dir = String::utf16((const char16_t *)real_current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace_char('\\', '/');170if (!new_dir.begins_with(base)) {171worked = false;172}173}174175if (worked) {176str_len = GetCurrentDirectoryW(0, nullptr);177real_current_dir_name.resize_uninitialized(str_len + 1);178GetCurrentDirectoryW(real_current_dir_name.size(), (LPWSTR)real_current_dir_name.ptrw());179current_dir = String::utf16((const char16_t *)real_current_dir_name.get_data());180}181182SetCurrentDirectoryW((LPCWSTR)(prev_dir.utf16().get_data()));183184return worked ? OK : ERR_INVALID_PARAMETER;185}186187Error DirAccessWindows::make_dir(String p_dir) {188GLOBAL_LOCK_FUNCTION189190if (FileAccessWindows::is_path_invalid(p_dir)) {191#ifdef DEBUG_ENABLED192WARN_PRINT("The path :" + p_dir + " is a reserved Windows system pipe, so it can't be used for creating directories.");193#endif194return ERR_INVALID_PARAMETER;195}196197String dir = fix_path(p_dir);198199bool success;200int err;201202success = CreateDirectoryW((LPCWSTR)(dir.utf16().get_data()), nullptr);203err = GetLastError();204205if (success) {206return OK;207}208209if (err == ERROR_ALREADY_EXISTS || err == ERROR_ACCESS_DENIED) {210return ERR_ALREADY_EXISTS;211}212213return ERR_CANT_CREATE;214}215216String DirAccessWindows::get_current_dir(bool p_include_drive) const {217String cdir = current_dir.trim_prefix(R"(\\?\)").replace_char('\\', '/');218String base = _get_root_path();219if (!base.is_empty()) {220String bd = cdir.replace_first(base, "");221if (bd.begins_with("/")) {222return _get_root_string() + bd.substr(1);223} else {224return _get_root_string() + bd;225}226}227228if (p_include_drive) {229return cdir;230} else {231if (_get_root_string().is_empty()) {232int pos = cdir.find_char(':');233if (pos != -1) {234return cdir.substr(pos + 1);235}236}237return cdir;238}239}240241bool DirAccessWindows::file_exists(String p_file) {242GLOBAL_LOCK_FUNCTION243244String file = fix_path(p_file);245246DWORD fileAttr;247fileAttr = GetFileAttributesW((LPCWSTR)(file.utf16().get_data()));248if (INVALID_FILE_ATTRIBUTES == fileAttr) {249return false;250}251252return !(fileAttr & FILE_ATTRIBUTE_DIRECTORY);253}254255bool DirAccessWindows::dir_exists(String p_dir) {256GLOBAL_LOCK_FUNCTION257258String dir = fix_path(p_dir);259260DWORD fileAttr;261fileAttr = GetFileAttributesW((LPCWSTR)(dir.utf16().get_data()));262if (INVALID_FILE_ATTRIBUTES == fileAttr) {263return false;264}265return (fileAttr & FILE_ATTRIBUTE_DIRECTORY);266}267268Error DirAccessWindows::rename(String p_path, String p_new_path) {269String path = fix_path(p_path);270String new_path = fix_path(p_new_path);271272// If we're only changing file name case we need to do a little juggling273if (path.to_lower() == new_path.to_lower()) {274if (dir_exists(path)) {275// The path is a dir; just rename276return MoveFileW((LPCWSTR)(path.utf16().get_data()), (LPCWSTR)(new_path.utf16().get_data())) != 0 ? OK : FAILED;277}278// The path is a file; juggle279// Note: do not use GetTempFileNameW, it's not long path aware!280Char16String tmpfile_utf16;281uint64_t id = OS::get_singleton()->get_ticks_usec();282while (true) {283tmpfile_utf16 = (path + itos(id++) + ".tmp").utf16();284HANDLE handle = CreateFileW((LPCWSTR)tmpfile_utf16.get_data(), GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);285if (handle != INVALID_HANDLE_VALUE) {286CloseHandle(handle);287break;288}289if (GetLastError() != ERROR_FILE_EXISTS && GetLastError() != ERROR_SHARING_VIOLATION) {290return FAILED;291}292}293294if (!::ReplaceFileW((LPCWSTR)tmpfile_utf16.get_data(), (LPCWSTR)(path.utf16().get_data()), nullptr, 0, nullptr, nullptr)) {295DeleteFileW((LPCWSTR)tmpfile_utf16.get_data());296return FAILED;297}298299return MoveFileW((LPCWSTR)tmpfile_utf16.get_data(), (LPCWSTR)(new_path.utf16().get_data())) != 0 ? OK : FAILED;300301} else {302if (file_exists(new_path)) {303if (remove(new_path) != OK) {304return FAILED;305}306}307308return MoveFileW((LPCWSTR)(path.utf16().get_data()), (LPCWSTR)(new_path.utf16().get_data())) != 0 ? OK : FAILED;309}310}311312Error DirAccessWindows::remove(String p_path) {313String path = fix_path(p_path);314const Char16String &path_utf16 = path.utf16();315316DWORD fileAttr;317318fileAttr = GetFileAttributesW((LPCWSTR)(path_utf16.get_data()));319if (INVALID_FILE_ATTRIBUTES == fileAttr) {320return FAILED;321}322if ((fileAttr & FILE_ATTRIBUTE_DIRECTORY)) {323return RemoveDirectoryW((LPCWSTR)(path_utf16.get_data())) != 0 ? OK : FAILED;324} else {325return DeleteFileW((LPCWSTR)(path_utf16.get_data())) != 0 ? OK : FAILED;326}327}328329uint64_t DirAccessWindows::get_space_left() {330uint64_t bytes = 0;331332String path = fix_path(current_dir);333334if (!path.ends_with("\\")) {335path += "\\";336}337338if (!GetDiskFreeSpaceExW((LPCWSTR)(path.utf16().get_data()), (PULARGE_INTEGER)&bytes, nullptr, nullptr)) {339return 0;340}341342// This is either 0 or a value in bytes.343return bytes;344}345346String DirAccessWindows::get_filesystem_type() const {347String path = current_dir.trim_prefix(R"(\\?\)");348349if (path.is_network_share_path()) {350return "Network Share";351}352353int unit_end = path.find_char(':');354ERR_FAIL_COND_V(unit_end == -1, String());355String unit = path.substr(0, unit_end + 1) + "\\";356357WCHAR szVolumeName[100];358WCHAR szFileSystemName[10];359DWORD dwSerialNumber = 0;360DWORD dwMaxFileNameLength = 0;361DWORD dwFileSystemFlags = 0;362363if (::GetVolumeInformationW((LPCWSTR)(unit.utf16().get_data()),364szVolumeName,365sizeof(szVolumeName),366&dwSerialNumber,367&dwMaxFileNameLength,368&dwFileSystemFlags,369szFileSystemName,370sizeof(szFileSystemName)) == TRUE) {371return String::utf16((const char16_t *)szFileSystemName).to_upper();372}373374ERR_FAIL_V("");375}376377bool DirAccessWindows::is_case_sensitive(const String &p_path) const {378String f = fix_path(p_path);379380HANDLE h_file = ::CreateFileW((LPCWSTR)(f.utf16().get_data()), 0,381FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,382nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);383384if (h_file == INVALID_HANDLE_VALUE) {385return false;386}387388NT_IO_STATUS_BLOCK io_status_block;389NT_FILE_CASE_SENSITIVE_INFO file_info;390LONG out = NtQueryInformationFile(h_file, &io_status_block, &file_info, sizeof(NT_FILE_CASE_SENSITIVE_INFO), FileCaseSensitiveInformation);391::CloseHandle(h_file);392393if (out >= 0) {394return file_info.Flags & NT_FILE_CS_FLAG_CASE_SENSITIVE_DIR;395} else {396return false;397}398}399400typedef struct {401ULONGLONG LowPart;402ULONGLONG HighPart;403} GD_FILE_ID_128;404405typedef struct {406ULONGLONG VolumeSerialNumber;407GD_FILE_ID_128 FileId;408} GD_FILE_ID_INFO;409410bool DirAccessWindows::is_equivalent(const String &p_path_a, const String &p_path_b) const {411String f1 = fix_path(p_path_a);412GD_FILE_ID_INFO st1;413HANDLE 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);414if (h1 == INVALID_HANDLE_VALUE) {415return DirAccess::is_equivalent(p_path_a, p_path_b);416}417::GetFileInformationByHandleEx(h1, (FILE_INFO_BY_HANDLE_CLASS)0x12 /*FileIdInfo*/, &st1, sizeof(st1));418::CloseHandle(h1);419420String f2 = fix_path(p_path_b);421GD_FILE_ID_INFO st2;422HANDLE 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);423if (h2 == INVALID_HANDLE_VALUE) {424return DirAccess::is_equivalent(p_path_a, p_path_b);425}426::GetFileInformationByHandleEx(h2, (FILE_INFO_BY_HANDLE_CLASS)0x12 /*FileIdInfo*/, &st2, sizeof(st2));427::CloseHandle(h2);428429return (st1.VolumeSerialNumber == st2.VolumeSerialNumber) && (st1.FileId.LowPart == st2.FileId.LowPart) && (st1.FileId.HighPart == st2.FileId.HighPart);430}431432bool DirAccessWindows::is_link(String p_file) {433String f = fix_path(p_file);434435DWORD attr = GetFileAttributesW((LPCWSTR)(f.utf16().get_data()));436if (attr == INVALID_FILE_ATTRIBUTES) {437return false;438}439440return (attr & FILE_ATTRIBUTE_REPARSE_POINT);441}442443String DirAccessWindows::read_link(String p_file) {444String f = fix_path(p_file);445446HANDLE hfile = CreateFileW((LPCWSTR)(f.utf16().get_data()), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);447if (hfile == INVALID_HANDLE_VALUE) {448return f;449}450451DWORD ret = GetFinalPathNameByHandleW(hfile, nullptr, 0, VOLUME_NAME_DOS | FILE_NAME_NORMALIZED);452if (ret == 0) {453return f;454}455Char16String cs;456cs.resize_uninitialized(ret + 1);457GetFinalPathNameByHandleW(hfile, (LPWSTR)cs.ptrw(), ret, VOLUME_NAME_DOS | FILE_NAME_NORMALIZED);458CloseHandle(hfile);459460return String::utf16((const char16_t *)cs.ptr(), ret).trim_prefix(R"(\\?\)").replace_char('\\', '/');461}462463Error DirAccessWindows::create_link(String p_source, String p_target) {464String source = fix_path(p_source);465String target = fix_path(p_target);466467DWORD file_attr = GetFileAttributesW((LPCWSTR)(source.utf16().get_data()));468bool is_dir = (file_attr & FILE_ATTRIBUTE_DIRECTORY);469470DWORD flags = ((is_dir) ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0) | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;471if (CreateSymbolicLinkW((LPCWSTR)target.utf16().get_data(), (LPCWSTR)source.utf16().get_data(), flags) != 0) {472return OK;473} else {474return FAILED;475}476}477478DirAccessWindows::DirAccessWindows() {479p = memnew(DirAccessWindowsPrivate);480p->h = INVALID_HANDLE_VALUE;481482Char16String real_current_dir_name;483size_t str_len = GetCurrentDirectoryW(0, nullptr);484real_current_dir_name.resize_uninitialized(str_len + 1);485GetCurrentDirectoryW(real_current_dir_name.size(), (LPWSTR)real_current_dir_name.ptrw());486current_dir = String::utf16((const char16_t *)real_current_dir_name.get_data());487488DWORD mask = GetLogicalDrives();489490for (int i = 0; i < MAX_DRIVES; i++) {491if (mask & (1 << i)) { //DRIVE EXISTS492493drives[drive_count] = 'A' + i;494drive_count++;495}496}497498change_dir(".");499}500501DirAccessWindows::~DirAccessWindows() {502list_dir_end();503504memdelete(p);505}506507#endif // WINDOWS_ENABLED508509510