/*
* Copyright (c) 2009-2023 Tony Bybell.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* 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.
*
* SPDX-License-Identifier: MIT
*/
/*
* possible disables:
*
* FST_DYNAMIC_ALIAS_DISABLE : dynamic aliases are not processed
* FST_DYNAMIC_ALIAS2_DISABLE : new encoding for dynamic aliases is not generated
* FST_WRITEX_DISABLE : fast write I/O routines are disabled
*
* possible enables:
*
* FST_DEBUG : not for production use, only enable for development
* FST_REMOVE_DUPLICATE_VC : glitch removal (has writer performance impact)
* HAVE_LIBPTHREAD -> FST_WRITER_PARALLEL : enables inclusion of parallel writer code
* FST_DO_MISALIGNED_OPS (defined automatically for x86 and some others) : CPU architecture can handle misaligned loads/stores
* _WAVE_HAVE_JUDY : use Judy arrays instead of Jenkins (undefine if LGPL is not acceptable)
*
*/
#ifndef FST_CONFIG_INCLUDE
# define FST_CONFIG_INCLUDE <config.h>
#endif
#include FST_CONFIG_INCLUDE
#include "fstapi.h"
#include "fastlz.h"
#include "lz4.h"
#include <errno.h>
#ifndef HAVE_LIBPTHREAD
#undef FST_WRITER_PARALLEL
#endif
#ifdef FST_WRITER_PARALLEL
#include <pthread.h>
#endif
#ifdef __MINGW32__
#include <windows.h>
#endif
#ifdef HAVE_ALLOCA_H
#include <alloca.h>
#elif defined(__GNUC__)
#ifndef __MINGW32__
#ifndef alloca
#define alloca __builtin_alloca
#endif
#else
#include <malloc.h>
#endif
#elif defined(_MSC_VER)
#include <malloc.h>
#define alloca _alloca
#endif
#ifndef PATH_MAX
#define PATH_MAX (4096)
#endif
#if defined(_MSC_VER)
typedef int64_t fst_off_t;
#else
typedef off_t fst_off_t;
#endif
/* note that Judy versus Jenkins requires more experimentation: they are */
/* functionally equivalent though it appears Jenkins is slightly faster. */
/* in addition, Jenkins is not bound by the LGPL. */
#ifdef _WAVE_HAVE_JUDY
#include <Judy.h>
#else
/* should be more than enough for fstWriterSetSourceStem() */
#define FST_PATH_HASHMASK ((1UL << 16) - 1)
typedef const void *Pcvoid_t;
typedef void *Pvoid_t;
typedef void **PPvoid_t;
#define JudyHSIns(a,b,c,d) JenkinsIns((a),(b),(c),(hashmask))
#define JudyHSFreeArray(a,b) JenkinsFree((a),(hashmask))
void JenkinsFree(void *base_i, uint32_t hashmask);
void **JenkinsIns(void *base_i, const unsigned char *mem, uint32_t length, uint32_t hashmask);
#endif
#ifndef FST_WRITEX_DISABLE
#define FST_WRITEX_MAX (64 * 1024)
#else
#define fstWritex(a,b,c) fstFwrite((b), (c), 1, fv)
#endif
/* these defines have a large impact on writer speed when a model has a */
/* huge number of symbols. as a default, use 128MB and increment when */
/* every 1M signals are defined. */
#define FST_BREAK_SIZE (1UL << 27)
#define FST_BREAK_ADD_SIZE (1UL << 22)
#define FST_BREAK_SIZE_MAX (1UL << 31)
#define FST_ACTIVATE_HUGE_BREAK (1000000)
#define FST_ACTIVATE_HUGE_INC (1000000)
#define FST_WRITER_STR "fstWriter"
#define FST_ID_NAM_SIZ (512)
#define FST_ID_NAM_ATTR_SIZ (65536+4096)
#define FST_DOUBLE_ENDTEST (2.7182818284590452354)
#define FST_HDR_SIM_VERSION_SIZE (128)
#define FST_HDR_DATE_SIZE (119)
#define FST_HDR_FILETYPE_SIZE (1)
#define FST_HDR_TIMEZERO_SIZE (8)
#define FST_GZIO_LEN (32768)
#define FST_HDR_FOURPACK_DUO_SIZE (4*1024*1024)
#if defined(__i386__) || defined(__x86_64__) || defined(_AIX)
#define FST_DO_MISALIGNED_OPS
#endif
#if defined(__APPLE__) && defined(__MACH__)
#define FST_MACOSX
#include <sys/sysctl.h>
#endif
#if defined(FST_MACOSX) || defined(__MINGW32__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__NetBSD__)
#define FST_UNBUFFERED_IO
#endif
#ifdef __GNUC__
/* Boolean expression more often true than false */
#define FST_LIKELY(x) __builtin_expect(!!(x), 1)
/* Boolean expression more often false than true */
#define FST_UNLIKELY(x) __builtin_expect(!!(x), 0)
#else
#define FST_LIKELY(x) (!!(x))
#define FST_UNLIKELY(x) (!!(x))
#endif
#define FST_APIMESS "FSTAPI | "
/***********************/
/*** ***/
/*** common function ***/
/*** ***/
/***********************/
#ifdef __MINGW32__
#include <io.h>
#ifndef HAVE_FSEEKO
#define ftello _ftelli64
#define fseeko _fseeki64
#endif
#endif
/*
* the recoded "extra" values...
* note that FST_RCV_Q is currently unused and is for future expansion.
* its intended use is as another level of escape such that any arbitrary
* value can be stored as the value: { time_delta, 8 bits, FST_RCV_Q }.
* this is currently not implemented so that the branchless decode is:
* uint32_t shcnt = 2 << (vli & 1); tdelta = vli >> shcnt;
*/
#define FST_RCV_X (1 | (0<<1))
#define FST_RCV_Z (1 | (1<<1))
#define FST_RCV_H (1 | (2<<1))
#define FST_RCV_U (1 | (3<<1))
#define FST_RCV_W (1 | (4<<1))
#define FST_RCV_L (1 | (5<<1))
#define FST_RCV_D (1 | (6<<1))
#define FST_RCV_Q (1 | (7<<1))
#define FST_RCV_STR "xzhuwl-?"
/* 01234567 */
/*
* prevent old file overwrite when currently being read
*/
static FILE *unlink_fopen(const char *nam, const char *mode)
{
unlink(nam);
return(fopen(nam, mode));
}
/*
* system-specific temp file handling
*/
#ifdef __MINGW32__
static FILE* tmpfile_open(char **nam)
{
char *fname = NULL;
TCHAR szTempFileName[MAX_PATH];
TCHAR lpTempPathBuffer[MAX_PATH];
DWORD dwRetVal = 0;
UINT uRetVal = 0;
FILE *fh = NULL;
if(nam) /* cppcheck warning fix: nam is always defined, so this is not needed */
{
dwRetVal = GetTempPath(MAX_PATH, lpTempPathBuffer);
if((dwRetVal > MAX_PATH) || (dwRetVal == 0))
{
fprintf(stderr, FST_APIMESS "GetTempPath() failed in " __FILE__ " line %d, exiting.\n", __LINE__);
exit(255);
}
else
{
uRetVal = GetTempFileName(lpTempPathBuffer, TEXT("FSTW"), 0, szTempFileName);
if (uRetVal == 0)
{
fprintf(stderr, FST_APIMESS "GetTempFileName() failed in " __FILE__ " line %d, exiting.\n", __LINE__);
exit(255);
}
else
{
fname = strdup(szTempFileName);
}
}
if(fname)
{
*nam = fname;
fh = unlink_fopen(fname, "w+b");
}
}
return(fh);
}
#else
static FILE* tmpfile_open(char **nam)
{
FILE *f = tmpfile(); /* replace with mkstemp() + fopen(), etc if this is not good enough */
if(nam) { *nam = NULL; }
return(f);
}
#endif
static void tmpfile_close(FILE **f, char **nam)
{
if(f)
{
if(*f) { fclose(*f); *f = NULL; }
}
if(nam)
{
if(*nam)
{
unlink(*nam);