diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp index c90e376e262..37a377e0f03 100644 --- a/core/io/dir_access.cpp +++ b/core/io/dir_access.cpp @@ -626,6 +626,10 @@ bool DirAccess::is_case_sensitive(const String &p_path) const { return true; } +bool DirAccess::is_equivalent(const String &p_path_a, const String &p_path_b) const { + return p_path_a == p_path_b; +} + void DirAccess::_bind_methods() { ClassDB::bind_static_method("DirAccess", D_METHOD("open", "path"), &DirAccess::_open); ClassDB::bind_static_method("DirAccess", D_METHOD("get_open_error"), &DirAccess::get_open_error); @@ -671,6 +675,7 @@ void DirAccess::_bind_methods() { ClassDB::bind_method(D_METHOD("get_include_hidden"), &DirAccess::get_include_hidden); ClassDB::bind_method(D_METHOD("is_case_sensitive", "path"), &DirAccess::is_case_sensitive); + ClassDB::bind_method(D_METHOD("is_equivalent", "path_a", "path_b"), &DirAccess::is_equivalent); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "include_navigational"), "set_include_navigational", "get_include_navigational"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "include_hidden"), "set_include_hidden", "get_include_hidden"); diff --git a/core/io/dir_access.h b/core/io/dir_access.h index f9e1ee10346..511a414f1c6 100644 --- a/core/io/dir_access.h +++ b/core/io/dir_access.h @@ -168,6 +168,7 @@ public: virtual bool is_case_sensitive(const String &p_path) const; virtual bool is_bundle(const String &p_file) const { return false; } + virtual bool is_equivalent(const String &p_path_a, const String &p_path_b) const; public: DirAccess() {} diff --git a/doc/classes/DirAccess.xml b/doc/classes/DirAccess.xml index cee4d1fe7ac..beb57aef2c3 100644 --- a/doc/classes/DirAccess.xml +++ b/doc/classes/DirAccess.xml @@ -248,6 +248,14 @@ [b]Note:[/b] This method is implemented on macOS, Linux (for EXT4 and F2FS filesystems only) and Windows. On other platforms, it always returns [code]true[/code]. + + + + + + Returns [code]true[/code] if paths [param path_a] and [param path_b] resolve to the same file system object. Returns [code]false[/code] otherwise, even if the files are bit-for-bit identical (e.g., identical copies of the file that are not symbolic links). + + diff --git a/drivers/unix/dir_access_unix.cpp b/drivers/unix/dir_access_unix.cpp index 223dee38afc..1b3757e0b00 100644 --- a/drivers/unix/dir_access_unix.cpp +++ b/drivers/unix/dir_access_unix.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #ifdef HAVE_MNTENT @@ -544,6 +545,24 @@ bool DirAccessUnix::is_case_sensitive(const String &p_path) const { return true; } +bool DirAccessUnix::is_equivalent(const String &p_path_a, const String &p_path_b) const { + String f1 = fix_path(p_path_a); + struct stat st1 = {}; + int err = stat(f1.utf8().get_data(), &st1); + if (err) { + return DirAccess::is_equivalent(p_path_a, p_path_b); + } + + String f2 = fix_path(p_path_b); + struct stat st2 = {}; + err = stat(f2.utf8().get_data(), &st2); + if (err) { + return DirAccess::is_equivalent(p_path_a, p_path_b); + } + + return (st1.st_dev == st2.st_dev) && (st1.st_ino == st2.st_ino); +} + DirAccessUnix::DirAccessUnix() { dir_stream = nullptr; _cisdir = false; diff --git a/drivers/unix/dir_access_unix.h b/drivers/unix/dir_access_unix.h index a6493d0323b..bfb8600ad55 100644 --- a/drivers/unix/dir_access_unix.h +++ b/drivers/unix/dir_access_unix.h @@ -85,6 +85,7 @@ public: virtual Error create_link(String p_source, String p_target) override; virtual bool is_case_sensitive(const String &p_path) const override; + virtual bool is_equivalent(const String &p_path_a, const String &p_path_b) const override; virtual uint64_t get_space_left() override; diff --git a/drivers/windows/dir_access_windows.cpp b/drivers/windows/dir_access_windows.cpp index 2817acd3353..0bf89ebcd76 100644 --- a/drivers/windows/dir_access_windows.cpp +++ b/drivers/windows/dir_access_windows.cpp @@ -391,6 +391,38 @@ bool DirAccessWindows::is_case_sensitive(const String &p_path) const { } } +typedef struct { + ULONGLONG LowPart; + ULONGLONG HighPart; +} GD_FILE_ID_128; + +typedef struct { + ULONGLONG VolumeSerialNumber; + GD_FILE_ID_128 FileId; +} GD_FILE_ID_INFO; + +bool DirAccessWindows::is_equivalent(const String &p_path_a, const String &p_path_b) const { + String f1 = fix_path(p_path_a); + GD_FILE_ID_INFO st1; + 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); + if (h1 == INVALID_HANDLE_VALUE) { + return DirAccess::is_equivalent(p_path_a, p_path_b); + } + ::GetFileInformationByHandleEx(h1, (FILE_INFO_BY_HANDLE_CLASS)0x12 /*FileIdInfo*/, &st1, sizeof(st1)); + ::CloseHandle(h1); + + String f2 = fix_path(p_path_b); + GD_FILE_ID_INFO st2; + 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); + if (h2 == INVALID_HANDLE_VALUE) { + return DirAccess::is_equivalent(p_path_a, p_path_b); + } + ::GetFileInformationByHandleEx(h2, (FILE_INFO_BY_HANDLE_CLASS)0x12 /*FileIdInfo*/, &st2, sizeof(st2)); + ::CloseHandle(h2); + + return (st1.VolumeSerialNumber == st2.VolumeSerialNumber) && (st1.FileId.LowPart == st2.FileId.LowPart) && (st1.FileId.HighPart == st2.FileId.HighPart); +} + bool DirAccessWindows::is_link(String p_file) { String f = fix_path(p_file); diff --git a/drivers/windows/dir_access_windows.h b/drivers/windows/dir_access_windows.h index 353e27833aa..a7e05f7662a 100644 --- a/drivers/windows/dir_access_windows.h +++ b/drivers/windows/dir_access_windows.h @@ -84,6 +84,7 @@ public: virtual String get_filesystem_type() const override; virtual bool is_case_sensitive(const String &p_path) const override; + virtual bool is_equivalent(const String &p_path_a, const String &p_path_b) const override; DirAccessWindows(); ~DirAccessWindows();