diff options
author | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2009-11-20 14:54:40 +0000 |
commit | 84adefa331c4159d432d22840663c38f155cd4c1 (patch) | |
tree | bff9a9c66adda4df2106dfd0e5c053ab182a12bd /erts/emulator/sys/unix | |
download | otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2 otp-84adefa331c4159d432d22840663c38f155cd4c1.zip |
The R13B03 release.OTP_R13B03
Diffstat (limited to 'erts/emulator/sys/unix')
-rw-r--r-- | erts/emulator/sys/unix/driver_int.h | 41 | ||||
-rw-r--r-- | erts/emulator/sys/unix/erl9_start.c | 130 | ||||
-rw-r--r-- | erts/emulator/sys/unix/erl_child_setup.c | 122 | ||||
-rw-r--r-- | erts/emulator/sys/unix/erl_main.c | 31 | ||||
-rw-r--r-- | erts/emulator/sys/unix/erl_unix_sys.h | 339 | ||||
-rw-r--r-- | erts/emulator/sys/unix/erl_unix_sys_ddll.c | 280 | ||||
-rw-r--r-- | erts/emulator/sys/unix/sys.c | 3346 | ||||
-rw-r--r-- | erts/emulator/sys/unix/sys_float.c | 815 | ||||
-rw-r--r-- | erts/emulator/sys/unix/sys_time.c | 134 |
9 files changed, 5238 insertions, 0 deletions
diff --git a/erts/emulator/sys/unix/driver_int.h b/erts/emulator/sys/unix/driver_int.h new file mode 100644 index 0000000000..a7ee8087ab --- /dev/null +++ b/erts/emulator/sys/unix/driver_int.h @@ -0,0 +1,41 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1997-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +/* + * System dependant driver declarations + */ + +#ifndef __DRIVER_INT_H__ +#define __DRIVER_INT_H__ + +#ifdef HAVE_SYS_UIO_H +#include <sys/types.h> +#include <sys/uio.h> + +typedef struct iovec SysIOVec; + +#else + +typedef struct { + char* iov_base; + int iov_len; +} SysIOVec; + +#endif + +#endif diff --git a/erts/emulator/sys/unix/erl9_start.c b/erts/emulator/sys/unix/erl9_start.c new file mode 100644 index 0000000000..578062d7e2 --- /dev/null +++ b/erts/emulator/sys/unix/erl9_start.c @@ -0,0 +1,130 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2002-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "sys.h" +#include "erl_vm.h" +#include "global.h" +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +/* + * XXX This is a temporary dummy to make sys.c happy until we'll rewrite it. + */ +unsigned preloaded_size_ring0 = 1; +unsigned char preloaded_ring0[1] = {0}; + +Preload pre_loaded[] = { + {"ring0", 1, preloaded_ring0}, + {0, 0, 0} +}; + +int +main(int argc, char** argv) +{ + char sbuf[1024]; + struct { + void* p; + int sz; + } bins[2]; + int bin_num = 0; + FILE* fp; + char* progname = argv[0]; + char* eq; + + argv++, argc--; + + if (argc > 0 && argv[0][0] == '-') { + argv++, argc--; + } + if (argc < 1) { + abort(); + } + if ((fp = fopen(argv[0], "r")) == NULL) { + abort(); + } + + /* Needs to be called before any memory allocation */ + erts_short_init(); + + while (fgets(sbuf, sizeof sbuf, fp)) { + if (sbuf[0] == '#') { + continue; /* Comment */ + } else if (sbuf[0] == 'e' && strncmp("exec", sbuf, 4) == 0) { + continue; /* Comment ;-) */ + } else if ((eq = strchr(sbuf, '=')) != NULL) { + char* val; + char* p = strchr(sbuf, '\n'); + if (p) { + *p = '\0'; + } + *eq = '\0'; + val = erts_read_env(sbuf); + if (val == NULL) { + *eq = '='; + erts_sys_putenv(sbuf, eq - &sbuf[0]); + } + erts_free_read_env(val); + } else if (sbuf[0] == ':' && '0' <= sbuf[1] && sbuf[1] <= '9') { + int load_size = atoi(sbuf+1); + void* bin; + + bin = malloc(load_size); + if (fread(bin, 1, load_size, fp) != load_size) { + abort(); + } + bins[bin_num].p = bin; + bins[bin_num].sz = load_size; + bin_num++; + } else if (strcmp(sbuf, "--end--\n") == 0) { + int rval; + Eterm mod = NIL; + char *val; + + fclose(fp); + + if (bin_num != 2) { + abort(); + } + + val = erts_read_env("ERLBREAKHANDLER"); + if (val) { + init_break_handler(); + } + erts_free_read_env(val); + + if ((rval = erts_load_module(NULL, 0, NIL, &mod, bins[0].p, bins[0].sz)) < 0) { + fprintf(stderr, "%s: Load of initial module failed: %d\n", + progname, rval); + abort(); + } + erts_first_process(mod, bins[1].p, bins[1].sz, argc, argv); + free(bins[0].p); + free(bins[1].p); + process_main(); + abort(); + } else { + fprintf(stderr, "%s: bad line: %s\n", progname, sbuf); + abort(); + } + } + abort(); +} diff --git a/erts/emulator/sys/unix/erl_child_setup.c b/erts/emulator/sys/unix/erl_child_setup.c new file mode 100644 index 0000000000..7c6e4a2f37 --- /dev/null +++ b/erts/emulator/sys/unix/erl_child_setup.c @@ -0,0 +1,122 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2002-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +/* + * After a vfork() (or fork()) the child exec()s to this program which + * sets up the child and exec()s to the user program (see spawn_start() + * in sys.c and ticket OTP-4389). + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#define NEED_CHILD_SETUP_DEFINES +#include "sys.h" +#include "erl_misc_utils.h" + +#ifdef SIG_SIGSET /* Old SysV */ +void sys_sigrelease(int sig) +{ + sigrelse(sig); +} +#else /* !SIG_SIGSET */ +#ifdef SIG_SIGNAL /* Old BSD */ +sys_sigrelease(int sig) +{ + sigsetmask(sigblock(0) & ~sigmask(sig)); +} +#else /* !SIG_SIGNAL */ /* The True Way - POSIX!:-) */ +void sys_sigrelease(int sig) +{ + sigset_t mask; + + sigemptyset(&mask); + sigaddset(&mask, sig); + sigprocmask(SIG_UNBLOCK, &mask, (sigset_t *)NULL); +} +#endif /* !SIG_SIGNAL */ +#endif /* !SIG_SIGSET */ + +int +main(int argc, char *argv[]) +{ + int i, from, to; + int erts_spawn_executable = 0; + + /* OBSERVE! + * Keep child setup after fork() (implemented in sys.c) up to date + * if changes are made here. + */ + + if (argc != CS_ARGV_NO_OF_ARGS) { + if (argc < CS_ARGV_NO_OF_ARGS) { + return 1; + } else { + erts_spawn_executable = 1; + } + } + + if (strcmp("false", argv[CS_ARGV_UNBIND_IX]) != 0) + if (erts_unbind_from_cpu_str(argv[CS_ARGV_UNBIND_IX]) != 0) + return 1; + + for (i = 0; i < CS_ARGV_NO_OF_DUP2_OPS; i++) { + if (argv[CS_ARGV_DUP2_OP_IX(i)][0] == '-' + && argv[CS_ARGV_DUP2_OP_IX(i)][1] == '\0') + break; + if (sscanf(argv[CS_ARGV_DUP2_OP_IX(i)], "%d:%d", &from, &to) != 2) + return 1; + if (dup2(from, to) < 0) + return 1; + } + + if (sscanf(argv[CS_ARGV_FD_CR_IX], "%d:%d", &from, &to) != 2) + return 1; + for (i = from; i <= to; i++) + (void) close(i); + + if (!(argv[CS_ARGV_WD_IX][0] == '.' && argv[CS_ARGV_WD_IX][1] == '\0') + && chdir(argv[CS_ARGV_WD_IX]) < 0) + return 1; + +#if defined(USE_SETPGRP_NOARGS) /* SysV */ + (void) setpgrp(); +#elif defined(USE_SETPGRP) /* BSD */ + (void) setpgrp(0, getpid()); +#else /* POSIX */ + (void) setsid(); +#endif + + sys_sigrelease(SIGCHLD); + sys_sigrelease(SIGINT); + sys_sigrelease(SIGUSR1); + + if (erts_spawn_executable) { + if (argv[CS_ARGV_NO_OF_ARGS + 1] == NULL) { + execl(argv[CS_ARGV_NO_OF_ARGS],argv[CS_ARGV_NO_OF_ARGS], + (char *) NULL); + } else { + execv(argv[CS_ARGV_NO_OF_ARGS],&(argv[CS_ARGV_NO_OF_ARGS + 1])); + } + } else { + execl("/bin/sh", "sh", "-c", argv[CS_ARGV_CMD_IX], (char *) NULL); + } + return 1; +} diff --git a/erts/emulator/sys/unix/erl_main.c b/erts/emulator/sys/unix/erl_main.c new file mode 100644 index 0000000000..b26f93f77e --- /dev/null +++ b/erts/emulator/sys/unix/erl_main.c @@ -0,0 +1,31 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2000-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "sys.h" +#include "erl_vm.h" +#include "global.h" + +int +main(int argc, char **argv) +{ + erl_start(argc, argv); + return 0; +} diff --git a/erts/emulator/sys/unix/erl_unix_sys.h b/erts/emulator/sys/unix/erl_unix_sys.h new file mode 100644 index 0000000000..2d5ef882f6 --- /dev/null +++ b/erts/emulator/sys/unix/erl_unix_sys.h @@ -0,0 +1,339 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1997-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + * + * This file handles differences between different Unix systems. + * This should be the only place with conditional compilation + * depending on the type of OS. + */ + +#ifndef _ERL_UNIX_SYS_H +#define _ERL_UNIX_SYS_H + +#include <stdio.h> +#include <math.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#ifndef QNX +#include <memory.h> +#endif + +#if defined(__sun__) && defined(__SVR4) && !defined(__EXTENSIONS__) +# define __EXTENSIONS__ +# include <sys/types.h> +# undef __EXTENSIONS__ +#else +# include <sys/types.h> +#endif +#include <sys/stat.h> +#include <sys/param.h> +#include <fcntl.h> +#include "erl_errno.h" +#include <signal.h> + + +#if HAVE_SYS_SOCKETIO_H +# include <sys/socketio.h> +#endif +#if HAVE_SYS_SOCKIO_H +# include <sys/sockio.h> +#endif + +#ifdef HAVE_NET_ERRNO_H +#include <net/errno.h> +#endif + +#ifdef HAVE_DIRENT_H +# include <dirent.h> +#endif + +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +#ifndef HAVE_MMAP +# define HAVE_MMAP 0 +#endif + +#if HAVE_MMAP +# include <sys/mman.h> +#endif + +#if TIME_WITH_SYS_TIME +# include <sys/time.h> +# include <time.h> +#else +# if HAVE_SYS_TIME_H +# include <sys/time.h> +# else +# include <time.h> +# endif +#endif + +#include <sys/times.h> + +#ifdef HAVE_IEEEFP_H +#include <ieeefp.h> +#endif + +#ifdef QNX +#include <process.h> +#include <sys/qnx_glob.h> +#endif + +#include <pwd.h> + +#ifndef HZ +#define HZ 60 +#endif + +#ifdef NETDB_H_NEEDS_IN_H +#include <netinet/in.h> +#endif +#include <netdb.h> + +/* + * Make sure that MAXPATHLEN is defined. + */ +#ifdef GETHRTIME_WITH_CLOCK_GETTIME +#undef HAVE_GETHRTIME +#define HAVE_GETHRTIME 1 +#endif + +#ifndef MAXPATHLEN +# ifdef PATH_MAX +# define MAXPATHLEN PATH_MAX +# else +# define MAXPATHLEN 2048 +# endif +#endif + +/* File descriptors are numbers anc consecutively allocated on Unix */ +#define ERTS_SYS_CONTINOUS_FD_NUMBERS + +#define HAVE_ERTS_CHECK_IO_DEBUG +int erts_check_io_debug(void); + + +#ifndef ENABLE_CHILD_WAITER_THREAD +# undef ERTS_POLL_NEED_ASYNC_INTERRUPT_SUPPORT +# define ERTS_POLL_NEED_ASYNC_INTERRUPT_SUPPORT +# ifdef ERTS_SMP +# define ERTS_SMP_SCHEDULERS_NEED_TO_CHECK_CHILDREN +void erts_check_children(void); +# endif +#endif + +typedef void *GETENV_STATE; + +/* +** For the erl_timer_sup module. +*/ + +typedef struct timeval SysTimeval; + +#define sys_gettimeofday(Arg) ((void) gettimeofday((Arg), NULL)) + +typedef struct tms SysTimes; + +extern int erts_ticks_per_sec; + +#define SYS_CLK_TCK (erts_ticks_per_sec) + +#define sys_times(Arg) times(Arg) + +#define ERTS_WRAP_SYS_TIMES 1 +extern int erts_ticks_per_sec_wrap; +#define SYS_CLK_TCK_WRAP (erts_ticks_per_sec_wrap) +extern clock_t sys_times_wrap(void); + +#ifdef HAVE_GETHRTIME +#ifdef GETHRTIME_WITH_CLOCK_GETTIME +typedef long long SysHrTime; + +extern SysHrTime sys_gethrtime(void); +#define sys_init_hrtime() /* Nothing */ + +#else /* Real gethrtime (Solaris) */ + +typedef hrtime_t SysHrTime; + +#define sys_gethrtime() gethrtime() +#define sys_init_hrtime() /* Nothing */ + +#endif /* GETHRTIME_WITH_CLOCK_GETTIME */ +#endif /* HAVE_GETHRTIME */ + +#if (defined(HAVE_GETHRVTIME) || defined(HAVE_CLOCK_GETTIME)) +typedef long long SysCpuTime; +typedef struct timespec SysTimespec; + +#if defined(HAVE_GETHRVTIME) +#define sys_gethrvtime() gethrvtime() +#define sys_get_proc_cputime(t,tp) (t) = sys_gethrvtime(), \ + (tp).tv_sec = (time_t)((t)/1000000000LL), \ + (tp).tv_nsec = (long)((t)%1000000000LL) +int sys_start_hrvtime(void); +int sys_stop_hrvtime(void); + +#elif defined(HAVE_CLOCK_GETTIME) +#define sys_clock_gettime(cid,tp) clock_gettime((cid),&(tp)) +#define sys_get_proc_cputime(t,tp) sys_clock_gettime(CLOCK_PROCESS_CPUTIME_ID,(tp)) + +#endif +#endif + +/* No use in having other resolutions than 1 Ms. */ +#define SYS_CLOCK_RESOLUTION 1 + +/* These are defined in sys.c */ +#if defined(SIG_SIGSET) /* Old SysV */ +RETSIGTYPE (*sys_sigset())(); +#elif defined(SIG_SIGNAL) /* Old BSD */ +RETSIGTYPE (*sys_sigset())(); +#else +RETSIGTYPE (*sys_sigset(int, RETSIGTYPE (*func)(int)))(int); +#endif +extern void sys_sigrelease(int); +extern void sys_sigblock(int); +extern void sys_stop_cat(void); + +/* + * Handling of floating point exceptions. + */ + +#ifdef USE_ISINF_ISNAN /* simulate finite() */ +# define finite(f) (!isinf(f) && !isnan(f)) +# define HAVE_FINITE +#endif + +#ifdef NO_FPE_SIGNALS + +#define erts_get_current_fp_exception() NULL +#ifdef ERTS_SMP +#define erts_thread_init_fp_exception() do{}while(0) +#endif +# define __ERTS_FP_CHECK_INIT(fpexnp) do {} while (0) +# define __ERTS_FP_ERROR(fpexnp, f, Action) if (!finite(f)) { Action; } else {} +# define __ERTS_FP_ERROR_THOROUGH(fpexnp, f, Action) __ERTS_FP_ERROR(fpexnp, f, Action) +# define __ERTS_SAVE_FP_EXCEPTION(fpexnp) +# define __ERTS_RESTORE_FP_EXCEPTION(fpexnp) + +#define erts_sys_block_fpe() 0 +#define erts_sys_unblock_fpe(x) do{}while(0) + +#else /* !NO_FPE_SIGNALS */ + +extern volatile unsigned long *erts_get_current_fp_exception(void); +#ifdef ERTS_SMP +extern void erts_thread_init_fp_exception(void); +#endif +# if (defined(__i386__) || defined(__x86_64__)) && defined(__GNUC__) +# define erts_fwait(fpexnp,f) \ + __asm__ __volatile__("fwait" : "=m"(*(fpexnp)) : "m"(f)) +# elif (defined(__powerpc__) || defined(__ppc__)) && defined(__GNUC__) +# define erts_fwait(fpexnp,f) \ + __asm__ __volatile__("" : "=m"(*(fpexnp)) : "fm"(f)) +# elif defined(__sparc__) && defined(__linux__) && defined(__GNUC__) +# define erts_fwait(fpexnp,f) \ + __asm__ __volatile__("" : "=m"(*(fpexnp)) : "em"(f)) +# else +# define erts_fwait(fpexnp,f) \ + __asm__ __volatile__("" : "=m"(*(fpexnp)) : "g"(f)) +# endif +# if (defined(__i386__) || defined(__x86_64__)) && defined(__GNUC__) + extern void erts_restore_fpu(void); +# else +# define erts_restore_fpu() /*empty*/ +# endif +# if (!defined(__GNUC__) || \ + (__GNUC__ < 2) || \ + (__GNUC__ == 2 && __GNUC_MINOR < 96)) && \ + !defined(__builtin_expect) +# define __builtin_expect(x, expected_value) (x) +# endif +static __inline__ int erts_check_fpe(volatile unsigned long *fp_exception, double f) +{ + erts_fwait(fp_exception, f); + if (__builtin_expect(*fp_exception == 0, 1)) + return 0; + *fp_exception = 0; + erts_restore_fpu(); + return 1; +} +# undef erts_fwait +# undef erts_restore_fpu +extern void erts_fp_check_init_error(volatile unsigned long *fp_exception); +static __inline__ void __ERTS_FP_CHECK_INIT(volatile unsigned long *fp_exception) +{ + if (__builtin_expect(*fp_exception == 0, 1)) + return; + erts_fp_check_init_error(fp_exception); +} +# define __ERTS_FP_ERROR(fpexnp, f, Action) do { if (erts_check_fpe((fpexnp),(f))) { Action; } } while (0) +# define __ERTS_SAVE_FP_EXCEPTION(fpexnp) unsigned long old_erl_fp_exception = *(fpexnp) +# define __ERTS_RESTORE_FP_EXCEPTION(fpexnp) \ + do { *(fpexnp) = old_erl_fp_exception; } while (0) + /* This is for library calls where we don't trust the external + code to always throw floating-point exceptions on errors. */ +static __inline__ int erts_check_fpe_thorough(volatile unsigned long *fp_exception, double f) +{ + return erts_check_fpe(fp_exception, f) || !finite(f); +} +# define __ERTS_FP_ERROR_THOROUGH(fpexnp, f, Action) \ + do { if (erts_check_fpe_thorough((fpexnp),(f))) { Action; } } while (0) + +int erts_sys_block_fpe(void); +void erts_sys_unblock_fpe(int); + +#endif /* !NO_FPE_SIGNALS */ + +#define ERTS_FP_CHECK_INIT(p) __ERTS_FP_CHECK_INIT(&(p)->fp_exception) +#define ERTS_FP_ERROR(p, f, A) __ERTS_FP_ERROR(&(p)->fp_exception, f, A) +#define ERTS_FP_ERROR_THOROUGH(p, f, A) __ERTS_FP_ERROR_THOROUGH(&(p)->fp_exception, f, A) + + +#ifdef NEED_CHILD_SETUP_DEFINES +/* The child setup argv[] */ +#define CS_ARGV_PROGNAME_IX 0 /* Program name */ +#define CS_ARGV_UNBIND_IX 1 /* Unbind from cpu */ +#define CS_ARGV_WD_IX 2 /* Working directory */ +#define CS_ARGV_CMD_IX 3 /* Command */ +#define CS_ARGV_FD_CR_IX 4 /* Fd close range */ +#define CS_ARGV_DUP2_OP_IX(N) ((N) + 5) /* dup2 operations */ + +#define CS_ARGV_NO_OF_DUP2_OPS 3 /* Number of dup2 ops */ +#define CS_ARGV_NO_OF_ARGS 8 /* Number of arguments */ +#endif /* #ifdef NEED_CHILD_SETUP_DEFINES */ + +/* Threads */ +#ifdef USE_THREADS +extern int init_async(int); +extern int exit_async(void); +#endif + +#define ERTS_EXIT_AFTER_DUMP _exit + +#ifdef ERTS_TIMER_THREAD +struct erts_iwait; /* opaque for clients */ +extern struct erts_iwait *erts_iwait_init(void); +extern void erts_iwait_wait(struct erts_iwait *iwait, struct timeval *delay); +extern void erts_iwait_interrupt(struct erts_iwait *iwait); +#endif /* ERTS_TIMER_THREAD */ + +#endif /* #ifndef _ERL_UNIX_SYS_H */ diff --git a/erts/emulator/sys/unix/erl_unix_sys_ddll.c b/erts/emulator/sys/unix/erl_unix_sys_ddll.c new file mode 100644 index 0000000000..336d9586c4 --- /dev/null +++ b/erts/emulator/sys/unix/erl_unix_sys_ddll.c @@ -0,0 +1,280 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2006-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +/* + * Interface functions to the dynamic linker using dl* functions. + * (As far as I know it works on SunOS 4, 5, Linux and FreeBSD. /Seb) + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "sys.h" +#include "erl_vm.h" +#include "global.h" +#ifdef HAVE_DLFCN_H +#include <dlfcn.h> +#endif + + +/* some systems do not have RTLD_NOW defined, and require the "mode" + * argument to dload() always be 1. + */ +#ifndef RTLD_NOW +# define RTLD_NOW 1 +#endif + +#define MAX_NAME_LEN 255 /* XXX should we get the system path size? */ +#define EXT_LEN 3 +#define FILE_EXT ".so" /* extension appended to the filename */ + +static char **errcodes = NULL; +static int num_errcodes = 0; +static int num_errcodes_allocated = 0; + +#define my_strdup(WHAT) my_strdup_in(ERTS_ALC_T_DDLL_ERRCODES, WHAT); + +static char *my_strdup_in(ErtsAlcType_t type, char *what) +{ + char *res = erts_alloc(type, strlen(what) + 1); + strcpy(res, what); + return res; +} + + +static int find_errcode(char *string, ErtsSysDdllError* err) +{ + int i; + + if (err != NULL) { + erts_sys_ddll_free_error(err); /* in case we ignored an earlier error */ + err->str = my_strdup_in(ERTS_ALC_T_DDLL_TMP_BUF, string); + return 0; + } + for(i=0;i<num_errcodes;++i) { + if (!strcmp(string, errcodes[i])) { + return i; + } + } + if (num_errcodes_allocated == num_errcodes) { + errcodes = (num_errcodes_allocated == 0) + ? erts_alloc(ERTS_ALC_T_DDLL_ERRCODES, + (num_errcodes_allocated = 10) * sizeof(char *)) + : erts_realloc(ERTS_ALC_T_DDLL_ERRCODES, errcodes, + (num_errcodes_allocated += 10) * sizeof(char *)); + } + errcodes[num_errcodes++] = my_strdup(string); + return (num_errcodes - 1); +} + +void erl_sys_ddll_init(void) { +#if defined(HAVE_DLOPEN) && defined(ERTS_NEED_DLOPEN_BEFORE_DLERROR) + /* + * dlopen() needs to be called before we make the first call to + * dlerror(); otherwise, dlerror() might dump core. At least + * some versions of linuxthread suffer from this bug. + */ + void *handle = dlopen("/nonexistinglib", RTLD_NOW); + if (handle) + dlclose(handle); +#endif + return; +} + +/* + * Open a shared object + */ +int erts_sys_ddll_open2(char *full_name, void **handle, ErtsSysDdllError* err) +{ +#if defined(HAVE_DLOPEN) + char* dlname; + int len = sys_strlen(full_name); + int ret; + + dlname = erts_alloc(ERTS_ALC_T_TMP, len + EXT_LEN + 1); + sys_strcpy(dlname, full_name); + sys_strcpy(dlname+len, FILE_EXT); + + ret = erts_sys_ddll_open_noext(dlname, handle, err); + + erts_free(ERTS_ALC_T_TMP, (void *) dlname); + return ret; +#else + return ERL_DE_ERROR_NO_DDLL_FUNCTIONALITY; +#endif +} + +int erts_sys_ddll_open_noext(char *dlname, void **handle, ErtsSysDdllError* err) +{ + int ret = ERL_DE_NO_ERROR; + char *str; + dlerror(); + if ((*handle = dlopen(dlname, RTLD_NOW)) == NULL) { + str = dlerror(); + + if (err == NULL) { + /* + * Remove prefix filename to avoid exploading number of + * error codes on extreme usage. + */ + if (strstr(str,dlname) == str) { + char *save_str = str; + str += strlen(dlname); + while (*str == ':' || *str == ' ') { + ++str; + } + if (*str == '\0') { /* Better with filename than nothing... */ + str = save_str; + } + } + } + ret = ERL_DE_DYNAMIC_ERROR_OFFSET - find_errcode(str, err); + } + return ret; +} + +/* + * Find a symbol in the shared object + */ +int erts_sys_ddll_sym2(void *handle, char *func_name, void **function, + ErtsSysDdllError* err) +{ +#if defined(HAVE_DLOPEN) + void *sym; + char *e; + int ret; + dlerror(); + sym = dlsym(handle, func_name); + if ((e = dlerror()) != NULL) { + ret = ERL_DE_DYNAMIC_ERROR_OFFSET - find_errcode(e, err); + } else { + *function = sym; + ret = ERL_DE_NO_ERROR; + } + return ret; +#else + return ERL_DE_ERROR_NO_DDLL_FUNCTIONALITY; +#endif +} + +/* XXX:PaN These two will be changed with new driver interface! */ + +/* + * Load the driver init function, might appear under different names depending on object arch... + */ + +int erts_sys_ddll_load_driver_init(void *handle, void **function) +{ + void *fn; + int res; + if ((res = erts_sys_ddll_sym2(handle, "driver_init", &fn, NULL)) != ERL_DE_NO_ERROR) { + res = erts_sys_ddll_sym2(handle, "_driver_init", &fn, NULL); + } + if (res == ERL_DE_NO_ERROR) { + *function = fn; + } + return res; +} + +int erts_sys_ddll_load_nif_init(void *handle, void **function, ErtsSysDdllError* err) +{ + void *fn; + int res; + if ((res = erts_sys_ddll_sym2(handle, "nif_init", &fn, err)) != ERL_DE_NO_ERROR) { + res = erts_sys_ddll_sym2(handle, "_nif_init", &fn, err); + } + if (res == ERL_DE_NO_ERROR) { + *function = fn; + } + return res; +} + +/* + * Call the driver_init function, whatever it's really called, simple on unix... +*/ +void *erts_sys_ddll_call_init(void *function) { + void *(*initfn)(void) = function; + return (*initfn)(); +} +void *erts_sys_ddll_call_nif_init(void *function) { + return erts_sys_ddll_call_init(function); +} + + + +/* + * Close a chared object + */ +int erts_sys_ddll_close2(void *handle, ErtsSysDdllError* err) +{ +#if defined(HAVE_DLOPEN) + int ret; + char *s; + dlerror(); + if (dlclose(handle) == 0) { + ret = ERL_DE_NO_ERROR; + } else { + if ((s = dlerror()) == NULL) { + find_errcode("unspecified error", err); + ret = ERL_DE_ERROR_UNSPECIFIED; + } else { + ret = ERL_DE_DYNAMIC_ERROR_OFFSET - find_errcode(s, err); + } + } + return ret; +#else + return ERL_DE_ERROR_NO_DDLL_FUNCTIONALITY; +#endif +} + + +/* + * Return string that describes the (current) error + */ +char *erts_sys_ddll_error(int code) +{ + int actual_code; + + if (code > ERL_DE_DYNAMIC_ERROR_OFFSET) { + return "Unspecified error"; + } + actual_code = -1*(code - ERL_DE_DYNAMIC_ERROR_OFFSET); +#if defined(HAVE_DLOPEN) + { + char *msg; + + if (actual_code >= num_errcodes) { + msg = "Unknown dlload error"; + } else { + msg = errcodes[actual_code]; + } + return msg; + } +#endif + return "no error"; +} + +void erts_sys_ddll_free_error(ErtsSysDdllError* err) +{ + if (err->str != NULL) { + erts_free(ERTS_ALC_T_DDLL_TMP_BUF, err->str); + } +} + diff --git a/erts/emulator/sys/unix/sys.c b/erts/emulator/sys/unix/sys.c new file mode 100644 index 0000000000..183525b222 --- /dev/null +++ b/erts/emulator/sys/unix/sys.c @@ -0,0 +1,3346 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1996-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights 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> + +#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 NEED_CHILD_SETUP_DEFINES +#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" + +#ifdef USE_THREADS +#include "erl_threads.h" +#endif + +#include "erl_mseg.h" + +extern char **environ; +static 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_sys_driver.h" +#include "erl_check_io.h" + +#ifndef DISABLE_VFORK +#define DISABLE_VFORK 0 +#endif + +#ifdef USE_THREADS +# ifdef ENABLE_CHILD_WAITER_THREAD +# define CHLDWTHR ENABLE_CHILD_WAITER_THREAD +# else +# define CHLDWTHR 0 +# endif +#else +# define CHLDWTHR 0 +#endif +/* + * [OTP-3906] + * Solaris signal management gets confused when threads are used and a + * lot of child processes dies. The confusion results in that SIGCHLD + * signals aren't delivered to the emulator which in turn results in + * a lot of defunct processes in the system. + * + * The problem seems to appear when a signal is frequently + * blocked/unblocked at the same time as the signal is frequently + * propagated. The child waiter thread is a workaround for this problem. + * The SIGCHLD signal is always blocked (in all threads), and the child + * waiter thread fetches the signal by a call to sigwait(). See + * child_waiter(). + */ + +typedef struct ErtsSysReportExit_ ErtsSysReportExit; +struct ErtsSysReportExit_ { + ErtsSysReportExit *next; + Eterm port; + int pid; + int ifd; + int ofd; +#if CHLDWTHR && !defined(ERTS_SMP) + int status; +#endif +}; + +static ErtsSysReportExit *report_exit_list; +#if CHLDWTHR && !defined(ERTS_SMP) +static ErtsSysReportExit *report_exit_transit_list; +#endif + +extern int check_async_ready(void); +extern int driver_interrupt(int, int); +/*EXTERN_FUNCTION(void, increment_time, (int));*/ +/*EXTERN_FUNCTION(int, next_time, (_VOID_));*/ +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, ...); + +#define DIR_SEPARATOR_CHAR '/' + +#if defined(DEBUG) +#define ERL_BUILD_TYPE_MARKER ".debug" +#elif defined(PURIFY) +#define ERL_BUILD_TYPE_MARKER ".purify" +#elif defined(QUANTIFY) +#define ERL_BUILD_TYPE_MARKER ".quantify" +#elif defined(PURECOV) +#define ERL_BUILD_TYPE_MARKER ".purecov" +#elif defined(VALGRIND) +#define ERL_BUILD_TYPE_MARKER ".valgrind" +#else /* opt */ +#define ERL_BUILD_TYPE_MARKER +#endif + +#define CHILD_SETUP_PROG_NAME "child_setup" ERL_BUILD_TYPE_MARKER +#if !DISABLE_VFORK +static char *child_setup_prog; +#endif + +#ifdef DEBUG +static int debug_log = 0; +#endif + +#ifdef ERTS_SMP +erts_smp_atomic_t erts_got_sigusr1; +#define ERTS_SET_GOT_SIGUSR1 \ + erts_smp_atomic_set(&erts_got_sigusr1, 1) +#define ERTS_UNSET_GOT_SIGUSR1 \ + erts_smp_atomic_set(&erts_got_sigusr1, 0) +static erts_smp_atomic_t have_prepared_crash_dump; +#define ERTS_PREPARED_CRASH_DUMP \ + ((int) erts_smp_atomic_xchg(&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 + +static 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}; +#elif defined(USE_THREADS) +static int async_fd[2]; +#endif + +#if CHLDWTHR || defined(ERTS_SMP) +erts_mtx_t chld_stat_mtx; +#endif +#if CHLDWTHR +static erts_tid_t child_waiter_tid; +/* chld_stat_mtx is used to protect against concurrent accesses + of the driver_data fields pid, alive, and status. */ +erts_cnd_t chld_stat_cnd; +static long children_alive; +#define CHLD_STAT_LOCK erts_mtx_lock(&chld_stat_mtx) +#define CHLD_STAT_UNLOCK erts_mtx_unlock(&chld_stat_mtx) +#define CHLD_STAT_WAIT erts_cnd_wait(&chld_stat_cnd, &chld_stat_mtx) +#define CHLD_STAT_SIGNAL erts_cnd_signal(&chld_stat_cnd) +#elif defined(ERTS_SMP) /* ------------------------------------------------- */ +#define CHLD_STAT_LOCK erts_mtx_lock(&chld_stat_mtx) +#define CHLD_STAT_UNLOCK erts_mtx_unlock(&chld_stat_mtx) + +#else /* ------------------------------------------------------------------- */ +#define CHLD_STAT_LOCK +#define CHLD_STAT_UNLOCK +static volatile int children_died; +#endif + + +static struct fd_data { + char pbuf[4]; /* hold partial packet bytes */ + int psz; /* size of pbuf */ + char *buf; + char *cpos; + int sz; + int remain; /* for input on fd */ +} *fd_data; /* indexed by fd */ + +/* static FUNCTION(int, write_fill, (int, char*, int)); unused? */ +static FUNCTION(void, note_child_death, (int, int)); + +#if CHLDWTHR +static FUNCTION(void *, child_waiter, (void *)); +#endif + +/********************* 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_atomic_t erts_break_requested; +#define ERTS_SET_BREAK_REQUESTED \ + erts_smp_atomic_set(&erts_break_requested, (long) 1) +#define ERTS_UNSET_BREAK_REQUESTED \ + erts_smp_atomic_set(&erts_break_requested, (long) 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_interrupt)(int); + void (*check_io_interrupt_tmd)(int, long); + void (*check_io)(int); + Uint (*size)(void); + Eterm (*info)(void *); + int (*check_io_debug)(void); +} 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(void) +{ + return (*io_func.check_io_debug)(); +} + + +static void +init_check_io(void) +{ + if (erts_use_kernel_poll) { + io_func.select = driver_select_kp; + io_func.event = driver_event_kp; + 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; + 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(); + } +} + +#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(); +} + +#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 + +#ifdef ERTS_SMP +void +erts_sys_schedule_interrupt(int set) +{ + ERTS_CHK_IO_INTR(set); +} + +void +erts_sys_schedule_interrupt_timed(int set, long msec) +{ + ERTS_CHK_IO_INTR_TMD(set, msec); +} +#endif + +Uint +erts_sys_misc_mem_sz(void) +{ + Uint res = ERTS_CHK_IO_SZ(); + res += erts_smp_atomic_read(&sys_misc_mem_sz); + return res; +} + +/* + * reset the terminal to the original settings on exit + */ +void sys_tty_reset(void) +{ + 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> +MALLOC_USE_HASH(1); +#endif + +#ifdef USE_THREADS +static void *ethr_internal_alloc(size_t size) +{ + return erts_alloc_fnf(ERTS_ALC_T_ETHR_INTERNAL, (Uint) size); +} +static void *ethr_internal_realloc(void *ptr, size_t size) +{ + return erts_realloc_fnf(ERTS_ALC_T_ETHR_INTERNAL, ptr, (Uint) size); +} +static void ethr_internal_free(void *ptr) +{ + erts_free(ERTS_ALC_T_ETHR_INTERNAL, ptr); +} + +#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, SIGCHLD, 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 unbind_child; +} 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; + ErtsSchedulerData *esdp; + + 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 + esdp = erts_get_scheduler_data(); + tcdp->unbind_child = esdp && erts_is_scheduler_bound(esdp); + + 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; + +#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; + +#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 + + if (tcdp->unbind_child) { + erts_smp_rwmtx_rlock(&erts_cpu_bind_rwmtx); + erts_unbind_from_cpu(erts_cpuinfo); + erts_smp_rwmtx_runlock(&erts_cpu_bind_rwmtx); + } + +} + +#endif /* #ifdef USE_THREADS */ + +void +erts_sys_pre_init(void) +{ + erts_printf_add_cr_to_stdout = 1; + erts_printf_add_cr_to_stderr = 1; +#ifdef USE_THREADS + { + erts_thr_init_data_t eid = ERTS_THR_INIT_DATA_DEF_INITER; + eid.alloc = ethr_internal_alloc; + eid.realloc = ethr_internal_realloc; + eid.free = ethr_internal_free; + + 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, SIGCHLD); /* block child signals */ + sigaddset(&thr_create_sigmask, SIGUSR1); /* block user defined signal */ +#endif + + erts_thr_init(&eid); + + report_exit_list = NULL; + +#ifdef ERTS_ENABLE_LOCK_COUNT + erts_lcnt_init(); +#endif + +#if CHLDWTHR || defined(ERTS_SMP) + erts_mtx_init(&chld_stat_mtx, "child_status"); +#endif +#if CHLDWTHR +#ifndef ERTS_SMP + report_exit_transit_list = NULL; +#endif + erts_cnd_init(&chld_stat_cnd); + children_alive = 0; +#endif + } +#ifdef ERTS_SMP + erts_smp_atomic_init(&erts_break_requested, 0); + erts_smp_atomic_init(&erts_got_sigusr1, 0); + erts_smp_atomic_init(&have_prepared_crash_dump, 0); +#else + erts_break_requested = 0; + erts_got_sigusr1 = 0; + have_prepared_crash_dump = 0; +#endif +#if !CHLDWTHR && !defined(ERTS_SMP) + children_died = 0; +#endif +#endif /* USE_THREADS */ + erts_smp_atomic_init(&sys_misc_mem_sz, 0); + erts_smp_rwmtx_init(&environ_rwmtx, "environ"); +} + +void +erl_sys_init(void) +{ +#if !DISABLE_VFORK + int res; + char bindir[MAXPATHLEN]; + size_t bindirsz = sizeof(bindir); + Uint csp_path_sz; + + res = erts_sys_getenv("BINDIR", bindir, &bindirsz); + if (res != 0) { + if (res < 0) + erl_exit(-1, + "Environment variable BINDIR is not set\n"); + if (res > 0) + erl_exit(-1, + "Value of environment variable BINDIR is too large\n"); + } + if (bindir[0] != DIR_SEPARATOR_CHAR) + erl_exit(-1, + "Environment variable BINDIR does not contain an" + " absolute path\n"); + csp_path_sz = (strlen(bindir) + + 1 /* DIR_SEPARATOR_CHAR */ + + sizeof(CHILD_SETUP_PROG_NAME) + + 1); + child_setup_prog = erts_alloc(ERTS_ALC_T_CS_PROG_PATH, csp_path_sz); + erts_smp_atomic_add(&sys_misc_mem_sz, csp_path_sz); + sprintf(child_setup_prog, + "%s%c%s", + bindir, + DIR_SEPARATOR_CHAR, + CHILD_SETUP_PROG_NAME); +#endif + +#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 */ + +#ifdef SIG_SIGSET /* Old SysV */ +RETSIGTYPE (*sys_sigset(sig, func))() +int sig; +RETSIGTYPE (*func)(); +{ + return(sigset(sig, func)); +} +void sys_sigblock(int sig) +{ + sighold(sig); +} +void sys_sigrelease(int sig) +{ + sigrelse(sig); +} +#else /* !SIG_SIGSET */ +#ifdef SIG_SIGNAL /* Old BSD */ +RETSIGTYPE (*sys_sigset(sig, func))(int, int) +int sig; +RETSIGTYPE (*func)(); +{ + return(signal(sig, func)); +} +sys_sigblock(int sig) +{ + sigblock(sig); +} +sys_sigrelease(int sig) +{ + sigsetmask(sigblock(0) & ~sigmask(sig)); +} +#else /* !SIG_SIGNAL */ /* The True Way - POSIX!:-) */ +RETSIGTYPE (*sys_sigset(int sig, RETSIGTYPE (*func)(int)))(int) +{ + 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); +} +#endif /* !SIG_SIGNAL */ +#endif /* !SIG_SIGSET */ + +#if (0) /* not used? -- gordon */ +static void (*break_func)(); +static RETSIGTYPE break_handler(int sig) +{ +#ifdef QNX + /* Turn off SIGCHLD during break processing */ + sys_sigblock(SIGCHLD); +#endif + (*break_func)(); +#ifdef QNX + sys_sigrelease(SIGCHLD); +#endif +} +#endif /* 0 */ + +static ERTS_INLINE void +prepare_crash_dump(void) +{ + int i, max; + char env[21]; /* enough to hold any 64-bit integer */ + size_t envsz; + + if (ERTS_PREPARED_CRASH_DUMP) + return; /* We have already been called */ + + /* Make sure we unregister at epmd (unknown fd) and get at least + one free filedescriptor (for erl_crash.dump) */ + max = max_files; + if (max < 1024) + max = 1024; + for (i = 3; i < max; i++) { +#if defined(ERTS_SMP) + /* We don't want to close the signal notification pipe... */ + if (i == sig_notify_fds[0] || i == sig_notify_fds[1]) + continue; +#elif defined(USE_THREADS) + /* We don't want to close the async notification pipe... */ + if (i == async_fd[0] || i == async_fd[1]) + continue; +#endif + close(i); + } + + 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; + } + nice(nice_val); + } + + envsz = sizeof(env); + i = erts_sys_getenv("ERL_CRASH_DUMP_SECONDS", env, &envsz); + if (i >= 0) { + unsigned sec; + sec = (unsigned) i != 0 ? 0 : atoi(env); + alarm(sec); + } + +} + +void +erts_sys_prepare_crash_dump(void) +{ + prepare_crash_dump(); +} + +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) + erl_exit(ERTS_INTR_EXIT, ""); + + ERTS_SET_BREAK_REQUESTED; + ERTS_CHK_IO_INTR(1); /* 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) +{ + /* 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; + prepare_crash_dump(); + erl_exit(1, "Received SIGUSR1\n"); +} + +#ifdef ETHR_UNUSABLE_SIGUSRX +#warning "Unusable SIGUSR1 & SIGUSR2. Disabling use of these signals" +#endif + +#ifndef ETHR_UNUSABLE_SIGUSRX + +#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 QUANTIFY +#if (defined(SIG_SIGSET) || defined(SIG_SIGNAL)) +static RETSIGTYPE user_signal2(void) +#else +static RETSIGTYPE user_signal2(int signum) +#endif +{ +#ifdef ERTS_SMP + smp_sig_notify('2'); +#else + quantify_save_data(); +#endif +} +#endif + +#endif /* #ifndef ETHR_UNUSABLE_SIGUSRX */ + +static void +quit_requested(void) +{ + erl_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_sigset(SIGINT, SIG_IGN); + sys_sigset(SIGQUIT, SIG_IGN); + sys_sigset(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_sigset(SIGINT, request_break); +#ifndef ETHR_UNUSABLE_SIGUSRX + sys_sigset(SIGUSR1, user_signal1); +#ifdef QUANTIFY + sys_sigset(SIGUSR2, user_signal2); +#endif +#endif /* #ifndef ETHR_UNUSABLE_SIGUSRX */ + sys_sigset(SIGQUIT, do_quit); +} + +int sys_max_files(void) +{ + return(max_files); +} + +static void block_signals(void) +{ +#if !CHLDWTHR + sys_sigblock(SIGCHLD); +#endif +#ifndef ERTS_SMP + sys_sigblock(SIGINT); +#ifndef ETHR_UNUSABLE_SIGUSRX + sys_sigblock(SIGUSR1); +#endif +#endif +} + +static void unblock_signals(void) +{ + /* Update erl_child_setup.c if changed */ +#if !CHLDWTHR + sys_sigrelease(SIGCHLD); +#endif +#ifndef ERTS_SMP + sys_sigrelease(SIGINT); +#ifndef ETHR_UNUSABLE_SIGUSRX + sys_sigrelease(SIGUSR1); +#endif /* #ifndef ETHR_UNUSABLE_SIGUSRX */ +#endif +} +/************************** Time stuff **************************/ +#ifdef HAVE_GETHRTIME +#ifdef GETHRTIME_WITH_CLOCK_GETTIME + +SysHrTime sys_gethrtime(void) +{ + struct timespec ts; + long long result; + if (clock_gettime(CLOCK_MONOTONIC,&ts) != 0) { + erl_exit(1,"Fatal, could not get clock_monotonic value!, " + "errno = %d\n", errno); + } + result = ((long long) ts.tv_sec) * 1000000000LL + + ((long long) ts.tv_nsec); + return (SysHrTime) result; +} +#endif +#endif + +/************************** 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. */ +{ + static int called = 0; + static struct utsname uts; /* Information about the system. */ + + if (!called) { + char* s; + + (void) uname(&uts); + called = 1; + 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); +} + + +/************************** Port I/O *******************************/ + + + +/* I. Common stuff */ + +/* + * Decreasing the size of it below 16384 is not allowed. + */ + +/* II. The spawn/fd/vanilla drivers */ + +#define ERTS_SYS_READ_BUF_SZ (64*1024) + +/* This data is shared by these drivers - initialized by spawn_init() */ +static struct driver_data { + int port_num, ofd, packet_bytes; + ErtsSysReportExit *report_exit; + int pid; + int alive; + int status; +} *driver_data; /* indexed by fd */ + +/* Driver interfaces */ +static ErlDrvData spawn_start(ErlDrvPort, char*, SysDriverOpts*); +static ErlDrvData fd_start(ErlDrvPort, char*, SysDriverOpts*); +static int fd_control(ErlDrvData, unsigned int, char *, int, char **, int); +static ErlDrvData vanilla_start(ErlDrvPort, char*, SysDriverOpts*); +static int spawn_init(void); +static void fd_stop(ErlDrvData); +static void stop(ErlDrvData); +static void ready_input(ErlDrvData, ErlDrvEvent); +static void ready_output(ErlDrvData, ErlDrvEvent); +static void output(ErlDrvData, char*, int); +static void outputv(ErlDrvData, ErlIOVec*); +static void stop_select(ErlDrvEvent, void*); + +struct erl_drv_entry spawn_driver_entry = { + spawn_init, + spawn_start, + stop, + output, + ready_input, + ready_output, + "spawn", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + ERL_DRV_EXTENDED_MARKER, + ERL_DRV_EXTENDED_MAJOR_VERSION, + ERL_DRV_EXTENDED_MINOR_VERSION, + ERL_DRV_FLAG_USE_PORT_LOCKING, + NULL, NULL, + stop_select +}; +struct erl_drv_entry fd_driver_entry = { + NULL, + fd_start, + fd_stop, + output, + ready_input, + ready_output, + "fd", + NULL, + NULL, + fd_control, + NULL, + outputv, + NULL, /* ready_async */ + NULL, /* flush */ + NULL, /* call */ + NULL, /* event */ + ERL_DRV_EXTENDED_MARKER, + ERL_DRV_EXTENDED_MAJOR_VERSION, + ERL_DRV_EXTENDED_MINOR_VERSION, + 0, /* ERL_DRV_FLAGs */ + NULL, /* handle2 */ + NULL, /* process_exit */ + stop_select +}; +struct erl_drv_entry vanilla_driver_entry = { + NULL, + vanilla_start, + stop, + output, + ready_input, + ready_output, + "vanilla", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, /* flush */ + NULL, /* call */ + NULL, /* event */ + ERL_DRV_EXTENDED_MARKER, + ERL_DRV_EXTENDED_MAJOR_VERSION, + ERL_DRV_EXTENDED_MINOR_VERSION, + 0, /* ERL_DRV_FLAGs */ + NULL, /* handle2 */ + NULL, /* process_exit */ + stop_select +}; + +#if defined(USE_THREADS) && !defined(ERTS_SMP) +static int async_drv_init(void); +static ErlDrvData async_drv_start(ErlDrvPort, char*, SysDriverOpts*); +static void async_drv_stop(ErlDrvData); +static void async_drv_input(ErlDrvData, ErlDrvEvent); + +/* INTERNAL use only */ + +struct erl_drv_entry async_driver_entry = { + async_drv_init, + async_drv_start, + async_drv_stop, + NULL, + async_drv_input, + NULL, + "async", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; +#endif + +/* Handle SIGCHLD signals. */ +#if (defined(SIG_SIGSET) || defined(SIG_SIGNAL)) +static RETSIGTYPE onchld(void) +#else +static RETSIGTYPE onchld(int signum) +#endif +{ +#if CHLDWTHR + ASSERT(0); /* We should *never* catch a SIGCHLD signal */ +#elif defined(ERTS_SMP) + smp_sig_notify('C'); +#else + children_died = 1; + ERTS_CHK_IO_INTR(1); /* Make sure we don't sleep in poll */ +#endif +} + +static int set_driver_data(int port_num, + int ifd, + int ofd, + int packet_bytes, + int read_write, + int exit_status, + int pid) +{ + ErtsSysReportExit *report_exit; + + if (!exit_status) + report_exit = NULL; + else { + report_exit = erts_alloc(ERTS_ALC_T_PRT_REP_EXIT, + sizeof(ErtsSysReportExit)); + report_exit->next = report_exit_list; + report_exit->port = erts_port[port_num].id; + report_exit->pid = pid; + report_exit->ifd = read_write & DO_READ ? ifd : -1; + report_exit->ofd = read_write & DO_WRITE ? ofd : -1; +#if CHLDWTHR && !defined(ERTS_SMP) + report_exit->status = 0; +#endif + report_exit_list = report_exit; + } + + if (read_write & DO_READ) { + driver_data[ifd].packet_bytes = packet_bytes; + driver_data[ifd].port_num = port_num; + driver_data[ifd].report_exit = report_exit; + driver_data[ifd].pid = pid; + driver_data[ifd].alive = 1; + driver_data[ifd].status = 0; + if (read_write & DO_WRITE) { + driver_data[ifd].ofd = ofd; + if (ifd != ofd) + driver_data[ofd] = driver_data[ifd]; /* structure copy */ + } else { /* DO_READ only */ + driver_data[ifd].ofd = -1; + } + (void) driver_select(port_num, ifd, (ERL_DRV_READ|ERL_DRV_USE), 1); + return(ifd); + } else { /* DO_WRITE only */ + driver_data[ofd].packet_bytes = packet_bytes; + driver_data[ofd].port_num = port_num; + driver_data[ofd].report_exit = report_exit; + driver_data[ofd].ofd = ofd; + driver_data[ofd].pid = pid; + driver_data[ofd].alive = 1; + driver_data[ofd].status = 0; + return(ofd); + } +} + +static int spawn_init() +{ + int i; +#if CHLDWTHR + erts_thr_opts_t thr_opts = ERTS_THR_OPTS_DEFAULT_INITER; + thr_opts.detached = 0; + thr_opts.suggested_stack_size = 0; /* Smallest possible */ +#endif + + sys_sigset(SIGPIPE, SIG_IGN); /* Ignore - we'll handle the write failure */ + driver_data = (struct driver_data *) + erts_alloc(ERTS_ALC_T_DRV_TAB, max_files * sizeof(struct driver_data)); + erts_smp_atomic_add(&sys_misc_mem_sz, + max_files * sizeof(struct driver_data)); + + for (i = 0; i < max_files; i++) + driver_data[i].pid = -1; + +#if CHLDWTHR + sys_sigblock(SIGCHLD); +#endif + + sys_sigset(SIGCHLD, onchld); /* Reap children */ + +#if CHLDWTHR + erts_thr_create(&child_waiter_tid, child_waiter, NULL, &thr_opts); +#endif + + return 1; +} + +static void close_pipes(int ifd[2], int ofd[2], int read_write) +{ + if (read_write & DO_READ) { + (void) close(ifd[0]); + (void) close(ifd[1]); + } + if (read_write & DO_WRITE) { + (void) close(ofd[0]); + (void) close(ofd[1]); + } +} + +static void init_fd_data(int fd, int prt) +{ + fd_data[fd].buf = NULL; + fd_data[fd].cpos = NULL; + fd_data[fd].remain = 0; + fd_data[fd].sz = 0; + fd_data[fd].psz = 0; +} + +static char **build_unix_environment(char *block) +{ + int i; + int j; + int len; + char *cp; + char **cpp; + char** old_env; + + ERTS_SMP_LC_ASSERT(erts_smp_lc_rwmtx_is_rlocked(&environ_rwmtx)); + + cp = block; + len = 0; + while (*cp != '\0') { + cp += strlen(cp) + 1; + len++; + } + old_env = environ; + while (*old_env++ != NULL) { + len++; + } + + cpp = (char **) erts_alloc_fnf(ERTS_ALC_T_ENVIRONMENT, + sizeof(char *) * (len+1)); + if (cpp == NULL) { + return NULL; + } + + cp = block; + len = 0; + while (*cp != '\0') { + cpp[len] = cp; + cp += strlen(cp) + 1; + len++; + } + + i = len; + for (old_env = environ; *old_env; old_env++) { + char* old = *old_env; + + for (j = 0; j < len; j++) { + char *s, *t; + + s = cpp[j]; + t = old; + while (*s == *t && *s != '=') { + s++, t++; + } + if (*s == '=' && *t == '=') { + break; + } + } + + if (j == len) { /* New version not found */ + cpp[len++] = old; + } + } + + for (j = 0; j < i; j++) { + if (cpp[j][strlen(cpp[j])-1] == '=') { + cpp[j] = cpp[--len]; + } + } + + cpp[len] = NULL; + return cpp; +} + +/* + [arndt] In most Unix systems, including Solaris 2.5, 'fork' allocates memory + in swap space for the child of a 'fork', whereas 'vfork' does not do this. + The natural call to use here is therefore 'vfork'. Due to a bug in + 'vfork' in Solaris 2.5 (apparently fixed in 2.6), using 'vfork' + can be dangerous in what seems to be these circumstances: + If the child code under a vfork sets the signal action to SIG_DFL + (or SIG_IGN) + for any signal which was previously set to a signal handler, the + state of the parent is clobbered, so that the later arrival of + such a signal yields a sigsegv in the parent. If the signal was + not set to a signal handler, but ignored, all seems to work. + If you change the forking code below, beware of this. + */ + +static ErlDrvData spawn_start(ErlDrvPort port_num, char* name, SysDriverOpts* opts) +{ +#define CMD_LINE_PREFIX_STR "exec " +#define CMD_LINE_PREFIX_STR_SZ (sizeof(CMD_LINE_PREFIX_STR) - 1) + + int ifd[2], ofd[2], len, pid, i; + char **volatile new_environ; /* volatile since a vfork() then cannot + cause 'new_environ' to be clobbered + in the parent process. */ + int saved_errno; + long res; + char *cmd_line; +#ifndef QNX + int unbind; +#endif +#if !DISABLE_VFORK + int no_vfork; + size_t no_vfork_sz = sizeof(no_vfork); + + no_vfork = (erts_sys_getenv("ERL_NO_VFORK", + (char *) &no_vfork, + &no_vfork_sz) >= 0); +#endif + + switch (opts->read_write) { + case DO_READ: + if (pipe(ifd) < 0) + return ERL_DRV_ERROR_ERRNO; + if (ifd[0] >= max_files) { + close_pipes(ifd, ofd, opts->read_write); + errno = EMFILE; + return ERL_DRV_ERROR_ERRNO; + } + ofd[1] = -1; /* keep purify happy */ + break; + case DO_WRITE: + if (pipe(ofd) < 0) return ERL_DRV_ERROR_ERRNO; + if (ofd[1] >= max_files) { + close_pipes(ifd, ofd, opts->read_write); + errno = EMFILE; + return ERL_DRV_ERROR_ERRNO; + } + ifd[0] = -1; /* keep purify happy */ + break; + case DO_READ|DO_WRITE: + if (pipe(ifd) < 0) return ERL_DRV_ERROR_ERRNO; + errno = EMFILE; /* default for next two conditions */ + if (ifd[0] >= max_files || pipe(ofd) < 0) { + close_pipes(ifd, ofd, DO_READ); + return ERL_DRV_ERROR_ERRNO; + } + if (ofd[1] >= max_files) { + close_pipes(ifd, ofd, opts->read_write); + errno = EMFILE; + return ERL_DRV_ERROR_ERRNO; + } + break; + default: + ASSERT(0); + return ERL_DRV_ERROR_GENERAL; + } + + if (opts->spawn_type == ERTS_SPAWN_EXECUTABLE) { + /* started with spawn_executable, not with spawn */ + len = strlen(name); + cmd_line = (char *) erts_alloc_fnf(ERTS_ALC_T_TMP, len + 1); + if (!cmd_line) { + close_pipes(ifd, ofd, opts->read_write); + errno = ENOMEM; + return ERL_DRV_ERROR_ERRNO; + } + memcpy((void *) cmd_line,(void *) name, len); + cmd_line[len] = '\0'; + if (access(cmd_line,X_OK) != 0) { + int save_errno = errno; + erts_free(ERTS_ALC_T_TMP, cmd_line); + errno = save_errno; + return ERL_DRV_ERROR_ERRNO; + } + } else { + /* make the string suitable for giving to "sh" */ + len = strlen(name); + cmd_line = (char *) erts_alloc_fnf(ERTS_ALC_T_TMP, + CMD_LINE_PREFIX_STR_SZ + len + 1); + if (!cmd_line) { + close_pipes(ifd, ofd, opts->read_write); + errno = ENOMEM; + return ERL_DRV_ERROR_ERRNO; + } + memcpy((void *) cmd_line, + (void *) CMD_LINE_PREFIX_STR, + CMD_LINE_PREFIX_STR_SZ); + memcpy((void *) (cmd_line + CMD_LINE_PREFIX_STR_SZ), (void *) name, len); + cmd_line[CMD_LINE_PREFIX_STR_SZ + len] = '\0'; + } + + erts_smp_rwmtx_rlock(&environ_rwmtx); + + if (opts->envir == NULL) { + new_environ = environ; + } else if ((new_environ = build_unix_environment(opts->envir)) == NULL) { + erts_smp_rwmtx_runlock(&environ_rwmtx); + erts_free(ERTS_ALC_T_TMP, (void *) cmd_line); + errno = ENOMEM; + return ERL_DRV_ERROR_ERRNO; + } + +#ifndef QNX + /* Block child from SIGINT and SIGUSR1. Must be before fork() + to be safe. */ + block_signals(); + + CHLD_STAT_LOCK; + + unbind = erts_is_scheduler_bound(NULL); + if (unbind) + erts_smp_rwmtx_rlock(&erts_cpu_bind_rwmtx); + +#if !DISABLE_VFORK + /* See fork/vfork discussion before this function. */ + if (no_vfork) { +#endif + + DEBUGF(("Using fork\n")); + pid = fork(); + + if (pid == 0) { + /* The child! Setup child... */ + + if (unbind && erts_unbind_from_cpu(erts_cpuinfo) != 0) + goto child_error; + + /* OBSERVE! + * Keep child setup after vfork() (implemented below and in + * erl_child_setup.c) up to date if changes are made here. + */ + + if (opts->use_stdio) { + if (opts->read_write & DO_READ) { + /* stdout for process */ + if (dup2(ifd[1], 1) < 0) + goto child_error; + if(opts->redir_stderr) + /* stderr for process */ + if (dup2(ifd[1], 2) < 0) + goto child_error; + } + if (opts->read_write & DO_WRITE) + /* stdin for process */ + if (dup2(ofd[0], 0) < 0) + goto child_error; + } + else { /* XXX will fail if ofd[0] == 4 (unlikely..) */ + if (opts->read_write & DO_READ) + if (dup2(ifd[1], 4) < 0) + goto child_error; + if (opts->read_write & DO_WRITE) + if (dup2(ofd[0], 3) < 0) + goto child_error; + } + + for (i = opts->use_stdio ? 3 : 5; i < max_files; i++) + (void) close(i); + + if (opts->wd && chdir(opts->wd) < 0) + goto child_error; + +#if defined(USE_SETPGRP_NOARGS) /* SysV */ + (void) setpgrp(); +#elif defined(USE_SETPGRP) /* BSD */ + (void) setpgrp(0, getpid()); +#else /* POSIX */ + (void) setsid(); +#endif + + unblock_signals(); + + if (opts->spawn_type == ERTS_SPAWN_EXECUTABLE) { + if (opts->argv == NULL) { + execle(cmd_line,cmd_line,(char *) NULL, new_environ); + } else { + if (opts->argv[0] == erts_default_arg0) { + opts->argv[0] = cmd_line; + } + execve(cmd_line, opts->argv, new_environ); + if (opts->argv[0] == cmd_line) { + opts->argv[0] = erts_default_arg0; + } + } + } else { + execle("/bin/sh", "sh", "-c", cmd_line, (char *) NULL, new_environ); + } + child_error: + _exit(1); + } +#if !DISABLE_VFORK + } + else { /* Use vfork() */ + char **cs_argv= erts_alloc(ERTS_ALC_T_TMP,(CS_ARGV_NO_OF_ARGS + 1)* + sizeof(char *)); + char fd_close_range[44]; /* 44 bytes are enough to */ + char dup2_op[CS_ARGV_NO_OF_DUP2_OPS][44]; /* hold any "%d:%d" string */ + /* on a 64-bit machine. */ + + /* Setup argv[] for the child setup program (implemented in + erl_child_setup.c) */ + i = 0; + if (opts->use_stdio) { + if (opts->read_write & DO_READ){ + /* stdout for process */ + sprintf(&dup2_op[i++][0], "%d:%d", ifd[1], 1); + if(opts->redir_stderr) + /* stderr for process */ + sprintf(&dup2_op[i++][0], "%d:%d", ifd[1], 2); + } + if (opts->read_write & DO_WRITE) + /* stdin for process */ + sprintf(&dup2_op[i++][0], "%d:%d", ofd[0], 0); + } else { /* XXX will fail if ofd[0] == 4 (unlikely..) */ + if (opts->read_write & DO_READ) + sprintf(&dup2_op[i++][0], "%d:%d", ifd[1], 4); + if (opts->read_write & DO_WRITE) + sprintf(&dup2_op[i++][0], "%d:%d", ofd[0], 3); + } + for (; i < CS_ARGV_NO_OF_DUP2_OPS; i++) + strcpy(&dup2_op[i][0], "-"); + sprintf(fd_close_range, "%d:%d", opts->use_stdio ? 3 : 5, max_files-1); + + cs_argv[CS_ARGV_PROGNAME_IX] = child_setup_prog; + cs_argv[CS_ARGV_WD_IX] = opts->wd ? opts->wd : "."; + cs_argv[CS_ARGV_UNBIND_IX] + = (unbind ? erts_get_unbind_from_cpu_str(erts_cpuinfo) : "false"); + cs_argv[CS_ARGV_FD_CR_IX] = fd_close_range; + for (i = 0; i < CS_ARGV_NO_OF_DUP2_OPS; i++) + cs_argv[CS_ARGV_DUP2_OP_IX(i)] = &dup2_op[i][0]; + + if (opts->spawn_type == ERTS_SPAWN_EXECUTABLE) { + int num = 0; + int j = 0; + if (opts->argv != NULL) { + for(; opts->argv[num] != NULL; ++num) + ; + } + cs_argv = erts_realloc(ERTS_ALC_T_TMP,cs_argv, (CS_ARGV_NO_OF_ARGS + 1 + num + 1) * sizeof(char *)); + cs_argv[CS_ARGV_CMD_IX] = "-"; + cs_argv[CS_ARGV_NO_OF_ARGS] = cmd_line; + if (opts->argv != NULL) { + for (;opts->argv[j] != NULL; ++j) { + if (opts->argv[j] == erts_default_arg0) { + cs_argv[CS_ARGV_NO_OF_ARGS + 1 + j] = cmd_line; + } else { + cs_argv[CS_ARGV_NO_OF_ARGS + 1 + j] = opts->argv[j]; + } + } + } + cs_argv[CS_ARGV_NO_OF_ARGS + 1 + j] = NULL; + } else { + cs_argv[CS_ARGV_CMD_IX] = cmd_line; /* Command */ + cs_argv[CS_ARGV_NO_OF_ARGS] = NULL; + } + DEBUGF(("Using vfork\n")); + pid = vfork(); + + if (pid == 0) { + /* The child! */ + + /* Observe! + * OTP-4389: The child setup program (implemented in + * erl_child_setup.c) will perform the necessary setup of the + * child before it execs to the user program. This because + * vfork() only allow an *immediate* execve() or _exit() in the + * child. + */ + execve(child_setup_prog, cs_argv, new_environ); + _exit(1); + } + erts_free(ERTS_ALC_T_TMP,cs_argv); + } +#endif + + if (unbind) + erts_smp_rwmtx_runlock(&erts_cpu_bind_rwmtx); + + if (pid == -1) { + saved_errno = errno; + CHLD_STAT_UNLOCK; + erts_smp_rwmtx_runlock(&environ_rwmtx); + erts_free(ERTS_ALC_T_TMP, (void *) cmd_line); + unblock_signals(); + close_pipes(ifd, ofd, opts->read_write); + errno = saved_errno; + return ERL_DRV_ERROR_ERRNO; + } +#else /* QNX */ + if (opts->use_stdio) { + if (opts->read_write & DO_READ) + qnx_spawn_options.iov[1] = ifd[1]; /* stdout for process */ + if (opts->read_write & DO_WRITE) + qnx_spawn_options.iov[0] = ofd[0]; /* stdin for process */ + } + else { + if (opts->read_write & DO_READ) + qnx_spawn_options.iov[4] = ifd[1]; + if (opts->read_write & DO_WRITE) + qnx_spawn_options.iov[3] = ofd[0]; + } + /* Close fds on exec */ + for (i = 3; i < max_files; i++) + fcntl(i, F_SETFD, 1); + + qnx_spawn_options.flags = _SPAWN_SETSID; + if ((pid = spawnl(P_NOWAIT, "/bin/sh", "/bin/sh", "-c", cmd_line, + (char *) 0)) < 0) { + erts_free(ERTS_ALC_T_TMP, (void *) cmd_line); + reset_qnx_spawn(); + erts_smp_rwmtx_runlock(&environ_rwmtx); + close_pipes(ifd, ofd, opts->read_write); + return ERL_DRV_ERROR_GENERAL; + } + reset_qnx_spawn(); +#endif /* QNX */ + + erts_free(ERTS_ALC_T_TMP, (void *) cmd_line); + + if (new_environ != environ) + erts_free(ERTS_ALC_T_ENVIRONMENT, (void *) new_environ); + + if (opts->read_write & DO_READ) + (void) close(ifd[1]); + if (opts->read_write & DO_WRITE) + (void) close(ofd[0]); + + if (opts->read_write & DO_READ) { + SET_NONBLOCKING(ifd[0]); + init_fd_data(ifd[0], port_num); + } + if (opts->read_write & DO_WRITE) { + SET_NONBLOCKING(ofd[1]); + init_fd_data(ofd[1], port_num); + } + + res = set_driver_data(port_num, ifd[0], ofd[1], opts->packet_bytes, + opts->read_write, opts->exit_status, pid); + /* Don't unblock SIGCHLD until now, since the call above must + first complete putting away the info about our new subprocess. */ + unblock_signals(); + +#if CHLDWTHR + ASSERT(children_alive >= 0); + + if (!(children_alive++)) + CHLD_STAT_SIGNAL; /* Wake up child waiter thread if no children + was alive before we fork()ed ... */ +#endif + /* Don't unlock chld_stat_mtx until now of the same reason as above */ + CHLD_STAT_UNLOCK; + + erts_smp_rwmtx_runlock(&environ_rwmtx); + + return (ErlDrvData)res; +#undef CMD_LINE_PREFIX_STR +#undef CMD_LINE_PREFIX_STR_SZ +} + +#ifdef QNX +static reset_qnx_spawn() +{ + int i; + + /* Reset qnx_spawn_options */ + qnx_spawn_options.flags = 0; + qnx_spawn_options.iov[0] = 0xff; + qnx_spawn_options.iov[1] = 0xff; + qnx_spawn_options.iov[2] = 0xff; + qnx_spawn_options.iov[3] = 0xff; +} +#endif + +#define FD_DEF_HEIGHT 24 +#define FD_DEF_WIDTH 80 +/* Control op */ +#define FD_CTRL_OP_GET_WINSIZE 100 + +static int fd_get_window_size(int fd, Uint32 *width, Uint32 *height) +{ +#ifdef TIOCGWINSZ + struct winsize ws; + if (ioctl(fd,TIOCGWINSZ,&ws) == 0) { + *width = (Uint32) ws.ws_col; + *height = (Uint32) ws.ws_row; + return 0; + } +#endif + return -1; +} + +static int fd_control(ErlDrvData drv_data, + unsigned int command, + char *buf, int len, + char **rbuf, int rlen) +{ + int fd = (int)(long)drv_data; + char resbuff[2*sizeof(Uint32)]; + switch (command) { + case FD_CTRL_OP_GET_WINSIZE: + { + Uint32 w,h; + if (fd_get_window_size(fd,&w,&h)) + return 0; + memcpy(resbuff,&w,sizeof(Uint32)); + memcpy(resbuff+sizeof(Uint32),&h,sizeof(Uint32)); + } + break; + default: + return 0; + } + if (rlen < 2*sizeof(Uint32)) { + *rbuf = driver_alloc(2*sizeof(Uint32)); + } + memcpy(*rbuf,resbuff,2*sizeof(Uint32)); + return 2*sizeof(Uint32); +} + +static ErlDrvData fd_start(ErlDrvPort port_num, char* name, + SysDriverOpts* opts) +{ + ErlDrvData res; + + if (((opts->read_write & DO_READ) && opts->ifd >= max_files) || + ((opts->read_write & DO_WRITE) && opts->ofd >= max_files)) + return ERL_DRV_ERROR_GENERAL; + + /* + * Historical: + * + * "Note about nonblocking I/O. + * + * At least on Solaris, setting the write end of a TTY to nonblocking, + * will set the input end to nonblocking as well (and vice-versa). + * If erl is run in a pipeline like this: cat | erl + * the input end of the TTY will be the standard input of cat. + * And cat is not prepared to handle nonblocking I/O." + * + * Actually, the reason for this is not that the tty itself gets set + * in non-blocking mode, but that the "input end" (cat's stdin) and + * the "output end" (erlang's stdout) are typically the "same" file + * descriptor, dup()'ed from a single fd by one of this process' + * ancestors. + * + * The workaround for this problem used to be a rather bad kludge, + * interposing an extra process ("internal cat") between erlang's + * stdout and the original stdout, allowing erlang to set its stdout + * in non-blocking mode without affecting the stdin of the preceding + * process in the pipeline - and being a kludge, it caused all kinds + * of weird problems. + * + * So, this is the current logic: + * + * The only reason to set non-blocking mode on the output fd at all is + * if it's something that can cause a write() to block, of course, + * i.e. primarily if it points to a tty, socket, pipe, or fifo. + * + * If we don't set non-blocking mode when we "should" have, and output + * becomes blocked, the entire runtime system will be suspended - this + * is normally bad of course, and can happen fairly "easily" - e.g. user + * hits ^S on tty - but doesn't necessarily happen. + * + * If we do set non-blocking mode when we "shouldn't" have, the runtime + * system will end up seeing EOF on the input fd (due to the preceding + * process dying), which typically will cause the entire runtime system + * to terminate immediately (due to whatever erlang process is seeing + * the EOF taking it as a signal to halt the system). This is *very* bad. + * + * I.e. we should take a conservative approach, and only set non- + * blocking mode when we a) need to, and b) are reasonably certain + * that it won't be a problem. And as in the example above, the problem + * occurs when input fd and output fd point to different "things". + * + * However, determining that they are not just the same "type" of + * "thing", but actually the same instance of that type of thing, is + * unreasonably complex in many/most cases. + * + * Also, with pipes, sockets, and fifos it's far from obvious that the + * user *wants* non-blocking output: If you're running erlang inside + * some complex pipeline, you're probably not running a real-time system + * that must never stop, but rather *want* it to suspend if the output + * channel is "full". + * + * So, the bottom line: We will only set the output fd non-blocking if + * it points to a tty, and either a) the input fd also points to a tty, + * or b) we can make sure that setting the output fd non-blocking + * doesn't interfere with someone else's input, via a somewhat milder + * kludge than the above. + * + * Also keep in mind that while this code is almost exclusively run as + * a result of an erlang open_port({fd,0,1}, ...), that isn't the only + * case - it can be called with any old pre-existing file descriptors, + * the relations between which (if they're even two) we can only guess + * at - still, we try our best... + */ + + if (opts->read_write & DO_READ) { + init_fd_data(opts->ifd, port_num); + } + if (opts->read_write & DO_WRITE) { + init_fd_data(opts->ofd, port_num); + + /* If we don't have a read end, all bets are off - no non-blocking. */ + if (opts->read_write & DO_READ) { + + if (isatty(opts->ofd)) { /* output fd is a tty:-) */ + + if (isatty(opts->ifd)) { /* input fd is also a tty */ + + /* To really do this "right", we should also check that + input and output fd point to the *same* tty - but + this seems like overkill; ttyname() isn't for free, + and this is a very common case - and it's hard to + imagine a scenario where setting non-blocking mode + here would cause problems - go ahead and do it. */ + + SET_NONBLOCKING(opts->ofd); + + } else { /* output fd is a tty, input fd isn't */ + + /* This is a "problem case", but also common (see the + example above) - i.e. it makes sense to try a bit + harder before giving up on non-blocking mode: Try to + re-open the tty that the output fd points to, and if + successful replace the original one with the "new" fd + obtained this way, and set *that* one in non-blocking + mode. (Yes, this is a kludge.) + + However, re-opening the tty may fail in a couple of + (unusual) cases: + + 1) The name of the tty (or an equivalent one, i.e. + same major/minor number) can't be found, because + it actually lives somewhere other than /dev (or + wherever ttyname() looks for it), and isn't + equivalent to any of those that do live in the + "standard" place - this should be *very* unusual. + + 2) Permissions on the tty don't allow us to open it - + it's perfectly possible to have an fd open to an + object whose permissions wouldn't allow us to open + it. This is not as unusual as it sounds, one case + is if the user has su'ed to someone else (not + root) - we have a read/write fd open to the tty + (because it has been inherited all the way down + here), but we have neither read nor write + permission for the tty. + + In these cases, we finally give up, and don't set the + output fd in non-blocking mode. */ + + char *tty; + int nfd; + + if ((tty = ttyname(opts->ofd)) != NULL && + (nfd = open(tty, O_WRONLY)) != -1) { + dup2(nfd, opts->ofd); + close(nfd); + SET_NONBLOCKING(opts->ofd); + } + } + } + } + } + CHLD_STAT_LOCK; + res = (ErlDrvData)(long)set_driver_data(port_num, opts->ifd, opts->ofd, + opts->packet_bytes, + opts->read_write, 0, -1); + CHLD_STAT_UNLOCK; + return res; +} + +static void clear_fd_data(int fd) +{ + if (fd_data[fd].sz > 0) { + erts_free(ERTS_ALC_T_FD_ENTRY_BUF, (void *) fd_data[fd].buf); + ASSERT(erts_smp_atomic_read(&sys_misc_mem_sz) >= fd_data[fd].sz); + erts_smp_atomic_add(&sys_misc_mem_sz, -1*fd_data[fd].sz); + } + fd_data[fd].buf = NULL; + fd_data[fd].sz = 0; + fd_data[fd].remain = 0; + fd_data[fd].cpos = NULL; + fd_data[fd].psz = 0; +} + +static void nbio_stop_fd(int prt, int fd) +{ + driver_select(prt,fd,DO_READ|DO_WRITE,0); + clear_fd_data(fd); + SET_BLOCKING(fd); +} + +static void fd_stop(ErlDrvData fd) /* Does not close the fds */ +{ + int ofd; + + nbio_stop_fd(driver_data[(int)(long)fd].port_num, (int)(long)fd); + ofd = driver_data[(int)(long)fd].ofd; + if (ofd != (int)(long)fd && ofd != -1) + nbio_stop_fd(driver_data[(int)(long)fd].port_num, (int)(long)ofd); +} + +static ErlDrvData vanilla_start(ErlDrvPort port_num, char* name, + SysDriverOpts* opts) +{ + int flags, fd; + ErlDrvData res; + + flags = (opts->read_write == DO_READ ? O_RDONLY : + opts->read_write == DO_WRITE ? O_WRONLY|O_CREAT|O_TRUNC : + O_RDWR|O_CREAT); + if ((fd = open(name, flags, 0666)) < 0) + return ERL_DRV_ERROR_GENERAL; + if (fd >= max_files) { + close(fd); + return ERL_DRV_ERROR_GENERAL; + } + SET_NONBLOCKING(fd); + init_fd_data(fd, port_num); + + CHLD_STAT_LOCK; + res = (ErlDrvData)(long)set_driver_data(port_num, fd, fd, + opts->packet_bytes, + opts->read_write, 0, -1); + CHLD_STAT_UNLOCK; + return res; +} + +/* Note that driver_data[fd].ifd == fd if the port was opened for reading, */ +/* otherwise (i.e. write only) driver_data[fd].ofd = fd. */ + +static void stop(ErlDrvData fd) +{ + int prt, ofd; + + prt = driver_data[(int)(long)fd].port_num; + nbio_stop_fd(prt, (int)(long)fd); + + ofd = driver_data[(int)(long)fd].ofd; + if (ofd != (int)(long)fd && (int)(long)ofd != -1) + nbio_stop_fd(prt, ofd); + else + ofd = -1; + + CHLD_STAT_LOCK; + + /* Mark as unused. Maybe resetting the 'port_num' slot is better? */ + driver_data[(int)(long)fd].pid = -1; + + CHLD_STAT_UNLOCK; + + /* SMP note: Close has to be last thing done (open file descriptors work + as locks on driver_data[] entries) */ + driver_select(prt, (int)(long)fd, ERL_DRV_USE, 0); /* close(fd); */ + if (ofd >= 0) { + driver_select(prt, (int)(long)ofd, ERL_DRV_USE, 0); /* close(ofd); */ + } +} + +static void outputv(ErlDrvData e, ErlIOVec* ev) +{ + int fd = (int)(long)e; + int ix = driver_data[fd].port_num; + int pb = driver_data[fd].packet_bytes; + int ofd = driver_data[fd].ofd; + int n; + int sz; + char lb[4]; + char* lbp; + int len = ev->size; + + /* (len > ((unsigned long)-1 >> (4-pb)*8)) */ + if (((pb == 2) && (len > 0xffff)) || (pb == 1 && len > 0xff)) { + driver_failure_posix(ix, EINVAL); + return; /* -1; */ + } + put_int32(len, lb); + lbp = lb + (4-pb); + + ev->iov[0].iov_base = lbp; + ev->iov[0].iov_len = pb; + ev->size += pb; + if ((sz = driver_sizeq(ix)) > 0) { + driver_enqv(ix, ev, 0); + if (sz + ev->size >= (1 << 13)) + set_busy_port(ix, 1); + } + else { + int vsize = ev->vsize > MAX_VSIZE ? MAX_VSIZE : ev->vsize; + + n = writev(ofd, (const void *) (ev->iov), vsize); + if (n == ev->size) + return; /* 0;*/ + if (n < 0) { + if ((errno != EINTR) && (errno != ERRNO_BLOCK)) { + driver_failure_posix(ix, errno); + return; /* -1;*/ + } + n = 0; + } + driver_enqv(ix, ev, n); /* n is the skip value */ + driver_select(ix, ofd, ERL_DRV_WRITE|ERL_DRV_USE, 1); + } + /* return 0;*/ +} + + +static void output(ErlDrvData e, char* buf, int len) +{ + int fd = (int)(long)e; + int ix = driver_data[fd].port_num; + int pb = driver_data[fd].packet_bytes; + int ofd = driver_data[fd].ofd; + int n; + int sz; + char lb[4]; + char* lbp; + struct iovec iv[2]; + + /* (len > ((unsigned long)-1 >> (4-pb)*8)) */ + if (((pb == 2) && (len > 0xffff)) || (pb == 1 && len > 0xff)) { + driver_failure_posix(ix, EINVAL); + return; /* -1; */ + } + put_int32(len, lb); + lbp = lb + (4-pb); + + if ((sz = driver_sizeq(ix)) > 0) { + driver_enq(ix, lbp, pb); + driver_enq(ix, buf, len); + if (sz + len + pb >= (1 << 13)) + set_busy_port(ix, 1); + } + else { + iv[0].iov_base = lbp; + iv[0].iov_len = pb; /* should work for pb=0 */ + iv[1].iov_base = buf; + iv[1].iov_len = len; + n = writev(ofd, iv, 2); + if (n == pb+len) + return; /* 0; */ + if (n < 0) { + if ((errno != EINTR) && (errno != ERRNO_BLOCK)) { + driver_failure_posix(ix, errno); + return; /* -1; */ + } + n = 0; + } + if (n < pb) { + driver_enq(ix, lbp+n, pb-n); + driver_enq(ix, buf, len); + } + else { + n -= pb; + driver_enq(ix, buf+n, len-n); + } + driver_select(ix, ofd, ERL_DRV_WRITE|ERL_DRV_USE, 1); + } + return; /* 0; */ +} + +static int port_inp_failure(int port_num, int ready_fd, int res) + /* Result: 0 (eof) or -1 (error) */ +{ + int err = errno; + + ASSERT(res <= 0); + (void) driver_select(port_num, ready_fd, ERL_DRV_READ|ERL_DRV_WRITE, 0); + clear_fd_data(ready_fd); + if (res == 0) { + if (driver_data[ready_fd].report_exit) { + CHLD_STAT_LOCK; + + if (driver_data[ready_fd].alive) { + /* + * We have eof and want to report exit status, but the process + * hasn't exited yet. When it does report_exit_status() will + * driver_select() this fd which will make sure that we get + * back here with driver_data[ready_fd].alive == 0 and + * driver_data[ready_fd].status set. + */ + CHLD_STAT_UNLOCK; + return 0; + } + else { + int status = driver_data[ready_fd].status; + CHLD_STAT_UNLOCK; + + /* We need not be prepared for stopped/continued processes. */ + if (WIFSIGNALED(status)) + status = 128 + WTERMSIG(status); + else + status = WEXITSTATUS(status); + + driver_report_exit(driver_data[ready_fd].port_num, status); + } + } + driver_failure_eof(port_num); + } else { + driver_failure_posix(port_num, err); + } + return 0; +} + +/* fd is the drv_data that is returned from the */ +/* initial start routine */ +/* ready_fd is the descriptor that is ready to read */ + +static void ready_input(ErlDrvData e, ErlDrvEvent ready_fd) +{ + int fd = (int)(long)e; + int port_num; + int packet_bytes; + int res; + Uint h; + + port_num = driver_data[fd].port_num; + packet_bytes = driver_data[fd].packet_bytes; + + if (packet_bytes == 0) { + byte *read_buf = (byte *) erts_alloc(ERTS_ALC_T_SYS_READ_BUF, + ERTS_SYS_READ_BUF_SZ); + res = read(ready_fd, read_buf, ERTS_SYS_READ_BUF_SZ); + if (res < 0) { + if ((errno != EINTR) && (errno != ERRNO_BLOCK)) + port_inp_failure(port_num, ready_fd, res); + } + else if (res == 0) + port_inp_failure(port_num, ready_fd, res); + else + driver_output(port_num, (char*) read_buf, res); + erts_free(ERTS_ALC_T_SYS_READ_BUF, (void *) read_buf); + } + else if (fd_data[ready_fd].remain > 0) { /* We try to read the remainder */ + /* space is allocated in buf */ + res = read(ready_fd, fd_data[ready_fd].cpos, + fd_data[ready_fd].remain); + if (res < 0) { + if ((errno != EINTR) && (errno != ERRNO_BLOCK)) + port_inp_failure(port_num, ready_fd, res); + } + else if (res == 0) { + port_inp_failure(port_num, ready_fd, res); + } + else if (res == fd_data[ready_fd].remain) { /* we're done */ + driver_output(port_num, fd_data[ready_fd].buf, + fd_data[ready_fd].sz); + clear_fd_data(ready_fd); + } + else { /* if (res < fd_data[ready_fd].remain) */ + fd_data[ready_fd].cpos += res; + fd_data[ready_fd].remain -= res; + } + } + else if (fd_data[ready_fd].remain == 0) { /* clean fd */ + byte *read_buf = (byte *) erts_alloc(ERTS_ALC_T_SYS_READ_BUF, + ERTS_SYS_READ_BUF_SZ); + /* We make one read attempt and see what happens */ + res = read(ready_fd, read_buf, ERTS_SYS_READ_BUF_SZ); + if (res < 0) { + if ((errno != EINTR) && (errno != ERRNO_BLOCK)) + port_inp_failure(port_num, ready_fd, res); + } + else if (res == 0) { /* eof */ + port_inp_failure(port_num, ready_fd, res); + } + else if (res < packet_bytes - fd_data[ready_fd].psz) { + memcpy(fd_data[ready_fd].pbuf+fd_data[ready_fd].psz, + read_buf, res); + fd_data[ready_fd].psz += res; + } + else { /* if (res >= packet_bytes) */ + unsigned char* cpos = read_buf; + int bytes_left = res; + + while (1) { + int psz = fd_data[ready_fd].psz; + char* pbp = fd_data[ready_fd].pbuf + psz; + + while(bytes_left && (psz < packet_bytes)) { + *pbp++ = *cpos++; + bytes_left--; + psz++; + } + + if (psz < packet_bytes) { + fd_data[ready_fd].psz = psz; + break; + } + fd_data[ready_fd].psz = 0; + + switch (packet_bytes) { + case 1: h = get_int8(fd_data[ready_fd].pbuf); break; + case 2: h = get_int16(fd_data[ready_fd].pbuf); break; + case 4: h = get_int32(fd_data[ready_fd].pbuf); break; + default: ASSERT(0); return; /* -1; */ + } + + if (h <= (bytes_left)) { + driver_output(port_num, (char*) cpos, h); + cpos += h; + bytes_left -= h; + continue; + } + else { /* The last message we got was split */ + char *buf = erts_alloc_fnf(ERTS_ALC_T_FD_ENTRY_BUF, h); + if (!buf) { + errno = ENOMEM; + port_inp_failure(port_num, ready_fd, -1); + } + else { + erts_smp_atomic_add(&sys_misc_mem_sz, h); + sys_memcpy(buf, cpos, bytes_left); + fd_data[ready_fd].buf = buf; + fd_data[ready_fd].sz = h; + fd_data[ready_fd].remain = h - bytes_left; + fd_data[ready_fd].cpos = buf + bytes_left; + } + break; + } + } + } + erts_free(ERTS_ALC_T_SYS_READ_BUF, (void *) read_buf); + } +} + + +/* fd is the drv_data that is returned from the */ +/* initial start routine */ +/* ready_fd is the descriptor that is ready to read */ + +static void ready_output(ErlDrvData e, ErlDrvEvent ready_fd) +{ + int fd = (int)(long)e; + int ix = driver_data[fd].port_num; + int n; + struct iovec* iv; + int vsize; + + + if ((iv = (struct iovec*) driver_peekq(ix, &vsize)) == NULL) { + driver_select(ix, ready_fd, ERL_DRV_WRITE, 0); + return; /* 0; */ + } + vsize = vsize > MAX_VSIZE ? MAX_VSIZE : vsize; + if ((n = writev(ready_fd, iv, vsize)) > 0) { + if (driver_deq(ix, n) == 0) + set_busy_port(ix, 0); + } + else if (n < 0) { + if (errno == ERRNO_BLOCK || errno == EINTR) + return; /* 0; */ + else { + int res = errno; + driver_select(ix, ready_fd, ERL_DRV_WRITE, 0); + driver_failure_posix(ix, res); + return; /* -1; */ + } + } + return; /* 0; */ +} + +static void stop_select(ErlDrvEvent fd, void* _) +{ + close((int)fd); +} + +/* +** Async opertation support +*/ +#if defined(USE_THREADS) && !defined(ERTS_SMP) +static void +sys_async_ready_failed(int fd, int r, int err) +{ + char buf[120]; + sprintf(buf, "sys_async_ready(): Fatal error: fd=%d, r=%d, errno=%d\n", + fd, r, err); + (void) write(2, buf, strlen(buf)); + abort(); +} + +/* called from threads !! */ +void sys_async_ready(int fd) +{ + int r; + while (1) { + r = write(fd, "0", 1); /* signal main thread fd MUST be async_fd[1] */ + if (r == 1) { + DEBUGF(("sys_async_ready(): r = 1\r\n")); + break; + } + if (r < 0 && errno == EINTR) { + DEBUGF(("sys_async_ready(): r = %d\r\n", r)); + continue; + } + sys_async_ready_failed(fd, r, errno); + } +} + +static int async_drv_init(void) +{ + async_fd[0] = -1; + async_fd[1] = -1; + return 0; +} + +static ErlDrvData async_drv_start(ErlDrvPort port_num, + char* name, SysDriverOpts* opts) +{ + if (async_fd[0] != -1) + return ERL_DRV_ERROR_GENERAL; + if (pipe(async_fd) < 0) + return ERL_DRV_ERROR_GENERAL; + + DEBUGF(("async_drv_start: %d\r\n", port_num)); + + SET_NONBLOCKING(async_fd[0]); + driver_select(port_num, async_fd[0], ERL_DRV_READ, 1); + + if (init_async(async_fd[1]) < 0) + return ERL_DRV_ERROR_GENERAL; + return (ErlDrvData)port_num; +} + +static void async_drv_stop(ErlDrvData e) +{ + int port_num = (int)(long)e; + + DEBUGF(("async_drv_stop: %d\r\n", port_num)); + + exit_async(); + + driver_select(port_num, async_fd[0], ERL_DRV_READ, 0); + + close(async_fd[0]); + close(async_fd[1]); + async_fd[0] = async_fd[1] = -1; +} + + +static void async_drv_input(ErlDrvData e, ErlDrvEvent fd) +{ + char *buf[32]; + DEBUGF(("async_drv_input\r\n")); + while (read((int) fd, (void *) buf, 32) > 0); /* fd MUST be async_fd[0] */ + check_async_ready(); /* invoke all async_ready */ +} +#endif + +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_block_system(0); + /* + * NOTE: since we allow gc we are not allowed to lock + * (any) process main locks while blocking system... + */ + + /* 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_release_system(); +} + +/* 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){ + pid_t p = getpid(); + /* Assume the pid is scalar and can rest in an unsigned long... */ + sprintf(buffer,"%lu",(unsigned long) p); +} + +int +erts_sys_putenv(char *buffer, int sep_ix) +{ + int res; + char *env; +#ifdef HAVE_COPYING_PUTENV + env = buffer; +#else + Uint sz = strlen(buffer)+1; + env = erts_alloc(ERTS_ALC_T_PUTENV_STR, sz); + erts_smp_atomic_add(&sys_misc_mem_sz, sz); + strcpy(env,buffer); +#endif + erts_smp_rwmtx_rwlock(&environ_rwmtx); + res = putenv(env); + erts_smp_rwmtx_rwunlock(&environ_rwmtx); + return res; +} + +int +erts_sys_getenv(char *key, char *value, size_t *size) +{ + char *orig_value; + int res; + erts_smp_rwmtx_rlock(&environ_rwmtx); + 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; + } + } + erts_smp_rwmtx_runlock(&environ_rwmtx); + return res; +} + +void +sys_init_io(void) +{ + fd_data = (struct fd_data *) + erts_alloc(ERTS_ALC_T_FD_TAB, max_files * sizeof(struct fd_data)); + erts_smp_atomic_add(&sys_misc_mem_sz, + max_files * sizeof(struct fd_data)); + +#ifdef USE_THREADS +#ifdef ERTS_SMP + if (init_async(-1) < 0) + erl_exit(1, "Failed to initialize async-threads\n"); +#else + { + /* This is speical stuff, starting a driver from the + * system routines, but is a nice way of handling stuff + * the erlang way + */ + SysDriverOpts dopts; + int ret; + + sys_memset((void*)&dopts, 0, sizeof(SysDriverOpts)); + add_driver_entry(&async_driver_entry); + ret = erts_open_driver(NULL, NIL, "async", &dopts, NULL); + DEBUGF(("open_driver = %d\n", ret)); + if (ret < 0) + erl_exit(1, "Failed to open async driver\n"); + erts_port[ret].status |= ERTS_PORT_SFLG_IMMORTAL; + } +#endif +#endif + +} + +#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) +{ + elib_ensure_initialized(); +} + +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 (?) */ + +int sys_get_key(fd) +int fd; +{ + int c; + unsigned char rbuf[64]; + + fflush(stdout); /* Flush query ??? */ + + if ((c = read(fd,rbuf,64)) <= 0) { + return c; + } + + return rbuf[0]; +} + + +#ifdef DEBUG + +extern int erts_initialized; +void +erl_assert_error(char* expr, char* file, int line) +{ + fflush(stdout); + fprintf(stderr, "Assertion failed: %s in %s, line %d\n", + expr, file, line); + 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(); +} + +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 */ + +static ERTS_INLINE void +report_exit_status(ErtsSysReportExit *rep, int status) +{ + Port *pp; +#ifdef ERTS_SMP + CHLD_STAT_UNLOCK; +#endif + pp = erts_id2port_sflgs(rep->port, + NULL, + 0, + ERTS_PORT_SFLGS_INVALID_DRIVER_LOOKUP); +#ifdef ERTS_SMP + CHLD_STAT_LOCK; +#endif + if (pp) { + if (rep->ifd >= 0) { + driver_data[rep->ifd].alive = 0; + driver_data[rep->ifd].status = status; + (void) driver_select((ErlDrvPort) internal_port_index(pp->id), + rep->ifd, + (ERL_DRV_READ|ERL_DRV_USE), + 1); + } + if (rep->ofd >= 0) { + driver_data[rep->ofd].alive = 0; + driver_data[rep->ofd].status = status; + (void) driver_select((ErlDrvPort) internal_port_index(pp->id), + rep->ofd, + (ERL_DRV_WRITE|ERL_DRV_USE), + 1); + } + erts_port_release(pp); + } + erts_free(ERTS_ALC_T_PRT_REP_EXIT, rep); +} + +#if !CHLDWTHR /* ---------------------------------------------------------- */ + +#define ERTS_REPORT_EXIT_STATUS report_exit_status + +static int check_children(void) +{ + int res = 0; + int pid; + int status; + +#ifndef ERTS_SMP + if (children_died) +#endif + { + sys_sigblock(SIGCHLD); + CHLD_STAT_LOCK; + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) + note_child_death(pid, status); +#ifndef ERTS_SMP + children_died = 0; +#endif + CHLD_STAT_UNLOCK; + sys_sigrelease(SIGCHLD); + res = 1; + } + return res; +} + +#ifdef ERTS_SMP + +void +erts_check_children(void) +{ + (void) check_children(); +} + +#endif + +#elif CHLDWTHR && defined(ERTS_SMP) /* ------------------------------------- */ + +#define ERTS_REPORT_EXIT_STATUS report_exit_status + +#define check_children() (0) + + +#else /* CHLDWTHR && !defined(ERTS_SMP) ------------------------------------ */ + +#define ERTS_REPORT_EXIT_STATUS initiate_report_exit_status + +static ERTS_INLINE void +initiate_report_exit_status(ErtsSysReportExit *rep, int status) +{ + rep->next = report_exit_transit_list; + rep->status = status; + report_exit_transit_list = rep; + /* + * We need the scheduler thread to call check_children(). + * If the scheduler thread is sleeping in a poll with a + * timeout, we need to wake the scheduler thread. We use the + * functionality of the async driver to do this, instead of + * implementing yet another driver doing the same thing. A + * little bit ugly, but it works... + */ + sys_async_ready(async_fd[1]); +} + +static int check_children(void) +{ + int res; + ErtsSysReportExit *rep; + CHLD_STAT_LOCK; + rep = report_exit_transit_list; + res = rep != NULL; + while (rep) { + ErtsSysReportExit *curr_rep = rep; + rep = rep->next; + report_exit_status(curr_rep, curr_rep->status); + } + report_exit_transit_list = NULL; + CHLD_STAT_UNLOCK; + return res; +} + +#endif /* ------------------------------------------------------------------ */ + +static void note_child_death(int pid, int status) +{ + ErtsSysReportExit **repp = &report_exit_list; + ErtsSysReportExit *rep = report_exit_list; + + while (rep) { + if (pid == rep->pid) { + *repp = rep->next; + ERTS_REPORT_EXIT_STATUS(rep, status); + break; + } + repp = &rep->next; + rep = rep->next; + } +} + +#if CHLDWTHR + +static void * +child_waiter(void *unused) +{ + int pid; + int status; + +#ifdef ERTS_ENABLE_LOCK_CHECK + erts_lc_set_thread_name("child waiter"); +#endif + + while(1) { +#ifdef DEBUG + int waitpid_errno; +#endif + pid = waitpid(-1, &status, 0); +#ifdef DEBUG + waitpid_errno = errno; +#endif + CHLD_STAT_LOCK; + if (pid < 0) { + ASSERT(waitpid_errno == ECHILD); + } + else { + children_alive--; + ASSERT(children_alive >= 0); + note_child_death(pid, status); + } + while (!children_alive) + CHLD_STAT_WAIT; /* Wait for children to wait on... :) */ + CHLD_STAT_UNLOCK; + } + + return NULL; +} + +#endif + +/* + * 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) +{ +#ifdef ERTS_SMP + ERTS_CHK_IO(!runnable); + ERTS_SMP_LC_ASSERT(!ERTS_LC_IS_BLOCKING); +#else + ERTS_CHK_IO_INTR(0); + if (runnable) { + ERTS_CHK_IO(0); /* Poll for I/O */ + check_async_ready(); /* Check async completions */ + } else { + int wait_for_io = !check_async_ready(); + if (wait_for_io) + wait_for_io = !check_children(); + ERTS_CHK_IO(wait_for_io); + } + (void) check_children(); +#endif +} + + +#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"; + (void) write(2, msg, sizeof(msg)); + abort(); + } +} + +static void * +signal_dispatcher_thread_func(void *unused) +{ + int initialized = 0; +#if !CHLDWTHR + int notify_check_children = 0; +#endif +#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; + erl_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., it hasn't called + * erts_register_blockable_thread()). 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 */ + initialized = 1; +#if !CHLDWTHR + if (!notify_check_children) +#endif + break; +#if !CHLDWTHR + case 'C': /* SIGCHLD */ + if (initialized) + erts_smp_notify_check_children_needed(); + else + notify_check_children = 1; + break; +#endif + case 'I': /* SIGINT */ + break_requested(); + break; + case 'Q': /* SIGQUIT */ + quit_requested(); + break; + case '1': /* SIGUSR1 */ + sigusr1_exit(); + break; +#ifdef QUANTIFY + case '2': /* SIGUSR2 */ + quantify_save_data(); /* Might take a substantial amount of + time, but this is a test/debug + build */ + break; +#endif + default: + erl_exit(ERTS_ABORT_EXIT, + "signal-dispatcher thread received unknown " + "signal notification: '%c'\n", + buf[i]); + } + } + ERTS_SMP_LC_ASSERT(!ERTS_LC_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; + + if (pipe(sig_notify_fds) < 0) { + erl_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); +} + +void +erts_sys_main_thread(void) +{ + erts_thread_disable_fpe(); + /* Become signal receiver thread... */ +#ifdef ERTS_ENABLE_LOCK_CHECK + erts_lc_set_thread_name("signal_receiver"); +#endif + + smp_sig_notify(0); /* Notify initialized */ + while (1) { + /* Wait for a signal to arrive... */ +#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; + + 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("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(); +#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; +} + + +#ifdef ERTS_TIMER_THREAD + +/* + * Interruptible-wait facility: low-level synchronisation state + * and methods that are implementation dependent. + * + * Constraint: Every implementation must define 'struct erts_iwait' + * with a field 'erts_smp_atomic_t state;'. + */ + +/* values for struct erts_iwait's state field */ +#define IWAIT_WAITING 0 +#define IWAIT_AWAKE 1 +#define IWAIT_INTERRUPT 2 + +#if 0 /* XXX: needs feature test in erts/configure.in */ + +/* + * This is an implementation of the interruptible wait facility on + * top of Linux-specific futexes. + */ +#include <asm/unistd.h> +#define FUTEX_WAIT 0 +#define FUTEX_WAKE 1 +static int sys_futex(void *futex, int op, int val, const struct timespec *timeout) +{ + return syscall(__NR_futex, futex, op, val, timeout); +} + +struct erts_iwait { + erts_smp_atomic_t state; /* &state.counter is our futex */ +}; + +static void iwait_lowlevel_init(struct erts_iwait *iwait) { /* empty */ } + +static void iwait_lowlevel_wait(struct erts_iwait *iwait, struct timeval *delay) +{ + struct timespec timeout; + int res; + + timeout.tv_sec = delay->tv_sec; + timeout.tv_nsec = delay->tv_usec * 1000; + res = sys_futex((void*)&iwait->state.counter, FUTEX_WAIT, IWAIT_WAITING, &timeout); + if (res < 0 && errno != ETIMEDOUT && errno != EWOULDBLOCK && errno != EINTR) + perror("FUTEX_WAIT"); +} + +static void iwait_lowlevel_interrupt(struct erts_iwait *iwait) +{ + int res = sys_futex((void*)&iwait->state.counter, FUTEX_WAKE, 1, NULL); + if (res < 0) + perror("FUTEX_WAKE"); +} + +#else /* using poll() or select() */ + +/* + * This is an implementation of the interruptible wait facility on + * top of pipe(), poll() or select(), read(), and write(). + */ +struct erts_iwait { + erts_smp_atomic_t state; + int read_fd; /* wait polls and reads this fd */ + int write_fd; /* interrupt writes this fd */ +}; + +static void iwait_lowlevel_init(struct erts_iwait *iwait) +{ + int fds[2]; + + if (pipe(fds) < 0) { + perror("pipe()"); + exit(1); + } + iwait->read_fd = fds[0]; + iwait->write_fd = fds[1]; +} + +#if defined(ERTS_USE_POLL) + +#include <sys/poll.h> +#define PERROR_POLL "poll()" + +static int iwait_lowlevel_poll(int read_fd, struct timeval *delay) +{ + struct pollfd pollfd; + int timeout; + + pollfd.fd = read_fd; + pollfd.events = POLLIN; + pollfd.revents = 0; + timeout = delay->tv_sec * 1000 + delay->tv_usec / 1000; + return poll(&pollfd, 1, timeout); +} + +#else /* !ERTS_USE_POLL */ + +#include <sys/select.h> +#define PERROR_POLL "select()" + +static int iwait_lowlevel_poll(int read_fd, struct timeval *delay) +{ + fd_set readfds; + + FD_ZERO(&readfds); + FD_SET(read_fd, &readfds); + return select(read_fd + 1, &readfds, NULL, NULL, delay); +} + +#endif /* !ERTS_USE_POLL */ + +static void iwait_lowlevel_wait(struct erts_iwait *iwait, struct timeval *delay) +{ + int res; + char buf[64]; + + res = iwait_lowlevel_poll(iwait->read_fd, delay); + if (res > 0) + (void)read(iwait->read_fd, buf, sizeof buf); + else if (res < 0 && errno != EINTR) + perror(PERROR_POLL); +} + +static void iwait_lowlevel_interrupt(struct erts_iwait *iwait) +{ + int res = write(iwait->write_fd, "!", 1); + if (res < 0) + perror("write()"); +} + +#endif /* using poll() or select() */ + +#if 0 /* not using poll() or select() */ +/* + * This is an implementation of the interruptible wait facility on + * top of pthread_cond_timedwait(). This has two problems: + * 1. pthread_cond_timedwait() requires an absolute time point, + * so the relative delay must be converted to absolute time. + * Worse, this breaks if the machine's time is adjusted while + * we're preparing to wait. + * 2. Each cond operation requires additional mutex lock/unlock operations. + * + * Problem 2 is probably not too bad on Linux (they'll just become + * relatively cheap futex operations), but problem 1 is the real killer. + * Only use this implementation if no better alternatives are available! + */ +struct erts_iwait { + erts_smp_atomic_t state; + pthread_cond_t cond; + pthread_mutex_t mutex; +}; + +static void iwait_lowlevel_init(struct erts_iwait *iwait) +{ + iwait->cond = (pthread_cond_t) PTHREAD_COND_INITIALIZER; + iwait->mutex = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER; +} + +static void iwait_lowlevel_wait(struct erts_iwait *iwait, struct timeval *delay) +{ + struct timeval tmp; + struct timespec timeout; + + /* Due to pthread_cond_timedwait()'s use of absolute + time, this must be the real gettimeofday(), _not_ + the "smoothed" one beam/erl_time_sup.c implements. */ + gettimeofday(&tmp, NULL); + + tmp.tv_sec += delay->tv_sec; + tmp.tv_usec += delay->tv_usec; + if (tmp.tv_usec >= 1000*1000) { + tmp.tv_usec -= 1000*1000; + tmp.tv_sec += 1; + } + timeout.tv_sec = tmp.tv_sec; + timeout.tv_nsec = tmp.tv_usec * 1000; + pthread_mutex_lock(&iwait->mutex); + pthread_cond_timedwait(&iwait->cond, &iwait->mutex, &timeout); + pthread_mutex_unlock(&iwait->mutex); +} + +static void iwait_lowlevel_interrupt(struct erts_iwait *iwait) +{ + pthread_mutex_lock(&iwait->mutex); + pthread_cond_signal(&iwait->cond); + pthread_mutex_unlock(&iwait->mutex); +} + +#endif /* not using POLL */ + +/* + * Interruptible-wait facility. This is just a wrapper around the + * low-level synchronisation code, where we maintain our logical + * state in order to suppress some state transitions. + */ + +struct erts_iwait *erts_iwait_init(void) +{ + struct erts_iwait *iwait = malloc(sizeof *iwait); + if (!iwait) { + perror("malloc"); + exit(1); + } + iwait_lowlevel_init(iwait); + erts_smp_atomic_init(&iwait->state, IWAIT_AWAKE); + return iwait; +} + +void erts_iwait_wait(struct erts_iwait *iwait, struct timeval *delay) +{ + if (erts_smp_atomic_xchg(&iwait->state, IWAIT_WAITING) != IWAIT_INTERRUPT) + iwait_lowlevel_wait(iwait, delay); + erts_smp_atomic_set(&iwait->state, IWAIT_AWAKE); +} + +void erts_iwait_interrupt(struct erts_iwait *iwait) +{ + if (erts_smp_atomic_xchg(&iwait->state, IWAIT_INTERRUPT) == IWAIT_WAITING) + iwait_lowlevel_interrupt(iwait); +} + +#endif /* ERTS_TIMER_THREAD */ diff --git a/erts/emulator/sys/unix/sys_float.c b/erts/emulator/sys/unix/sys_float.c new file mode 100644 index 0000000000..15da6ab45c --- /dev/null +++ b/erts/emulator/sys/unix/sys_float.c @@ -0,0 +1,815 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2001-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "sys.h" +#include "global.h" +#include "erl_process.h" + + +#ifdef NO_FPE_SIGNALS + +void +erts_sys_init_float(void) +{ +# ifdef SIGFPE + sys_sigset(SIGFPE, SIG_IGN); /* Ignore so we can test for NaN and Inf */ +# endif +} + +static ERTS_INLINE void set_current_fp_exception(unsigned long pc) +{ + /* nothing to do */ +} + +#else /* !NO_FPE_SIGNALS */ + +#ifdef ERTS_SMP +static erts_tsd_key_t fpe_key; + +/* once-only initialisation early in the main thread (via erts_sys_init_float()) */ +static void erts_init_fp_exception(void) +{ + /* XXX: the wrappers prevent using a pthread destructor to + deallocate the key's value; so when/where do we do that? */ + erts_tsd_key_create(&fpe_key); +} + +void erts_thread_init_fp_exception(void) +{ + unsigned long *fpe = erts_alloc(ERTS_ALC_T_FP_EXCEPTION, sizeof(*fpe)); + *fpe = 0L; + erts_tsd_set(fpe_key, fpe); +} + +static ERTS_INLINE volatile unsigned long *erts_thread_get_fp_exception(void) +{ + return (volatile unsigned long*)erts_tsd_get(fpe_key); +} +#else /* !SMP */ +#define erts_init_fp_exception() /*empty*/ +static volatile unsigned long fp_exception; +#define erts_thread_get_fp_exception() (&fp_exception) +#endif /* SMP */ + +volatile unsigned long *erts_get_current_fp_exception(void) +{ + Process *c_p; + + c_p = erts_get_current_process(); + if (c_p) + return &c_p->fp_exception; + return erts_thread_get_fp_exception(); +} + +static void set_current_fp_exception(unsigned long pc) +{ + volatile unsigned long *fpexnp = erts_get_current_fp_exception(); + ASSERT(fpexnp != NULL); + *fpexnp = pc; +} + +void erts_fp_check_init_error(volatile unsigned long *fpexnp) +{ + char buf[64]; + snprintf(buf, sizeof buf, "ERTS_FP_CHECK_INIT at %p: detected unhandled FPE at %p\r\n", + __builtin_return_address(0), (void*)*fpexnp); + write(2, buf, strlen(buf)); + *fpexnp = 0; +#if defined(__i386__) || defined(__x86_64__) + erts_restore_fpu(); +#endif +} + +/* Is there no standard identifier for Darwin/MacOSX ? */ +#if defined(__APPLE__) && defined(__MACH__) && !defined(__DARWIN__) +#define __DARWIN__ 1 +#endif + +#if (defined(__i386__) || defined(__x86_64__)) && defined(__GNUC__) + +static void unmask_x87(void) +{ + unsigned short cw; + + __asm__ __volatile__("fstcw %0" : "=m"(cw)); + cw &= ~(0x01|0x04|0x08); /* unmask IM, ZM, OM */ + __asm__ __volatile__("fldcw %0" : : "m"(cw)); +} + +/* mask x87 FPE, return true if the previous state was unmasked */ +static int mask_x87(void) +{ + unsigned short cw; + int unmasked; + + __asm__ __volatile__("fstcw %0" : "=m"(cw)); + unmasked = (cw & (0x01|0x04|0x08)) == 0; + /* or just set cw = 0x37f */ + cw |= (0x01|0x04|0x08); /* mask IM, ZM, OM */ + __asm__ __volatile__("fldcw %0" : : "m"(cw)); + return unmasked; +} + +static void unmask_sse2(void) +{ + unsigned int mxcsr; + + __asm__ __volatile__("stmxcsr %0" : "=m"(mxcsr)); + mxcsr &= ~(0x003F|0x0680); /* clear exn flags, unmask OM, ZM, IM (not PM, UM, DM) */ + __asm__ __volatile__("ldmxcsr %0" : : "m"(mxcsr)); +} + +/* mask SSE2 FPE, return true if the previous state was unmasked */ +static int mask_sse2(void) +{ + unsigned int mxcsr; + int unmasked; + + __asm__ __volatile__("stmxcsr %0" : "=m"(mxcsr)); + unmasked = (mxcsr & 0x0680) == 0; + /* or just set mxcsr = 0x1f80 */ + mxcsr &= ~0x003F; /* clear exn flags */ + mxcsr |= 0x0680; /* mask OM, ZM, IM (not PM, UM, DM) */ + __asm__ __volatile__("ldmxcsr %0" : : "m"(mxcsr)); + return unmasked; +} + +#if defined(__x86_64__) + +static inline int cpu_has_sse2(void) { return 1; } + +#else /* !__x86_64__ */ + +/* + * Check if an x86-32 processor has SSE2. + */ +static unsigned int xor_eflags(unsigned int mask) +{ + unsigned int eax, edx; + + eax = mask; /* eax = mask */ + __asm__("pushfl\n\t" + "popl %0\n\t" /* edx = original EFLAGS */ + "xorl %0, %1\n\t" /* eax = mask ^ EFLAGS */ + "pushl %1\n\t" + "popfl\n\t" /* new EFLAGS = mask ^ original EFLAGS */ + "pushfl\n\t" + "popl %1\n\t" /* eax = new EFLAGS */ + "xorl %0, %1\n\t" /* eax = new EFLAGS ^ old EFLAGS */ + "pushl %0\n\t" + "popfl" /* restore original EFLAGS */ + : "=d"(edx), "=a"(eax) + : "1"(eax)); + return eax; +} + +static __inline__ unsigned int cpuid_eax(unsigned int op) +{ + unsigned int eax, save_ebx; + + /* In PIC mode i386 reserves EBX. So we must save + and restore it ourselves to not upset gcc. */ + __asm__( + "movl %%ebx, %1\n\t" + "cpuid\n\t" + "movl %1, %%ebx" + : "=a"(eax), "=m"(save_ebx) + : "0"(op) + : "cx", "dx"); + return eax; +} + +static __inline__ unsigned int cpuid_edx(unsigned int op) +{ + unsigned int eax, edx, save_ebx; + + /* In PIC mode i386 reserves EBX. So we must save + and restore it ourselves to not upset gcc. */ + __asm__( + "movl %%ebx, %2\n\t" + "cpuid\n\t" + "movl %2, %%ebx" + : "=a"(eax), "=d"(edx), "=m"(save_ebx) + : "0"(op) + : "cx"); + return edx; +} + +/* The AC bit, bit #18, is a new bit introduced in the EFLAGS + * register on the Intel486 processor to generate alignment + * faults. This bit cannot be set on the Intel386 processor. + */ +static __inline__ int is_386(void) +{ + return ((xor_eflags(1<<18) >> 18) & 1) == 0; +} + +/* Newer x86 processors have a CPUID instruction, as indicated by + * the ID bit (#21) in EFLAGS being modifiable. + */ +static __inline__ int has_CPUID(void) +{ + return (xor_eflags(1<<21) >> 21) & 1; +} + +static int cpu_has_sse2(void) +{ + unsigned int maxlev, features; + static int has_sse2 = -1; + + if (has_sse2 >= 0) + return has_sse2; + has_sse2 = 0; + + if (is_386()) + return 0; + if (!has_CPUID()) + return 0; + maxlev = cpuid_eax(0); + /* Intel A-step Pentium had a preliminary version of CPUID. + It also didn't have SSE2. */ + if ((maxlev & 0xFFFFFF00) == 0x0500) + return 0; + /* If max level is zero then CPUID cannot report any features. */ + if (maxlev == 0) + return 0; + features = cpuid_edx(1); + has_sse2 = (features & (1 << 26)) != 0; + + return has_sse2; +} +#endif /* !__x86_64__ */ + +static void unmask_fpe(void) +{ + __asm__ __volatile__("fnclex"); + unmask_x87(); + if (cpu_has_sse2()) + unmask_sse2(); +} + +static void unmask_fpe_conditional(int unmasked) +{ + if (unmasked) + unmask_fpe(); +} + +/* mask x86 FPE, return true if the previous state was unmasked */ +static int mask_fpe(void) +{ + int unmasked; + + unmasked = mask_x87(); + if (cpu_has_sse2()) + unmasked |= mask_sse2(); + return unmasked; +} + +void erts_restore_fpu(void) +{ + __asm__ __volatile__("fninit"); + unmask_x87(); + if (cpu_has_sse2()) + unmask_sse2(); +} + +#elif defined(__sparc__) && defined(__linux__) + +#if defined(__arch64__) +#define LDX "ldx" +#define STX "stx" +#else +#define LDX "ld" +#define STX "st" +#endif + +static void unmask_fpe(void) +{ + unsigned long fsr; + + __asm__(STX " %%fsr, %0" : "=m"(fsr)); + fsr &= ~(0x1FUL << 23); /* clear FSR[TEM] field */ + fsr |= (0x1AUL << 23); /* enable NV, OF, DZ exceptions */ + __asm__ __volatile__(LDX " %0, %%fsr" : : "m"(fsr)); +} + +static void unmask_fpe_conditional(int unmasked) +{ + if (unmasked) + unmask_fpe(); +} + +/* mask SPARC FPE, return true if the previous state was unmasked */ +static int mask_fpe(void) +{ + unsigned long fsr; + int unmasked; + + __asm__(STX " %%fsr, %0" : "=m"(fsr)); + unmasked = ((fsr >> 23) & 0x1A) == 0x1A; + fsr &= ~(0x1FUL << 23); /* clear FSR[TEM] field */ + __asm__ __volatile__(LDX " %0, %%fsr" : : "m"(fsr)); + return unmasked; +} + +#elif (defined(__powerpc__) && defined(__linux__)) || (defined(__ppc__) && defined(__DARWIN__)) + +#if defined(__linux__) +#include <sys/prctl.h> + +static void set_fpexc_precise(void) +{ + if (prctl(PR_SET_FPEXC, PR_FP_EXC_PRECISE) < 0) { + perror("PR_SET_FPEXC"); + exit(1); + } +} + +#elif defined(__DARWIN__) + +#include <mach/mach.h> +#include <pthread.h> + +/* + * FE0 FE1 MSR bits + * 0 0 floating-point exceptions disabled + * 0 1 floating-point imprecise nonrecoverable + * 1 0 floating-point imprecise recoverable + * 1 1 floating-point precise mode + * + * Apparently: + * - Darwin 5.5 (MacOS X <= 10.1) starts with FE0 == FE1 == 0, + * and resets FE0 and FE1 to 0 after each SIGFPE. + * - Darwin 6.0 (MacOS X 10.2) starts with FE0 == FE1 == 1, + * and does not reset FE0 or FE1 after a SIGFPE. + */ +#define FE0_MASK (1<<11) +#define FE1_MASK (1<<8) + +/* a thread cannot get or set its own MSR bits */ +static void *fpu_fpe_enable(void *arg) +{ + thread_t t = *(thread_t*)arg; + struct ppc_thread_state state; + unsigned int state_size = PPC_THREAD_STATE_COUNT; + + if (thread_get_state(t, PPC_THREAD_STATE, (natural_t*)&state, &state_size) != KERN_SUCCESS) { + perror("thread_get_state"); + exit(1); + } + if ((state.srr1 & (FE1_MASK|FE0_MASK)) != (FE1_MASK|FE0_MASK)) { +#if 1 + /* This would also have to be performed in the SIGFPE handler + to work around the MSR reset older Darwin releases do. */ + state.srr1 |= (FE1_MASK|FE0_MASK); + thread_set_state(t, PPC_THREAD_STATE, (natural_t*)&state, state_size); +#else + fprintf(stderr, "srr1 == 0x%08x, your Darwin is too old\n", state.srr1); + exit(1); +#endif + } + return NULL; /* Ok, we appear to be on Darwin 6.0 or later */ +} + +static void set_fpexc_precise(void) +{ + thread_t self = mach_thread_self(); + pthread_t enabler; + + if (pthread_create(&enabler, NULL, fpu_fpe_enable, &self)) { + perror("pthread_create"); + } else if (pthread_join(enabler, NULL)) { + perror("pthread_join"); + } +} + +#endif + +static void set_fpscr(unsigned int fpscr) +{ + union { + double d; + unsigned int fpscr[2]; + } u; + + u.fpscr[0] = 0xFFF80000; + u.fpscr[1] = fpscr; + __asm__ __volatile__("mtfsf 255,%0" : : "f"(u.d)); +} + +static unsigned int get_fpscr(void) +{ + union { + double d; + unsigned int fpscr[2]; + } u; + + __asm__("mffs %0" : "=f"(u.d)); + return u.fpscr[1]; +} + +static void unmask_fpe(void) +{ + set_fpexc_precise(); + set_fpscr(0x80|0x40|0x10); /* VE, OE, ZE; not UE or XE */ +} + +static void unmask_fpe_conditional(int unmasked) +{ + if (unmasked) + unmask_fpe(); +} + +/* mask PowerPC FPE, return true if the previous state was unmasked */ +static int mask_fpe(void) +{ + int unmasked; + + unmasked = (get_fpscr() & (0x80|0x40|0x10)) == (0x80|0x40|0x10); + set_fpscr(0x00); + return unmasked; +} + +#else + +static void unmask_fpe(void) +{ + fpsetmask(FP_X_INV | FP_X_OFL | FP_X_DZ); +} + +static void unmask_fpe_conditional(int unmasked) +{ + if (unmasked) + unmask_fpe(); +} + +/* mask IEEE FPE, return true if previous state was unmasked */ +static int mask_fpe(void) +{ + const fp_except unmasked_mask = FP_X_INV | FP_X_OFL | FP_X_DZ; + fp_except old_mask; + + old_mask = fpsetmask(0); + return (old_mask & unmasked_mask) == unmasked_mask; +} + +#endif + +#if (defined(__linux__) && (defined(__i386__) || defined(__x86_64__) || defined(__sparc__) || defined(__powerpc__))) || (defined(__DARWIN__) && (defined(__i386__) || defined(__x86_64__) || defined(__ppc__))) || (defined(__FreeBSD__) && (defined(__x86_64__) || defined(__i386__))) || (defined(__OpenBSD__) && defined(__x86_64__)) || (defined(__sun__) && defined(__x86_64__)) + +#if defined(__linux__) && defined(__i386__) +#if !defined(X86_FXSR_MAGIC) +#define X86_FXSR_MAGIC 0x0000 +#endif +#elif defined(__FreeBSD__) && defined(__x86_64__) +#include <sys/types.h> +#include <machine/fpu.h> +#elif defined(__FreeBSD__) && defined(__i386__) +#include <sys/types.h> +#include <machine/npx.h> +#elif defined(__DARWIN__) +#include <machine/signal.h> +#elif defined(__OpenBSD__) && defined(__x86_64__) +#include <sys/types.h> +#include <machine/fpu.h> +#endif +#if !(defined(__OpenBSD__) && defined(__x86_64__)) +#include <ucontext.h> +#endif +#include <string.h> + +#if defined(__linux__) && defined(__x86_64__) +#define mc_pc(mc) ((mc)->gregs[REG_RIP]) +#elif defined(__linux__) && defined(__i386__) +#define mc_pc(mc) ((mc)->gregs[REG_EIP]) +#elif defined(__DARWIN__) && defined(__i386__) +#ifdef DARWIN_MODERN_MCONTEXT +#define mc_pc(mc) ((mc)->__ss.__eip) +#else +#define mc_pc(mc) ((mc)->ss.eip) +#endif +#elif defined(__DARWIN__) && defined(__x86_64__) +#ifdef DARWIN_MODERN_MCONTEXT +#define mc_pc(mc) ((mc)->__ss.__rip) +#else +#define mc_pc(mc) ((mc)->ss.rip) +#endif +#elif defined(__FreeBSD__) && defined(__x86_64__) +#define mc_pc(mc) ((mc)->mc_rip) +#elif defined(__FreeBSD__) && defined(__i386__) +#define mc_pc(mc) ((mc)->mc_eip) +#elif defined(__OpenBSD__) && defined(__x86_64__) +#define mc_pc(mc) ((mc)->sc_rip) +#elif defined(__sun__) && defined(__x86_64__) +#define mc_pc(mc) ((mc)->gregs[REG_RIP]) +#endif + +static void fpe_sig_action(int sig, siginfo_t *si, void *puc) +{ + ucontext_t *uc = puc; + unsigned long pc; + +#if defined(__linux__) +#if defined(__x86_64__) + mcontext_t *mc = &uc->uc_mcontext; + fpregset_t fpstate = mc->fpregs; + pc = mc_pc(mc); + /* A failed SSE2 instruction will restart. To avoid + looping we mask SSE2 exceptions now and unmask them + again later in erts_check_fpe()/erts_restore_fpu(). + On RISCs we update PC to skip the failed instruction, + but the ever increasing complexity of the x86 instruction + set encoding makes that a poor solution here. */ + fpstate->mxcsr = 0x1F80; + fpstate->swd &= ~0xFF; +#elif defined(__i386__) + mcontext_t *mc = &uc->uc_mcontext; + fpregset_t fpstate = mc->fpregs; + pc = mc_pc(mc); + if ((fpstate->status >> 16) == X86_FXSR_MAGIC) + ((struct _fpstate*)fpstate)->mxcsr = 0x1F80; + fpstate->sw &= ~0xFF; +#elif defined(__sparc__) && defined(__arch64__) + /* on SPARC the 3rd parameter points to a sigcontext not a ucontext */ + struct sigcontext *sc = (struct sigcontext*)puc; + pc = sc->sigc_regs.tpc; + sc->sigc_regs.tpc = sc->sigc_regs.tnpc; + sc->sigc_regs.tnpc += 4; +#elif defined(__sparc__) + /* on SPARC the 3rd parameter points to a sigcontext not a ucontext */ + struct sigcontext *sc = (struct sigcontext*)puc; + pc = sc->si_regs.pc; + sc->si_regs.pc = sc->si_regs.npc; + sc->si_regs.npc = (unsigned long)sc->si_regs.npc + 4; +#elif defined(__powerpc__) +#if defined(__powerpc64__) + mcontext_t *mc = &uc->uc_mcontext; + unsigned long *regs = &mc->gp_regs[0]; +#else + mcontext_t *mc = uc->uc_mcontext.uc_regs; + unsigned long *regs = &mc->gregs[0]; +#endif + pc = regs[PT_NIP]; + regs[PT_NIP] += 4; + regs[PT_FPSCR] = 0x80|0x40|0x10; /* VE, OE, ZE; not UE or XE */ +#endif +#elif defined(__DARWIN__) && (defined(__i386__) || defined(__x86_64__)) +#ifdef DARWIN_MODERN_MCONTEXT + mcontext_t mc = uc->uc_mcontext; + pc = mc_pc(mc); + mc->__fs.__fpu_mxcsr = 0x1F80; + *(unsigned short *)&mc->__fs.__fpu_fsw &= ~0xFF; +#else + mcontext_t mc = uc->uc_mcontext; + pc = mc_pc(mc); + mc->fs.fpu_mxcsr = 0x1F80; + *(unsigned short *)&mc->fs.fpu_fsw &= ~0xFF; +#endif /* DARWIN_MODERN_MCONTEXT */ +#elif defined(__DARWIN__) && defined(__ppc__) + mcontext_t mc = uc->uc_mcontext; + pc = mc->ss.srr0; + mc->ss.srr0 += 4; + mc->fs.fpscr = 0x80|0x40|0x10; +#elif defined(__FreeBSD__) && defined(__x86_64__) + mcontext_t *mc = &uc->uc_mcontext; + struct savefpu *savefpu = (struct savefpu*)&mc->mc_fpstate; + struct envxmm *envxmm = &savefpu->sv_env; + pc = mc_pc(mc); + envxmm->en_mxcsr = 0x1F80; + envxmm->en_sw &= ~0xFF; +#elif defined(__FreeBSD__) && defined(__i386__) + mcontext_t *mc = &uc->uc_mcontext; + union savefpu *savefpu = (union savefpu*)&mc->mc_fpstate; + pc = mc_pc(mc); + if (mc->mc_fpformat == _MC_FPFMT_XMM) { + struct envxmm *envxmm = &savefpu->sv_xmm.sv_env; + envxmm->en_mxcsr = 0x1F80; + envxmm->en_sw &= ~0xFF; + } else { + struct env87 *env87 = &savefpu->sv_87.sv_env; + env87->en_sw &= ~0xFF; + } +#elif defined(__OpenBSD__) && defined(__x86_64__) + struct fxsave64 *fxsave = uc->sc_fpstate; + pc = mc_pc(uc); + fxsave->fx_mxcsr = 0x1F80; + fxsave->fx_fsw &= ~0xFF; +#elif defined(__sun__) && defined(__x86_64__) + mcontext_t *mc = &uc->uc_mcontext; + struct fpchip_state *fpstate = &mc->fpregs.fp_reg_set.fpchip_state; + pc = mc_pc(mc); + fpstate->mxcsr = 0x1F80; + fpstate->sw &= ~0xFF; +#endif +#if 0 + { + char buf[64]; + snprintf(buf, sizeof buf, "%s: FPE at %p\r\n", __FUNCTION__, (void*)pc); + write(2, buf, strlen(buf)); + } +#endif + set_current_fp_exception(pc); +} + +static void erts_thread_catch_fp_exceptions(void) +{ + struct sigaction act; + memset(&act, 0, sizeof act); + act.sa_sigaction = fpe_sig_action; + act.sa_flags = SA_SIGINFO; + sigaction(SIGFPE, &act, NULL); + unmask_fpe(); +} + +#else /* !((__linux__ && (__i386__ || __x86_64__ || __powerpc__)) || (__DARWIN__ && (__i386__ || __x86_64__ || __ppc__))) */ + +static void fpe_sig_handler(int sig) +{ + set_current_fp_exception(1); /* XXX: convert to sigaction so we can get the trap PC */ +} + +static void erts_thread_catch_fp_exceptions(void) +{ + sys_sigset(SIGFPE, fpe_sig_handler); + unmask_fpe(); +} + +#endif /* (__linux__ && (__i386__ || __x86_64__ || __powerpc__)) || (__DARWIN__ && (__i386__ || __x86_64__ || __ppc__))) */ + +/* once-only initialisation early in the main thread */ +void erts_sys_init_float(void) +{ + erts_init_fp_exception(); + erts_thread_catch_fp_exceptions(); + erts_printf_block_fpe = erts_sys_block_fpe; + erts_printf_unblock_fpe = erts_sys_unblock_fpe; +} + +#endif /* NO_FPE_SIGNALS */ + +void erts_thread_init_float(void) +{ +#ifdef ERTS_SMP + /* This allows Erlang schedulers to leave Erlang-process context + and still have working FP exceptions. XXX: is this needed? */ + erts_thread_init_fp_exception(); +#endif + +#ifndef NO_FPE_SIGNALS + /* NOTE: + * erts_thread_disable_fpe() is called in all threads at + * creation. We at least need to call unmask_fpe() + */ +#if defined(__DARWIN__) || defined(__FreeBSD__) + /* Darwin (7.9.0) does not appear to propagate FP exception settings + to a new thread from its parent. So if we want FP exceptions, we + must manually re-enable them in each new thread. + FreeBSD 6.1 appears to suffer from a similar issue. */ + erts_thread_catch_fp_exceptions(); +#else + unmask_fpe(); +#endif + +#endif +} + +void erts_thread_disable_fpe(void) +{ +#if !defined(NO_FPE_SIGNALS) + (void)mask_fpe(); +#endif +} + +#if !defined(NO_FPE_SIGNALS) +int erts_sys_block_fpe(void) +{ + return mask_fpe(); +} + +void erts_sys_unblock_fpe(int unmasked) +{ + unmask_fpe_conditional(unmasked); +} +#endif + +/* The following check is incorporated from the Vee machine */ + +#define ISDIGIT(d) ((d) >= '0' && (d) <= '9') + +/* + ** Convert a double to ascii format 0.dddde[+|-]ddd + ** return number of characters converted + ** + ** These two functions should maybe use localeconv() to pick up + ** the current radix character, but since it is uncertain how + ** expensive such a system call is, and since no-one has heard + ** of other radix characters than '.' and ',' an ad-hoc + ** low execution time solution is used instead. + */ + +int +sys_double_to_chars(double fp, char *buf) +{ + char *s = buf; + + (void) sprintf(buf, "%.20e", fp); + /* Search upto decimal point */ + if (*s == '+' || *s == '-') s++; + while (ISDIGIT(*s)) s++; + if (*s == ',') *s++ = '.'; /* Replace ',' with '.' */ + /* Scan to end of string */ + while (*s) s++; + return s-buf; /* i.e strlen(buf) */ +} + +/* Float conversion */ + +int +sys_chars_to_double(char* buf, double* fp) +{ +#ifndef NO_FPE_SIGNALS + volatile unsigned long *fpexnp = erts_get_current_fp_exception(); +#endif + char *s = buf, *t, *dp; + + /* Robert says that something like this is what he really wanted: + * (The [.,] radix test is NOT what Robert wanted - it was added later) + * + * 7 == sscanf(Tbuf, "%[+-]%[0-9][.,]%[0-9]%[eE]%[+-]%[0-9]%s", ....); + * if (*s2 == 0 || *s3 == 0 || *s4 == 0 || *s6 == 0 || *s7) + * break; + */ + + /* Scan string to check syntax. */ + if (*s == '+' || *s == '-') s++; + if (!ISDIGIT(*s)) /* Leading digits. */ + return -1; + while (ISDIGIT(*s)) s++; + if (*s != '.' && *s != ',') /* Decimal part. */ + return -1; + dp = s++; /* Remember decimal point pos just in case */ + if (!ISDIGIT(*s)) + return -1; + while (ISDIGIT(*s)) s++; + if (*s == 'e' || *s == 'E') { + /* There is an exponent. */ + s++; + if (*s == '+' || *s == '-') s++; + if (!ISDIGIT(*s)) + return -1; + while (ISDIGIT(*s)) s++; + } + if (*s) /* That should be it */ + return -1; + +#ifdef NO_FPE_SIGNALS + errno = 0; +#endif + __ERTS_FP_CHECK_INIT(fpexnp); + *fp = strtod(buf, &t); + __ERTS_FP_ERROR_THOROUGH(fpexnp, *fp, return -1); + if (t != s) { /* Whole string not scanned */ + /* Try again with other radix char */ + *dp = (*dp == '.') ? ',' : '.'; + errno = 0; + __ERTS_FP_CHECK_INIT(fpexnp); + *fp = strtod(buf, &t); + __ERTS_FP_ERROR_THOROUGH(fpexnp, *fp, return -1); + } + +#ifdef NO_FPE_SIGNALS + if (errno == ERANGE && (*fp == 0.0 || *fp == HUGE_VAL || *fp == -HUGE_VAL)) { + return -1; + } +#endif + return 0; +} + +int +matherr(struct exception *exc) +{ +#if !defined(NO_FPE_SIGNALS) + set_current_fp_exception((unsigned long)__builtin_return_address(0)); +#endif + return 1; +} diff --git a/erts/emulator/sys/unix/sys_time.c b/erts/emulator/sys/unix/sys_time.c new file mode 100644 index 0000000000..fcce54a2c4 --- /dev/null +++ b/erts/emulator/sys/unix/sys_time.c @@ -0,0 +1,134 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2005-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +/* These need to be undef:ed to not break activation of + * micro level process accounting on /proc/self + */ +#ifdef _LARGEFILE_SOURCE +# undef _LARGEFILE_SOURCE +#endif +#ifdef _FILE_OFFSET_BITS +# undef _FILE_OFFSET_BITS +#endif + +#include "sys.h" +#include "global.h" + +#ifdef NO_SYSCONF +# define TICKS_PER_SEC() HZ +#else +#define TICKS_PER_SEC() sysconf(_SC_CLK_TCK) +#endif + +#ifdef HAVE_GETHRVTIME_PROCFS_IOCTL +# include <unistd.h> +# include <sys/types.h> +# include <sys/stat.h> +# include <sys/signal.h> +# include <sys/fault.h> +# include <sys/syscall.h> +# include <sys/procfs.h> +# include <fcntl.h> +#endif + +/******************* Routines for time measurement *********************/ + +int erts_ticks_per_sec = 0; /* Will be SYS_CLK_TCK in erl_unix_sys.h */ +int erts_ticks_per_sec_wrap = 0; /* Will be SYS_CLK_TCK_WRAP */ +static int ticks_bsr = 0; /* Shift wrapped tick value this much to the right */ + +/* + * init timers, chose a tick length, and return it. + * Unix is priviliged when it comes to time, as erl_time_sup.c + * does almost everything. Other platforms have to + * emulate Unix in this sense. + */ +int sys_init_time(void) +{ + /* + * This (erts_ticks_per_sec) is only for times() (CLK_TCK), + * the resolution is always one millisecond.. + */ + if ((erts_ticks_per_sec = TICKS_PER_SEC()) < 0) + erl_exit(1, "Can't get clock ticks/sec\n"); + if (erts_ticks_per_sec >= 1000) { + /* Workaround for beta linux kernels, need to be done in runtime + to make erlang run on both 2.4 and 2.5 kernels. In the future, + the kernel ticks might as + well be used as a high res timer instead, but that's for when the + majority uses kernels with HZ == 1024 */ + ticks_bsr = 3; + } else { + ticks_bsr = 0; + } + erts_ticks_per_sec_wrap = (erts_ticks_per_sec >> ticks_bsr); + return SYS_CLOCK_RESOLUTION; +} + +clock_t sys_times_wrap(void) +{ + SysTimes dummy; + clock_t result = (sys_times(&dummy) >> ticks_bsr); + return result; +} + + + + +#ifdef HAVE_GETHRVTIME_PROCFS_IOCTL + +int sys_start_hrvtime(void) +{ + long msacct = PR_MSACCT; + int fd; + + if ( (fd = open("/proc/self", O_WRONLY)) == -1) { + return -1; + } + if (ioctl(fd, PIOCSET, &msacct) < 0) { + close(fd); + return -2; + } + close(fd); + return 0; +} + +int sys_stop_hrvtime(void) +{ + long msacct = PR_MSACCT; + int fd; + + if ( (fd = open("/proc/self", O_WRONLY)) == -1) { + return -1; + } + if (ioctl(fd, PIOCRESET, &msacct) < 0) { + close(fd); + return -2; + } + close(fd); + return 0; +} + +#endif /* HAVE_GETHRVTIME_PROCFS_IOCTL */ + + |