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