text: // SoftEther VPN Source Code - Stable Edition Repository
  // Mayaqua Kernel
  // 
  // SoftEther VPN Server, Client and Bridge are free software under the Apache License, Version 2.0.
  // 
  // Copyright (c) Daiyuu Nobori.
  // Copyright (c) SoftEther VPN Project, University of Tsukuba, Japan.
  // Copyright (c) SoftEther Corporation.
  // Copyright (c) all contributors on SoftEther VPN project in GitHub.
  // 
  // All Rights Reserved.
  // 
  // http://www.softether.org/
  // 
  // This stable branch is officially managed by Daiyuu Nobori, the owner of SoftEther VPN Project.
  // Pull requests should be sent to the Developer Edition Master Repository on https://github.com/SoftEtherVPN/SoftEtherVPN
  // Contributors:
  // - nattoheaven (https://github.com/nattoheaven)
  // 
  // License: The Apache License, Version 2.0
  // https://www.apache.org/licenses/LICENSE-2.0
  // 
  // DISCLAIMER
  // ==========
  // 
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  // SOFTWARE.
  // 
  // THIS SOFTWARE IS DEVELOPED IN JAPAN, AND DISTRIBUTED FROM JAPAN, UNDER
  // JAPANESE LAWS. YOU MUST AGREE IN ADVANCE TO USE, COPY, MODIFY, MERGE, PUBLISH,
  // DISTRIBUTE, SUBLICENSE, AND/OR SELL COPIES OF THIS SOFTWARE, THAT ANY
  // JURIDICAL DISPUTES WHICH ARE CONCERNED TO THIS SOFTWARE OR ITS CONTENTS,
  // AGAINST US (SOFTETHER PROJECT, SOFTETHER CORPORATION, DAIYUU NOBORI OR OTHER
  // SUPPLIERS), OR ANY JURIDICAL DISPUTES AGAINST US WHICH ARE CAUSED BY ANY KIND
  // OF USING, COPYING, MODIFYING, MERGING, PUBLISHING, DISTRIBUTING, SUBLICENSING,
  // AND/OR SELLING COPIES OF THIS SOFTWARE SHALL BE REGARDED AS BE CONSTRUED AND
  // CONTROLLED BY JAPANESE LAWS, AND YOU MUST FURTHER CONSENT TO EXCLUSIVE
  // JURISDICTION AND VENUE IN THE COURTS SITTING IN TOKYO, JAPAN. YOU MUST WAIVE
  // ALL DEFENSES OF LACK OF PERSONAL JURISDICTION AND FORUM NON CONVENIENS.
  // PROCESS MAY BE SERVED ON EITHER PARTY IN THE MANNER AUTHORIZED BY APPLICABLE
  // LAW OR COURT RULE.
  // 
  // USE ONLY IN JAPAN. DO NOT USE THIS SOFTWARE IN ANOTHER COUNTRY UNLESS YOU HAVE
  // A CONFIRMATION THAT THIS SOFTWARE DOES NOT VIOLATE ANY CRIMINAL LAWS OR CIVIL
  // RIGHTS IN THAT PARTICULAR COUNTRY. USING THIS SOFTWARE IN OTHER COUNTRIES IS
  // COMPLETELY AT YOUR OWN RISK. THE SOFTETHER VPN PROJECT HAS DEVELOPED AND
  // DISTRIBUTED THIS SOFTWARE TO COMPLY ONLY WITH THE JAPANESE LAWS AND EXISTING
  // CIVIL RIGHTS INCLUDING PATENTS WHICH ARE SUBJECTS APPLY IN JAPAN. OTHER
  // COUNTRIES' LAWS OR CIVIL RIGHTS ARE NONE OF OUR CONCERNS NOR RESPONSIBILITIES.
  // WE HAVE NEVER INVESTIGATED ANY CRIMINAL REGULATIONS, CIVIL LAWS OR
  // INTELLECTUAL PROPERTY RIGHTS INCLUDING PATENTS IN ANY OF OTHER 200+ COUNTRIES
  // AND TERRITORIES. BY NATURE, THERE ARE 200+ REGIONS IN THE WORLD, WITH
  // DIFFERENT LAWS. IT IS IMPOSSIBLE TO VERIFY EVERY COUNTRIES' LAWS, REGULATIONS
  // AND CIVIL RIGHTS TO MAKE THE SOFTWARE COMPLY WITH ALL COUNTRIES' LAWS BY THE
  // PROJECT. EVEN IF YOU WILL BE SUED BY A PRIVATE ENTITY OR BE DAMAGED BY A
  // PUBLIC SERVANT IN YOUR COUNTRY, THE DEVELOPERS OF THIS SOFTWARE WILL NEVER BE
  // LIABLE TO RECOVER OR COMPENSATE SUCH DAMAGES, CRIMINAL OR CIVIL
  // RESPONSIBILITIES. NOTE THAT THIS LINE IS NOT LICENSE RESTRICTION BUT JUST A
  // STATEMENT FOR WARNING AND DISCLAIMER.
  // 
  // READ AND UNDERSTAND THE 'WARNING.TXT' FILE BEFORE USING THIS SOFTWARE.
  // SOME SOFTWARE PROGRAMS FROM THIRD PARTIES ARE INCLUDED ON THIS SOFTWARE WITH
  // LICENSE CONDITIONS WHICH ARE DESCRIBED ON THE 'THIRD_PARTY.TXT' FILE.
  // 
  // 
  // SOURCE CODE CONTRIBUTION
  // ------------------------
  // 
  // Your contribution to SoftEther VPN Project is much appreciated.
  // Please send patches to us through GitHub.
  // Read the SoftEther VPN Patch Acceptance Policy in advance:
  // http://www.softether.org/5-download/src/9.patch
  // 
  // 
  // DEAR SECURITY EXPERTS
  // ---------------------
  // 
  // If you find a bug or a security vulnerability please kindly inform us
  // about the problem immediately so that we can fix the security problem
  // to protect a lot of users around the world as soon as possible.
  // 
  // Our e-mail address for security reports is:
  // softether-vpn-security [at] softether.org
  // 
  // Please note that the above e-mail address is not a technical support
  // inquiry address. If you need technical assistance, please visit
  // http://www.softether.org/ and ask your question on the users forum.
  // 
  // Thank you for your cooperation.
  // 
  // 
  // NO MEMORY OR RESOURCE LEAKS
  // ---------------------------
  // 
  // The memory-leaks and resource-leaks verification under the stress
  // test has been passed before release this source code.
  
  
  // Kernel.c
  // System service processing routine
  
  #include <GlobalConst.h>
  
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <wchar.h>
  #include <stdarg.h>
  #include <time.h>
  #include <errno.h>
  #include <Mayaqua/Mayaqua.h>
  
  #ifndef TM_YEAR_MAX
  #define TM_YEAR_MAX         2106
  #endif
  #ifndef TM_MON_MAX
  #define TM_MON_MAX          1
  #endif
  #ifndef TM_MDAY_MAX
  #define TM_MDAY_MAX         7
  #endif
  #ifndef TM_HOUR_MAX
  #define TM_HOUR_MAX         6
  #endif
  #ifndef TM_MIN_MAX
  #define TM_MIN_MAX          28
  #endif
  #ifndef TM_SEC_MAX
  #define TM_SEC_MAX          14
  #endif
  
  #define ADJUST_TM(tm_member, tm_carry, modulus) \
  	if ((tm_member) < 0){ \
  	tm_carry -= (1 - ((tm_member)+1) / (modulus)); \
  	tm_member = (modulus-1) + (((tm_member)+1) % (modulus)); \
  	} else if ((tm_member) >= (modulus)) { \
  	tm_carry += (tm_member) / (modulus); \
  	tm_member = (tm_member) % (modulus); \
  	}
  #define leap(y) (((y) % 4 == 0 && (y) % 100 != 0) || (y) % 400 == 0)
  #define nleap(y) (((y) - 1969) / 4 - ((y) - 1901) / 100 + ((y) - 1601) / 400)
  #define leapday(m, y) ((m) == 1 && leap (y))
  #define monthlen(m, y) (ydays[(m)+1] - ydays[m] + leapday (m, y))
  static int ydays[] =
  {
  	0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
  };
  
  static UINT current_num_thread = 0;
  static UINT cached_number_of_cpus = 0;
  
  
  
  static wchar_t *default_locale_str =
  L"- - $ : : $ Sun Mon Tue Wed Thu Fri Sat : : : $ (None)";
  
  
  static LOCALE current_locale;
  LOCK *tick_manual_lock = NULL;
  UINT g_zero = 0;
  
  #define MONSPERYEAR 12
  #define DAYSPERNYEAR 365
  #define DAYSPERLYEAR 366
  #define SECSPERMIN 60
  #define SECSPERHOUR (60*60)
  #define SECSPERDAY (24*60*60)
  #define DAYSPERWEEK 7
  #define TM_SUNDAY	0
  #define TM_MONDAY	1
  #define TM_TUESDAY	2
  #define TM_WEDNESDAY	3
  #define TM_THURSDAY	4
  #define TM_FRIDAY	5
  #define TM_SATURDAY	6
  
  #define TM_YEAR_BASE	1900
  
  #define EPOCH_YEAR	1970
  #define EPOCH_WDAY	TM_THURSDAY
  
  #define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
  
  static const int	mon_lengths[2][MONSPERYEAR] = {
  	{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
  	{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
  };
  
  static const int	year_lengths[2] = {
  	DAYSPERNYEAR, DAYSPERLYEAR
  };
  
  
  /*
   * Taken from FreeBSD src / lib / libc / stdtime / localtime.c 1.43 revision.
   * localtime.c 7.78.
   * tzfile.h 1.8
   * adapted to be replacement gmtime_r.
   */
  static void
  c_timesub(timep, offset, tmp)
  const time_64t * const			timep;
  const long				offset;
  struct tm * const		tmp;
  {
  	INT64			days;
  	INT64			rem;
  	INT64			y;
  	int			yleap;
  	const int *		ip;
  
  	days = *timep / SECSPERDAY;
  	rem = *timep % SECSPERDAY;
  	rem += (offset);
  	while (rem < 0) {
  		rem += SECSPERDAY;
  		--days;
  	}
  	while (rem >= SECSPERDAY) {
  		rem -= SECSPERDAY;
  		++days;
  	}
  	tmp->tm_hour = (int) (rem / SECSPERHOUR);
  	rem = rem % SECSPERHOUR;
  	tmp->tm_min = (int) (rem / SECSPERMIN);
  	/*
  	** A positive leap second requires a special
  	** representation.  This uses "... ??:59:60" et seq.
  	*/
  	tmp->tm_sec = (int) (rem % SECSPERMIN) ;
  	tmp->tm_wday = (int) ((EPOCH_WDAY + days) % DAYSPERWEEK);
  	if (tmp->tm_wday < 0)
  		tmp->tm_wday += DAYSPERWEEK;
  	y = EPOCH_YEAR;
  #define LEAPS_THRU_END_OF(y)	((y) / 4 - (y) / 100 + (y) / 400)
  	while (days < 0 || days >= (long) year_lengths[yleap = isleap(y)]) {
  		INT64	newy;
  
  		newy = y + days / DAYSPERNYEAR;
  		if (days < 0)
  			--newy;
  		days -= (newy - y) * DAYSPERNYEAR +
  			LEAPS_THRU_END_OF(newy - 1) -
  			LEAPS_THRU_END_OF(y - 1);
  		y = newy;
  	}
  	tmp->tm_year = (int)(y - TM_YEAR_BASE);
  	tmp->tm_yday = (int) days;
  	ip = mon_lengths[yleap];
  	for (tmp->tm_mon = 0; days >= (INT64) ip[tmp->tm_mon]; ++(tmp->tm_mon))
  		days = days - (INT64) ip[tmp->tm_mon];
  	tmp->tm_mday = (int) (days + 1);
  	tmp->tm_isdst = 0;
  }
  
  /*
  * Re-entrant version of gmtime.
  */
  struct tm * c_gmtime_r(const time_64t* timep, struct tm *tm)
  {
  	c_timesub(timep, 0L, tm);
  	return tm;
  }
  
  // Get the real-time system timer
  UINT TickRealtime()
  {
  #if	defined(OS_WIN32) || defined(CLOCK_REALTIME) || defined(CLOCK_MONOTONIC) || defined(CLOCK_HIGHRES) || defined(UNIX_MACOS)
  	return Tick() + 1;
  #else
  	return TickRealtimeManual() + 1;
  #endif
  }
  
  #ifndef	OS_WIN32
  
  static UINT64 last_manual_tick = 0;
  static UINT64 manual_tick_add_value = 0;
  
  // For systems which not have clock_gettime (such as MacOS X)
  UINT TickRealtimeManual()
  {
  	UINT64 ret;
  	Lock(tick_manual_lock);
  	{
  		ret = TickGetRealtimeTickValue64();
  
  		if (last_manual_tick != 0 && (last_manual_tick > ret))
  		{
  			manual_tick_add_value += (last_manual_tick - ret);
  		}
  
  		last_manual_tick = ret;
  	}
  	Unlock(tick_manual_lock);
  
  	return (UINT)(ret + manual_tick_add_value);
  }
  
  // Returns a appropriate value from the current time
  UINT64 TickGetRealtimeTickValue64()
  {
  	struct timeval tv;
  	struct timezone tz;
  	UINT64 ret;
  
  	memset(&tv, 0, sizeof(tv));
  	memset(&tz, 0, sizeof(tz));
  
  	gettimeofday(&tv, &tz);
  
  	if (sizeof(tv.tv_sec) != 4)
  	{
  		ret = (UINT64)tv.tv_sec * 1000ULL + (UINT64)tv.tv_usec / 1000ULL;
  	}
  	else
  	{
  		ret = (UINT64)((UINT64)((UINT32)tv.tv_sec)) * 1000ULL + (UINT64)tv.tv_usec / 1000ULL;
  	}
  
  	return ret;
  }
  
  #endif	// OS_WIN32
  
  // Get the number of CPUs
  UINT GetNumberOfCpu()
  {
  	UINT ret = 0;
  
  	if (cached_number_of_cpus == 0)
  	{
  		UINT i = 0;
  
  #ifdef	OS_WIN32
  		i = Win32GetNumberOfCpuInner();
  #else	// OS_WIN32
  		i = UnixGetNumberOfCpuInner();
  #endif	// OS_WIN32
  
  		if (i == 0)
  		{
  			i = 8;
  		}
  
  		cached_number_of_cpus = i;
  	}
  
  	ret = cached_number_of_cpus;
  
  	if (ret == 0)
  	{
  		ret = 1;
  	}
  	if (ret > 128)
  	{
  		ret = 128;
  	}
  
  	return ret;
  }
  
  // Creating a thread list
  LIST *NewThreadList()
  {
  	LIST *o = NewList(NULL);
  
  	return o;
  }
  
  // Remove the thread from the thread list
  void DelThreadFromThreadList(LIST *o, THREAD *t)
  {
  	// Validate arguments
  	if (o == NULL || t == NULL)
  	{
  		return;
  	}
  
  	LockList(o);
  	{
  		if (Delete(o, t))
  		{
  			ReleaseThread(t);
  		}
  	}
  	UnlockList(o);
  }
  
  // Add the thread to the thread list
  void AddThreadToThreadList(LIST *o, THREAD *t)
  {
  	// Validate arguments
  	if (o == NULL || t == NULL)
  	{
  		return;
  	}
  
  	LockList(o);
  	{
  		if (IsInList(o, t) == false)
  		{
  			AddRef(t->ref);
  
  			Add(o, t);
  		}
  	}
  	UnlockList(o);
  }
  
  // Maintain thread list
  void MainteThreadList(LIST *o)
  {
  	UINT i;
  	LIST *delete_list = NULL;
  	// Validate arguments
  	if (o == NULL)
  	{
  		return;
  	}
  
  	LockList(o);
  	{
  		for (i = 0;i < LIST_NUM(o);i++)
  		{
  			THREAD *t = LIST_DATA(o, i);
  
  			if (t->Stopped)
  			{
  				if (delete_list == NULL)
  				{
  					delete_list = NewListFast(NULL);
  				}
  
  				Add(delete_list, t);
  			}
  		}
  
  		if (delete_list != NULL)
  		{
  			for (i = 0;i < LIST_NUM(delete_list);i++)
  			{
  				THREAD *t = LIST_DATA(delete_list, i);
  
  				ReleaseThread(t);
  
  				Delete(o, t);
  			}
  
  			ReleaseList(delete_list);
  		}
  	}
  	UnlockList(o);
  }
  
  // Wait until all threads in the thread list will be stopped
  void WaitAllThreadsWillBeStopped(LIST *o)
  {
  	// Validate arguments
  	if (o == NULL)
  	{
  		return;
  	}
  
  	while (LIST_NUM(o) != 0)
  	{
  		SleepThread(100);
  	}
  }
  
  // Stop all the threads in the thread list
  void StopThreadList(LIST *o)
  {
  	UINT i;
  	// Validate arguments
  	if (o == NULL)
  	{
  		return;
  	}
  
  	LockList(o);
  	{
  		for (i = 0;i < LIST_NUM(o);i++)
  		{
  			THREAD *t = LIST_DATA(o, i);
  
  			WaitThread(t, INFINITE);
  		}
  	}
  	UnlockList(o);
  }
  
  // Release the thread list
  void FreeThreadList(LIST *o)
  {
  	UINT i;
  	// Validate arguments
  	if (o == NULL)
  	{
  		return;
  	}
  
  	LockList(o);
  	{
  		for (i = 0;i < LIST_NUM(o);i++)
  		{
  			THREAD *t = LIST_DATA(o, i);
  
  			WaitThread(t, INFINITE);
  
  			ReleaseThread(t);
  		}
  
  		DeleteAll(o);
  	}
  	UnlockList(o);
  
  	ReleaseList(o);
  }
  
  // Get the home directory
  void GetHomeDirW(wchar_t *path, UINT size)
  {
  	// Validate arguments
  	if (path == NULL)
  	{
  		return;
  	}
  
  	if (GetEnvW(L"HOME", path, size) == false)
  	{
  		wchar_t drive[MAX_SIZE];
  		wchar_t hpath[MAX_SIZE];
  		if (GetEnvW(L"HOMEDRIVE", drive, sizeof(drive)) &&
  			GetEnvW(L"HOMEPATH", hpath, sizeof(hpath)))
  		{
  			UniFormat(path, size, L"%s%s", drive, hpath);
  		}
  		else
  		{
  #ifdef	OS_WIN32
  			Win32GetCurrentDirW(path, size);
  #else	// OS_WIN32
  			UnixGetCurrentDirW(path, size);
  #endif	// OS_WIN32
  		}
  	}
  }
  void GetHomeDir(char *path, UINT size)
  {
  	// Validate arguments
  	if (path == NULL)
  	{
  		return;
  	}
  
  	if (GetEnv("HOME", path, size) == false)
  	{
  		char drive[MAX_SIZE];
  		char hpath[MAX_SIZE];
  		if (GetEnv("HOMEDRIVE", drive, sizeof(drive)) &&
  			GetEnv("HOMEPATH", hpath, sizeof(hpath)))
  		{
  			Format(path, size, "%s%s", drive, hpath);
  		}
  		else
  		{
  #ifdef	OS_WIN32
  			Win32GetCurrentDir(path, size);
  #else	// OS_WIN32
  			UnixGetCurrentDir(path, size);
  #endif	// OS_WIN32
  		}
  	}
  }
  
  // Get the environment variable string
  bool GetEnv(char *name, char *data, UINT size)
  {
  	char *ret;
  	// Validate arguments
  	if (name == NULL || data == NULL)
  	{
  		return false;
  	}
  
  	StrCpy(data, size, "");
  
  	ret = getenv(name);
  	if (ret == NULL)
  	{
  		return false;
  	}
  
  	StrCpy(data, size, ret);
  
  	return true;
  }
  bool GetEnvW(wchar_t *name, wchar_t *data, UINT size)
  {
  #ifdef	OS_WIN32
  	return GetEnvW_ForWin32(name, data, size);
  #else	// OS_WIN32
  	return GetEnvW_ForUnix(name, data, size);
  #endif	// OS_WIN32
  }
  
  #ifdef	OS_WIN32
  bool GetEnvW_ForWin32(wchar_t *name, wchar_t *data, UINT size)
  {
  	wchar_t *ret;
  	// Validate arguments
  	if (name == NULL || data == NULL)
  	{
  		return false;
  	}
  
  	if (IsNt() == false)
  	{
  		bool ret;
  		char *name_a = CopyUniToStr(name);
  		char data_a[MAX_SIZE];
  
  		ret = GetEnv(name_a, data_a, sizeof(data_a));
  
  		if (ret)
  		{
  			StrToUni(data, size, data_a);
  		}
  
  		Free(name_a);
  
  		return ret;
  	}
  
  	UniStrCpy(data, size, L"");
  
  	ret = _wgetenv(name);
  	if (ret == NULL)
  	{
  		return false;
  	}
  
  	UniStrCpy(data, size, ret);
  
  	return true;
  }
  
  #endif	// OS_WIN32
  
  #ifdef	OS_UNIX
  
  bool GetEnvW_ForUnix(wchar_t *name, wchar_t *data, UINT size)
  {
  	char *name_a;
  	bool ret;
  	char data_a[MAX_SIZE];
  	// Validate arguments
  	if (name == NULL || data == NULL)
  	{
  		return false;
  	}
  
  	name_a = CopyUniToUtf(name);
  
  	ret = GetEnv(name_a, data_a, sizeof(data_a));
  
  	if (ret)
  	{
  		UtfToUni(data, size, data_a);
  	}
  
  	Free(name_a);
  
  	return ret;
  }
  
  #endif	// OS_UNIX
  
  // Get the memory information
  void GetMemInfo(MEMINFO *info)
  {
  	OSGetMemInfo(info);
  }
  
  // Start the single-instance
  INSTANCE *NewSingleInstance(char *instance_name)
  {
  	return NewSingleInstanceEx(instance_name, false);
  }
  INSTANCE *NewSingleInstanceEx(char *instance_name, bool user_local)
  {
  	char name[MAX_SIZE];
  	INSTANCE *ret;
  	void *data;
  
  	if (instance_name != NULL)
  	{
  		if (user_local == false)
  		{
  			HashInstanceName(name, sizeof(name), instance_name);
  		}
  		else
  		{
  			HashInstanceNameLocal(name, sizeof(name), instance_name);
  		}
  
  		data = OSNewSingleInstance(name);
  	}
  	else
  	{
  		data = OSNewSingleInstance(NULL);
  	}
  
  	if (data == NULL)
  	{
  		return NULL;
  	}
  
  	ret = ZeroMalloc(sizeof(INSTANCE));
  	if (instance_name != NULL)
  	{
  		ret->Name = CopyStr(instance_name);
  	}
  
  	ret->pData = data;
  
  	return ret;
  }
  
  // Release of single instance
  void FreeSingleInstance(INSTANCE *inst)
  {
  	// Validate arguments
  	if (inst == NULL)
  	{
  		return;
  	}
  
  	OSFreeSingleInstance(inst->pData);
  
  	if (inst->Name != NULL)
  	{
  		Free(inst->Name);
  	}
  	Free(inst);
  }
  
  // Hashing the instance name
  void HashInstanceName(char *name, UINT size, char *instance_name)
  {
  	char tmp[MAX_SIZE];
  	UCHAR hash[SHA1_SIZE];
  	char key[11];
  	// Validate arguments
  	if (name == NULL || instance_name == NULL)
  	{
  		return;
  	}
  
  	StrCpy(tmp, sizeof(tmp), instance_name);
  	Trim(tmp);
  	StrUpper(tmp);
  
  	Hash(hash, tmp, StrLen(tmp), SHA1_SIZE);
  	BinToStr(key, sizeof(key), hash, 5);
  	key[10] = 0;
  
  	Format(name, size, "VPN-%s", key);
  
  	if (OS_IS_WINDOWS_NT(GetOsInfo()->OsType))
  	{
  		if (GET_KETA(GetOsInfo()->OsType, 100) >= 2 ||
  			GetOsInfo()->OsType == OSTYPE_WINDOWS_NT_4_TERMINAL_SERVER)
  		{
  			StrCpy(tmp, sizeof(tmp), name);
  			Format(name, size, "Global\\%s", tmp);
  		}
  	}
  }
  void HashInstanceNameLocal(char *name, UINT size, char *instance_name)
  {
  	char tmp[MAX_SIZE];
  	UCHAR hash[SHA1_SIZE];
  	char key[11];
  	// Validate arguments
  	if (name == NULL || instance_name == NULL)
  	{
  		return;
  	}
  
  	StrCpy(tmp, sizeof(tmp), instance_name);
  	Trim(tmp);
  	StrUpper(tmp);
  
  	Hash(hash, tmp, StrLen(tmp), SHA1_SIZE);
  	BinToStr(key, sizeof(key), hash, 5);
  	key[10] = 0;
  
  	Format(name, size, "VPN-%s", key);
  
  	if (OS_IS_WINDOWS_NT(GetOsInfo()->OsType))
  	{
  		if (GET_KETA(GetOsInfo()->OsType, 100) >= 2 ||
  			GetOsInfo()->OsType == OSTYPE_WINDOWS_NT_4_TERMINAL_SERVER)
  		{
  			StrCpy(tmp, sizeof(tmp), name);
  			Format(name, size, "Local\\%s", tmp);
  		}
  	}
  }
  
  // Run the process
  bool Run(char *filename, char *arg, bool hide, bool wait)
  {
  	// Validate arguments
  	if (filename == NULL)
  	{
  		return false;
  	}
  
  	return OSRun(filename, arg, hide, wait);
  }
  bool RunW(wchar_t *filename, wchar_t *arg, bool hide, bool wait)
  {
  	// Validate arguments
  	if (filename == NULL)
  	{
  		return false;
  	}
  
  	return OSRunW(filename, arg, hide, wait);
  }
  
  // Date and time related functions
  void GetDateTimeStr64Uni(wchar_t *str, UINT size, UINT64 sec64)
  {
  	char tmp[MAX_SIZE];
  	if (str == NULL)
  	{
  		return;
  	}
  
  	GetDateTimeStr64(tmp, sizeof(tmp), sec64);
  	StrToUni(str, size, tmp);
  }
  void GetDateTimeStr64(char *str, UINT size, UINT64 sec64)
  {
  	SYSTEMTIME st;
  	UINT64ToSystem(&st, sec64);
  	GetDateTimeStr(str, size, &st);
  }
  void GetDateTimeStrMilli64(char *str, UINT size, UINT64 sec64)
  {
  	SYSTEMTIME st;
  	UINT64ToSystem(&st, sec64);
  	GetDateTimeStrMilli(str, size, &st);
  }
  void GetDateTimeStrMilli64ForFileName(char *str, UINT size, UINT64 sec64)
  {
  	SYSTEMTIME st;
  	UINT64ToSystem(&st, sec64);
  	GetDateTimeStrMilliForFileName(str, size, &st);
  }
  void GetDateTimeStrMilliForFileName(char *str, UINT size, SYSTEMTIME *tm)
  {
  	Format(str, size, "%04u%02u%02u_%02u%02u%02u",
  		tm->wYear, tm->wMonth, tm->wDay, tm->wHour, tm->wMinute, tm->wSecond);
  }
  void GetDateStr64(char *str, UINT size, UINT64 sec64)
  {
  	SYSTEMTIME st;
  	if (sec64 == 0)
  	{
  		StrCpy(str, size, "(Unknown)");
  		return;
  	}
  	UINT64ToSystem(&st, sec64);
  	GetDateStr(str, size, &st);
  }
  void GetDateTimeStrEx64(wchar_t *str, UINT size, UINT64 sec64, LOCALE *locale)
  {
  	SYSTEMTIME st;
  	if (locale == NULL)
  	{
  		locale = &current_locale;
  	}
  	if (sec64 == 0 || SystemToLocal64(sec64) == 0 || LocalToSystem64(sec64) == 0)
  	{
  		UniStrCpy(str, size, locale->Unknown);
  		return;
  	}
  	UINT64ToSystem(&st, sec64);
  	GetDateTimeStrEx(str, size, &st, locale);
  }
  void GetTimeStrEx64(wchar_t *str, UINT size, UINT64 sec64, LOCALE *locale)
  {
  	SYSTEMTIME st;
  	if (locale == NULL)
  	{
  		locale = &current_locale;
  	}
  	if (sec64 == 0 || SystemToLocal64(sec64) == 0 || LocalToSystem64(sec64) == 0)
  	{
  		UniStrCpy(str, size, locale->Unknown);
  		return;
  	}
  	UINT64ToSystem(&st, sec64);
  	GetTimeStrEx(str, size, &st, locale);
  }
  void GetDateStrEx64(wchar_t *str, UINT size, UINT64 sec64, LOCALE *locale)
  {
  	SYSTEMTIME st;
  	if (locale == NULL)
  	{
  		locale = &current_locale;
  	}
  	if (sec64 == 0 || SystemToLocal64(sec64) == 0 || LocalToSystem64(sec64) == 0)
  	{
  		UniStrCpy(str, size, locale->Unknown);
  		return;
  	}
  	UINT64ToSystem(&st, sec64);
  	GetDateStrEx(str, size, &st, locale);
  }
  void GetTimeStrMilli64(char *str, UINT size, UINT64 sec64)
  {
  	SYSTEMTIME st;
  	if (sec64 == 0 || SystemToLocal64(sec64) == 0 || LocalToSystem64(sec64) == 0)
  	{
  		StrCpy(str, size, "(Unknown)");
  		return;
  	}
  	UINT64ToSystem(&st, sec64);
  	GetTimeStrMilli(str, size, &st);
  }
  void GetTimeStr64(char *str, UINT size, UINT64 sec64)
  {
  	SYSTEMTIME st;
  	if (sec64 == 0 || SystemToLocal64(sec64) == 0 || LocalToSystem64(sec64) == 0)
  	{
  		StrCpy(str, size, "(Unknown)");
  		return;
  	}
  	UINT64ToSystem(&st, sec64);
  	GetTimeStr(str, size, &st);
  }
  
  // Convert to a time to be used safely in the current POSIX implementation
  UINT64 SafeTime64(UINT64 sec64)
  {
  	return MAKESURE(sec64, 0, 4102243323123ULL);
  }
  
  // Thread pool
  static SK *thread_pool = NULL;
  static COUNTER *thread_count = NULL;
  
  // Initialization of thread pool
  void InitThreading()
  {
  	thread_pool = NewSk();
  	thread_count = NewCounter();
  }
  
  // Release of thread pool
  void FreeThreading()
  {
  	while (true)
  	{
  		if (Count(thread_count) == 0)
  		{
  			break;
  		}
  
  		SleepThread(25);
  	}
  
  	while (true)
  	{
  		THREAD_POOL_DATA *pd;
  		THREAD *t = Pop(thread_pool);
  
  		if (t == NULL)
  		{
  			break;
  		}
  
  		pd = (THREAD_POOL_DATA *)t->param;
  
  		pd->ThreadProc = NULL;
  		Set(pd->Event);
  
  		WaitThreadInternal(t);
  
  		pd = (THREAD_POOL_DATA *)t->param;
  		ReleaseEvent(pd->Event);
  		ReleaseEvent(pd->InitFinishEvent);
  
  		ReleaseThreadInternal(t);
  
  		Free(pd);
  	}
  
  	ReleaseSk(thread_pool);
  
  	DeleteCounter(thread_count);
  	thread_count = NULL;
  }
  
  // Thread pool procedure
  void ThreadPoolProc(THREAD *t, void *param)
  {
  	THREAD_POOL_DATA *pd;
  	// Validate arguments
  	if (t == NULL)
  	{
  		return;
  	}
  
  	pd = (THREAD_POOL_DATA *)param;
  
  	NoticeThreadInitInternal(t);
  
  	while (true)
  	{
  		THREAD *thread;
  		UINT i, num;
  		EVENT **ee;
  
  		// Wait for the next job
  		Wait(pd->Event, INFINITE);
  
  		if (pd->ThreadProc == NULL)
  		{
  			// Stop the pool thread
  			break;
  		}
  
  		thread = pd->Thread;
  		thread->ThreadId = ThreadId();
  
  		// Initialization is completed
  		Set(pd->InitFinishEvent);
  
  		// Set the thread name
  		if (thread->Name != NULL)
  		{
  			SetThreadName(thread->ThreadId, thread->Name, thread->param);
  		}
  		else
  		{
  			SetThreadName(thread->ThreadId, "Unknown", 0);
  		}
  
  		// Run the thread procedure
  		pd->ThreadProc(pd->Thread, thread->param);
  
  		// Set the thread name
  		SetThreadName(thread->ThreadId, NULL, 0);
  
  		pd->Thread->Stopped = true;
  
  		thread->PoolHalting = true;
  
  		// Set the waiting event list
  		LockList(thread->PoolWaitList);
  		{
  			num = LIST_NUM(thread->PoolWaitList);
  			ee = ToArray(thread->PoolWaitList);
  
  			DeleteAll(thread->PoolWaitList);
  		}
  		UnlockList(thread->PoolWaitList);
  
  		for (i = 0;i < num;i++)
  		{
  			EVENT *e = ee[i];
  
  			Set(e);
  			ReleaseEvent(e);
  		}
  
  		Free(ee);
  
  		while (true)
  		{
  			if (Count(thread->ref->c) <= 1)
  			{
  				break;
  			}
  
  			Wait(thread->release_event, 256);
  		}
  
  		ReleaseThread(thread);
  
  #ifdef	OS_WIN32
  		// For Win32: Recover the priority of the thread
  		MsRestoreThreadPriority();
  #endif	// OS_WIN32
  
  		// Register the thread itself to the thread pool
  		LockSk(thread_pool);
  		{
  			Push(thread_pool, t);
  		}
  		UnlockSk(thread_pool);
  
  		Dec(thread_count);
  	}
  }
  
  // Set the thread name
  void SetThreadName(UINT thread_id, char *name, void *param)
  {
  #ifdef	OS_WIN32
  	if (IsDebug())
  	{
  		char tmp[MAX_SIZE];
  
  		if (name == NULL)
  		{
  			strcpy(tmp, "idle");
  		}
  		else
  		{
  			sprintf(tmp, "%s (0x%x)", name, POINTER_TO_UINT32(param));
  		}
  
  		Win32SetThreadName(thread_id, tmp);
  	}
  #else	// OS_WIN32
  #ifdef	_DEBUG
  #ifdef	PR_SET_NAME
  	char tmp[MAX_SIZE];
  
  	if (name == NULL)
  	{
  		strcpy(tmp, "idle");
  	}
  	else
  	{
  		sprintf(tmp, "%s (%p)", name, param);
  	}
  
  	tmp[15] = 0;
  
  	prctl(PR_SET_NAME, (unsigned long)tmp, 0, 0, 0);
  #endif	// PR_SET_NAME
  #endif	// _DEBUG
  #endif	// OS_WIN32
  }
  
  // Do Nothing
  UINT DoNothing()
  {
  	return g_zero;
  }
  
  // Thread creation (pool)
  THREAD *NewThreadNamed(THREAD_PROC *thread_proc, void *param, char *name)
  {
  	THREAD *host = NULL;
  	THREAD_POOL_DATA *pd = NULL;
  	THREAD *ret;
  	bool new_thread = false;
  	// Validate arguments
  	if (thread_proc == NULL)
  	{
  		return NULL;
  	}
  
  	if (IsTrackingEnabled() == false)
  	{
  		DoNothing();
  	}
  
  	Inc(thread_count);
  
  	LockSk(thread_pool);
  	{
  		// Examine whether there is a thread that is currently vacant in the pool
  		host = Pop(thread_pool);
  	}
  	UnlockSk(thread_pool);
  
  	if (host == NULL)
  	{
  		// Create a new thread because a vacant thread is not found
  		pd = ZeroMalloc(sizeof(THREAD_POOL_DATA));
  		pd->Event = NewEvent();
  		pd->InitFinishEvent = NewEvent();
  		host = NewThreadInternal(ThreadPoolProc, pd);
  		WaitThreadInitInternal(host);
  
  		new_thread = true;
  	}
  	else
  	{
  		pd = (THREAD_POOL_DATA *)host->param;
  	}
  
  	// Creating a thread pool
  	ret = ZeroMalloc(sizeof(THREAD));
  	ret->ref = NewRef();
  	ret->thread_proc = thread_proc;
  	ret->param = param;
  	ret->pData = NULL;
  	ret->init_finished_event = NewEvent();
  	ret->PoolThread = true;
  	ret->PoolWaitList = NewList(NULL);
  	ret->PoolHostThread = host;
  	ret->release_event = NewEvent();
  
  	if (IsEmptyStr(name) == false)
  	{
  		ret->Name = CopyStr(name);
  	}
  
  	// Run
  	pd->ThreadProc = thread_proc;
  	pd->Thread = ret;
  	AddRef(ret->ref);
  
  	Set(pd->Event);
  
  	Wait(pd->InitFinishEvent, INFINITE);
  
  	current_num_thread++;
  
  //	Debug("current_num_thread = %u\n", current_num_thread);
  
  	return ret;
  }
  
  // Clean up of thread (pool)
  void CleanupThread(THREAD *t)
  {
  	// Validate arguments
  	if (t == NULL)
  	{
  		return;
  	}
  
  	ReleaseEvent(t->init_finished_event);
  	ReleaseEvent(t->release_event);
  	ReleaseList(t->PoolWaitList);
  
  	if (t->Name != NULL)
  	{
  		Free(t->Name);
  	}
  
  	Free(t);
  
  	current_num_thread--;
  	//Debug("current_num_thread = %u\n", current_num_thread);
  }
  
  // Release thread (pool)
  void ReleaseThread(THREAD *t)
  {
  	UINT ret;
  	EVENT *e;
  	// Validate arguments
  	if (t == NULL)
  	{
  		return;
  	}
  
  	e = t->release_event;
  	if (e != NULL)
  	{
  		AddRef(e->ref);
  	}
  
  	ret = Release(t->ref);
  	Set(e);
  
  	ReleaseEvent(e);
  
  	if (ret == 0)
  	{
  		CleanupThread(t);
  	}
  }
  
  // Notify the completion of the thread initialization (pool)
  void NoticeThreadInit(THREAD *t)
  {
  	// Validate arguments
  	if (t == NULL)
  	{
  		return;
  	}
  
  	// Notification
  	Set(t->init_finished_event);
  }
  
  // Wait the completion of the thread initialization (pool)
  void WaitThreadInit(THREAD *t)
  {
  	// Validate arguments
  	if (t == NULL)
  	{
  		return;
  	}
  
  	// KS
  	KS_INC(KS_WAITFORTHREAD_COUNT);
  
  	// Wait
  	Wait(t->init_finished_event, INFINITE);
  }
  
  // Wait for the termination of the thread (pool)
  bool WaitThread(THREAD *t, UINT timeout)
  {
  	bool ret = false;
  	EVENT *e = NULL;
  	// Validate arguments
  	if (t == NULL)
  	{
  		return false;
  	}
  
  	LockList(t->PoolWaitList);
  	{
  		if (t->PoolHalting)
  		{
  			// Has already been stopped
  			ret = true;
  		}
  		else
  		{
  			// Register the completion notifying event to the list
  			e = NewEvent();
  			AddRef(e->ref);
  			Insert(t->PoolWaitList, e);
  		}
  	}
  	UnlockList(t->PoolWaitList);
  
  	if (e != NULL)
  	{
  		// Wait Event
  		ret = Wait(e, timeout);
  
  		LockList(t->PoolWaitList);
  		{
  			if (Delete(t->PoolWaitList, e))
  			{
  				ReleaseEvent(e);
  			}
  		}
  		UnlockList(t->PoolWaitList);
  
  		ReleaseEvent(e);
  	}
  
  	return ret;
  }
  
  // Get Thread ID
  UINT ThreadId()
  {
  	return OSThreadId();
  }
  
  // Creating a thread
  THREAD *NewThreadInternal(THREAD_PROC *thread_proc, void *param)
  {
  	THREAD *t;
  	UINT retry = 0;
  	// Validate arguments
  	if (thread_proc == NULL)
  	{
  		return NULL;
  	}
  
  	// Initialize Thread object
  	t = ZeroMalloc(sizeof(THREAD));
  	t->init_finished_event = NewEvent();
  
  	t->param = param;
  	t->ref = NewRef();
  	t->thread_proc = thread_proc;
  
  	// Wait until the OS to initialize the thread
  	while (true)
  	{
  		if ((retry++) > 60)
  		{
  			printf("\n\n*** error: new thread create failed.\n\n");
  			AbortExit();
  		}
  		if (OSInitThread(t))
  		{
  			break;
  		}
  		SleepThread(500);
  	}
  
  	// KS
  	KS_INC(KS_NEWTHREAD_COUNT);
  
  	return t;
  }
  
  // Release of thread
  void ReleaseThreadInternal(THREAD *t)
  {
  	// Validate arguments
  	if (t == NULL)
  	{
  		return;
  	}
  
  	if (Release(t->ref) == 0)
  	{
  		CleanupThreadInternal(t);
  	}
  }
  
  // Clean up of the thread
  void CleanupThreadInternal(THREAD *t)
  {
  	// Validate arguments
  	if (t == NULL)
  	{
  		return;
  	}
  
  	// Release of the thread
  	OSFreeThread(t);
  
  	// Release the event
  	ReleaseEvent(t->init_finished_event);
  	// Memory release
  	Free(t);
  
  	// KS
  	KS_INC(KS_FREETHREAD_COUNT);
  }
  
  // Wait for the termination of the thread
  bool WaitThreadInternal(THREAD *t)
  {
  	// Validate arguments
  	if (t == NULL)
  	{
  		return false;
  	}
  
  	return OSWaitThread(t);
  }
  
  // Notify that the thread initialization is complete
  void NoticeThreadInitInternal(THREAD *t)
  {
  	// Validate arguments
  	if (t == NULL)
  	{
  		return;
  	}
  
  	// Notify
  	Set(t->init_finished_event);
  }
  
  // Wait for completion of thread initialization
  void WaitThreadInitInternal(THREAD *t)
  {
  	// Validate arguments
  	if (t == NULL)
  	{
  		return;
  	}
  
  	// KS
  	KS_INC(KS_WAITFORTHREAD_COUNT);
  
  	// Wait
  	Wait(t->init_finished_event, INFINITE);
  }
  
  // Get the date and time string by using the locale information
  void GetDateTimeStrEx(wchar_t *str, UINT size, SYSTEMTIME *st, LOCALE *locale)
  {
  	wchar_t tmp1[MAX_SIZE];
  	wchar_t tmp2[MAX_SIZE];
  	// Validate arguments
  	if (str == NULL || st == NULL)
  	{
  		return;
  	}
  
  	GetDateStrEx(tmp1, sizeof(tmp1), st, locale);
  	GetTimeStrEx(tmp2, sizeof(tmp2), st, locale);
  	UniFormat(str, size, L"%s %s", tmp1, tmp2);
  }
  
  // Get the time string by using the locale information
  void GetTimeStrEx(wchar_t *str, UINT size, SYSTEMTIME *st, LOCALE *locale)
  {
  	wchar_t *tag = L"%02u%s%02u%s%02u%s";
  	// Validate arguments
  	if (str == NULL || st == NULL)
  	{
  		return;
  	}
  
  	if (_GETLANG() == SE_LANG_JAPANESE || _GETLANG() == SE_LANG_CHINESE_ZH)
  	{
  		tag = L"%2u%s%2u%s%2u%s";
  	}
  
  	locale = (locale != NULL ? locale : &current_locale);
  	UniFormat(str, size,
  		tag,
  		st->wHour, locale->HourStr,
  		st->wMinute, locale->MinuteStr,
  		st->wSecond, locale->SecondStr);
  }
  
  // Get a date string by using the locale information
  void GetDateStrEx(wchar_t *str, UINT size, SYSTEMTIME *st, LOCALE *locale)
  {
  	wchar_t *tag = L"%04u%s%02u%s%02u%s (%s)";
  	// Validate arguments
  	if (str == NULL || st == NULL)
  	{
  		return;
  	}
  
  	if (_GETLANG() == SE_LANG_JAPANESE || _GETLANG() == SE_LANG_CHINESE_ZH)
  	{
  		tag = L"%4u%s%2u%s%2u%s(%s)";
  	}
  
  	locale = (locale != NULL ? locale : &current_locale);
  	UniFormat(str, size,
  		tag,
  		st->wYear, locale->YearStr,
  		st->wMonth, locale->MonthStr,
  		st->wDay, locale->DayStr,
  		locale->DayOfWeek[st->wDayOfWeek]);
  }
  
  // Get the time string to milliseconds (for example, 12:34:56.789)
  void GetTimeStrMilli(char *str, UINT size, SYSTEMTIME *st)
  {
  	// Validate arguments
  	if (st == NULL || str == NULL)
  	{
  		return;
  	}
  
  	Format(str, size, "%02u:%02u:%02u.%03u",
  		st->wHour, st->wMinute, st->wSecond, st->wMilliseconds);
  }
  
  // Get the time string (for example, 12:34:56)
  void GetTimeStr(char *str, UINT size, SYSTEMTIME *st)
  {
  	// Validate arguments
  	if (str == NULL || st == NULL)
  	{
  		return;
  	}
  
  	Format(str, size, "%02u:%02u:%02u",
  		st->wHour, st->wMinute, st->wSecond);
  }
  
  // Get the date string (example: 2004/07/23)
  void GetDateStr(char *str, UINT size, SYSTEMTIME *st)
  {
  	// Validate arguments
  	if (str == NULL || st == NULL)
  	{
  		return;
  	}
  
  	Format(str, size, "%04u-%02u-%02u",
  		st->wYear, st->wMonth, st->wDay);
  }
  
  // Get the date and time string (example: 2004/07/23 12:34:56)
  void GetDateTimeStr(char *str, UINT size, SYSTEMTIME *st)
  {
  	// Validate arguments
  	if (str == NULL || st == NULL)
  	{
  		return;
  	}
  
  	Format(str, size, "%04u-%02u-%02u %02u:%02u:%02u",
  		st->wYear, st->wMonth, st->wDay,
  		st->wHour, st->wMinute, st->wSecond);
  }
  
  // Get the date and time string in milliseconds (example: 2004/07/23 12:34:56.789)
  void GetDateTimeStrMilli(char *str, UINT size, SYSTEMTIME *st)
  {
  	// Validate arguments
  	if (str == NULL || st == NULL)
  	{
  		return;
  	}
  
  	Format(str, size, "%04u-%02u-%02u %02u:%02u:%02u.%03u",
  		st->wYear, st->wMonth, st->wDay,
  		st->wHour, st->wMinute, st->wSecond,
  		st->wMilliseconds);
  }
  
  // Convert string RFC3339 format (example: 2017-09-27T18:25:55.434-9:00) to UINT64
  UINT64 DateTimeStrRFC3339ToSystemTime64(char *str)
  {
  	SYSTEMTIME st;
  	if (DateTimeStrRFC3339ToSystemTime(&st, str))
  	{
  		return SystemToUINT64(&st);
  	}
  	else
  	{
  		return 0;
  	}
  }
  
  // Convert string RFC3339 format (example: 2017-09-27T18:25:55.434-9:00) to SYSTEMTIME
  bool DateTimeStrRFC3339ToSystemTime(SYSTEMTIME *st, char *str)
  {
  	bool ok = false;
  	UINT index_plus;
  	char tmp[MAX_PATH];
  	Zero(st, sizeof(SYSTEMTIME));
  	if (st == NULL || str == NULL)
  	{
  		return false;
  	}
  
  	StrCpy(tmp, sizeof(tmp), str);
  
  	index_plus = SearchStrEx(tmp, "+", 0, false);
  	if (index_plus != INFINITE)
  	{
  		tmp[index_plus] = 0;
  	}
  
  	if (StrLen(tmp) >= 19)
  	{
  		if (tmp[4] == '-' && tmp[7] == '-' && tmp[10] == 'T' && tmp[13] == ':' &&
  			tmp[16] == ':')
  		{
  			char str_year[16], str_month[16], str_day[16], str_hour[16], str_minute[16],
  				 str_second[16], str_msec[16];
  
  			StrCpy(str_year, sizeof(str_year), tmp + 0);
  			str_year[4] = 0;
  
  			StrCpy(str_month, sizeof(str_month), tmp + 5);
  			str_month[2] = 0;
  
  			StrCpy(str_day, sizeof(str_day), tmp + 8);
  			str_day[2] = 0;
  
  			StrCpy(str_hour, sizeof(str_hour), tmp + 11);
  			str_hour[2] = 0;
  
  			StrCpy(str_minute, sizeof(str_minute), tmp + 14);
  			str_minute[2] = 0;
  
  			StrCpy(str_second, sizeof(str_second), tmp + 17);
  			str_second[2] = 0;
  
  			str_msec[0] = 0;
  
  			if (StrLen(tmp) >= 21 && tmp[19] == '.')
  			{
  				StrCpy(str_msec, sizeof(str_msec), tmp + 20);
  				str_msec[StrLen(tmp) - 21] = 0;
  				while (StrLen(str_msec) < 3)
  				{
  					StrCat(str_msec, sizeof(str_msec), "0");
  				}
  				str_msec[3] = 0;
  			}
  
  			st->wYear = ToInt(str_year);
  			st->wMonth = ToInt(str_month);
  			st->wDay = ToInt(str_day);
  			st->wHour = ToInt(str_hour);
  			st->wMinute = ToInt(str_minute);
  			st->wSecond = ToInt(str_second);
  			st->wMilliseconds = ToInt(str_msec);
  
  			NormalizeSystem(st);
  
  			ok = true;
  		}
  	}
  
  	return ok;
  }
  
  // Get the date and time string in RFC3339 format (example: 2017-09-27T18:25:55.434-9:00)
  void GetDateTimeStrRFC3339(char *str, UINT size, SYSTEMTIME *st, int timezone_min){
  	// Validate arguments
  	if (str == NULL || st == NULL)
  	{
  		ClearStr(str, size);
  		return;
  	}
  
  	if(timezone_min == 0){
  		Format(str, size, "%04u-%02u-%02uT%02u:%02u:%02u.%03uZ",
  		st->wYear, st->wMonth, st->wDay,
  		st->wHour, st->wMinute, st->wSecond,
  		st->wMilliseconds);
  	}else{
  		Format(str, size, "%04u-%02u-%02uT%02u:%02u:%02u.%03u%+02d:%02d",
  		st->wYear, st->wMonth, st->wDay,
  		st->wHour, st->wMinute, st->wSecond,
  		st->wMilliseconds, timezone_min/60, timezone_min%60);
  	}
  }
  
  // Get the time string
  void GetSpanStr(char *str, UINT size, UINT64 sec64)
  {
  	char tmp[MAX_SIZE];
  	// Validate arguments
  	if (str == NULL)
  	{
  		return;
  	}
  
  	StrCpy(tmp, sizeof(tmp), "");
  	if (sec64 >= (UINT64)(1000 * 3600 * 24))
  	{
  		Format(tmp, sizeof(tmp), "%u:", (UINT)(sec64 / (UINT64)(1000 * 3600 * 24)));
  	}
  
  	Format(tmp, sizeof(tmp), "%s%02u:%02u:%02u", tmp,
  		(UINT)(sec64 % (UINT64)(1000 * 60 * 60 * 24)) / (1000 * 60 * 60),
  		(UINT)(sec64 % (UINT64)(1000 * 60 * 60)) / (1000 * 60),
  		(UINT)(sec64 % (UINT64)(1000 * 60)) / 1000);
  
  	Trim(tmp);
  	StrCpy(str, size, tmp);
  }
  
  // Get the time string (in milliseconds)
  void GetSpanStrMilli(char *str, UINT size, UINT64 sec64)
  {
  	char tmp[MAX_SIZE];
  	// Validate arguments
  	if (str == NULL)
  	{
  		return;
  	}
  
  	StrCpy(tmp, sizeof(tmp), "");
  	if (sec64 >= (UINT64)(1000 * 3600 * 24))
  	{
  		Format(tmp, sizeof(tmp), "%u:", (UINT)(sec64 / (UINT64)(1000 * 3600 * 24)));
  	}
  
  	Format(tmp, sizeof(tmp), "%s%02u:%02u:%02u.%03u", tmp,
  		(UINT)(sec64 % (UINT64)(1000 * 60 * 60 * 24)) / (1000 * 60 * 60),
  		(UINT)(sec64 % (UINT64)(1000 * 60 * 60)) / (1000 * 60),
  		(UINT)(sec64 % (UINT64)(1000 * 60)) / 1000,
  		(UINT)(sec64 % (UINT64)(1000)));
  
  	Trim(tmp);
  	StrCpy(str, size, tmp);
  }
  
  // Get the time string (extended)
  void GetSpanStrEx(wchar_t *str, UINT size, UINT64 sec64, LOCALE *locale)
  {
  	wchar_t tmp[MAX_SIZE];
  	// Validate arguments
  	if (str == NULL)
  	{
  		return;
  	}
  
  	locale = (locale != NULL ? locale : &current_locale);
  
  	UniStrCpy(tmp, sizeof(tmp), L"");
  	if (sec64 >= (UINT64)(1000 * 3600 * 24))
  	{
  		UniFormat(tmp, sizeof(tmp), L"%u%s ", (UINT)(sec64 / (UINT64)(1000 * 3600 * 24)),
  			locale->SpanDay);
  	}
  
  	UniFormat(tmp, sizeof(tmp), L"%s%u%s %02u%s %02u%s", tmp,
  		(UINT)(sec64 % (UINT64)(1000 * 60 * 60 * 24)) / (1000 * 60 * 60),
  		locale->SpanHour,
  		(UINT)(sec64 % (UINT64)(1000 * 60 * 60)) / (1000 * 60),
  		locale->SpanMinute,
  		(UINT)(sec64 % (UINT64)(1000 * 60)) / 1000,
  		locale->SpanSecond);
  
  	UniTrim(tmp);
  	UniStrCpy(str, size, tmp);
  }
  
  // Get the current locale information
  void GetCurrentLocale(LOCALE *locale)
  {
  	// Validate arguments
  	if (locale == NULL)
  	{
  		return;
  	}
  
  	Copy(locale, &current_locale, sizeof(LOCALE));
  }
  
  // Set the locale information
  void SetLocale(wchar_t *str)
  {
  	wchar_t *set_locale_str;
  	LOCALE tmp;
  
  	if (str != NULL)
  	{
  		set_locale_str = str;
  	}
  	else
  	{
  		set_locale_str = default_locale_str;
  	}
  
  	if (LoadLocale(&tmp, set_locale_str) == false)
  	{
  		if (LoadLocale(&tmp, default_locale_str) == false)
  		{
  			return;
  		}
  	}
  
  	Copy(&current_locale, &tmp, sizeof(LOCALE));
  }
  
  #define	COPY_LOCALE_STR(dest, size, src)	UniStrCpy(dest, size, UniStrCmp(src, L"$") == 0 ? L"" : src)
  
  // Read the locale information
  bool LoadLocale(LOCALE *locale, wchar_t *str)
  {
  	UNI_TOKEN_LIST *tokens;
  	UINT i;
  	// Validate arguments
  	if (locale == NULL || str == NULL)
  	{
  		return false;
  	}
  
  	// Analysis of the token
  	tokens = UniParseToken(str, L" ");
  	if (tokens->NumTokens != 18)
  	{
  		UniFreeToken(tokens);
  		return false;
  	}
  
  	// Set to the structure
  	Zero(locale, sizeof(LOCALE));
  	COPY_LOCALE_STR(locale->YearStr, sizeof(locale->YearStr), tokens->Token[0]);
  	COPY_LOCALE_STR(locale->MonthStr, sizeof(locale->MonthStr), tokens->Token[1]);
  	COPY_LOCALE_STR(locale->DayStr, sizeof(locale->DayStr), tokens->Token[2]);
  	COPY_LOCALE_STR(locale->HourStr, sizeof(locale->HourStr), tokens->Token[3]);
  	COPY_LOCALE_STR(locale->MinuteStr, sizeof(locale->MinuteStr), tokens->Token[4]);
  	COPY_LOCALE_STR(locale->SecondStr, sizeof(locale->SecondStr), tokens->Token[5]);
  
  	for (i = 0;i < 7;i++)
  	{
  		COPY_LOCALE_STR(locale->DayOfWeek[i], sizeof(locale->DayOfWeek[i]),
  			tokens->Token[6 + i]);
  	}
  
  	COPY_LOCALE_STR(locale->SpanDay, sizeof(locale->SpanDay), tokens->Token[13]);
  	COPY_LOCALE_STR(locale->SpanHour, sizeof(locale->SpanHour), tokens->Token[14]);
  	COPY_LOCALE_STR(locale->SpanMinute, sizeof(locale->SpanMinute), tokens->Token[15]);
  	COPY_LOCALE_STR(locale->SpanSecond, sizeof(locale->SpanSecond), tokens->Token[16]);
  
  	COPY_LOCALE_STR(locale->Unknown, sizeof(locale->Unknown), tokens->Token[17]);
  
  	UniFreeToken(tokens);
  	return true;
  }
  
  // Convert SYSTEMTIME into DOS date
  USHORT SystemToDosDate(SYSTEMTIME *st)
  {
  	return (USHORT)(
  		((UINT)(st->wYear - 1980) << 9) |
  		((UINT)st->wMonth<< 5) |
  		(UINT)st->wDay);
  }
  USHORT System64ToDosDate(UINT64 i)
  {
  	SYSTEMTIME st;
  	UINT64ToSystem(&st, i);
  	return SystemToDosDate(&st);
  }
  
  // Convert SYSTEMTIME into DOS time
  USHORT SystemToDosTime(SYSTEMTIME *st)
  {
  	return (USHORT)(
  		((UINT)st->wHour << 11) |
  		((UINT)st->wMinute << 5) |
  		((UINT)st->wSecond >> 1));
  }
  USHORT System64ToDosTime(UINT64 i)
  {
  	SYSTEMTIME st;
  	UINT64ToSystem(&st, i);
  	return SystemToDosTime(&st);
  }
  
  // Convert the tm to the SYSTEMTIME
  void TmToSystem(SYSTEMTIME *st, struct tm *t)
  {
  	struct tm tmp;
  	// Validate arguments
  	if (st == NULL || t == NULL)
  	{
  		return;
  	}
  
  	Copy(&tmp, t, sizeof(struct tm));
  	NormalizeTm(&tmp);
  
  	Zero(st, sizeof(SYSTEMTIME));
  	st->wYear = MAKESURE(tmp.tm_year + 1900, 1970, 2099);
  	st->wMonth = MAKESURE(tmp.tm_mon + 1, 1, 12);
  	st->wDay = MAKESURE(tmp.tm_mday, 1, 31);
  	st->wDayOfWeek = MAKESURE(tmp.tm_wday, 0, 6);
  	st->wHour = MAKESURE(tmp.tm_hour, 0, 23);
  	st->wMinute = MAKESURE(tmp.tm_min, 0, 59);
  	st->wSecond = MAKESURE(tmp.tm_sec, 0, 59);
  	st->wMilliseconds = 0;
  }
  
  // Convert the SYSTEMTIME to tm
  void SystemToTm(struct tm *t, SYSTEMTIME *st)
  {
  	// Validate arguments
  	if (t == NULL || st == NULL)
  	{
  		return;
  	}
  
  	Zero(t, sizeof(struct tm));
  	t->tm_year = MAKESURE(st->wYear, 1970, 2099) - 1900;
  	t->tm_mon = MAKESURE(st->wMonth, 1, 12) - 1;
  	t->tm_mday = MAKESURE(st->wDay, 1, 31);
  	t->tm_hour = MAKESURE(st->wHour, 0, 23);
  	t->tm_min = MAKESURE(st->wMinute, 0, 59);
  	t->tm_sec = MAKESURE(st->wSecond, 0, 59);
  
  	t->tm_isdst = -1;
  	NormalizeTm(t);
  }
  
  // Convert the time_t to SYSTEMTIME
  void TimeToSystem(SYSTEMTIME *st, time_64t t)
  {
  	struct tm tmp;
  	// Validate arguments
  	if (st == NULL)
  	{
  		return;
  	}
  
  	TimeToTm(&tmp, t);
  	TmToSystem(st, &tmp);
  }
  
  // Convert the time_t to 64-bit SYSTEMTIME
  UINT64 TimeToSystem64(time_64t t)
  {
  	SYSTEMTIME st;
  
  	TimeToSystem(&st, t);
  
  	return SystemToUINT64(&st);
  }
  
  // Convert the SYSTEMTIME to time_t
  time_64t SystemToTime(SYSTEMTIME *st)
  {
  	struct tm t;
  	// Validate arguments
  	if (st == NULL)
  	{
  		return 0;
  	}
  
  	SystemToTm(&t, st);
  	return TmToTime(&t);
  }
  
  // Convert a 64-bit SYSTEMTIME to a time_t
  time_64t System64ToTime(UINT64 i)
  {
  	SYSTEMTIME st;
  
  	UINT64ToSystem(&st, i);
  
  	return SystemToTime(&st);
  }
  
  // Convert the tm to time_t
  time_64t TmToTime(struct tm *t)
  {
  	time_64t tmp;
  	// Validate arguments
  	if (t == NULL)
  	{
  		return 0;
  	}
  
  	tmp = c_mkgmtime(t);
  	if (tmp == (time_64t)-1)
  	{
  		return 0;
  	}
  	return tmp;
  }
  
  // Convert time_t to tm
  void TimeToTm(struct tm *t, time_64t time)
  {
  	// Validate arguments
  	if (t == NULL)
  	{
  		return;
  	}
  
  	Zero(t, sizeof(struct tm));
  	c_gmtime_r(&time, t);
  }
  
  // Normalize the tm
  void NormalizeTm(struct tm *t)
  {
  	time_64t tmp;
  	// Validate arguments
  	if (t == NULL)
  	{
  		return;
  	}
  
  	tmp = c_mkgmtime(t);
  	if (tmp == (time_64t)-1)
  	{
  		return;
  	}
  
  	c_gmtime_r(&tmp, t);
  }
  
  // Normalize the SYSTEMTIME
  void NormalizeSystem(SYSTEMTIME *st)
  {
  	UINT64 sec64;
  	// Validate arguments
  	if (st == NULL)
  	{
  		return;
  	}
  
  	sec64 = SystemToUINT64(st);
  	UINT64ToSystem(st, sec64);
  }
  
  // Convert a 64-bit local time to a system time
  UINT64 LocalToSystem64(UINT64 t)
  {
  	SYSTEMTIME st;
  	UINT64ToSystem(&st, t);
  	LocalToSystem(&st, &st);
  	return SystemToUINT64(&st);
  }
  
  // Convert the 64bit system time to local time
  UINT64 SystemToLocal64(UINT64 t)
  {
  	SYSTEMTIME st;
  	UINT64ToSystem(&st, t);
  	SystemToLocal(&st, &st);
  	return SystemToUINT64(&st);
  }
  
  // Convert local time to system time
  void LocalToSystem(SYSTEMTIME *system, SYSTEMTIME *local)
  {
  	UINT64 sec64;
  	// Validate arguments
  	if (local == NULL || system == NULL)
  	{
  		return;
  	}
  
  	sec64 = (UINT64)((INT64)SystemToUINT64(local) - GetTimeDiffEx(local, true));
  	UINT64ToSystem(system, sec64);
  }
  
  // Convert the system time to local time
  void SystemToLocal(SYSTEMTIME *local, SYSTEMTIME *system)
  {
  	UINT64 sec64;
  	// Validate arguments
  	if (local == NULL || system == NULL)
  	{
  		return;
  	}
  
  	sec64 = (UINT64)((INT64)SystemToUINT64(system) + GetTimeDiffEx(system, false));
  	UINT64ToSystem(local, sec64);
  }
  
  // Get the time difference between the local time and the system time based on the specified time
  INT64 GetTimeDiffEx(SYSTEMTIME *basetime, bool local_time)
  {
  	time_t tmp;
  	struct tm t1, t2;
  	SYSTEMTIME snow;
  	struct tm now;
  	SYSTEMTIME s1, s2;
  	INT64 ret;
  
  	Copy(&snow, basetime, sizeof(SYSTEMTIME));
  
  	if (sizeof(time_t) == 4)
  	{
  		if (snow.wYear >= 2038)
  		{
  			// For old systems: avoid the 2038-year problem
  			snow.wYear = 2037;
  		}
  	}
  
  	SystemToTm(&now, &snow);
  	if (local_time == false)
  	{
  		tmp = (time_t)c_mkgmtime(&now);
  	}
  	else
  	{
  		tmp = mktime(&now);
  	}
  
  	if (tmp == (time_t)-1)
  	{
  		return 0;
  	}
  
  #ifndef	OS_UNIX
  	Copy(&t1, localtime(&tmp), sizeof(struct tm));
  	Copy(&t2, gmtime(&tmp), sizeof(struct tm));
  #else	// OS_UNIX
  	localtime_r(&tmp, &t1);
  	gmtime_r(&tmp, &t2);
  #endif	// OS_UNIX
  
  	TmToSystem(&s1, &t1);
  	TmToSystem(&s2, &t2);
  
  	ret = (INT)SystemToUINT64(&s1) - (INT)SystemToUINT64(&s2);
  
  	return ret;
  }
  
  // Convert UINT64 to the SYSTEMTIME
  void UINT64ToSystem(SYSTEMTIME *st, UINT64 sec64)
  {
  	UINT64 tmp64;
  	UINT sec, millisec;
  	time_64t time;
  	// Validate arguments
  	if (st == NULL)
  	{
  		return;
  	}
  
  	sec64 = SafeTime64(sec64 + 32400000ULL);
  	tmp64 = sec64 / (UINT64)1000;
  	millisec = (UINT)(sec64 - tmp64 * (UINT64)1000);
  	sec = (UINT)tmp64;
  	time = (time_64t)sec;
  	TimeToSystem(st, time);
  	st->wMilliseconds = (WORD)millisec;
  }
  
  // Convert the SYSTEMTIME to UINT64
  UINT64 SystemToUINT64(SYSTEMTIME *st)
  {
  	UINT64 sec64;
  	time_64t time;
  	// Validate arguments
  	if (st == NULL)
  	{
  		return 0;
  	}
  
  	time = SystemToTime(st);
  
  	//For times before 1970-01-01, clamp to the minimum
  	//because we have to return an unsigned integer.
  	//This is less wrong than casting it to UINT64
  	//and returning a time far in the future.
  	//For some reason we subtract 9 hours below, so
  	//account for that here.
  	if( time < 32400000LL ) return 0;
  
  	sec64 = (UINT64)time * (UINT64)1000;
  	sec64 += st->wMilliseconds;
  
  	return sec64 - 32400000ULL;
  }
  
  // Get local time in UINT64
  UINT64 LocalTime64()
  {
  	SYSTEMTIME s;
  	LocalTime(&s);
  	return SystemToUINT64(&s);
  }
  
  // Get the system time in UINT64
  UINT64 SystemTime64()
  {
  	SYSTEMTIME s;
  	SystemTime(&s);
  	return SystemToUINT64(&s);
  }
  
  // Get local time
  void LocalTime(SYSTEMTIME *st)
  {
  	SYSTEMTIME tmp;
  	// Validate arguments
  	if (st == NULL)
  	{
  		return;
  	}
  
  	SystemTime(&tmp);
  	SystemToLocal(st, &tmp);
  }
  
  // Get the System Time
  void SystemTime(SYSTEMTIME *st)
  {
  	// Validate arguments
  	if (st == NULL)
  	{
  		return;
  	}
  
  	OSGetSystemTime(st);
  
  	// KS
  	KS_INC(KS_GETTIME_COUNT);
  }
  
  time_64t c_mkgmtime(struct tm *tm)
  {
  	int years, months, days, hours, minutes, seconds;
  
  	years = tm->tm_year + 1900;   /* year - 1900 -> year */
  	months = tm->tm_mon;          /* 0..11 */
  	days = tm->tm_mday - 1;       /* 1..31 -> 0..30 */
  	hours = tm->tm_hour;          /* 0..23 */
  	minutes = tm->tm_min;         /* 0..59 */
  	seconds = tm->tm_sec;         /* 0..61 in ANSI C. */
  
  	ADJUST_TM(seconds, minutes, 60);
  	ADJUST_TM(minutes, hours, 60);
  	ADJUST_TM(hours, days, 24);
  	ADJUST_TM(months, years, 12);
  	if (days < 0)
  		do {
  			if (--months < 0) {
  				--years;
  				months = 11;
  			}
  			days += monthlen(months, years);
  		} while (days < 0);
  	else
  		while (days >= monthlen(months, years)) {
  			days -= monthlen(months, years);
  			if (++months >= 12) {
  				++years;
  				months = 0;
  			}
  		}
  
  		/* Restore adjusted values in tm structure */
  		tm->tm_year = years - 1900;
  		tm->tm_mon = months;
  		tm->tm_mday = days + 1;
  		tm->tm_hour = hours;
  		tm->tm_min = minutes;
  		tm->tm_sec = seconds;
  
  		/* Set `days' to the number of days into the year. */
  		days += ydays[months] + (months > 1 && leap (years));
  		tm->tm_yday = days;
  
  		/* Now calculate `days' to the number of days since Jan 1, 1970. */
  		days = (unsigned)days + 365 * (unsigned)(years - 1970) +
  			(unsigned)(nleap (years));
  		tm->tm_wday = ((unsigned)days + 4) % 7; /* Jan 1, 1970 was Thursday. */
  		tm->tm_isdst = 0;
  
  		if (years < 1970)
  			return (time_64t)-1;
  
  #if (defined(TM_YEAR_MAX) && defined(TM_MON_MAX) && defined(TM_MDAY_MAX))
  #if (defined(TM_HOUR_MAX) && defined(TM_MIN_MAX) && defined(TM_SEC_MAX))
  		if (years > TM_YEAR_MAX ||
  			(years == TM_YEAR_MAX &&
  			(tm->tm_yday > ydays[TM_MON_MAX] + (TM_MDAY_MAX - 1) +
  			(TM_MON_MAX > 1 && leap (TM_YEAR_MAX)) ||
  			(tm->tm_yday == ydays[TM_MON_MAX] + (TM_MDAY_MAX - 1) +
  			(TM_MON_MAX > 1 && leap (TM_YEAR_MAX)) &&
  			(hours > TM_HOUR_MAX ||
  			(hours == TM_HOUR_MAX &&
  			(minutes > TM_MIN_MAX ||
  			(minutes == TM_MIN_MAX && seconds > TM_SEC_MAX) )))))))
  			return (time_64t)-1;
  #endif
  #endif
  
  		return (time_64t)(86400L * (unsigned long)(unsigned)days +
  			3600L * (unsigned long)hours +
  			(unsigned long)(60 * minutes + seconds));
  }
  
  // Get the system timer
  UINT Tick()
  {
  	// KS
  	KS_INC(KS_GETTICK_COUNT);
  	return OSGetTick();
  }
  
  // Sleep thread
  void SleepThread(UINT time)
  {
  	// KS
  	KS_INC(KS_SLEEPTHREAD_COUNT);
  
  	OSSleep(time);
  }
  
  // Yield
  void YieldCpu()
  {
  	OSYield();
  }
  
  // Stop system (abnormal termination)
  void AbortExit()
  {
  #ifdef	OS_WIN32
  	_exit(1);
  #else	// OS_WIN32
  
  #ifdef	RLIMIT_CORE
  	UnixSetResourceLimit(RLIMIT_CORE, 0);
  #endif	// RLIMIT_CORE
  
  	abort();
  #endif	// OS_WIN32
  }
  
  
  void AbortExitEx(char *msg)
  {
  	FILE *f;
  	// Validate arguments
  	if (msg == NULL)
  	{
  		msg = "Unknown Error";
  	}
  
  	f = fopen("abort_error_log.txt", "w");
  	if (f != NULL)
  	{
  		fwrite(msg, 1, strlen(msg), f);
  		fclose(f);
  	}
  
  	fputs("Fatal Error: ", stdout);
  	fputs(msg, stdout);
  	fputs("\r\n", stdout);
  
  #ifdef	OS_WIN32
  	_exit(1);
  #else	// OS_WIN32
  
  #ifdef	RLIMIT_CORE
  	UnixSetResourceLimit(RLIMIT_CORE, 0);
  #endif	// RLIMIT_CORE
  
  	abort();
  #endif	// OS_WIN32
  }
  
  