/**
    system-dependent functions, including crash reporter and audio plug-and-play

    Copyright (c) 2020-2021 The Creators of Simphone

    See the file COPYING.LESSER.txt for copying permission.
**/

#ifndef _GNU_SOURCE
#define _GNU_SOURCE /* CLOCK_MONOTONIC in time.h and RUSAGE_THREAD in resource.h */
#endif

#include "config.h"
#include "spth.h"

#include "logger.h"
#include "table.h"
#include "error.h"
#include "utils.h"
#include "system.h"
#include "firewall.h"
#include "crypto.h"
#include "file.h"
#include "socket.h"
#include "contact.h"
#include "param.h"
#include "proto.h"
#include "audio.h"
#include "api.h"

#ifndef _WIN32
#include <sys/utsname.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/mman.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>

#if HAVE_SYS_UCONTEXT_H
#include <sys/ucontext.h>
#endif

#if HAVE_BACKTRACE
#include <execinfo.h>
#endif

#if HAVE_SYSCTL
#include <sys/sysctl.h>
#endif

#ifdef __NetBSD__
#undef KERN_PROC_PATHNAME
#endif

#if HAVE_DLADDR
#include <dlfcn.h>
#endif

#include <dirent.h>

#if HAVE_UDEV
#include <libudev.h>
#endif
#else /* _WIN32 */
#include <io.h>
#include <dbt.h>

#include <tlhelp32.h>
#include <imagehlp.h>

void _exit (int); /* maybe mingw doesn't have it defined */

#include "qos2.h"
#endif

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>

#ifdef __APPLE__
#include <Carbon/Carbon.h>
#endif

#define SIM_MODULE SIM_MODULE_SYSTEM

#ifndef SIM_STACK_PAGE_SIZE
#define SIM_STACK_PAGE_SIZE 4096 /* number of stack bytes to skip when address is invalid */
#endif

static int system_crash_mask = 0;
static simtype system_exe_hash, system_version_string;

static pth_t tid_pnp = NULL;

unsigned system_version_major = 0, system_version_minor = 0; /* set by sim_init_ */

#if defined(_WIN32) || defined(__APPLE__)
static int sim_system_init_audio_ (void) {
  int err = audio_init_ (PTH_PROTECT_ (SIM_STATUS_OFF));
  return PTH_UNPROTECT (err);
}
#endif

#ifdef HAVE_SYS_UCONTEXT_H
static void log_hex_ (simbyte value, unsigned offset, const char *format, const simbyte *pointer) {
  if (! (offset & 31)) {
    LOG_DEBUG_ ("\n");
    LOG_DEBUG_ (format, pointer);
  } else if (! (offset & 7))
    LOG_DEBUG_ (" ");
  LOG_DEBUG_ (" %02X", value);
}
#endif

#ifdef _WIN32

#define SIM_PNP_CLASS "simcorePnPcheckerWindowClass"
#define SIM_PNP_TIMER 1

static HWND system_pnp_window = NULL;
static UINT_PTR system_pnp_timer = 0;
static int system_pnp_timeout = 0;

struct win_dir {
  long handle;
  simtype name;
  struct _wfinddata_t data;
};

typedef BOOL (WINAPI *queryCycleTime) (HANDLE, PULONG64);

static HMODULE system_kernel_dll = NULL;
static queryCycleTime system_cpu_cycle = NULL;

typedef BOOL (WINAPI *qosCreateHandle) (PQOS_VERSION, PHANDLE);
typedef BOOL (WINAPI *qosCloseHandle) (HANDLE);
typedef BOOL (WINAPI *qosAddSocketToFlow) (HANDLE, SOCKET, PSOCKADDR, QOS_TRAFFIC_TYPE, DWORD, PQOS_FLOWID);
typedef BOOL (WINAPI *qosRemoveSocketFromFlow) (HANDLE, SOCKET, QOS_FLOWID, DWORD);
typedef BOOL (WINAPI *qosSetFlow) (HANDLE, QOS_FLOWID, QOS_SET_FLOW, ULONG, PVOID, DWORD, LPOVERLAPPED);

static HMODULE system_qwave_dll = NULL;
static qosSetFlow system_qos_flow = NULL;

static simbool system_endsession_flag = false;

#define SYSTEM_STACK_INIT(pipes) 0
#define SYSTEM_STACK_UNINIT(pipes)

#if ! HAVE_LIBPTH
static size_t sim_system_stack_size (simbyte **stack) {
#ifdef __GNUC__
  DWORD **tib;
  __asm__("movl %%fs:0x18, %0"
          : "=r"(tib)
          :
          :);
  *stack = (simbyte *) tib[2];
  return tib[1] - tib[2];
#else
  return 0;
#endif
}
#endif

static int sim_system_stack_read (const int *pipes, const simbyte *stackaddr) {
  simbyte ret;
  return ReadProcessMemory (GetCurrentProcess (), stackaddr, &ret, sizeof (ret), NULL) ? ret : 256;
}

#ifdef HAVE_SYS_UCONTEXT_H
static HMODULE system_dbghelp_dll = NULL;

static void *system_crash_address[2];

static LONG WINAPI sim_system_callback_crash (EXCEPTION_POINTERS *exception) {
  static const char *system_crash_titles[] = { "ABORT", "ASSERT", "CRASH", " STOP" };
  static char system_crash_buffer[8192];
  unsigned k, n, x;
  PEXCEPTION_RECORD record = exception->ExceptionRecord;
  DWORD eip[3];
  PCONTEXT ctx = exception->ContextRecord;
  simbyte *stackptr = (simbyte *) ctx->Esp, *stacktop = (simbyte *) -9;
  simbool logok = log_protect_ (NULL, SIM_LOG_FATAL);
  const char *version = NULL;
  char *s = sim_get_version (NULL, &version);
  for (k = 0; k < SIM_ARRAY_SIZE (system_crash_address); k++)
    if (record->ExceptionAddress == system_crash_address[k])
      break;
  eip[0] = eip[1] = eip[2] = 0;
  sprintf (system_crash_buffer, "%s: %08lX %08lX %08lX\"", system_crash_titles[k], eip[0], eip[1], eip[2]);
  if (system_exe_hash.typ == SIMSTRING) {
    sprintf (s + strlen (s), " (%s)", system_exe_hash.str);
  } else if (system_exe_hash.typ == SIMNUMBER)
    sprintf (s + strlen (s), " (%I64d)", system_exe_hash.num);
  strcat (system_crash_buffer, s);
  LOG_FATAL_ERROR_ ("%s\n", s);
  if (version) {
    LOG_FATAL_ERROR_ ("%s\n", version);
    sprintf (system_crash_buffer + strlen (system_crash_buffer), "\"%s", version);
  }
  sprintf (s = system_crash_buffer + strlen (system_crash_buffer), "\"\"ExceptionCode = %08lX", record->ExceptionCode);
  LOG_FATAL_ERROR_ ("%s", s + 2);
  for (n = 0; n < record->NumberParameters && n < EXCEPTION_MAXIMUM_PARAMETERS; n++) {
    s = system_crash_buffer + strlen (system_crash_buffer);
    sprintf (s, " %s%08lX", n ? "" : "(", record->ExceptionInformation[n]);
    LOG_FATAL_ERROR_ ("%s", s);
  }
  s = system_crash_buffer + strlen (system_crash_buffer);
  sprintf (s, "%s ExceptionAddress = %p", record->NumberParameters ? ")" : "", record->ExceptionAddress);
  LOG_FATAL_ERROR_ ("%s", s);
  if (record->ExceptionFlags) {
    sprintf (s = system_crash_buffer + strlen (system_crash_buffer), " ExceptionFlags = %08lX", record->ExceptionFlags);
    LOG_FATAL_ERROR_ ("%s", s);
  }
  LOG_FATAL_ERROR_ ("\n");
  s = system_crash_buffer + strlen (system_crash_buffer);
  sprintf (s, "\"ContextFlags = %08lX EFlags = %08lX", ctx->ContextFlags, ctx->EFlags);
  LOG_FATAL_ERROR_ ("%s\n", s + 1);
  s = system_crash_buffer + strlen (system_crash_buffer);
  sprintf (s, "\"EAX = %08lX EBX = %08lX ECX = %08lX EDX = %08lX", ctx->Eax, ctx->Ebx, ctx->Ecx, ctx->Edx);
  LOG_FATAL_ERROR_ ("%s\n", s + 1);
  s = system_crash_buffer + strlen (system_crash_buffer);
  sprintf (s, "\"ESI = %08lX EDI = %08lX EBP = %08lX ESP = %08lX EIP = %08lX",
           ctx->Esi, ctx->Edi, ctx->Ebp, ctx->Esp, ctx->Eip);
  LOG_FATAL_ERROR_ ("%s\n", s + 1);
  sprintf (system_crash_buffer + strlen (system_crash_buffer),
           "\"CS = %08lX SS = %08lX DS = %08lX ES = %08lX FS = %08lX GS = %08lX",
           ctx->SegCs, ctx->SegSs, ctx->SegDs, ctx->SegEs, ctx->SegFs, ctx->SegGs);
  LOG_FATAL_ERROR_ (" CS = %08lX  SS = %08lX  DS = %08lX  ES = %08lX  FS = %08lX  GS = %08lX\n",
                    ctx->SegCs, ctx->SegSs, ctx->SegDs, ctx->SegEs, ctx->SegFs, ctx->SegGs);
  LOG_FATAL_ERROR_ ("\n");
  if (system_dbghelp_dll && record->ExceptionCode != EXCEPTION_STACK_OVERFLOW) {
    typedef BOOL (WINAPI * symInitialize) (HANDLE, PVOID, BOOL);
    symInitialize sym_initialize = (symInitialize) GetProcAddress (system_dbghelp_dll, "SymInitialize");
    typedef BOOL (WINAPI * stackWalk) (DWORD, HANDLE, HANDLE, LPSTACKFRAME, LPVOID, FARPROC, FARPROC, FARPROC, FARPROC);
    stackWalk stack_walk = (stackWalk) GetProcAddress (system_dbghelp_dll, "StackWalk");
    STACKFRAME frame;
    if (sym_initialize)
      sym_initialize (GetCurrentProcess (), NULL, true);
    memset (&frame, 0, sizeof (frame));
    frame.AddrPC.Offset = ctx->Eip;
    frame.AddrPC.Mode = AddrModeFlat;
    frame.AddrStack.Offset = ctx->Esp;
    frame.AddrStack.Mode = AddrModeFlat;
    frame.AddrFrame.Offset = ctx->Ebp;
    frame.AddrFrame.Mode = AddrModeFlat;
    if (stack_walk) {
      sprintf (system_crash_buffer + strlen (system_crash_buffer), "\"STACK:");
      while (stack_walk (IMAGE_FILE_MACHINE_I386, GetCurrentProcess (), GetCurrentThread (), &frame, ctx, NULL,
                         GetProcAddress (system_dbghelp_dll, "SymFunctionTableAccess"),
                         GetProcAddress (system_dbghelp_dll, "SymGetModuleBase"), NULL)) {
        stacktop = (simbyte *) frame.AddrFrame.Offset;
        if (! eip[0]) {
          eip[0] = frame.AddrPC.Offset;
        } else if (! eip[1]) {
          eip[1] = frame.AddrPC.Offset;
        } else if (! eip[2])
          eip[2] = frame.AddrPC.Offset;
        s = system_crash_buffer + strlen (system_crash_buffer);
        sprintf (s, "\"EIP = %08lX EBP = %08lX", frame.AddrPC.Offset, (long) stacktop);
        LOG_FATAL_ERROR_ ("%s", s + 1);
        if (s - system_crash_buffer >= sizeof (system_crash_buffer) - 100)
          *s = 0;
        for (n = 0; n < SIM_ARRAY_SIZE (frame.Params); n++) {
          s = system_crash_buffer + strlen (system_crash_buffer);
          sprintf (s, " %sP%d = %08lX", n ? " " : "(", n + 1, frame.Params[n]);
          LOG_FATAL_ERROR_ ("%s", s);
          if (s - system_crash_buffer >= sizeof (system_crash_buffer) - 100)
            *s = 0;
        }
        if (s - system_crash_buffer < sizeof (system_crash_buffer) - 100)
          strcat (system_crash_buffer, ")");
        LOG_FATAL_ERROR_ (")\n");
      }
      sprintf (s = system_crash_buffer + strlen (system_crash_buffer), "\"ERROR = %08lX ", GetLastError ());
      LOG_FATAL_ERROR_ ("%s", s + 1);
    }
    sprintf (s = system_crash_buffer + strlen (system_crash_buffer), "RET = %08lX", frame.AddrReturn.Offset);
    LOG_FATAL_ERROR_ ("%s\n", s);
    /*SymCleanup (GetCurrentProcess());*/
  }
  if (LOG_CHECK_LEVEL_ (SIM_LOG_DEBUG))
    for (n = 0; stackptr < stacktop + sizeof (DWORD) && (x = sim_system_stack_read (NULL, stackptr)) < 256; n++)
      log_hex_ (x, n, "%08X:", stackptr++);
  LOG_DEBUG_ ("\n");
  LOG_FATAL_ERROR_ ("\n");
  if (system_crash_mask & 4 && ! system_endsession_flag) {
    int err = record->ExceptionCode == EXCEPTION_IN_PAGE_ERROR ? record->ExceptionCode : SIM_OK;
    if (! eip[0] || k >= SIM_ARRAY_SIZE (system_crash_address))
      eip[0] = (DWORD) record->ExceptionAddress;
    if (err != SIM_OK) {
      n = eip[0] = eip[1] = eip[2] = 0;
      for (; n < record->NumberParameters && n < SIM_ARRAY_SIZE (eip) && n < EXCEPTION_MAXIMUM_PARAMETERS; n++)
        eip[n] = record->ExceptionInformation[n];
      k = SIM_ARRAY_SIZE (system_crash_titles) - 1;
    }
    LOG_FATAL_ERROR_ ("%s: %lX %lX %lX\n", system_crash_titles[k], eip[0], eip[1], eip[2]);
    sprintf (system_crash_buffer, "%s: %08lX %08lX %08lX", system_crash_titles[k], eip[0], eip[1], eip[2]);
    system_crash_buffer[strlen (system_crash_buffer)] = '"';
    sim_event_send_fatal (system_crash_mask == 4 ? err : SIM_NO_ERROR, pointer_new (system_crash_buffer));
    LOG_FATAL_ERROR_ ("error %d\n", system_crash_mask == 4 ? err : SIM_NO_ERROR);
  }
  if (logok)
    log_unprotect_ ();
  return system_crash_mask & 1 ? EXCEPTION_CONTINUE_SEARCH : EXCEPTION_EXECUTE_HANDLER;
}
#endif

void system_init_crash (int mask) {
#ifdef HAVE_SYS_UCONTEXT_H
  static const char *system_crash_names[] = { "abort", "_assert" };
  static byte system_crash_bytes[2];
  unsigned n;
  if (mask > 1) {
#ifndef DONOT_DEFINE
    HMODULE msvcrt = LoadLibrary ("msvcrt.dll");
    const byte breakpoint = 0xCC;
#endif
    system_dbghelp_dll = LoadLibrary ("dbghelp.dll");
    SetUnhandledExceptionFilter (sim_system_callback_crash);
#ifndef DONOT_DEFINE
    for (n = 0; n < SIM_ARRAY_SIZE (system_crash_address); n++)
      if ((system_crash_address[n] = msvcrt ? GetProcAddress (msvcrt, system_crash_names[n]) : NULL) != NULL)
        if (ReadProcessMemory (GetCurrentProcess (), system_crash_address[n], &system_crash_bytes[n], 1, NULL))
          WriteProcessMemory (GetCurrentProcess (), system_crash_address[n], &breakpoint, sizeof (breakpoint), NULL);
#endif
  } else if (system_crash_mask) {
#ifndef DONOT_DEFINE
    for (n = 0; n < SIM_ARRAY_SIZE (system_crash_address); n++)
      if (system_crash_address[n]) {
        WriteProcessMemory (GetCurrentProcess (), system_crash_address[n], &system_crash_bytes[n], 1, NULL);
        system_crash_address[n] = NULL;
      }
#endif
    SetUnhandledExceptionFilter (NULL);
  }
#endif
  system_crash_mask = mask;
}

simbool system_qos_set (simsocket sock, void *sin) {
  if (system_version_major <= 5)
    return false;
  if (! system_qwave_dll) {
    if ((system_qwave_dll = LoadLibrary ("qwave.dll")) == NULL) {
      LOG_WARN_ ("qos load $%d error %d\n", sock->fd, GetLastError ());
    } else if ((system_qos_flow = (qosSetFlow) GetProcAddress (system_qwave_dll, "QOSSetFlow")) == NULL)
      LOG_WARN_ ("qos get $%d error %d\n", sock->fd, GetLastError ());
  }
  if (system_qwave_dll && system_qos_flow) {
    QOS_TRAFFIC_TYPE tos = sock->qos.tos;
    void *handle = sock->qos.handle;
    if (! handle) {
      qosAddSocketToFlow qos_add;
      qosCreateHandle qos_create = (qosCreateHandle) GetProcAddress (system_qwave_dll, "QOSCreateHandle");
      QOS_VERSION ver;
      ver.MajorVersion = 1, ver.MinorVersion = 0;
      if (! qos_create || ! qos_create (&ver, &sock->qos.handle)) {
        LOG_WARN_ ("qos create $%d error %d\n", sock->fd, GetLastError ());
      } else if ((qos_add = (qosAddSocketToFlow) GetProcAddress (system_qwave_dll, "QOSAddSocketToFlow")) == NULL) {
        LOG_WARN_ ("qos flow $%d error %d\n", sock->fd, GetLastError ());
      } else if (! qos_add (sock->qos.handle, sock->fd, sin,
                            QOSTrafficTypeBestEffort, QOS_NON_ADAPTIVE_FLOW, &sock->qos.flowid)) {
        LOG_WARN_ ("qos add $%d error %d\n", sock->fd, GetLastError ());
      } else
        handle = sock->qos.handle;
    }
    if (handle)
      if (! system_qos_flow (sock->qos.handle, sock->qos.flowid, QOSSetTrafficType, sizeof (tos), &tos, 0, NULL))
        LOG_WARN_ ("qos set $%d error %d\n", sock->fd, GetLastError ());
  }
  return true;
}

void system_qos_unset (simsocket sock) {
  if (system_qwave_dll) {
    if (sock->qos.flowid) {
      qosRemoveSocketFromFlow qos_remove =
        (qosRemoveSocketFromFlow) GetProcAddress (system_qwave_dll, "QOSRemoveSocketFromFlow");
      if (! qos_remove || ! qos_remove (sock->qos.handle, 0, sock->qos.flowid, 0))
        LOG_ERROR_ ("qos remove $%d error %d\n", sock->fd, GetLastError ());
      sock->qos.flowid = 0;
    }
    if (sock->qos.handle) {
      qosCloseHandle qos_close = (qosCloseHandle) GetProcAddress (system_qwave_dll, "QOSCloseHandle");
      if (! qos_close || ! qos_close (sock->qos.handle))
        LOG_ERROR_ ("qos close $%d error %d\n", sock->fd, GetLastError ());
      sock->qos.handle = NULL;
    }
  }
}

const char *inet_ntop (int af, const void *src, char *dst, socklen_t length) {
  DWORD len, size = length;
  union {
    struct sockaddr_in sin;
    struct sockaddr_in6 sin6;
  } sin;
  memset (&sin, 0, sizeof (sin));
  switch (af) {
    default:
      return NULL;
    case AF_INET:
      len = sizeof (sin.sin);
      sin.sin.sin_family = AF_INET;
      memcpy (&sin.sin.sin_addr, src, sizeof (sin.sin.sin_addr));
      break;
    case AF_INET6:
      len = sizeof (sin.sin6);
      sin.sin6.sin6_family = AF_INET6;
      memcpy (&sin.sin6.sin6_addr, src, sizeof (sin.sin6.sin6_addr));
  }
  return WSAAddressToStringA ((struct sockaddr *) &sin, len, 0, dst, &size) == SOCKET_ERROR ? NULL : dst;
}

int sim_system_cpu_count (void) {
  SYSTEM_INFO sysinfo;
  memset (&sysinfo, 0, sizeof (sysinfo));
  GetSystemInfo (&sysinfo);
  return sysinfo.dwNumberOfProcessors > 0 ? sysinfo.dwNumberOfProcessors : 1;
}

simunsigned sim_system_cpu_get (int cpu, void *tid) {
  simunsigned t0, t1;
  FILETIME crtime, extime, ktime, utime;
  HANDLE handle = cpu == SYSTEM_CPU_TIME_PROCESS ? GetCurrentProcess () : GetCurrentThread ();
  if (tid) {
#if ! HAVE_LIBPTH
    if (cpu == SYSTEM_CPU_TIME_PROCESS)
#endif
      return 0;
    handle = pth_get_handle (tid);
  }
  if (cpu == SYSTEM_CPU_TIME_CYCLES && system_cpu_cycle && system_cpu_cycle (handle, &t0))
    return (t0 & ~(simunsigned) 1) | 2;
  if (cpu == SYSTEM_CPU_TIME_PROCESS ? ! GetProcessTimes (handle, &crtime, &extime, &ktime, &utime) :
                                       ! GetThreadTimes (handle, &crtime, &extime, &ktime, &utime))
    return 0;
  t0 = (simunsigned) utime.dwHighDateTime << 32 | utime.dwLowDateTime;
  t1 = (simunsigned) ktime.dwHighDateTime << 32 | ktime.dwLowDateTime;
  return (t0 + t1) * 100 + 2;
}

simunsigned sim_get_tick (void) {
  return GetTickCount ();
}

static void _sim_system_get_version (char *version, char *release) {
  struct {
    ULONG dwOSVersionInfoSize;
    ULONG dwMajorVersion;
    ULONG dwMinorVersion;
    ULONG dwBuildNumber;
    ULONG dwPlatformId;
    WCHAR szCSDVersion[128];
    USHORT wServicePackMajor;
    USHORT wServicePackMinor;
    USHORT wSuiteMask;
    UCHAR wProductType;
    UCHAR wReserved;
  } ver;
  typedef LONG (WINAPI * rtlGetVersion) (PVOID);
  rtlGetVersion rtl_get_version = (rtlGetVersion) GetProcAddress (GetModuleHandle ("ntdll.dll"), "RtlGetVersion");
  memset (&ver, 0, sizeof (ver));
  ver.dwOSVersionInfoSize = sizeof (ver);
  if (! rtl_get_version || rtl_get_version (&ver))
    LOG_ERROR_ ("RtlGetGetVersion error\n");
  system_version_minor = ver.dwMinorVersion;
  system_version_major = ver.dwMajorVersion;
  system_version_string = nil ();
  if (version) {
    DWORD size = 512, type = REG_NONE;
    HKEY hkey;
    char *s = memset (release, 0, size + 1);
    if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, "Hardware\\Description\\System\\CentralProcessor\\0",
                      0, KEY_QUERY_VALUE, &hkey) == ERROR_SUCCESS) {
      if (RegQueryValueEx (hkey, "Identifier", 0, &type, (simbyte *) release, &size) == ERROR_SUCCESS && type == REG_SZ)
        s = strchr (release, ' ');
      if (s)
        *s = 0;
      RegCloseKey (hkey);
    }
    sprintf (version, "Windows %d.%d.%d %s (build %d)", (int) ver.dwMajorVersion, (int) ver.dwMinorVersion,
             (int) ver.wProductType, release, (int) ver.dwBuildNumber);
    sprintf (release, "%ls (SP %d.%d)", ver.szCSDVersion, (int) ver.wServicePackMajor, (int) ver.wServicePackMinor);
  } else if (! system_cpu_cycle && (system_kernel_dll || (system_kernel_dll = LoadLibrary ("kernel32.dll")) != NULL))
    system_cpu_cycle = (queryCycleTime) GetProcAddress (system_kernel_dll, "QueryThreadCycleTime");
}

simnumber sim_system_get_memory (simbool total) {
  MEMORYSTATUSEX status;
  memset (&status, 0, sizeof (status));
  status.dwLength = sizeof (status);
  return GlobalMemoryStatusEx (&status) ? (total ? status.ullTotalPhys : status.ullAvailPhys) : -1;
}

simtype sim_system_get_exe (simtype *exe) {
  if (exe) {
    wchar_t path[MAX_PATH];
    int l = GetModuleFileNameW (NULL, SIM_ARRAY (path));
    *exe = nil ();
    if (l > 0 && l < SIM_ARRAY_SIZE (path)) {
      unsigned len = wcslen (path);
      *exe = sim_convert_ucs_to_utf (path, &len);
    }
  }
  return system_exe_hash;
}

void *sim_system_dir_open (const char *filename) {
  struct win_dir *dir;
  int err;
  unsigned len = strlen (filename);
  simtype ucs = sim_convert_utf_to_ucs (filename, &len);
  unsigned short *name = ucs.ptr;
  if (ucs.typ == SIMNIL) {
    errno = SIM_FILE_BAD_NAME;
    return NULL;
  }
  if (len) {
    wchar_t c = name[--len];
    if (c != ':' && c != '\\' && c != '/')
      name[++len] = '\\';
    name[++len] = '*';
    name[++len] = 0;
  }
  dir = sim_new (sizeof (*dir));
  dir->handle = _wfindfirst (name, &dir->data);
  dir->name = nil ();
  err = errno;
  string_free (ucs);
  if (dir->handle == -1)
    sim_free (dir, sizeof (*dir)), dir = NULL;
  errno = err;
  return dir;
}

int sim_system_dir_close (void *dir) {
  struct win_dir *windir = dir;
  int ret = _findclose (windir->handle) ? errno : SIM_OK;
  string_free (windir->name);
  sim_free (windir, sizeof (*windir));
  return ret;
}

char *sim_system_dir_read (void *dir) {
  unsigned len;
  struct win_dir *windir = dir;
  if (! windir->data.name[0])
    return NULL;
  string_free (windir->name);
  len = wcslen (windir->data.name);
  if ((windir->name = sim_convert_ucs_to_utf (windir->data.name, &len)).typ == SIMNIL)
    windir->name = pointer_new (".");
  if (_wfindnext (windir->handle, &windir->data))
    windir->data.name[0] = 0;
  return windir->name.ptr;
}

simbool sim_system_pnp_cancel (void) {
  UINT_PTR timer = system_pnp_timer;
  if (timer && system_pnp_window && ! KillTimer (system_pnp_window, timer)) {
    LOG_ERROR_ ("KillTimer error %d\n", GetLastError ());
  } else
    system_pnp_timer = 0;
  return timer != 0;
}

static VOID CALLBACK sim_system_pnp_callback_timeout__ (HWND window, UINT message, UINT_PTR timer, DWORD dwTime) {
  sim_system_pnp_cancel ();
  sim_system_init_audio_ ();
}

static LRESULT CALLBACK sim_system_pnp_callback_message__ (HWND window, UINT message, WPARAM wParam, LPARAM lParam) {
  if (message == WM_ENDSESSION)
    system_endsession_flag = wParam;
  if (message != WM_DEVICECHANGE)
    return DefWindowProc (window, message, wParam, lParam);
  if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE) {
    DEV_BROADCAST_DEVICEINTERFACE *dev = (void *) lParam;
    if (dev->dbcc_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
      static const GUID system_pnp_guid = {
        0xA5DCBF10, 0x6530, 0x11D2, { 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED }
      };
      if (! memcmp (&dev->dbcc_classguid, &system_pnp_guid, sizeof (system_pnp_guid)))
        LOG_DEBUG_ ("device %s %s\n", wParam == DBT_DEVICEARRIVAL ? "added" : "removed", dev->dbcc_name);
      sim_system_pnp_cancel ();
      system_pnp_timer = SetTimer (window, SIM_PNP_TIMER, system_pnp_timeout, sim_system_pnp_callback_timeout__);
      if (system_pnp_timer != SIM_PNP_TIMER)
        LOG_ERROR_ ("SetTimer %d error %d\n", system_pnp_timer, GetLastError ());
    }
  }
  return TRUE;
}

static void sim_system_pnp_stop_thread (void) {
  if (system_pnp_window && ! PostMessage (system_pnp_window, WM_QUIT, 0, 0))
    LOG_ERROR_ ("PostMessage error %d\n", GetLastError ());
  system_pnp_window = NULL;
}

static void *thread_pnp_ (void *arg) {
#if ! HAVE_LIBPTH && HAVE_LIBSPEEX
  HWND window;
  HDEVNOTIFY notify;
  MSG msgs;
  WNDCLASSEX klass;
  LOG_API_DEBUG_ ("%d\n", system_pnp_timeout);
  memset (&klass, 0, sizeof (klass));
  klass.cbSize = sizeof (klass);
  klass.style = CS_DBLCLKS;
  klass.lpfnWndProc = sim_system_pnp_callback_message__;
  klass.lpszClassName = SIM_PNP_CLASS;
  if (RegisterClassEx (&klass)) {
    if ((window = CreateWindowEx (WS_EX_LEFT, SIM_PNP_CLASS, SIM_PNP_CLASS, WS_OVERLAPPED, CW_USEDEFAULT, CW_USEDEFAULT,
                                  CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, NULL)) != NULL) {
      const DWORD flags = DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES;
      DEV_BROADCAST_DEVICEINTERFACE filter;
      memset (&filter, 0, sizeof (filter));
      filter.dbcc_size = sizeof (filter);
      filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
      if ((notify = RegisterDeviceNotification (system_pnp_window = window, &filter, flags)) != NULL) {
        int ret;
        pth_unprotect ();
        while ((ret = GetMessage (&msgs, NULL, 0, 0)) > 0) {
          TranslateMessage (&msgs);
          DispatchMessage (&msgs);
        }
        if (PTH_PROTECT_ (ret) < 0)
          LOG_ERROR_ ("GetMessage error %d\n", GetLastError ());
        if (! UnregisterDeviceNotification (notify))
          LOG_ERROR_ ("UnregisterDeviceNotification error %d\n", GetLastError ());
        sim_system_pnp_cancel ();
      } else
        LOG_ERROR_ ("RegisterDeviceNotification error %d\n", GetLastError ());
      if (! DestroyWindow (window)) {
        LOG_WARN_ ("DestroyWindow error %d\n", GetLastError ());
      } else if (! UnregisterClass (SIM_PNP_CLASS, NULL))
        LOG_ERROR_ ("UnregisterClass error %d\n", GetLastError ());
      system_pnp_window = NULL;
    } else {
      LOG_ERROR_ ("CreateWindowEx error %d\n", GetLastError ());
      UnregisterClass (SIM_PNP_CLASS, NULL);
    }
  } else
    LOG_ERROR_ ("RegisterClassEx error %d\n", GetLastError ());
#endif
  LOG_API_DEBUG_ ("\n");
  return pth_thread_exit_ (false);
}

#else /* _WIN32 */

#define SYSTEM_VERSION_NAME "PRETTY_NAME="

#if defined(__linux__) && defined(__i386__)
#define SYSTEM_GET_CONTEXT_IP(context) ((simbyte *) (context)->uc_mcontext.gregs[REG_EIP])
#define SYSTEM_GET_CONTEXT_SP(context) ((simbyte *) (context)->uc_mcontext.gregs[REG_ESP])
#define SYSTEM_GET_X32(NAME) { REG_##NAME, #NAME },
#elif defined(__linux__) && defined(__x86_64__)
#define SYSTEM_GET_CONTEXT_IP(context) ((simbyte *) (context)->uc_mcontext.gregs[REG_RIP])
#define SYSTEM_GET_CONTEXT_SP(context) ((simbyte *) (context)->uc_mcontext.gregs[REG_RSP])
#define SYSTEM_GET_X64(NAME) { REG_##NAME, #NAME },
#elif defined(__NetBSD__) && defined(__i386__)
#define SYSTEM_GET_CONTEXT_IP(context) ((simbyte *) (context)->uc_mcontext.__gregs[_REG_EIP])
#define SYSTEM_GET_CONTEXT_SP(context) ((simbyte *) (context)->uc_mcontext.__gregs[_REG_ESP])
#define SYSTEM_GET_X32(NAME) { _REG_##NAME, #NAME },
#elif defined(__NetBSD__) && defined(__x86_64__)
#define SYSTEM_GET_CONTEXT_IP(context) ((simbyte *) (context)->uc_mcontext.__gregs[_REG_RIP])
#define SYSTEM_GET_CONTEXT_SP(context) ((simbyte *) (context)->uc_mcontext.__gregs[_REG_RSP])
#define SYSTEM_GET_X64(NAME) { _REG_##NAME, #NAME },
#elif defined(__APPLE__) && defined(__x86_64__)
#define SYSTEM_GET_CONTEXT_IP(context) ((simbyte *) (context)->uc_mcontext->__ss.__rip)
#define SYSTEM_GET_CONTEXT_SP(context) ((simbyte *) (context)->uc_mcontext->__ss.__rsp)
#define SYSTEM_GET_X64(NAME) { (int) (long) &((mcontext_t) NULL)->__ss.__##NAME / sizeof (void *), #NAME },
#define SYSTEM_GET_REG_X64(NAME) { (int) (long) &((mcontext_t) NULL)->__es.__##NAME / sizeof (void *), #NAME },
#elif defined(__APPLE__) && defined(__i386__)
#define SYSTEM_GET_CONTEXT_IP(context) ((simbyte *) (context)->uc_mcontext->__ss.__eip)
#define SYSTEM_GET_CONTEXT_SP(context) ((simbyte *) (context)->uc_mcontext->__ss.__esp)
#define SYSTEM_GET_X32(NAME) { (int) (long) &((mcontext_t) NULL)->__ss.__##NAME / sizeof (void *), #NAME },
#define SYSTEM_GET_REG_X32(NAME) { (int) (long) &((mcontext_t) NULL)->__es.__##NAME / sizeof (void *), #NAME },
#elif (defined(__FreeBSD__) || defined(__DragonFly__)) && defined(__x86_64__)
#define SYSTEM_GET_CONTEXT_IP(context) ((simbyte *) (context)->uc_mcontext.mc_rip)
#define SYSTEM_GET_CONTEXT_SP(context) ((simbyte *) (context)->uc_mcontext.mc_rsp)
#define SYSTEM_GET_X64(NAME) { (int) (long) &((mcontext_t *) NULL)->mc_##NAME / sizeof (void *), #NAME },
#define SYSTEM_GET_REG_X64(NAME) SYSTEM_GET_X64 (NAME)
#elif (defined(__FreeBSD__) || defined(__DragonFly__)) && defined(__i386__)
#define SYSTEM_GET_CONTEXT_IP(context) ((simbyte *) (context)->uc_mcontext.mc_eip)
#define SYSTEM_GET_CONTEXT_SP(context) ((simbyte *) (context)->uc_mcontext.mc_esp)
#define SYSTEM_GET_X32(NAME) { (int) (long) &((mcontext_t *) NULL)->mc_##NAME / sizeof (void *), #NAME },
#define SYSTEM_GET_REG_X32(NAME) SYSTEM_GET_X32 (NAME)
#elif defined(__OpenBSD__) && defined(__x86_64__)
#define SYSTEM_GET_CONTEXT_IP(context) ((simbyte *) (context)->sc_rip)
#define SYSTEM_GET_CONTEXT_SP(context) ((simbyte *) (context)->sc_rsp)
#elif defined(__OpenBSD__) && defined(__i386__)
#define SYSTEM_GET_CONTEXT_IP(context) ((simbyte *) (context)->sc_eip)
#define SYSTEM_GET_CONTEXT_SP(context) ((simbyte *) (context)->sc_esp)
#else /* generic unix */
#define SYSTEM_GET_CONTEXT_IP(context) NULL
#define SYSTEM_GET_CONTEXT_SP(context) NULL
#endif

#ifdef __APPLE__
#define SYSTEM_GET_CONTEXT_SIZE(context) ((context)->uc_mcsize)
#define SYSTEM_GET_CONTEXT_REG(context, idx) (((void **) (context)->uc_mcontext)[idx])
#elif defined(__OpenBSD__)
#define SYSTEM_GET_CONTEXT_SIZE(context) sizeof (*(context))
#define SYSTEM_GET_CONTEXT_REG(context, idx) (((void **) (context))[idx])
#else
#define SYSTEM_GET_CONTEXT_SIZE(context) sizeof ((context)->uc_mcontext)
#define SYSTEM_GET_CONTEXT_REG(context, idx) (((void **) &(context)->uc_mcontext)[idx])
#endif

#ifndef SYSTEM_GET_X32
#define SYSTEM_GET_X32(NAME)
#define SYSTEM_GET_REG_X32(NAME)
#endif

#ifndef SYSTEM_GET_X64
#define SYSTEM_GET_X64(NAME)
#define SYSTEM_GET_REG_X64(NAME)
#endif

static const struct system_register {
  unsigned idx;
  const char *name;
} system_registers[] = {
#if defined(__linux__) || defined(__NetBSD__)
  SYSTEM_GET_X32 (EAX) SYSTEM_GET_X32 (EBX) SYSTEM_GET_X32 (ECX) SYSTEM_GET_X32 (EDX)
    SYSTEM_GET_X32 (ESI) SYSTEM_GET_X32 (EDI) SYSTEM_GET_X32 (EBP) SYSTEM_GET_X32 (ESP)
      SYSTEM_GET_X32 (CS) SYSTEM_GET_X32 (SS) SYSTEM_GET_X32 (DS)
        SYSTEM_GET_X32 (ES) SYSTEM_GET_X32 (FS) SYSTEM_GET_X32 (GS)
          SYSTEM_GET_X32 (EIP) SYSTEM_GET_X32 (EFL) SYSTEM_GET_X32 (ERR) SYSTEM_GET_X32 (UESP) SYSTEM_GET_X32 (TRAPNO)
            SYSTEM_GET_X64 (RAX) SYSTEM_GET_X64 (RBX) SYSTEM_GET_X64 (RCX) SYSTEM_GET_X64 (RDX)
              SYSTEM_GET_X64 (RSI) SYSTEM_GET_X64 (RDI) SYSTEM_GET_X64 (RBP) SYSTEM_GET_X64 (RSP)
                SYSTEM_GET_X64 (R15) SYSTEM_GET_X64 (R14) SYSTEM_GET_X64 (R13) SYSTEM_GET_X64 (R12)
                  SYSTEM_GET_X64 (R11) SYSTEM_GET_X64 (R10) SYSTEM_GET_X64 (R9) SYSTEM_GET_X64 (R8)
                    SYSTEM_GET_X64 (RIP) SYSTEM_GET_X64 (ERR) SYSTEM_GET_X64 (TRAPNO)
#ifdef __linux
                      SYSTEM_GET_X64 (CR2) SYSTEM_GET_X64 (EFL) SYSTEM_GET_X64 (CSGSFS) SYSTEM_GET_X64 (OLDMASK)
#else
                      SYSTEM_GET_X64 (CS) SYSTEM_GET_X64 (SS) SYSTEM_GET_X64 (DS) SYSTEM_GET_X64 (ES)
                        SYSTEM_GET_X64 (FS) SYSTEM_GET_X64 (GS) SYSTEM_GET_X64 (RFL)
#endif
#else
  SYSTEM_GET_X32 (eax) SYSTEM_GET_X32 (ebx) SYSTEM_GET_X32 (ecx) SYSTEM_GET_X32 (edx)
    SYSTEM_GET_X32 (esi) SYSTEM_GET_X32 (edi) SYSTEM_GET_X32 (ebp) SYSTEM_GET_X32 (esp)
      SYSTEM_GET_X32 (cs) SYSTEM_GET_X32 (ss) SYSTEM_GET_X32 (ds)
        SYSTEM_GET_X32 (es) SYSTEM_GET_X32 (fs) SYSTEM_GET_X32 (gs)
          SYSTEM_GET_X32 (eip) SYSTEM_GET_REG_X32 (err) SYSTEM_GET_REG_X32 (trapno) SYSTEM_GET_X32 (eflags)
            SYSTEM_GET_X64 (rax) SYSTEM_GET_X64 (rbx) SYSTEM_GET_X64 (rcx) SYSTEM_GET_X64 (rdx)
              SYSTEM_GET_X64 (rsi) SYSTEM_GET_X64 (rdi) SYSTEM_GET_X64 (rbp) SYSTEM_GET_X64 (rsp)
                SYSTEM_GET_X64 (r15) SYSTEM_GET_X64 (r14) SYSTEM_GET_X64 (r13) SYSTEM_GET_X64 (r12)
                  SYSTEM_GET_X64 (r11) SYSTEM_GET_X64 (r10) SYSTEM_GET_X64 (r9) SYSTEM_GET_X64 (r8)
                    SYSTEM_GET_X64 (rip) SYSTEM_GET_REG_X64 (err) SYSTEM_GET_REG_X64 (trapno) SYSTEM_GET_X64 (rflags)
#ifdef __APPLE__
                      SYSTEM_GET_REG_X32 (faultvaddr)
                        SYSTEM_GET_X64 (cs) SYSTEM_GET_X64 (fs) SYSTEM_GET_X64 (gs) SYSTEM_GET_REG_X64 (faultvaddr)
#else
                      SYSTEM_GET_X32 (isp)
                        SYSTEM_GET_X64 (cs) SYSTEM_GET_X64 (ss) SYSTEM_GET_X64 (flags) SYSTEM_GET_REG_X64 (addr)
#endif
#endif /* __linux__ || __NetBSD__ */
                        { (unsigned) -1, NULL }
};

static simbyte *system_exe_pointer = NULL;

#define SYSTEM_STACK_INIT(pipes) pipe (pipes)
#define SYSTEM_STACK_UNINIT(pipes) close ((pipes)[0]), close ((pipes)[1])

#if ! HAVE_LIBPTH
static size_t sim_system_stack_size (simbyte **stack) {
  size_t size = 0;
#if HAVE_PTHREAD_GETATTR_NP
  pthread_attr_t attr;
  if (! pthread_getattr_np (pthread_self (), &attr)) {
    if (pthread_attr_getstack (&attr, (void **) stack, &size))
      size = 0;
    pthread_attr_destroy (&attr);
  }
#endif
  return size;
}
#endif

static int sim_system_stack_read (const int *pipes, const simbyte *stackaddr) {
  simbyte ret;
  if (sim_file_write (pipes[1], stackaddr, sizeof (*stackaddr)) <= 0)
    return 256; /* invalid address */
  return sim_file_read (pipes[0], &ret, sizeof (ret)) == sizeof (ret) ? ret : -1;
}

#ifdef HAVE_SYS_UCONTEXT_H
static void sim_system_callback_crash (int sig, siginfo_t *siginfo, void *context) {
  static const int system_crash_signals[] = { SIGABRT, SIGFPE, SIGILL, SIGBUS };
  static const char *system_crash_titles[] = { "ABORT", "OVERFLOW", "ILLEGAL", "EXCEPTION", "CRASH" };
#if HAVE_BACKTRACE
  static void *system_crash_stack[1024];
#endif
  static char system_crash_buffer[4096];
  unsigned k, i, n, x;
  ucontext_t *ctx = context;
  simbyte *ip = SYSTEM_GET_CONTEXT_IP (ctx), *stackptr = SYSTEM_GET_CONTEXT_SP (ctx);
  int pipes[2], ret = SYSTEM_STACK_INIT (pipes);
  simbool logok = log_protect_ (NULL, SIM_LOG_FATAL);
  const char *version;
  char *s = sim_get_version (NULL, &version);
  for (k = 0; k < SIM_ARRAY_SIZE (system_crash_signals); k++)
    if (sig == system_crash_signals[k])
      break;
  sprintf (system_crash_buffer, "%s: %p", system_crash_titles[k], (void *) (ip - system_exe_pointer));
  if (system_exe_pointer)
    sprintf (system_crash_buffer + strlen (system_crash_buffer), " + %p", system_exe_pointer);
  if (system_exe_hash.typ == SIMSTRING) {
    sprintf (s + strlen (s), " (%s)", system_exe_hash.str);
  } else if (system_exe_hash.typ == SIMNUMBER)
    sprintf (s + strlen (s), " (%lld)", system_exe_hash.num);
  sprintf (system_crash_buffer + strlen (system_crash_buffer), "\"%s", s);
  LOG_FATAL_ERROR_ ("%s\n", s);
  if (version) {
    LOG_FATAL_ERROR_ ("%s\n", version);
    sprintf (system_crash_buffer + strlen (system_crash_buffer), "\"%s", version);
  }
  LOG_FATAL_ERROR_ ("SIGNAL = %d CODE = 0x%x DATA = %p", siginfo->si_signo, siginfo->si_code, siginfo->si_addr);
  sprintf (system_crash_buffer + strlen (system_crash_buffer), "\"\"SIGNAL = %d CODE = 0x%x DATA = %p",
           siginfo->si_signo, siginfo->si_code, siginfo->si_addr);
  n = SYSTEM_GET_CONTEXT_SIZE (ctx);
  if (n != sizeof (void *))
    for (i = 0; i < n / sizeof (void *); i++) {
      const struct system_register *r;
      void *p = SYSTEM_GET_CONTEXT_REG (ctx, i);
      LOG_FATAL_ERROR_ ("%s", i % 4 ? " " : "\n");
      for (r = system_registers; r->name && r->idx != i; r++) {}
      if (r->name) {
        LOG_FATAL_ERROR_ ("%%%s = %p", r->name, p);
        sprintf (system_crash_buffer + strlen (system_crash_buffer), " %%%s = %p", r->name, p);
      } else {
        LOG_FATAL_ERROR_ ("%%%02d = %p", i, p);
        sprintf (system_crash_buffer + strlen (system_crash_buffer), " %%%02d = %p", i, p);
      }
    }
  LOG_FATAL_ERROR_ ("\n");
  LOG_FATAL_ERROR_ ("\n");
  if (system_exe_pointer)
    LOG_FATAL_ERROR_ ("BASE = %p\n", (void *) system_exe_pointer);
#if HAVE_BACKTRACE
  sprintf (system_crash_buffer + strlen (system_crash_buffer), "\"\"STACK:\"");
  n = backtrace (SIM_ARRAY (system_crash_stack));
  for (i = 0; i < n; i++) {
    LOG_FATAL_ERROR_ ("EIP = %p\n", (void *) ((simbyte *) system_crash_stack[i] - system_exe_pointer));
    if (i < 100)
      sprintf (system_crash_buffer + strlen (system_crash_buffer), "%p ",
               (void *) ((simbyte *) system_crash_stack[i] - system_exe_pointer));
  }
#endif
  if (stackptr && ! ret && LOG_CHECK_LEVEL_ (SIM_LOG_DEBUG))
    for (i = 0; (x = sim_system_stack_read (pipes, stackptr)) < 256; i++)
      log_hex_ ((simbyte) x, i, "%p:", stackptr++);
  if (! ret) {
    LOG_DEBUG_ ("\n");
    SYSTEM_STACK_UNINIT (pipes);
  }
  LOG_FATAL_ERROR_ ("\n");
  if (system_crash_mask & 4) {
    LOG_FATAL_ERROR_ ("%s: %p\n", system_crash_titles[k], (void *) (ip - system_exe_pointer));
    sim_event_send_fatal (system_crash_mask == 4 ? SIM_OK : SIM_NO_ERROR, pointer_new (system_crash_buffer));
    LOG_FATAL_ERROR_ ("error %d\n", system_crash_mask == 4 ? SIM_OK : SIM_NO_ERROR);
  }
  if (logok)
    log_unprotect_ ();
  if (system_crash_mask == 2)
    _exit (255);
}
#endif

void system_init_crash (int mask) {
#ifdef HAVE_SYS_UCONTEXT_H
  static struct sigaction system_old_segv, system_old_abrt, system_old_fpe, system_old_ill, system_old_bus;
#if defined(SA_ONSTACK) && defined(SIGSTKSZ) && ! defined(__APPLE__)
  static stack_t system_old_stack;
#endif
  if (mask > 1) {
    struct sigaction action;
#if defined(SA_ONSTACK) && defined(SIGSTKSZ) && ! defined(__APPLE__)
    static char system_crash_stack[SIGSTKSZ];
    int ret;
    stack_t sigstk;
    memset (&sigstk, 0, sizeof (sigstk));
    sigstk.ss_sp = system_crash_stack;
    sigstk.ss_size = sizeof (system_crash_stack);
    ret = sigaltstack (&sigstk, &system_old_stack);
#endif
    memset (&action, 0, sizeof (action));
    action.sa_sigaction = sim_system_callback_crash;
    sigemptyset (&action.sa_mask);
#if defined(SA_ONSTACK) && defined(SIGSTKSZ) && ! defined(__APPLE__)
    if (! ret)
      action.sa_flags = SA_ONSTACK;
#endif
    action.sa_flags |= SA_SIGINFO;
    if (mask & 1)
      action.sa_flags |= SA_RESETHAND;
    sigaction (SIGSEGV, &action, &system_old_segv);
    sigaction (SIGABRT, &action, &system_old_abrt);
    sigaction (SIGFPE, &action, &system_old_fpe);
    sigaction (SIGILL, &action, &system_old_ill);
    sigaction (SIGBUS, &action, &system_old_bus);
  } else if (system_crash_mask) {
    sigaction (SIGSEGV, &system_old_segv, NULL);
    sigaction (SIGABRT, &system_old_abrt, NULL);
    sigaction (SIGFPE, &system_old_fpe, NULL);
    sigaction (SIGILL, &system_old_ill, NULL);
    sigaction (SIGBUS, &system_old_bus, NULL);
#if defined(SA_ONSTACK) && defined(SIGSTKSZ) && ! defined(__APPLE__)
    sigaltstack (&system_old_stack, NULL);
#endif
  }
#endif
  system_crash_mask = mask;
}

int sim_system_cpu_count (void) {
  int n = 0;
#if HAVE_SYSCTL
  size_t len = sizeof (n);
  static int system_ctl_ncpu[2] = { CTL_HW, HW_NCPU };
  if (! sysctl (SIM_ARRAY (system_ctl_ncpu), &n, &len, NULL, 0) && n)
    return n;
#endif
#ifdef _SC_NPROCESSORS_ONLN
  n = (int) sysconf (_SC_NPROCESSORS_ONLN);
#endif
  return n > 0 ? n : 1;
}

simunsigned sim_system_cpu_get (int cpu, void *tid) {
  long t;
  struct rusage usage;
#if defined(CLOCK_THREAD_CPUTIME_ID) && ! defined(__NetBSD__)
  struct timespec ts;
  clockid_t id = CLOCK_THREAD_CPUTIME_ID;
  if (tid)
#if ! HAVE_LIBPTH && ! defined(__OpenBSD__)
    if (pthread_getcpuclockid (pth_get_handle (tid), &id))
#endif
      return 0;
  if (! clock_gettime (cpu == SYSTEM_CPU_TIME_PROCESS ? CLOCK_PROCESS_CPUTIME_ID : id, &ts))
    return (((simunsigned) ts.tv_sec * 1000000000 + ts.tv_nsec) & ~(simunsigned) 1) | 2;
#endif
#ifdef RUSAGE_THREAD
  if (tid || getrusage (cpu == SYSTEM_CPU_TIME_PROCESS ? RUSAGE_SELF : RUSAGE_THREAD, &usage))
#else
  if (tid || getrusage (RUSAGE_SELF, &usage))
#endif
    return 0;
  t = (usage.ru_utime.tv_usec + usage.ru_stime.tv_usec) * 1000 + 2;
  return t + (simunsigned) 1000000000 * (usage.ru_utime.tv_sec + usage.ru_stime.tv_sec);
}

simunsigned sim_get_tick (void) {
  struct timeval bsd;
#if defined(CLOCK_MONOTONIC_RAW) || defined(CLOCK_MONOTONIC)
  struct timespec ts;
#ifdef CLOCK_MONOTONIC_RAW
  if (! clock_gettime (CLOCK_MONOTONIC_RAW, &ts) || ! clock_gettime (CLOCK_MONOTONIC, &ts))
#else
  if (! clock_gettime (CLOCK_MONOTONIC, &ts))
#endif
    return (simunsigned) ts.tv_sec * 1000 + (ts.tv_nsec + 500000) / 1000000;
#endif
  return gettimeofday (&bsd, NULL) ? 0 : (simunsigned) bsd.tv_sec * 1000 + (bsd.tv_usec + 500) / 1000;
}

static void _sim_system_get_version (char *version, char *release) {
  struct utsname buf;
  if (! version) {
    char ver[128];
#ifdef __APPLE__
    int major, minor, patch;
    typedef OSErr (*GestaltFunction) (OSType selector, int *response);
    CFBundleRef bundle = CFBundleGetBundleWithIdentifier (CFSTR ("com.apple.CoreServices"));
    GestaltFunction gestalt = CFBundleGetFunctionPointerForName (bundle, CFSTR ("Gestalt"));
    if (gestalt && ! gestalt ('sys1', &major) && ! gestalt ('sys2', &minor) && ! gestalt ('sys3', &patch)) {
      sprintf (ver, "MacOS %d.%d.%d", major, minor, patch);
      system_version_string = string_copy (ver);
    }
#else
    FILE *f = fopen ("/etc/os-release", "rt");
    system_version_string = nil ();
    if (! f)
      f = fopen ("/usr/lib/os-release", "rt");
    if (f) {
      int err = SIM_OK;
      unsigned len;
      while (err == SIM_OK && ! feof (f)) {
        errno = 0;
        if (fgets (ver, sizeof (ver) - 1, f)) {
          ver[sizeof (ver) - 1] = 0;
          if ((len = strlen (ver)) != 0 && ver[len - 1] == '\n')
            ver[--len] = 0;
          if (! SIM_STRING_CHECK_DIFF_CONST (ver, SYSTEM_VERSION_NAME)) {
            system_version_string = string_copy (ver + SIM_STRING_GET_LENGTH_CONST (SYSTEM_VERSION_NAME));
            break;
          }
        } else if (ferror (f))
          err = errno ? errno : EPERM;
      }
      fclose (f);
    }
#endif
  } else if (! uname (&buf)) {
    sprintf (version, "%s %s %s ", buf.sysname, buf.release, buf.machine);
    if (system_version_string.len)
      strcat (version, system_version_string.ptr);
    strcpy (release, buf.version);
  } else {
    strcpy (version, "unix");
    *release = 0;
  }
}

simnumber sim_system_get_memory (simbool total) {
  simnumber num = 0;
#ifdef __linux__
  if (! total) {
    static const char *system_free_size_names[] = { "MemFree:", "Buffers:", "Cached:", "SReclaimable:", NULL };
    FILE *f = fopen ("/proc/meminfo", "rt");
    if (f) {
      simnumber size = 0;
      const char **sizes;
      char *s, *e, buf[512];
      while (fgets (buf, sizeof (buf) - 1, f)) {
        buf[sizeof (buf) - 1] = 0;
        for (sizes = system_free_size_names; *sizes; sizes++)
          if (! strncmp (buf, *sizes, strlen (*sizes))) {
            for (s = buf + strlen (*sizes); *s && *s == ' '; s++) {}
            for (e = s; *e && *e != ' '; e++) {}
            if (! strcmp (e, " kB\n")) {
              *e = 0;
              if (sim_convert_string_to_simunsigned (s, &num))
                size += num;
            }
          }
      }
      fclose (f);
      if (size)
        return size << 10;
    }
  }
#elif defined(__APPLE__)
  size_t len = sizeof (num);
  static int system_ctl_memsize[2] = { CTL_HW, HW_MEMSIZE };
  if (! total) {
    mach_msg_type_number_t count = HOST_VM_INFO_COUNT;
    int size = getpagesize ();
    vm_statistics_data_t vmstat;
    memset (&vmstat, 0, sizeof (vmstat));
    if (size > 0 && host_statistics (mach_host_self (), HOST_VM_INFO, (host_info_t) &vmstat, &count) == KERN_SUCCESS)
      return (simunsigned) size * (vmstat.free_count - vmstat.speculative_count + vmstat.inactive_count);
  }
  if (! sysctl (SIM_ARRAY (system_ctl_memsize), &num, &len, NULL, 0) && num)
    return num;
#endif
#if defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE)
  return (num = sysconf (_SC_PHYS_PAGES)) > 0 ? num * sysconf (_SC_PAGESIZE) : num;
#else
  return 0;
#endif
}

simtype sim_system_get_exe (simtype *exe) {
  if (exe)
    *exe = nil ();
  if (exe) {
#if HAVE_DLADDR && ! defined(KERN_PROC_PATHNAME) && ! defined(__linux__)
    Dl_info info;
    if (dladdr (sim_system_get_exe, &info)) {
#ifndef __APPLE__
      if (! strchr (info.dli_fname, '/')) {
        char *path = getenv ("PATH");
        while (path && *path) {
          simtype t = string_copy (path);
          char *s = strchr (t.ptr, ':');
          struct stat buf;
          if (s) {
            *s = 0;
            s = path + (s - (char *) t.str) + 1;
          }
          *exe = string_concat (t.ptr, FILE_DIRECTORY_SLASH, info.dli_fname, NULL);
          if (! stat (exe->ptr, &buf) && S_ISREG (buf.st_mode) && buf.st_mode & S_IXUSR)
            break;
          string_free (*exe), *exe = nil ();
          string_free (t);
          path = s;
        }
      } else
#endif
        *exe = string_copy (info.dli_fname);
      system_exe_pointer = info.dli_fbase;
    }
#else
    char path[BUFSIZ];
#if HAVE_SYSCTL
    size_t len = sizeof (path);
    static int system_ctl_procpath[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
    if (! sysctl (SIM_ARRAY (system_ctl_procpath), path, &len, NULL, 0)) {
      *exe = string_copy (path);
    } else
#endif
    {
#if defined(__linux__) || defined(__CYGWIN__)
      int len;
      char buf[BUFSIZ];
      sprintf (path, "/proc/%u/exe", getpid ());
      len = readlink (path, buf, sizeof (buf));
      if (len > 0 && len < (int) sizeof (buf))
        *exe = string_copy_len (buf, len);
#endif
    }
#endif /* HAVE_DLADDR */
  }
  return system_exe_hash;
}

void *sim_system_dir_open (const char *filename) {
  return opendir (filename);
}

int sim_system_dir_close (void *dir) {
  return ! closedir (dir) ? SIM_OK : errno ? errno : EACCES;
}

char *sim_system_dir_read (void *dir) {
  struct dirent *ent = readdir (dir);
  return ent ? ent->d_name : NULL;
}

#ifdef __APPLE__
#include <IOKit/IOKitLib.h>

#define SIM_PNP_TIMER_INFINITE 631152000
#define SYSTEM_PNP_CALC_TIMER(seconds) (CFAbsoluteTimeGetCurrent () + (CFTimeInterval) (seconds))

static int system_pnp_timeout = 0;
static CFRunLoopTimerRef system_pnp_timer = NULL;
static CFRunLoopRef system_pnp_loop = NULL;

simbool sim_system_pnp_cancel (void) {
  CFRunLoopTimerRef timer = system_pnp_timer;
  if (timer)
    CFRunLoopTimerSetNextFireDate (timer, SYSTEM_PNP_CALC_TIMER (SIM_PNP_TIMER_INFINITE));
  return timer != 0;
}

static void sim_system_pnp_callback_timeout__ (CFRunLoopTimerRef timer, void *context) {
  sim_system_pnp_cancel ();
  sim_system_init_audio_ ();
}

static void sim_system_pnp_callback_message (void *context, io_iterator_t iterator) {
  io_object_t obj;
  for (; (obj = IOIteratorNext (iterator)) != 0; IOObjectRelease (obj)) {}
  if (context)
    CFRunLoopTimerSetNextFireDate (system_pnp_timer, SYSTEM_PNP_CALC_TIMER (system_pnp_timeout / 1000.0));
}

static void sim_system_pnp_stop_thread (void) {
  if (system_pnp_loop)
    CFRunLoopStop (system_pnp_loop);
  system_pnp_loop = NULL;
}
#else
static int system_pnp_timer = 0, system_pnp_timeout = 0;

simbool sim_system_pnp_cancel (void) {
  int timer = system_pnp_timer;
  system_pnp_timer = 0;
  return timer != 0;
}

#if HAVE_UDEV
#include <syslog.h>

static int system_pnp_pipe[2];

static void sim_system_pnp_stop_thread (void) {
  int ret = 0;
  if ((ret = sim_file_write (system_pnp_pipe[1], &ret, sizeof (ret))) != sizeof (ret))
    LOG_ERROR_ ("pipe %d error %d\n", ret, errno);
}

static void log_callback_pnp_ (struct udev *udev, int priority, const char *file, int line,
                               const char *function, const char *format, va_list args) {
  static const int system_udev_log_levels[] = {
    SIM_LOG_ERROR, SIM_LOG_WARN, SIM_LOG_NOTE, SIM_LOG_INFO, SIM_LOG_DEBUG
  };
  static const int system_udev_log_priorities[] = { LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG };
  int i = SIM_ARRAY_SIZE (system_udev_log_priorities);
  while (i-- && system_udev_log_priorities[i] != priority) {}
  log_print_function_ (SIM_MODULE, system_udev_log_levels[i + 1], function ? function : "udev", format, args);
}

#define SYSTEM_CALL(CODE) (errno = 0, (CODE))
#else
#define sim_system_pnp_stop_thread()
#endif /* HAVE_UDEV */
#endif /* __APPLE__ */

static void *thread_pnp_ (void *arg) {
#if ! HAVE_LIBPTH && HAVE_LIBSPEEX && defined(__APPLE__)
  CFMutableDictionaryRef dict;
  LOG_API_DEBUG_ ("%d\n", system_pnp_timeout);
  system_pnp_timer = CFRunLoopTimerCreate (NULL, SYSTEM_PNP_CALC_TIMER (SIM_PNP_TIMER_INFINITE),
                                           SIM_PNP_TIMER_INFINITE, 0, 0, sim_system_pnp_callback_timeout__, NULL);
  if (system_pnp_timer && (dict = CFDictionaryCreateMutable (kCFAllocatorDefault, 0, NULL, NULL)) != NULL) {
    IOReturn ret;
    io_iterator_t matched = 0, terminated = 0;
    IONotificationPortRef port = IONotificationPortCreate (kIOMasterPortDefault);
    CFRunLoopAddTimer (system_pnp_loop = CFRunLoopGetCurrent (), system_pnp_timer, kCFRunLoopCommonModes);
    CFRunLoopAddSource (system_pnp_loop, IONotificationPortGetRunLoopSource (port), kCFRunLoopDefaultMode);
    dict = (CFMutableDictionaryRef) CFRetain (dict);
    if ((ret = IOServiceAddMatchingNotification (port, kIOMatchedNotification, dict, sim_system_pnp_callback_message,
                                                 &ret, &matched)) == KERN_SUCCESS) {
      sim_system_pnp_callback_message (NULL, matched);
    } else
      LOG_ERROR_ ("IOServiceAddMatchingNotification matched error %d\n", ret);
    ret = IOServiceAddMatchingNotification (port, kIOTerminatedNotification, dict,
                                            sim_system_pnp_callback_message, &ret, &terminated);
    if (ret == KERN_SUCCESS) {
      sim_system_pnp_callback_message (NULL, terminated);
    } else
      LOG_ERROR_ ("IOServiceAddMatchingNotification terminated error %d\n", ret);
    PTH_UNPROTECT_PROTECT_ (CFRunLoopRun ());
    if (terminated)
      IOObjectRelease (terminated);
    if (matched)
      IOObjectRelease (matched);
  } else
    LOG_ERROR_ ("%s error\n", system_pnp_timer ? "CFDictionaryCreateMutable" : "CFRunLoopTimerCreate");
  if (system_pnp_timer) {
    CFRelease (system_pnp_timer);
    system_pnp_timer = 0;
  }
#elif HAVE_UDEV
  struct udev *udev;
  LOG_API_DEBUG_ ("%d\n", system_pnp_timeout);
  sim_system_pnp_cancel ();
  if (SYSTEM_CALL (udev = udev_new ()) != NULL) {
    struct udev_monitor *mon;
    udev_set_log_fn (udev, log_callback_pnp_);
    udev_set_log_priority (udev, LOG_DEBUG);
    if ((mon = SYSTEM_CALL (udev_monitor_new_from_netlink (udev, "udev"))) != NULL) {
      int err = SYSTEM_CALL (udev_monitor_filter_add_match_subsystem_devtype (mon, "sound", NULL)), fd;
      if (err) {
        LOG_ERROR_ ("udev_monitor_filter_add_match_subsystem_devtype error %d\n", err);
      } else if (SYSTEM_CALL (err = udev_monitor_enable_receiving (mon)) != 0) {
        LOG_ERROR_ ("udev_monitor_enable_receiving %d error %d\n", err, errno);
      } else if (SYSTEM_CALL (fd = udev_monitor_get_fd (mon)) >= 0) {
        LOG_DEBUG_ ("udev fd $%d\n", fd);
        while ((err = socket_select_pipe_ (fd, system_pnp_pipe[0], system_pnp_timer ? 1000 : -1)) <= 1) {
          if (err < 0 && errno != EINTR) {
            LOG_ERROR_ ("udev select $%d error %d\n", fd, errno);
            break;
          }
          if (err > 0) {
            struct udev_device *dev;
            if ((dev = SYSTEM_CALL (udev_monitor_receive_device (mon))) == NULL) {
              LOG_ERROR_ ("udev_monitor_receive_device error %d\n", errno);
              break;
            }
            LOG_DEBUG_ ("device %s %s\n", udev_device_get_action (dev), udev_device_get_devnode (dev));
            udev_device_unref (dev);
            system_pnp_timer = system_pnp_timeout;
          } else if (system_pnp_timer && ! err && (system_pnp_timer -= 1000) <= 0) {
            sim_system_pnp_cancel ();
            audio_init_ (SIM_STATUS_OFF);
          }
        }
      } else
        LOG_ERROR_ ("udev_monitor_get_fd error %d\n", errno);
      udev_monitor_unref (mon);
    } else
      LOG_ERROR_ ("udev_monitor_new_from_netlink error %d\n", errno);
    udev_unref (udev);
  } else
    LOG_ERROR_ ("udev_new error %d\n", errno);
#endif /* __APPLE__ */
  LOG_API_DEBUG_ ("\n");
  return pth_thread_exit_ (false);
}

#endif /* _WIN32 */

void system_pnp_start (void) {
  if (! tid_pnp) {
    system_pnp_timeout = param_get_number ("audio.pnp");
#if HAVE_UDEV
    if (pipe (system_pnp_pipe)) {
      LOG_ERROR_ ("pipe error %d\n", errno);
    } else
#endif
    {
      if (! system_pnp_timeout || pth_thread_spawn (thread_pnp_, NULL, &tid_pnp, -1) != SIM_OK) {
#if HAVE_UDEV
        close (system_pnp_pipe[0]), system_pnp_pipe[0] = -1;
        close (system_pnp_pipe[1]), system_pnp_pipe[1] = -1;
#endif
      }
    }
  }
}

void system_pnp_stop_ (void) {
  if (tid_pnp) {
    sim_system_pnp_stop_thread ();
    pth_thread_join_ (&tid_pnp, NULL, thread_pnp_, -1);
#if HAVE_UDEV
    close (system_pnp_pipe[0]), system_pnp_pipe[0] = -1;
    close (system_pnp_pipe[1]), system_pnp_pipe[1] = -1;
#endif
  }
}

simunsigned system_get_tick (void) {
  static simunsigned system_time_last = 0, system_time_high = 0;
  simunsigned tick = sim_get_tick () + system_time_high;
  if (! tick) {
    LOG_FATAL_ (SIM_OK, "SYSTEM CLOCK FROZEN");
    return 0;
  }
  if (tick < system_time_last) {
#ifdef _WIN32
    if (system_time_last - tick >= 0x80000000)
      return system_time_last = tick += system_time_high += 0x100000000LL;
#endif
    LOG_DEBUG_ ("TIME WARP %lld ms\n", system_time_last - tick);
    tick = system_time_last;
  }
  return system_time_last = tick;
}

char *sim_system_get_version (char *version, char *release) {
  if (version || release)
    _sim_system_get_version (version, release);
#ifdef _WIN32
  if (system_version_major == 10)
    return system_version_minor ? "windows-10x" : "windows-10";
  if (system_version_major > 10)
    return "windows-1x";
#endif
  return NULL; /* getenv ("SIMPHONE_BAD"); */
}

int system_init_memory (int megabytes) {
  int err = SIM_OK;
#if HAVE_MLOCKALL
  static simbool system_locked_flag = false;
  if (! megabytes) {
    if (system_locked_flag && munlockall ()) {
      err = errno ? errno : ENOMEM;
    } else
      system_locked_flag = false;
  } else if (! mlockall (MCL_FUTURE)) {
    void *test = malloc (megabytes * 1048576);
    if (! test) { /* RLIMIT_MEMLOCK exceeded or something */
      err = errno ? errno : ENOMEM;
      munlockall ();
    } else {
      free (test);
      system_locked_flag = true;
    }
  } else
    err = errno ? errno : ENOMEM;
  if (err)
    LOG_WARN_ ("mlockall error %d\n", err);
#endif
  return err;
}

void system_init_random (void) {
  simbyte buffer[300];
  int *things = (int *) buffer;
  unsigned seed = (unsigned) time (NULL);
#ifndef _WIN32
  void *dir;
  struct timeval tv;
#if HAVE_SYSCTL
  size_t len = 0, size;
  static int system_ctl_procall[3] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
#endif
  srand (seed);
  srandom (seed);
  gettimeofday (&tv, NULL);
  *things++ = tv.tv_usec;
  *things++ = getuid ();
  *things++ = getgid ();
  *things++ = getppid ();
  *things++ = getpid ();
#if HAVE_SYSCTL
  if (! sysctl (SIM_ARRAY (system_ctl_procall), NULL, &len, NULL, 0)) {
    simbyte *buf = sim_new (size = len);
    if (! sysctl (SIM_ARRAY (system_ctl_procall), buf, &len, NULL, 0))
      random_init_entropy (pointer_new_len (buf, len));
    sim_free (buf, size);
  } else
#endif
  {
    if ((dir = sim_system_dir_open ("/proc")) != NULL) {
      char *filename;
      while ((filename = sim_system_dir_read (dir)) != NULL) {
        simtype name = string_cat ("/proc/", filename);
        struct stat buf;
        if (! stat (name.ptr, &buf))
          random_init_entropy (pointer_new_len (&buf, sizeof (buf)));
        random_init_entropy (pointer_new (filename));
        string_free (name);
      }
      sim_system_dir_close (dir);
    }
  }
#else /* _WIN32 */
  simbyte *buf = buffer;
  HANDLE snap = CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0);
  srand (seed);
  if (snap != INVALID_HANDLE_VALUE) {
    PROCESSENTRY32 proc;
    proc.dwSize = sizeof (proc);
    if (Process32First (snap, &proc))
      do {
        random_init_entropy (pointer_new_len (&proc, sizeof (proc)));
      } while (Process32Next (snap, &proc));
    CloseHandle (snap);
  }
  GetCaretPos ((POINT *) buf);
  buf += sizeof (POINT);
  GetCursorInfo ((CURSORINFO *) buf);
  buf += sizeof (CURSORINFO);
  GlobalMemoryStatus ((MEMORYSTATUS *) buf);
  buf += sizeof (MEMORYSTATUS);
  GetSystemInfo ((SYSTEM_INFO *) buf);
  buf += sizeof (SYSTEM_INFO);
  GetSystemTime ((SYSTEMTIME *) buf);
  buf += sizeof (SYSTEMTIME);
  QueryPerformanceCounter ((LARGE_INTEGER *) buf);
  buf += sizeof (LARGE_INTEGER);
  *(HWND *) buf = GetForegroundWindow ();
  things = (int *) (buf + sizeof (HWND));
  *things++ = GetQueueStatus (QS_ALLINPUT);
  *things++ = GetLogicalDrives ();
  *things++ = GetCurrentThreadId ();
  *things++ = GetCurrentProcessId ();
#endif
  *things++ = sim_get_random (0x40000000);
  random_init_entropy (pointer_new_len (buffer, (simbyte *) things - buffer));
}

int system_init_ (simbool install, int *error) {
  if (! error) {
    int fd, len;
    simtype exe, hash;
    simnumber size;
    _sim_system_get_version (NULL, NULL);
    sim_system_get_exe (&exe);
    sim_file_size (exe.ptr, &size, NULL);
    system_exe_hash = number_new (size);
    if ((fd = sim_file_open (exe.ptr, FILE_BIT_READ)) >= 0) {
      simbyte buf[4096];
      void *md = sim_crypt_md_new (CRYPT_MD_RIPEMD);
      while ((len = sim_file_read (fd, buf, sizeof (buf))) > 0)
        sim_crypt_md_update (md, buf, len);
      sim_file_close (fd);
      hash = sim_crypt_md_free (md);
      if (! len)
        system_exe_hash = sim_contact_convert_to_address (hash, 'F');
      string_free (hash);
    }
    string_free (exe);
  } else
    *error = SIM_OK;
#ifndef __unix__
  return firewall_init_ (install, error);
#else
  return SIM_OK;
#endif
}

void system_uninit (void) {
  type_free (system_exe_hash), system_exe_hash = nil ();
  string_free (system_version_string), system_version_string = nil ();
}

void _system_log_stack (const char *module, int level, simunsigned created, const char *function) {
#if ! HAVE_LIBPTH
  if (module) {
    simbool nl = false;
    int i, ret = 0, pipes[2];
    simbyte *stackptr = NULL;
    size_t stacklen, stacksize = sim_system_stack_size (&stackptr);
#if defined(_WIN32) || defined(RUSAGE_THREAD) || defined(CLOCK_THREAD_CPUTIME_ID)
    if (created) {
      simnumber cputime = sim_system_cpu_get (SYSTEM_CPU_TIME_THREAD, NULL), realtime = system_get_tick () - created;
      log_any_ (module, level, "%s runtime: %lld.%03lld/%lld.%03lld seconds (%lld.%03lld%% CPU)", function,
                cputime / 1000000000, cputime / 1000000 % 1000, realtime / 1000, realtime % 1000,
                realtime ? (cputime / realtime) / 10000 : 0, realtime ? (cputime / realtime) % 10000 / 10 : 0);
      function = ",";
      nl = true;
    }
#endif
    if (! SYSTEM_STACK_INIT (pipes)) {
      for (stacklen = stacksize; (int) stacklen > 0; stackptr += SIM_STACK_PAGE_SIZE) {
        if ((simbyte) (ret = sim_system_stack_read (pipes, stackptr))) {
          if (ret >= 0) {
          done:
            log_any_ (module, level, "%s stack: %ld/%ld bytes", function,
                      (long) stacklen + SIM_STACK_PAGE_SIZE, (long) stacksize);
            nl = true;
          }
          break;
        }
        if (! ret) {
          for (i = 0; i < SIM_STACK_PAGE_SIZE; i++) {
            if (stackptr[i])
              goto done;
            stacklen--;
          }
        } else
          stacklen -= SIM_STACK_PAGE_SIZE;
      }
      SYSTEM_STACK_UNINIT (pipes);
    }
    if (nl)
      log_any_ (module, level, "\n");
  }
#else /* HAVE_LIBPTH */
  if (created && module) {
    simnumber realtime = system_get_tick () - created;
    log_any_ (module, level, "%s runtime: %lld.%03lld seconds\n", function, realtime / 1000, realtime % 1000);
  }
#endif
}
