2014-02-10 02:10:30 +01:00
/**************************************************************************/
/* os_unix.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
2018-01-05 00:50:27 +01:00
2014-02-10 02:10:30 +01:00
# include "os_unix.h"
# ifdef UNIX_ENABLED
2020-11-07 23:33:38 +01:00
# include "core/config/project_settings.h"
2020-02-27 03:30:20 +01:00
# include "core/debugger/engine_debugger.h"
# include "core/debugger/script_debugger.h"
2018-09-11 18:13:45 +02:00
# include "drivers/unix/dir_access_unix.h"
# include "drivers/unix/file_access_unix.h"
2023-10-19 13:35:10 +02:00
# include "drivers/unix/file_access_unix_pipe.h"
2018-09-02 05:32:12 +02:00
# include "drivers/unix/net_socket_posix.h"
2018-09-11 18:13:45 +02:00
# include "drivers/unix/thread_posix.h"
2020-03-27 19:21:27 +01:00
# include "servers/rendering_server.h"
2014-02-10 02:10:30 +01:00
2023-04-03 10:46:29 +02:00
# if defined(__APPLE__)
2016-04-20 09:49:48 +02:00
# include <mach-o/dyld.h>
2023-04-03 10:46:29 +02:00
# include <mach/host_info.h>
# include <mach/mach_host.h>
2018-09-25 13:40:53 +02:00
# include <mach/mach_time.h>
2023-04-03 10:46:29 +02:00
# include <sys/sysctl.h>
2016-04-20 09:49:48 +02:00
# endif
2020-09-18 11:54:18 +02:00
# if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
2015-01-27 10:01:37 +01:00
# include <sys/param.h>
2018-01-08 01:52:24 +01:00
# include <sys/sysctl.h>
2015-01-27 10:01:37 +01:00
# endif
2018-09-11 18:13:45 +02:00
2023-04-03 10:46:29 +02:00
# if defined(__FreeBSD__)
# include <kvm.h>
# endif
# if defined(__OpenBSD__)
# include <sys/swap.h>
# include <uvm/uvmexp.h>
# endif
# if defined(__NetBSD__)
# include <uvm/uvm_extern.h>
# endif
2017-03-08 02:50:13 +01:00
# include <dlfcn.h>
2014-02-10 02:10:30 +01:00
# include <errno.h>
# include <poll.h>
# include <signal.h>
# include <stdarg.h>
2020-01-04 04:01:57 +01:00
# include <stdio.h>
2014-02-10 02:10:30 +01:00
# include <stdlib.h>
# include <string.h>
2023-04-03 10:46:29 +02:00
# include <sys/resource.h>
2014-02-10 02:10:30 +01:00
# include <sys/time.h>
# include <sys/wait.h>
2021-01-19 13:29:41 +01:00
# include <time.h>
2017-09-22 07:56:02 +02:00
# include <unistd.h>
2014-02-10 02:10:30 +01:00
2023-10-07 17:48:34 +02:00
# ifndef RTLD_DEEPBIND
# define RTLD_DEEPBIND 0
# endif
2023-10-30 22:14:59 +01:00
# ifndef SANITIZERS_ENABLED
# define GODOT_DLOPEN_MODE RTLD_NOW | RTLD_DEEPBIND
# else
# define GODOT_DLOPEN_MODE RTLD_NOW
# endif
2022-07-20 08:28:22 +02:00
# if defined(MACOS_ENABLED) || (defined(__ANDROID_API__) && __ANDROID_API__ >= 28)
2022-02-07 17:33:55 +01:00
// Random location for getentropy. Fitting.
# include <sys/random.h>
# define UNIX_GET_ENTROPY
# elif defined(__FreeBSD__) || defined(__OpenBSD__) || (defined(__GLIBC_MINOR__) && (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 26))
// In <unistd.h>.
// One day... (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700)
// https://publications.opengroup.org/standards/unix/c211
# define UNIX_GET_ENTROPY
# endif
# if !defined(UNIX_GET_ENTROPY) && !defined(NO_URANDOM)
# include <fcntl.h>
# endif
2018-09-25 13:40:53 +02:00
/// Clock Setup function (used by get_ticks_usec)
static uint64_t _clock_start = 0 ;
# if defined(__APPLE__)
static double _clock_scale = 0 ;
static void _setup_clock ( ) {
mach_timebase_info_data_t info ;
kern_return_t ret = mach_timebase_info ( & info ) ;
2019-08-15 04:57:49 +02:00
ERR_FAIL_COND_MSG ( ret ! = 0 , " OS CLOCK IS NOT WORKING! " ) ;
2018-09-30 18:00:17 +02:00
_clock_scale = ( ( double ) info . numer / ( double ) info . denom ) / 1000.0 ;
2018-09-25 13:40:53 +02:00
_clock_start = mach_absolute_time ( ) * _clock_scale ;
}
# else
2022-08-28 20:27:45 +02:00
# if defined(CLOCK_MONOTONIC_RAW) && !defined(WEB_ENABLED) // This is a better clock on Linux.
2018-09-25 13:40:53 +02:00
# define GODOT_CLOCK CLOCK_MONOTONIC_RAW
# else
# define GODOT_CLOCK CLOCK_MONOTONIC
# endif
static void _setup_clock ( ) {
struct timespec tv_now = { 0 , 0 } ;
2019-08-15 04:57:49 +02:00
ERR_FAIL_COND_MSG ( clock_gettime ( GODOT_CLOCK , & tv_now ) ! = 0 , " OS CLOCK IS NOT WORKING! " ) ;
2018-09-25 13:40:53 +02:00
_clock_start = ( ( uint64_t ) tv_now . tv_nsec / 1000L ) + ( uint64_t ) tv_now . tv_sec * 1000000L ;
}
# endif
2018-02-11 12:08:37 +01:00
static void handle_interrupt ( int sig ) {
2020-05-14 16:41:43 +02:00
if ( ! EngineDebugger : : is_active ( ) ) {
2018-02-11 12:08:37 +01:00
return ;
2020-05-14 16:41:43 +02:00
}
2018-02-11 12:08:37 +01:00
2020-02-27 03:30:20 +01:00
EngineDebugger : : get_script_debugger ( ) - > set_depth ( - 1 ) ;
EngineDebugger : : get_script_debugger ( ) - > set_lines_left ( 1 ) ;
2018-02-11 12:08:37 +01:00
}
void OS_Unix : : initialize_debugging ( ) {
2020-02-27 03:30:20 +01:00
if ( EngineDebugger : : is_active ( ) ) {
2018-02-11 12:08:37 +01:00
struct sigaction action ;
2019-04-08 16:06:57 +02:00
memset ( & action , 0 , sizeof ( action ) ) ;
2018-02-11 12:08:37 +01:00
action . sa_handler = handle_interrupt ;
2020-04-02 01:20:12 +02:00
sigaction ( SIGINT , & action , nullptr ) ;
2018-02-11 12:08:37 +01:00
}
}
2014-02-10 02:10:30 +01:00
int OS_Unix : : unix_initialize_audio ( int p_audio_driver ) {
return 0 ;
}
2017-03-05 16:44:50 +01:00
2014-02-10 02:10:30 +01:00
void OS_Unix : : initialize_core ( ) {
2023-12-01 19:39:09 +01:00
# ifdef THREADS_ENABLED
2021-01-19 13:29:41 +01:00
init_thread_posix ( ) ;
2023-12-01 19:39:09 +01:00
# endif
2021-01-19 13:29:41 +01:00
2014-02-10 02:10:30 +01:00
FileAccess : : make_default < FileAccessUnix > ( FileAccess : : ACCESS_RESOURCES ) ;
FileAccess : : make_default < FileAccessUnix > ( FileAccess : : ACCESS_USERDATA ) ;
FileAccess : : make_default < FileAccessUnix > ( FileAccess : : ACCESS_FILESYSTEM ) ;
2023-10-19 13:35:10 +02:00
FileAccess : : make_default < FileAccessUnixPipe > ( FileAccess : : ACCESS_PIPE ) ;
2014-02-10 02:10:30 +01:00
DirAccess : : make_default < DirAccessUnix > ( DirAccess : : ACCESS_RESOURCES ) ;
DirAccess : : make_default < DirAccessUnix > ( DirAccess : : ACCESS_USERDATA ) ;
DirAccess : : make_default < DirAccessUnix > ( DirAccess : : ACCESS_FILESYSTEM ) ;
2018-09-02 05:32:12 +02:00
NetSocketPosix : : make_default ( ) ;
2021-05-06 02:48:18 +02:00
IPUnix : : make_default ( ) ;
2024-04-07 20:19:46 +02:00
process_map = memnew ( ( HashMap < ProcessID , ProcessInfo > ) ) ;
2014-02-10 02:10:30 +01:00
2018-09-25 13:40:53 +02:00
_setup_clock ( ) ;
2014-02-10 02:10:30 +01:00
}
2017-09-22 07:56:02 +02:00
void OS_Unix : : finalize_core ( ) {
2024-04-07 20:19:46 +02:00
memdelete ( process_map ) ;
2018-09-13 15:47:00 +02:00
NetSocketPosix : : cleanup ( ) ;
2014-02-10 02:10:30 +01:00
}
2022-10-11 12:39:41 +02:00
Vector < String > OS_Unix : : get_video_adapter_driver_info ( ) const {
return Vector < String > ( ) ;
}
2022-12-29 21:06:11 +01:00
String OS_Unix : : get_stdin_string ( ) {
char buff [ 1024 ] ;
return String : : utf8 ( fgets ( buff , 1024 , stdin ) ) ;
2014-02-10 02:10:30 +01:00
}
2022-02-07 17:33:55 +01:00
Error OS_Unix : : get_entropy ( uint8_t * r_buffer , int p_bytes ) {
# if defined(UNIX_GET_ENTROPY)
int left = p_bytes ;
int ofs = 0 ;
do {
int chunk = MIN ( left , 256 ) ;
ERR_FAIL_COND_V ( getentropy ( r_buffer + ofs , chunk ) , FAILED ) ;
left - = chunk ;
ofs + = chunk ;
} while ( left > 0 ) ;
2022-10-03 11:43:20 +02:00
// Define this yourself if you don't want to fall back to /dev/urandom.
2022-02-07 17:33:55 +01:00
# elif !defined(NO_URANDOM)
int r = open ( " /dev/urandom " , O_RDONLY ) ;
ERR_FAIL_COND_V ( r < 0 , FAILED ) ;
int left = p_bytes ;
do {
ssize_t ret = read ( r , r_buffer , p_bytes ) ;
ERR_FAIL_COND_V ( ret < = 0 , FAILED ) ;
left - = ret ;
} while ( left > 0 ) ;
# else
return ERR_UNAVAILABLE ;
# endif
return OK ;
}
2019-05-20 19:36:24 +02:00
String OS_Unix : : get_name ( ) const {
2014-02-10 02:10:30 +01:00
return " Unix " ;
}
2022-09-16 11:14:14 +02:00
String OS_Unix : : get_distribution_name ( ) const {
return " " ;
}
String OS_Unix : : get_version ( ) const {
return " " ;
}
2020-05-31 14:19:31 +02:00
double OS_Unix : : get_unix_time ( ) const {
2018-12-19 22:18:52 +01:00
struct timeval tv_now ;
2020-04-02 01:20:12 +02:00
gettimeofday ( & tv_now , nullptr ) ;
2020-05-31 14:19:31 +02:00
return ( double ) tv_now . tv_sec + double ( tv_now . tv_usec ) / 1000000 ;
2022-02-16 13:56:32 +01:00
}
2018-12-19 22:18:52 +01:00
2022-09-08 07:36:10 +02:00
OS : : DateTime OS_Unix : : get_datetime ( bool p_utc ) const {
2020-04-02 01:20:12 +02:00
time_t t = time ( nullptr ) ;
2020-09-04 13:22:43 +02:00
struct tm lt ;
2021-10-28 17:16:47 +02:00
if ( p_utc ) {
2020-09-04 13:22:43 +02:00
gmtime_r ( & t , & lt ) ;
2020-05-14 16:41:43 +02:00
} else {
2020-09-04 13:22:43 +02:00
localtime_r ( & t , & lt ) ;
2020-05-14 16:41:43 +02:00
}
2022-09-08 07:36:10 +02:00
DateTime ret ;
2020-09-04 13:22:43 +02:00
ret . year = 1900 + lt . tm_year ;
2016-03-13 03:13:57 +01:00
// Index starting at 1 to match OS_Unix::get_date
// and Windows SYSTEMTIME and tm_mon follows the typical structure
// of 0-11, noted here: http://www.cplusplus.com/reference/ctime/tm/
2020-09-04 13:22:43 +02:00
ret . month = ( Month ) ( lt . tm_mon + 1 ) ;
ret . day = lt . tm_mday ;
ret . weekday = ( Weekday ) lt . tm_wday ;
ret . hour = lt . tm_hour ;
2021-05-24 13:54:05 +02:00
ret . minute = lt . tm_min ;
ret . second = lt . tm_sec ;
2022-09-08 07:36:10 +02:00
ret . dst = lt . tm_isdst ;
2014-02-10 02:10:30 +01:00
return ret ;
}
2015-06-06 05:35:38 +02:00
OS : : TimeZoneInfo OS_Unix : : get_time_zone_info ( ) const {
2020-04-02 01:20:12 +02:00
time_t t = time ( nullptr ) ;
2020-09-04 13:22:43 +02:00
struct tm lt ;
localtime_r ( & t , & lt ) ;
2015-06-06 05:35:38 +02:00
char name [ 16 ] ;
2020-09-04 13:22:43 +02:00
strftime ( name , 16 , " %Z " , & lt ) ;
2015-06-06 05:35:38 +02:00
name [ 15 ] = 0 ;
TimeZoneInfo ret ;
ret . name = name ;
char bias_buf [ 16 ] ;
2020-09-04 13:22:43 +02:00
strftime ( bias_buf , 16 , " %z " , & lt ) ;
2015-06-06 05:35:38 +02:00
int bias ;
bias_buf [ 15 ] = 0 ;
sscanf ( bias_buf , " %d " , & bias ) ;
// convert from ISO 8601 (1 minute=1, 1 hour=100) to minutes
int hour = ( int ) bias / 100 ;
int minutes = bias % 100 ;
2020-05-14 16:41:43 +02:00
if ( bias < 0 ) {
2015-06-06 05:35:38 +02:00
ret . bias = hour * 60 - minutes ;
2020-05-14 16:41:43 +02:00
} else {
2015-06-06 05:35:38 +02:00
ret . bias = hour * 60 + minutes ;
2020-05-14 16:41:43 +02:00
}
2015-06-06 05:35:38 +02:00
return ret ;
}
2014-02-10 02:10:30 +01:00
void OS_Unix : : delay_usec ( uint32_t p_usec ) const {
2020-10-12 16:27:31 +02:00
struct timespec requested = { static_cast < time_t > ( p_usec / 1000000 ) , ( static_cast < long > ( p_usec ) % 1000000 ) * 1000 } ;
struct timespec remaining ;
while ( nanosleep ( & requested , & remaining ) = = - 1 & & errno = = EINTR ) {
requested . tv_sec = remaining . tv_sec ;
requested . tv_nsec = remaining . tv_nsec ;
2017-08-22 18:02:12 +02:00
}
2014-02-10 02:10:30 +01:00
}
2020-05-14 14:29:06 +02:00
2014-02-10 02:10:30 +01:00
uint64_t OS_Unix : : get_ticks_usec ( ) const {
2018-09-25 13:40:53 +02:00
# if defined(__APPLE__)
uint64_t longtime = mach_absolute_time ( ) * _clock_scale ;
# else
// Unchecked return. Static analyzers might complain.
2019-02-13 09:23:29 +01:00
// If _setup_clock() succeeded, we assume clock_gettime() works.
2018-09-25 13:40:53 +02:00
struct timespec tv_now = { 0 , 0 } ;
clock_gettime ( GODOT_CLOCK , & tv_now ) ;
uint64_t longtime = ( ( uint64_t ) tv_now . tv_nsec / 1000L ) + ( uint64_t ) tv_now . tv_sec * 1000000L ;
# endif
longtime - = _clock_start ;
2017-03-05 16:44:50 +01:00
2014-02-10 02:10:30 +01:00
return longtime ;
}
2023-04-03 10:46:29 +02:00
Dictionary OS_Unix : : get_memory_info ( ) const {
Dictionary meminfo ;
meminfo [ " physical " ] = - 1 ;
meminfo [ " free " ] = - 1 ;
meminfo [ " available " ] = - 1 ;
meminfo [ " stack " ] = - 1 ;
# if defined(__APPLE__)
int pagesize = 0 ;
size_t len = sizeof ( pagesize ) ;
if ( sysctlbyname ( " vm.pagesize " , & pagesize , & len , nullptr , 0 ) < 0 ) {
ERR_PRINT ( vformat ( " Could not get vm.pagesize, error code: %d - %s " , errno , strerror ( errno ) ) ) ;
}
int64_t phy_mem = 0 ;
len = sizeof ( phy_mem ) ;
if ( sysctlbyname ( " hw.memsize " , & phy_mem , & len , nullptr , 0 ) < 0 ) {
ERR_PRINT ( vformat ( " Could not get hw.memsize, error code: %d - %s " , errno , strerror ( errno ) ) ) ;
}
mach_msg_type_number_t count = HOST_VM_INFO64_COUNT ;
vm_statistics64_data_t vmstat ;
if ( host_statistics64 ( mach_host_self ( ) , HOST_VM_INFO64 , ( host_info64_t ) & vmstat , & count ) ! = KERN_SUCCESS ) {
2023-06-28 16:27:55 +02:00
ERR_PRINT ( " Could not get host vm statistics. " ) ;
2023-04-03 10:46:29 +02:00
}
struct xsw_usage swap_used ;
len = sizeof ( swap_used ) ;
if ( sysctlbyname ( " vm.swapusage " , & swap_used , & len , nullptr , 0 ) < 0 ) {
ERR_PRINT ( vformat ( " Could not get vm.swapusage, error code: %d - %s " , errno , strerror ( errno ) ) ) ;
}
if ( phy_mem ! = 0 ) {
meminfo [ " physical " ] = phy_mem ;
}
if ( vmstat . free_count * ( int64_t ) pagesize ! = 0 ) {
meminfo [ " free " ] = vmstat . free_count * ( int64_t ) pagesize ;
}
if ( swap_used . xsu_avail + vmstat . free_count * ( int64_t ) pagesize ! = 0 ) {
meminfo [ " available " ] = swap_used . xsu_avail + vmstat . free_count * ( int64_t ) pagesize ;
}
# elif defined(__FreeBSD__)
int pagesize = 0 ;
size_t len = sizeof ( pagesize ) ;
if ( sysctlbyname ( " vm.stats.vm.v_page_size " , & pagesize , & len , nullptr , 0 ) < 0 ) {
ERR_PRINT ( vformat ( " Could not get vm.stats.vm.v_page_size, error code: %d - %s " , errno , strerror ( errno ) ) ) ;
}
uint64_t mtotal = 0 ;
len = sizeof ( mtotal ) ;
if ( sysctlbyname ( " vm.stats.vm.v_page_count " , & mtotal , & len , nullptr , 0 ) < 0 ) {
ERR_PRINT ( vformat ( " Could not get vm.stats.vm.v_page_count, error code: %d - %s " , errno , strerror ( errno ) ) ) ;
}
uint64_t mfree = 0 ;
len = sizeof ( mfree ) ;
if ( sysctlbyname ( " vm.stats.vm.v_free_count " , & mfree , & len , nullptr , 0 ) < 0 ) {
ERR_PRINT ( vformat ( " Could not get vm.stats.vm.v_free_count, error code: %d - %s " , errno , strerror ( errno ) ) ) ;
}
uint64_t stotal = 0 ;
uint64_t sused = 0 ;
char errmsg [ _POSIX2_LINE_MAX ] = { } ;
kvm_t * kd = kvm_openfiles ( nullptr , " /dev/null " , nullptr , 0 , errmsg ) ;
if ( kd = = nullptr ) {
ERR_PRINT ( vformat ( " kvm_openfiles failed, error: %s " , errmsg ) ) ;
} else {
struct kvm_swap swap_info [ 32 ] ;
int count = kvm_getswapinfo ( kd , swap_info , 32 , 0 ) ;
for ( int i = 0 ; i < count ; i + + ) {
stotal + = swap_info [ i ] . ksw_total ;
sused + = swap_info [ i ] . ksw_used ;
}
kvm_close ( kd ) ;
}
if ( mtotal * pagesize ! = 0 ) {
meminfo [ " physical " ] = mtotal * pagesize ;
}
if ( mfree * pagesize ! = 0 ) {
meminfo [ " free " ] = mfree * pagesize ;
}
if ( ( mfree + stotal - sused ) * pagesize ! = 0 ) {
meminfo [ " available " ] = ( mfree + stotal - sused ) * pagesize ;
}
# elif defined(__OpenBSD__)
int pagesize = sysconf ( _SC_PAGESIZE ) ;
const int mib [ ] = { CTL_VM , VM_UVMEXP } ;
uvmexp uvmexp_info ;
size_t len = sizeof ( uvmexp_info ) ;
if ( sysctl ( mib , 2 , & uvmexp_info , & len , nullptr , 0 ) < 0 ) {
ERR_PRINT ( vformat ( " Could not get CTL_VM, VM_UVMEXP, error code: %d - %s " , errno , strerror ( errno ) ) ) ;
}
uint64_t stotal = 0 ;
uint64_t sused = 0 ;
int count = swapctl ( SWAP_NSWAP , 0 , 0 ) ;
if ( count > 0 ) {
swapent swap_info [ count ] ;
count = swapctl ( SWAP_STATS , swap_info , count ) ;
for ( int i = 0 ; i < count ; i + + ) {
if ( swap_info [ i ] . se_flags & SWF_ENABLE ) {
sused + = swap_info [ i ] . se_inuse ;
stotal + = swap_info [ i ] . se_nblks ;
}
}
}
if ( uvmexp_info . npages * pagesize ! = 0 ) {
meminfo [ " physical " ] = uvmexp_info . npages * pagesize ;
}
if ( uvmexp_info . free * pagesize ! = 0 ) {
meminfo [ " free " ] = uvmexp_info . free * pagesize ;
}
if ( ( uvmexp_info . free * pagesize ) + ( stotal - sused ) * DEV_BSIZE ! = 0 ) {
meminfo [ " available " ] = ( uvmexp_info . free * pagesize ) + ( stotal - sused ) * DEV_BSIZE ;
}
# elif defined(__NetBSD__)
int pagesize = sysconf ( _SC_PAGESIZE ) ;
const int mib [ ] = { CTL_VM , VM_UVMEXP2 } ;
uvmexp_sysctl uvmexp_info ;
size_t len = sizeof ( uvmexp_info ) ;
if ( sysctl ( mib , 2 , & uvmexp_info , & len , nullptr , 0 ) < 0 ) {
ERR_PRINT ( vformat ( " Could not get CTL_VM, VM_UVMEXP2, error code: %d - %s " , errno , strerror ( errno ) ) ) ;
}
if ( uvmexp_info . npages * pagesize ! = 0 ) {
meminfo [ " physical " ] = uvmexp_info . npages * pagesize ;
}
if ( uvmexp_info . free * pagesize ! = 0 ) {
meminfo [ " free " ] = uvmexp_info . free * pagesize ;
}
if ( ( uvmexp_info . free + uvmexp_info . swpages - uvmexp_info . swpginuse ) * pagesize ! = 0 ) {
meminfo [ " available " ] = ( uvmexp_info . free + uvmexp_info . swpages - uvmexp_info . swpginuse ) * pagesize ;
}
# else
Error err ;
Ref < FileAccess > f = FileAccess : : open ( " /proc/meminfo " , FileAccess : : READ , & err ) ;
uint64_t mtotal = 0 ;
uint64_t mfree = 0 ;
uint64_t sfree = 0 ;
while ( f . is_valid ( ) & & ! f - > eof_reached ( ) ) {
String s = f - > get_line ( ) . strip_edges ( ) ;
if ( s . begins_with ( " MemTotal: " ) ) {
Vector < String > stok = s . replace ( " MemTotal: " , " " ) . strip_edges ( ) . split ( " " ) ;
if ( stok . size ( ) = = 2 ) {
mtotal = stok [ 0 ] . to_int ( ) * 1024 ;
}
}
if ( s . begins_with ( " MemFree: " ) ) {
Vector < String > stok = s . replace ( " MemFree: " , " " ) . strip_edges ( ) . split ( " " ) ;
if ( stok . size ( ) = = 2 ) {
mfree = stok [ 0 ] . to_int ( ) * 1024 ;
}
}
if ( s . begins_with ( " SwapFree: " ) ) {
Vector < String > stok = s . replace ( " SwapFree: " , " " ) . strip_edges ( ) . split ( " " ) ;
if ( stok . size ( ) = = 2 ) {
sfree = stok [ 0 ] . to_int ( ) * 1024 ;
}
}
}
if ( mtotal ! = 0 ) {
meminfo [ " physical " ] = mtotal ;
}
if ( mfree ! = 0 ) {
meminfo [ " free " ] = mfree ;
}
if ( mfree + sfree ! = 0 ) {
meminfo [ " available " ] = mfree + sfree ;
}
# endif
rlimit stackinfo = { } ;
getrlimit ( RLIMIT_STACK , & stackinfo ) ;
if ( stackinfo . rlim_cur ! = 0 ) {
meminfo [ " stack " ] = ( int64_t ) stackinfo . rlim_cur ;
}
return meminfo ;
}
2023-10-19 13:35:10 +02:00
Dictionary OS_Unix : : execute_with_pipe ( const String & p_path , const List < String > & p_arguments ) {
# define CLEAN_PIPES \
if ( pipe_in [ 0 ] > = 0 ) { \
: : close ( pipe_in [ 0 ] ) ; \
} \
if ( pipe_in [ 1 ] > = 0 ) { \
: : close ( pipe_in [ 1 ] ) ; \
} \
if ( pipe_out [ 0 ] > = 0 ) { \
: : close ( pipe_out [ 0 ] ) ; \
} \
if ( pipe_out [ 1 ] > = 0 ) { \
: : close ( pipe_out [ 1 ] ) ; \
} \
if ( pipe_err [ 0 ] > = 0 ) { \
: : close ( pipe_err [ 0 ] ) ; \
} \
if ( pipe_err [ 1 ] > = 0 ) { \
: : close ( pipe_err [ 1 ] ) ; \
}
Dictionary ret ;
# ifdef __EMSCRIPTEN__
// Don't compile this code at all to avoid undefined references.
// Actual virtual call goes to OS_Web.
ERR_FAIL_V ( ret ) ;
# else
// Create pipes.
int pipe_in [ 2 ] = { - 1 , - 1 } ;
int pipe_out [ 2 ] = { - 1 , - 1 } ;
int pipe_err [ 2 ] = { - 1 , - 1 } ;
ERR_FAIL_COND_V ( pipe ( pipe_in ) ! = 0 , ret ) ;
if ( pipe ( pipe_out ) ! = 0 ) {
CLEAN_PIPES
ERR_FAIL_V ( ret ) ;
}
if ( pipe ( pipe_err ) ! = 0 ) {
CLEAN_PIPES
ERR_FAIL_V ( ret ) ;
}
// Create process.
pid_t pid = fork ( ) ;
if ( pid < 0 ) {
CLEAN_PIPES
ERR_FAIL_V ( ret ) ;
}
if ( pid = = 0 ) {
// The child process.
Vector < CharString > cs ;
cs . push_back ( p_path . utf8 ( ) ) ;
2024-04-15 15:18:34 +02:00
for ( const String & arg : p_arguments ) {
cs . push_back ( arg . utf8 ( ) ) ;
2023-10-19 13:35:10 +02:00
}
Vector < char * > args ;
for ( int i = 0 ; i < cs . size ( ) ; i + + ) {
args . push_back ( ( char * ) cs [ i ] . get_data ( ) ) ;
}
args . push_back ( 0 ) ;
: : close ( STDIN_FILENO ) ;
: : dup2 ( pipe_in [ 0 ] , STDIN_FILENO ) ;
: : close ( STDOUT_FILENO ) ;
: : dup2 ( pipe_out [ 1 ] , STDOUT_FILENO ) ;
: : close ( STDERR_FILENO ) ;
: : dup2 ( pipe_err [ 1 ] , STDERR_FILENO ) ;
CLEAN_PIPES
execvp ( p_path . utf8 ( ) . get_data ( ) , & args [ 0 ] ) ;
// The execvp() function only returns if an error occurs.
ERR_PRINT ( " Could not create child process: " + p_path ) ;
raise ( SIGKILL ) ;
}
: : close ( pipe_in [ 0 ] ) ;
: : close ( pipe_out [ 1 ] ) ;
: : close ( pipe_err [ 1 ] ) ;
Ref < FileAccessUnixPipe > main_pipe ;
main_pipe . instantiate ( ) ;
main_pipe - > open_existing ( pipe_out [ 0 ] , pipe_in [ 1 ] ) ;
Ref < FileAccessUnixPipe > err_pipe ;
err_pipe . instantiate ( ) ;
err_pipe - > open_existing ( pipe_err [ 0 ] , 0 ) ;
2024-04-07 20:19:46 +02:00
ProcessInfo pi ;
process_map_mutex . lock ( ) ;
process_map - > insert ( pid , pi ) ;
process_map_mutex . unlock ( ) ;
2023-10-19 13:35:10 +02:00
ret [ " stdio " ] = main_pipe ;
ret [ " stderr " ] = err_pipe ;
ret [ " pid " ] = pid ;
# undef CLEAN_PIPES
return ret ;
# endif
}
2021-12-16 14:00:55 +01:00
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 ) {
2018-10-29 21:15:15 +01:00
# ifdef __EMSCRIPTEN__
// Don't compile this code at all to avoid undefined references.
2022-08-28 20:27:45 +02:00
// Actual virtual call goes to OS_Web.
2018-10-29 21:15:15 +01:00
ERR_FAIL_V ( ERR_BUG ) ;
# else
2020-12-18 19:49:13 +01:00
if ( r_pipe ) {
String command = " \" " + p_path + " \" " ;
2024-04-15 15:18:34 +02:00
for ( const String & arg : p_arguments ) {
command + = String ( " \" " ) + arg + " \" " ;
2014-02-10 02:10:30 +01:00
}
2017-10-03 20:09:04 +02:00
if ( read_stderr ) {
2020-12-18 19:49:13 +01:00
command + = " 2>&1 " ; // Include stderr
2017-10-03 20:09:04 +02:00
} else {
2020-12-18 19:49:13 +01:00
command + = " 2>/dev/null " ; // Silence stderr
2017-10-03 20:09:04 +02:00
}
2014-02-10 02:10:30 +01:00
2020-12-18 19:49:13 +01:00
FILE * f = popen ( command . utf8 ( ) . get_data ( ) , " r " ) ;
2023-09-09 17:46:44 +02:00
ERR_FAIL_NULL_V_MSG ( f , ERR_CANT_OPEN , " Cannot create pipe from command: " + command + " . " ) ;
2014-02-10 02:10:30 +01:00
char buf [ 65535 ] ;
while ( fgets ( buf , 65535 , f ) ) {
2019-04-07 20:46:52 +02:00
if ( p_pipe_mutex ) {
p_pipe_mutex - > lock ( ) ;
}
2022-07-05 14:18:29 +02:00
String pipe_out ;
if ( pipe_out . parse_utf8 ( buf ) = = OK ) {
( * r_pipe ) + = pipe_out ;
} else {
( * r_pipe ) + = String ( buf ) ; // If not valid UTF-8 try decode as Latin-1
}
2019-04-07 20:46:52 +02:00
if ( p_pipe_mutex ) {
p_pipe_mutex - > unlock ( ) ;
}
2014-02-10 02:10:30 +01:00
}
int rv = pclose ( f ) ;
2020-12-18 19:49:13 +01:00
2020-05-14 16:41:43 +02:00
if ( r_exitcode ) {
2019-09-07 19:39:24 +02:00
* r_exitcode = WEXITSTATUS ( rv ) ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
return OK ;
}
pid_t pid = fork ( ) ;
ERR_FAIL_COND_V ( pid < 0 , ERR_CANT_FORK ) ;
if ( pid = = 0 ) {
2020-12-18 19:49:13 +01:00
// The child process
2021-01-13 10:38:02 +01:00
Vector < CharString > cs ;
cs . push_back ( p_path . utf8 ( ) ) ;
2024-04-15 15:18:34 +02:00
for ( const String & arg : p_arguments ) {
cs . push_back ( arg . utf8 ( ) ) ;
2021-01-13 10:38:02 +01:00
}
Vector < char * > args ;
for ( int i = 0 ; i < cs . size ( ) ; i + + ) {
args . push_back ( ( char * ) cs [ i ] . get_data ( ) ) ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
args . push_back ( 0 ) ;
2017-10-11 01:03:05 +02:00
execvp ( p_path . utf8 ( ) . get_data ( ) , & args [ 0 ] ) ;
2020-12-18 19:49:13 +01:00
// The execvp() function only returns if an error occurs.
2021-01-13 11:06:52 +01:00
ERR_PRINT ( " Could not create child process: " + p_path ) ;
raise ( SIGKILL ) ;
2014-02-10 02:10:30 +01:00
}
2020-12-18 19:49:13 +01:00
int status ;
waitpid ( pid , & status , 0 ) ;
if ( r_exitcode ) {
* r_exitcode = WIFEXITED ( status ) ? WEXITSTATUS ( status ) : status ;
}
return OK ;
# endif
}
2019-04-07 20:46:52 +02:00
2021-12-16 14:00:55 +01:00
Error OS_Unix : : create_process ( const String & p_path , const List < String > & p_arguments , ProcessID * r_child_id , bool p_open_console ) {
2020-12-18 19:49:13 +01:00
# ifdef __EMSCRIPTEN__
// Don't compile this code at all to avoid undefined references.
2022-08-28 20:27:45 +02:00
// Actual virtual call goes to OS_Web.
2020-12-18 19:49:13 +01:00
ERR_FAIL_V ( ERR_BUG ) ;
# else
pid_t pid = fork ( ) ;
ERR_FAIL_COND_V ( pid < 0 , ERR_CANT_FORK ) ;
if ( pid = = 0 ) {
// The new process
// Create a new session-ID so parent won't wait for it.
// This ensures the process won't go zombie at the end.
setsid ( ) ;
2021-01-13 10:38:02 +01:00
Vector < CharString > cs ;
cs . push_back ( p_path . utf8 ( ) ) ;
2024-04-15 15:18:34 +02:00
for ( const String & arg : p_arguments ) {
cs . push_back ( arg . utf8 ( ) ) ;
2021-01-13 10:38:02 +01:00
}
Vector < char * > args ;
for ( int i = 0 ; i < cs . size ( ) ; i + + ) {
args . push_back ( ( char * ) cs [ i ] . get_data ( ) ) ;
2020-05-14 16:41:43 +02:00
}
2020-12-18 19:49:13 +01:00
args . push_back ( 0 ) ;
execvp ( p_path . utf8 ( ) . get_data ( ) , & args [ 0 ] ) ;
// The execvp() function only returns if an error occurs.
2021-01-13 11:06:52 +01:00
ERR_PRINT ( " Could not create child process: " + p_path ) ;
raise ( SIGKILL ) ;
2014-02-10 02:10:30 +01:00
}
2024-04-07 20:19:46 +02:00
ProcessInfo pi ;
process_map_mutex . lock ( ) ;
process_map - > insert ( pid , pi ) ;
process_map_mutex . unlock ( ) ;
2020-12-18 19:49:13 +01:00
if ( r_child_id ) {
* r_child_id = pid ;
}
2014-02-10 02:10:30 +01:00
return OK ;
2018-10-29 21:15:15 +01:00
# endif
2014-02-10 02:10:30 +01:00
}
2018-08-27 17:32:43 +02:00
Error OS_Unix : : kill ( const ProcessID & p_pid ) {
2014-02-10 02:10:30 +01:00
int ret = : : kill ( p_pid , SIGKILL ) ;
2014-12-17 02:31:57 +01:00
if ( ! ret ) {
//avoid zombie process
int st ;
: : waitpid ( p_pid , & st , 0 ) ;
}
2014-02-10 02:10:30 +01:00
return ret ? ERR_INVALID_PARAMETER : OK ;
}
2017-08-07 12:17:31 +02:00
int OS_Unix : : get_process_id ( ) const {
2014-04-05 17:39:30 +02:00
return getpid ( ) ;
2022-02-16 13:56:32 +01:00
}
2014-04-05 17:39:30 +02:00
2021-08-13 05:36:23 +02:00
bool OS_Unix : : is_process_running ( const ProcessID & p_pid ) const {
2024-04-07 20:19:46 +02:00
MutexLock lock ( process_map_mutex ) ;
const ProcessInfo * pi = process_map - > getptr ( p_pid ) ;
if ( pi & & ! pi - > is_running ) {
return false ;
}
2021-08-13 05:36:23 +02:00
int status = 0 ;
if ( waitpid ( p_pid , & status , WNOHANG ) ! = 0 ) {
2024-04-07 20:19:46 +02:00
if ( pi ) {
pi - > is_running = false ;
pi - > exit_code = status ;
}
2021-08-13 05:36:23 +02:00
return false ;
}
return true ;
}
2024-04-07 20:19:46 +02:00
int OS_Unix : : get_process_exit_code ( const ProcessID & p_pid ) const {
MutexLock lock ( process_map_mutex ) ;
const ProcessInfo * pi = process_map - > getptr ( p_pid ) ;
if ( pi & & ! pi - > is_running ) {
return pi - > exit_code ;
}
int status = 0 ;
if ( waitpid ( p_pid , & status , WNOHANG ) ! = 0 ) {
status = WIFEXITED ( status ) ? WEXITSTATUS ( status ) : status ;
if ( pi ) {
pi - > is_running = false ;
pi - > exit_code = status ;
}
return status ;
}
return - 1 ;
}
2014-02-10 02:10:30 +01:00
String OS_Unix : : get_locale ( ) const {
2020-05-14 16:41:43 +02:00
if ( ! has_environment ( " LANG " ) ) {
2014-02-10 02:10:30 +01:00
return " en " ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
String locale = get_environment ( " LANG " ) ;
int tp = locale . find ( " . " ) ;
2020-05-14 16:41:43 +02:00
if ( tp ! = - 1 ) {
2014-02-10 02:10:30 +01:00
locale = locale . substr ( 0 , tp ) ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
return locale ;
}
2024-04-19 16:14:05 +02:00
Error OS_Unix : : open_dynamic_library ( const String & p_path , void * & p_library_handle , GDExtensionData * p_data ) {
2018-01-04 19:42:29 +01:00
String path = p_path ;
2021-08-30 01:43:47 +02:00
if ( FileAccess : : exists ( path ) & & path . is_relative_path ( ) ) {
2018-04-28 14:36:35 +02:00
// dlopen expects a slash, in this case a leading ./ for it to be interpreted as a relative path,
// otherwise it will end up searching various system directories for the lib instead and finally failing.
path = " ./ " + path ;
}
2018-01-04 19:42:29 +01:00
if ( ! FileAccess : : exists ( path ) ) {
2022-03-09 13:56:14 +01:00
// This code exists so GDExtension can load .so files from within the executable path.
2022-08-30 02:34:01 +02:00
path = get_executable_path ( ) . get_base_dir ( ) . path_join ( p_path . get_file ( ) ) ;
2018-01-04 19:42:29 +01:00
}
if ( ! FileAccess : : exists ( path ) ) {
2022-03-09 13:56:14 +01:00
// This code exists so GDExtension can load .so files from a standard unix location.
2022-08-30 02:34:01 +02:00
path = get_executable_path ( ) . get_base_dir ( ) . path_join ( " ../lib " ) . path_join ( p_path . get_file ( ) ) ;
2018-01-04 19:42:29 +01:00
}
2024-01-01 12:48:28 +01:00
ERR_FAIL_COND_V ( ! FileAccess : : exists ( path ) , ERR_FILE_NOT_FOUND ) ;
2023-10-30 22:14:59 +01:00
p_library_handle = dlopen ( path . utf8 ( ) . get_data ( ) , GODOT_DLOPEN_MODE ) ;
2023-09-09 17:46:44 +02:00
ERR_FAIL_NULL_V_MSG ( p_library_handle , ERR_CANT_OPEN , vformat ( " Can't open dynamic library: %s. Error: %s. " , p_path , dlerror ( ) ) ) ;
2022-04-29 00:51:04 +02:00
2024-04-19 16:14:05 +02:00
if ( p_data ! = nullptr & & p_data - > r_resolved_path ! = nullptr ) {
* p_data - > r_resolved_path = path ;
2022-04-29 00:51:04 +02:00
}
2017-03-08 02:50:13 +01:00
return OK ;
}
2017-03-08 03:59:12 +01:00
Error OS_Unix : : close_dynamic_library ( void * p_library_handle ) {
if ( dlclose ( p_library_handle ) ) {
2017-03-08 02:50:13 +01:00
return FAILED ;
}
return OK ;
}
2024-01-09 02:36:19 +01:00
Error OS_Unix : : get_dynamic_library_symbol_handle ( void * p_library_handle , const String & p_name , void * & p_symbol_handle , bool p_optional ) {
2017-04-06 14:00:54 +02:00
const char * error ;
2017-03-08 02:50:13 +01:00
dlerror ( ) ; // Clear existing errors
p_symbol_handle = dlsym ( p_library_handle , p_name . utf8 ( ) . get_data ( ) ) ;
2017-03-08 03:59:12 +01:00
error = dlerror ( ) ;
2020-04-02 01:20:12 +02:00
if ( error ! = nullptr ) {
2019-08-15 04:57:49 +02:00
ERR_FAIL_COND_V_MSG ( ! p_optional , ERR_CANT_RESOLVE , " Can't resolve symbol " + p_name + " . Error: " + error + " . " ) ;
return ERR_CANT_RESOLVE ;
2017-03-08 02:50:13 +01:00
}
return OK ;
}
2014-02-10 02:10:30 +01:00
Error OS_Unix : : set_cwd ( const String & p_cwd ) {
2020-05-14 16:41:43 +02:00
if ( chdir ( p_cwd . utf8 ( ) . get_data ( ) ) ! = 0 ) {
2014-02-10 02:10:30 +01:00
return ERR_CANT_OPEN ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
return OK ;
}
2023-01-16 14:26:14 +01:00
bool OS_Unix : : has_environment ( const String & p_var ) const {
return getenv ( p_var . utf8 ( ) . get_data ( ) ) ! = nullptr ;
}
2014-02-10 02:10:30 +01:00
String OS_Unix : : get_environment ( const String & p_var ) const {
2024-01-07 18:53:43 +01:00
const char * val = getenv ( p_var . utf8 ( ) . get_data ( ) ) ;
if ( val = = nullptr ) { // Not set; return empty string
return " " ;
2020-05-14 16:41:43 +02:00
}
2024-01-07 18:53:43 +01:00
String s ;
if ( s . parse_utf8 ( val ) = = OK ) {
return s ;
}
return String ( val ) ; // Not valid UTF-8, so return as-is
2014-02-10 02:10:30 +01:00
}
2023-01-16 14:26:14 +01:00
void OS_Unix : : set_environment ( const String & p_var , const String & p_value ) const {
ERR_FAIL_COND_MSG ( p_var . is_empty ( ) | | p_var . contains ( " = " ) , vformat ( " Invalid environment variable name '%s', cannot be empty or include '='. " , p_var ) ) ;
int err = setenv ( p_var . utf8 ( ) . get_data ( ) , p_value . utf8 ( ) . get_data ( ) , /* overwrite: */ 1 ) ;
ERR_FAIL_COND_MSG ( err ! = 0 , vformat ( " Failed setting environment variable '%s', the system is out of memory. " , p_var ) ) ;
}
void OS_Unix : : unset_environment ( const String & p_var ) const {
ERR_FAIL_COND_MSG ( p_var . is_empty ( ) | | p_var . contains ( " = " ) , vformat ( " Invalid environment variable name '%s', cannot be empty or include '='. " , p_var ) ) ;
unsetenv ( p_var . utf8 ( ) . get_data ( ) ) ;
2019-01-29 22:59:38 +01:00
}
2017-11-17 15:25:22 +01:00
String OS_Unix : : get_user_data_dir ( ) const {
2022-10-18 16:43:37 +02:00
String appname = get_safe_dir_name ( GLOBAL_GET ( " application/config/name " ) ) ;
2021-12-09 10:42:46 +01:00
if ( ! appname . is_empty ( ) ) {
2022-10-18 16:43:37 +02:00
bool use_custom_dir = GLOBAL_GET ( " application/config/use_custom_user_dir " ) ;
2017-11-26 19:00:53 +01:00
if ( use_custom_dir ) {
2022-10-18 16:43:37 +02:00
String custom_dir = get_safe_dir_name ( GLOBAL_GET ( " application/config/custom_user_dir_name " ) , true ) ;
2021-12-09 10:42:46 +01:00
if ( custom_dir . is_empty ( ) ) {
2017-11-26 19:00:53 +01:00
custom_dir = appname ;
}
2022-08-30 02:34:01 +02:00
return get_data_path ( ) . path_join ( custom_dir ) ;
Add initial support for the XDG Base Directory spec
Spec version 0.7 from https://standards.freedesktop.org/basedir-spec/basedir-spec-0.7.html
(latest as of this commit).
Three virtual methods are added to OS for the various XDG paths we will use:
- OS::get_data_path gives XDG_DATA_HOME, or if missing:
~/.local/share on X11, ~/Library/Application Support/ on macOS and %APPDATA% on Windows
- OS::get_config_path gives XDG_CONFIG_HOME, or if missing:
~/.config on X11, ~/Library/Application Support/ on macOS and %APPDATA% on Windows
- OS::get_cache_path gives XDG_CACHE_HOME, or if missing:
~/.cache on X11, ~/Library/Caches on macOS and %APPDATA% on Windows
So for Windows there are no changes, for Linux we follow the full split spec
and for macOS stuff will move from ~/.godot to ~/Library/Application Support/Godot.
Support for system-wide installation of templates on Unix was removed for now,
as it's a bit hackish and I don't think anyone uses it.
user:// will still be OS::get_data_path() + "/godot/app_userdata/$name" by
default, but when using the application/config/use_shared_user_dir option
it will now use XDG_DATA_HOME/$name, e.g. ~/.local/share/MyGame.
For now everything still goes in EditorSettings::get_settings_dir(), but
this will be changed in a later commit to make use of the new splitting
where relevant.
Part of #3513.
2017-11-17 17:11:41 +01:00
} else {
2022-08-30 02:34:01 +02:00
return get_data_path ( ) . path_join ( get_godot_dir_name ( ) ) . path_join ( " app_userdata " ) . path_join ( appname ) ;
2014-02-10 02:10:30 +01:00
}
}
2022-08-30 02:34:01 +02:00
return get_data_path ( ) . path_join ( get_godot_dir_name ( ) ) . path_join ( " app_userdata " ) . path_join ( " [unnamed project] " ) ;
2017-02-06 04:38:39 +01:00
}
2014-02-10 02:10:30 +01:00
String OS_Unix : : get_executable_path ( ) const {
# ifdef __linux__
//fix for running from a symlink
char buf [ 256 ] ;
memset ( buf , 0 , 256 ) ;
2018-10-02 20:42:57 +02:00
ssize_t len = readlink ( " /proc/self/exe " , buf , sizeof ( buf ) ) ;
2014-02-15 06:01:39 +01:00
String b ;
2018-10-02 20:42:57 +02:00
if ( len > 0 ) {
b . parse_utf8 ( buf , len ) ;
}
2021-12-09 10:42:46 +01:00
if ( b . is_empty ( ) ) {
2014-02-10 02:10:30 +01:00
WARN_PRINT ( " Couldn't get executable path from /proc/self/exe, using argv[0] " ) ;
return OS : : get_executable_path ( ) ;
}
return b ;
2023-11-05 04:29:50 +01:00
# elif defined(__OpenBSD__)
2015-01-27 10:01:37 +01:00
char resolved_path [ MAXPATHLEN ] ;
realpath ( OS : : get_executable_path ( ) . utf8 ( ) . get_data ( ) , resolved_path ) ;
2023-11-05 04:29:50 +01:00
return String ( resolved_path ) ;
# elif defined(__NetBSD__)
int mib [ 4 ] = { CTL_KERN , KERN_PROC_ARGS , - 1 , KERN_PROC_PATHNAME } ;
char buf [ MAXPATHLEN ] ;
size_t len = sizeof ( buf ) ;
if ( sysctl ( mib , 4 , buf , & len , nullptr , 0 ) ! = 0 ) {
WARN_PRINT ( " Couldn't get executable path from sysctl " ) ;
return OS : : get_executable_path ( ) ;
}
// 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:
char resolved_path [ MAXPATHLEN ] ;
realpath ( buf , resolved_path ) ;
2015-01-27 10:01:37 +01:00
return String ( resolved_path ) ;
2018-01-08 01:52:24 +01:00
# elif defined(__FreeBSD__)
int mib [ 4 ] = { CTL_KERN , KERN_PROC , KERN_PROC_PATHNAME , - 1 } ;
char buf [ MAXPATHLEN ] ;
size_t len = sizeof ( buf ) ;
2020-04-02 01:20:12 +02:00
if ( sysctl ( mib , 4 , buf , & len , nullptr , 0 ) ! = 0 ) {
2018-01-08 01:52:24 +01:00
WARN_PRINT ( " Couldn't get executable path from sysctl " ) ;
return OS : : get_executable_path ( ) ;
}
String b ;
b . parse_utf8 ( buf ) ;
return b ;
2016-04-20 09:49:48 +02:00
# elif defined(__APPLE__)
char temp_path [ 1 ] ;
uint32_t buff_size = 1 ;
_NSGetExecutablePath ( temp_path , & buff_size ) ;
char * resolved_path = new char [ buff_size + 1 ] ;
2022-02-16 13:56:32 +01:00
if ( _NSGetExecutablePath ( resolved_path , & buff_size ) = = 1 ) {
2016-04-20 09:49:48 +02:00
WARN_PRINT ( " MAXPATHLEN is too small " ) ;
2022-02-16 13:56:32 +01:00
}
2016-04-20 09:49:48 +02:00
2022-11-16 17:55:37 +01:00
String path = String : : utf8 ( resolved_path ) ;
2016-04-20 09:49:48 +02:00
delete [ ] resolved_path ;
return path ;
2014-02-10 02:10:30 +01:00
# else
ERR_PRINT ( " Warning, don't know how to obtain executable path on this OS! Please override this function properly. " ) ;
return OS : : get_executable_path ( ) ;
# endif
}
2021-09-22 17:36:40 +02:00
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 ) {
2017-09-22 07:56:02 +02:00
if ( ! should_log ( true ) ) {
return ;
}
const char * err_details ;
2020-05-14 16:41:43 +02:00
if ( p_rationale & & p_rationale [ 0 ] ) {
2017-09-22 07:56:02 +02:00
err_details = p_rationale ;
2020-05-14 16:41:43 +02:00
} else {
2017-09-22 07:56:02 +02:00
err_details = p_code ;
2020-05-14 16:41:43 +02:00
}
2017-09-22 07:56:02 +02:00
2020-01-04 04:01:57 +01:00
// Disable color codes if stdout is not a TTY.
// This prevents Godot from writing ANSI escape codes when redirecting
// stdout and stderr to a file.
const bool tty = isatty ( fileno ( stdout ) ) ;
2020-01-18 23:26:28 +01:00
const char * gray = tty ? " \ E[0;90m " : " " ;
const char * red = tty ? " \ E[0;91m " : " " ;
2020-01-04 04:01:57 +01:00
const char * red_bold = tty ? " \ E[1;31m " : " " ;
2020-01-18 23:26:28 +01:00
const char * yellow = tty ? " \ E[0;93m " : " " ;
2020-01-04 04:01:57 +01:00
const char * yellow_bold = tty ? " \ E[1;33m " : " " ;
2020-01-18 23:26:28 +01:00
const char * magenta = tty ? " \ E[0;95m " : " " ;
2020-01-04 04:01:57 +01:00
const char * magenta_bold = tty ? " \ E[1;35m " : " " ;
2020-01-18 23:26:28 +01:00
const char * cyan = tty ? " \ E[0;96m " : " " ;
2020-01-04 04:01:57 +01:00
const char * cyan_bold = tty ? " \ E[1;36m " : " " ;
const char * reset = tty ? " \ E[0m " : " " ;
2017-09-22 07:56:02 +02:00
switch ( p_type ) {
case ERR_WARNING :
2020-01-18 23:26:28 +01:00
logf_error ( " %sWARNING:%s %s \n " , yellow_bold , yellow , err_details ) ;
logf_error ( " %s at: %s (%s:%i)%s \n " , gray , p_function , p_file , p_line , reset ) ;
2017-09-22 07:56:02 +02:00
break ;
case ERR_SCRIPT :
2020-01-18 23:26:28 +01:00
logf_error ( " %sSCRIPT ERROR:%s %s \n " , magenta_bold , magenta , err_details ) ;
logf_error ( " %s at: %s (%s:%i)%s \n " , gray , p_function , p_file , p_line , reset ) ;
2017-09-22 07:56:02 +02:00
break ;
case ERR_SHADER :
2020-01-18 23:26:28 +01:00
logf_error ( " %sSHADER ERROR:%s %s \n " , cyan_bold , cyan , err_details ) ;
logf_error ( " %s at: %s (%s:%i)%s \n " , gray , p_function , p_file , p_line , reset ) ;
2017-09-22 07:56:02 +02:00
break ;
case ERR_ERROR :
default :
2020-01-18 23:26:28 +01:00
logf_error ( " %sERROR:%s %s \n " , red_bold , red , err_details ) ;
logf_error ( " %s at: %s (%s:%i)%s \n " , gray , p_function , p_file , p_line , reset ) ;
2017-09-22 07:56:02 +02:00
break ;
}
}
UnixTerminalLogger : : ~ UnixTerminalLogger ( ) { }
2017-11-21 10:35:01 +01:00
OS_Unix : : OS_Unix ( ) {
Vector < Logger * > loggers ;
loggers . push_back ( memnew ( UnixTerminalLogger ) ) ;
_set_logger ( memnew ( CompositeLogger ( loggers ) ) ) ;
}
2014-02-10 02:10:30 +01:00
# endif