/*
* %CopyrightBegin%
*
* Copyright Ericsson AB 1996-2016. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* %CopyrightEnd%
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef ISC32
#define _POSIX_SOURCE
#define _XOPEN_SOURCE
#endif
#include <sys/times.h> /* ! */
#include <time.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/uio.h>
#include <termios.h>
#include <ctype.h>
#include <sys/utsname.h>
#include <sys/select.h>
#ifdef ISC32
#include <sys/bsdtypes.h>
#endif
#include <termios.h>
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#define ERTS_WANT_BREAK_HANDLING
#define ERTS_WANT_GOT_SIGUSR1
#define WANT_NONBLOCKING /* must define this to pull in defs from sys.h */
#include "sys.h"
#include "erl_thr_progress.h"
#if defined(__APPLE__) && defined(__MACH__) && !defined(__DARWIN__)
#define __DARWIN__ 1
#endif
#ifdef USE_THREADS
#include "erl_threads.h"
#endif
#include "erl_mseg.h"
extern char **environ;
erts_smp_rwmtx_t environ_rwmtx;
#define MAX_VSIZE 16 /* Max number of entries allowed in an I/O
* vector sock_sendv().
*/
/*
* Don't need global.h, but bif_table.h (included by bif.h),
* won't compile otherwise
*/
#include "global.h"
#include "bif.h"
#include "erl_check_io.h"
#include "erl_cpu_topology.h"
extern int driver_interrupt(int, int);
extern void do_break(void);
extern void erl_sys_args(int*, char**);
/* The following two defs should probably be moved somewhere else */
extern void erts_sys_init_float(void);
extern void erl_crash_dump(char* file, int line, char* fmt, ...);
#ifdef DEBUG
static int debug_log = 0;
#endif
#ifdef ERTS_SMP
erts_smp_atomic32_t erts_got_sigusr1;
#define ERTS_SET_GOT_SIGUSR1 \
erts_smp_atomic32_set_mb(&erts_got_sigusr1, 1)
#define ERTS_UNSET_GOT_SIGUSR1 \
erts_smp_atomic32_set_mb(&erts_got_sigusr1, 0)
static erts_smp_atomic32_t have_prepared_crash_dump;
#define ERTS_PREPARED_CRASH_DUMP \
((int) erts_smp_atomic32_xchg_nob(&have_prepared_crash_dump, 1))
#else
volatile int erts_got_sigusr1;
#define ERTS_SET_GOT_SIGUSR1 (erts_got_sigusr1 = 1)
#define ERTS_UNSET_GOT_SIGUSR1 (erts_got_sigusr1 = 0)
static volatile int have_prepared_crash_dump;
#define ERTS_PREPARED_CRASH_DUMP \
(have_prepared_crash_dump++)
#endif
erts_smp_atomic_t sys_misc_mem_sz;
#if defined(ERTS_SMP)
static void smp_sig_notify(char c);
static int sig_notify_fds[2] = {-1, -1};
#if !defined(ETHR_UNUSABLE_SIGUSRX) && defined(ERTS_THR_HAVE_SIG_FUNCS)
static int sig_suspend_fds[2] = {-1, -1};
#define ERTS_SYS_SUSPEND_SIGNAL SIGUSR2
#endif
#endif
jmp_buf erts_sys_sigsegv_jmp;
static int crashdump_companion_cube_fd = -1;
/********************* General functions ****************************/
/* This is used by both the drivers and general I/O, must be set early */
static int max_files = -1;
/*
* a few variables used by the break handler
*/
#ifdef ERTS_SMP
erts_smp_atomic32_t erts_break_requested;
#define ERTS_SET_BREAK_REQUESTED \
erts_smp_atomic32_set_nob(&erts_break_requested, (erts_aint32_t) 1)
#define ERTS_UNSET_BREAK_REQUESTED \
erts_smp_atomic32_set_nob(&erts_break_requested, (erts_aint32_t) 0)
#else
volatile int erts_break_requested = 0;
#define ERTS_SET_BREAK_REQUESTED (erts_break_requested = 1)
#define ERTS_UNSET_BREAK_REQUESTED (erts_break_requested = 0)
#endif
/* set early so the break handler has access to initial mode */
static struct termios initial_tty_mode;
static int replace_intr = 0;
/* assume yes initially, ttsl_init will clear it */
int using_oldshell = 1;
#ifdef ERTS_ENABLE_KERNEL_POLL
int erts_use_kernel_poll = 0;
struct {
int (*select)(ErlDrvPort, ErlDrvEvent, int, int);
int (*event)(ErlDrvPort, ErlDrvEvent, ErlDrvEventData);
void (*check_io_as_interrupt)(void);
void (*check_io_interrupt)(int);
void (*check_io_interrupt_tmd)(int, ErtsMonotonicTime);
void (*check_io)(int);
Uint (*size)(void);
Eterm (*info)(void *);
int (*check_io_debug)(ErtsCheckIoDebugInfo *);
} io_func = {0};
int
driver_select(ErlDrvPort port, ErlDrvEvent event, int mode, int on)
{
return (*io_func.select)(port, event, mode, on);
}
int
driver_event(ErlDrvPort port, ErlDrvEvent event, ErlDrvEventData event_data)
{
return (*io_func.event)(port, event, event_data);
}
Eterm erts_check_io_info(void *p)
{
return (*io_func.info)(p);
}
int
erts_check_io_debug(ErtsCheckIoDebugInfo *ip)
{
return (*io_func.check_io_debug)(ip);
}
static void
init_check_io(void)
{
if (erts_use_kernel_poll) {
io_func.select = driver_select_kp;
io_func.event = driver_event_kp;
#ifdef ERTS_POLL_NEED_ASYNC_INTERRUPT_SUPPORT
io_func.check_io_as_interrupt = erts_check_io_async_sig_interrupt_kp;
#endif
io_func.check_io_interrupt = erts_check_io_interrupt_kp;
io_func.check_io_interrupt_tmd = erts_check_io_interrupt_timed_kp;
io_func.check_io = erts_check_io_kp;
io_func.size = erts_check_io_size_kp;
io_func.info = erts_check_io_info_kp;
io_func.check_io_debug = erts_check_io_debug_kp;
erts_init_check_io_kp();
max_files = erts_check_io_max_files_kp();
}
else {
io_func.select = driver_select_nkp;
io_func.event = driver_event_nkp;
#ifdef ERTS_POLL_NEED_ASYNC_INTERRUPT_SUPPORT
io_func.check_io_as_interrupt = erts_check_io_async_sig_interrupt_nkp;
#endif
io_func.check_io_interrupt = erts_check_io_interrupt_nkp;
io_func.check_io_interrupt_tmd = erts_check_io_interrupt_timed_nkp;
io_func.check_io = erts_check_io_nkp;
io_func.size = erts_check_io_size_nkp;
io_func.info = erts_check_io_info_nkp;
io_func.check_io_debug = erts_check_io_debug_nkp;
erts_init_check_io_nkp();
max_files = erts_check_io_max_files_nkp();
}
}
#ifdef ERTS_POLL_NEED_ASYNC_INTERRUPT_SUPPORT
#define ERTS_CHK_IO_AS_INTR() (*io_func.check_io_as_interrupt)()
#else
#define ERTS_CHK_IO_AS_INTR() (*io_func.check_io_interrupt)(1)
#endif
#define ERTS_CHK_IO_INTR (*io_func.check_io_interrupt)
#define ERTS_CHK_IO_INTR_TMD (*io_func.check_io_interrupt_tmd)
#define ERTS_CHK_IO (*io_func.check_io)
#define ERTS_CHK_IO_SZ (*io_func.size)
#else /* !ERTS_ENABLE_KERNEL_POLL */
static void
init_check_io(void)
{
erts_init_check_io();
max_files = erts_check_io_max_files();
}
#ifdef ERTS_POLL_NEED_ASYNC_INTERRUPT_SUPPORT
#define ERTS_CHK_IO_AS_INTR() erts_check_io_async_sig_interrupt()
#else
#define ERTS_CHK_IO_AS_INTR() erts_check_io_interrupt(1)
#endif
#define ERTS_CHK_IO_INTR erts_check_io_interrupt
#define ERTS_CHK_IO_INTR_TMD erts_check_io_interrupt_timed
#define ERTS_CHK_IO erts_check_io
#define ERTS_CHK_IO_SZ erts_check_io_size
#endif
void
erts_sys_schedule_interrupt(int set)
{
ERTS_CHK_IO_INTR(set);
}
#ifdef ERTS_SMP
void
erts_sys_schedule_interrupt_timed(int set, ErtsMonotonicTime timeout_time)
{
ERTS_CHK_IO_INTR_TMD(set, timeout_time);
}
#endif
Uint
erts_sys_misc_mem_sz(void)
{
Uint res = ERTS_CHK_IO_SZ();
res += erts_smp_atomic_read_mb(&sys_misc_mem_sz);
return res;
}
/*
* reset the terminal to the original settings on exit
*/
void sys_tty_reset(int exit_code)
{
if (using_oldshell && !replace_intr) {
SET_BLOCKING(0);
}
else if (isatty(0)) {
tcsetattr(0,TCSANOW,&initial_tty_mode);
}
}
#ifdef __tile__
/* Direct malloc to spread memory around the caches of multiple tiles. */
#include <malloc.h>
#if defined(MALLOC_USE_HASH)
MALLOC_USE_HASH(1);
#endif
#endif
#ifdef USE_THREADS
#ifdef ERTS_THR_HAVE_SIG_FUNCS
/*
* Child thread inherits parents signal mask at creation. In order to
* guarantee that the main thread will receive all SIGINT, and
* SIGUSR1 signals sent to the process, we block these signals in the
* parent thread when creating a new thread.
*/
static sigset_t thr_create_sigmask;
#endif /* #ifdef ERTS_THR_HAVE_SIG_FUNCS */
typedef struct {
#ifdef ERTS_THR_HAVE_SIG_FUNCS
sigset_t saved_sigmask;
#endif
int sched_bind_data;
} erts_thr_create_data_t;
/*
* thr_create_prepare() is called in parent thread before thread creation.
* Returned value is passed as argument to thr_create_cleanup().
*/
static void *
thr_create_prepare(void)
{
erts_thr_create_data_t *tcdp;
tcdp = erts_alloc(ERTS_ALC_T_TMP, sizeof(erts_thr_create_data_t));
#ifdef ERTS_THR_HAVE_SIG_FUNCS
erts_thr_sigmask(SIG_BLOCK, &thr_create_sigmask, &tcdp->saved_sigmask);
#endif
tcdp->sched_bind_data = erts_sched_bind_atthrcreate_prepare();
return (void *) tcdp;
}
/* thr_create_cleanup() is called in parent thread after thread creation. */
static void
thr_create_cleanup(void *vtcdp)
{
erts_thr_create_data_t *tcdp = (erts_thr_create_data_t *) vtcdp;
erts_sched_bind_atthrcreate_parent(tcdp->sched_bind_data);
#ifdef ERTS_THR_HAVE_SIG_FUNCS
/* Restore signalmask... */
erts_thr_sigmask(SIG_SETMASK, &tcdp->saved_sigmask, NULL);
#endif
erts_free(ERTS_ALC_T_TMP, tcdp);
}
static void
thr_create_prepare_child(void *vtcdp)
{
erts_thr_create_data_t *tcdp = (erts_thr_create_data_t *) vtcdp;
#ifdef ERTS_ENABLE_LOCK_COUNT
erts_lcnt_thread_setup();
#endif
#ifndef NO_FPE_SIGNALS
/*
* We do not want fp exeptions in other threads than the
* scheduler threads. We enable fpe explicitly in the scheduler
* threads after this.
*/
erts_thread_disable_fpe();
#endif
erts_sched_bind_atthrcreate_child(tcdp->sched_bind_data);
}
#endif /* #ifdef USE_THREADS */
void
erts_sys_pre_init(void)
{
#ifdef USE_THREADS
erts_thr_init_data_t eid = ERTS_THR_INIT_DATA_DEF_INITER;
#endif
erts_printf_add_cr_to_stdout = 1;
erts_printf_add_cr_to_stderr = 1;
#ifdef USE_THREADS
eid.thread_create_child_func = thr_create_prepare_child;
/* Before creation in parent */
eid.thread_create_prepare_func = thr_create_prepare;
/* After creation in parent */
eid.thread_create_parent_func = thr_create_cleanup,
#ifdef ERTS_THR_HAVE_SIG_FUNCS
sigemptyset(&thr_create_sigmask);
sigaddset(&thr_create_sigmask, SIGINT); /* block interrupt */
sigaddset(&thr_create_sigmask, SIGUSR1); /* block user defined signal */
#endif
erts_thr_init(&eid);
#ifdef ERTS_ENABLE_LOCK_COUNT
erts_lcnt_init();
#endif
#endif /* USE_THREADS */
erts_init_sys_time_sup();
#ifdef USE_THREADS
#ifdef ERTS_SMP
erts_smp_atomic32_init_nob(&erts_break_requested, 0);
erts_smp_atomic32_init_nob(&erts_got_sigusr1, 0);
erts_smp_atomic32_init_nob(&have_prepared_crash_dump, 0);
#else
erts_break_requested = 0;
erts_got_sigusr1 = 0;
have_prepared_crash_dump = 0;
#endif
#endif /* USE_THREADS */
erts_smp_atomic_init_nob(&sys_misc_mem_sz, 0);
{
/*
* Unfortunately we depend on fd 0,1,2 in the old shell code.
* So if for some reason we do not have those open when we start
* we have to open them here. Not doing this can cause the emulator
* to deadlock when reaping the fd_driver ports :(
*/
int fd;
/* Make sure fd 0 is open */
if ((fd = open("/dev/null", O_RDONLY)) != 0)
close(fd);
/* Make sure fds 1 and 2 are open */
while (fd < 3) {
fd = open("/dev/null", O_WRONLY);
}
close(fd);
}
/* We need a file descriptor to close in the crashdump creation.
* We close this one to be sure we can get a fd for our real file ...
* so, we create one here ... a stone to carry all the way home.
*/
crashdump_companion_cube_fd = open("/dev/null", O_RDONLY);
/* don't lose it, there will be cake */
}
void
erl_sys_init(void)
{
#ifdef USE_SETLINEBUF
setlinebuf(stdout);
#else
setvbuf(stdout, (char *)NULL, _IOLBF, BUFSIZ);
#endif
erts_sys_init_float();
/* we save this so the break handler can set and reset it properly */
/* also so that we can reset on exit (break handler or not) */
if (isatty(0)) {
tcgetattr(0,&initial_tty_mode);
}
tzset(); /* Required at least for NetBSD with localtime_r() */
}
/* signal handling */
SIGFUNC sys_signal(int sig, SIGFUNC func)
{
struct sigaction act, oact;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
act.sa_handler = func;
sigaction(sig, &act, &oact);
return(oact.sa_handler);
}
#ifdef USE_THREADS
#undef sigprocmask
#define sigprocmask erts_thr_sigmask
#endif
void sys_sigblock(int sig)
{
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, sig);
sigprocmask(SIG_BLOCK, &mask, (sigset_t *)NULL);
}
void sys_sigrelease(int sig)
{
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, sig);
sigprocmask(SIG_UNBLOCK, &mask, (sigset_t *)NULL);
}
void erts_sys_sigsegv_handler(int signo) {
if (signo == SIGSEGV) {
longjmp(erts_sys_sigsegv_jmp, 1);
}
}
/*
* Function returns 1 if we can read from all values in between
* start and stop.
*/
int
erts_sys_is_area_readable(char *start, char *stop) {
int fds[2];
if (!pipe(fds)) {
/* We let write try to figure out if the pointers are readable */
int res = write(fds[1], start, (char*)stop - (char*)start);
if (res == -1) {
close(fds[0]);
close(fds[1]);
return 0;
}
close(fds[0]);
close(fds[1]);
return 1;
}
return 0;
}
static ERTS_INLINE int
prepare_crash_dump(int secs)
{
#define NUFBUF (3)
int i;
char env[21]; /* enough to hold any 64-bit integer */
size_t envsz;
DeclareTmpHeapNoproc(heap,NUFBUF);
Port *heart_port;
Eterm *hp = heap;
Eterm list = NIL;
int has_heart = 0;
UseTmpHeapNoproc(NUFBUF);
if (ERTS_PREPARED_CRASH_DUMP)
return 0; /* We have already been called */
heart_port = erts_get_heart_port();
/* Positive secs means an alarm must be set
* 0 or negative means no alarm
*
* Set alarm before we try to write to a port
* we don't want to hang on a port write with
* no alarm.
*
*/
if (secs >= 0) {
alarm((unsigned int)secs);
}
/* close all viable sockets via emergency close callbacks.
* Specifically we want to close epmd sockets.
*/
erts_emergency_close_ports();
if (heart_port) {
has_heart = 1;
list = CONS(hp, make_small(8), list); hp += 2;
/* send to heart port, CMD = 8, i.e. prepare crash dump =o */
erts_port_output(NULL, ERTS_PORT_SIG_FLG_FORCE_IMM_CALL, heart_port,
heart_port->common.id, list, NULL);
}
/* Make sure we have a fd for our crashdump file. */
close(crashdump_companion_cube_fd);
envsz = sizeof(env);
i = erts_sys_getenv__("ERL_CRASH_DUMP_NICE", env, &envsz);
if (i >= 0) {
int nice_val;
nice_val = i != 0 ? 0 : atoi(env);
if (nice_val > 39) {
nice_val = 39;
}
erts_silence_warn_unused_result(nice(nice_val));
}
UnUseTmpHeapNoproc(NUFBUF);
#undef NUFBUF
return has_heart;
}
int erts_sys_prepare_crash_dump(int secs)
{
return prepare_crash_dump(secs);
}
static ERTS_INLINE void
break_requested(void)
{
/*
* just set a flag - checked for and handled by
* scheduler threads erts_check_io() (not signal handler).
*/
#ifdef DEBUG
fprintf(stderr,"break!\n");
#endif
if (ERTS_BREAK_REQUESTED)
erts_exit(ERTS_INTR_EXIT, "");
ERTS_SET_BREAK_REQUESTED;
ERTS_CHK_IO_AS_INTR(); /* Make sure we don't sleep in poll */
}
/* set up signal handlers for break and quit */
#if (defined(SIG_SIGSET) || defined(SIG_SIGNAL))
static RETSIGTYPE request_break(void)
#else
static RETSIGTYPE request_break(int signum)
#endif
{
#ifdef ERTS_SMP
smp_sig_notify('I');
#else
break_requested();
#endif
}
static ERTS_INLINE void
sigusr1_exit(void)
{
char env[21]; /* enough to hold any 64-bit integer */
size_t envsz;
int i, secs = -1;
/* We do this at interrupt level, since the main reason for
* wanting to generate a crash dump in this way is that the emulator
* is hung somewhere, so it won't be able to poll any flag we set here.
*/
ERTS_SET_GOT_SIGUSR1;
envsz = sizeof(env);
if ((i = erts_sys_getenv_raw("ERL_CRASH_DUMP_SECONDS", env, &envsz)) >= 0) {
secs = i != 0 ? 0 : atoi(env);
}
prepare_crash_dump(secs);
erts_exit(ERTS_ERROR_EXIT, "Received SIGUSR1\n");
}
#ifdef ETHR_UNUSABLE_SIGUSRX
#warning "Unusable SIGUSR1 & SIGUSR2. Disabling use of these signals"
#else
#ifdef ERTS_SYS_SUSPEND_SIGNAL
void
sys_thr_suspend(erts_tid_t tid) {
erts_thr_kill(tid, ERTS_SYS_SUSPEND_SIGNAL);
}
void
sys_thr_resume(erts_tid_t tid) {
int i = 0, res;
do {
res = write(sig_suspend_fds[1],&i,sizeof(i));
} while (res < 0 && errno == EAGAIN);
}
#endif
#if (defined(SIG_SIGSET) || defined(SIG_SIGNAL))
static RETSIGTYPE user_signal1(void)
#else
static RETSIGTYPE user_signal1(int signum)
#endif
{
#ifdef ERTS_SMP
smp_sig_notify('1');
#else
sigusr1_exit();
#endif
}
#ifdef ERTS_SYS_SUSPEND_SIGNAL
#if (defined(SIG_SIGSET) || defined(SIG_SIGNAL))
static RETSIGTYPE suspend_signal(void)
#else
static RETSIGTYPE suspend_signal(int signum)
#endif
{
int res;
int buf[1];
do {
res = read(sig_suspend_fds[0], buf, sizeof(int));
} while (res < 0 && errno == EINTR);
}
#endif /* #ifdef ERTS_SYS_SUSPEND_SIGNAL */
#endif /* #ifndef ETHR_UNUSABLE_SIGUSRX */
static void
quit_requested(void)
{
erts_exit(ERTS_INTR_EXIT, "");
}
#if (defined(SIG_SIGSET) || defined(SIG_SIGNAL))
static RETSIGTYPE do_quit(void)
#else
static RETSIGTYPE do_quit(int signum)
#endif
{
#ifdef ERTS_SMP
smp_sig_notify('Q');
#else
quit_requested();
#endif
}
/* Disable break */
void erts_set_ignore_break(void) {
sys_signal(SIGINT, SIG_IGN);
sys_signal(SIGQUIT, SIG_IGN);
sys_signal(SIGTSTP, SIG_IGN);
}
/* Don't use ctrl-c for break handler but let it be
used by the shell instead (see user_drv.erl) */
void erts_replace_intr(void) {
struct termios mode;
if (isatty(0)) {
tcgetattr(0, &mode);
/* here's an example of how to replace ctrl-c with ctrl-u */
/* mode.c_cc[VKILL] = 0;
mode.c_cc[VINTR] = CKILL; */
mode.c_cc[VINTR] = 0; /* disable ctrl-c */
tcsetattr(0, TCSANOW, &mode);
replace_intr = 1;
}
}
void init_break_handler(void)
{
sys_signal(SIGINT, request_break);
#ifndef ETHR_UNUSABLE_SIGUSRX
sys_signal(SIGUSR1, user_signal1);
#endif /* #ifndef ETHR_UNUSABLE_SIGUSRX */
sys_signal(SIGQUIT, do_quit);
}
void sys_init_suspend_handler(void)
{
#ifdef ERTS_SYS_SUSPEND_SIGNAL
sys_signal(ERTS_SYS_SUSPEND_SIGNAL, suspend_signal);
#endif
}
int sys_max_files(void)
{
return(max_files);
}
/************************** OS info *******************************/
/* Used by erlang:info/1. */
/* (This code was formerly in drv.XXX/XXX_os_drv.c) */
char os_type[] = "unix";
static int
get_number(char **str_ptr)
{
char* s = *str_ptr; /* Pointer to beginning of string. */
char* dot; /* Pointer to dot in string or NULL. */
if (!isdigit((int) *s))
return 0;
if ((dot = strchr(s, '.')) == NULL) {
*str_ptr = s+strlen(s);
return atoi(s);
} else {
*dot = '\0';
*str_ptr = dot+1;
return atoi(s);
}
}
void
os_flavor(char* namebuf, /* Where to return the name. */
unsigned size) /* Size of name buffer. */
{
struct utsname uts; /* Information about the system. */
char* s;
(void) uname(&uts);
for (s = uts.sysname; *s; s++) {
if (isupper((int) *s)) {
*s = tolower((int) *s);
}
}
strcpy(namebuf, uts.sysname);
}
void
os_version(pMajor, pMinor, pBuild)
int* pMajor; /* Pointer to major version. */
int* pMinor; /* Pointer to minor version. */
int* pBuild; /* Pointer to build number. */
{
struct utsname uts; /* Information about the system. */
char* release; /* Pointer to the release string:
* X.Y or X.Y.Z.
*/
(void) uname(&uts);
release = uts.release;
*pMajor = get_number(&release);
*pMinor = get_number(&release);
*pBuild = get_number(&release);
}
void init_getenv_state(GETENV_STATE *state)
{
erts_smp_rwmtx_rlock(&environ_rwmtx);
*state = NULL;
}
char *getenv_string(GETENV_STATE *state0)
{
char **state = (char **) *state0;
char *cp;
ERTS_SMP_LC_ASSERT(erts_smp_lc_rwmtx_is_rlocked(&environ_rwmtx));
if (state == NULL)
state = environ;
cp = *state++;
*state0 = (GETENV_STATE) state;
return cp;
}
void fini_getenv_state(GETENV_STATE *state)
{
*state = NULL;
erts_smp_rwmtx_runlock(&environ_rwmtx);
}
void erts_do_break_handling(void)
{
struct termios temp_mode;
int saved = 0;
/*
* Most functions that do_break() calls are intentionally not thread safe;
* therefore, make sure that all threads but this one are blocked before
* proceeding!
*/
erts_smp_thr_progress_block();
/* during break we revert to initial settings */
/* this is done differently for oldshell */
if (using_oldshell && !replace_intr) {
SET_BLOCKING(1);
}
else if (isatty(0)) {
tcgetattr(0,&temp_mode);
tcsetattr(0,TCSANOW,&initial_tty_mode);
saved = 1;
}
/* call the break handling function, reset the flag */
do_break();
ERTS_UNSET_BREAK_REQUESTED;
fflush(stdout);
/* after break we go back to saved settings */
if (using_oldshell && !replace_intr) {
SET_NONBLOCKING(1);
}
else if (saved) {
tcsetattr(0,TCSANOW,&temp_mode);
}
erts_smp_thr_progress_unblock();
}
/* Fills in the systems representation of the jam/beam process identifier.
** The Pid is put in STRING representation in the supplied buffer,
** no interpretatione of this should be done by the rest of the
** emulator. The buffer should be at least 21 bytes long.
*/
void sys_get_pid(char *buffer, size_t buffer_size){
pid_t p = getpid();
/* Assume the pid is scalar and can rest in an unsigned long... */
erts_snprintf(buffer, buffer_size, "%lu",(unsigned long) p);
}
int
erts_sys_putenv_raw(char *key, char *value) {
return erts_sys_putenv(key, value);
}
int
erts_sys_putenv(char *key, char *value)
{
int res;
char *env;
Uint need = strlen(key) + strlen(value) + 2;
#ifdef HAVE_COPYING_PUTENV
env = erts_alloc(ERTS_ALC_T_TMP, need);
#else
env = erts_alloc(ERTS_ALC_T_PUTENV_STR, need);
erts_smp_atomic_add_nob(&sys_misc_mem_sz, need);
#endif
strcpy(env,key);
strcat(env,"=");
strcat(env,value);
erts_smp_rwmtx_rwlock(&environ_rwmtx);
res = putenv(env);
erts_smp_rwmtx_rwunlock(&environ_rwmtx);
#ifdef HAVE_COPYING_PUTENV
erts_free(ERTS_ALC_T_TMP, env);
#endif
return res;
}
int
erts_sys_getenv__(char *key, char *value, size_t *size)
{
int res;
char *orig_value = getenv(key);
if (!orig_value)
res = -1;
else {
size_t len = sys_strlen(orig_value);
if (len >= *size) {
*size = len + 1;
res = 1;
}
else {
*size = len;
sys_memcpy((void *) value, (void *) orig_value, len+1);
res = 0;
}
}
return res;
}
int
erts_sys_getenv_raw(char *key, char *value, size_t *size) {
return erts_sys_getenv(key, value, size);
}
/*
* erts_sys_getenv
* returns:
* -1, if environment key is not set with a value
* 0, if environment key is set and value fits into buffer size
* 1, if environment key is set but does not fit into buffer size
* size is set with the needed buffer size value
*/
int
erts_sys_getenv(char *key, char *value, size_t *size)
{
int res;
erts_smp_rwmtx_rlock(&environ_rwmtx);
res = erts_sys_getenv__(key, value, size);
erts_smp_rwmtx_runlock(&environ_rwmtx);
return res;
}
int
erts_sys_unsetenv(char *key)
{
int res;
erts_smp_rwmtx_rwlock(&environ_rwmtx);
res = unsetenv(key);
erts_smp_rwmtx_rwunlock(&environ_rwmtx);
return res;
}
void
sys_init_io(void)
{
}
#if (0) /* unused? */
static int write_fill(fd, buf, len)
int fd, len;
char *buf;
{
int i, done = 0;
do {
if ((i = write(fd, buf+done, len-done)) < 0) {
if (errno != EINTR)
return (i);
i = 0;
}
done += i;
} while (done < len);
return (len);
}
#endif
extern const char pre_loaded_code[];
extern Preload pre_loaded[];
void erts_sys_alloc_init(void)
{
}
#if ERTS_HAVE_ERTS_SYS_ALIGNED_ALLOC
void *erts_sys_aligned_alloc(UWord alignment, UWord size)
{
#ifdef HAVE_POSIX_MEMALIGN
void *ptr = NULL;
int error;
ASSERT(alignment && (alignment & (alignment-1)) == 0); /* power of 2 */
error = posix_memalign(&ptr, (size_t) alignment, (size_t) size);
#if HAVE_ERTS_MSEG
if (error || !ptr) {
erts_mseg_clear_cache();
error = posix_memalign(&ptr, (size_t) alignment, (size_t) size);
}
#endif
if (error) {
errno = error;
return NULL;
}
if (!ptr)
errno = ENOMEM;
ASSERT(!ptr || (((UWord) ptr) & (alignment - 1)) == 0);
return ptr;
#else
# error "Missing erts_sys_aligned_alloc() implementation"
#endif
}
void erts_sys_aligned_free(UWord alignment, void *ptr)
{
ASSERT(alignment && (alignment & (alignment-1)) == 0); /* power of 2 */
free(ptr);
}
void *erts_sys_aligned_realloc(UWord alignment, void *ptr, UWord size, UWord old_size)
{
void *new_ptr = erts_sys_aligned_alloc(alignment, size);
if (new_ptr) {
UWord copy_size = old_size < size ? old_size : size;
sys_memcpy(new_ptr, ptr, (size_t) copy_size);
erts_sys_aligned_free(alignment, ptr);
}
return new_ptr;
}
#endif
void *erts_sys_alloc(ErtsAlcType_t t, void *x, Uint sz)
{
void *res = malloc((size_t) sz);
#if HAVE_ERTS_MSEG
if (!res) {
erts_mseg_clear_cache();
return malloc((size_t) sz);
}
#endif
return res;
}
void *erts_sys_realloc(ErtsAlcType_t t, void *x, void *p, Uint sz)
{
void *res = realloc(p, (size_t) sz);
#if HAVE_ERTS_MSEG
if (!res) {
erts_mseg_clear_cache();
return realloc(p, (size_t) sz);
}
#endif
return res;
}
void erts_sys_free(ErtsAlcType_t t, void *x, void *p)
{
free(p);
}
/* Return a pointer to a vector of names of preloaded modules */
Preload*
sys_preloaded(void)
{
return pre_loaded;
}
/* Return a pointer to preloaded code for module "module" */
unsigned char*
sys_preload_begin(Preload* p)
{
return p->code;
}
/* Clean up if allocated */
void sys_preload_end(Preload* p)
{
/* Nothing */
}
/* Read a key from console, used by break.c
Here we assume that all schedulers are stopped so that erl_poll
does not interfere with the select below.
*/
int sys_get_key(fd)
int fd;
{
int c, ret;
unsigned char rbuf[64];
fd_set fds;
fflush(stdout); /* Flush query ??? */
FD_ZERO(&fds);
FD_SET(fd,&fds);
ret = select(fd+1, &fds, NULL, NULL, NULL);
if (ret == 1) {
do {
c = read(fd,rbuf,64);
} while (c < 0 && errno == EAGAIN);
if (c <= 0)
return c;
}
return rbuf[0];
}
extern int erts_initialized;
void
erl_assert_error(const char* expr, const char* func, const char* file, int line)
{
fflush(stdout);
fprintf(stderr, "%s:%d:%s() Assertion failed: %s\n",
file, line, func, expr);
fflush(stderr);
#if !defined(ERTS_SMP) && 0
/* Writing a crashdump from a failed assertion when smp support
* is enabled almost a guaranteed deadlocking, don't even bother.
*
* It could maybe be useful (but I'm not convinced) to write the
* crashdump if smp support is disabled...
*/
if (erts_initialized)
erl_crash_dump(file, line, "Assertion failed: %s\n", expr);
#endif
abort();
}
#ifdef DEBUG
void
erl_debug(char* fmt, ...)
{
char sbuf[1024]; /* Temporary buffer. */
va_list va;
if (debug_log) {
va_start(va, fmt);
vsprintf(sbuf, fmt, va);
va_end(va);
fprintf(stderr, "%s", sbuf);
}
}
#endif /* DEBUG */
/*
* Called from schedule() when it runs out of runnable processes,
* or when Erlang code has performed INPUT_REDUCTIONS reduction
* steps. runnable == 0 iff there are no runnable Erlang processes.
*/
void
erl_sys_schedule(int runnable)
{
ERTS_CHK_IO(!runnable);
ERTS_SMP_LC_ASSERT(!erts_thr_progress_is_blocking());
}
#ifdef ERTS_SMP
static erts_smp_tid_t sig_dispatcher_tid;
static void
smp_sig_notify(char c)
{
int res;
do {
/* write() is async-signal safe (according to posix) */
res = write(sig_notify_fds[1], &c, 1);
} while (res < 0 && errno == EINTR);
if (res != 1) {
char msg[] =
"smp_sig_notify(): Failed to notify signal-dispatcher thread "
"about received signal";
erts_silence_warn_unused_result(write(2, msg, sizeof(msg)));
abort();
}
}
static void *
signal_dispatcher_thread_func(void *unused)
{
#ifdef ERTS_ENABLE_LOCK_CHECK
erts_lc_set_thread_name("signal_dispatcher");
#endif
while (1) {
char buf[32];
int res, i;
/* Block on read() waiting for a signal notification to arrive... */
res = read(sig_notify_fds[0], (void *) &buf[0], 32);
if (res < 0) {
if (errno == EINTR)
continue;
erts_exit(ERTS_ABORT_EXIT,
"signal-dispatcher thread got unexpected error: %s (%d)\n",
erl_errno_id(errno),
errno);
}
for (i = 0; i < res; i++) {
/*
* NOTE 1: The signal dispatcher thread should not do work
* that takes a substantial amount of time (except
* perhaps in test and debug builds). It needs to
* be responsive, i.e, it should only dispatch work
* to other threads.
*
* NOTE 2: The signal dispatcher thread is not a blockable
* thread (i.e., not a thread managed by the
* erl_thr_progress module). This is intentional.
* We want to be able to interrupt writing of a crash
* dump by hitting C-c twice. Since it isn't a
* blockable thread it is important that it doesn't
* change the state of any data that a blocking thread
* expects to have exclusive access to (unless the
* signal dispatcher itself explicitly is blocking all
* blockable threads).
*/
switch (buf[i]) {
case 0: /* Emulator initialized */
break;
case 'I': /* SIGINT */
break_requested();
break;
case 'Q': /* SIGQUIT */
quit_requested();
break;
case '1': /* SIGUSR1 */
sigusr1_exit();
break;
default:
erts_exit(ERTS_ABORT_EXIT,
"signal-dispatcher thread received unknown "
"signal notification: '%c'\n",
buf[i]);
}
}
ERTS_SMP_LC_ASSERT(!erts_thr_progress_is_blocking());
}
return NULL;
}
static void
init_smp_sig_notify(void)
{
erts_smp_thr_opts_t thr_opts = ERTS_SMP_THR_OPTS_DEFAULT_INITER;
thr_opts.detached = 1;
thr_opts.name = "sys_sig_dispatcher";
if (pipe(sig_notify_fds) < 0) {
erts_exit(ERTS_ABORT_EXIT,
"Failed to create signal-dispatcher pipe: %s (%d)\n",
erl_errno_id(errno),
errno);
}
/* Start signal handler thread */
erts_smp_thr_create(&sig_dispatcher_tid,
signal_dispatcher_thread_func,
NULL,
&thr_opts);
}
static void
init_smp_sig_suspend(void) {
#ifdef ERTS_SYS_SUSPEND_SIGNAL
if (pipe(sig_suspend_fds) < 0) {
erts_exit(ERTS_ABORT_EXIT,
"Failed to create sig_suspend pipe: %s (%d)\n",
erl_errno_id(errno),
errno);
}
#endif
}
#ifdef __DARWIN__
int erts_darwin_main_thread_pipe[2];
int erts_darwin_main_thread_result_pipe[2];
static void initialize_darwin_main_thread_pipes(void)
{
if (pipe(erts_darwin_main_thread_pipe) < 0 ||
pipe(erts_darwin_main_thread_result_pipe) < 0) {
erts_exit(ERTS_ERROR_EXIT,"Fatal error initializing Darwin main thread stealing");
}
}
#endif
void
erts_sys_main_thread(void)
{
erts_thread_disable_fpe();
#ifdef __DARWIN__
initialize_darwin_main_thread_pipes();
#endif
/* Become signal receiver thread... */
#ifdef ERTS_ENABLE_LOCK_CHECK
erts_lc_set_thread_name("signal_receiver");
#endif
smp_sig_notify(0); /* Notify initialized */
/* Wait for a signal to arrive... */
#ifdef __DARWIN__
while (1) {
/*
* The wx driver needs to be able to steal the main thread for Cocoa to
* work properly.
*/
fd_set readfds;
int res;
FD_ZERO(&readfds);
FD_SET(erts_darwin_main_thread_pipe[0], &readfds);
res = select(erts_darwin_main_thread_pipe[0] + 1, &readfds, NULL, NULL, NULL);
if (res > 0 && FD_ISSET(erts_darwin_main_thread_pipe[0],&readfds)) {
void* (*func)(void*);
void* arg;
void *resp;
res = read(erts_darwin_main_thread_pipe[0],&func,sizeof(void* (*)(void*)));
if (res != sizeof(void* (*)(void*)))
break;
res = read(erts_darwin_main_thread_pipe[0],&arg,sizeof(void*));
if (res != sizeof(void*))
break;
resp = (*func)(arg);
write(erts_darwin_main_thread_result_pipe[1],&resp,sizeof(void *));
}
if (res == -1 && errno != EINTR)
break;
}
/* Something broke with the main thread pipe, so we ignore it for now.
Most probably erts has closed this pipe and is about to exit. */
#endif /* #ifdef __DARWIN__ */
while (1) {
#ifdef DEBUG
int res =
#else
(void)
#endif
select(0, NULL, NULL, NULL, NULL);
ASSERT(res < 0);
ASSERT(errno == EINTR);
}
}
#endif /* ERTS_SMP */
#ifdef ERTS_ENABLE_KERNEL_POLL /* get_value() is currently only used when
kernel-poll is enabled */
/* Get arg marks argument as handled by
putting NULL in argv */
static char *
get_value(char* rest, char** argv, int* ip)
{
char *param = argv[*ip]+1;
argv[*ip] = NULL;
if (*rest == '\0') {
char *next = argv[*ip + 1];
if (next[0] == '-'
&& next[1] == '-'
&& next[2] == '\0') {
erts_fprintf(stderr, "bad \"%s\" value: \n", param);
erts_usage();
}
(*ip)++;
argv[*ip] = NULL;
return next;
}
return rest;
}
#endif /* ERTS_ENABLE_KERNEL_POLL */
void
erl_sys_args(int* argc, char** argv)
{
int i, j;
erts_smp_rwmtx_init(&environ_rwmtx, "environ");
i = 1;
ASSERT(argc && argv);
while (i < *argc) {
if(argv[i][0] == '-') {
switch (argv[i][1]) {
#ifdef ERTS_ENABLE_KERNEL_POLL
case 'K': {
char *arg = get_value(argv[i] + 2, argv, &i);
if (strcmp("true", arg) == 0) {
erts_use_kernel_poll = 1;
}
else if (strcmp("false", arg) == 0) {
erts_use_kernel_poll = 0;
}
else {
erts_fprintf(stderr, "bad \"K\" value: %s\n", arg);
erts_usage();
}
break;
}
#endif
case '-':
goto done_parsing;
default:
break;
}
}
i++;
}
done_parsing:
#ifdef ERTS_ENABLE_KERNEL_POLL
if (erts_use_kernel_poll) {
char no_kp[10];
size_t no_kp_sz = sizeof(no_kp);
int res = erts_sys_getenv_raw("ERL_NO_KERNEL_POLL", no_kp, &no_kp_sz);
if (res > 0
|| (res == 0
&& sys_strcmp("false", no_kp) != 0
&& sys_strcmp("FALSE", no_kp) != 0)) {
erts_use_kernel_poll = 0;
}
}
#endif
init_check_io();
#ifdef ERTS_SMP
init_smp_sig_notify();
init_smp_sig_suspend();
#endif
/* Handled arguments have been marked with NULL. Slide arguments
not handled towards the beginning of argv. */
for (i = 0, j = 0; i < *argc; i++) {
if (argv[i])
argv[j++] = argv[i];
}
*argc = j;
}