aboutsummaryrefslogtreecommitdiff
path: root/project/dclib/dclib-file.c
diff options
context:
space:
mode:
authorroot <root@dc13.lemmi>2020-07-21 10:00:46 +0200
committerroot <root@dc13.lemmi>2020-07-21 10:00:46 +0200
commit1192c4575ea7afdf38e3fa0df4587a08620af11e (patch)
tree6910a62487c282d0840d2e668aa7eb40b7c8a3fd /project/dclib/dclib-file.c
parent76de8a89f7c1d1b4522eb66e8f89d248e8c1781f (diff)
SZS v2.18a
Diffstat (limited to '')
-rw-r--r--project/dclib/dclib-file.c5360
1 files changed, 5360 insertions, 0 deletions
diff --git a/project/dclib/dclib-file.c b/project/dclib/dclib-file.c
new file mode 100644
index 0000000..8a69370
--- /dev/null
+++ b/project/dclib/dclib-file.c
@@ -0,0 +1,5360 @@
+
+/***************************************************************************
+ * *
+ * _____ ____ *
+ * | __ \ / __ \ _ _ _____ *
+ * | | \ \ / / \_\ | | | | _ \ *
+ * | | \ \| | | | | | |_| | *
+ * | | | || | | | | | ___/ *
+ * | | / /| | __ | | | | _ \ *
+ * | |__/ / \ \__/ / | |___| | |_| | *
+ * |_____/ \____/ |_____|_|_____/ *
+ * *
+ * Wiimms source code library *
+ * *
+ ***************************************************************************
+ * *
+ * Copyright (c) 2012-2020 by Dirk Clemens <wiimm@wiimm.de> *
+ * *
+ ***************************************************************************
+ * *
+ * This program 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. *
+ * *
+ * This program 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. *
+ * *
+ * See file gpl-2.0.txt or http://www.gnu.org/licenses/gpl-2.0.txt *
+ * *
+ ***************************************************************************/
+
+#define _GNU_SOURCE 1
+
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <termios.h>
+#include <utime.h>
+#include <errno.h>
+#include <dirent.h>
+#include <poll.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/resource.h>
+
+#include "dclib-basics.h"
+#include "dclib-file.h"
+#include "dclib-debug.h"
+#include "dclib-network.h"
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// termios support ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static struct termios termios_data;
+int termios_valid = 0;
+
+void ResetTermios()
+{
+ if ( termios_valid > 0 )
+ tcsetattr(0,TCSANOW,&termios_data);
+}
+
+static bool LoadTermios()
+{
+ if (!termios_valid)
+ {
+ termios_valid = -1;
+ if ( isatty(0) && !tcgetattr(0,&termios_data) )
+ termios_valid = 1;
+ }
+ return termios_valid > 0;
+}
+
+bool EnableSingleCharInput()
+{
+ if (!LoadTermios())
+ return false;
+
+ struct termios tios;
+ memcpy(&tios,&termios_data,sizeof(tios));
+ tios.c_lflag &= ~(ICANON|ECHO);
+ tcsetattr(0,TCSANOW,&tios);
+ return true;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// enum FileMode_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ccp GetFileModeStatus
+(
+ char *buf, // result buffer
+ // NULL: use a local circulary static buffer
+ size_t buf_size, // size of 'buf', ignored if buf==NULL
+ FileMode_t file_mode, // filemode to print
+ uint print_mode // 0: short vector: 1 char for 1 attrib
+ // 1: long mode: keyword for each set attrib
+)
+{
+ struct info_t
+ {
+ FileMode_t mode;
+ char ch;
+ ccp name;
+ };
+
+ static const struct info_t info[] =
+ {
+ { FM_TEST, 't', "test" },
+ { FM_SILENT, 's', "silent" },
+ { FM_IGNORE, 'i', "ignore" },
+ { FM_MODIFY, 'M', "modify" },
+ { FM_APPEND, 'A', "append" },
+ { FM_UPDATE, 'U', "update" },
+ { FM_OVERWRITE, 'O', "overwrite" },
+ { FM_NUMBER, 'N', "number" },
+ { FM_REMOVE, 'R', "remove" },
+ { FM_MKDIR, 'M', "mkdir" },
+ { FM_STDIO, 'S', "stdio" },
+ { FM_TCP, 'I', "tcp" },
+ { FM_DEV, 'D', "dev" },
+ { FM_SPC, 'P', "spc" },
+ { FM_TOUCH, 'H', "touch" },
+ { FM_TEMP, 'T', "temp" },
+ {0,0,0}
+ };
+
+ if ( print_mode == 0 )
+ {
+ if ( !buf || !buf_size )
+ buf = GetCircBuf( buf_size = 16 );
+ char *dest = buf, *end = buf + buf_size - 1;
+ const struct info_t *ip;
+ for ( ip = info; ip->mode && dest < end; ip++ )
+ *dest++ = ip->mode & file_mode ? ip->ch : '-';
+ *dest = 0;
+ return buf;
+ }
+
+ if ( !buf || !buf_size )
+ buf = GetCircBuf( buf_size = 100 );
+
+ char *dest = buf, *end = buf + buf_size - 1;
+ const struct info_t *ip;
+ for ( ip = info; ip->mode && dest < end; ip++ )
+ if ( ip->mode & file_mode )
+ {
+ if ( dest > buf )
+ *dest++ = ',';
+ dest = StringCopyE(dest,end,ip->name);
+ }
+ *dest = 0;
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp GetFileOpenMode
+(
+ bool create, // false: open, true: create
+ FileMode_t file_mode // open modes
+)
+{
+ if ( file_mode & FM_TEST )
+ return "r";
+
+ if (!create)
+ return file_mode & FM_MODIFY ? "r+b" : "rb";
+
+ switch ( (uint)file_mode & (FM_MODIFY|FM_APPEND) )
+ {
+ case FM_MODIFY | FM_APPEND : return "a+b";
+ case FM_MODIFY : return "w+b";
+ case FM_APPEND : return "ab";
+ default : return "wb";
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct FileAttrib_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+FileAttrib_t * ClearFileAttrib
+(
+ FileAttrib_t * dest // NULL or destination attribute
+)
+{
+ if (dest)
+ memset(dest,0,sizeof(*dest));
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+FileAttrib_t * TouchFileAttrib
+(
+ FileAttrib_t * dest // valid destination attribute
+)
+{
+ DASSERT(dest);
+ dest->mtime = dest->ctime = dest->atime = dest->itime = time(0);
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+FileAttrib_t * SetFileAttrib
+(
+ FileAttrib_t * dest, // valid destination attribute
+ const FileAttrib_t * src_fa, // NULL or source attribute
+ const struct stat * src_stat // NULL or source attribute
+ // only used if 'src_fa==NULL'
+)
+{
+ DASSERT(dest);
+
+ if (src_fa)
+ memcpy(dest,src_fa,sizeof(*dest));
+ else if (src_stat)
+ {
+ memset(dest,0,sizeof(*dest));
+ if ( S_ISREG(src_stat->st_mode) )
+ {
+ dest->mtime = src_stat->st_mtime;
+ dest->ctime = src_stat->st_ctime;
+ dest->itime = dest->mtime > dest->ctime ? dest->mtime : dest->ctime;
+ dest->atime = src_stat->st_atime;
+ dest->size = src_stat->st_size;
+ }
+ dest->mode = src_stat->st_mode;
+ }
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+FileAttrib_t * MaxFileAttrib
+(
+ FileAttrib_t * dest, // valid source and destination attribute
+ const FileAttrib_t * src_fa, // NULL or second source attribute
+ const struct stat * src_stat // NULL or third source attribute
+)
+{
+ DASSERT(dest);
+
+ if (src_fa)
+ {
+ if ( dest->mtime < src_fa->mtime ) dest->mtime = src_fa->mtime;
+ if ( dest->ctime < src_fa->ctime ) dest->ctime = src_fa->ctime;
+ if ( dest->atime < src_fa->atime ) dest->atime = src_fa->atime;
+ if ( dest->itime < src_fa->itime ) dest->itime = src_fa->itime;
+
+ if ( dest->size < src_fa->size ) dest->size = src_fa->size;
+
+ dest->mode = src_fa->mode;
+ }
+
+ if (src_stat)
+ {
+ if ( S_ISREG(src_stat->st_mode) )
+ {
+ if ( dest->mtime < src_stat->st_mtime ) dest->mtime = src_stat->st_mtime;
+ if ( dest->ctime < src_stat->st_ctime ) dest->ctime = src_stat->st_ctime;
+ if ( dest->atime < src_stat->st_atime ) dest->atime = src_stat->st_atime;
+
+ if ( dest->itime < dest->mtime ) dest->itime = dest->mtime;
+ if ( dest->itime < dest->ctime ) dest->itime = dest->ctime;
+
+ if ( dest->size < src_stat->st_size ) dest->size = src_stat->st_size;
+ }
+ dest->mode = src_stat->st_mode;
+ }
+
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+FileAttrib_t * NormalizeFileAttrib
+(
+ FileAttrib_t * fa // valid attribute
+)
+{
+ DASSERT(fa);
+
+ if ( fa->size < 0 )
+ fa->size = 0;
+
+ time_t mtime = fa->mtime;
+ if (!mtime)
+ {
+ mtime = fa->itime;
+ if (!mtime)
+ {
+ mtime = fa->ctime;
+ if (!mtime)
+ mtime = fa->atime;
+ }
+ }
+ fa->mtime = mtime;
+
+ if (!fa->itime)
+ fa->itime = fa->ctime > mtime ? fa->ctime : mtime;
+
+ if (!fa->ctime)
+ fa->ctime = fa->itime > mtime ? fa->itime : mtime;
+
+ if (!fa->atime)
+ fa->atime = fa->itime > fa->ctime ? fa->itime : fa->ctime;
+
+ return fa;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// simple UNIX+TCP socket connect ///////////////
+///////////////////////////////////////////////////////////////////////////////
+// not stored in dclib-network.c to allow static linking!
+
+int ConnectUnixTCP
+(
+ ccp fname, // unix socket filename
+ bool silent // true: suppress error messages
+)
+{
+ DASSERT(fname);
+
+
+ //--- check file name
+
+ struct sockaddr_un sa;
+ const uint flen = fname ? strlen(fname)+1 : 0;
+ if ( flen > sizeof(sa.sun_path) )
+ {
+ if (!silent)
+ ERROR0(ERR_CANT_CONNECT,
+ "Path name to long for UNIX/STREAM socket: %s\n",fname);
+ return -1;
+ }
+
+ memset(&sa,0,sizeof(sa));
+ memcpy(sa.sun_path,fname,flen);
+ sa.sun_family = AF_UNIX;
+
+
+ //--- create socket
+
+ const int sock = socket(AF_UNIX,SOCK_STREAM,0);
+ if ( sock == -1 )
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_CONNECT,
+ "Can't create UNIX/STREAM socket: %s\n",fname);
+ return -1;
+ }
+
+
+ //--- connect
+
+ if (connect(sock,(struct sockaddr *)&sa,sizeof(sa)))
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_CONNECT,
+ "Can't connect to UNIX/STREAM socket: %s\n",fname);
+ close(sock);
+ return -1;
+ }
+
+ return sock;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int (*ConnectTCP_Hook)
+(
+ // if set: ConnectNumericTCP() uses ConnectTCP()
+
+ ccp addr, // TCP address: ['tcp':] IPv4 [:PORT]
+ int default_port, // NULL or default port, if not found in 'addr'
+ bool silent // true: suppress error messages
+) = 0;
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * ScanNumericIP4
+(
+ // returns next unread character or NULL on error
+
+ ccp addr, // address to scan
+ u32 *r_ipv4, // not NULL: store result here (local endian)
+ u32 *r_port, // not NULL: scan port too (local endian)
+ uint default_port // return this if no port found
+)
+{
+ if (!addr)
+ {
+ abort:
+ if (r_ipv4)
+ *r_ipv4 = 0;
+ if (r_port)
+ *r_port = 0;
+ return 0;
+ }
+
+ u32 num[4] = {0};
+ uint i;
+ ccp ptr = addr;
+ for ( i = 0; i < 4; )
+ {
+ char *end;
+ uint temp = str2ul(ptr,&end,10);
+ if ( !end || end == ptr )
+ break;
+ num[i++] = temp;
+ ptr = addr = end;
+ if ( *ptr != '.' )
+ break;
+ ptr++;
+ }
+
+ uint ip4;
+ switch (i)
+ {
+ case 1:
+ ip4 = num[0];
+ break;
+
+ case 2:
+ ip4 = num[0] << 24 | num[1];
+ break;
+
+ case 3:
+ ip4 = num[0] << 24 | num[1] << 16 | num[2];
+ break;
+
+ case 4:
+ ip4 = num[0] << 24 | num[1] << 16 | num[2] << 8 | num[3];
+ break;
+
+ default:
+ goto abort;
+ }
+ if (r_ipv4)
+ *r_ipv4 = ip4;
+
+ if (r_port)
+ {
+ if ( *addr == ':' )
+ {
+ char *end;
+ uint num = str2ul(addr+1,&end,10);
+ if ( end && end > addr+1 && num < 0x10000 )
+ {
+ addr = end;
+ default_port = num;
+ }
+ }
+ *r_port = default_port;
+ }
+
+ return (char*)addr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+mem_t ScanNumericIP4Mem
+(
+ // returns unread character or NullMem on error
+
+ mem_t addr, // address to scan
+ u32 *r_ipv4, // not NULL: store result here (local endian)
+ u32 *r_port, // not NULL: scan port too (local endian)
+ uint default_port // return this if no port found
+)
+{
+ if ( !addr.ptr || !addr.len )
+ {
+ abort:
+ if (r_ipv4)
+ *r_ipv4 = 0;
+ if (r_port)
+ *r_port = 0;
+ return NullMem;
+ }
+
+ u32 num[4] = {0};
+ uint i;
+ ccp ptr = addr.ptr;
+ ccp end = ptr + addr.len;
+
+ for ( i = 0; i < 4; )
+ {
+ uint temp;
+ ccp next = ScanNumber(&temp,ptr,end,10,10);
+ if ( !next || next == ptr )
+ break;
+ num[i++] = temp;
+ addr = BehindMem(addr,next);
+ ptr = addr.ptr;
+ if ( *ptr != '.' )
+ break;
+ ptr++;
+ }
+
+ uint ip4;
+ switch (i)
+ {
+ case 1:
+ ip4 = num[0];
+ break;
+
+ case 2:
+ ip4 = num[0] << 24 | num[1];
+ break;
+
+ case 3:
+ ip4 = num[0] << 24 | num[1] << 16 | num[2];
+ break;
+
+ case 4:
+ ip4 = num[0] << 24 | num[1] << 16 | num[2] << 8 | num[3];
+ break;
+
+ default:
+ goto abort;
+ }
+ if (r_ipv4)
+ *r_ipv4 = ip4;
+
+ if (r_port)
+ {
+ ptr = addr.ptr;
+ if ( ptr < end && *ptr == ':' )
+ {
+ ptr++;
+ uint num;
+ ccp next = ScanNumber(&num,ptr,end,10,10);
+ if ( next > ptr && num < 0x10000 )
+ {
+ addr = BehindMem(addr,next);
+ default_port = num;
+ }
+ }
+ *r_port = default_port;
+ }
+
+ return addr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Returns a NETWORK MASK "/a.b.c.d" or as CIDR number "/num" between 0 and 32.
+// An optional slash '/' at the beginning is skipped.
+// Returns modified 'source' if a MASK or CDIR is detected.
+// If no one is detected, source is unmodified and returned mask = ~0.
+
+mem_t ScanNetworkMaskMem ( u32 *mask, mem_t source )
+{
+ u32 m = M1(m);
+ if ( source.ptr && source.len )
+ {
+ ccp ptr = source.ptr;
+ ccp end = ptr + source.len;
+ if ( *ptr == '/' )
+ ptr++;
+
+ uint num;
+ ccp next = ScanNumber(&num,ptr,end,10,10);
+ if ( next > ptr )
+ {
+ if ( next < end && *next == '.' )
+ {
+ // is a network mask
+ source = ScanNumericIP4Mem(BehindMem(source,ptr),&m,0,0);
+ }
+ else if ( num <= 32 )
+ {
+ m = num ? ~(( 1 << (32-num) ) - 1) : 0;
+ source = BehindMem(source,next);
+ }
+ }
+ }
+
+ if (mask)
+ *mask = m;
+ return source;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int ConnectNumericTCP
+(
+ // like ConnectTCP(), but without name resolution (only numeric ip+port)
+
+ ccp addr, // TCP address: ['tcp':] IPv4 [:PORT]
+ int default_port, // NULL or default port, if not found in 'addr'
+ bool silent // true: suppress error messages
+)
+{
+ if (ConnectTCP_Hook)
+ return ConnectTCP_Hook(addr,default_port,silent);
+
+ ccp unix_path = CheckUnixSocketPath(addr,0);
+ if (unix_path)
+ return ConnectUnixTCP(unix_path,silent);
+
+ if ( !strncasecmp(addr,"tcp:",4) )
+ addr += 4;
+
+ u32 ip4, port;
+ ccp end = ScanNumericIP4(addr,&ip4,&port,default_port);
+ if (!end)
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_CONNECT,
+ "Invalid IPv4 address: %s\n",addr);
+ return -1;
+ }
+
+ int sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
+ if ( sock == -1 )
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_CONNECT,
+ "Can't create socket: %s\n",addr);
+ return -1;
+ }
+
+ struct sockaddr_in sa;
+ sa.sin_family = AF_INET;
+ sa.sin_addr.s_addr = htonl(ip4);
+ sa.sin_port = htons(port);
+
+ if (connect(sock,(struct sockaddr *)&sa,sizeof(sa)))
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_CONNECT,
+ "Can't connect to %s\n",PrintIP4(0,0,ip4,port) );
+ close(sock);
+ return -1;
+ }
+
+ return sock;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct File_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeFile
+(
+ File_t * f // file structure
+)
+{
+ DASSERT(f);
+ memset(f,0,sizeof(*f));
+ f->fname = EmptyString;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError ResetFile
+(
+ File_t * f, // file structure
+ uint set_time // 0: don't set
+ // 1: set time before closing using 'fatt'
+ // 2: set current time before closing
+)
+{
+ DASSERT(f);
+ const enumError err = CloseFile(f,set_time);
+ FreeString(f->fname);
+ if (f->data_alloced)
+ FREE(f->data);
+ InitializeFile(f);
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError CloseFile
+(
+ File_t * f, // file structure
+ uint set_time // 0: don't set
+ // 1: set time before closing using 'fatt'
+ // 2: set current time before closing
+)
+{
+ DASSERT(f);
+ TRACE("CloseFile(%p,%d) fname=%s\n",f,set_time,f->fname);
+
+ if (f->f)
+ {
+ if (!f->is_stdio)
+ {
+ if ( fclose(f->f) && f->max_err <= ERR_WARNING )
+ f->max_err = ERROR1(ERR_WRITE_FAILED,
+ "Error while closing file: %s\n",f->fname);
+
+ if (!f->is_socket)
+ {
+ if ( f->fmode & FM_TEMP )
+ {
+ TRACE("UNLINK TEMP %s\n",f->fname);
+ unlink(f->fname);
+ }
+ else if ( f->is_writing )
+ {
+ if ( f->max_err > ERR_WARNING )
+ {
+ TRACE("UNLINK %s\n",f->fname);
+ unlink(f->fname);
+ }
+ else if ( set_time == 1 && f->fatt.mtime )
+ {
+ struct utimbuf ubuf;
+ ubuf.actime = f->fatt.atime
+ ? f->fatt.atime : f->fatt.mtime;
+ ubuf.modtime = f->fatt.mtime;
+ utime(f->fname,&ubuf);
+ }
+ else if ( set_time > 1 || f->fmode & FM_TOUCH )
+ {
+ utime(f->fname,0);
+ }
+ }
+ }
+ }
+ f->f = 0;
+ }
+
+ return f->max_err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+static bool is_seekable
+(
+ File_t * F // file structure
+)
+{
+ const mode_t mode = F->st.st_mode;
+ F->is_seekable = ( S_ISREG(mode) || S_ISBLK(mode) || S_ISCHR(mode) )
+ && F->st.st_size
+ && lseek(fileno(F->f),0,SEEK_SET) != (off_t)-1;
+ return F->is_seekable;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError OpenFile
+(
+ File_t * f, // file structure
+ bool initialize, // true: initialize 'f'
+ ccp fname, // file to open
+ FileMode_t file_mode, // open modes
+ off_t limit, // >0: don't open, if file size > limit
+ ccp limit_message // NULL or a with LF terminated text.
+ // It is printed after the message.
+)
+{
+ DASSERT(f);
+ DASSERT(fname);
+
+ if (initialize)
+ InitializeFile(f);
+ else
+ ResetFile(f,false);
+ f->fmode = file_mode & FM_M_OPEN;
+
+ TRACE("OpenFile(%p,%d,,%s,%llu) [%s] %s\n",
+ f, initialize, GetFileModeStatus(0,0,file_mode,0), (u64)limit,
+ GetFileModeStatus(0,0,f->fmode,1), fname );
+
+
+ //--- test for TCP
+
+ if ( file_mode & FM_TCP && !strncasecmp(fname,"tcp:",4) )
+ {
+ const int fd = ConnectNumericTCP(fname,0,(f->fmode&FM_SILENT)!=0);
+ if ( fd == -1 )
+ return f->max_err = ERR_CANT_OPEN;
+
+ f->f = fdopen(fd,"rw");
+ f->is_socket = f->is_reading = f->is_writing = true;
+ return f->max_err;
+ }
+
+
+ //--- test for stdin
+
+ if ( *fname == '-' && !fname[1] )
+ {
+ f->fname = MinusString;
+ if ( f->fmode & FM_STDIO )
+ {
+ f->is_stdio = true;
+ f->f = stdin;
+ fstat(fileno(stdin),&f->st);
+ is_seekable(f);
+ return ERR_OK;
+ }
+ }
+
+ f->fname = STRDUP(fname);
+
+
+ //--- test 'unix:' prefix
+
+ bool have_unix = file_mode & FM_TCP && !strncasecmp(fname,"unix:",5);
+ if (have_unix)
+ fname += 5;
+
+
+ //--- does file exist?
+
+ for(;;)
+ {
+ if (!stat(fname,&f->st))
+ break;
+
+ if (have_unix)
+ {
+ have_unix = false;
+ fname -= 5;
+ continue;
+ }
+
+ not_found:
+ memset(&f->st,0,sizeof(f->st));
+ if ( f->fmode & FM_IGNORE )
+ return f->max_err = ERR_NOT_EXISTS;
+
+ if ( !(f->fmode & FM_SILENT) )
+ ERROR1(ERR_CANT_OPEN,"Can't find file: %s\n",fname);
+ return f->max_err = ERR_CANT_OPEN;
+ }
+
+
+ //--- UNIX socket -> try STREAM connect
+
+ if (S_ISSOCK(f->st.st_mode))
+ {
+ f->fmode &= ~(FM_REMOVE|FM_TEMP);
+
+ const int fd = ConnectUnixTCP(fname,(f->fmode&FM_SILENT)!=0);
+ if ( fd == -1 )
+ return f->max_err = ERR_CANT_OPEN;
+
+ f->f = fdopen(fd,"rw");
+ f->is_socket = f->is_reading = f->is_writing = true;
+ return f->max_err;
+ }
+
+ if (have_unix)
+ goto not_found;
+
+
+ //--- check size limit
+
+ SetFileAttrib(&f->fatt,0,&f->st);
+ if ( limit && f->st.st_size > limit )
+ {
+ char sbuf[12], lbuf[12];
+ PrintSize1024(sbuf,sizeof(sbuf),(u64)f->st.st_size,false);
+ PrintSize1024(lbuf,sizeof(lbuf),(u64)limit,false);
+
+ // be tolerant, if both values produce same text
+ if (strcmp(sbuf,lbuf))
+ {
+ if ( !(f->fmode & FM_SILENT) )
+ ERROR0(ERR_CANT_OPEN,
+ "File too large (size=%s, limit=%s): %s\n%s",
+ sbuf, lbuf, fname,
+ limit_message ? limit_message : "" );
+ return f->max_err = ERR_CANT_OPEN;
+ }
+ }
+
+
+ //--- open file
+
+ ccp omode = GetFileOpenMode(false,f->fmode);
+ f->f = fopen(fname,omode);
+ TRACE("OpenFile(%s,%s) : [%s], %p\n",
+ fname, omode, GetFileModeStatus(0,0,f->fmode,1), f->f );
+
+ if (!f->f)
+ {
+ if ( !(f->fmode & FM_SILENT) )
+ ERROR1(ERR_CANT_OPEN,"Can't open file: %s\n",fname);
+ return f->max_err = ERR_CANT_OPEN;
+ }
+
+ f->is_reading = true;
+ if ( f->fmode & FM_MODIFY )
+ f->is_writing = true;
+
+ is_seekable(f);
+ return f->max_err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError CheckCreateFile
+(
+ // returns:
+ // ERR_DIFFER: [FM_STDIO] source is "-" => 'st' is zeroed
+ // ERR_WARNING: [FM_DEV,FM_SOCK,FM_SPC] not a regular file
+ // ERR_WRONG_FILE_TYPE: file exists, but is not a regular file
+ // ERR_ALREADY_EXISTS: file already exists
+ // ERR_INVALID_VERSION: already exists, but FM_NUMBER set => no msg printed
+ // ERR_CANT_CREATE: FM_UPDATE is set, but file don't exist
+ // ERR_OK: file not exist or can be overwritten
+
+ ccp fname, // filename to open
+ FileMode_t file_mode, // open modes
+ struct stat *st // not NULL: store file status here
+)
+{
+ DASSERT(fname);
+
+ if ( file_mode & FM_STDIO && fname[0] == '-' && !fname[1] )
+ {
+ if (st)
+ memset(st,0,sizeof(*st));
+ return ERR_DIFFER;
+ }
+
+ struct stat local_st;
+ if (!st)
+ st = &local_st;
+
+ if (!stat(fname,st))
+ {
+ if ( S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode) )
+ {
+ if ( file_mode & FM_DEV )
+ return ERR_WARNING;
+
+ if ( !(file_mode & FM_SILENT) )
+ ERROR0( ERR_ALREADY_EXISTS,
+ "Can't write to %s device: %s\n",
+ S_ISBLK(st->st_mode) ? "block" : "character", fname );
+ return ERR_WRONG_FILE_TYPE;
+ }
+
+ if ( S_ISSOCK(st->st_mode) )
+ {
+ if ( file_mode & FM_SOCK )
+ return ERR_WARNING;
+
+ if ( !(file_mode & FM_SILENT) )
+ ERROR0( ERR_ALREADY_EXISTS,
+ "Can't write to UNIX socket: %s\n", fname );
+ return ERR_WRONG_FILE_TYPE;
+ }
+
+ if (!S_ISREG(st->st_mode))
+ {
+ if ( file_mode & FM_SPC )
+ return ERR_WARNING;
+
+ if ( !(file_mode & FM_SILENT) )
+ ERROR0( ERR_WRONG_FILE_TYPE,
+ "Not a plain file: %s\n", fname );
+ return ERR_WRONG_FILE_TYPE;
+ }
+
+ if (!( file_mode & (FM_OVERWRITE|FM_REMOVE|FM_UPDATE|FM_APPEND) ))
+ {
+ if ( file_mode & FM_NUMBER )
+ return ERR_INVALID_VERSION;
+
+ if ( !(file_mode & FM_SILENT) )
+ ERROR0( ERR_ALREADY_EXISTS,
+ "File already exists: %s\n", fname );
+ return ERR_ALREADY_EXISTS;
+ }
+ }
+ else
+ {
+ memset(st,0,sizeof(*st));
+ if ( file_mode & FM_UPDATE )
+ {
+ if ( !(file_mode & FM_SILENT) )
+ ERROR0(ERR_CANT_CREATE,
+ "Try to update non existing file: %s\n", fname );
+ return ERR_CANT_CREATE;
+ }
+ }
+
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError CreateFile
+(
+ File_t * f, // file structure
+ bool initialize, // true: initialize 'f'
+ ccp fname, // file to open
+ FileMode_t file_mode // open modes
+)
+{
+ DASSERT(f);
+ DASSERT(fname);
+
+ if (initialize)
+ InitializeFile(f);
+ else
+ ResetFile(f,false);
+ f->fmode = file_mode & FM_M_CREATE;
+
+ noPRINT("CreateFile(%p,%d,,%s) [%s] %s\n",
+ f, initialize, GetFileModeStatus(0,0,file_mode,0),
+ GetFileModeStatus(0,0,f->fmode,1), fname );
+
+
+ //--- test for stdout
+
+ if ( *fname == '-' && !fname[1] )
+ {
+ f->fname = MinusString;
+ if ( f->fmode & FM_STDIO )
+ {
+ f->is_stdio = true;
+ f->f = stdout;
+ fstat(fileno(stdout),&f->st);
+ is_seekable(f);
+ return ERR_OK;
+ }
+ }
+
+
+ //--- mode calculations
+
+// if ( f->fmode & FM_UPDATE )
+// f->fmode = f->fmode & ~(FM_REMOVE|FM_MKDIR) | FM_OVERWRITE;
+
+ enumError err = CheckCreateFile(fname,f->fmode,&f->st);
+
+ char num_fname[PATH_MAX];
+ if ( err == ERR_INVALID_VERSION )
+ {
+ NumberedFilename(num_fname,sizeof(num_fname),fname,0,0,false);
+ fname = num_fname;
+ err = CheckCreateFile( fname, f->fmode & ~FM_NUMBER, &f->st );
+ }
+
+ f->fname = STRDUP(fname);
+ if ( f->fmode & (FM_MODIFY|FM_APPEND) )
+ SetFileAttrib(&f->fatt,0,&f->st);
+
+ if (err)
+ {
+ if ( err > ERR_WARNING )
+ return err;
+
+ // is a device, socket or special file
+ err = ERR_OK;
+ f->fmode = f->fmode & ~(FM_REMOVE|FM_TEMP) | FM_OVERWRITE;
+ }
+
+ if ( f->fmode & FM_TEST )
+ {
+ // never create or remove a file in TEST mode.
+ return ERR_OK;
+ }
+
+ if (S_ISSOCK(f->st.st_mode))
+ {
+ const int fd = ConnectUnixTCP(fname,(f->fmode&FM_SILENT)!=0);
+ if ( fd == -1 )
+ return f->max_err = ERR_CANT_OPEN;
+
+ f->f = fdopen(fd,"rw");
+ f->is_socket = f->is_reading = f->is_writing = true;
+ return ERR_OK;
+ }
+
+ if ( f->st.st_mode && f->fmode & FM_REMOVE )
+ {
+ unlink(fname);
+
+ struct stat st;
+ if (!stat(fname,&st))
+ {
+ if ( !(f->fmode & FM_SILENT) )
+ ERROR0(ERR_REMOVE_FAILED,"Can't remove file: %s\n",fname);
+ return f->max_err = ERR_REMOVE_FAILED;
+ }
+ }
+
+ ccp omode = GetFileOpenMode(true,f->fmode);
+ f->f = fopen(fname,omode);
+ noPRINT("CreateFile(%s,%s) : [%s], %p\n",
+ fname, omode, GetFileModeStatus(0,0,f->fmode,1), f->f );
+
+ if ( !f->f )
+ {
+ if ( f->fmode & FM_MKDIR )
+ {
+ CreatePath(fname,false);
+ f->f = fopen(fname,omode);
+ }
+
+ if ( !f->f )
+ {
+ if ( !(f->fmode & FM_SILENT) )
+ ERROR1(ERR_CANT_CREATE,"Can't create file: %s\n",fname);
+ return f->max_err = ERR_CANT_CREATE;
+ }
+ }
+
+ f->is_writing = true;
+ if ( f->fmode & FM_MODIFY)
+ f->is_reading = true;
+
+ if (!fstat(fileno(f->f),&f->st))
+ {
+ const mode_t mode = f->st.st_mode;
+ f->is_seekable = S_ISREG(mode) || S_ISBLK(mode) || S_ISCHR(mode);
+ }
+
+ return f->max_err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError WriteFileAt
+(
+ File_t * F, // file to write
+ size_t * cur_offset, // pointer to current file offset, modified
+ size_t offset, // offset to write
+ const void * data, // data to write
+ size_t size // size of 'data'
+)
+{
+ DASSERT(F);
+ DASSERT(F->f);
+ DASSERT(data||!size);
+ DASSERT(cur_offset);
+
+ if (!size)
+ return ERR_OK;
+
+ if ( offset != *cur_offset )
+ {
+ if (F->is_seekable)
+ {
+ noPRINT("FSEEK %zx -> %zx\n",*cur_offset,offset);
+ if (fseek(F->f,offset,SEEK_SET))
+ return ERROR1(ERR_WRITE_FAILED,"Can't set file pointer: %s\n",F->fname);
+ }
+ else if ( offset < *cur_offset )
+ {
+ noPRINT("FSEEK %zx -> %zx\n",*cur_offset,offset);
+ return ERROR0(ERR_WRITE_FAILED,
+ "Can't set file pointer on non seekable file: %s\n",F->fname);
+ }
+ else
+ {
+ char buf[0x8000] = {0};
+ size_t fill_size = offset - *cur_offset;
+ while (fill_size)
+ {
+ const size_t write_size = fill_size < sizeof(buf) ? fill_size : sizeof(buf);
+ noPRINT("FILL/ZERO %zx/%zx\n",write_size,fill_size);
+ const size_t written = fwrite(buf,1,write_size,F->f);
+ if ( written != write_size )
+ return ERROR1(ERR_WRITE_FAILED,"Writing %zu NULL bytes failed: %s\n",
+ write_size, F->fname );
+ fill_size -= write_size;
+ }
+ }
+ }
+
+ noPRINT("WRITE %zx @%zx -> %zx\n",size,offset,offset+size);
+ const size_t written = fwrite(data,1,size,F->f);
+ if ( written != size )
+ return ERROR1(ERR_WRITE_FAILED,"Writing %zu bytes at offset %zu failed: %s\n",
+ size,offset,F->fname);
+
+ *cur_offset = offset + size;
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError SetFileSize
+(
+ File_t * F, // file to write
+ size_t * cur_offset, // pointer to current file offset, modified
+ size_t size // offset to write
+)
+{
+ DASSERT(F);
+ DASSERT(F->f);
+ DASSERT(cur_offset);
+
+ fflush(F->f);
+ if (!F->is_seekable)
+ return WriteFileAt(F,cur_offset,size,0,0);
+
+ if (ftruncate(fileno(F->f),size))
+ {
+ if ( F->max_err < ERR_WRITE_FAILED )
+ F->max_err = ERR_WRITE_FAILED;
+ return ERROR1( ERR_WRITE_FAILED,
+ "Set file size to %llu failed: %s\n",
+ (u64)size, F->fname );
+ }
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError SkipFile
+(
+ File_t *F, // file to write
+ size_t skip // number of bytes to skip
+)
+{
+ DASSERT(F);
+ DASSERT(F->f);
+
+ if (!skip)
+ return ERR_OK;
+
+ if ( F->is_seekable && !fseek(F->f,skip,SEEK_CUR) )
+ return ERR_OK;
+
+ char buf[0x8000];
+ while ( skip > 0 )
+ {
+ size_t max = skip < sizeof(buf) ? skip : sizeof(buf);
+ size_t stat = fread(buf,1,max,F->f);
+ if (!stat)
+ {
+ if (feof(F->f))
+ break;
+ return ERROR1(ERR_READ_FAILED,
+ "Reading %zu to skip failed: %s\n",
+ skip, F->fname );
+ }
+ skip -= stat;
+ }
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError RegisterFileError
+(
+ File_t * f, // file structure
+ enumError new_error // new error code
+)
+{
+ DASSERT(f);
+ if ( f->max_err < new_error )
+ f->max_err = new_error;
+ return f->max_err;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+s64 GetFileSize
+(
+ ccp path1, // NULL or part 1 of path
+ ccp path2, // NULL or part 2 of path
+ s64 not_found_val, // return value if no regular file found
+ FileAttrib_t * fatt, // not NULL: store file attributes
+ bool fatt_max // true: store max values to 'fatt'
+)
+{
+ char pathbuf[PATH_MAX];
+ ccp path = PathCatPP(pathbuf,sizeof(pathbuf),path1,path2);
+ TRACE("GetFileSize(%s,%lld)\n",path,not_found_val);
+
+ struct stat st;
+ if ( stat(path,&st) || !S_ISREG(st.st_mode) )
+ {
+ if ( fatt && !fatt_max )
+ memset(fatt,0,sizeof(*fatt));
+ return not_found_val;
+ }
+
+ if (fatt)
+ {
+ if (fatt_max)
+ MaxFileAttrib(fatt,0,&st);
+ else
+ SetFileAttrib(fatt,0,&st);
+ }
+
+ return st.st_size;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError OpenReadFile
+(
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ FileMode_t file_mode, // open modes
+ off_t limit, // >0: don't open, if file size > limit
+ ccp limit_message, // NULL or a with LF terminated text.
+ // It is printed after the message.
+
+ u8 ** res_data, // store alloced data here (always NULL terminated)
+ uint * res_size, // not NULL: store data size
+ ccp * res_fname, // not NULL: store alloced filename
+ FileAttrib_t * res_fatt // not NULL: store file attributes
+)
+{
+ DASSERT( path1 || path2 );
+ DASSERT(res_data);
+
+ *res_data = 0;
+ if (res_size)
+ *res_size = 0;
+ if (res_fname)
+ *res_fname = 0;
+ if (res_fatt)
+ memset(res_fatt,0,sizeof(*res_fatt));
+
+ char pathbuf[PATH_MAX];
+ ccp path = PathCatPP(pathbuf,sizeof(pathbuf),path1,path2);
+ PRINT("OpenReadFile(%s,%s)\n",path,GetFileModeStatus(0,0,file_mode,0));
+
+ File_t F;
+ enumError err = OpenFile(&F,true,path,file_mode,limit,limit_message);
+ if (err)
+ return err;
+
+ uint data_size = F.st.st_size;
+ if (!F.is_seekable)
+ data_size = limit;
+ noPRINT("OpenReadFile() fsize=%u, seekable=%d\n",data_size,F.is_seekable);
+ u8 *data = MALLOC(data_size+1);
+
+ size_t read_size = fread(data,1,data_size,F.f);
+ if ( read_size && !F.is_seekable )
+ {
+ PRINT("non seekable read: %zu\n",read_size);
+ data_size = read_size;
+ data = REALLOC(data,data_size+1);
+ }
+ data[data_size] = 0; // termination for text files
+
+ if ( read_size != data_size )
+ {
+ if (!(file_mode&FM_SILENT))
+ FILEERROR1(&F,ERR_READ_FAILED,"Read failed: %s\n",path);
+ ResetFile(&F,false);
+ FREE(data);
+ return ERR_READ_FAILED;
+ }
+
+ *res_data = data;
+ if (res_size)
+ *res_size = data_size;
+ if (res_fname)
+ {
+ *res_fname = F.fname;
+ F.fname = EmptyString;
+ }
+ if (res_fatt)
+ SetFileAttrib(res_fatt,&F.fatt,0);
+
+ ResetFile(&F,false);
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError LoadFile
+(
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ size_t skip, // skip num of bytes before reading
+ void * data, // destination buffer, size = 'size'
+ size_t size, // size to read
+ int silent, // 0: print all error messages
+ // 1: suppress file size warning
+ // 2: suppress all error messages
+ FileAttrib_t * fatt, // not NULL: store file attributes
+ bool fatt_max // true: store *max* values to 'fatt'
+)
+{
+ ASSERT(data);
+ if (!size)
+ return ERR_OK;
+
+ char pathbuf[PATH_MAX];
+ ccp path = PathCatPP(pathbuf,sizeof(pathbuf),path1,path2);
+ TRACE("LoadFile(%s,%zu,%zu,%d)\n",path,skip,size,silent);
+
+ if (fatt)
+ {
+ struct stat st;
+ if (!stat(path,&st))
+ UseFileAttrib(fatt,0,&st,fatt_max);
+ }
+
+ FILE * f = fopen(path,"rb");
+ if (!f)
+ {
+ if (silent<2)
+ ERROR1(ERR_CANT_OPEN,"Can't open file: %s\n",path);
+ return ERR_CANT_OPEN;
+ }
+
+ if ( skip > 0 )
+ fseek(f,skip,SEEK_SET);
+
+ size_t read_stat = fread(data,1,size,f);
+ fclose(f);
+
+ if ( read_stat == size )
+ return ERR_OK;
+
+ enumError err = ERR_READ_FAILED;
+ if ( silent == 1 && size > 0 )
+ err = ERR_WARNING;
+ else if ( silent < 2 )
+ ERROR1(ERR_READ_FAILED,"Can't read file: %s\n",path);
+
+ noPRINT("D=%p, s=%zu/%zu: %s\n",data,read_stat,size,path);
+ if ( read_stat >= 0 && read_stat < size )
+ memset((char*)data+read_stat,0,size-read_stat);
+
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError LoadFileAlloc
+(
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ size_t skip, // skip num of bytes before reading
+ u8 ** res_data, // result: free existing data, store ptr to alloc data
+ // always one more byte is alloced and set to NULL
+ size_t * res_size, // result: size of 'res_data'
+ size_t max_size, // >0: a file size limit
+ int silent, // 0: print all error messages
+ // 1: suppress file size warning
+ // 2: suppress all error messages
+ FileAttrib_t * fatt, // not NULL: store file attributes
+ bool fatt_max // true: store max values to 'fatt'
+)
+{
+ DASSERT(res_data);
+ DASSERT(res_size);
+
+ //--- clear return data
+
+ if (res_data)
+ *res_data = 0;
+
+ if (res_size)
+ *res_size = 0;
+
+ if ( fatt && !fatt_max )
+ memset(fatt,0,sizeof(*fatt));
+
+
+ //--- get file size
+
+ char pathbuf[PATH_MAX];
+ ccp path = PathCatPP(pathbuf,sizeof(pathbuf),path1,path2);
+
+ const s64 size = GetFileSize(path,0,-1,0,0);
+ if ( size == -1 )
+ {
+ if ( silent < 2 )
+ ERROR0(ERR_SYNTAX,"File not found: %s\n",path);
+ return ERR_SYNTAX;
+ }
+
+ if ( max_size && size > max_size )
+ {
+ if ( silent < 2 )
+ ERROR0(ERR_INVALID_FILE,"File too large: %s\n",path);
+ return ERR_INVALID_FILE;
+ }
+
+ u8 *data = MALLOC(size+1);
+ enumError err = LoadFile(path,0,skip,data,size,silent,fatt,fatt_max);
+ if (err)
+ {
+ FREE(data);
+ return err;
+ }
+
+ if (res_data)
+ {
+ data[size] = 0;
+ *res_data = data;
+ }
+ else
+ FREE(data);
+
+ if (res_size)
+ *res_size = size;
+
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError SaveFile
+(
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ FileMode_t file_mode, // open modes
+ const void * data, // data to write
+ uint data_size, // size of 'data'
+ FileAttrib_t * fatt // not NULL: set timestamps using this attrib
+)
+{
+ DASSERT(data);
+
+ File_t F;
+ enumError err = OpenWriteFile(&F,true,path1,path2,file_mode,data,data_size);
+ if ( err == ERR_OK )
+ {
+ if (fatt)
+ memcpy(&F.fatt,fatt,sizeof(F.fatt));
+ err = ResetFile(&F,true);
+ }
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError OpenWriteFile
+(
+ File_t * f, // file structure
+ bool initialize, // true: initialize 'f'
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ FileMode_t file_mode, // open modes
+ const void * data, // data to write
+ uint data_size // size of 'data'
+)
+{
+ DASSERT(f);
+ DASSERT(data);
+
+ char pathbuf[PATH_MAX];
+ ccp path = PathCatPP(pathbuf,sizeof(pathbuf),path1,path2);
+ TRACE("SaveFile(%s) %s\n",GetFileModeStatus(0,0,file_mode,0),path);
+
+ enumError err = CreateFile(f,true,path,file_mode);
+ if ( err || !f->f )
+ {
+ ResetFile(f,0);
+ return err;
+ }
+
+ const uint written = fwrite(data,1,data_size,f->f);
+ if ( written != data_size )
+ {
+ ERROR1(ERR_WRITE_FAILED,"Write to file failed: %s\n",path);
+ ResetFile(f,0);
+ return ERR_WRITE_FAILED;
+ }
+
+ return ERR_OK;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+static int CompareInt ( const int * a, const int *b )
+{
+ DASSERT( a && b );
+ return *a < *b ? -1 : *a > *b;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint CloseAllExcept
+(
+ int *list, // exception list of unordered FD, max contain -1
+ uint n_list, // number of elements in 'list'
+ int min, // close all until and including this fd
+ int add // if a FD was found, incremet 'min' to 'fd+add'
+)
+{
+ //--- use a private copy of the except list
+
+ enum { LOCAL_LIST_SIZE = 1000 };
+ int local_list[LOCAL_LIST_SIZE];
+ int *except_list = n_list < LOCAL_LIST_SIZE
+ ? local_list : MALLOC((n_list+1)*sizeof(*except_list));
+
+ int *ptr = except_list;
+ while ( n_list-- > 0 )
+ {
+ const int fd = *list++;
+ if ( fd != -1 )
+ *ptr++ = fd;
+ }
+ *ptr = -1;
+
+ //--- ... and sort it
+
+ n_list = ptr - except_list;
+ if ( n_list > 1 )
+ qsort(except_list,n_list,sizeof(*except_list),(qsort_func)CompareInt);
+
+
+ //--- adjust 'min' and 'add'
+
+ if ( add <= 0 )
+ add = 1;
+ const int temp = ( n_list ? except_list[n_list-1] : 0 ) + add;
+ if ( min < temp )
+ min = temp;
+
+
+ //--- close files
+
+ ptr = except_list;
+ int *end = ptr + n_list;
+ int i, count = 0;
+ for ( i = 0; i <= min; i++ )
+ {
+ while ( ptr < end && *ptr < i )
+ ptr++;
+ if ( i != *ptr && !close(i) )
+ {
+ count++;
+ const int temp = i + add;
+ if ( min < temp )
+ min = temp;
+ //printf("closed: %d/%d\n",i,min);
+ }
+ }
+
+ if ( except_list != local_list )
+ FREE(local_list);
+ return count;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// CopyFile*(), TransferFile() ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#undef LOGPRINT
+//#define LOGPRINT PRINT
+#define LOGPRINT noPRINT
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool IsSameFile ( ccp path1, ccp path2 )
+{
+ if (!strcmp(path1,path2))
+ return true;
+
+ struct stat st1, st2;
+ if ( stat(path1,&st1) || stat(path2,&st2) )
+ return false;
+
+ return st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError CopyFileHelper
+(
+ ccp src, // source path
+ ccp dest, // destination path
+ int open_flags, // flags for open()
+ mode_t open_mode, // mode for open()
+ uint temp_and_move // >0: ignore open_flags,
+ // create temp, then move temp to dest
+)
+{
+ LOGPRINT("CopyFileHelper(%x,%x,%d) %s -> %s\n",
+ open_flags, open_mode, temp_and_move>0, src, dest );
+
+ int fd_dest, fd_src;
+ char buf[16*1024], temp[PATH_MAX];
+
+ if ( !dest || !src || !*dest || !*src )
+ return ERR_MISSING_PARAM;
+
+ if (IsSameFile(src,dest))
+ return ERR_OK;
+
+ fd_src = open(src,O_RDONLY);
+ if ( fd_src < 0 )
+ return ERR_CANT_OPEN;
+
+ if (temp_and_move)
+ {
+ StringCat2S(temp,sizeof(temp),dest,"XXXXXX");
+ fd_dest = mkstemp(temp);
+ LOGPRINT("CopyFileHelper(TEMP) %s -> %d\n",temp,fd_dest);
+ }
+ else
+ fd_dest = open(dest,open_mode,open_mode);
+ if ( fd_dest < 0 )
+ goto error;
+
+ ssize_t nread;
+ while ( nread = read(fd_src,buf,sizeof(buf)), nread > 0 )
+ {
+ char *ptr = buf;
+ do
+ {
+ ssize_t nwritten = write(fd_dest,ptr,nread);
+
+ if ( nwritten >= 0 )
+ {
+ nread -= nwritten;
+ ptr += nwritten;
+ }
+ else if ( errno != EINTR )
+ goto error;
+ }
+ while (nread > 0);
+ }
+
+ if ( nread == 0 )
+ {
+ if ( close(fd_dest) < 0 )
+ {
+ fd_dest = -1;
+ goto error;
+ }
+ if (temp_and_move)
+ {
+ chmod(temp,open_mode);
+ if (rename(temp,dest))
+ {
+ unlink(temp);
+ goto error;
+ }
+ }
+ close(fd_src);
+ return ERR_OK;
+ }
+
+ error:;
+ int saved_errno = errno;
+
+ close(fd_src);
+ if ( fd_dest >= 0 )
+ {
+ close(fd_dest);
+ if (temp_and_move)
+ unlink(temp);
+ }
+
+ errno = saved_errno;
+ return ERR_WRITE_FAILED;
+}
+
+//-----------------------------------------------------------------------------
+
+enumError CopyFile
+(
+ ccp src, // source path
+ ccp dest, // destination path
+ mode_t open_mode // mode for open()
+)
+{
+ return CopyFileHelper(src,dest,O_WRONLY|O_TRUNC|O_EXCL,open_mode,false);
+}
+
+//-----------------------------------------------------------------------------
+
+enumError CopyFileCreate
+(
+ ccp src, // source path
+ ccp dest, // destination path
+ mode_t open_mode // mode for open()
+)
+{
+ return CopyFileHelper(src,dest,O_WRONLY|O_CREAT|O_EXCL,open_mode,false);
+}
+
+//-----------------------------------------------------------------------------
+
+enumError CopyFileTemp
+(
+ ccp src, // source path
+ ccp dest, // destination path
+ mode_t open_mode // mode for open()
+)
+{
+ return CopyFileHelper(src,dest,0,open_mode,true);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError TransferFile
+(
+ ccp src, // source path
+ ccp dest, // destination path
+ TransferMode_t tfer_mode, // transfer mode
+ mode_t open_mode // mode for CopyFile*() -> open()
+)
+{
+ if ( !dest || !src || !*dest || !*src )
+ return ERR_MISSING_PARAM;
+
+ if (IsSameFile(src,dest))
+ return ERR_OK;
+
+ if ( tfer_mode & TFMD_J_MOVE1 )
+ {
+ LOGPRINT("TransferFile(MOVE1) %s -> %s\n",src,dest);
+ struct stat st;
+ if ( !stat(src,&st) && st.st_nlink == 1 && !rename(src,dest) )
+ return true;
+ }
+ else if ( tfer_mode & TFMD_J_MOVE )
+ {
+ LOGPRINT("TransferFile(MOVE) %s -> %s\n",src,dest);
+ if (!rename(src,dest))
+ return true;
+ }
+
+ if ( tfer_mode & TFMD_J_LINK )
+ {
+ LOGPRINT("TransferFile(RM_DEST) %s\n",dest);
+ if ( tfer_mode & TFMD_J_RM_DEST )
+ unlink(dest);
+
+ LOGPRINT("TransferFile(LINK) %s -> %s\n",src,dest);
+ if (!link(src,dest))
+ return true;
+ }
+
+ if ( tfer_mode & TFMD_J_COPY )
+ {
+ LOGPRINT("TransferFile(COPY) %s -> %s\n",src,dest);
+ enumError err = tfer_mode & TFMD_J_RM_DEST
+ ? CopyFileTemp(src,dest,open_mode)
+ : CopyFile(src,dest,open_mode);
+ if ( err == ERR_OK )
+ {
+ if ( tfer_mode & TFMD_J_RM_SRC )
+ {
+ PRINT("TransferFile(RM_SRC) %s",src);
+ unlink(src);
+ }
+ return true;
+ }
+ }
+
+ return ERR_ERROR;
+}
+
+#undef LOGPRINT
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct MemFile_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool IsValidMemFile ( MemFile_t *mf )
+{
+ return mf
+ && ( mf->data || !mf->size )
+ && mf->size >= mf->fend
+ && mf->fend >= mf->fpos;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void DumpMemFile
+(
+ FILE *f, // valid output stream
+ int indent, // indent of output
+ MemFile_t *mf // valid MemFile_t data
+)
+{
+ DASSERT(f);
+ DASSERT(mf);
+
+ indent = NormalizeIndent(indent);
+ fprintf(f,"%*sfpos=0x%x/0x%x, size=0x%x/0x%x, zero=%d, valid=%d\n",
+ indent,"",
+ mf->fpos, mf->fend, mf->size, mf->max_size,
+ mf->zero_extend, IsValidMemFile(mf) );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool AssertMemFile
+(
+ MemFile_t *mf // valid MemFile_t data
+)
+{
+ if (IsValidMemFile(mf))
+ return true;
+
+ if (mf)
+ DumpMemFile(stderr,0,mf);
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+u8 * GetDataMemFile
+(
+ // return NULL on err or a pointer to the data posisiton.
+
+ MemFile_t *mf, // valid MemFile_t data
+ uint fpos, // fpos to read from
+ uint size // needed data size
+)
+{
+ DASSERT(mf);
+ uint end = fpos + size;
+ if ( end < fpos || end < size )
+ return 0; // integer overflow
+
+ if ( end > mf->max_size )
+ return 0; // too large
+
+ if ( end > mf->size )
+ {
+ // grow buffer
+ uint new_size = ALIGN32( end + end/10 + 0x1000, 0x1000 );
+ if ( new_size > mf->max_size )
+ new_size = mf->max_size;
+ DASSERT( new_size >= end );
+ mf->data = REALLOC(mf->data,new_size);
+ DASSERT(mf->data);
+ memset(mf->data+mf->size,0,new_size-mf->size);
+ mf->size = new_size;
+ }
+ DASSERT( end <= mf->size );
+
+ mf->fpos = fpos;
+ if ( mf->fend < end )
+ mf->fend = end;
+ DASSERT(AssertMemFile(mf));
+
+ return mf->data + fpos;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeMemFile
+(
+ MemFile_t *mf, // structure to initialize
+ uint start_size, // >0: alloc this for data
+ uint max_size // >0: file never grows above 'max_size'
+)
+{
+ DASSERT(mf);
+ memset(mf,0,sizeof(*mf));
+ mf->max_size = max_size ? max_size : M1(mf->max_size);
+
+ if ( start_size > 0 )
+ {
+ GetDataMemFile( mf, 0, start_size < mf->max_size ? start_size : mf->max_size );
+ mf->fend = 0;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetMemFile
+(
+ MemFile_t *mf // valid MemFile_t data
+)
+{
+ DASSERT(mf);
+ FREE(mf->data);
+ if (mf->err_name_alloced)
+ FreeString(mf->err_name);
+ InitializeMemFile(mf,0,mf->max_size);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError WriteMemFileAt
+(
+ MemFile_t *mf, // valid MemFile_t data
+ uint fpos, // fpos to write
+ const void *data, // data to write
+ uint size // size to write
+)
+{
+ DASSERT(mf);
+ DASSERT(data||!size);
+
+ u8 *dest = GetDataMemFile(mf,fpos,size);
+ if (!dest)
+ {
+ if (mf->err_name)
+ ERROR0(ERR_WRITE_FAILED,"Memory file is full: %s\n",mf->err_name);
+ return ERR_WRITE_FAILED;
+ }
+
+ mf->eof = false;
+ if (size)
+ memcpy(dest,data,size);
+ mf->fpos = fpos + size;
+ if ( mf->fend < mf->fpos )
+ mf->fend = mf->fpos;
+
+ DASSERT(AssertMemFile(mf));
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint ReadMemFileAt
+(
+ MemFile_t *mf, // valid MemFile_t data
+ uint fpos, // fpos to read from
+ void *buf, // destination buffer
+ uint size // size to read
+)
+{
+ DASSERT(mf);
+ DASSERT(buf);
+
+ uint read_count = 0;
+ u8 *dest = buf;
+ if ( fpos < mf->fend )
+ {
+ read_count = mf->fend - fpos;
+ if ( read_count > size )
+ read_count = size;
+ memcpy(dest,mf->data+fpos,read_count);
+ mf->fpos = fpos + read_count;
+ dest += read_count;
+ size -= read_count;
+ }
+
+ mf->eof = size > 0;
+ if ( mf->eof && mf->zero_extend )
+ {
+ memset(dest,0,size);
+ mf->fpos += size;
+ read_count += size;
+ }
+
+ return read_count;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError LoadMemFile
+(
+ MemFile_t *mf, // MemFile_t data
+ bool init_mf, // true: initalize 'mf' first
+
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ size_t skip, // >0: skip num of bytes before reading
+ size_t size, // >0: max size to read; 0: read all (remaining)
+ bool silent // true: suppress error messages
+)
+{
+ DASSERT(mf);
+
+ if (init_mf)
+ InitializeMemFile(mf,0,0);
+ else
+ ResetMemFile(mf);
+
+ char pathbuf[PATH_MAX];
+ ccp path = PathCatPP(pathbuf,sizeof(pathbuf),path1,path2);
+ PRINT("LoadMemFile(silent=%d) %s\n",silent,path);
+
+ struct stat st;
+ if (stat(path,&st))
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_OPEN,"Can't get file status: %s\n",path);
+ return ERR_CANT_OPEN;
+ }
+ UseFileAttrib(&mf->fatt,0,&st,0);
+
+ if ( skip >= st.st_size )
+ return ERR_OK;
+
+ const uint max_size = st.st_size - skip;
+ if ( !size || size > max_size )
+ size = max_size;
+
+ u8 *dest = GetDataMemFile(mf,0,size);
+ if (!dest)
+ {
+ if (!silent)
+ ERROR0(ERR_READ_FAILED,"Memory file is too small: %s\n",path);
+ return ERR_READ_FAILED;
+ }
+
+ FILE * f = fopen(path,"rb");
+ if (!f)
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_OPEN,"Can't open file: %s\n",path);
+ return ERR_CANT_OPEN;
+ }
+
+ if ( skip > 0 )
+ fseek(f,skip,SEEK_SET);
+
+ mf->fend = fread(dest,1,size,f);
+ fclose(f);
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError SaveMemFile
+(
+ MemFile_t *mf, // MemFile_t data
+
+ ccp path1, // NULL or part #1 of path
+ ccp path2, // NULL or part #2 of path
+ FileMode_t fmode, // mode for file creation
+ bool sparse // true: enable sparse support
+)
+{
+ DASSERT(mf);
+
+ char pathbuf[PATH_MAX];
+ ccp path = PathCatPP(pathbuf,sizeof(pathbuf),path1,path2);
+ PRINT("SaveMemFile(fmode=0x%x,sparse=%d) %u/%u/%u %s\n",
+ fmode, sparse, mf->fpos, mf->fend, mf->size, path );
+
+ File_t F;
+ enumError err = CreateFile(&F,true,path,fmode);
+ if ( err || !F.f )
+ {
+ ResetFile(&F,0);
+ return err;
+ }
+
+ if (!sparse)
+ {
+ const uint written = fwrite(mf->data,1,mf->fend,F.f);
+ if ( written != mf->fend )
+ {
+ if (!(fmode&FM_SILENT))
+ ERROR1(ERR_WRITE_FAILED,"Write to file failed: %s\n",path);
+ err_exit:
+ ResetFile(&F,0);
+ return ERR_WRITE_FAILED;
+ }
+ }
+ else
+ {
+ enum { BLOCK_SIZE = 512 };
+ char zero[BLOCK_SIZE];
+ memset(zero,0,sizeof(zero));
+
+ size_t cur_offset = 0;
+ uint off = 0;
+ while ( off < mf->fend )
+ {
+ //--- skip sparse blocks
+
+ while ( off < mf->fend )
+ {
+ uint max = mf->fend - off;
+ if ( max > BLOCK_SIZE )
+ max = BLOCK_SIZE;
+ if (memcmp(mf->data+off,zero,max))
+ break;
+ off += max;
+ }
+
+ //--- search next sparse block
+
+ uint start = off;
+ while ( off < mf->fend )
+ {
+ uint max = mf->fend - off;
+ if ( max > BLOCK_SIZE )
+ max = BLOCK_SIZE;
+ if (!memcmp(mf->data+off,zero,max))
+ break;
+ off += max;
+ }
+
+ err = WriteFileAt(&F,&cur_offset,start,mf->data+start,off-start);
+ if (err)
+ goto err_exit;
+ }
+
+ err = SetFileSize(&F,&cur_offset,mf->fend);
+ if (err)
+ goto err_exit;
+ }
+ return ResetFile(&F,true);
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// TraceLog_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool OpenTraceLog ( TraceLog_t *tl )
+{
+ if ( !tl || tl->level < 0 )
+ return false;
+
+ if ( !tl->log && tl->fname )
+ {
+ tl->log = fopen(tl->fname,"wb");
+ if (tl->log)
+ {
+ fcntl(fileno(tl->log),F_SETFD,FD_CLOEXEC);
+ fprintf(tl->log,"# %s, pid=%d\n",
+ PrintTimeByFormat("%F %T %z",time(0)), getpid() );
+ }
+ }
+
+ if (!tl->log)
+ {
+ tl->level = -1;
+ return false;
+ }
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool TraceLogText ( TraceLog_t *tl, ccp text )
+{
+ if (!OpenTraceLog(tl))
+ return false;
+
+ if (text)
+ {
+ fputs(text,tl->log);
+ fflush(tl->log);
+ }
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool TraceLogPrint ( TraceLog_t *tl, ccp format, ... )
+{
+ if (!OpenTraceLog(tl))
+ return false;
+
+ if (format)
+ {
+ va_list arg;
+ va_start(arg,format);
+ vfprintf(tl->log,format,arg);
+ va_end(arg);
+ fflush(tl->log);
+ }
+ return true;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// struct LineBuffer_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+#ifndef __APPLE__
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeLineBuffer ( LineBuffer_t *lb, uint max_buf_size )
+{
+ DASSERT(lb);
+ memset(lb,0,sizeof(*lb));
+ InitializeGrowBuffer(&lb->buf,max_buf_size);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetLineBuffer ( LineBuffer_t *lb )
+{
+ DASSERT(lb);
+ CloseLineBuffer(lb);
+ ResetGrowBuffer(&lb->buf);
+ FREE(lb->line);
+ lb->line = 0;
+ lb->seq_count++;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ClearLineBuffer ( LineBuffer_t *lb )
+{
+ DASSERT(lb);
+ ClearGrowBuffer(&lb->buf);
+ lb->used_lines = 0;
+ lb->seq_count++;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void RedirectLineBuffer
+(
+ LineBuffer_t *lb, // valid line buffer
+ FILE *f, // output file, if NULL use 'lb->old_fd'
+ int max_lines // >=0: max number of redirected lines
+)
+{
+ DASSERT(lb);
+ if ( lb->buf.used && max_lines )
+ {
+ if (!f)
+ f = lb->old_fp;
+ if (f)
+ {
+ if ( max_lines < 0 )
+ fwrite(lb->buf.ptr,1,lb->buf.used,f);
+ else
+ {
+ mem_t *line;
+ uint n = GetMemListLineBuffer(lb,&line,-max_lines);
+ while ( n-- > 0 )
+ {
+ fprintf(f,"%.*s\n",line->len-1,line->ptr);
+ line++;
+ }
+//DEL fprintf(f,"==> %zd %d\n",
+//DEL line[-1].ptr + line[-1].len - (ccp)lb->buf.ptr,
+//DEL lb->buf.used );
+ }
+
+ fflush(f);
+ if ( f == stdout )
+ stdout_seq_count++;
+ }
+ }
+ ClearGrowBuffer(&lb->buf);
+ lb->used_lines = 0;
+ lb->seq_count++;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+LineBuffer_t * OpenLineBuffer
+(
+ LineBuffer_t *lb, // line buffer; if NULL, malloc() one
+ bool init_lb, // true: initialize 'lb'
+ FILE **fp_pos, // NULL or external place of the file pointer
+ uint max_lines, // max numbers of lines
+ uint max_line_size, // >0: max size of a single line
+ uint max_buf_size // >0: max total buffer size
+)
+{
+ if (!lb)
+ lb = MALLOC(sizeof(*lb));
+ else if (!init_lb)
+ ResetLineBuffer(lb);
+ InitializeLineBuffer(lb,max_buf_size);
+
+ DASSERT(lb);
+ if (fp_pos)
+ {
+ lb->old_fp = *fp_pos;
+ lb->old_fp_pos = fp_pos;
+ }
+
+ lb->max_lines = max_lines > 1 ? max_lines : 1;
+ lb->max_line_size = max_line_size;
+ lb->line = MALLOC(lb->max_lines*sizeof(*lb->line));
+
+ static cookie_io_functions_t funcs =
+ {
+ 0, // read
+ (cookie_write_function_t*)WriteLineBuffer,
+ 0, // seek
+ (cookie_close_function_t*)CloseLineBuffer
+ };
+ lb->fp = fopencookie(lb,"wb",funcs);
+ if ( fp_pos && lb->fp )
+ *fp_pos = lb->fp;
+ return lb;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int CloseLineBuffer ( LineBuffer_t *lb )
+{
+ DASSERT(lb);
+ if (lb->fp)
+ {
+ if ( lb->old_fp_pos && *lb->old_fp_pos == lb->fp )
+ *lb->old_fp_pos = lb->old_fp;
+ lb->fp = 0;
+ }
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ssize_t WriteLineBuffer ( LineBuffer_t *lb, ccp buf, size_t size )
+{
+ DASSERT(lb);
+
+ if ( lb->redirect > 0 )
+ {
+ if (lb->old_fp)
+ {
+ ssize_t res = fwrite(buf,1,size,lb->old_fp);
+ fflush(lb->old_fp);
+ if ( lb->old_fp == stdout )
+ stdout_seq_count++;
+ return res;
+ }
+ }
+ else if (size)
+ {
+ if ( lb->open_line && lb->buf.used )
+ lb->buf.used--;
+
+ if ( lb->buf.max_size )
+ {
+ const int max_used = lb->buf.max_size - size - 1;
+ if ( max_used <= 0 )
+ {
+ ClearGrowBuffer(&lb->buf);
+ buf -= max_used;
+ size += max_used;
+ }
+ else if ( lb->buf.used > max_used )
+ {
+ u8 *ptr = lb->buf.ptr;
+ u8 *end = ptr + lb->buf.used;
+ ptr += lb->buf.used - max_used;
+ while ( ptr < end )
+ if ( *ptr++ == '\n' )
+ break;
+ DropGrowBuffer(&lb->buf,ptr-lb->buf.ptr);
+ }
+ }
+
+ uint new_lines = 0;
+ ccp ptr = buf, end = ptr + size;
+ while ( ptr < end )
+ if ( *ptr++ == '\n' )
+ new_lines++;
+ lb->line_count += new_lines;
+
+ InsertGrowBuffer(&lb->buf,buf,size);
+ lb->open_line = buf[size-1] != '\n';
+ if (lb->open_line)
+ InsertCharGrowBuffer(&lb->buf,'\n');
+
+ FixMemListLineBuffer(lb,true);
+ lb->seq_count++;
+ PRINT("WRITE %3zu bytes, is_open: %d, total lines: %d\n",
+ size, lb->open_line, lb->used_lines );
+ }
+ return size;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void FixMemListLineBuffer ( LineBuffer_t *lb, bool force )
+{
+ DASSERT(lb);
+ if (!lb->buf.used)
+ lb->used_lines = 0;
+ else if ( force
+ || lb->prev_buf_ptr != lb->buf.ptr
+ || lb->prev_seq_count != lb->seq_count )
+ {
+ u8 *beg = lb->buf.ptr;
+ u8 *ptr = beg + lb->buf.used;
+
+ mem_t *dest = lb->line + lb->max_lines;
+ while ( beg < ptr && dest > lb->line )
+ {
+ u8* end = ptr--;
+ while ( beg < ptr && ptr[-1] != '\n' )
+ ptr--;
+
+ dest--;
+ dest->ptr = (ccp)ptr;
+ dest->len = end - ptr;
+ }
+
+ lb->used_lines = lb->line + lb->max_lines - dest;
+ if ( dest > lb->line )
+ memmove(lb->line,dest,lb->used_lines*sizeof(*dest));
+
+ if ( ptr > beg )
+ {
+ // don't use DropGrowBuffer() to hold buf stable
+ lb->buf.used -= ptr - beg;
+ lb->buf.ptr = ptr;
+ }
+
+ lb->prev_buf_ptr = lb->buf.ptr;
+ lb->prev_seq_count = lb->seq_count;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint GetMemListLineBuffer
+(
+ // returns the number of elements
+
+ LineBuffer_t *lb, // line buffer; if NULL, malloc() one
+ mem_t **res, // store pointer to first element here
+ int max_lines // !0: limit lines, <0: count from end
+)
+{
+ DASSERT(lb);
+
+ if ( lb->prev_buf_ptr != lb->buf.ptr )
+ FixMemListLineBuffer(lb,false);
+
+ uint pos = 0;
+ if (!max_lines)
+ max_lines = lb->used_lines;
+ else if ( max_lines < 0 )
+ {
+ max_lines = -max_lines;
+ if ( max_lines > lb->used_lines )
+ max_lines = lb->used_lines;
+ pos = lb->used_lines - max_lines;
+ }
+ else if ( max_lines > lb->used_lines )
+ max_lines = lb->used_lines;
+
+ if (res)
+ *res = lb->line + pos;
+ return max_lines;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint PrintLineBuffer
+(
+ // returns number of written lines
+
+ FILE *f, // output stream
+ int indent, // indention
+ LineBuffer_t *lb, // line buffer; if NULL, malloc() one
+ int max_lines, // !0: limit lines, <0: count from end
+ ccp open_line // not NULL: append this text for an open line
+)
+{
+ DASSERT(f);
+ DASSERT(lb);
+ indent = NormalizeIndent(indent);
+
+ mem_t *line;
+ uint i, n = GetMemListLineBuffer(lb,&line,max_lines);
+ for ( i = 1; i <= n; i++, line++ )
+ fprintf(f,"%*s%.*s%s\n",
+ indent,"",
+ line->len-1, line->ptr,
+ open_line && i == n && lb->open_line ? open_line : "" );
+ return n;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+#endif // !__APPLE__
+///////////////////////////////////////////////////////////////////////////////
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// file helpers ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int IsDirectory
+(
+ // analyse filename (last char == '/') and stat(fname)
+
+ ccp fname, // NULL or path
+ int answer_if_empty // answer, if !fname || !*fname
+)
+{
+ if ( !fname || !*fname )
+ return answer_if_empty;
+
+ if ( *fname == '-' && !fname[1] )
+ return false;
+
+ if ( fname[strlen(fname)-1] == '/' )
+ return true;
+
+ struct stat st;
+ return !stat(fname,&st) && S_ISDIR(st.st_mode);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int ExistDirectory
+(
+ // check only real file (stat(fname))
+
+ ccp fname, // NULL or path
+ int answer_if_empty // answer, if !fname || !*fname
+)
+{
+ if ( !fname || !*fname )
+ return answer_if_empty;
+
+ if ( *fname == '-' && !fname[1] )
+ return false;
+
+ struct stat st;
+ return !stat(fname,&st) && S_ISDIR(st.st_mode);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool IsSameFilename ( ccp fn1, ccp fn2 )
+{
+ if ( !fn1 || !fn2 )
+ return false;
+
+ while ( fn1[0] == '.' && fn1[1] == '/' )
+ fn1 += 2;
+ while ( fn2[0] == '.' && fn2[1] == '/' )
+ fn2 += 2;
+
+ return !strcmp(fn1,fn2);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool enable_config_search_log = false;
+
+ccp FindConfigFile
+(
+ // returns NULL or found file (alloced)
+
+ FindConfigFile_t mode, // bit field: select search destinations
+ ccp share_name, // NULL or <SHARE>
+ ccp file_name, // NULL or <FILENAME>
+ ccp prog_ext // <EXT>, if NULL use '.' + <FILENAME>
+)
+{
+ struct stat st;
+ char path[PATH_MAX];
+
+ //--- empty strings like NULL
+
+ if ( share_name && !*share_name ) share_name = progname;
+ if ( file_name && !*file_name ) file_name = 0;
+ if ( prog_ext && !*prog_ext ) prog_ext = 0;
+
+ if ( file_name && share_name )
+ {
+ //--- the first attemps need FILENAME
+
+ if ( mode & FCF_HOME )
+ {
+ ccp home = getenv("HOME");
+ if ( home && *home )
+ {
+ snprintf(path,sizeof(path),"%s/.%s/%s",home,share_name,file_name);
+ if ( mode & FCF_F_DEBUG )
+ fprintf(stderr,"SEARCH: %s\n",path);
+ if ( !stat(path,&st) && S_ISREG(st.st_mode) )
+ return STRDUP(path);
+ }
+ }
+
+ if ( mode & FCF_REL_SHARE )
+ {
+ ccp pdir = ProgramDirectory();
+ if (pdir)
+ {
+ snprintf(path,sizeof(path),"%s/../share/%s/%s",pdir,share_name,file_name );
+ if ( mode & FCF_F_DEBUG )
+ fprintf(stderr,"SEARCH: %s\n",path);
+ if ( !stat(path,&st) && S_ISREG(st.st_mode) )
+ return STRDUP(path);
+ }
+ }
+
+ if ( mode & FCF_LOC_SHARE )
+ {
+ snprintf(path,sizeof(path),"/usr/local/share/%s/%s",share_name,file_name);
+ if ( mode & FCF_F_DEBUG )
+ fprintf(stderr,"SEARCH: %s\n",path);
+ if ( !stat(path,&st) && S_ISREG(st.st_mode) )
+ return STRDUP(path);
+ }
+
+ if ( mode & FCF_USR_SHARE )
+ {
+ snprintf(path,sizeof(path),"/usr/share/%s/%s",share_name,file_name);
+ if ( mode & FCF_F_DEBUG )
+ fprintf(stderr,"SEARCH: %s\n",path);
+ if ( !stat(path,&st) && S_ISREG(st.st_mode) )
+ return STRDUP(path);
+ }
+ }
+
+ if ( mode & (FCF_PROG_SHARE|FCF_PROG) && ( prog_ext || file_name ) )
+ {
+ ccp ppath = ProgramPathNoExt();
+ if (ppath)
+ {
+ if ( mode & FCF_PROG_SHARE )
+ {
+ ccp pdir = ProgramDirectory();
+ noPRINT("pdir=%s\n",pdir);
+ if (pdir)
+ {
+ ccp fname = strrchr(ppath,'/');
+ fname = fname ? fname+1 : ppath;
+
+ if (prog_ext)
+ snprintf(path,sizeof(path),"%s/share/%s%s",
+ pdir, fname, prog_ext );
+ else
+ snprintf(path,sizeof(path),"%s/share/%s.%s",
+ pdir, fname, file_name );
+ if ( mode & FCF_F_DEBUG )
+ fprintf(stderr,"SEARCH: %s\n",path);
+ if ( !stat(path,&st) && S_ISREG(st.st_mode) )
+ return STRDUP(path);
+ }
+ }
+
+ if ( mode & FCF_PROG )
+ {
+ if (prog_ext)
+ snprintf(path,sizeof(path),"%s%s",ppath,prog_ext);
+ else
+ snprintf(path,sizeof(path),"%s.%s",ppath,file_name);
+ if ( mode & FCF_F_DEBUG )
+ fprintf(stderr,"SEARCH: %s\n",path);
+ if ( !stat(path,&st) && S_ISREG(st.st_mode) )
+ return STRDUP(path);
+ }
+ }
+ }
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int SearchFiles
+(
+ ccp path1, // not NULL: part #1 of base path
+ ccp path2, // not NULL: part #2 of base path
+ ccp match, // not NULL: filter files by MatchPattern()
+ // if empty: extract from combined path
+ SearchFilesFunc func, // callback function, never NULL
+ void *param // third parameter of func()
+)
+{
+ DASSERT(func);
+
+ char pathbuf[PATH_MAX];
+ ccp path = PathCatPP(pathbuf,sizeof(pathbuf),path1,path2);
+
+ if ( match && !*match )
+ {
+ match = 0;
+ if ( path != pathbuf )
+ {
+ // path is modified!
+ StringCopyS(pathbuf,sizeof(pathbuf),path);
+ path = pathbuf;
+ }
+
+ char *found = strrchr(pathbuf,'/');
+ if (found)
+ {
+ if ( found == pathbuf )
+ path = "/";
+ *found++ = 0;
+ match = *found ? found : 0;
+ }
+ else
+ {
+ match = pathbuf;
+ path = ".";
+ }
+ }
+
+ if ( !path || !*path )
+ path = ".";
+
+ DIR *fdir = opendir(path);
+ int stat = errno;
+
+ if (fdir)
+ {
+ for(;;)
+ {
+ struct dirent *dent = readdir(fdir);
+ if (!dent)
+ break;
+
+ if ( !match || MatchPatternFull(match,dent->d_name) )
+ {
+ stat = func(path,dent,param);
+ if ( stat < 0 )
+ break;
+ }
+ }
+ closedir(fdir);
+ }
+ return stat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError CreatePath
+(
+ ccp path, // path to create
+ bool is_pure_dir // true: 'path' don't contains a filename part
+)
+{
+ TRACE("CreatePath(%s)\n",path);
+
+ char buf[PATH_MAX];
+ char *dest = StringCopyS(buf,sizeof(buf)-1,path);
+ if (is_pure_dir)
+ {
+ *dest++ = '/';
+ *dest = 0;
+ }
+ dest = buf;
+
+ for(;;)
+ {
+ // skip slashes
+ while ( *dest == '/' )
+ dest++;
+
+ // search end of current directory
+ while ( *dest && *dest != '/' )
+ dest++;
+ if (!*dest)
+ break;
+
+ *dest = 0;
+ if ( mkdir(buf,0777) && errno != EEXIST && !IsDirectory(buf,0) )
+ {
+ noTRACE("CREATE-DIR: %s -> err=%d (ENOTDIR=%d)\n",buf,errno,ENOTDIR);
+ if ( errno == ENOTDIR )
+ {
+ while ( dest > buf && *dest != '/' )
+ dest--;
+ if ( dest > buf )
+ *dest = 0;
+ }
+ return ERROR1( ERR_CANT_CREATE_DIR,
+ errno == ENOTDIR
+ ? "Not a directory: %s\n"
+ : "Can't create directory: %s\n", buf );
+ }
+ TRACE("CREATE-DIR: %s -> OK\n",buf);
+ *dest++ = '/';
+ }
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError RemoveSource
+(
+ ccp fname, // file to remove
+ ccp dest_fname, // NULL or dest file name
+ // If real pathes are same: don't remove
+ bool print_log, // true: print a log message
+ bool testmode // true: don't remove, log only
+)
+{
+ DASSERT(fname);
+ if (dest_fname)
+ {
+ if (!strcmp(fname,dest_fname))
+ return ERR_OK;
+
+ char p1[PATH_MAX], p2[PATH_MAX];
+ if ( realpath(fname,p1) && realpath(dest_fname,p2) && !strcmp(p1,p2) )
+ return ERR_OK;
+ }
+
+ if ( print_log || testmode )
+ fprintf( stdlog ? stdlog : stdout,
+ "%sREMOVE %s\n",
+ testmode ? "WOULD " : "", fname );
+
+ return !testmode && unlink(fname)
+ ? ERROR1( ERR_CANT_REMOVE, "Can't remove source file: %s\n", fname )
+ : ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+char * FindFilename
+(
+ // Find the last part of the path, trailing '/' are ignored
+ // return poiner to found string
+
+ ccp path, // path to analyze, valid string
+ uint * result_len // not NULL: return length of found string
+)
+{
+ DASSERT(path);
+
+ ccp end = path + strlen(path);
+ if ( end > path && end[-1] == '/' )
+ end--;
+
+ ccp start = end;
+ while ( start > path && start[-1] != '/' )
+ start--;
+ if (result_len)
+ *result_len = end - start;
+ return (char*)start;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint NumberedFilename
+(
+ // return used index number or 0, if not modified.
+
+ char * buf, // destination buffer
+ size_t bufsize, // size of 'buf'
+ ccp source, // source filename, may be part of 'buf'
+ // if NULL or empty:
+ ccp ext, // not NULL & not empty: file extension ('.ext')
+ uint ext_mode, // only relevant, if 'ext' is neither NULL not empty
+ // 0: use 'ext' only for detection
+ // 1: replace 'source' extension by 'ext'
+ // 2: append 'ext' if 'source' differ
+ // 3: append 'ext' always
+ bool detect_stdio // true: don't number "-"
+)
+{
+ //--- analyse stdin/stdout
+
+ char *end = StringCopyS(buf,bufsize,source);
+ if ( !source || !*source || detect_stdio && *source == '-' && !source[1] )
+ return 0;
+
+
+ //--- analyse parameter 'ext'
+
+ uint ext_len = 0;
+ if ( ext && *ext )
+ {
+ ext_len = strlen(ext);
+ if ( ext_mode >= 3 )
+ {
+ // append extension always
+ StringCopyE(end,buf+bufsize,ext);
+ }
+ else if ( end - buf <= ext_len || memcmp(end-ext_len,ext,ext_len) )
+ {
+ // extensions differ
+ if (!ext_mode)
+ ext_len = 0;
+ else if ( ext_mode == 2 )
+ StringCopyE(end,buf+bufsize,ext);
+ else
+ {
+ char *slash = strrchr(buf,'/');
+ char *point = strrchr(buf,'.');
+ if ( point && ( !slash || point > slash ) )
+ end = point;
+ StringCopyE(end,buf+bufsize,ext);
+ }
+ }
+ }
+
+
+ //--- does files exist? => if not: return without numbering
+
+ struct stat st;
+ if (stat(buf,&st))
+ return 0;
+
+
+
+ //--- split filename into dir + name + index + ext
+
+ char temp[PATH_MAX];
+
+ ccp dir = buf, search_dir;
+ uint dir_len;
+ ccp name = strrchr(buf,'/');
+ if (name)
+ {
+ dir_len = name++ - dir;
+ StringCopyS(temp,sizeof(temp),buf);
+ if ( dir_len < sizeof(temp) )
+ temp[dir_len] = 0;
+ search_dir = temp;
+ dir_len++;
+ }
+ else
+ {
+ name = buf;
+ dir_len = 0;
+ dir = "";
+ search_dir = ".";
+ }
+
+ uint name_len;
+ if (ext_len)
+ {
+ name_len = strlen(name);
+ if ( ext_len >= name_len )
+ goto find_ext;
+
+ name_len -= ext_len;
+ ext = name + name_len;
+ }
+ else
+ {
+ find_ext:
+ ext = strrchr(name,'.');
+ if ( ext && ext > name )
+ {
+ name_len = ext - name;
+ ext_len = strlen(ext);
+ }
+ else
+ {
+ name_len = strlen(name);
+ ext_len = 0;
+ ext = "";
+ }
+ }
+
+ ccp ptr = name + name_len - 1;
+ while ( ptr >= name && *ptr >= '0' && *ptr <= '9' )
+ ptr--;
+ name_len = ++ptr - name;
+ uint index = strtoul(ptr,0,10);
+
+ PRINT("|%s| : |%.*s|%.*s|#%u|%.*s|\n",
+ search_dir, dir_len, dir, name_len, name, index, ext_len, ext );
+
+
+ //--- iterate directory to find highest index
+
+ DIR *fdir = opendir(search_dir);
+ if (fdir)
+ {
+ for(;;)
+ {
+ struct dirent *dent = readdir(fdir);
+ if (!dent)
+ break;
+
+ ccp fname = dent->d_name;
+ const uint fname_len = strlen(fname);
+ if ( fname_len > name_len + ext_len
+ && !strncasecmp(name,fname,name_len) )
+ {
+ ccp fext = fname + fname_len - ext_len;
+ if (!strncasecmp(ext,fext,ext_len))
+ {
+ char *end;
+ uint idx = strtoul(fname+name_len,&end,10);
+ if ( end == fext && idx > index )
+ index = idx;
+ }
+ }
+ }
+ closedir(fdir);
+ }
+
+
+ //--- create new filename
+
+ index++;
+ snprintf(temp,sizeof(temp),"%.*s%u%s",name_len,name,index,ext);
+ StringCopyE(buf+dir_len,buf+bufsize,temp);
+
+ return index;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// normalize filenames ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+char * NormalizeFileName
+(
+ // returns a pointer to the NULL terminator within 'buf'
+
+ char * buf, // valid destination buffer
+ uint buf_size, // size of buf
+ ccp source, // NULL or source
+ bool allow_slash, // true: allow '/' in source
+ bool is_utf8 // true: enter UTF-8 mode
+)
+{
+ DASSERT(buf);
+ DASSERT(buf_size>1);
+
+ char *dest = buf, *end = buf + buf_size - 1;
+ TRACE("NormalizeFileName(%s,%d)\n",source,allow_slash);
+
+ if (source)
+ {
+ #ifdef __CYGWIN__
+ if (allow_slash)
+ {
+ const int drv_len = IsWindowsDriveSpec(source);
+ if (drv_len)
+ {
+ dest = StringCopyE(dest,end,"/cygdrive/c/");
+ if ( dest < end )
+ dest[-2] = tolower((int)*source);
+ source += drv_len;
+ }
+ }
+ #endif
+
+ bool skip_space = true;
+ while ( *source && dest < end )
+ {
+ unsigned char ch = *source++;
+ if ( ch == ':' )
+ {
+ if (!skip_space)
+ *dest++ = ' ';
+ if ( dest + 2 <= end )
+ {
+ *dest++ = '-';
+ *dest++ = ' ';
+ }
+ skip_space = true;
+ }
+ else
+ {
+ if ( isalnum(ch)
+ || ( is_utf8
+ ? ch >= 0x80
+ : ch == 0xe4 // ä
+ || ch == 0xf6 // ö
+ || ch == 0xfc // ü
+ || ch == 0xdf // ß
+ || ch == 0xc4 // Ä
+ || ch == 0xd6 // Ö
+ || ch == 0xdc // Ü
+ )
+ || strchr("_+-=%'\"$%&,.!()[]{}<>",ch)
+ || ch == '/' && allow_slash )
+ {
+ *dest++ = ch;
+ skip_space = false;
+ }
+ #ifdef __CYGWIN__
+ else if ( ch == '\\' && allow_slash )
+ {
+ *dest++ = '/';
+ skip_space = false;
+ }
+ #endif
+ else if (!skip_space)
+ {
+ *dest++ = ' ';
+ skip_space = true;
+ }
+ }
+ }
+ }
+ if ( dest > buf && dest[-1] == ' ' )
+ dest--;
+
+ DASSERT( dest <= end );
+ *dest = 0;
+ return dest;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef __CYGWIN__
+
+ uint IsWindowsDriveSpec
+ (
+ // returns the length of the found windows drive specification (0|2|3)
+
+ ccp src // valid string
+ )
+ {
+ DASSERT(src);
+
+ if ( ( *src >= 'a' && *src <= 'z' || *src >= 'A' && *src <= 'Z' )
+ && src[1] == ':' )
+ {
+ if (!src[2])
+ return 2;
+
+ if ( src[2] == '/' || src[2] == '\\' )
+ return 3;
+ }
+ return 0;
+ }
+
+#endif // __CYGWIN__
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef __CYGWIN__
+
+ uint NormalizeFilenameCygwin
+ (
+ // returns a pointer to the NULL terminator within 'buf'
+
+ char * buf, // valid destination buffer
+ uint buf_size, // size of buf
+ ccp src // NULL or source
+ )
+ {
+ static char prefix[] = "/cygdrive/";
+
+ if ( buf_size < sizeof(prefix) + 5 || !src )
+ {
+ *buf = 0;
+ return 0;
+ }
+
+ char * end = buf + buf_size - 1;
+ char * dest = buf;
+
+ if ( ( *src >= 'a' && *src <= 'z' || *src >= 'A' && *src <= 'Z' )
+ && src[1] == ':'
+ && ( src[2] == 0 || src[2] == '/' || src[2] == '\\' ))
+ {
+ memcpy(buf,prefix,sizeof(prefix));
+ dest = buf + sizeof(prefix)-1;
+ *dest++ = tolower((int)*src); // cygwin needs the '(int)'
+ *dest = 0;
+ if (IsDirectory(buf,false))
+ {
+ *dest++ = '/';
+ src += 2;
+ if (*src)
+ src++;
+ }
+ else
+ dest = buf;
+ }
+ ASSERT( dest < buf + buf_size );
+
+ while ( dest < end && *src )
+ if ( *src == '\\' )
+ {
+ *dest++ = '/';
+ src++;
+ }
+ else
+ *dest++ = *src++;
+
+ *dest = 0;
+ ASSERT( dest < buf + buf_size );
+ return dest - buf;
+ }
+
+#endif // __CYGWIN__
+
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef __CYGWIN__
+
+ char * AllocNormalizedFilenameCygwin
+ (
+ // returns an alloced buffer with the normalized filename
+
+ ccp source // NULL or string
+ )
+ {
+ char buf[PATH_MAX];
+ const uint len = NormalizeFilenameCygwin(buf,sizeof(buf),source);
+ char * result = MEMDUP(buf,len); // MEMDUP allocs +1 byte and set it to NULL
+ DASSERT(buf[len]==0);
+ return result;
+ }
+
+#endif // __CYGWIN__
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// FDList_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void ClearFDList ( FDList_t *fdl )
+{
+ DASSERT(fdl);
+
+ fdl->max_fd = -1;
+ FD_ZERO(&fdl->readfds);
+ FD_ZERO(&fdl->writefds);
+ FD_ZERO(&fdl->exceptfds);
+
+ fdl->poll_used = 0;
+ fdl->n_sock = 0;
+
+ fdl->now_usec = GetTimeUSec(false);
+ fdl->timeout_usec = M1(fdl->timeout_usec);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void InitializeFDList ( FDList_t *fdl, bool use_poll )
+{
+ DASSERT(fdl);
+ memset(fdl,0,sizeof(*fdl));
+ fdl->use_poll = use_poll;
+ ClearFDList(fdl);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetFDList ( FDList_t *fdl )
+{
+ DASSERT(fdl);
+ FREE(fdl->poll_list);
+ InitializeFDList(fdl,fdl->use_poll);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+struct pollfd * AllocFDList ( FDList_t *fdl, uint n )
+{
+ DASSERT(fdl);
+ uint min_size = fdl->poll_used + n;
+ if ( fdl->poll_size < min_size )
+ {
+ fdl->poll_size = ( min_size/0x18 + 2 ) * 0x20;
+ DASSERT( fdl->poll_size >= min_size );
+ fdl->poll_list = REALLOC(fdl->poll_list,fdl->poll_size*sizeof(*fdl->poll_list));
+ }
+ DASSERT( fdl->poll_used + n <= fdl->poll_size );
+
+ struct pollfd *ptr = fdl->poll_list + fdl->poll_used;
+ fdl->poll_used += n;
+ memset(ptr,0,n*sizeof(*ptr));
+ return ptr;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void AnnounceFDList ( FDList_t *fdl, uint n )
+{
+ if (fdl->use_poll)
+ {
+ AllocFDList(fdl,n);
+ fdl->poll_used -= n;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint AddFDList
+(
+ // returns the pool-index if available, ~0 otherwise
+
+ FDList_t *fdl, // valid socket list
+ int sock, // socket to add
+ uint events // bit field: POLLIN|POLLOUT|POLLERR
+)
+{
+ if ( sock == -1 || !(events&(POLLIN|POLLOUT|POLLERR)) )
+ return ~0;
+
+ fdl->n_sock++;
+ if ( fdl->max_fd < sock )
+ fdl->max_fd = sock;
+
+ DASSERT(fdl);
+ if (fdl->use_poll)
+ {
+ struct pollfd *pp = AllocFDList(fdl,1);
+ DASSERT(pp);
+ pp->fd = sock;
+ pp->events = events;
+ return pp - fdl->poll_list;
+ }
+
+ noPRINT("ADD-SOCK %d = %x\n",sock,events);
+ if ( events & POLLIN )
+ FD_SET(sock,&fdl->readfds);
+ if ( events & POLLOUT )
+ FD_SET(sock,&fdl->writefds);
+ if ( events & POLLERR )
+ FD_SET(sock,&fdl->exceptfds);
+ return ~0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint GetEventFDList
+(
+ // returns bit field: POLLIN|POLLOUT|POLLERR
+
+ FDList_t *fdl, // valid socket list
+ int sock, // socket to look for
+ uint poll_index // if use_poll: use the index for a fast search
+)
+{
+ if ( sock == -1 )
+ return 0;
+
+ DASSERT(fdl);
+ if (fdl->use_poll)
+ {
+ const struct pollfd *pp = fdl->poll_list;
+ if ( poll_index < fdl->poll_used && pp[poll_index].fd == sock )
+ return pp[poll_index].revents;
+
+ const struct pollfd *pend = pp + fdl->poll_used;
+ for ( ; pp < pend; pp++ )
+ if ( pp->fd == sock )
+ return pp->revents;
+ return 0;
+ }
+
+ uint revents = 0;
+ if (FD_ISSET(sock,&fdl->readfds))
+ revents |= POLLIN;
+ if (FD_ISSET(sock,&fdl->writefds))
+ revents |= POLLOUT;
+ if (FD_ISSET(sock,&fdl->exceptfds))
+ revents |= POLLERR;
+ return revents;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int WaitFDList
+(
+ FDList_t *fdl // valid socket list
+)
+{
+ DASSERT(fdl);
+
+ int stat;
+ u64 now_usec = GetTimeUSec(false);
+
+ if (fdl->use_poll)
+ {
+ int timeout;
+ if ( fdl->timeout_usec > now_usec )
+ {
+ u64 delta = ( fdl->timeout_usec - now_usec ) / 1000;
+ timeout = delta > 0x7fffffffull ? -1 : delta;
+ }
+ else
+ timeout = 0;
+
+ stat = poll( fdl->poll_list, fdl->poll_used, timeout );
+ }
+ else
+ {
+ struct timeval tv, *ptv = &tv;
+ if ( fdl->timeout_usec > now_usec )
+ {
+ const u64 delta = fdl->timeout_usec - now_usec;
+ if ( delta > 1000000ull * 0x7fffffffull )
+ ptv = NULL;
+ else
+ {
+ tv.tv_sec = delta / 1000000;
+ tv.tv_usec = delta % 1000000;
+ }
+ }
+ else
+ tv.tv_sec = tv.tv_usec = 0;
+
+//DEL fprintf(stderr,"#SELECT %lld: ptv=%d: %lu.%06lu\n",
+//DEL fdl->timeout_usec-now_usec, ptv!=0, tv.tv_sec, tv.tv_usec );
+ stat = select( fdl->max_fd+1, &fdl->readfds, &fdl->writefds,
+ &fdl->exceptfds, ptv );
+ }
+
+ fdl->now_usec = GetTimeUSec(false);
+ fdl->cur_usec = fdl->now_usec - now_usec;
+ fdl->wait_usec += fdl->cur_usec;
+ fdl->wait_count++;
+ return stat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int PWaitFDList
+(
+ FDList_t *fdl, // valid socket list
+ const sigset_t *sigmask // NULL or signal mask
+)
+{
+ DASSERT(fdl);
+
+ #ifdef __APPLE__
+ // [[2do]] ??? untested work around
+ if (fdl->use_poll)
+ {
+ sigset_t origmask;
+ if (sigmask)
+ sigprocmask(SIG_SETMASK,sigmask,&origmask);
+ const int stat = WaitFDList(fdl);
+ if (sigmask)
+ sigprocmask(SIG_SETMASK,&origmask,NULL);
+ return stat;
+ }
+ #endif
+
+ struct timespec ts, *pts = &ts;
+ u64 now_usec = GetTimeUSec(false);
+ if ( fdl->timeout_usec > now_usec )
+ {
+ const u64 delta = fdl->timeout_usec - now_usec;
+ if ( delta > 1000000ull * 0x7fffffffull )
+ pts = NULL;
+ else
+ {
+ ts.tv_sec = delta / 1000000;
+ ts.tv_nsec = delta % 1000000 * 1000;
+ }
+ }
+ else
+ ts.tv_sec = ts.tv_nsec = 0;
+
+ #ifdef __APPLE__
+ const int stat = pselect( fdl->max_fd+1, &fdl->readfds, &fdl->writefds,
+ &fdl->exceptfds, pts, sigmask );
+ #else
+ const int stat = fdl->use_poll
+ ? ppoll( fdl->poll_list, fdl->poll_used, pts, sigmask )
+ : pselect( fdl->max_fd+1, &fdl->readfds, &fdl->writefds,
+ &fdl->exceptfds, pts, sigmask );
+ #endif
+
+ fdl->now_usec = GetTimeUSec(false);
+ fdl->cur_usec = fdl->now_usec - now_usec;
+ fdl->wait_usec += fdl->cur_usec;
+ fdl->wait_count++;
+ return stat;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+mem_t CheckUnixSocketPathMem
+(
+ mem_t src, // NULL or source path to analyse
+ int tolerance // <1: 'unix:', 'file:', '/', './' and '../' detected
+ // 1: not 'NAME:' && at relast one '/'
+ // 2: not 'NAME:'
+)
+{
+ if ( src.ptr && src.len )
+ {
+ ccp ptr = src.ptr;
+ ccp end = ptr + src.len;
+
+ switch (*ptr)
+ {
+ case '/':
+ return src;
+
+ case '.':
+ {
+ ccp s = ptr+1;
+ if ( s < end && *s == '.' )
+ s++;
+ if ( s < end && *s == '/' )
+ return src;
+ }
+ break;
+
+ case 'f':
+ if ( end-ptr >= 5 && !memcmp(ptr,"file:",5) )
+ return RightMem(src,-5);
+
+ case 'u':
+ if ( end-ptr >= 5 && !memcmp(ptr,"unix:",5) )
+ return RightMem(src,-5);
+ break;
+ }
+
+ if ( tolerance > 0 )
+ {
+ while ( ptr < end && isalnum((int)*ptr) )
+ ptr++;
+ if ( ptr < end && *ptr != ':' && ( tolerance > 1 || strchr(ptr,'/')) )
+ return BehindMem(src,ptr);
+ }
+ }
+
+ return NullMem;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp CheckUnixSocketPath
+(
+ ccp src, // NULL or source path to analyse
+ int tolerance // <1: 'unix:', 'file:', '/', './' and '../' detected
+ // 1: not 'NAME:' && at relast one '/'
+ // 2: not 'NAME:'
+)
+{
+ mem_t res = CheckUnixSocketPathMem(MemByString(src),tolerance);
+ return res.ptr;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// Catch Output ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int CatchIgnoreOutput
+(
+ struct CatchOutput_t *ctrl, // control struct incl. data
+ int call_mode // 0:init, 1:new data, 2:term
+)
+{
+ DASSERT(ctrl);
+ ClearGrowBuffer(&ctrl->buf);
+ return 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void ResetCatchOutput ( CatchOutput_t *co, uint n )
+{
+ while ( n-- > 0 )
+ {
+ if ( co->pipe_fd[0] != -1 )
+ {
+ close(co->pipe_fd[0]);
+ co->pipe_fd[0] = -1;
+ }
+
+ if ( co->pipe_fd[1] != -1 )
+ {
+ close(co->pipe_fd[1]);
+ co->pipe_fd[1] = -1;
+ }
+
+ ResetGrowBuffer(&co->buf);
+ co++;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError CatchOutput
+(
+ ccp command, // command to execute
+ int argc, // num(arguments) in 'argv'; -1: argv is NULL terminated
+ char *const* argv, // list of arguments
+
+ CatchOutputFunc stdout_func, // NULL or function to catch stdout
+ CatchOutputFunc stderr_func, // NULL or function to catch stderr
+ void *user_ptr, // NULL or user defined parameter
+ bool silent // true: suppress error messages
+)
+{
+ if ( !command || !*command )
+ return ERR_NOTHING_TO_DO;
+
+
+ //--- setup streams & pipes
+
+ uint n_catch_out = 0;
+ CatchOutput_t catch_out[2];
+ memset(catch_out,0,sizeof(catch_out));
+
+ uint i;
+ for ( i = 1; i <= 2; i++ )
+ {
+ CatchOutputFunc func = i==1 ? stdout_func : stderr_func;
+ if (!func)
+ continue;
+
+ CatchOutput_t *co = catch_out + n_catch_out++;
+ co->mode = i;
+ co->func = func;
+ co->user_ptr = user_ptr;
+ InitializeGrowBuffer(&co->buf,16*KiB);
+
+ if ( pipe(co->pipe_fd) == -1 )
+ {
+ ResetCatchOutput(catch_out,n_catch_out);
+ if (!silent)
+ ERROR1(ERR_CANT_CREATE,"Can't create pipe\n");
+ return ERR_CANT_CREATE;
+ }
+ }
+
+
+ //--- setup arguments
+
+ if (!argv)
+ argc = 0;
+ else if ( argc < 0 )
+ for ( argc = 0; argv[argc]; argc++ )
+ ;
+
+ const uint local_args_size = 100;
+ char *local_args[local_args_size+2];
+ char **args = local_args;
+ if ( argc >= local_args_size )
+ args = MALLOC((argc+2)*sizeof(*args));
+
+ char **destarg = args;
+ *destarg++ = (char*)command;
+
+ for ( i = 0; i < argc; i++ )
+ *destarg++ = argv[i];
+ *destarg = 0;
+
+ #ifdef TEST
+ {
+ printf("EXEC '%s' [argc=%u]\n",command,argc);
+ uint i;
+ for ( i = 0; i < argc+2; i++ )
+ printf("%3d: %s\n",i,args[i]);
+ }
+ #endif
+
+
+ //--- fork
+
+ enumError err = ERR_OK;
+
+ int fork_stat = fork();
+ if ( fork_stat < 0 )
+ {
+ if (!silent)
+ ERROR1(ERR_ERROR,"Can't fork: %s\n",command);
+ err = ERR_ERROR;
+ }
+ else if (fork_stat)
+ {
+ PRINT("WAIT FOR %d\n",fork_stat);
+
+ CatchOutput_t *co, *co_end = catch_out + n_catch_out;
+ for ( co = catch_out; co < co_end; co++ )
+ {
+ DASSERT(co->func);
+ co->func(co,0);
+ close(co->pipe_fd[1]); // close childs (write) end
+ co->pipe_fd[1] = -1;
+ }
+
+ FDList_t fdl;
+ InitializeFDList(&fdl,false);
+ char buf[0x1000];
+
+ for(;;)
+ {
+ ClearFDList(&fdl);
+ fdl.timeout_usec = GetTimeUSec(false) + 60*USEC_PER_SEC;
+ for ( co = catch_out; co < co_end; co++ )
+ AddFDList(&fdl,co->pipe_fd[0],POLLIN);
+ if ( fdl.max_fd == -1 )
+ break;
+
+ const int stat = WaitFDList(&fdl);
+ if ( stat > 0 )
+ {
+ for ( co = catch_out; co < co_end; co++ )
+ {
+ int fd = co->pipe_fd[0];
+ uint event = GetEventFDList(&fdl,fd,0);
+ if ( event & POLLIN )
+ {
+ ssize_t read_size = read(fd,buf,sizeof(buf));
+ PRINT("READ %d[%d], %zd bytes\n",co->mode,fd,read_size);
+ if ( read_size > 0 )
+ {
+ PrepareGrowBuffer(&co->buf,read_size,true);
+ InsertGrowBuffer(&co->buf,buf,read_size);
+ if (co->func)
+ co->func(co,1);
+ }
+ else
+ {
+ close(fd);
+ co->pipe_fd[0] = -1;
+ }
+ }
+ }
+ }
+ }
+ ResetFDList(&fdl);
+
+ int wait_stat;
+ for(;;)
+ {
+ int pid = waitpid(fork_stat,&wait_stat,0);
+
+ PRINT("RESUME: %d = %x: exited=%d, wait_stat=%d [%s]\n",
+ wait_stat, wait_stat, WIFEXITED(wait_stat),
+ WEXITSTATUS(wait_stat), GetErrorName(WEXITSTATUS(wait_stat),"?") );
+ if ( pid == fork_stat )
+ break;
+ }
+
+ for ( co = catch_out; co < co_end; co++ )
+ if (co->func)
+ co->func(co,2);
+ ResetCatchOutput(catch_out,n_catch_out);
+
+ if (WIFEXITED(wait_stat))
+ return WEXITSTATUS(wait_stat);
+ if (!silent)
+ ERROR1(ERR_SYNTAX,"Child teminated with error: %s\n",command);
+ return ERR_SYNTAX;
+ }
+ else
+ {
+ PRINT("CHILD\n");
+
+ CatchOutput_t *co, *co_end = catch_out + n_catch_out;
+ for ( co = catch_out; co < co_end; co++ )
+ {
+ close(co->pipe_fd[0]); // close parents (read) end
+ co->pipe_fd[0] = -1;
+
+ if ( dup2(co->pipe_fd[1],co->mode) == -1 )
+ {
+ if (!silent)
+ ERROR1(ERR_CANT_CREATE,"dup2(,%d) failed: %s\n",
+ co->mode, command );
+ _exit(ERR_CANT_CREATE);
+ }
+ }
+
+ execvp(command,args);
+ if (!silent)
+ ERROR1(ERR_CANT_CREATE,"Can't create process: %s\n",command);
+ _exit(ERR_CANT_CREATE);
+ }
+
+ if ( args != local_args )
+ FREE(args);
+ return err;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+enumError CatchOutputLine
+(
+ ccp command_line, // command line to execute
+ CatchOutputFunc stdout_func, // NULL or function to catch stdout
+ CatchOutputFunc stderr_func, // NULL or function to catch stderr
+ void *user_ptr, // NULL or user defined parameter
+ bool silent // true: suppress error messages
+)
+{
+ SplitArg_t sa;
+ ScanSplitArg(&sa,true,command_line,0,0);
+
+ enumError err = ERR_SYNTAX;
+ if ( sa.argc > 1 )
+ err = CatchOutput( sa.argv[1], sa.argc-2, sa.argv+2,
+ stdout_func, stderr_func, user_ptr, silent );
+ ResetSplitArg(&sa);
+ return err;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// scan sections ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool FindSection
+(
+ // search file until next section found
+ // => read line and store data into 'si'
+ // => return TRUE, if section found
+
+ SectionInfo_t *si, // valid section info
+ char *buf, // use this buffer for line scanning
+ uint buf_size, // size of 'buf'
+ bool scan_buf // true: buffer contains already a valid
+ // and NULL terminated line
+)
+{
+ DASSERT(si);
+ DASSERT(si->f);
+ DASSERT(buf);
+ DASSERT( buf_size > 10 );
+
+ FreeString(si->section);
+ FreeString(si->path);
+ si->section = si->path = 0;
+ si->index = -1;
+ ResetParamField(&si->param);
+
+ buf_size--;
+ for(;;)
+ {
+ if ( !scan_buf && !fgets(buf,buf_size,si->f) )
+ break;
+ buf[buf_size] = 0;
+ scan_buf = false;
+
+ char *ptr = buf;
+ while ( *ptr > 0 && *ptr <= ' ' )
+ ptr++;
+
+ if ( *ptr != '[' )
+ continue;
+
+ ccp sect = ++ptr;
+ while ( isalnum((int)*ptr) || *ptr == '-' || *ptr == '_' )
+ ptr++;
+
+ ccp path = ptr;
+ if ( *ptr == '/' )
+ {
+ *ptr++ = 0;
+ path = ptr;
+ while ( *ptr > ' ' && *ptr != ':' && *ptr != ']' )
+ ptr++;
+ }
+
+ int index = -1;
+ if ( *ptr == ':' )
+ {
+ *ptr++ = 0;
+ index = str2ul(ptr,&ptr,10);
+ }
+
+ if ( *ptr != ']' )
+ continue;
+ *ptr = 0;
+
+ si->section = STRDUP(sect);
+ if (*path)
+ si->path = STRDUP(path);
+ si->index = index;
+ return true;
+ }
+
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int ScanSections
+(
+ // return the last returned value by On*()
+
+ FILE *f, // source file
+ SectionFunc OnSection, // not NULL: call this function for each section
+ // on result: <0: abort, 0:next section, 1:scan param
+ SectionFunc OnParams, // not NULL: call this function after param scan
+ // on result: <0: abort, continue
+ void *user_param // user defined parameter
+
+)
+{
+ DASSERT(f);
+
+ SectionInfo_t si;
+ memset(&si,0,sizeof(si));
+ si.f = f;
+ si.user_param = user_param;
+ InitializeParamField(&si.param);
+ si.param.free_data = true;
+
+ int stat = 0;
+ char buf[10000];
+ bool scan_buf = false;
+ while (FindSection(&si,buf,sizeof(buf),scan_buf))
+ {
+ scan_buf = false;
+ if (OnSection)
+ {
+ int stat = OnSection(&si);
+ if ( stat < 0 )
+ break;
+ if (!stat)
+ continue;
+ }
+
+ if (!OnParams)
+ continue;
+
+ // scan param
+
+ while (fgets(buf,sizeof(buf)-1,f))
+ {
+ buf[sizeof(buf)-1] = 0;
+ char *ptr = buf;
+ while ( *ptr > 0 && *ptr <= ' ' )
+ ptr++;
+ if ( *ptr == '[' )
+ {
+ scan_buf = true;
+ break;
+ }
+
+ //--- scan name
+ ccp name = ptr;
+ while ( *ptr > ' ' && *ptr != '=' )
+ ptr++;
+ char *name_end = ptr;
+
+ //--- scan param
+ while ( *ptr == ' ' || *ptr == '\t' )
+ ptr++;
+ if ( *ptr != '=' )
+ continue;
+ if ( *++ptr == ' ' ) // skip max 1 space
+ ptr++;
+
+ ccp value = ptr;
+ while ( *ptr && *ptr != '\n' && *ptr != '\r' )
+ ptr++;
+
+ *name_end = *ptr = 0;
+ InsertParamField(&si.param,name,false,0,STRDUP(value));
+ }
+
+ stat = OnParams(&si);
+ if ( stat < 0 )
+ break;
+ }
+
+ FreeString(si.section);
+ FreeString(si.path);
+ ResetParamField(&si.param);
+ return stat;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// scan configuration ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+enumError RestoreState
+(
+ const RestoreStateTab_t
+ *table, // section table, terminated by name==0
+ void *data, // file data, modified, terminated by NULL or LF
+ uint size, // size of file data
+ RestoreStateLog_t log_mode, // print warnings (bit field)
+ FILE *log_file, // error file; if NULL use stderr
+ cvp user_param // pointer provided by user
+)
+{
+ DASSERT(data||!size);
+ if (!size)
+ return ERR_OK;
+
+ if (!log_file)
+ log_file = stderr;
+
+ char *ptr = data;
+ char *end = ptr + size;
+ if ( !*end && *end != '\n' )
+ end[-1] = 0;
+
+ while ( ptr < end )
+ {
+ //--- search next section
+
+ while ( ptr < end && (uchar)*ptr <= ' ' )
+ ptr++;
+ if ( *ptr != '[' )
+ {
+ while ( ptr < end && *ptr && *ptr != '\n' )
+ ptr++;
+ if ( ptr < end )
+ ptr++;
+ continue;
+ }
+
+ ccp sect = ++ptr;
+ while ( isalnum((int)*ptr) || *ptr == '-' || *ptr == '_' )
+ ptr++;
+
+ ccp path = ptr;
+ if ( *ptr == '/' )
+ {
+ *ptr++ = 0;
+ path = ptr;
+ while ( isalnum((int)*ptr) || *ptr == '-' || *ptr == '_' || *ptr == '/' )
+ ptr++;
+ }
+
+ int index = -1;
+ if ( *ptr == ':' )
+ {
+ *ptr++ = 0;
+ index = strtoul(ptr,0,10);
+ }
+ *ptr = 0;
+
+ const RestoreStateTab_t *tab;
+ for ( tab = table; tab->name; tab++ )
+ if (!strcmp(tab->name,sect))
+ break;
+
+ if (!tab->name)
+ {
+ if ( log_mode & RSL_WARNINGS )
+ fprintf(log_file,"! #RESTORE: Unknown section: %s\n",sect);
+ continue;
+ }
+
+ TRACE("SECT: [%s/%s:%d]\n",sect,path,index);
+ if (!tab->func) // no need to analyse section
+ continue;
+
+
+ //--- scan name=value
+
+ RestoreState_t rs;
+ rs.sect = sect;
+ rs.path = path;
+ rs.index = index;
+ rs.user_param = user_param;
+ rs.user_info = 0;
+ rs.log_mode = log_mode;
+ rs.log_file = log_file;
+ InitializeParamField(&rs.param);
+
+ while ( ptr < end )
+ {
+ //--- next line
+ while ( ptr < end && *ptr && *ptr != '\n' )
+ ptr++;
+
+ //--- skip lines and blanks
+ while ( ptr < end && (uchar)*ptr <= ' ' )
+ ptr++;
+
+ if ( *ptr == '[' )
+ break;
+
+ //--- scan name
+ ccp name = ptr;
+ while ( isalnum((int)*ptr) || *ptr == '-' || *ptr == '_' || *ptr == '@' )
+ ptr++;
+ char *name_end = ptr;
+
+ //--- scan param
+ while ( *ptr == ' ' || *ptr == '\t' )
+ ptr++;
+ if ( *ptr != '=' )
+ continue;
+ if ( *++ptr == ' ' ) // skip max 1 space
+ ptr++;
+
+ ccp value = ptr;
+ while ( (uchar)*ptr >= ' ' || *ptr == '\t' )
+ ptr++;
+
+ *name_end = *ptr = 0;
+ InsertParamField(&rs.param,name,false,0,value);
+ }
+
+ #ifdef TEST0
+ {
+ ParamFieldItem_t *ptr = rs.param.field, *end;
+ for ( end = ptr + rs.param.used; ptr < end; ptr++ )
+ printf("%4u: %20s = %s|\n",ptr->num,ptr->key,(ccp)ptr->data);
+ }
+ #endif
+
+ tab->func(&rs,tab->user_table);
+ if ( log_mode & RSL_UNUSED_NAMES )
+ {
+ ParamFieldItem_t *ptr = rs.param.field, *end;
+ for ( end = ptr + rs.param.used; ptr < end; ptr++ )
+ if (!ptr->num)
+ {
+ const uint len = strlen((ccp)ptr->data);
+ if ( len > 20 )
+ fprintf(log_file,"! #RESTORE[%s]: Unused: %s [%u] %.20s...\n",
+ PrintRestoreStateSection(&rs),
+ ptr->key, len, (ccp)ptr->data );
+ else
+ fprintf(log_file,"! #RESTORE[%s]: Unused: %s [%u] %s\n",
+ PrintRestoreStateSection(&rs),
+ ptr->key, len, (ccp)ptr->data );
+ }
+ }
+ ResetParamField(&rs.param);
+ }
+
+ return ERR_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintRestoreStateSection ( const RestoreState_t *rs )
+{
+ // print into cyclic buffer
+ DASSERT(rs);
+
+ uint len = strlen(rs->sect) + strlen(rs->path) + 2;
+
+ char index[20];
+ *index = 0;
+ if ( rs->index >= 0 )
+ len += snprintf(index,sizeof(index),":%u",rs->index);
+
+ char *buf = GetCircBuf(len);
+ if (*rs->path)
+ snprintf(buf,len,"%s/%s%s",rs->sect,rs->path,index);
+ else
+ snprintf(buf,len,"%s%s",rs->sect,index);
+
+ return buf;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+ParamFieldItem_t * GetParamField
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name // name of member
+)
+{
+ DASSERT(rs);
+ DASSERT(name);
+
+ ParamFieldItem_t *it = FindParamField(&rs->param,name);
+ if (it)
+ it->num++;
+ else if ( rs->log_mode & RSL_MISSED_NAMES )
+ fprintf(rs->log_file,"! #RESTORE[%s]: Missed: %s\n",
+ PrintRestoreStateSection(rs), name );
+
+ return it;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+int GetParamFieldINT
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ int not_found // return this value if not found
+)
+{
+ DASSERT(rs);
+ DASSERT(name);
+
+ ParamFieldItem_t *it = GetParamField(rs,name);
+ return it ? str2l((ccp)it->data,0,10) : not_found;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint GetParamFieldUINT
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ uint not_found // return this value if not found
+)
+{
+ DASSERT(rs);
+ DASSERT(name);
+
+ ParamFieldItem_t *it = GetParamField(rs,name);
+ return it ? str2ul((ccp)it->data,0,10) : not_found;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u64 GetParamFieldS64
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ s64 not_found // return this value if not found
+)
+{
+ DASSERT(rs);
+ DASSERT(name);
+
+ ParamFieldItem_t *it = GetParamField(rs,name);
+ return it ? str2ll((ccp)it->data,0,10) : not_found;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+u64 GetParamFieldU64
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ u64 not_found // return this value if not found
+)
+{
+ DASSERT(rs);
+ DASSERT(name);
+
+ ParamFieldItem_t *it = GetParamField(rs,name);
+ return it ? str2ull((ccp)it->data,0,10) : not_found;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+float GetParamFieldFLOAT
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ float not_found // return this value if not found
+)
+{
+ DASSERT(rs);
+ DASSERT(name);
+
+ ParamFieldItem_t *it = GetParamField(rs,name);
+ return it ? strtof((ccp)it->data,0) : not_found;
+}
+
+//-----------------------------------------------------------------------------
+
+double GetParamFieldDBL
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ double not_found // return this value if not found
+)
+{
+ DASSERT(rs);
+ DASSERT(name);
+
+ ParamFieldItem_t *it = GetParamField(rs,name);
+ return it ? strtod((ccp)it->data,0) : not_found;
+}
+
+//-----------------------------------------------------------------------------
+
+long double GetParamFieldLDBL
+(
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ long double not_found // return this value if not found
+)
+{
+ DASSERT(rs);
+ DASSERT(name);
+
+ ParamFieldItem_t *it = GetParamField(rs,name);
+ return it ? strtold((ccp)it->data,0) : not_found;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+int GetParamFieldBUF
+(
+ // returns >=0: length of read data; -1:nothing done (not_found==NULL)
+
+ char *buf, // buffer to store result
+ uint buf_size, // size of buffer
+
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ EncodeMode_t decode, // decoding mode, fall back to OFF
+ // supported: STRING, UTF8, BASE64, BASE64X
+ ccp not_found // not NULL: store this value if not found
+)
+{
+ DASSERT(buf);
+ DASSERT(buf_size>1);
+ DASSERT(rs);
+ DASSERT(name);
+
+ ParamFieldItem_t *it = GetParamField(rs,name);
+ if (!it)
+ return not_found ? StringCopyS(buf,buf_size,not_found) - buf : -1;
+
+ return DecodeByMode(buf,buf_size,(ccp)it->data,-1,decode);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+mem_t GetParamFieldMEM
+(
+ // Returns the decoded 'source'. Result is NULL-terminated.
+ // It points either to 'buf' or is alloced (on buf==NULL or to less space)
+ // If alloced (mem.ptr!=buf) => call FreeMem(&mem)
+
+ char *buf, // buffer to store result
+ uint buf_size, // size of buffer
+
+ const RestoreState_t *rs, // valid restore-state structure
+ ccp name, // name of member
+ EncodeMode_t decode, // decoding mode, fall back to OFF
+ // supported: STRING, UTF8, BASE64, BASE64X
+ mem_t not_found // not NULL: return this value
+)
+{
+ DASSERT(rs);
+ DASSERT(name);
+
+ ParamFieldItem_t *it = GetParamField(rs,name);
+ return it
+ ? DecodeByModeMem(buf,buf_size,(ccp)it->data,-1,decode)
+ : not_found;
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// save + restore config by table ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void DumpStateTable
+(
+ FILE *f, // valid output file
+ int indent,
+ const SaveRestoreTab_t
+ *srt // list of variables
+)
+{
+ if ( !f || !srt )
+ return;
+
+ indent = NormalizeIndent(indent);
+ for ( ; srt->type != SRT__TERM; srt++ )
+ {
+ if ( srt->type == SRT__IS_LIST )
+ {
+ if ( srt->name )
+ fprintf(f,"%*s# %s\n",indent,"",srt->name);
+ else
+ putc('\n',f);
+ }
+ else
+ fprintf(f,"%*s%4u %4u *%-3u %2u %2u %s\n",
+ indent,"",
+ srt->offset, srt->size, srt->n_elem, srt->type, srt->emode,
+ srt->name ? srt->name : "-" );
+ }
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+static void print_scs_name ( FILE *f, int fw, int use_tab, ccp format, ... )
+ __attribute__ ((__format__(__printf__,4,5)));
+
+static void print_scs_name ( FILE *f, int fw, int use_tab, ccp format, ... )
+{
+ va_list arg;
+ va_start(arg,format);
+ const int len = vfprintf(f,format,arg);
+ va_end(arg);
+
+ if (use_tab)
+ {
+ const int tablen = ( use_tab - len ) / 8;
+ fprintf(f,"%.*s= ", tablen<0 ? 0 : tablen, Tabs20 );
+ }
+ else
+ fprintf(f,"%*s= ", len < fw ? fw-len : 0, "" );
+}
+
+//-----------------------------------------------------------------------------
+
+void SaveCurrentStateByTable
+(
+ FILE *f, // valid output file
+ cvp data0, // valid pointer to source struct
+ const SaveRestoreTab_t
+ *srt, // list of variables
+ ccp prefix, // NULL or prefix for names
+ uint fw_name // field width of name, 0=AUTO
+ // tabs are used for multiple of 8 and for AUTO
+)
+{
+ DASSERT(data0);
+ DASSERT(srt);
+ if ( !f || !data0 || !srt )
+ return;
+
+ if (!prefix)
+ prefix = EmptyString;
+ const uint prelen = strlen(prefix);
+
+ if (!fw_name)
+ {
+ const SaveRestoreTab_t *ptr;
+ for ( ptr = srt; ptr->type != SRT__TERM; ptr++ )
+ if (ptr->name)
+ {
+ int slen = strlen(ptr->name);
+ switch(srt->type)
+ {
+ case SRT_STRING_FIELD:
+ case SRT_PARAM_FIELD:
+ slen += 3;
+ break;
+ }
+
+ if ( fw_name < slen )
+ fw_name = slen;
+ }
+ fw_name += prelen;
+ fw_name = (fw_name+7)/8*8;
+ }
+
+ const int use_tab = (fw_name&7) ? 0 : (fw_name/8)*8+7;
+
+ //-------------------------------------------------------------------------
+
+ u64 get_uint ( const u8* data, int idx, const SaveRestoreTab_t *srt )
+ {
+ DASSERT(srt);
+ DASSERT(srt->var);
+
+ switch(srt->size)
+ {
+ case 1: return ((u8*) (data+srt->offset))[idx];
+ case 2: return ((u16*)(data+srt->offset))[idx];
+ case 4: return ((u32*)(data+srt->offset))[idx];
+ case 8: return ((u64*)(data+srt->offset))[idx];
+ }
+ return 0;
+ }
+
+ //-------------------------------------------------------------------------
+
+ s64 get_int ( const u8* data, int idx, const SaveRestoreTab_t *srt )
+ {
+ DASSERT(srt);
+ DASSERT(srt->var);
+
+ switch(srt->size)
+ {
+ case 1: return ((s8*) (data+srt->offset))[idx];
+ case 2: return ((s16*)(data+srt->offset))[idx];
+ case 4: return ((s32*)(data+srt->offset))[idx];
+ case 8: return ((s64*)(data+srt->offset))[idx];
+ }
+ return 0;
+ }
+
+ //-------------------------------------------------------------------------
+
+ void save_string ( FILE *f, ccp src, int slen, EncodeMode_t emode )
+ {
+ char buf[4000];
+ mem_t res = EncodeByModeMem(buf,sizeof(buf),src,slen,emode);
+
+ fwrite(res.ptr,1,res.len,f);
+ fputc('\n',f);
+ if ( res.ptr != buf )
+ FreeMem(&res);
+ }
+
+ //-------------------------------------------------------------------------
+
+ const u8 *data = data0;
+
+ for ( ; srt->type != SRT__TERM; srt++ )
+ {
+ if ( srt->type == SRT__IS_LIST )
+ {
+ if ( srt->name )
+ fprintf(f,"# %s\n",srt->name);
+ else
+ putc('\n',f);
+ continue;
+ }
+
+ if (!srt->name)
+ continue;
+
+ if ( srt->type < SRT__IS_LIST )
+ print_scs_name(f,fw_name,use_tab,"%s%s",prefix,srt->name);
+
+ switch(srt->type)
+ {
+ case SRT_BOOL:
+ if ( srt->n_elem > 1 )
+ {
+ uint i;
+ for ( i = 0; i < srt->n_elem; i++ )
+ fprintf(f,"%s%u", i ? "," : "", get_uint(data,i,srt)!=0 );
+ fputc('\n',f);
+ }
+ else
+ fprintf(f,"%u\n",get_uint(data,0,srt)!=0);
+ break;
+
+ case SRT_UINT:
+ if ( srt->n_elem > 1 )
+ {
+ uint i;
+ for ( i = 0; i < srt->n_elem; i++ )
+ fprintf(f,"%s%llu", i ? "," : "", get_uint(data,i,srt) );
+ fputc('\n',f);
+ }
+ else
+ fprintf(f,"%llu\n",get_uint(data,0,srt));
+ break;
+
+ case SRT_HEX:
+ if ( srt->n_elem > 1 )
+ {
+ uint i;
+ for ( i = 0; i < srt->n_elem; i++ )
+ fprintf(f,"%s%#llx", i ? "," : "", get_uint(data,i,srt) );
+ fputc('\n',f);
+ }
+ else
+ fprintf(f,"%#llx\n",get_uint(data,0,srt));
+ break;
+
+ case SRT_INT:
+ if ( srt->n_elem > 1 )
+ {
+ uint i;
+ for ( i = 0; i < srt->n_elem; i++ )
+ fprintf(f,"%s%lld", i ? "," : "", get_int(data,i,srt) );
+ fputc('\n',f);
+ }
+ else
+ fprintf(f,"%lld\n",get_int(data,0,srt));
+ break;
+
+ case SRT_FLOAT:
+ switch(srt->size)
+ {
+ case sizeof(float):
+ {
+ uint i;
+ for ( i = 0; i < srt->n_elem; i++ )
+ fprintf(f,"%s%.8g", i ? "," : "",
+ ((float*)(data+srt->offset))[i] );
+ }
+ break;
+
+ case sizeof(double):
+ {
+ uint i;
+ for ( i = 0; i < srt->n_elem; i++ )
+ fprintf(f,"%s%.16g", i ? "," : "",
+ ((double*)(data+srt->offset))[i] );
+ }
+ break;
+
+ case sizeof(long double):
+ {
+ uint i;
+ for ( i = 0; i < srt->n_elem; i++ )
+ fprintf(f,"%s%.20Lg", i ? "," : "",
+ ((long double*)(data+srt->offset))[i] );
+ }
+ break;
+
+ default:
+ fputs("0.0",f);
+ }
+ fputc('\n',f);
+ break;
+
+ case SRT_XFLOAT:
+ switch(srt->size)
+ {
+ case sizeof(float):
+ {
+ uint i;
+ for ( i = 0; i < srt->n_elem; i++ )
+ fprintf(f,"%s%a", i ? "," : "",
+ ((float*)(data+srt->offset))[i] );
+ }
+ break;
+
+ case sizeof(double):
+ {
+ uint i;
+ for ( i = 0; i < srt->n_elem; i++ )
+ fprintf(f,"%s%a", i ? "," : "",
+ ((double*)(data+srt->offset))[i] );
+ }
+ break;
+
+ case sizeof(long double):
+ {
+ uint i;
+ for ( i = 0; i < srt->n_elem; i++ )
+ fprintf(f,"%s%La", i ? "," : "",
+ ((long double*)(data+srt->offset))[i] );
+ }
+ break;
+
+ default:
+ fputs("0.0",f);
+ }
+ fputc('\n',f);
+ break;
+
+ //-----
+
+ case SRT_STRING_SIZE:
+ save_string(f,(char*)(data+srt->offset),-1,srt->emode);
+ break;
+
+ case SRT_STRING_ALLOC:
+ save_string(f,*(ccp*)(data+srt->offset),-1,srt->emode);
+ break;
+
+ case SRT_MEM:
+ {
+ mem_t *mem = (mem_t*)(data+srt->offset);
+ save_string(f,mem->ptr,mem->len,srt->emode);
+ break;
+ }
+
+ //-----
+
+ case SRT_STRING_FIELD:
+ {
+ print_scs_name(f,fw_name,use_tab,"%s%s@n",prefix,srt->name);
+ const StringField_t *sf = (StringField_t*)(data+srt->offset);
+ fprintf(f,"%u\n",sf->used);
+
+ int i;
+ ccp *ptr = sf->field;
+ for ( i = 0; i < sf->used; i++, ptr++ )
+ {
+ print_scs_name(f,fw_name,use_tab,"%s%s@%u",prefix,srt->name,i);
+ save_string(f,*ptr,-1,srt->emode);
+ }
+ break;
+ }
+
+ case SRT_PARAM_FIELD:
+ {
+ print_scs_name(f,fw_name,use_tab,"%s%s@n",prefix,srt->name);
+ const ParamField_t *pf = (ParamField_t*)(data+srt->offset);
+ fprintf(f,"%u\n",pf->used);
+
+ int i;
+ ParamFieldItem_t *ptr = pf->field;
+ for ( i = 0; i < pf->used; i++, ptr++ )
+ {
+ print_scs_name(f,fw_name,use_tab,"%s%s@%u",prefix,srt->name,i);
+ fprintf(f,"%d ",ptr->num);
+ save_string(f,ptr->key,-1,srt->emode);
+ }
+ break;
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+void RestoreStateByTable
+(
+ RestoreState_t *rs, // info data, can be modified (cleaned after call)
+ void *data0, // valid pointer to destination struct
+ const SaveRestoreTab_t *srt, // list of variables
+ ccp prefix // NULL or prefix for names
+)
+{
+ DASSERT(rs);
+ DASSERT(srt);
+
+ char buf[4000];
+ if (!prefix)
+ prefix = EmptyString;
+
+ for ( ; srt->type != SRT__TERM; srt++ )
+ {
+ if ( srt->type == SRT__IS_LIST || !srt->name )
+ continue;
+
+ const u8 *data = data0;
+
+ if ( srt->type < SRT__IS_LIST )
+ StringCat2S(buf,sizeof(buf),prefix,srt->name);
+
+ switch(srt->type)
+ {
+ case SRT_BOOL:
+ case SRT_UINT:
+ case SRT_HEX:
+ {
+ ParamFieldItem_t *it = GetParamField(rs,buf);
+ if (!it)
+ break;
+
+ char *src = (char*)it->data;
+ u8 *val = (u8*)(data+srt->offset);
+ uint i = 0;
+ while (*src)
+ {
+ u64 num = str2ull(src,&src,10);
+ switch(srt->size)
+ {
+ case 1: *(u8*) val = num; break;
+ case 2: *(u16*)val = num; break;
+ case 4: *(u32*)val = num; break;
+ case 8: *(u64*)val = num; break;
+ }
+ if ( ++i == srt->n_elem )
+ break;
+ val += srt->size;
+ if ( *src == ',' )
+ src++;
+ }
+ break;
+ }
+
+ case SRT_INT:
+ {
+ ParamFieldItem_t *it = GetParamField(rs,buf);
+ if (!it)
+ break;
+
+ char *src = (char*)it->data;
+ u8 *val = (u8*)(data+srt->offset);
+ uint i = 0;
+ while (*src)
+ {
+ s64 num = str2ll(src,&src,10);
+ switch(srt->size)
+ {
+ case 1: *(s8*) val = num; break;
+ case 2: *(s16*)val = num; break;
+ case 4: *(s32*)val = num; break;
+ case 8: *(s64*)val = num; break;
+ }
+ if ( ++i == srt->n_elem )
+ break;
+ val += srt->size;
+ if ( *src == ',' )
+ src++;
+ }
+ break;
+ }
+
+ case SRT_FLOAT:
+ case SRT_XFLOAT:
+ {
+ ParamFieldItem_t *it = GetParamField(rs,buf);
+ if (!it)
+ break;
+
+ char *src = (char*)it->data;
+ u8 *val = (u8*)(data+srt->offset);
+ uint i = 0;
+ while (*src)
+ {
+ switch(srt->size)
+ {
+ case sizeof(float): *(float*)val = strtof(src,&src); break;
+ case sizeof(double): *(double*)val = strtod(src,&src); break;
+ case sizeof(long double): *(long double*)val = strtold(src,&src); break;
+ }
+ if ( ++i == srt->n_elem )
+ break;
+ val += srt->size;
+ if ( *src == ',' )
+ src++;
+ }
+ break;
+ }
+
+ //-----
+
+ case SRT_STRING_SIZE:
+ {
+ char *val = (char*)(data+srt->offset);
+ mem_t res = GetParamFieldMEM(buf,sizeof(buf),rs,buf,
+ srt->emode,MemByString(val));
+ StringCopyS(val,srt->size,res.ptr);
+ if ( res.ptr != buf && res.ptr != val )
+ FreeString(res.ptr);
+ break;
+ }
+
+ case SRT_STRING_ALLOC:
+ {
+ ccp *val = (ccp*)(data+srt->offset);
+ mem_t res = GetParamFieldMEM(buf,sizeof(buf),rs,buf,
+ srt->emode,MemByString0(*val));
+ if ( res.ptr != *val )
+ {
+ FreeString(*val);
+ *val = MEMDUP(res.ptr,res.len);
+ }
+ else if ( res.ptr != buf )
+ FreeString(res.ptr);
+ break;
+ }
+
+ case SRT_MEM:
+ {
+ mem_t *val = (mem_t*)(data+srt->offset);
+ mem_t res = GetParamFieldMEM(buf,sizeof(buf),rs,buf,srt->emode,*val);
+ if ( res.ptr != val->ptr )
+ {
+ FreeString(val->ptr);
+ *val = res.ptr == buf ? DupMem(res) : res;
+ }
+ else if ( res.ptr != buf )
+ FreeString(res.ptr);
+ break;
+ }
+
+ //-----
+
+ case SRT_STRING_FIELD:
+ {
+ StringField_t *sf = (StringField_t*)(data+srt->offset);
+ ResetStringField(sf);
+
+ snprintf(buf,sizeof(buf),"%s%s@n",prefix,srt->name);
+ const int n = GetParamFieldINT(rs,buf,0);
+ int i;
+ for ( i = 0; i < n; i++ )
+ {
+ snprintf(buf,sizeof(buf),"%s%s@%u",prefix,srt->name,i);
+ mem_t res = GetParamFieldMEM(buf,sizeof(buf),rs,buf,srt->emode,NullMem);
+ InsertStringField(sf,res.ptr,res.ptr!=buf);
+ }
+ break;
+ }
+
+ case SRT_PARAM_FIELD:
+ {
+ ParamField_t *sf = (ParamField_t*)(data+srt->offset);
+ ResetParamField(sf);
+
+ snprintf(buf,sizeof(buf),"%s%s@n",prefix,srt->name);
+ const int n = GetParamFieldINT(rs,buf,0);
+ int i;
+ for ( i = 0; i < n; i++ )
+ {
+ snprintf(buf,sizeof(buf),"%s%s@%u",prefix,srt->name,i);
+ ParamFieldItem_t *it = GetParamField(rs,buf);
+ if (it)
+ {
+ char *str;
+ const int num = strtol((ccp)it->data,&str,10);
+ if ( *str == ' ' ) // space should always exist
+ str++;
+
+ mem_t res = DecodeByModeMem(buf,sizeof(buf),str,-1,srt->emode);
+ InsertParamField(sf,res.ptr,false,num,0);
+ if ( res.ptr != buf )
+ FreeString(res.ptr);
+ }
+ }
+ break;
+ }
+ }
+ }
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// scan socket type ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+bool ScanSocketInfo
+(
+ // Syntax: [ PREFIX ',' ]... [ PREFIX ':' ] address
+ // returns TRUE, if a protocol or a type is found
+
+ socket_info_t *si, // result, not NULL, will be initialized
+ ccp address, // address to analyze
+ uint tolerance // tolerace:
+ // 0: invalid without prefix
+ // 1: analyse beginning of address part:
+ // '/' or './' or '../' -> AF_UNIX
+ // 2: estimate type
+ // '/' before first ':' -> AF_UNIX
+ // 1.2 | 1.2.3 | 1.2.3.4 -> AF_INET
+ // a HEX:IP combi -> AF_INET6
+ // domain name -> AF_INET_ANY
+)
+{
+ DASSERT(si);
+ InitializeSocketInfo(si);
+
+ if (!address)
+ return false;
+
+ while ( *address && (uchar)*address <= ' ' )
+ address++;
+
+ static const KeywordTab_t keytab[] =
+ {
+ { -1, "STREAM", 0, SOCK_STREAM },
+ { -1, "DGRAM", "DATAGRAM", SOCK_DGRAM },
+
+ { AF_UNIX, "UNIX", "FILE", -1 },
+ { AF_UNIX, "UNIX-TCP", "FILE-TCP", SOCK_STREAM },
+ { AF_UNIX, "UNIX-STREAM", "FILE-STREAM", SOCK_STREAM },
+ { AF_UNIX, "UNIX-UDP", "FILE-UDP", SOCK_DGRAM },
+ { AF_UNIX, "UNIX-DGRAM", "FILE-DGRAM", SOCK_DGRAM },
+ { AF_UNIX, "UNIX-DATAGRAM","FILE-DATAGRAM",SOCK_DGRAM },
+ { AF_UNIX, "UNIX-SEQ", "FILE-SEQ", SOCK_SEQPACKET },
+ { AF_UNIX, "SEQ", 0, SOCK_SEQPACKET },
+
+ { AF_INET_ANY, "IP", 0, -1 },
+ { AF_INET_ANY, "TCP", 0, SOCK_STREAM },
+ { AF_INET_ANY, "UDP", 0, SOCK_DGRAM },
+ { AF_INET_ANY, "RAW", 0, SOCK_RAW },
+
+ { AF_INET, "IP4", "IPV4", -1 },
+ { AF_INET, "TCP4", "TCPV4", SOCK_STREAM },
+ { AF_INET, "UDP4", "UDPV4", SOCK_DGRAM },
+ { AF_INET, "RAW4", "RAWV4", SOCK_RAW },
+
+ { AF_INET6, "IP6", "IPV6", -1 },
+ { AF_INET6, "TCP6", "TCPV6", SOCK_STREAM },
+ { AF_INET6, "UDP6", "UDPV6", SOCK_DGRAM },
+ { AF_INET6, "RAW6", "RAWV6", SOCK_RAW },
+
+ {0,0,0,0}
+ };
+
+ bool ok = false;
+ int proto = -1;
+ int type = -1;
+ ccp start = address;
+ for(;;)
+ {
+ char namebuf[10];
+ char *dest = namebuf;
+ ccp ptr = start;
+ while ( dest < namebuf + sizeof(namebuf) - 1
+ && isalnum((int)*ptr) || *ptr == '-' )
+ {
+ *dest++ = *ptr++;
+ }
+ if ( ptr == start || *ptr != ',' && *ptr != ':' )
+ break;
+
+ *dest = 0;
+ int abbrev;
+ const KeywordTab_t *k = ScanKeyword(&abbrev,namebuf,keytab);
+ if ( !k || abbrev )
+ break;
+
+ if ( k->id == AF_INET_ANY ? ( proto == -1 ) : ( k->id >= 0 ) )
+ proto = k->id;
+ if ( k->opt >= 0 )
+ type = k->opt;
+
+ noPRINT("%3lld %3lld |%s|%s| -> %d,%d\n",
+ k->id, k->opt, k->name1, k->name2 ? k->name2 : "",
+ proto, type );
+ start = ptr;
+ if ( *start++ == ':' )
+ {
+ ok = true;
+ break;
+ }
+ }
+
+ if (!ok)
+ {
+ start = address;
+ proto = -1;
+ type = -1;
+ }
+
+ if ( proto < 0 && tolerance > 0 )
+ {
+ if ( *start == '/' || !memcmp(start,"./",2) || !memcmp(start,"../",3) )
+ {
+ proto = AF_UNIX;
+ goto proto_found;
+ }
+
+ if ( tolerance > 1 )
+ {
+ ccp colon = strchr(start,':');
+ if (!colon)
+ colon = start + strlen(start);
+
+ //--- test for UNIX
+
+ ccp slash = strchr(start,'/');
+ if ( slash && slash < colon )
+ {
+ proto = AF_UNIX;
+ goto proto_found;
+ }
+
+ //--- test for IPv4
+
+ ccp ptr = start;
+ uint count = 0;
+ while ( ptr < colon )
+ {
+ const char ch = *ptr;
+ if ( ch == '.' )
+ count++;
+ else if ( ch < '0' || ch > '9' )
+ break;
+ ptr++;
+ }
+ if ( ptr == colon && count < 4 )
+ {
+ proto = AF_INET;
+ if ( count > 0 )
+ goto proto_found;
+ }
+
+ //--- test for IPv6
+
+ ptr = start;
+ const bool have_bracket = *ptr == '[';
+ if (have_bracket)
+ ptr++;
+ count = 0;
+ while (*ptr)
+ {
+ const char ch = *ptr;
+ if ( ch == ':' )
+ count++;
+ else if ( ch != '.'
+ && !( ch >= '0' && ch <= '9' )
+ && !( ch >= 'A' && ch <= 'F' )
+ && !( ch >= 'a' && ch <= 'f' )
+ )
+ {
+ break;
+ }
+ ptr++;
+ }
+ if ( count > 1 && count < 8 && ( !have_bracket || *ptr == ']' ))
+ {
+ proto = AF_INET6;
+ goto proto_found;
+ }
+
+ //--- test for a domain name
+
+ ptr = start;
+ count = 0;
+ ccp beg_name = ptr;
+ while ( ptr < colon )
+ {
+ const int ch = *ptr;
+ if ( ptr > beg_name && ch == '.' )
+ {
+ count++;
+ beg_name = ptr+1;
+ }
+ else if ( ptr == beg_name ? !isalpha(ch) : !isalnum(ch) )
+ break;
+ ptr++;
+ }
+ if ( count > 0 && ptr == colon )
+ {
+ proto = AF_INET_ANY;
+ goto proto_found;
+ }
+ }
+ }
+
+ if ( proto || type )
+ {
+ proto_found:
+ si->is_valid = true;
+ si->protocol = proto;
+ si->type = type;
+ si->address = start;
+ }
+ //printf(">>> v=%d {%d,%d}: %s\n",ok,proto,type,start);
+ return si->is_valid;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintSocketInfo ( int protocol, int type )
+{
+ switch (protocol)
+ {
+ case AF_UNIX:
+ switch(type)
+ {
+ case SOCK_STREAM: return "unix-tcp:";
+ case SOCK_DGRAM: return "unix-udp:";
+ case SOCK_SEQPACKET: return "unix-seq:";
+ default: return "unix:";
+ }
+ break;
+
+ case AF_INET:
+ switch(type)
+ {
+ case SOCK_STREAM: return "tcp4:";
+ case SOCK_DGRAM: return "udp4:";
+ case SOCK_RAW: return "raw4:";
+ default: return "ipv4:";
+ }
+ break;
+
+ case AF_INET6:
+ switch(type)
+ {
+ case SOCK_STREAM: return "tcp6:";
+ case SOCK_DGRAM: return "udp6:";
+ case SOCK_RAW: return "raw6:";
+ default: return "ipv6:";
+ }
+ break;
+
+ case AF_INET_ANY:
+ switch(type)
+ {
+ case SOCK_STREAM: return "tcp:";
+ case SOCK_DGRAM: return "udp:";
+ case SOCK_RAW: return "raw:";
+ default: return "ip:";
+ }
+ break;
+ };
+
+ return "";
+}
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// stat_file_count_t ///////////////
+///////////////////////////////////////////////////////////////////////////////
+
+stat_file_count_t stat_file_count = {0};
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint CountOpenFiles()
+{
+ DIR *fdir = opendir("/proc/self/fd");
+ if (fdir)
+ {
+ uint count = 0;
+ for(;;)
+ {
+ struct dirent *dent = readdir(fdir);
+ if (!dent)
+ break;
+ if (dent->d_name[0] != '.' )
+ count++;
+ }
+ closedir(fdir);
+
+ stat_file_count.cur_files = count;
+ if ( stat_file_count.max_files < count )
+ stat_file_count.max_files = count;
+ }
+
+ return stat_file_count.cur_files;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void RegisterFileId ( int fd )
+{
+ if ( (int)stat_file_count.max_files <= fd )
+ stat_file_count.max_files = fd+1;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void UpdateOpenFiles ( bool count_current )
+{
+ struct rlimit rlim;
+ if (!getrlimit(RLIMIT_NOFILE,&rlim))
+ {
+ stat_file_count.cur_limit = rlim.rlim_cur;
+ stat_file_count.max_limit = rlim.rlim_max;
+ }
+
+ if ( count_current || !stat_file_count.max_files )
+ CountOpenFiles();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+uint SetOpenFilesLimit ( uint limit )
+{
+ struct rlimit rlim;
+ if (!getrlimit(RLIMIT_NOFILE,&rlim))
+ {
+ rlim.rlim_cur = limit;
+ setrlimit(RLIMIT_NOFILE,&rlim);
+ }
+
+ UpdateOpenFiles(false);
+ return stat_file_count.cur_limit;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ccp PrintOpenFiles ( bool count_current )
+{
+ if (!stat_file_count.cur_limit)
+ UpdateOpenFiles(count_current);
+ else if (count_current)
+ CountOpenFiles();
+
+ return PrintCircBuf("cur=%u, max=%u, limit=%u/%u",
+ stat_file_count.cur_files,
+ stat_file_count.max_files,
+ stat_file_count.cur_limit,
+ stat_file_count.max_limit );
+};
+
+//
+///////////////////////////////////////////////////////////////////////////////
+/////////////// END ///////////////
+///////////////////////////////////////////////////////////////////////////////
+