From fc948e87f6bcbd403b2a5935ceae8b9c42751dfc Mon Sep 17 00:00:00 2001
From: bruvzg <7645683+bruvzg@users.noreply.github.com>
Date: Wed, 24 Apr 2024 10:48:47 +0300
Subject: [PATCH] Add symlink API support for Windows, expose symlink methods.
---
core/io/dir_access.cpp | 4 ++
doc/classes/DirAccess.xml | 26 +++++++++++
drivers/windows/dir_access_windows.cpp | 60 ++++++++++++++++++++++++++
drivers/windows/dir_access_windows.h | 6 +--
4 files changed, 93 insertions(+), 3 deletions(-)
diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp
index e99885befab..5df67b1103c 100644
--- a/core/io/dir_access.cpp
+++ b/core/io/dir_access.cpp
@@ -582,6 +582,10 @@ void DirAccess::_bind_methods() {
ClassDB::bind_method(D_METHOD("remove", "path"), &DirAccess::remove);
ClassDB::bind_static_method("DirAccess", D_METHOD("remove_absolute", "path"), &DirAccess::remove_absolute);
+ ClassDB::bind_method(D_METHOD("is_link", "path"), &DirAccess::is_link);
+ ClassDB::bind_method(D_METHOD("read_link", "path"), &DirAccess::read_link);
+ ClassDB::bind_method(D_METHOD("create_link", "source", "target"), &DirAccess::create_link);
+
ClassDB::bind_method(D_METHOD("set_include_navigational", "enable"), &DirAccess::set_include_navigational);
ClassDB::bind_method(D_METHOD("get_include_navigational"), &DirAccess::get_include_navigational);
ClassDB::bind_method(D_METHOD("set_include_hidden", "enable"), &DirAccess::set_include_hidden);
diff --git a/doc/classes/DirAccess.xml b/doc/classes/DirAccess.xml
index 03d7f68f439..9c71addf0c5 100644
--- a/doc/classes/DirAccess.xml
+++ b/doc/classes/DirAccess.xml
@@ -94,6 +94,16 @@
Static version of [method copy]. Supports only absolute paths.
+
+
+
+
+
+ Creates symbolic link between files or folders.
+ [b]Note:[/b] On Windows, this method works only if the application is running with elevated privileges or Developer Mode is enabled.
+ [b]Note:[/b] This method is implemented on macOS, Linux, and Windows.
+
+
@@ -212,6 +222,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 the file or directory is a symbolic link, directory junction, or other reparse point.
+ [b]Note:[/b] This method is implemented on macOS, Linux, and Windows.
+
+
@@ -264,6 +282,14 @@
Returns [code]null[/code] if opening the directory failed. You can use [method get_open_error] to check the error that occurred.
+
+
+
+
+ Returns target of the symbolic link.
+ [b]Note:[/b] This method is implemented on macOS, Linux, and Windows.
+
+
diff --git a/drivers/windows/dir_access_windows.cpp b/drivers/windows/dir_access_windows.cpp
index 43dd62cdf63..63ba6a6c968 100644
--- a/drivers/windows/dir_access_windows.cpp
+++ b/drivers/windows/dir_access_windows.cpp
@@ -396,6 +396,66 @@ bool DirAccessWindows::is_case_sensitive(const String &p_path) const {
}
}
+bool DirAccessWindows::is_link(String p_file) {
+ String f = p_file;
+
+ if (!f.is_absolute_path()) {
+ f = get_current_dir().path_join(f);
+ }
+ f = fix_path(f);
+
+ DWORD attr = GetFileAttributesW((LPCWSTR)(f.utf16().get_data()));
+ if (attr == INVALID_FILE_ATTRIBUTES) {
+ return false;
+ }
+
+ return (attr & FILE_ATTRIBUTE_REPARSE_POINT);
+}
+
+String DirAccessWindows::read_link(String p_file) {
+ String f = p_file;
+
+ if (!f.is_absolute_path()) {
+ f = get_current_dir().path_join(f);
+ }
+ f = fix_path(f);
+
+ HANDLE hfile = CreateFileW((LPCWSTR)(f.utf16().get_data()), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
+ if (hfile == INVALID_HANDLE_VALUE) {
+ return f;
+ }
+
+ DWORD ret = GetFinalPathNameByHandleW(hfile, nullptr, 0, VOLUME_NAME_DOS | FILE_NAME_NORMALIZED);
+ if (ret == 0) {
+ return f;
+ }
+ Char16String cs;
+ cs.resize(ret + 1);
+ GetFinalPathNameByHandleW(hfile, (LPWSTR)cs.ptrw(), ret, VOLUME_NAME_DOS | FILE_NAME_NORMALIZED);
+ CloseHandle(hfile);
+
+ return String::utf16((const char16_t *)cs.ptr(), ret).trim_prefix(R"(\\?\)");
+}
+
+Error DirAccessWindows::create_link(String p_source, String p_target) {
+ if (p_target.is_relative_path()) {
+ p_target = get_current_dir().path_join(p_target);
+ }
+
+ p_source = fix_path(p_source);
+ p_target = fix_path(p_target);
+
+ DWORD file_attr = GetFileAttributesW((LPCWSTR)(p_source.utf16().get_data()));
+ bool is_dir = (file_attr & FILE_ATTRIBUTE_DIRECTORY);
+
+ DWORD flags = ((is_dir) ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0) | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
+ if (CreateSymbolicLinkW((LPCWSTR)p_target.utf16().get_data(), (LPCWSTR)p_source.utf16().get_data(), flags) != 0) {
+ return OK;
+ } else {
+ return FAILED;
+ }
+}
+
DirAccessWindows::DirAccessWindows() {
p = memnew(DirAccessWindowsPrivate);
p->h = INVALID_HANDLE_VALUE;
diff --git a/drivers/windows/dir_access_windows.h b/drivers/windows/dir_access_windows.h
index 576ba18d9a1..46755cbf33c 100644
--- a/drivers/windows/dir_access_windows.h
+++ b/drivers/windows/dir_access_windows.h
@@ -77,9 +77,9 @@ public:
virtual Error rename(String p_path, String p_new_path) override;
virtual Error remove(String p_path) override;
- virtual bool is_link(String p_file) override { return false; };
- virtual String read_link(String p_file) override { return p_file; };
- virtual Error create_link(String p_source, String p_target) override { return FAILED; };
+ virtual bool is_link(String p_file) override;
+ virtual String read_link(String p_file) override;
+ virtual Error create_link(String p_source, String p_target) override;
uint64_t get_space_left() override;