Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/drivers/unix/os_unix.cpp
21052 views
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/file_access_unix_pipe.h"
41
#include "drivers/unix/net_socket_unix.h"
42
#include "drivers/unix/thread_posix.h"
43
#include "servers/rendering/rendering_server.h"
44
45
#if defined(__APPLE__)
46
#include <mach-o/dyld.h>
47
#include <mach/host_info.h>
48
#include <mach/mach.h>
49
#include <mach/mach_host.h>
50
#include <mach/mach_time.h>
51
#include <sys/sysctl.h>
52
#endif
53
54
#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
55
#include <sys/param.h>
56
#include <sys/sysctl.h>
57
#endif
58
59
#if defined(__FreeBSD__)
60
#include <kvm.h>
61
#endif
62
63
#if defined(__OpenBSD__)
64
#include <sys/swap.h>
65
#include <uvm/uvmexp.h>
66
#endif
67
68
#if defined(__NetBSD__)
69
#include <uvm/uvm_extern.h>
70
#endif
71
72
#include <dlfcn.h>
73
#include <poll.h>
74
#include <sys/resource.h>
75
#include <sys/stat.h>
76
#include <sys/time.h>
77
#include <sys/wait.h>
78
#include <unistd.h>
79
#include <cerrno>
80
#include <csignal>
81
#include <cstdarg>
82
#include <cstdio>
83
#include <cstdlib>
84
#include <ctime>
85
86
#ifndef RTLD_DEEPBIND
87
#define RTLD_DEEPBIND 0
88
#endif
89
90
#ifndef SANITIZERS_ENABLED
91
#define GODOT_DLOPEN_MODE RTLD_NOW | RTLD_DEEPBIND
92
#else
93
#define GODOT_DLOPEN_MODE RTLD_NOW
94
#endif
95
96
#if defined(MACOS_ENABLED) || (defined(__ANDROID_API__) && __ANDROID_API__ >= 28)
97
// Random location for getentropy. Fitting.
98
#include <sys/random.h>
99
#define UNIX_GET_ENTROPY
100
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || (defined(__GLIBC_MINOR__) && (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 26))
101
// In <unistd.h>.
102
// One day... (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700)
103
// https://publications.opengroup.org/standards/unix/c211
104
#define UNIX_GET_ENTROPY
105
#endif
106
107
#if !defined(UNIX_GET_ENTROPY) && !defined(NO_URANDOM)
108
#include <fcntl.h>
109
#endif
110
111
/// Clock Setup function (used by get_ticks_usec)
112
static uint64_t _clock_start = 0;
113
#if defined(__APPLE__)
114
static double _clock_scale = 0;
115
static void _setup_clock() {
116
mach_timebase_info_data_t info;
117
kern_return_t ret = mach_timebase_info(&info);
118
ERR_FAIL_COND_MSG(ret != 0, "OS CLOCK IS NOT WORKING!");
119
_clock_scale = ((double)info.numer / (double)info.denom) / 1000.0;
120
_clock_start = mach_absolute_time() * _clock_scale;
121
}
122
#else
123
#if defined(CLOCK_MONOTONIC_RAW) && !defined(WEB_ENABLED) // This is a better clock on Linux.
124
#define GODOT_CLOCK CLOCK_MONOTONIC_RAW
125
#else
126
#define GODOT_CLOCK CLOCK_MONOTONIC
127
#endif
128
static void _setup_clock() {
129
struct timespec tv_now = { 0, 0 };
130
ERR_FAIL_COND_MSG(clock_gettime(GODOT_CLOCK, &tv_now) != 0, "OS CLOCK IS NOT WORKING!");
131
_clock_start = ((uint64_t)tv_now.tv_nsec / 1000L) + (uint64_t)tv_now.tv_sec * 1000000L;
132
}
133
#endif
134
135
struct sigaction old_action;
136
137
static void handle_interrupt(int sig) {
138
if (!EngineDebugger::is_active()) {
139
return;
140
}
141
142
EngineDebugger::get_script_debugger()->set_depth(-1);
143
EngineDebugger::get_script_debugger()->set_lines_left(1);
144
145
// Ensure we call the old action if it was configured.
146
if (old_action.sa_handler && old_action.sa_handler != SIG_IGN && old_action.sa_handler != SIG_DFL) {
147
old_action.sa_handler(sig);
148
}
149
}
150
151
void OS_Unix::initialize_debugging() {
152
if (EngineDebugger::is_active()) {
153
struct sigaction action;
154
memset(&action, 0, sizeof(action));
155
action.sa_handler = handle_interrupt;
156
sigaction(SIGINT, &action, &old_action);
157
}
158
}
159
160
int OS_Unix::unix_initialize_audio(int p_audio_driver) {
161
return 0;
162
}
163
164
void OS_Unix::initialize_core() {
165
#ifdef THREADS_ENABLED
166
init_thread_posix();
167
#endif
168
169
FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_RESOURCES);
170
FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_USERDATA);
171
FileAccess::make_default<FileAccessUnix>(FileAccess::ACCESS_FILESYSTEM);
172
FileAccess::make_default<FileAccessUnixPipe>(FileAccess::ACCESS_PIPE);
173
DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_RESOURCES);
174
DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_USERDATA);
175
DirAccess::make_default<DirAccessUnix>(DirAccess::ACCESS_FILESYSTEM);
176
177
#ifndef UNIX_SOCKET_UNAVAILABLE
178
NetSocketUnix::make_default();
179
IPUnix::make_default();
180
#endif
181
process_map = memnew((HashMap<ProcessID, ProcessInfo>));
182
183
_setup_clock();
184
}
185
186
void OS_Unix::finalize_core() {
187
memdelete(process_map);
188
#ifndef UNIX_SOCKET_UNAVAILABLE
189
NetSocketUnix::cleanup();
190
#endif
191
}
192
193
Vector<String> OS_Unix::get_video_adapter_driver_info() const {
194
return Vector<String>();
195
}
196
197
String OS_Unix::get_stdin_string(int64_t p_buffer_size) {
198
Vector<uint8_t> data;
199
data.resize(p_buffer_size);
200
if (fgets((char *)data.ptrw(), data.size(), stdin)) {
201
return String::utf8((char *)data.ptr()).replace("\r\n", "\n").rstrip("\n");
202
}
203
return String();
204
}
205
206
PackedByteArray OS_Unix::get_stdin_buffer(int64_t p_buffer_size) {
207
Vector<uint8_t> data;
208
data.resize(p_buffer_size);
209
size_t sz = fread((void *)data.ptrw(), 1, data.size(), stdin);
210
if (sz > 0) {
211
data.resize(sz);
212
return data;
213
}
214
return PackedByteArray();
215
}
216
217
OS_Unix::StdHandleType OS_Unix::get_stdin_type() const {
218
int h = fileno(stdin);
219
if (h == -1) {
220
return STD_HANDLE_INVALID;
221
}
222
223
if (isatty(h)) {
224
return STD_HANDLE_CONSOLE;
225
}
226
struct stat statbuf;
227
if (fstat(h, &statbuf) < 0) {
228
return STD_HANDLE_UNKNOWN;
229
}
230
if (S_ISFIFO(statbuf.st_mode)) {
231
return STD_HANDLE_PIPE;
232
} else if (S_ISREG(statbuf.st_mode) || S_ISLNK(statbuf.st_mode)) {
233
return STD_HANDLE_FILE;
234
}
235
return STD_HANDLE_UNKNOWN;
236
}
237
238
OS_Unix::StdHandleType OS_Unix::get_stdout_type() const {
239
int h = fileno(stdout);
240
if (h == -1) {
241
return STD_HANDLE_INVALID;
242
}
243
244
if (isatty(h)) {
245
return STD_HANDLE_CONSOLE;
246
}
247
struct stat statbuf;
248
if (fstat(h, &statbuf) < 0) {
249
return STD_HANDLE_UNKNOWN;
250
}
251
if (S_ISFIFO(statbuf.st_mode)) {
252
return STD_HANDLE_PIPE;
253
} else if (S_ISREG(statbuf.st_mode) || S_ISLNK(statbuf.st_mode)) {
254
return STD_HANDLE_FILE;
255
}
256
return STD_HANDLE_UNKNOWN;
257
}
258
259
OS_Unix::StdHandleType OS_Unix::get_stderr_type() const {
260
int h = fileno(stderr);
261
if (h == -1) {
262
return STD_HANDLE_INVALID;
263
}
264
265
if (isatty(h)) {
266
return STD_HANDLE_CONSOLE;
267
}
268
struct stat statbuf;
269
if (fstat(h, &statbuf) < 0) {
270
return STD_HANDLE_UNKNOWN;
271
}
272
if (S_ISFIFO(statbuf.st_mode)) {
273
return STD_HANDLE_PIPE;
274
} else if (S_ISREG(statbuf.st_mode) || S_ISLNK(statbuf.st_mode)) {
275
return STD_HANDLE_FILE;
276
}
277
return STD_HANDLE_UNKNOWN;
278
}
279
280
Error OS_Unix::get_entropy(uint8_t *r_buffer, int p_bytes) {
281
#if defined(UNIX_GET_ENTROPY)
282
int left = p_bytes;
283
int ofs = 0;
284
do {
285
int chunk = MIN(left, 256);
286
ERR_FAIL_COND_V(getentropy(r_buffer + ofs, chunk), FAILED);
287
left -= chunk;
288
ofs += chunk;
289
} while (left > 0);
290
// Define this yourself if you don't want to fall back to /dev/urandom.
291
#elif !defined(NO_URANDOM)
292
int r = open("/dev/urandom", O_RDONLY);
293
ERR_FAIL_COND_V(r < 0, FAILED);
294
int left = p_bytes;
295
do {
296
ssize_t ret = read(r, r_buffer, p_bytes);
297
ERR_FAIL_COND_V(ret <= 0, FAILED);
298
left -= ret;
299
} while (left > 0);
300
#else
301
return ERR_UNAVAILABLE;
302
#endif
303
return OK;
304
}
305
306
String OS_Unix::get_name() const {
307
return "Unix";
308
}
309
310
String OS_Unix::get_distribution_name() const {
311
return "";
312
}
313
314
String OS_Unix::get_version() const {
315
return "";
316
}
317
318
String OS_Unix::get_temp_path() const {
319
return "/tmp";
320
}
321
322
double OS_Unix::get_unix_time() const {
323
struct timeval tv_now;
324
gettimeofday(&tv_now, nullptr);
325
return (double)tv_now.tv_sec + double(tv_now.tv_usec) / 1000000;
326
}
327
328
OS::DateTime OS_Unix::get_datetime(bool p_utc) const {
329
time_t t = time(nullptr);
330
struct tm lt;
331
if (p_utc) {
332
gmtime_r(&t, &lt);
333
} else {
334
localtime_r(&t, &lt);
335
}
336
DateTime ret;
337
ret.year = 1900 + lt.tm_year;
338
// Index starting at 1 to match OS_Unix::get_date
339
// and Windows SYSTEMTIME and tm_mon follows the typical structure
340
// of 0-11, noted here: http://www.cplusplus.com/reference/ctime/tm/
341
ret.month = (Month)(lt.tm_mon + 1);
342
ret.day = lt.tm_mday;
343
ret.weekday = (Weekday)lt.tm_wday;
344
ret.hour = lt.tm_hour;
345
ret.minute = lt.tm_min;
346
ret.second = lt.tm_sec;
347
ret.dst = lt.tm_isdst;
348
349
return ret;
350
}
351
352
OS::TimeZoneInfo OS_Unix::get_time_zone_info() const {
353
time_t t = time(nullptr);
354
struct tm lt;
355
localtime_r(&t, &lt);
356
char name[16];
357
strftime(name, 16, "%Z", &lt);
358
name[15] = 0;
359
TimeZoneInfo ret;
360
ret.name = name;
361
362
char bias_buf[16];
363
strftime(bias_buf, 16, "%z", &lt);
364
int bias;
365
bias_buf[15] = 0;
366
sscanf(bias_buf, "%d", &bias);
367
368
// convert from ISO 8601 (1 minute=1, 1 hour=100) to minutes
369
int hour = (int)bias / 100;
370
int minutes = bias % 100;
371
if (bias < 0) {
372
ret.bias = hour * 60 - minutes;
373
} else {
374
ret.bias = hour * 60 + minutes;
375
}
376
377
return ret;
378
}
379
380
void OS_Unix::delay_usec(uint32_t p_usec) const {
381
struct timespec requested = { static_cast<time_t>(p_usec / 1000000), (static_cast<long>(p_usec) % 1000000) * 1000 };
382
struct timespec remaining;
383
while (nanosleep(&requested, &remaining) == -1 && errno == EINTR) {
384
requested.tv_sec = remaining.tv_sec;
385
requested.tv_nsec = remaining.tv_nsec;
386
}
387
}
388
389
uint64_t OS_Unix::get_ticks_usec() const {
390
#if defined(__APPLE__)
391
uint64_t longtime = mach_absolute_time() * _clock_scale;
392
#else
393
// Unchecked return. Static analyzers might complain.
394
// If _setup_clock() succeeded, we assume clock_gettime() works.
395
struct timespec tv_now = { 0, 0 };
396
clock_gettime(GODOT_CLOCK, &tv_now);
397
uint64_t longtime = ((uint64_t)tv_now.tv_nsec / 1000L) + (uint64_t)tv_now.tv_sec * 1000000L;
398
#endif
399
longtime -= _clock_start;
400
401
return longtime;
402
}
403
404
Dictionary OS_Unix::get_memory_info() const {
405
Dictionary meminfo;
406
407
meminfo["physical"] = -1;
408
meminfo["free"] = -1;
409
meminfo["available"] = -1;
410
meminfo["stack"] = -1;
411
412
#if defined(__APPLE__)
413
int64_t phy_mem = 0;
414
size_t len = sizeof(phy_mem);
415
if (sysctlbyname("hw.memsize", &phy_mem, &len, nullptr, 0) < 0) {
416
ERR_PRINT(vformat("Could not get hw.memsize, error code: %d - %s", errno, strerror(errno)));
417
}
418
419
mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
420
vm_statistics64_data_t vmstat;
421
if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)&vmstat, &count) != KERN_SUCCESS) {
422
ERR_PRINT("Could not get host vm statistics.");
423
}
424
int64_t used = (vmstat.active_count + vmstat.inactive_count + vmstat.speculative_count + vmstat.wire_count + vmstat.compressor_page_count - vmstat.purgeable_count - vmstat.external_page_count) * (int64_t)vm_page_size;
425
426
#if !defined(APPLE_EMBEDDED_ENABLED)
427
struct xsw_usage swap_used = {};
428
len = sizeof(swap_used);
429
if (sysctlbyname("vm.swapusage", &swap_used, &len, nullptr, 0) < 0) {
430
ERR_PRINT(vformat("Could not get vm.swapusage, error code: %d - %s", errno, strerror(errno)));
431
}
432
#endif
433
434
if (phy_mem != 0) {
435
meminfo["physical"] = phy_mem;
436
}
437
if (used != 0) {
438
meminfo["free"] = phy_mem - used;
439
}
440
#if defined(APPLE_EMBEDDED_ENABLED)
441
meminfo["available"] = meminfo["free"];
442
#else
443
if (swap_used.xsu_avail + (phy_mem - used) != 0) {
444
meminfo["available"] = swap_used.xsu_avail + (phy_mem - used);
445
}
446
#endif
447
448
#elif defined(__FreeBSD__)
449
int pagesize = 0;
450
size_t len = sizeof(pagesize);
451
if (sysctlbyname("vm.stats.vm.v_page_size", &pagesize, &len, nullptr, 0) < 0) {
452
ERR_PRINT(vformat("Could not get vm.stats.vm.v_page_size, error code: %d - %s", errno, strerror(errno)));
453
}
454
455
uint64_t mtotal = 0;
456
len = sizeof(mtotal);
457
if (sysctlbyname("vm.stats.vm.v_page_count", &mtotal, &len, nullptr, 0) < 0) {
458
ERR_PRINT(vformat("Could not get vm.stats.vm.v_page_count, error code: %d - %s", errno, strerror(errno)));
459
}
460
uint64_t mfree = 0;
461
len = sizeof(mfree);
462
if (sysctlbyname("vm.stats.vm.v_free_count", &mfree, &len, nullptr, 0) < 0) {
463
ERR_PRINT(vformat("Could not get vm.stats.vm.v_free_count, error code: %d - %s", errno, strerror(errno)));
464
}
465
466
uint64_t stotal = 0;
467
uint64_t sused = 0;
468
char errmsg[_POSIX2_LINE_MAX] = {};
469
kvm_t *kd = kvm_openfiles(nullptr, "/dev/null", nullptr, 0, errmsg);
470
if (kd == nullptr) {
471
ERR_PRINT(vformat("kvm_openfiles failed, error: %s", errmsg));
472
} else {
473
struct kvm_swap swap_info[32];
474
int count = kvm_getswapinfo(kd, swap_info, 32, 0);
475
for (int i = 0; i < count; i++) {
476
stotal += swap_info[i].ksw_total;
477
sused += swap_info[i].ksw_used;
478
}
479
kvm_close(kd);
480
}
481
482
if (mtotal * pagesize != 0) {
483
meminfo["physical"] = mtotal * pagesize;
484
}
485
if (mfree * pagesize != 0) {
486
meminfo["free"] = mfree * pagesize;
487
}
488
if ((mfree + stotal - sused) * pagesize != 0) {
489
meminfo["available"] = (mfree + stotal - sused) * pagesize;
490
}
491
#elif defined(__OpenBSD__)
492
int pagesize = sysconf(_SC_PAGESIZE);
493
494
const int mib[] = { CTL_VM, VM_UVMEXP };
495
uvmexp uvmexp_info;
496
size_t len = sizeof(uvmexp_info);
497
if (sysctl(mib, 2, &uvmexp_info, &len, nullptr, 0) < 0) {
498
ERR_PRINT(vformat("Could not get CTL_VM, VM_UVMEXP, error code: %d - %s", errno, strerror(errno)));
499
}
500
501
uint64_t stotal = 0;
502
uint64_t sused = 0;
503
int count = swapctl(SWAP_NSWAP, 0, 0);
504
if (count > 0) {
505
swapent swap_info[count];
506
count = swapctl(SWAP_STATS, swap_info, count);
507
508
for (int i = 0; i < count; i++) {
509
if (swap_info[i].se_flags & SWF_ENABLE) {
510
sused += swap_info[i].se_inuse;
511
stotal += swap_info[i].se_nblks;
512
}
513
}
514
}
515
516
if (uvmexp_info.npages * pagesize != 0) {
517
meminfo["physical"] = uvmexp_info.npages * pagesize;
518
}
519
if (uvmexp_info.free * pagesize != 0) {
520
meminfo["free"] = uvmexp_info.free * pagesize;
521
}
522
if ((uvmexp_info.free * pagesize) + (stotal - sused) * DEV_BSIZE != 0) {
523
meminfo["available"] = (uvmexp_info.free * pagesize) + (stotal - sused) * DEV_BSIZE;
524
}
525
#elif defined(__NetBSD__)
526
int pagesize = sysconf(_SC_PAGESIZE);
527
528
const int mib[] = { CTL_VM, VM_UVMEXP2 };
529
uvmexp_sysctl uvmexp_info;
530
size_t len = sizeof(uvmexp_info);
531
if (sysctl(mib, 2, &uvmexp_info, &len, nullptr, 0) < 0) {
532
ERR_PRINT(vformat("Could not get CTL_VM, VM_UVMEXP2, error code: %d - %s", errno, strerror(errno)));
533
}
534
535
if (uvmexp_info.npages * pagesize != 0) {
536
meminfo["physical"] = uvmexp_info.npages * pagesize;
537
}
538
if (uvmexp_info.free * pagesize != 0) {
539
meminfo["free"] = uvmexp_info.free * pagesize;
540
}
541
if ((uvmexp_info.free + uvmexp_info.swpages - uvmexp_info.swpginuse) * pagesize != 0) {
542
meminfo["available"] = (uvmexp_info.free + uvmexp_info.swpages - uvmexp_info.swpginuse) * pagesize;
543
}
544
#else
545
Error err;
546
Ref<FileAccess> f = FileAccess::open("/proc/meminfo", FileAccess::READ, &err);
547
uint64_t mtotal = 0;
548
uint64_t mfree = 0;
549
uint64_t sfree = 0;
550
while (f.is_valid() && !f->eof_reached()) {
551
String s = f->get_line().strip_edges();
552
if (s.begins_with("MemTotal:")) {
553
Vector<String> stok = s.replace("MemTotal:", "").strip_edges().split(" ");
554
if (stok.size() == 2) {
555
mtotal = stok[0].to_int() * 1024;
556
}
557
}
558
if (s.begins_with("MemFree:")) {
559
Vector<String> stok = s.replace("MemFree:", "").strip_edges().split(" ");
560
if (stok.size() == 2) {
561
mfree = stok[0].to_int() * 1024;
562
}
563
}
564
if (s.begins_with("SwapFree:")) {
565
Vector<String> stok = s.replace("SwapFree:", "").strip_edges().split(" ");
566
if (stok.size() == 2) {
567
sfree = stok[0].to_int() * 1024;
568
}
569
}
570
}
571
572
if (mtotal != 0) {
573
meminfo["physical"] = mtotal;
574
}
575
if (mfree != 0) {
576
meminfo["free"] = mfree;
577
}
578
if (mfree + sfree != 0) {
579
meminfo["available"] = mfree + sfree;
580
}
581
#endif
582
583
rlimit stackinfo = {};
584
getrlimit(RLIMIT_STACK, &stackinfo);
585
586
if (stackinfo.rlim_cur != 0) {
587
meminfo["stack"] = (int64_t)stackinfo.rlim_cur;
588
}
589
590
return meminfo;
591
}
592
593
#if !defined(__GLIBC__) && !defined(WEB_ENABLED)
594
void OS_Unix::_load_iconv() {
595
#if defined(MACOS_ENABLED) || defined(IOS_ENABLED)
596
String iconv_lib_aliases[] = { "/usr/lib/libiconv.2.dylib" };
597
String iconv_func_aliases[] = { "iconv" };
598
String charset_lib_aliases[] = { "/usr/lib/libcharset.1.dylib" };
599
#else
600
String iconv_lib_aliases[] = { "", "libiconv.2.so", "libiconv.so" };
601
String iconv_func_aliases[] = { "libiconv", "iconv", "bsd_iconv", "rpl_iconv" };
602
String charset_lib_aliases[] = { "", "libcharset.1.so", "libcharset.so" };
603
#endif
604
605
for (size_t i = 0; i < sizeof(iconv_lib_aliases) / sizeof(iconv_lib_aliases[0]); i++) {
606
void *iconv_lib = iconv_lib_aliases[i].is_empty() ? RTLD_NEXT : dlopen(iconv_lib_aliases[i].utf8().get_data(), RTLD_NOW);
607
if (iconv_lib) {
608
for (size_t j = 0; j < sizeof(iconv_func_aliases) / sizeof(iconv_func_aliases[0]); j++) {
609
gd_iconv_open = (PIConvOpen)dlsym(iconv_lib, (iconv_func_aliases[j] + "_open").utf8().get_data());
610
gd_iconv = (PIConv)dlsym(iconv_lib, (iconv_func_aliases[j]).utf8().get_data());
611
gd_iconv_close = (PIConvClose)dlsym(iconv_lib, (iconv_func_aliases[j] + "_close").utf8().get_data());
612
if (gd_iconv_open && gd_iconv && gd_iconv_close) {
613
break;
614
}
615
}
616
if (gd_iconv_open && gd_iconv && gd_iconv_close) {
617
break;
618
}
619
if (!iconv_lib_aliases[i].is_empty()) {
620
dlclose(iconv_lib);
621
}
622
}
623
}
624
625
for (size_t i = 0; i < sizeof(charset_lib_aliases) / sizeof(charset_lib_aliases[0]); i++) {
626
void *cs_lib = charset_lib_aliases[i].is_empty() ? RTLD_NEXT : dlopen(charset_lib_aliases[i].utf8().get_data(), RTLD_NOW);
627
if (cs_lib) {
628
gd_locale_charset = (PIConvLocaleCharset)dlsym(cs_lib, "locale_charset");
629
if (gd_locale_charset) {
630
break;
631
}
632
if (!charset_lib_aliases[i].is_empty()) {
633
dlclose(cs_lib);
634
}
635
}
636
}
637
_iconv_ok = gd_iconv_open && gd_iconv && gd_iconv_close && gd_locale_charset;
638
}
639
#endif
640
641
String OS_Unix::multibyte_to_string(const String &p_encoding, const PackedByteArray &p_array) const {
642
ERR_FAIL_COND_V_MSG(!_iconv_ok, String(), "Conversion failed: Unable to load libiconv");
643
644
LocalVector<char> chars;
645
#if defined(__GLIBC__) || defined(WEB_ENABLED)
646
gd_iconv_t ctx = gd_iconv_open("UTF-8", p_encoding.is_empty() ? nl_langinfo(CODESET) : p_encoding.utf8().get_data());
647
#else
648
gd_iconv_t ctx = gd_iconv_open("UTF-8", p_encoding.is_empty() ? gd_locale_charset() : p_encoding.utf8().get_data());
649
#endif
650
ERR_FAIL_COND_V_MSG(ctx == (gd_iconv_t)(-1), String(), "Conversion failed: Unknown encoding");
651
652
char *in_ptr = (char *)p_array.ptr();
653
size_t in_size = p_array.size();
654
655
chars.resize(in_size);
656
char *out_ptr = (char *)chars.ptr();
657
size_t out_size = chars.size();
658
659
while (gd_iconv(ctx, &in_ptr, &in_size, &out_ptr, &out_size) == (size_t)-1) {
660
if (errno != E2BIG) {
661
gd_iconv_close(ctx);
662
ERR_FAIL_V_MSG(String(), vformat("Conversion failed: %d - %s", errno, strerror(errno)));
663
}
664
int64_t rate = (chars.size()) / (p_array.size() - in_size);
665
size_t oldpos = chars.size() - out_size;
666
chars.resize(chars.size() + in_size * rate);
667
out_ptr = (char *)chars.ptr() + oldpos;
668
out_size = chars.size() - oldpos;
669
}
670
chars.resize(chars.size() - out_size);
671
gd_iconv_close(ctx);
672
673
return String::utf8((const char *)chars.ptr(), chars.size());
674
}
675
676
PackedByteArray OS_Unix::string_to_multibyte(const String &p_encoding, const String &p_string) const {
677
ERR_FAIL_COND_V_MSG(!_iconv_ok, PackedByteArray(), "Conversion failed: Unable to load libiconv");
678
679
CharString charstr = p_string.utf8();
680
681
PackedByteArray ret;
682
#if defined(__GLIBC__) || defined(WEB_ENABLED)
683
gd_iconv_t ctx = gd_iconv_open(p_encoding.is_empty() ? nl_langinfo(CODESET) : p_encoding.utf8().get_data(), "UTF-8");
684
#else
685
gd_iconv_t ctx = gd_iconv_open(p_encoding.is_empty() ? gd_locale_charset() : p_encoding.utf8().get_data(), "UTF-8");
686
#endif
687
ERR_FAIL_COND_V_MSG(ctx == (gd_iconv_t)(-1), PackedByteArray(), "Conversion failed: Unknown encoding");
688
689
char *in_ptr = (char *)charstr.ptr();
690
size_t in_size = charstr.size();
691
692
ret.resize(in_size);
693
char *out_ptr = (char *)ret.ptrw();
694
size_t out_size = ret.size();
695
696
while (gd_iconv(ctx, &in_ptr, &in_size, &out_ptr, &out_size) == (size_t)-1) {
697
if (errno != E2BIG) {
698
gd_iconv_close(ctx);
699
ERR_FAIL_V_MSG(PackedByteArray(), vformat("Conversion failed: %d - %s", errno, strerror(errno)));
700
}
701
int64_t rate = (ret.size()) / (charstr.size() - in_size);
702
size_t oldpos = ret.size() - out_size;
703
ret.resize(ret.size() + in_size * rate);
704
out_ptr = (char *)ret.ptrw() + oldpos;
705
out_size = ret.size() - oldpos;
706
}
707
ret.resize(ret.size() - out_size);
708
gd_iconv_close(ctx);
709
710
return ret;
711
}
712
713
Dictionary OS_Unix::execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking) {
714
#define CLEAN_PIPES \
715
if (pipe_in[0] >= 0) { \
716
::close(pipe_in[0]); \
717
} \
718
if (pipe_in[1] >= 0) { \
719
::close(pipe_in[1]); \
720
} \
721
if (pipe_out[0] >= 0) { \
722
::close(pipe_out[0]); \
723
} \
724
if (pipe_out[1] >= 0) { \
725
::close(pipe_out[1]); \
726
} \
727
if (pipe_err[0] >= 0) { \
728
::close(pipe_err[0]); \
729
} \
730
if (pipe_err[1] >= 0) { \
731
::close(pipe_err[1]); \
732
}
733
734
Dictionary ret;
735
#ifdef __EMSCRIPTEN__
736
// Don't compile this code at all to avoid undefined references.
737
// Actual virtual call goes to OS_Web.
738
ERR_FAIL_V(ret);
739
#else
740
// Create pipes.
741
int pipe_in[2] = { -1, -1 };
742
int pipe_out[2] = { -1, -1 };
743
int pipe_err[2] = { -1, -1 };
744
745
ERR_FAIL_COND_V(pipe(pipe_in) != 0, ret);
746
if (pipe(pipe_out) != 0) {
747
CLEAN_PIPES
748
ERR_FAIL_V(ret);
749
}
750
if (pipe(pipe_err) != 0) {
751
CLEAN_PIPES
752
ERR_FAIL_V(ret);
753
}
754
755
// Create process.
756
pid_t pid = fork();
757
if (pid < 0) {
758
CLEAN_PIPES
759
ERR_FAIL_V(ret);
760
}
761
762
if (pid == 0) {
763
// The new process
764
// Create a new session-ID so parent won't wait for it.
765
// This ensures the process won't go zombie at the end.
766
setsid();
767
768
// The child process.
769
Vector<CharString> cs;
770
cs.push_back(p_path.utf8());
771
for (const String &arg : p_arguments) {
772
cs.push_back(arg.utf8());
773
}
774
775
Vector<char *> args;
776
for (int i = 0; i < cs.size(); i++) {
777
args.push_back((char *)cs[i].get_data());
778
}
779
args.push_back(0);
780
781
::close(STDIN_FILENO);
782
::dup2(pipe_in[0], STDIN_FILENO);
783
784
::close(STDOUT_FILENO);
785
::dup2(pipe_out[1], STDOUT_FILENO);
786
787
::close(STDERR_FILENO);
788
::dup2(pipe_err[1], STDERR_FILENO);
789
790
CLEAN_PIPES
791
792
execvp(p_path.utf8().get_data(), &args[0]);
793
// The execvp() function only returns if an error occurs.
794
ERR_PRINT("Could not create child process: " + p_path);
795
raise(SIGKILL);
796
}
797
::close(pipe_in[0]);
798
::close(pipe_out[1]);
799
::close(pipe_err[1]);
800
801
Ref<FileAccessUnixPipe> main_pipe;
802
main_pipe.instantiate();
803
main_pipe->open_existing(pipe_out[0], pipe_in[1], p_blocking);
804
805
Ref<FileAccessUnixPipe> err_pipe;
806
err_pipe.instantiate();
807
err_pipe->open_existing(pipe_err[0], -1, p_blocking);
808
809
ProcessInfo pi;
810
process_map_mutex.lock();
811
process_map->insert(pid, pi);
812
process_map_mutex.unlock();
813
814
ret["stdio"] = main_pipe;
815
ret["stderr"] = err_pipe;
816
ret["pid"] = pid;
817
818
#undef CLEAN_PIPES
819
return ret;
820
#endif
821
}
822
823
int OS_Unix::_wait_for_pid_completion(const pid_t p_pid, int *r_status, int p_options, pid_t *r_pid) {
824
while (true) {
825
pid_t pid = waitpid(p_pid, r_status, p_options);
826
if (pid != -1) {
827
// When `p_options` has `WNOHANG`, 0 can be returned if the process is still running.
828
if (r_pid) {
829
*r_pid = pid;
830
}
831
return 0;
832
}
833
const int error = errno;
834
if (error == EINTR) {
835
// We're in a debugger, should call waitpid again.
836
// See https://stackoverflow.com/a/45472920/730797.
837
continue;
838
}
839
return error;
840
}
841
}
842
843
bool OS_Unix::_check_pid_is_running(const pid_t p_pid, int *r_status) const {
844
const ProcessInfo *pi = process_map->getptr(p_pid);
845
846
if (pi && !pi->is_running) {
847
// Can return cached value.
848
if (r_status) {
849
*r_status = pi->exit_code;
850
}
851
return false;
852
}
853
854
pid_t pid = -1;
855
int status = 0;
856
const int result = _wait_for_pid_completion(p_pid, &status, WNOHANG, &pid);
857
if (result == 0 && pid == 0) {
858
// Thread is still running.
859
return true;
860
}
861
862
ERR_FAIL_COND_V_MSG(result != 0, false, vformat("Thread %d exited with errno: %d", (int)p_pid, errno));
863
864
// Thread exited normally.
865
status = WIFEXITED(status) ? WEXITSTATUS(status) : status;
866
867
if (pi) {
868
pi->is_running = false;
869
pi->exit_code = status;
870
}
871
872
if (r_status) {
873
*r_status = status;
874
}
875
876
return false;
877
}
878
879
Error 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) {
880
#ifdef __EMSCRIPTEN__
881
// Don't compile this code at all to avoid undefined references.
882
// Actual virtual call goes to OS_Web.
883
ERR_FAIL_V(ERR_BUG);
884
#else
885
if (r_pipe) {
886
String command = "\"" + p_path + "\"";
887
for (const String &arg : p_arguments) {
888
command += String(" \"") + arg + "\"";
889
}
890
if (read_stderr) {
891
command += " 2>&1"; // Include stderr
892
} else {
893
command += " 2>/dev/null"; // Silence stderr
894
}
895
896
FILE *f = popen(command.utf8().get_data(), "r");
897
ERR_FAIL_NULL_V_MSG(f, ERR_CANT_OPEN, "Cannot create pipe from command: " + command + ".");
898
char buf[65535];
899
while (fgets(buf, 65535, f)) {
900
if (p_pipe_mutex) {
901
p_pipe_mutex->lock();
902
}
903
String pipe_out;
904
if (pipe_out.append_utf8(buf) == OK) {
905
(*r_pipe) += pipe_out;
906
} else {
907
(*r_pipe) += String(buf); // If not valid UTF-8 try decode as Latin-1
908
}
909
if (p_pipe_mutex) {
910
p_pipe_mutex->unlock();
911
}
912
}
913
int rv = pclose(f);
914
915
if (r_exitcode) {
916
*r_exitcode = WEXITSTATUS(rv);
917
}
918
return OK;
919
}
920
921
pid_t pid = fork();
922
ERR_FAIL_COND_V(pid < 0, ERR_CANT_FORK);
923
924
if (pid == 0) {
925
// The child process
926
Vector<CharString> cs;
927
cs.push_back(p_path.utf8());
928
for (const String &arg : p_arguments) {
929
cs.push_back(arg.utf8());
930
}
931
932
Vector<char *> args;
933
for (int i = 0; i < cs.size(); i++) {
934
args.push_back((char *)cs[i].get_data());
935
}
936
args.push_back(0);
937
938
execvp(p_path.utf8().get_data(), &args[0]);
939
// The execvp() function only returns if an error occurs.
940
ERR_PRINT("Could not create child process: " + p_path);
941
raise(SIGKILL);
942
}
943
944
int status = 0;
945
const int result = _wait_for_pid_completion(pid, &status, 0);
946
if (r_exitcode) {
947
*r_exitcode = WIFEXITED(status) ? WEXITSTATUS(status) : status;
948
}
949
return result ? FAILED : OK;
950
#endif
951
}
952
953
Error OS_Unix::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) {
954
#ifdef __EMSCRIPTEN__
955
// Don't compile this code at all to avoid undefined references.
956
// Actual virtual call goes to OS_Web.
957
ERR_FAIL_V(ERR_BUG);
958
#else
959
pid_t pid = fork();
960
ERR_FAIL_COND_V(pid < 0, ERR_CANT_FORK);
961
962
if (pid == 0) {
963
// The new process
964
// Create a new session-ID so parent won't wait for it.
965
// This ensures the process won't go zombie at the end.
966
setsid();
967
968
Vector<CharString> cs;
969
cs.push_back(p_path.utf8());
970
for (const String &arg : p_arguments) {
971
cs.push_back(arg.utf8());
972
}
973
974
Vector<char *> args;
975
for (int i = 0; i < cs.size(); i++) {
976
args.push_back((char *)cs[i].get_data());
977
}
978
args.push_back(0);
979
980
execvp(p_path.utf8().get_data(), &args[0]);
981
// The execvp() function only returns if an error occurs.
982
ERR_PRINT("Could not create child process: " + p_path);
983
raise(SIGKILL);
984
}
985
986
ProcessInfo pi;
987
process_map_mutex.lock();
988
process_map->insert(pid, pi);
989
process_map_mutex.unlock();
990
991
if (r_child_id) {
992
*r_child_id = pid;
993
}
994
return OK;
995
#endif
996
}
997
998
Error OS_Unix::kill(const ProcessID &p_pid) {
999
int ret = ::kill(p_pid, SIGKILL);
1000
if (!ret) {
1001
//avoid zombie process
1002
int st;
1003
_wait_for_pid_completion(p_pid, &st, 0);
1004
}
1005
return ret ? ERR_INVALID_PARAMETER : OK;
1006
}
1007
1008
int OS_Unix::get_process_id() const {
1009
return getpid();
1010
}
1011
1012
bool OS_Unix::is_process_running(const ProcessID &p_pid) const {
1013
MutexLock lock(process_map_mutex);
1014
return _check_pid_is_running(p_pid, nullptr);
1015
}
1016
1017
int OS_Unix::get_process_exit_code(const ProcessID &p_pid) const {
1018
MutexLock lock(process_map_mutex);
1019
1020
int exit_code = 0;
1021
if (_check_pid_is_running(p_pid, &exit_code)) {
1022
// Thread is still running
1023
return -1;
1024
}
1025
1026
return exit_code;
1027
}
1028
1029
String OS_Unix::get_locale() const {
1030
if (!has_environment("LANG")) {
1031
return "en";
1032
}
1033
1034
String locale = get_environment("LANG");
1035
int tp = locale.find_char('.');
1036
if (tp != -1) {
1037
locale = locale.substr(0, tp);
1038
}
1039
return locale;
1040
}
1041
1042
Error OS_Unix::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
1043
String path = p_path;
1044
1045
if (FileAccess::exists(path) && path.is_relative_path()) {
1046
// dlopen expects a slash, in this case a leading ./ for it to be interpreted as a relative path,
1047
// otherwise it will end up searching various system directories for the lib instead and finally failing.
1048
path = "./" + path;
1049
}
1050
1051
if (!FileAccess::exists(path)) {
1052
// This code exists so GDExtension can load .so files from within the executable path.
1053
path = get_executable_path().get_base_dir().path_join(p_path.get_file());
1054
}
1055
1056
if (!FileAccess::exists(path)) {
1057
// This code exists so GDExtension can load .so files from a standard unix location.
1058
path = get_executable_path().get_base_dir().path_join("../lib").path_join(p_path.get_file());
1059
}
1060
1061
ERR_FAIL_COND_V(!FileAccess::exists(path), ERR_FILE_NOT_FOUND);
1062
1063
p_library_handle = dlopen(path.utf8().get_data(), GODOT_DLOPEN_MODE);
1064
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
1065
1066
if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
1067
*p_data->r_resolved_path = path;
1068
}
1069
1070
return OK;
1071
}
1072
1073
Error OS_Unix::close_dynamic_library(void *p_library_handle) {
1074
if (dlclose(p_library_handle)) {
1075
return FAILED;
1076
}
1077
return OK;
1078
}
1079
1080
Error OS_Unix::get_dynamic_library_symbol_handle(void *p_library_handle, const String &p_name, void *&p_symbol_handle, bool p_optional) {
1081
const char *error;
1082
dlerror(); // Clear existing errors
1083
1084
p_symbol_handle = dlsym(p_library_handle, p_name.utf8().get_data());
1085
1086
error = dlerror();
1087
if (error != nullptr) {
1088
ERR_FAIL_COND_V_MSG(!p_optional, ERR_CANT_RESOLVE, "Can't resolve symbol " + p_name + ". Error: " + error + ".");
1089
1090
return ERR_CANT_RESOLVE;
1091
}
1092
return OK;
1093
}
1094
1095
Error OS_Unix::set_cwd(const String &p_cwd) {
1096
if (chdir(p_cwd.utf8().get_data()) != 0) {
1097
return ERR_CANT_OPEN;
1098
}
1099
1100
return OK;
1101
}
1102
1103
String OS_Unix::get_cwd() const {
1104
String dir;
1105
char real_current_dir_name[2048];
1106
ERR_FAIL_NULL_V(getcwd(real_current_dir_name, 2048), ".");
1107
if (dir.append_utf8(real_current_dir_name) != OK) {
1108
dir = real_current_dir_name;
1109
}
1110
return dir;
1111
}
1112
1113
bool OS_Unix::has_environment(const String &p_var) const {
1114
return getenv(p_var.utf8().get_data()) != nullptr;
1115
}
1116
1117
String OS_Unix::get_environment(const String &p_var) const {
1118
const char *val = getenv(p_var.utf8().get_data());
1119
if (val == nullptr) { // Not set; return empty string
1120
return "";
1121
}
1122
String s;
1123
if (s.append_utf8(val) == OK) {
1124
return s;
1125
}
1126
return String(val); // Not valid UTF-8, so return as-is
1127
}
1128
1129
void OS_Unix::set_environment(const String &p_var, const String &p_value) const {
1130
ERR_FAIL_COND_MSG(p_var.is_empty() || p_var.contains_char('='), vformat("Invalid environment variable name '%s', cannot be empty or include '='.", p_var));
1131
int err = setenv(p_var.utf8().get_data(), p_value.utf8().get_data(), /* overwrite: */ 1);
1132
ERR_FAIL_COND_MSG(err != 0, vformat("Failed setting environment variable '%s', the system is out of memory.", p_var));
1133
}
1134
1135
void OS_Unix::unset_environment(const String &p_var) const {
1136
ERR_FAIL_COND_MSG(p_var.is_empty() || p_var.contains_char('='), vformat("Invalid environment variable name '%s', cannot be empty or include '='.", p_var));
1137
unsetenv(p_var.utf8().get_data());
1138
}
1139
1140
String OS_Unix::get_user_data_dir(const String &p_user_dir) const {
1141
return get_data_path().path_join(p_user_dir);
1142
}
1143
1144
String OS_Unix::get_executable_path() const {
1145
#ifdef __linux__
1146
//fix for running from a symlink
1147
char buf[PATH_MAX];
1148
memset(buf, 0, PATH_MAX);
1149
ssize_t len = readlink("/proc/self/exe", buf, sizeof(buf));
1150
String b;
1151
if (len > 0) {
1152
b.append_utf8(buf, len);
1153
}
1154
if (b.is_empty()) {
1155
WARN_PRINT("Couldn't get executable path from /proc/self/exe, using argv[0]");
1156
return OS::get_executable_path();
1157
}
1158
return b;
1159
#elif defined(__OpenBSD__)
1160
char resolved_path[MAXPATHLEN];
1161
1162
realpath(OS::get_executable_path().utf8().get_data(), resolved_path);
1163
1164
return String(resolved_path);
1165
#elif defined(__NetBSD__)
1166
int mib[4] = { CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME };
1167
char buf[MAXPATHLEN];
1168
size_t len = sizeof(buf);
1169
if (sysctl(mib, 4, buf, &len, nullptr, 0) != 0) {
1170
WARN_PRINT("Couldn't get executable path from sysctl");
1171
return OS::get_executable_path();
1172
}
1173
1174
// 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:
1175
char resolved_path[MAXPATHLEN];
1176
1177
realpath(buf, resolved_path);
1178
1179
return String(resolved_path);
1180
#elif defined(__FreeBSD__)
1181
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
1182
char buf[MAXPATHLEN];
1183
size_t len = sizeof(buf);
1184
if (sysctl(mib, 4, buf, &len, nullptr, 0) != 0) {
1185
WARN_PRINT("Couldn't get executable path from sysctl");
1186
return OS::get_executable_path();
1187
}
1188
1189
return String::utf8(buf);
1190
#elif defined(__APPLE__)
1191
char temp_path[1];
1192
uint32_t buff_size = 1;
1193
_NSGetExecutablePath(temp_path, &buff_size);
1194
1195
char *resolved_path = new char[buff_size + 1];
1196
1197
if (_NSGetExecutablePath(resolved_path, &buff_size) == 1) {
1198
WARN_PRINT("MAXPATHLEN is too small");
1199
}
1200
1201
String path = String::utf8(resolved_path);
1202
delete[] resolved_path;
1203
1204
return path;
1205
#else
1206
ERR_PRINT("Warning, don't know how to obtain executable path on this OS! Please override this function properly.");
1207
return OS::get_executable_path();
1208
#endif
1209
}
1210
1211
void 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, const Vector<Ref<ScriptBacktrace>> &p_script_backtraces) {
1212
if (!should_log(true)) {
1213
return;
1214
}
1215
1216
const char *err_details;
1217
if (p_rationale && p_rationale[0]) {
1218
err_details = p_rationale;
1219
} else {
1220
err_details = p_code;
1221
}
1222
1223
// Disable color codes if stdout is not a TTY.
1224
// This prevents Godot from writing ANSI escape codes when redirecting
1225
// stdout and stderr to a file.
1226
const bool tty = isatty(fileno(stdout));
1227
const char *gray = tty ? "\E[0;90m" : "";
1228
const char *red = tty ? "\E[0;91m" : "";
1229
const char *red_bold = tty ? "\E[1;31m" : "";
1230
const char *yellow = tty ? "\E[0;93m" : "";
1231
const char *yellow_bold = tty ? "\E[1;33m" : "";
1232
const char *magenta = tty ? "\E[0;95m" : "";
1233
const char *magenta_bold = tty ? "\E[1;35m" : "";
1234
const char *cyan = tty ? "\E[0;96m" : "";
1235
const char *cyan_bold = tty ? "\E[1;36m" : "";
1236
const char *reset = tty ? "\E[0m" : "";
1237
1238
const char *bold_color;
1239
const char *normal_color;
1240
switch (p_type) {
1241
case ERR_WARNING:
1242
bold_color = yellow_bold;
1243
normal_color = yellow;
1244
break;
1245
case ERR_SCRIPT:
1246
bold_color = magenta_bold;
1247
normal_color = magenta;
1248
break;
1249
case ERR_SHADER:
1250
bold_color = cyan_bold;
1251
normal_color = cyan;
1252
break;
1253
case ERR_ERROR:
1254
default:
1255
bold_color = red_bold;
1256
normal_color = red;
1257
break;
1258
}
1259
1260
logf_error("%s%s:%s %s\n", bold_color, error_type_string(p_type), normal_color, err_details);
1261
logf_error("%s%sat: %s (%s:%i)%s\n", gray, error_type_indent(p_type), p_function, p_file, p_line, reset);
1262
1263
for (const Ref<ScriptBacktrace> &backtrace : p_script_backtraces) {
1264
if (!backtrace->is_empty()) {
1265
logf_error("%s%s%s\n", gray, backtrace->format(strlen(error_type_indent(p_type))).utf8().get_data(), reset);
1266
}
1267
}
1268
}
1269
1270
UnixTerminalLogger::~UnixTerminalLogger() {}
1271
1272
OS_Unix::OS_Unix() {
1273
#if !defined(__GLIBC__) && !defined(WEB_ENABLED)
1274
_load_iconv();
1275
#endif
1276
1277
Vector<Logger *> loggers;
1278
loggers.push_back(memnew(UnixTerminalLogger));
1279
_set_logger(memnew(CompositeLogger(loggers)));
1280
}
1281
1282
#endif
1283
1284