/* This file contains library-wide symbols that are always needed when one
 * links to the library.
 */
/*
Copyright (C) 2004-2017,2018 John E. Davis

This file is part of the S-Lang Library.

The S-Lang Library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.

The S-Lang Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
USA.
*/
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include "slinclud.h"

#include <errno.h>
#include "slang.h"
#include "_slang.h"

#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif

#ifdef HAVE_LANGINFO_H
# include <langinfo.h>
#endif

#if defined(__WIN32__)
# include <windows.h>
#endif

#define SLSYSWRAP_REALLOC realloc
#define SLSYSWRAP_MALLOC malloc
#ifdef SLSYSWRAP
# include <slsyswrap.h>
#endif

int SLang_Version = SLANG_VERSION;
SLFUTURE_CONST char *SLang_Version_String = SLANG_VERSION_STRING;

int _pSLinterp_UTF8_Mode = 0;
int _pSLtt_UTF8_Mode = 0;
int _pSLutf8_mode = 0;

#ifndef HAVE_LOCALE_H
# define setlocale(x,y) (NULL)
# define LC_ALL 0
#endif

int SLutf8_is_utf8_mode (void)
{
   return _pSLutf8_mode;
}

int SLinterp_utf8_enable (int mode)
{
   if (mode == -1)
     mode = _pSLutf8_mode;

   return _pSLinterp_UTF8_Mode = mode;
}

int SLinterp_is_utf8_mode (void)
{
   return _pSLinterp_UTF8_Mode;
}

static int utf8_enable (int mode)
{
   char *locale;

   if (mode != -1)
     return (mode != 0);

#if defined(__WIN32__)
   if (GetConsoleOutputCP () == 65001)
     return 1;
#endif

   (void) setlocale (LC_ALL, "");

#ifdef HAVE_NL_LANGINFO_CODESET
   locale = nl_langinfo (CODESET);
# ifdef SLSYSWRAP
     {
	char *env;
	if ((NULL != (env = getenv ("SLTEST_NO_LANGINFO")))
	    && (atoi (env) != 0))
	  locale = NULL;		       /* skip this block of code */
     }
# endif
   if ((locale != NULL) && (*locale))
     {
	if ((0 == strcmp (locale, "UTF-8"))
	    || (0 == strcmp (locale, "utf-8"))
	    || (0 == strcmp (locale, "utf8"))
	    || (0 == strcmp (locale, "UTF8")))
	  return 1;

	return 0;
     }
#endif

   locale = setlocale (LC_ALL, "");

   if (((locale == NULL) || (*locale == 0))
       && ((NULL == (locale = getenv ("LC_ALL"))) || (*locale == 0))
       && ((NULL == (locale = getenv ("LC_CTYPE"))) || (*locale == 0))
       && ((NULL == (locale = getenv ("LANG"))) || (*locale == 0)))
     return 0;

   /* setlocale man page says the return value is something like:
    *   language[_territory][.codeset][@modifier][+special][,...
    * Here, we want the codeset, if present.
    */

   while (*locale && (*locale != '.') && (*locale != '@')
	  && (*locale != '+') && (*locale != ','))
     locale++;

   if (*locale == '.')
     {
	locale++;
	if (0 == strncmp (locale, "UTF-8", 5))
	  locale += 5;
	else if (0 == strncmp (locale, "utf8", 4))
	  locale += 4;
	else
	  return 0;

	if ((*locale == 0) || (*locale == '@')
	    || (*locale == '+') || (*locale == ','))
	  return 1;
     }

   return 0;
}

/* Returns the value of _pSLutf8_mode */
int SLutf8_enable (int mode)
{
   char *cjk;

   mode = utf8_enable (mode);
   _pSLutf8_mode = mode;
   _pSLtt_UTF8_Mode = mode;
   _pSLinterp_UTF8_Mode = mode;
   if (mode && (NULL != (cjk = getenv ("WCWIDTH_CJK_LEGACY"))))
     {
	if ((*cjk == 0) || (0==strcmp(cjk,"yes")))
	  (void) SLwchar_set_wcwidth_flags (SLWCWIDTH_CJK_LEGACY);
     }
   return mode;
}

/* Mallocing 0 bytes leads to undefined behavior.  Some C libraries will
 * return NULL, and others return non-NULL.  Here, if 0 bytes is requested,
 * and the library malloc function returns NULL, then malloc will be retried
 * using 1 byte.
 */
SLFUTURE_VOID *SLmalloc (SLstrlen_Type len)
{
   SLFUTURE_VOID *p;

   if (NULL != (p = (SLFUTURE_VOID *)SLSYSWRAP_MALLOC(len)))
     return p;

   if (len || (NULL == (p = (SLFUTURE_VOID *)SLSYSWRAP_MALLOC(1))))
     SLang_set_error (SL_MALLOC_ERROR);

   return p;
}

void SLfree (SLFUTURE_VOID *p)
{
   if (p != NULL) free (p);
}

SLFUTURE_VOID *SLrealloc (SLFUTURE_VOID *p, SLstrlen_Type len)
{
   if (len == 0)
     {
	SLfree (p);
	return NULL;
     }

   if (p == NULL) p = SLmalloc (len);
   else
     {
	p = (SLFUTURE_VOID *) SLSYSWRAP_REALLOC(p, len);
	if (p == NULL)
	  SLang_set_error (SL_MALLOC_ERROR);
     }
   return p;
}

SLFUTURE_VOID *_SLrecalloc (SLFUTURE_VOID *p, SLstrlen_Type nelems, SLstrlen_Type len)
{
   unsigned int nlen = nelems * len;

   if (nelems && (nlen/nelems != len))
     {
	SLang_set_error (SL_Malloc_Error);
	return NULL;
     }
   return SLrealloc (p, nlen);
}

SLFUTURE_VOID *_SLcalloc (SLstrlen_Type nelems, SLstrlen_Type len)
{
   SLstrlen_Type nlen = nelems * len;

   if (nelems && (nlen/nelems != len))
     {
	SLang_set_error (SL_Malloc_Error);
	return NULL;
     }
   return SLmalloc (nlen);
}

SLFUTURE_VOID *SLcalloc (SLstrlen_Type nelems, SLstrlen_Type len)
{
   SLFUTURE_VOID *p = _SLcalloc (nelems, len);
   if (p != NULL) memset (p, 0, len*nelems);
   return p;
}

int _pSLsecure_issetugid (void)
{
#ifdef HAVE_ISSETUGID
   return (1 == issetugid ());
#else
# if defined(HAVE_GETUID) && defined(HAVE_GETEUID) && defined(HAVE_GETGID) && defined(HAVE_GETEUID)
   static int enable_secure;
   if (enable_secure == 0)
     {
	if ((getuid () != geteuid ())
	    || (getgid () != getegid ()))
	  enable_secure = 1;
	else
	  enable_secure = -1;
     }
   return (enable_secure == 1);
# else
   return 0;
# endif
#endif
}

/* Like getenv, except if running as setuid or setgid, returns NULL */
char *_pSLsecure_getenv (SLCONST char *s)
{
   if (_pSLsecure_issetugid ())
     return NULL;
   return getenv (s);
}

typedef struct Interrupt_Hook_Type
{
   int (*func)(VOID_STAR);
   VOID_STAR client_data;
   struct Interrupt_Hook_Type *next;
}
Interrupt_Hook_Type;

static Interrupt_Hook_Type *Interrupt_Hooks = NULL;

static Interrupt_Hook_Type *
  find_interrupt_hook (int (*func)(VOID_STAR), VOID_STAR cd,
		       Interrupt_Hook_Type **prevp)
{
   Interrupt_Hook_Type *h = Interrupt_Hooks;
   Interrupt_Hook_Type *prev = NULL;
   while (h != NULL)
     {
	if ((h->func == func) && (h->client_data == cd))
	  {
	     if (prevp != NULL)
	       *prevp = prev;
	     return h;
	  }
	prev = h;
	h = h->next;
     }
   return NULL;
}

int SLang_add_interrupt_hook (int (*func)(VOID_STAR), VOID_STAR cd)
{
   Interrupt_Hook_Type *h;

   if (NULL != find_interrupt_hook (func, cd, NULL))
     return 0;

   if (NULL == (h = (Interrupt_Hook_Type *)SLmalloc (sizeof (Interrupt_Hook_Type))))
     return -1;

   h->func = func;
   h->client_data = cd;
   h->next = Interrupt_Hooks;
   Interrupt_Hooks = h;
   return 0;
}

void SLang_remove_interrupt_hook (int (*func)(VOID_STAR), VOID_STAR cd)
{
   Interrupt_Hook_Type *h, *hprev;

   if (NULL == (h = find_interrupt_hook (func, cd, &hprev)))
     return;

   if (hprev == NULL)
     Interrupt_Hooks = h->next;
   else
     hprev->next = h->next;

   SLfree ((char *) h);
}

int SLang_handle_interrupt (void)
{
   Interrupt_Hook_Type *h;
   int status = 0;
   int e = errno;
   int se = _pSLerrno_errno;

   h = Interrupt_Hooks;
   while (h != NULL)
     {
	if (-1 == (*h->func)(h->client_data))
	  status = -1;

	h = h->next;
     }
   errno = e;
   _pSLerrno_errno = se;
   return status;
}

int _pSLerrno_errno;

int SLerrno_set_errno (int sys_errno)
{
   _pSLerrno_errno = sys_errno;
   return 0;
}

#if defined(__WIN32__) && defined(SLANG_DLL)
# include <windows.h>

/* where is the prototype for DllMain? */
BOOL WINAPI DllMain(HANDLE hInstance,DWORD dwReason,LPVOID lpParam);

BOOL WINAPI DllMain(HANDLE hInstance,DWORD dwReason,LPVOID lpParam)
{
   return 1;
}
#endif
