Skip to content

Commit 58d31dd

Browse files
Add function OS::get_real_path()
* Added a unified `OS.get_real_path(path)` API (exposed via scripting/bindings) to return normalized “real” paths across platforms. * **Bug Fixes** * Windows: improved executable-path resolution with proper normalization, better handling when module path retrieval fails, and more reliable environment variable existence/value detection. * Unix: executable-path and platform “real” path normalization now use real-path resolution with validation and safe fallback. * **Refactor** * Introduced a shared real-path resolution interface, aligning OS implementations across platforms.
1 parent bb285fe commit 58d31dd

9 files changed

Lines changed: 81 additions & 35 deletions

File tree

core/core_bind.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,10 @@ String OS::get_executable_path() const {
360360
return ::OS::get_singleton()->get_executable_path();
361361
}
362362

363+
String OS::get_real_path(const String &p_path) const {
364+
return ::OS::get_singleton()->get_real_path(p_path);
365+
}
366+
363367
Error OS::shell_open(const String &p_uri) {
364368
if (p_uri.begins_with("res://")) {
365369
WARN_PRINT("Attempting to open an URL with the \"res://\" protocol. Use `ProjectSettings.globalize_path()` to convert a Redot-specific path to a system path before opening it with `OS.shell_open()`.");
@@ -767,6 +771,7 @@ void OS::_bind_methods() {
767771
ClassDB::bind_method(D_METHOD("get_system_font_path", "font_name", "weight", "stretch", "italic"), &OS::get_system_font_path, DEFVAL(400), DEFVAL(100), DEFVAL(false));
768772
ClassDB::bind_method(D_METHOD("get_system_font_path_for_text", "font_name", "text", "locale", "script", "weight", "stretch", "italic"), &OS::get_system_font_path_for_text, DEFVAL(String()), DEFVAL(String()), DEFVAL(400), DEFVAL(100), DEFVAL(false));
769773
ClassDB::bind_method(D_METHOD("get_executable_path"), &OS::get_executable_path);
774+
ClassDB::bind_method(D_METHOD("get_real_path", "path"), &OS::get_real_path);
770775

771776
ClassDB::bind_method(D_METHOD("read_string_from_stdin", "buffer_size"), &OS::read_string_from_stdin, DEFVAL(1024));
772777
ClassDB::bind_method(D_METHOD("read_buffer_from_stdin", "buffer_size"), &OS::read_buffer_from_stdin, DEFVAL(1024));

core/core_bind.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ class OS : public Object {
216216
String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const;
217217
Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const;
218218
String get_executable_path() const;
219+
String get_real_path(const String &p_path) const;
219220

220221
String read_string_from_stdin(int64_t p_buffer_size = 1024);
221222
PackedByteArray read_buffer_from_stdin(int64_t p_buffer_size = 1024);

core/os/os.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,10 @@ bool OS::is_delta_smoothing_enabled() const {
175175
return _delta_smoothing_enabled;
176176
}
177177

178+
String OS::get_real_path(const String &p_path) const {
179+
return p_path;
180+
}
181+
178182
String OS::get_executable_path() const {
179183
return _execpath;
180184
}

core/os/os.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ class OS {
195195
virtual Vector<String> get_system_fonts() const { return Vector<String>(); }
196196
virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return String(); }
197197
virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return Vector<String>(); }
198+
virtual String get_real_path(const String &p_path) const;
198199
virtual String get_executable_path() const;
199200
virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) = 0;
200201
virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking = true) { return Dictionary(); }

doc/classes/OS.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,15 @@
458458
[b]Note:[/b] This method is only implemented on Windows, macOS, Linux and iOS. On Android and Web, [method get_processor_name] returns an empty string.
459459
</description>
460460
</method>
461+
<method name="get_real_path" qualifiers="const">
462+
<return type="String" />
463+
<param index="0" name="path" type="String" />
464+
<description>
465+
Normalizes a given path, removing consecutive double-slash path separators, converts relative path logic to absolute, and resolves all symbolic links.
466+
[b]Note:[/b] On Windows, if a path passed to the function contains backwards slashes for any path separators, those are replaced with forward slashes.
467+
[b]Note:[/b] If the function failed for any reason, max path byte limit exceeded, the original input string with forward slash separators is returned.
468+
</description>
469+
</method>
461470
<method name="get_restart_on_exit_arguments" qualifiers="const">
462471
<return type="PackedStringArray" />
463472
<description>

drivers/unix/os_unix.cpp

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -58,20 +58,19 @@
5858
#include <sys/sysctl.h>
5959
#endif
6060

61-
#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
61+
#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) || defined(__NetBSD__)
6262
#include <sys/param.h>
6363
#include <sys/sysctl.h>
6464
#endif
6565

66-
#if defined(__FreeBSD__) || defined(__OpenBSD__)
66+
#if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__)
6767
#include <kvm.h>
6868
#endif
6969

7070
#if defined(__OpenBSD__)
7171
#include <sys/swap.h>
7272
#include <sys/types.h>
7373
#include <uvm/uvmexp.h>
74-
#include <climits>
7574
#include <sstream>
7675
#include <string>
7776
#include <vector>
@@ -89,6 +88,7 @@
8988
#include <sys/wait.h>
9089
#include <unistd.h>
9190
#include <cerrno>
91+
#include <climits>
9292
#include <csignal>
9393
#include <cstdarg>
9494
#include <cstdio>
@@ -109,7 +109,7 @@
109109
// Random location for getentropy. Fitting.
110110
#include <sys/random.h>
111111
#define UNIX_GET_ENTROPY
112-
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || (defined(__GLIBC_MINOR__) && (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 26))
112+
#elif defined(__FreeBSD__) || defined(__DragonFly__) || defined(__OpenBSD__) || (defined(__GLIBC_MINOR__) && (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 26))
113113
// In <unistd.h>.
114114
// One day... (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700)
115115
// https://publications.opengroup.org/standards/unix/c211
@@ -454,7 +454,7 @@ Dictionary OS_Unix::get_memory_info() const {
454454
if (swap_used.xsu_avail + ((vmstat.free_count - vmstat.speculative_count) + vmstat.external_page_count) * (int64_t)pagesize != 0) {
455455
meminfo["available"] = swap_used.xsu_avail + ((vmstat.free_count - vmstat.speculative_count) + vmstat.external_page_count) * (int64_t)pagesize;
456456
}
457-
#elif defined(__FreeBSD__)
457+
#elif defined(__FreeBSD__) || defined(__DragonFly__)
458458
int pagesize = 0;
459459
size_t len = sizeof(pagesize);
460460
if (sysctlbyname("vm.stats.vm.v_page_size", &pagesize, &len, nullptr, 0) < 0) {
@@ -1135,21 +1135,23 @@ String OS_Unix::get_user_data_dir(const String &p_user_dir) const {
11351135
return get_data_path().path_join(p_user_dir);
11361136
}
11371137

1138+
String OS_Unix::get_real_path(const String &p_path) const {
1139+
String result = p_path;
1140+
char resolved_path[PATH_MAX];
1141+
if (realpath(p_path.utf8().get_data(), resolved_path)) {
1142+
result = String::utf8(resolved_path);
1143+
}
1144+
return result;
1145+
}
1146+
11381147
String OS_Unix::get_executable_path() const {
11391148
#ifdef __linux__
1140-
//fix for running from a symlink
1141-
char buf[PATH_MAX];
1142-
memset(buf, 0, PATH_MAX);
1143-
ssize_t len = readlink("/proc/self/exe", buf, sizeof(buf));
1144-
String b;
1145-
if (len > 0) {
1146-
b.append_utf8(buf, len);
1147-
}
1148-
if (b.is_empty()) {
1149-
WARN_PRINT("Couldn't get executable path from /proc/self/exe, using argv[0]");
1149+
String s = get_real_path("/proc/self/exe");
1150+
if (s.is_empty() || s == "/proc/self/exe" || !FileAccess::exists(s)) {
1151+
WARN_PRINT("Couldn't get executable path from /proc/self/exe");
11501152
return OS::get_executable_path();
11511153
}
1152-
return b;
1154+
return s;
11531155
#elif defined(__OpenBSD__)
11541156
std::string path;
11551157
auto cpp_getexe = [](std::string exe) {
@@ -1318,13 +1320,8 @@ String OS_Unix::get_executable_path() const {
13181320
return OS::get_executable_path();
13191321
}
13201322

1321-
// NetBSD does not always return a normalized path. For example if argv[0] is "./a.out" then executable path is "/home/netbsd/./a.out". Normalize with realpath:
1322-
char resolved_path[MAXPATHLEN];
1323-
1324-
realpath(buf, resolved_path);
1325-
1326-
return String::utf8(resolved_path);
1327-
#elif defined(__FreeBSD__)
1323+
return get_real_path(String::utf8(buf));
1324+
#elif defined(__FreeBSD__) || defined(__DragonFly__)
13281325
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
13291326
char buf[MAXPATHLEN];
13301327
size_t len = sizeof(buf);
@@ -1333,7 +1330,7 @@ String OS_Unix::get_executable_path() const {
13331330
return OS::get_executable_path();
13341331
}
13351332

1336-
return String::utf8(buf);
1333+
return get_real_path(String::utf8(buf));
13371334
#elif defined(__APPLE__)
13381335
char temp_path[1];
13391336
uint32_t buff_size = 1;
@@ -1348,7 +1345,7 @@ String OS_Unix::get_executable_path() const {
13481345
String path = String::utf8(resolved_path);
13491346
delete[] resolved_path;
13501347

1351-
return path;
1348+
return get_real_path(path);
13521349
#else
13531350
ERR_PRINT("Warning, don't know how to obtain executable path on this OS! Please override this function properly.");
13541351
return OS::get_executable_path();

drivers/unix/os_unix.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ class OS_Unix : public OS {
143143

144144
virtual void initialize_debugging() override;
145145

146+
virtual String get_real_path(const String &p_path) const override;
146147
virtual String get_executable_path() const override;
147148
virtual String get_user_data_dir(const String &p_user_dir) const override;
148149

platform/windows/os_windows.cpp

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2051,24 +2051,51 @@ String OS_Windows::get_system_font_path(const String &p_font_name, int p_weight,
20512051
return String();
20522052
}
20532053

2054+
String OS_Windows::get_real_path(const String &p_path) const {
2055+
WCHAR buf[4096];
2056+
String res = p_path;
2057+
HANDLE handle = CreateFileW((const WCHAR *)res.utf16().get_data(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, nullptr);
2058+
if (handle != INVALID_HANDLE_VALUE) {
2059+
DWORD len = GetFinalPathNameByHandleW(handle, buf, 4096, FILE_NAME_NORMALIZED | VOLUME_NAME_DOS);
2060+
if (len && len < 4096) {
2061+
res = String::utf16((const char16_t *)buf);
2062+
if (res.begins_with("\\\\?\\UNC\\")) {
2063+
res = "\\" + res.substr(7);
2064+
} else if (res.begins_with("\\\\?\\")) {
2065+
res = res.substr(4);
2066+
}
2067+
}
2068+
CloseHandle(handle);
2069+
}
2070+
return res.replace_char('\\', '/');
2071+
}
2072+
20542073
String OS_Windows::get_executable_path() const {
2055-
WCHAR bufname[4096];
2056-
GetModuleFileNameW(nullptr, bufname, 4096);
2057-
String s = String::utf16((const char16_t *)bufname).replace_char('\\', '/');
2058-
return s;
2074+
WCHAR buf[4096];
2075+
if (!GetModuleFileNameW(nullptr, buf, 4096)) {
2076+
WARN_PRINT("Couldn't get executable path from GetModuleFileName");
2077+
return OS::get_executable_path().replace_char('\\', '/');
2078+
}
2079+
return get_real_path(String::utf16((const char16_t *)buf));
20592080
}
20602081

20612082
bool OS_Windows::has_environment(const String &p_var) const {
2062-
return GetEnvironmentVariableW((LPCWSTR)(p_var.utf16().get_data()), nullptr, 0) > 0;
2083+
// An environment variable can still exist in the environment block, but just have an empty value; check for ERROR_ENVVAR_NOT_FOUND:
2084+
return (!(GetEnvironmentVariableW((LPCWSTR)(p_var.utf16().get_data()), nullptr, 0) == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND));
20632085
}
20642086

20652087
String OS_Windows::get_environment(const String &p_var) const {
2066-
WCHAR wval[0x7fff]; // MSDN says 32767 char is the maximum
2067-
int wlen = GetEnvironmentVariableW((LPCWSTR)(p_var.utf16().get_data()), wval, 0x7fff);
2068-
if (wlen > 0) {
2069-
return String::utf16((const char16_t *)wval);
2088+
String res;
2089+
DWORD len = GetEnvironmentVariableW((LPCWSTR)(p_var.utf16().get_data()), nullptr, 0);
2090+
if (len) {
2091+
WCHAR *val = new WCHAR[len]();
2092+
len = GetEnvironmentVariableW((LPCWSTR)(p_var.utf16().get_data()), val, len);
2093+
if (len) {
2094+
res = String::utf16((const char16_t *)val);
2095+
}
2096+
delete[] val;
20702097
}
2071-
return "";
2098+
return res;
20722099
}
20732100

20742101
void OS_Windows::set_environment(const String &p_var, const String &p_value) const {

platform/windows/os_windows.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ class OS_Windows : public OS {
227227
virtual String get_system_font_path(const String &p_font_name, int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override;
228228
virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const override;
229229

230+
virtual String get_real_path(const String &p_path) const override;
230231
virtual String get_executable_path() const override;
231232

232233
virtual String get_locale() const override;

0 commit comments

Comments
 (0)