Path: blob/master/drivers/windows/file_access_windows.cpp
9903 views
/**************************************************************************/1/* file_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#ifdef WINDOWS_ENABLED3132#include "file_access_windows.h"3334#include "core/config/project_settings.h"35#include "core/os/os.h"36#include "core/string/print_string.h"3738#include <share.h> // _SH_DENYNO39#include <shlwapi.h>40#define WIN32_LEAN_AND_MEAN41#include <windows.h>4243#include <io.h>44#include <sys/stat.h>45#include <sys/types.h>46#include <tchar.h>47#include <cerrno>48#include <cwchar>4950#ifdef _MSC_VER51#define S_ISREG(m) ((m) & _S_IFREG)52#endif5354void FileAccessWindows::check_errors(bool p_write) const {55ERR_FAIL_NULL(f);5657last_error = OK;58if (ferror(f)) {59if (p_write) {60last_error = ERR_FILE_CANT_WRITE;61} else {62last_error = ERR_FILE_CANT_READ;63}64}65if (!p_write && feof(f)) {66last_error = ERR_FILE_EOF;67}68}6970bool FileAccessWindows::is_path_invalid(const String &p_path) {71// Check for invalid operating system file.72String fname = p_path.get_file().to_lower();7374int dot = fname.find_char('.');75if (dot != -1) {76fname = fname.substr(0, dot);77}78return invalid_files.has(fname);79}8081String FileAccessWindows::fix_path(const String &p_path) const {82String r_path = FileAccess::fix_path(p_path);8384if (r_path.is_relative_path()) {85Char16String current_dir_name;86size_t str_len = GetCurrentDirectoryW(0, nullptr);87current_dir_name.resize_uninitialized(str_len + 1);88GetCurrentDirectoryW(current_dir_name.size(), (LPWSTR)current_dir_name.ptrw());89r_path = String::utf16((const char16_t *)current_dir_name.get_data()).trim_prefix(R"(\\?\)").replace_char('\\', '/').path_join(r_path);90}91r_path = r_path.simplify_path();92r_path = r_path.replace_char('/', '\\');93if (!r_path.is_network_share_path() && !r_path.begins_with(R"(\\?\)")) {94r_path = R"(\\?\)" + r_path;95}96return r_path;97}9899Error FileAccessWindows::open_internal(const String &p_path, int p_mode_flags) {100if (is_path_invalid(p_path)) {101#ifdef DEBUG_ENABLED102if (p_mode_flags != READ) {103WARN_PRINT("The path :" + p_path + " is a reserved Windows system pipe, so it can't be used for creating files.");104}105#endif106return ERR_INVALID_PARAMETER;107}108109_close();110111path_src = p_path;112path = fix_path(p_path);113114const WCHAR *mode_string;115116if (p_mode_flags == READ) {117mode_string = L"rb";118} else if (p_mode_flags == WRITE) {119mode_string = L"wb";120} else if (p_mode_flags == READ_WRITE) {121mode_string = L"rb+";122} else if (p_mode_flags == WRITE_READ) {123mode_string = L"wb+";124} else {125return ERR_INVALID_PARAMETER;126}127128if (path.ends_with(":\\") || path.ends_with(":")) {129return ERR_FILE_CANT_OPEN;130}131DWORD file_attr = GetFileAttributesW((LPCWSTR)(path.utf16().get_data()));132if (file_attr != INVALID_FILE_ATTRIBUTES && (file_attr & FILE_ATTRIBUTE_DIRECTORY)) {133return ERR_FILE_CANT_OPEN;134}135136#ifdef TOOLS_ENABLED137// Windows is case insensitive in the default configuration, but other platforms can be sensitive to it138// To ease cross-platform development, we issue a warning if users try to access139// a file using the wrong case (which *works* on Windows, but won't on other140// platforms), we only check for relative paths, or paths in res:// or user://,141// other paths aren't likely to be portable anyway.142if (p_mode_flags == READ && (p_path.is_relative_path() || get_access_type() != ACCESS_FILESYSTEM)) {143String base_path = p_path;144String working_path;145String proper_path;146147if (get_access_type() == ACCESS_RESOURCES) {148if (ProjectSettings::get_singleton()) {149working_path = ProjectSettings::get_singleton()->get_resource_path();150if (!working_path.is_empty()) {151base_path = working_path.path_to_file(base_path);152}153}154proper_path = "res://";155} else if (get_access_type() == ACCESS_USERDATA) {156working_path = OS::get_singleton()->get_user_data_dir();157if (!working_path.is_empty()) {158base_path = working_path.path_to_file(base_path);159}160proper_path = "user://";161}162working_path = fix_path(working_path);163164WIN32_FIND_DATAW d;165Vector<String> parts = base_path.simplify_path().split("/");166167bool mismatch = false;168169for (const String &part : parts) {170working_path = working_path + "\\" + part;171172HANDLE fnd = FindFirstFileW((LPCWSTR)(working_path.utf16().get_data()), &d);173if (fnd == INVALID_HANDLE_VALUE) {174mismatch = false;175break;176}177178const String fname = String::utf16((const char16_t *)(d.cFileName));179180FindClose(fnd);181182if (!mismatch) {183mismatch = (part != fname && part.findn(fname) == 0);184}185186proper_path = proper_path.path_join(fname);187}188189if (mismatch) {190WARN_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.");191}192}193#endif194195if (is_backup_save_enabled() && p_mode_flags == WRITE) {196save_path = path;197// Create a temporary file in the same directory as the target file.198// Note: do not use GetTempFileNameW, it's not long path aware!199String tmpfile;200uint64_t id = OS::get_singleton()->get_ticks_usec();201while (true) {202tmpfile = path + itos(id++) + ".tmp";203HANDLE handle = CreateFileW((LPCWSTR)tmpfile.utf16().get_data(), GENERIC_WRITE, 0, nullptr, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);204if (handle != INVALID_HANDLE_VALUE) {205CloseHandle(handle);206break;207}208if (GetLastError() != ERROR_FILE_EXISTS && GetLastError() != ERROR_SHARING_VIOLATION) {209last_error = ERR_FILE_CANT_WRITE;210return FAILED;211}212}213path = tmpfile;214}215216f = _wfsopen((LPCWSTR)(path.utf16().get_data()), mode_string, is_backup_save_enabled() ? ((p_mode_flags == READ) ? _SH_DENYWR : _SH_DENYRW) : _SH_DENYNO);217218if (f == nullptr) {219switch (errno) {220case ENOENT: {221last_error = ERR_FILE_NOT_FOUND;222} break;223default: {224last_error = ERR_FILE_CANT_OPEN;225} break;226}227return last_error;228} else {229last_error = OK;230flags = p_mode_flags;231return OK;232}233}234235void FileAccessWindows::_close() {236if (!f) {237return;238}239240fclose(f);241f = nullptr;242243if (!save_path.is_empty()) {244// This workaround of trying multiple times is added to deal with paranoid Windows245// antiviruses that love reading just written files even if they are not executable, thus246// locking the file and preventing renaming from happening.247248bool rename_error = true;249const Char16String &path_utf16 = path.utf16();250const Char16String &save_path_utf16 = save_path.utf16();251for (int i = 0; i < 1000; i++) {252if (ReplaceFileW((LPCWSTR)(save_path_utf16.get_data()), (LPCWSTR)(path_utf16.get_data()), nullptr, REPLACEFILE_IGNORE_MERGE_ERRORS | REPLACEFILE_IGNORE_ACL_ERRORS, nullptr, nullptr)) {253rename_error = false;254} else {255// Either the target exists and is locked (temporarily, hopefully)256// or it doesn't exist; let's assume the latter before re-trying.257rename_error = MoveFileW((LPCWSTR)(path_utf16.get_data()), (LPCWSTR)(save_path_utf16.get_data())) == 0;258}259260if (!rename_error) {261break;262}263264OS::get_singleton()->delay_usec(1000);265}266267if (rename_error) {268if (close_fail_notify) {269close_fail_notify(save_path);270}271}272273save_path = "";274275ERR_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.");276}277}278279String FileAccessWindows::get_path() const {280return path_src;281}282283String FileAccessWindows::get_path_absolute() const {284return path.trim_prefix(R"(\\?\)").replace_char('\\', '/');285}286287bool FileAccessWindows::is_open() const {288return (f != nullptr);289}290291void FileAccessWindows::seek(uint64_t p_position) {292ERR_FAIL_NULL(f);293294if (_fseeki64(f, p_position, SEEK_SET)) {295check_errors();296}297prev_op = 0;298}299300void FileAccessWindows::seek_end(int64_t p_position) {301ERR_FAIL_NULL(f);302303if (_fseeki64(f, p_position, SEEK_END)) {304check_errors();305}306prev_op = 0;307}308309uint64_t FileAccessWindows::get_position() const {310ERR_FAIL_NULL_V_MSG(f, 0, "File must be opened before use.");311312int64_t aux_position = _ftelli64(f);313if (aux_position < 0) {314check_errors();315}316return aux_position;317}318319uint64_t FileAccessWindows::get_length() const {320ERR_FAIL_NULL_V(f, 0);321322uint64_t pos = get_position();323_fseeki64(f, 0, SEEK_END);324uint64_t size = get_position();325_fseeki64(f, pos, SEEK_SET);326327return size;328}329330bool FileAccessWindows::eof_reached() const {331return feof(f);332}333334uint64_t FileAccessWindows::get_buffer(uint8_t *p_dst, uint64_t p_length) const {335ERR_FAIL_NULL_V(f, -1);336ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);337338if (flags == READ_WRITE || flags == WRITE_READ) {339if (prev_op == WRITE) {340fflush(f);341}342prev_op = READ;343}344345uint64_t read = fread(p_dst, 1, p_length, f);346check_errors();347348return read;349}350351Error FileAccessWindows::get_error() const {352return last_error;353}354355Error FileAccessWindows::resize(int64_t p_length) {356ERR_FAIL_NULL_V_MSG(f, FAILED, "File must be opened before use.");357errno_t res = _chsize_s(_fileno(f), p_length);358switch (res) {359case 0:360return OK;361case EACCES:362case EBADF:363return ERR_FILE_CANT_OPEN;364case ENOSPC:365return ERR_OUT_OF_MEMORY;366case EINVAL:367return ERR_INVALID_PARAMETER;368default:369return FAILED;370}371}372373void FileAccessWindows::flush() {374ERR_FAIL_NULL(f);375376fflush(f);377if (prev_op == WRITE) {378prev_op = 0;379}380}381382bool FileAccessWindows::store_buffer(const uint8_t *p_src, uint64_t p_length) {383ERR_FAIL_NULL_V(f, false);384ERR_FAIL_COND_V(!p_src && p_length > 0, false);385386if (flags == READ_WRITE || flags == WRITE_READ) {387if (prev_op == READ) {388if (last_error != ERR_FILE_EOF) {389fseek(f, 0, SEEK_CUR);390}391}392prev_op = WRITE;393}394395bool res = fwrite(p_src, 1, p_length, f) == (size_t)p_length;396check_errors(true);397return res;398}399400bool FileAccessWindows::file_exists(const String &p_name) {401if (is_path_invalid(p_name)) {402return false;403}404405String filename = fix_path(p_name);406DWORD file_attr = GetFileAttributesW((LPCWSTR)(filename.utf16().get_data()));407return (file_attr != INVALID_FILE_ATTRIBUTES) && !(file_attr & FILE_ATTRIBUTE_DIRECTORY);408}409410uint64_t FileAccessWindows::_get_modified_time(const String &p_file) {411if (is_path_invalid(p_file)) {412return 0;413}414415String file = fix_path(p_file);416if (file.ends_with("\\") && file != "\\") {417file = file.substr(0, file.length() - 1);418}419420HANDLE 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);421422if (handle != INVALID_HANDLE_VALUE) {423FILETIME ft_create, ft_write;424425bool status = GetFileTime(handle, &ft_create, nullptr, &ft_write);426427CloseHandle(handle);428429if (status) {430uint64_t ret = 0;431432// If write time is invalid, fallback to creation time.433if (ft_write.dwHighDateTime == 0 && ft_write.dwLowDateTime == 0) {434ret = ft_create.dwHighDateTime;435ret <<= 32;436ret |= ft_create.dwLowDateTime;437} else {438ret = ft_write.dwHighDateTime;439ret <<= 32;440ret |= ft_write.dwLowDateTime;441}442443const uint64_t WINDOWS_TICKS_PER_SECOND = 10000000;444const uint64_t TICKS_TO_UNIX_EPOCH = 116444736000000000LL;445446if (ret >= TICKS_TO_UNIX_EPOCH) {447return (ret - TICKS_TO_UNIX_EPOCH) / WINDOWS_TICKS_PER_SECOND;448}449}450}451452return 0;453}454455uint64_t FileAccessWindows::_get_access_time(const String &p_file) {456if (is_path_invalid(p_file)) {457return 0;458}459460String file = fix_path(p_file);461if (file.ends_with("\\") && file != "\\") {462file = file.substr(0, file.length() - 1);463}464465HANDLE 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);466467if (handle != INVALID_HANDLE_VALUE) {468FILETIME ft_create, ft_access;469470bool status = GetFileTime(handle, &ft_create, &ft_access, nullptr);471472CloseHandle(handle);473474if (status) {475uint64_t ret = 0;476477// If access time is invalid, fallback to creation time.478if (ft_access.dwHighDateTime == 0 && ft_access.dwLowDateTime == 0) {479ret = ft_create.dwHighDateTime;480ret <<= 32;481ret |= ft_create.dwLowDateTime;482} else {483ret = ft_access.dwHighDateTime;484ret <<= 32;485ret |= ft_access.dwLowDateTime;486}487488const uint64_t WINDOWS_TICKS_PER_SECOND = 10000000;489const uint64_t TICKS_TO_UNIX_EPOCH = 116444736000000000LL;490491if (ret >= TICKS_TO_UNIX_EPOCH) {492return (ret - TICKS_TO_UNIX_EPOCH) / WINDOWS_TICKS_PER_SECOND;493}494}495}496497ERR_FAIL_V_MSG(0, "Failed to get access time for: " + p_file + "");498}499500int64_t FileAccessWindows::_get_size(const String &p_file) {501if (is_path_invalid(p_file)) {502return 0;503}504505String file = fix_path(p_file);506if (file.ends_with("\\") && file != "\\") {507file = file.substr(0, file.length() - 1);508}509510DWORD file_attr = GetFileAttributesW((LPCWSTR)(file.utf16().get_data()));511HANDLE 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);512513if (handle != INVALID_HANDLE_VALUE && !(file_attr & FILE_ATTRIBUTE_DIRECTORY)) {514LARGE_INTEGER fsize;515516bool status = GetFileSizeEx(handle, &fsize);517518CloseHandle(handle);519520if (status) {521return (int64_t)fsize.QuadPart;522}523}524ERR_FAIL_V_MSG(-1, "Failed to get size for: " + p_file + "");525}526527BitField<FileAccess::UnixPermissionFlags> FileAccessWindows::_get_unix_permissions(const String &p_file) {528return 0;529}530531Error FileAccessWindows::_set_unix_permissions(const String &p_file, BitField<FileAccess::UnixPermissionFlags> p_permissions) {532return ERR_UNAVAILABLE;533}534535bool FileAccessWindows::_get_hidden_attribute(const String &p_file) {536String file = fix_path(p_file);537538DWORD attrib = GetFileAttributesW((LPCWSTR)file.utf16().get_data());539ERR_FAIL_COND_V_MSG(attrib == INVALID_FILE_ATTRIBUTES, false, "Failed to get attributes for: " + p_file);540return (attrib & FILE_ATTRIBUTE_HIDDEN);541}542543Error FileAccessWindows::_set_hidden_attribute(const String &p_file, bool p_hidden) {544String file = fix_path(p_file);545const Char16String &file_utf16 = file.utf16();546547DWORD attrib = GetFileAttributesW((LPCWSTR)file_utf16.get_data());548ERR_FAIL_COND_V_MSG(attrib == INVALID_FILE_ATTRIBUTES, FAILED, "Failed to get attributes for: " + p_file);549BOOL ok;550if (p_hidden) {551ok = SetFileAttributesW((LPCWSTR)file_utf16.get_data(), attrib | FILE_ATTRIBUTE_HIDDEN);552} else {553ok = SetFileAttributesW((LPCWSTR)file_utf16.get_data(), attrib & ~FILE_ATTRIBUTE_HIDDEN);554}555ERR_FAIL_COND_V_MSG(!ok, FAILED, "Failed to set attributes for: " + p_file);556557return OK;558}559560bool FileAccessWindows::_get_read_only_attribute(const String &p_file) {561String file = fix_path(p_file);562563DWORD attrib = GetFileAttributesW((LPCWSTR)file.utf16().get_data());564ERR_FAIL_COND_V_MSG(attrib == INVALID_FILE_ATTRIBUTES, false, "Failed to get attributes for: " + p_file);565return (attrib & FILE_ATTRIBUTE_READONLY);566}567568Error FileAccessWindows::_set_read_only_attribute(const String &p_file, bool p_ro) {569String file = fix_path(p_file);570const Char16String &file_utf16 = file.utf16();571572DWORD attrib = GetFileAttributesW((LPCWSTR)file_utf16.get_data());573ERR_FAIL_COND_V_MSG(attrib == INVALID_FILE_ATTRIBUTES, FAILED, "Failed to get attributes for: " + p_file);574BOOL ok;575if (p_ro) {576ok = SetFileAttributesW((LPCWSTR)file_utf16.get_data(), attrib | FILE_ATTRIBUTE_READONLY);577} else {578ok = SetFileAttributesW((LPCWSTR)file_utf16.get_data(), attrib & ~FILE_ATTRIBUTE_READONLY);579}580ERR_FAIL_COND_V_MSG(!ok, FAILED, "Failed to set attributes for: " + p_file);581582return OK;583}584585void FileAccessWindows::close() {586_close();587}588589FileAccessWindows::~FileAccessWindows() {590_close();591}592593HashSet<String> FileAccessWindows::invalid_files;594595void FileAccessWindows::initialize() {596static const char *reserved_files[]{597"con", "prn", "aux", "nul", "com0", "com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8", "com9", "lpt0", "lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9", nullptr598};599int reserved_file_index = 0;600while (reserved_files[reserved_file_index] != nullptr) {601invalid_files.insert(reserved_files[reserved_file_index]);602reserved_file_index++;603}604605_setmaxstdio(8192);606print_verbose(vformat("Maximum number of file handles: %d", _getmaxstdio()));607}608609void FileAccessWindows::finalize() {610invalid_files.clear();611}612613#endif // WINDOWS_ENABLED614615616