1/**************************************************************************/
2/* os_unix.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "os_unix.h"
32
33#ifdef UNIX_ENABLED
34
35#include "core/config/project_settings.h"
36#include "core/debugger/engine_debugger.h"
37#include "core/debugger/script_debugger.h"
38#include "drivers/unix/dir_access_unix.h"
39#include "drivers/unix/file_access_unix.h"
40#include "drivers/unix/net_socket_posix.h"
41#include "drivers/unix/thread_posix.h"
42#include "servers/rendering_server.h"
43
44#if defined(__APPLE__)
45#include <mach-o/dyld.h>
46#include <mach/host_info.h>
47#include <mach/mach_host.h>
48#include <mach/mach_time.h>
49#include <sys/sysctl.h>
50#endif
51
52#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
53#include <sys/param.h>
54#include <sys/sysctl.h>
55#endif
56
57#if defined(__FreeBSD__)
58#include <kvm.h>
59#endif
60
61#if defined(__OpenBSD__)
62#include <sys/swap.h>
63#include <uvm/uvmexp.h>
64#endif
65
66#if defined(__NetBSD__)
67#include <uvm/uvm_extern.h>
68#endif
69
70#include <dlfcn.h>
71#include <errno.h>
72#include <poll.h>
73#include <signal.h>
74#include <stdarg.h>
75#include <stdio.h>
76#include <stdlib.h>
77#include <string.h>
78#include <sys/resource.h>
79#include <sys/time.h>
80#include <sys/wait.h>
81#include <time.h>
82#include <unistd.h>
83
84#if defined(MACOS_ENABLED) || (defined(__ANDROID_API__) && __ANDROID_API__ >= 28)
85// Random location for getentropy. Fitting.
86#include <sys/random.h>
87#define UNIX_GET_ENTROPY
88#elif defined(__FreeBSD__) || defined(__OpenBSD__) || (defined(__GLIBC_MINOR__) && (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 26))
89// In <unistd.h>.
90// One day... (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700)
91// https://publications.opengroup.org/standards/unix/c211
92#define UNIX_GET_ENTROPY
93#endif
94
95#if !defined(UNIX_GET_ENTROPY) && !defined(NO_URANDOM)
96#include <fcntl.h>
97#endif
98
99/// Clock Setup function (used by get_ticks_usec)
100static uint64_t _clock_start = 0;
101#if defined(__APPLE__)
102static double _clock_scale = 0;
103static void _setup_clock() {
104 mach_timebase_info_data_t info;
105 kern_return_t ret = mach_timebase_info(&info);
106 ERR_FAIL_COND_MSG(ret != 0, "OS CLOCK IS NOT WORKING!");
107 _clock_scale = ((double)info.numer / (double)info.denom) / 1000.0;
108 _clock_start = mach_absolute_time() * _clock_scale;
109}
110#else
111#if defined(CLOCK_MONOTONIC_RAW) && !defined(WEB_ENABLED) // This is a better clock on Linux.
112#define GODOT_CLOCK CLOCK_MONOTONIC_RAW
113#else
114#define GODOT_CLOCK CLOCK_MONOTONIC
115#endif
116static void _setup_clock() {
117 struct timespec tv_now = { 0, 0 };
118 ERR_FAIL_COND_MSG(clock_gettime(GODOT_CLOCK, &tv_now) != 0, "OS CLOCK IS NOT WORKING!");
119 _clock_start = ((uint64_t)tv_now.tv_nsec / 1000L) + (uint64_t)tv_now.tv_sec * 1000000L;
120}
121#endif
122
123static void handle_interrupt(int sig) {
124 if (!EngineDebugger::is_active()) {
125 return;
126 }
127
128 EngineDebugger::get_script_debugger()->set_depth(-1);
129 EngineDebugger::get_script_debugger()->set_lines_left(1);
130}
131
132void OS_Unix::initialize_debugging() {
133 if (EngineDebugger::is_active()) {
134 struct sigaction action;
135 memset(&action, 0, sizeof(action));
136 action.sa_handler = handle_interrupt;
137 sigaction(SIGINT, &action, nullptr);
138 }
139}
140
141int OS_Unix::unix_initialize_audio(int p_audio_driver) {
142 return 0;
143}
144
145void OS_Unix::initialize_core() {
146 init_thread_posix();
147
148 FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES);
149 FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_USERDATA);
150 FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_FILESYSTEM);
151 DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_RESOURCES);
152 DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_USERDATA);
153 DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_FILESYSTEM);
154
155 NetSocketPosix::make_default();
156 IPUnix::make_default();
157
158 _setup_clock();
159}
160
161void OS_Unix::finalize_core() {
162 NetSocketPosix::cleanup();
163}
164
165Vector<String> OS_Unix::get_video_adapter_driver_info() const {
166 return Vector<String>();
167}
168
169String OS_Unix::get_stdin_string() {
170 char buff[1024];
171 return String::utf8(fgets(buff, 1024, stdin));
172}
173
174Error OS_Unix::get_entropy(uint8_t *r_buffer, int p_bytes) {
175#if defined(UNIX_GET_ENTROPY)
176 int left = p_bytes;
177 int ofs = 0;
178 do {
179 int chunk = MIN(left, 256);
180 ERR_FAIL_COND_V(getentropy(r_buffer + ofs, chunk), FAILED);
181 left -= chunk;
182 ofs += chunk;
183 } while (left > 0);
184// Define this yourself if you don't want to fall back to /dev/urandom.
185#elif !defined(NO_URANDOM)
186 int r = open("/dev/urandom", O_RDONLY);
187 ERR_FAIL_COND_V(r < 0, FAILED);
188 int left = p_bytes;
189 do {
190 ssize_t ret = read(r, r_buffer, p_bytes);
191 ERR_FAIL_COND_V(ret <= 0, FAILED);
192 left -= ret;
193 } while (left > 0);
194#else
195 return ERR_UNAVAILABLE;
196#endif
197 return OK;
198}
199
200String OS_Unix::get_name() const {
201 return "Unix";
202}
203
204String OS_Unix::get_distribution_name() const {
205 return "";
206}
207
208String OS_Unix::get_version() const {
209 return "";
210}
211
212double OS_Unix::get_unix_time() const {
213 struct timeval tv_now;
214 gettimeofday(&tv_now, nullptr);
215 return (double)tv_now.tv_sec + double(tv_now.tv_usec) / 1000000;
216}
217
218OS::DateTime OS_Unix::get_datetime(bool p_utc) const {
219 time_t t = time(nullptr);
220 struct tm lt;
221 if (p_utc) {
222 gmtime_r(&t, &lt);
223 } else {
224 localtime_r(&t, &lt);
225 }
226 DateTime ret;
227 ret.year = 1900 + lt.tm_year;
228 // Index starting at 1 to match OS_Unix::get_date
229 // and Windows SYSTEMTIME and tm_mon follows the typical structure
230 // of 0-11, noted here: http://www.cplusplus.com/reference/ctime/tm/
231 ret.month = (Month)(lt.tm_mon + 1);
232 ret.day = lt.tm_mday;
233 ret.weekday = (Weekday)lt.tm_wday;
234 ret.hour = lt.tm_hour;
235 ret.minute = lt.tm_min;
236 ret.second = lt.tm_sec;
237 ret.dst = lt.tm_isdst;
238
239 return ret;
240}
241
242OS::TimeZoneInfo OS_Unix::get_time_zone_info() const {
243 time_t t = time(nullptr);
244 struct tm lt;
245 localtime_r(&t, &lt);
246 char name[16];
247 strftime(name, 16, "%Z", &lt);
248 name[15] = 0;
249 TimeZoneInfo ret;
250 ret.name = name;
251
252 char bias_buf[16];
253 strftime(bias_buf, 16, "%z", &lt);
254 int bias;
255 bias_buf[15] = 0;
256 sscanf(bias_buf, "%d", &bias);
257
258 // convert from ISO 8601 (1 minute=1, 1 hour=100) to minutes
259 int hour = (int)bias / 100;
260 int minutes = bias % 100;
261 if (bias < 0) {
262 ret.bias = hour * 60 - minutes;
263 } else {
264 ret.bias = hour * 60 + minutes;
265 }
266
267 return ret;
268}
269
270void OS_Unix::delay_usec(uint32_t p_usec) const {
271 struct timespec requested = { static_cast<time_t>(p_usec / 1000000), (static_cast<long>(p_usec) % 1000000) * 1000 };
272 struct timespec remaining;
273 while (nanosleep(&requested, &remaining) == -1 && errno == EINTR) {
274 requested.tv_sec = remaining.tv_sec;
275 requested.tv_nsec = remaining.tv_nsec;
276 }
277}
278
279uint64_t OS_Unix::get_ticks_usec() const {
280#if defined(__APPLE__)
281 uint64_t longtime = mach_absolute_time() * _clock_scale;
282#else
283 // Unchecked return. Static analyzers might complain.
284 // If _setup_clock() succeeded, we assume clock_gettime() works.
285 struct timespec tv_now = { 0, 0 };
286 clock_gettime(GODOT_CLOCK, &tv_now);
287 uint64_t longtime = ((uint64_t)tv_now.tv_nsec / 1000L) + (uint64_t)tv_now.tv_sec * 1000000L;
288#endif
289 longtime -= _clock_start;
290
291 return longtime;
292}
293
294Dictionary OS_Unix::get_memory_info() const {
295 Dictionary meminfo;
296
297 meminfo["physical"] = -1;
298 meminfo["free"] = -1;
299 meminfo["available"] = -1;
300 meminfo["stack"] = -1;
301
302#if defined(__APPLE__)
303 int pagesize = 0;
304 size_t len = sizeof(pagesize);
305 if (sysctlbyname("vm.pagesize", &pagesize, &len, nullptr, 0) < 0) {
306 ERR_PRINT(vformat("Could not get vm.pagesize, error code: %d - %s", errno, strerror(errno)));
307 }
308
309 int64_t phy_mem = 0;
310 len = sizeof(phy_mem);
311 if (sysctlbyname("hw.memsize", &phy_mem, &len, nullptr, 0) < 0) {
312 ERR_PRINT(vformat("Could not get hw.memsize, error code: %d - %s", errno, strerror(errno)));
313 }
314
315 mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
316 vm_statistics64_data_t vmstat;
317 if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&vmstat, &count) != KERN_SUCCESS) {
318 ERR_PRINT("Could not get host vm statistics.");
319 }
320 struct xsw_usage swap_used;
321 len = sizeof(swap_used);
322 if (sysctlbyname("vm.swapusage", &swap_used, &len, nullptr, 0) < 0) {
323 ERR_PRINT(vformat("Could not get vm.swapusage, error code: %d - %s", errno, strerror(errno)));
324 }
325
326 if (phy_mem != 0) {
327 meminfo["physical"] = phy_mem;
328 }
329 if (vmstat.free_count * (int64_t)pagesize != 0) {
330 meminfo["free"] = vmstat.free_count * (int64_t)pagesize;
331 }
332 if (swap_used.xsu_avail + vmstat.free_count * (int64_t)pagesize != 0) {
333 meminfo["available"] = swap_used.xsu_avail + vmstat.free_count * (int64_t)pagesize;
334 }
335#elif defined(__FreeBSD__)
336 int pagesize = 0;
337 size_t len = sizeof(pagesize);
338 if (sysctlbyname("vm.stats.vm.v_page_size", &pagesize, &len, nullptr, 0) < 0) {
339 ERR_PRINT(vformat("Could not get vm.stats.vm.v_page_size, error code: %d - %s", errno, strerror(errno)));
340 }
341
342 uint64_t mtotal = 0;
343 len = sizeof(mtotal);
344 if (sysctlbyname("vm.stats.vm.v_page_count", &mtotal, &len, nullptr, 0) < 0) {
345 ERR_PRINT(vformat("Could not get vm.stats.vm.v_page_count, error code: %d - %s", errno, strerror(errno)));
346 }
347 uint64_t mfree = 0;
348 len = sizeof(mfree);
349 if (sysctlbyname("vm.stats.vm.v_free_count", &mfree, &len, nullptr, 0) < 0) {
350 ERR_PRINT(vformat("Could not get vm.stats.vm.v_free_count, error code: %d - %s", errno, strerror(errno)));
351 }
352
353 uint64_t stotal = 0;
354 uint64_t sused = 0;
355 char errmsg[_POSIX2_LINE_MAX] = {};
356 kvm_t *kd = kvm_openfiles(nullptr, "/dev/null", nullptr, 0, errmsg);
357 if (kd == nullptr) {
358 ERR_PRINT(vformat("kvm_openfiles failed, error: %s", errmsg));
359 } else {
360 struct kvm_swap swap_info[32];
361 int count = kvm_getswapinfo(kd, swap_info, 32, 0);
362 for (int i = 0; i < count; i++) {
363 stotal += swap_info[i].ksw_total;
364 sused += swap_info[i].ksw_used;
365 }
366 kvm_close(kd);
367 }
368
369 if (mtotal * pagesize != 0) {
370 meminfo["physical"] = mtotal * pagesize;
371 }
372 if (mfree * pagesize != 0) {
373 meminfo["free"] = mfree * pagesize;
374 }
375 if ((mfree + stotal - sused) * pagesize != 0) {
376 meminfo["available"] = (mfree + stotal - sused) * pagesize;
377 }
378#elif defined(__OpenBSD__)
379 int pagesize = sysconf(_SC_PAGESIZE);
380
381 const int mib[] = { CTL_VM, VM_UVMEXP };
382 uvmexp uvmexp_info;
383 size_t len = sizeof(uvmexp_info);
384 if (sysctl(mib, 2, &uvmexp_info, &len, nullptr, 0) < 0) {
385 ERR_PRINT(vformat("Could not get CTL_VM, VM_UVMEXP, error code: %d - %s", errno, strerror(errno)));
386 }
387
388 uint64_t stotal = 0;
389 uint64_t sused = 0;
390 int count = swapctl(SWAP_NSWAP, 0, 0);
391 if (count > 0) {
392 swapent swap_info[count];
393 count = swapctl(SWAP_STATS, swap_info, count);
394
395 for (int i = 0; i < count; i++) {
396 if (swap_info[i].se_flags & SWF_ENABLE) {
397 sused += swap_info[i].se_inuse;
398 stotal += swap_info[i].se_nblks;
399 }
400 }
401 }
402
403 if (uvmexp_info.npages * pagesize != 0) {
404 meminfo["physical"] = uvmexp_info.npages * pagesize;
405 }
406 if (uvmexp_info.free * pagesize != 0) {
407 meminfo["free"] = uvmexp_info.free * pagesize;
408 }
409 if ((uvmexp_info.free * pagesize) + (stotal - sused) * DEV_BSIZE != 0) {
410 meminfo["available"] = (uvmexp_info.free * pagesize) + (stotal - sused) * DEV_BSIZE;
411 }
412#elif defined(__NetBSD__)
413 int pagesize = sysconf(_SC_PAGESIZE);
414
415 const int mib[] = { CTL_VM, VM_UVMEXP2 };
416 uvmexp_sysctl uvmexp_info;
417 size_t len = sizeof(uvmexp_info);
418 if (sysctl(mib, 2, &uvmexp_info, &len, nullptr, 0) < 0) {
419 ERR_PRINT(vformat("Could not get CTL_VM, VM_UVMEXP2, error code: %d - %s", errno, strerror(errno)));
420 }
421
422 if (uvmexp_info.npages * pagesize != 0) {
423 meminfo["physical"] = uvmexp_info.npages * pagesize;
424 }
425 if (uvmexp_info.free * pagesize != 0) {
426 meminfo["free"] = uvmexp_info.free * pagesize;
427 }
428 if ((uvmexp_info.free + uvmexp_info.swpages - uvmexp_info.swpginuse) * pagesize != 0) {
429 meminfo["available"] = (uvmexp_info.free + uvmexp_info.swpages - uvmexp_info.swpginuse) * pagesize;
430 }
431#else
432 Error err;
433 Ref<FileAccess> f = FileAccess::open("/proc/meminfo", FileAccess::READ, &err);
434 uint64_t mtotal = 0;
435 uint64_t mfree = 0;
436 uint64_t sfree = 0;
437 while (f.is_valid() && !f->eof_reached()) {
438 String s = f->get_line().strip_edges();
439 if (s.begins_with("MemTotal:")) {
440 Vector<String> stok = s.replace("MemTotal:", "").strip_edges().split(" ");
441 if (stok.size() == 2) {
442 mtotal = stok[0].to_int() * 1024;
443 }
444 }
445 if (s.begins_with("MemFree:")) {
446 Vector<String> stok = s.replace("MemFree:", "").strip_edges().split(" ");
447 if (stok.size() == 2) {
448 mfree = stok[0].to_int() * 1024;
449 }
450 }
451 if (s.begins_with("SwapFree:")) {
452 Vector<String> stok = s.replace("SwapFree:", "").strip_edges().split(" ");
453 if (stok.size() == 2) {
454 sfree = stok[0].to_int() * 1024;
455 }
456 }
457 }
458
459 if (mtotal != 0) {
460 meminfo["physical"] = mtotal;
461 }
462 if (mfree != 0) {
463 meminfo["free"] = mfree;
464 }
465 if (mfree + sfree != 0) {
466 meminfo["available"] = mfree + sfree;
467 }
468#endif
469
470 rlimit stackinfo = {};
471 getrlimit(RLIMIT_STACK, &stackinfo);
472
473 if (stackinfo.rlim_cur != 0) {
474 meminfo["stack"] = (int64_t)stackinfo.rlim_cur;
475 }
476
477 return meminfo;
478}
479
480Error OS_Unix::execute(const String &p_path, const List<String> &p_arguments, String *r_pipe, int *r_exitcode, bool read_stderr, Mutex *p_pipe_mutex, bool p_open_console) {
481#ifdef __EMSCRIPTEN__
482 // Don't compile this code at all to avoid undefined references.
483 // Actual virtual call goes to OS_Web.
484 ERR_FAIL_V(ERR_BUG);
485#else
486 if (r_pipe) {
487 String command = "\"" + p_path + "\"";
488 for (int i = 0; i < p_arguments.size(); i++) {
489 command += String(" \"") + p_arguments[i] + "\"";
490 }
491 if (read_stderr) {
492 command += " 2>&1"; // Include stderr
493 } else {
494 command += " 2>/dev/null"; // Silence stderr
495 }
496
497 FILE *f = popen(command.utf8().get_data(), "r");
498 ERR_FAIL_NULL_V_MSG(f, ERR_CANT_OPEN, "Cannot create pipe from command: " + command + ".");
499 char buf[65535];
500 while (fgets(buf, 65535, f)) {
501 if (p_pipe_mutex) {
502 p_pipe_mutex->lock();
503 }
504 String pipe_out;
505 if (pipe_out.parse_utf8(buf) == OK) {
506 (*r_pipe) += pipe_out;
507 } else {
508 (*r_pipe) += String(buf); // If not valid UTF-8 try decode as Latin-1
509 }
510 if (p_pipe_mutex) {
511 p_pipe_mutex->unlock();
512 }
513 }
514 int rv = pclose(f);
515
516 if (r_exitcode) {
517 *r_exitcode = WEXITSTATUS(rv);
518 }
519 return OK;
520 }
521
522 pid_t pid = fork();
523 ERR_FAIL_COND_V(pid < 0, ERR_CANT_FORK);
524
525 if (pid == 0) {
526 // The child process
527 Vector<CharString> cs;
528 cs.push_back(p_path.utf8());
529 for (int i = 0; i < p_arguments.size(); i++) {
530 cs.push_back(p_arguments[i].utf8());
531 }
532
533 Vector<char *> args;
534 for (int i = 0; i < cs.size(); i++) {
535 args.push_back((char *)cs[i].get_data());
536 }
537 args.push_back(0);
538
539 execvp(p_path.utf8().get_data(), &args[0]);
540 // The execvp() function only returns if an error occurs.
541 ERR_PRINT("Could not create child process: " + p_path);
542 raise(SIGKILL);
543 }
544
545 int status;
546 waitpid(pid, &status, 0);
547 if (r_exitcode) {
548 *r_exitcode = WIFEXITED(status) ? WEXITSTATUS(status) : status;
549 }
550 return OK;
551#endif
552}
553
554Error OS_Unix::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) {
555#ifdef __EMSCRIPTEN__
556 // Don't compile this code at all to avoid undefined references.
557 // Actual virtual call goes to OS_Web.
558 ERR_FAIL_V(ERR_BUG);
559#else
560 pid_t pid = fork();
561 ERR_FAIL_COND_V(pid < 0, ERR_CANT_FORK);
562
563 if (pid == 0) {
564 // The new process
565 // Create a new session-ID so parent won't wait for it.
566 // This ensures the process won't go zombie at the end.
567 setsid();
568
569 Vector<CharString> cs;
570 cs.push_back(p_path.utf8());
571 for (int i = 0; i < p_arguments.size(); i++) {
572 cs.push_back(p_arguments[i].utf8());
573 }
574
575 Vector<char *> args;
576 for (int i = 0; i < cs.size(); i++) {
577 args.push_back((char *)cs[i].get_data());
578 }
579 args.push_back(0);
580
581 execvp(p_path.utf8().get_data(), &args[0]);
582 // The execvp() function only returns if an error occurs.
583 ERR_PRINT("Could not create child process: " + p_path);
584 raise(SIGKILL);
585 }
586
587 if (r_child_id) {
588 *r_child_id = pid;
589 }
590 return OK;
591#endif
592}
593
594Error OS_Unix::kill(const ProcessID &p_pid) {
595 int ret = ::kill(p_pid, SIGKILL);
596 if (!ret) {
597 //avoid zombie process
598 int st;
599 ::waitpid(p_pid, &st, 0);
600 }
601 return ret ? ERR_INVALID_PARAMETER : OK;
602}
603
604int OS_Unix::get_process_id() const {
605 return getpid();
606}
607
608bool OS_Unix::is_process_running(const ProcessID &p_pid) const {
609 int status = 0;
610 if (waitpid(p_pid, &status, WNOHANG) != 0) {
611 return false;
612 }
613
614 return true;
615}
616
617String OS_Unix::get_locale() const {
618 if (!has_environment("LANG")) {
619 return "en";
620 }
621
622 String locale = get_environment("LANG");
623 int tp = locale.find(".");
624 if (tp != -1) {
625 locale = locale.substr(0, tp);
626 }
627 return locale;
628}
629
630Error OS_Unix::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path, String *r_resolved_path) {
631 String path = p_path;
632
633 if (FileAccess::exists(path) && path.is_relative_path()) {
634 // dlopen expects a slash, in this case a leading ./ for it to be interpreted as a relative path,
635 // otherwise it will end up searching various system directories for the lib instead and finally failing.
636 path = "./" + path;
637 }
638
639 if (!FileAccess::exists(path)) {
640 // This code exists so GDExtension can load .so files from within the executable path.
641 path = get_executable_path().get_base_dir().path_join(p_path.get_file());
642 }
643
644 if (!FileAccess::exists(path)) {
645 // This code exists so GDExtension can load .so files from a standard unix location.
646 path = get_executable_path().get_base_dir().path_join("../lib").path_join(p_path.get_file());
647 }
648
649 p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
650 ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
651
652 if (r_resolved_path != nullptr) {
653 *r_resolved_path = path;
654 }
655
656 return OK;
657}
658
659Error OS_Unix::close_dynamic_library(void *p_library_handle) {
660 if (dlclose(p_library_handle)) {
661 return FAILED;
662 }
663 return OK;
664}
665
666Error OS_Unix::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) {
667 const char *error;
668 dlerror(); // Clear existing errors
669
670 p_symbol_handle = dlsym(p_library_handle, p_name.utf8().get_data());
671
672 error = dlerror();
673 if (error != nullptr) {
674 ERR_FAIL_COND_V_MSG(!p_optional, ERR_CANT_RESOLVE, "Can't resolve symbol " + p_name + ". Error: " + error + ".");
675
676 return ERR_CANT_RESOLVE;
677 }
678 return OK;
679}
680
681Error OS_Unix::set_cwd(const String &p_cwd) {
682 if (chdir(p_cwd.utf8().get_data()) != 0) {
683 return ERR_CANT_OPEN;
684 }
685
686 return OK;
687}
688
689bool OS_Unix::has_environment(const String &p_var) const {
690 return getenv(p_var.utf8().get_data()) != nullptr;
691}
692
693String OS_Unix::get_environment(const String &p_var) const {
694 if (getenv(p_var.utf8().get_data())) {
695 return getenv(p_var.utf8().get_data());
696 }
697 return "";
698}
699
700void OS_Unix::set_environment(const String &p_var, const String &p_value) const {
701 ERR_FAIL_COND_MSG(p_var.is_empty() || p_var.contains("="), vformat("Invalid environment variable name '%s', cannot be empty or include '='.", p_var));
702 int err = setenv(p_var.utf8().get_data(), p_value.utf8().get_data(), /* overwrite: */ 1);
703 ERR_FAIL_COND_MSG(err != 0, vformat("Failed setting environment variable '%s', the system is out of memory.", p_var));
704}
705
706void OS_Unix::unset_environment(const String &p_var) const {
707 ERR_FAIL_COND_MSG(p_var.is_empty() || p_var.contains("="), vformat("Invalid environment variable name '%s', cannot be empty or include '='.", p_var));
708 unsetenv(p_var.utf8().get_data());
709}
710
711String OS_Unix::get_user_data_dir() const {
712 String appname = get_safe_dir_name(GLOBAL_GET("application/config/name"));
713 if (!appname.is_empty()) {
714 bool use_custom_dir = GLOBAL_GET("application/config/use_custom_user_dir");
715 if (use_custom_dir) {
716 String custom_dir = get_safe_dir_name(GLOBAL_GET("application/config/custom_user_dir_name"), true);
717 if (custom_dir.is_empty()) {
718 custom_dir = appname;
719 }
720 return get_data_path().path_join(custom_dir);
721 } else {
722 return get_data_path().path_join(get_godot_dir_name()).path_join("app_userdata").path_join(appname);
723 }
724 }
725
726 return get_data_path().path_join(get_godot_dir_name()).path_join("app_userdata").path_join("[unnamed project]");
727}
728
729String OS_Unix::get_executable_path() const {
730#ifdef __linux__
731 //fix for running from a symlink
732 char buf[256];
733 memset(buf, 0, 256);
734 ssize_t len = readlink("/proc/self/exe", buf, sizeof(buf));
735 String b;
736 if (len > 0) {
737 b.parse_utf8(buf, len);
738 }
739 if (b.is_empty()) {
740 WARN_PRINT("Couldn't get executable path from /proc/self/exe, using argv[0]");
741 return OS::get_executable_path();
742 }
743 return b;
744#elif defined(__OpenBSD__) || defined(__NetBSD__)
745 char resolved_path[MAXPATHLEN];
746
747 realpath(OS::get_executable_path().utf8().get_data(), resolved_path);
748
749 return String(resolved_path);
750#elif defined(__FreeBSD__)
751 int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
752 char buf[MAXPATHLEN];
753 size_t len = sizeof(buf);
754 if (sysctl(mib, 4, buf, &len, nullptr, 0) != 0) {
755 WARN_PRINT("Couldn't get executable path from sysctl");
756 return OS::get_executable_path();
757 }
758 String b;
759 b.parse_utf8(buf);
760 return b;
761#elif defined(__APPLE__)
762 char temp_path[1];
763 uint32_t buff_size = 1;
764 _NSGetExecutablePath(temp_path, &buff_size);
765
766 char *resolved_path = new char[buff_size + 1];
767
768 if (_NSGetExecutablePath(resolved_path, &buff_size) == 1) {
769 WARN_PRINT("MAXPATHLEN is too small");
770 }
771
772 String path = String::utf8(resolved_path);
773 delete[] resolved_path;
774
775 return path;
776#else
777 ERR_PRINT("Warning, don't know how to obtain executable path on this OS! Please override this function properly.");
778 return OS::get_executable_path();
779#endif
780}
781
782void UnixTerminalLogger::log_error(const char *p_function, const char *p_file, int p_line, const char *p_code, const char *p_rationale, bool p_editor_notify, ErrorType p_type) {
783 if (!should_log(true)) {
784 return;
785 }
786
787 const char *err_details;
788 if (p_rationale && p_rationale[0]) {
789 err_details = p_rationale;
790 } else {
791 err_details = p_code;
792 }
793
794 // Disable color codes if stdout is not a TTY.
795 // This prevents Godot from writing ANSI escape codes when redirecting
796 // stdout and stderr to a file.
797 const bool tty = isatty(fileno(stdout));
798 const char *gray = tty ? "\E[0;90m" : "";
799 const char *red = tty ? "\E[0;91m" : "";
800 const char *red_bold = tty ? "\E[1;31m" : "";
801 const char *yellow = tty ? "\E[0;93m" : "";
802 const char *yellow_bold = tty ? "\E[1;33m" : "";
803 const char *magenta = tty ? "\E[0;95m" : "";
804 const char *magenta_bold = tty ? "\E[1;35m" : "";
805 const char *cyan = tty ? "\E[0;96m" : "";
806 const char *cyan_bold = tty ? "\E[1;36m" : "";
807 const char *reset = tty ? "\E[0m" : "";
808
809 switch (p_type) {
810 case ERR_WARNING:
811 logf_error("%sWARNING:%s %s\n", yellow_bold, yellow, err_details);
812 logf_error("%s at: %s (%s:%i)%s\n", gray, p_function, p_file, p_line, reset);
813 break;
814 case ERR_SCRIPT:
815 logf_error("%sSCRIPT ERROR:%s %s\n", magenta_bold, magenta, err_details);
816 logf_error("%s at: %s (%s:%i)%s\n", gray, p_function, p_file, p_line, reset);
817 break;
818 case ERR_SHADER:
819 logf_error("%sSHADER ERROR:%s %s\n", cyan_bold, cyan, err_details);
820 logf_error("%s at: %s (%s:%i)%s\n", gray, p_function, p_file, p_line, reset);
821 break;
822 case ERR_ERROR:
823 default:
824 logf_error("%sERROR:%s %s\n", red_bold, red, err_details);
825 logf_error("%s at: %s (%s:%i)%s\n", gray, p_function, p_file, p_line, reset);
826 break;
827 }
828}
829
830UnixTerminalLogger::~UnixTerminalLogger() {}
831
832OS_Unix::OS_Unix() {
833 Vector<Logger *> loggers;
834 loggers.push_back(memnew(UnixTerminalLogger));
835 _set_logger(memnew(CompositeLogger(loggers)));
836}
837
838#endif
839