| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952 |
- /* minIni - Multi-Platform INI file parser, suitable for embedded systems
- *
- * These routines are in part based on the article "Multiplatform .INI Files"
- * by Joseph J. Graf in the March 1994 issue of Dr. Dobb's Journal.
- *
- * Copyright (c) CompuPhase, 2008-2021
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy
- * of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations
- * under the License.
- *
- * Version: $Id: minIni.c 53 2015-01-18 13:35:11Z thiadmer.riemersma@gmail.com $
- */
- #if (defined _UNICODE || defined __UNICODE__ || defined UNICODE) && !defined INI_ANSIONLY
- # if !defined UNICODE /* for Windows */
- # define UNICODE
- # endif
- # if !defined _UNICODE /* for C library */
- # define _UNICODE
- # endif
- #endif
- #define MININI_IMPLEMENTATION
- #include "minIni.h"
- #if defined NDEBUG
- #define assert(e)
- #else
- #include <assert.h>
- #endif
- #if !defined __T || defined INI_ANSIONLY
- #include <ctype.h>
- #include <string.h>
- #include <stdlib.h>
- #define TCHAR char
- #define __T(s) s
- #define _tcscat strcat
- #define _tcschr strchr
- #define _tcscmp strcmp
- #define _tcscpy strcpy
- #define _tcsicmp stricmp
- #define _tcslen strlen
- #define _tcsncmp strncmp
- #define _tcsnicmp strnicmp
- #define _tcsrchr strrchr
- #define _tcstol strtol
- #define _tcstod strtod
- #define _totupper toupper
- #define _stprintf sprintf
- #define _tfgets fgets
- #define _tfputs fputs
- #define _tfopen fopen
- #define _tremove remove
- #define _trename rename
- #endif
- #if defined __linux || defined __linux__
- #define __LINUX__
- #elif defined FREEBSD && !defined __FreeBSD__
- #define __FreeBSD__
- #elif defined(_MSC_VER)
- #pragma warning(disable: 4996) /* for Microsoft Visual C/C++ */
- #endif
- #if !defined strnicmp && !defined PORTABLE_STRNICMP
- #if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ || defined __NetBSD__ || defined __DragonFly__ || defined __GNUC__
- #define strnicmp strncasecmp
- #endif
- #endif
- #if !defined _totupper
- #define _totupper toupper
- #endif
- #if !defined INI_LINETERM
- #if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ || defined __NetBSD__ || defined __DragonFly__
- #define INI_LINETERM __T("\n")
- #else
- #define INI_LINETERM __T("\r\n")
- #endif
- #endif
- #if !defined INI_FILETYPE
- #error Missing definition for INI_FILETYPE.
- #endif
- #if !defined sizearray
- #define sizearray(a) (sizeof(a) / sizeof((a)[0]))
- #endif
- enum quote_option {
- QUOTE_NONE,
- QUOTE_ENQUOTE,
- QUOTE_DEQUOTE,
- };
- #if defined PORTABLE_STRNICMP
- int strnicmp(const TCHAR *s1, const TCHAR *s2, size_t n)
- {
- while (n-- != 0 && (*s1 || *s2)) {
- register int c1, c2;
- c1 = *s1++;
- if ('a' <= c1 && c1 <= 'z')
- c1 += ('A' - 'a');
- c2 = *s2++;
- if ('a' <= c2 && c2 <= 'z')
- c2 += ('A' - 'a');
- if (c1 != c2)
- return c1 - c2;
- }
- return 0;
- }
- #endif /* PORTABLE_STRNICMP */
- static TCHAR *skipleading(const TCHAR *str)
- {
- assert(str != NULL);
- while ('\0' < *str && *str <= ' ')
- str++;
- return (TCHAR *)str;
- }
- static TCHAR *skiptrailing(const TCHAR *str, const TCHAR *base)
- {
- assert(str != NULL);
- assert(base != NULL);
- while (str > base && '\0' < *(str-1) && *(str-1) <= ' ')
- str--;
- return (TCHAR *)str;
- }
- static TCHAR *striptrailing(TCHAR *str)
- {
- TCHAR *ptr = skiptrailing(_tcschr(str, '\0'), str);
- assert(ptr != NULL);
- *ptr = '\0';
- return str;
- }
- static TCHAR *ini_strncpy(TCHAR *dest, const TCHAR *source, size_t maxlen, enum quote_option option)
- {
- size_t d, s;
- assert(maxlen>0);
- assert(source != NULL && dest != NULL);
- assert((dest < source || (dest == source && option != QUOTE_ENQUOTE)) || dest > source + strlen(source));
- if (option == QUOTE_ENQUOTE && maxlen < 3)
- option = QUOTE_NONE; /* cannot store two quotes and a terminating zero in less than 3 characters */
- switch (option) {
- case QUOTE_NONE:
- for (d = 0; d < maxlen - 1 && source[d] != '\0'; d++)
- dest[d] = source[d];
- assert(d < maxlen);
- dest[d] = '\0';
- break;
- case QUOTE_ENQUOTE:
- d = 0;
- dest[d++] = '"';
- for (s = 0; source[s] != '\0' && d < maxlen - 2; s++, d++) {
- if (source[s] == '"') {
- if (d >= maxlen - 3)
- break; /* no space to store the escape character plus the one that follows it */
- dest[d++] = '\\';
- }
- dest[d] = source[s];
- }
- dest[d++] = '"';
- dest[d] = '\0';
- break;
- case QUOTE_DEQUOTE:
- for (d = s = 0; source[s] != '\0' && d < maxlen - 1; s++, d++) {
- if ((source[s] == '"' || source[s] == '\\') && source[s + 1] == '"')
- s++;
- dest[d] = source[s];
- }
- dest[d] = '\0';
- break;
- default:
- assert(0);
- }
- return dest;
- }
- static TCHAR *cleanstring(TCHAR *string, enum quote_option *quotes)
- {
- int isstring;
- TCHAR *ep;
- assert(string != NULL);
- assert(quotes != NULL);
- /* Remove a trailing comment */
- isstring = 0;
- for (ep = string; *ep != '\0' && ((*ep != ';' && *ep != '#') || isstring); ep++) {
- if (*ep == '"') {
- if (*(ep + 1) == '"')
- ep++; /* skip "" (both quotes) */
- else
- isstring = !isstring; /* single quote, toggle isstring */
- } else if (*ep == '\\' && *(ep + 1) == '"') {
- ep++; /* skip \" (both quotes */
- }
- }
- assert(ep != NULL && (*ep == '\0' || *ep == ';' || *ep == '#'));
- *ep = '\0'; /* terminate at a comment */
- striptrailing(string);
- /* Remove double quotes surrounding a value */
- *quotes = QUOTE_NONE;
- if (*string == '"' && (ep = _tcschr(string, '\0')) != NULL && *(ep - 1) == '"') {
- string++;
- *--ep = '\0';
- *quotes = QUOTE_DEQUOTE; /* this is a string, so remove escaped characters */
- }
- return string;
- }
- static int getkeystring(INI_FILETYPE *fp, const TCHAR *Section, const TCHAR *Key,
- int idxSection, int idxKey, TCHAR *Buffer, int BufferSize,
- INI_FILEPOS *mark)
- {
- TCHAR *sp, *ep;
- int len, idx;
- enum quote_option quotes;
- TCHAR LocalBuffer[INI_BUFFERSIZE];
- assert(fp != NULL);
- /* Move through file 1 line at a time until a section is matched or EOF. If
- * parameter Section is NULL, only look at keys above the first section. If
- * idxSection is positive, copy the relevant section name.
- */
- len = (Section != NULL) ? (int)_tcslen(Section) : 0;
- if (len > 0 || idxSection >= 0) {
- assert(idxSection >= 0 || Section != NULL);
- idx = -1;
- do {
- do {
- if (!ini_read(LocalBuffer, INI_BUFFERSIZE, fp))
- return 0;
- sp = skipleading(LocalBuffer);
- ep = _tcsrchr(sp, ']');
- } while (*sp != '[' || ep == NULL);
- /* When arrived here, a section was found; now optionally skip leading and
- * trailing whitespace.
- */
- assert(sp != NULL && *sp == '[');
- sp = skipleading(sp + 1);
- assert(ep != NULL && *ep == ']');
- ep = skiptrailing(ep, sp);
- } while ((((int)(ep-sp) != len || Section == NULL || _tcsnicmp(sp, Section, len) != 0) && ++idx != idxSection));
- if (idxSection >= 0) {
- if (idx == idxSection) {
- assert(ep != NULL);
- *ep = '\0'; /* the end of the section name was found earlier */
- ini_strncpy(Buffer, sp, BufferSize, QUOTE_NONE);
- return 1;
- }
- return 0; /* no more section found */
- }
- }
- /* Now that the section has been found, find the entry.
- * Stop searching upon leaving the section's area.
- */
- assert(Key != NULL || idxKey >= 0);
- len = (Key != NULL) ? (int)_tcslen(Key) : 0;
- idx = -1;
- do {
- if (mark != NULL)
- ini_tell(fp, mark); /* optionally keep the mark to the start of the line */
- if (!ini_read(LocalBuffer,INI_BUFFERSIZE,fp) || *(sp = skipleading(LocalBuffer)) == '[')
- return 0;
- sp = skipleading(LocalBuffer);
- ep = _tcschr(sp, '='); /* Parse out the equal sign */
- if (ep == NULL)
- ep = _tcschr(sp, ':');
- } while (*sp == ';' || *sp == '#' || ep == NULL
- || ((len == 0 || (int)(skiptrailing(ep,sp)-sp) != len || _tcsnicmp(sp,Key,len) != 0) && ++idx != idxKey));
- if (idxKey >= 0) {
- if (idx == idxKey) {
- assert(ep != NULL);
- assert(*ep == '=' || *ep == ':');
- *ep = '\0';
- striptrailing(sp);
- ini_strncpy(Buffer, sp, BufferSize, QUOTE_NONE);
- return 1;
- }
- return 0; /* no more key found (in this section) */
- }
- /* Copy up to BufferSize chars to buffer */
- assert(ep != NULL);
- assert(*ep == '=' || *ep == ':');
- sp = skipleading(ep + 1);
- sp = cleanstring(sp, "es); /* Remove a trailing comment */
- ini_strncpy(Buffer, sp, BufferSize, quotes);
- return 1;
- }
- /** ini_gets()
- * \param Section the name of the section to search for
- * \param Key the name of the entry to find the value of
- * \param DefValue default string in the event of a failed read
- * \param Buffer a pointer to the buffer to copy into
- * \param BufferSize the maximum number of characters to copy
- * \param Filename the name and full path of the .ini file to read from
- *
- * \return the number of characters copied into the supplied buffer
- */
- int ini_gets(const TCHAR *Section, const TCHAR *Key, const TCHAR *DefValue,
- TCHAR *Buffer, int BufferSize, const TCHAR *Filename)
- {
- INI_FILETYPE fp;
- int ok = 0;
- if (Buffer == NULL || BufferSize <= 0 || Key == NULL)
- return 0;
- if (ini_openread(Filename, &fp)) {
- ok = getkeystring(&fp, Section, Key, -1, -1, Buffer, BufferSize, NULL);
- (void)ini_close(&fp);
- }
- if (!ok)
- ini_strncpy(Buffer, (DefValue != NULL) ? DefValue : __T(""), BufferSize, QUOTE_NONE);
- return (int)_tcslen(Buffer);
- }
- /** ini_getl()
- * \param Section the name of the section to search for
- * \param Key the name of the entry to find the value of
- * \param DefValue the default value in the event of a failed read
- * \param Filename the name of the .ini file to read from
- *
- * \return the value located at Key
- */
- long ini_getl(const TCHAR *Section, const TCHAR *Key, long DefValue, const TCHAR *Filename)
- {
- TCHAR LocalBuffer[64];
- int len = ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename);
- return (len == 0) ? DefValue
- : ((len >= 2 && _totupper((int)LocalBuffer[1]) == 'X') ? _tcstol(LocalBuffer, NULL, 16)
- : _tcstol(LocalBuffer, NULL, 10));
- }
- #if defined INI_REAL
- /** ini_getf()
- * \param Section the name of the section to search for
- * \param Key the name of the entry to find the value of
- * \param DefValue the default value in the event of a failed read
- * \param Filename the name of the .ini file to read from
- *
- * \return the value located at Key
- */
- INI_REAL ini_getf(const TCHAR *Section, const TCHAR *Key, INI_REAL DefValue, const TCHAR *Filename)
- {
- TCHAR LocalBuffer[64];
- int len = ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename);
- return (len == 0) ? DefValue : ini_atof(LocalBuffer);
- }
- #endif
- /** ini_getbool()
- * \param Section the name of the section to search for
- * \param Key the name of the entry to find the value of
- * \param DefValue default value in the event of a failed read; it should
- * zero (0) or one (1).
- * \param Filename the name and full path of the .ini file to read from
- *
- * A true boolean is found if one of the following is matched:
- * - A string starting with 'y' or 'Y'
- * - A string starting with 't' or 'T'
- * - A string starting with '1'
- *
- * A false boolean is found if one of the following is matched:
- * - A string starting with 'n' or 'N'
- * - A string starting with 'f' or 'F'
- * - A string starting with '0'
- *
- * \return the true/false flag as interpreted at Key
- */
- int ini_getbool(const TCHAR *Section, const TCHAR *Key, int DefValue, const TCHAR *Filename)
- {
- TCHAR LocalBuffer[2] = __T("");
- int ret;
- ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename);
- LocalBuffer[0] = (TCHAR)_totupper((int)LocalBuffer[0]);
- if (LocalBuffer[0] == 'Y' || LocalBuffer[0] == '1' || LocalBuffer[0] == 'T')
- ret = 1;
- else if (LocalBuffer[0] == 'N' || LocalBuffer[0] == '0' || LocalBuffer[0] == 'F')
- ret = 0;
- else
- ret = DefValue;
- return(ret);
- }
- /** ini_getsection()
- * \param idx the zero-based sequence number of the section to return
- * \param Buffer a pointer to the buffer to copy into
- * \param BufferSize the maximum number of characters to copy
- * \param Filename the name and full path of the .ini file to read from
- *
- * \return the number of characters copied into the supplied buffer
- */
- int ini_getsection(int idx, TCHAR *Buffer, int BufferSize, const TCHAR *Filename)
- {
- INI_FILETYPE fp;
- int ok = 0;
- if (Buffer == NULL || BufferSize <= 0 || idx < 0)
- return 0;
- if (ini_openread(Filename, &fp)) {
- ok = getkeystring(&fp, NULL, NULL, idx, -1, Buffer, BufferSize, NULL);
- (void)ini_close(&fp);
- }
- if (!ok)
- *Buffer = '\0';
- return (int)_tcslen(Buffer);
- }
- /** ini_getkey()
- * \param Section the name of the section to browse through, or NULL to
- * browse through the keys outside any section
- * \param idx the zero-based sequence number of the key to return
- * \param Buffer a pointer to the buffer to copy into
- * \param BufferSize the maximum number of characters to copy
- * \param Filename the name and full path of the .ini file to read from
- *
- * \return the number of characters copied into the supplied buffer
- */
- int ini_getkey(const TCHAR *Section, int idx, TCHAR *Buffer, int BufferSize, const TCHAR *Filename)
- {
- INI_FILETYPE fp;
- int ok = 0;
- if (Buffer == NULL || BufferSize <= 0 || idx < 0)
- return 0;
- if (ini_openread(Filename, &fp)) {
- ok = getkeystring(&fp, Section, NULL, -1, idx, Buffer, BufferSize, NULL);
- (void)ini_close(&fp);
- }
- if (!ok)
- *Buffer = '\0';
- return (int)_tcslen(Buffer);
- }
- /** ini_hassection()
- * \param Section the name of the section to search for
- * \param Filename the name of the .ini file to read from
- *
- * \return 1 if the section is found, 0 if not found
- */
- int ini_hassection(const mTCHAR *Section, const mTCHAR *Filename)
- {
- TCHAR LocalBuffer[32]; /* dummy buffer */
- INI_FILETYPE fp;
- int ok = 0;
- if (ini_openread(Filename, &fp)) {
- ok = getkeystring(&fp, Section, NULL, -1, 0, LocalBuffer, sizearray(LocalBuffer), NULL);
- (void)ini_close(&fp);
- }
- return ok;
- }
- /** ini_haskey()
- * \param Section the name of the section to search for
- * \param Key the name of the entry to find the value of
- * \param Filename the name of the .ini file to read from
- *
- * \return 1 if the key is found, 0 if not found
- */
- int ini_haskey(const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Filename)
- {
- TCHAR LocalBuffer[32]; /* dummy buffer */
- INI_FILETYPE fp;
- int ok = 0;
- if (ini_openread(Filename, &fp)) {
- ok = getkeystring(&fp, Section, Key, -1, -1, LocalBuffer, sizearray(LocalBuffer), NULL);
- (void)ini_close(&fp);
- }
- return ok;
- }
- #if !defined INI_NOBROWSE
- /** ini_browse()
- * \param Callback a pointer to a function that will be called for every
- * setting in the INI file.
- * \param UserData arbitrary data, which the function passes on the
- * \c Callback function
- * \param Filename the name and full path of the .ini file to read from
- *
- * \return 1 on success, 0 on failure (INI file not found)
- *
- * \note The \c Callback function must return 1 to continue
- * browsing through the INI file, or 0 to stop. Even when the
- * callback stops the browsing, this function will return 1
- * (for success).
- */
- int ini_browse(INI_CALLBACK Callback, void *UserData, const TCHAR *Filename)
- {
- TCHAR LocalBuffer[INI_BUFFERSIZE];
- int lenSec, lenKey;
- enum quote_option quotes;
- INI_FILETYPE fp;
- if (Callback == NULL)
- return 0;
- if (!ini_openread(Filename, &fp))
- return 0;
- LocalBuffer[0] = '\0'; /* copy an empty section in the buffer */
- lenSec = (int)_tcslen(LocalBuffer) + 1;
- for ( ;; ) {
- TCHAR *sp, *ep;
- if (!ini_read(LocalBuffer + lenSec, INI_BUFFERSIZE - lenSec, &fp))
- break;
- sp = skipleading(LocalBuffer + lenSec);
- /* ignore empty strings and comments */
- if (*sp == '\0' || *sp == ';' || *sp == '#')
- continue;
- /* see whether we reached a new section */
- ep = _tcsrchr(sp, ']');
- if (*sp == '[' && ep != NULL) {
- sp = skipleading(sp + 1);
- ep = skiptrailing(ep, sp);
- *ep = '\0';
- ini_strncpy(LocalBuffer, sp, INI_BUFFERSIZE, QUOTE_NONE);
- lenSec = (int)_tcslen(LocalBuffer) + 1;
- continue;
- }
- /* not a new section, test for a key/value pair */
- ep = _tcschr(sp, '='); /* test for the equal sign or colon */
- if (ep == NULL)
- ep = _tcschr(sp, ':');
- if (ep == NULL)
- continue; /* invalid line, ignore */
- *ep++ = '\0'; /* split the key from the value */
- striptrailing(sp);
- ini_strncpy(LocalBuffer + lenSec, sp, INI_BUFFERSIZE - lenSec, QUOTE_NONE);
- lenKey = (int)_tcslen(LocalBuffer + lenSec) + 1;
- /* clean up the value */
- sp = skipleading(ep);
- sp = cleanstring(sp, "es); /* Remove a trailing comment */
- ini_strncpy(LocalBuffer + lenSec + lenKey, sp, INI_BUFFERSIZE - lenSec - lenKey, quotes);
- /* call the callback */
- if (!Callback(LocalBuffer, LocalBuffer + lenSec, LocalBuffer + lenSec + lenKey, UserData))
- break;
- }
- (void)ini_close(&fp);
- return 1;
- }
- #endif /* INI_NOBROWSE */
- #if ! defined INI_READONLY
- static void ini_tempname(TCHAR *dest, const TCHAR *source, int maxlength)
- {
- TCHAR *p;
- ini_strncpy(dest, source, maxlength, QUOTE_NONE);
- p = _tcschr(dest, '\0');
- assert(p != NULL);
- *(p - 1) = '~';
- }
- static enum quote_option check_enquote(const TCHAR *Value)
- {
- const TCHAR *p;
- /* run through the value, if it has trailing spaces, or '"', ';' or '#'
- * characters, enquote it
- */
- assert(Value != NULL);
- for (p = Value; *p != '\0' && *p != '"' && *p != ';' && *p != '#'; p++)
- /* nothing */;
- return (*p != '\0' || (p > Value && *(p - 1) == ' ')) ? QUOTE_ENQUOTE : QUOTE_NONE;
- }
- static void writesection(TCHAR *LocalBuffer, const TCHAR *Section, INI_FILETYPE *fp)
- {
- if (Section != NULL && _tcslen(Section) > 0) {
- TCHAR *p;
- LocalBuffer[0] = '[';
- ini_strncpy(LocalBuffer + 1, Section, INI_BUFFERSIZE - 4, QUOTE_NONE); /* -1 for '[', -1 for ']', -2 for '\r\n' */
- p = _tcschr(LocalBuffer, '\0');
- assert(p != NULL);
- *p++ = ']';
- _tcscpy(p, INI_LINETERM); /* copy line terminator (typically "\n") */
- if (fp != NULL)
- (void)ini_write(LocalBuffer, fp);
- }
- }
- static void writekey(TCHAR *LocalBuffer, const TCHAR *Key, const TCHAR *Value, INI_FILETYPE *fp)
- {
- TCHAR *p;
- enum quote_option option = check_enquote(Value);
- ini_strncpy(LocalBuffer, Key, INI_BUFFERSIZE - 3, QUOTE_NONE); /* -1 for '=', -2 for '\r\n' */
- p = _tcschr(LocalBuffer, '\0');
- assert(p != NULL);
- *p++ = '=';
- ini_strncpy(p, Value, INI_BUFFERSIZE - (p - LocalBuffer) - 2, option); /* -2 for '\r\n' */
- p = _tcschr(LocalBuffer, '\0');
- assert(p != NULL);
- _tcscpy(p, INI_LINETERM); /* copy line terminator (typically "\n") */
- if (fp != NULL)
- (void)ini_write(LocalBuffer, fp);
- }
- static int cache_accum(const TCHAR *string, int *size, int max)
- {
- int len = (int)_tcslen(string);
- if (*size + len >= max)
- return 0;
- *size += len;
- return 1;
- }
- static int cache_flush(TCHAR *buffer, int *size,
- INI_FILETYPE *rfp, INI_FILETYPE *wfp, INI_FILEPOS *mark)
- {
- int terminator_len = (int)_tcslen(INI_LINETERM);
- int pos = 0, pos_prev = -1;
- (void)ini_seek(rfp, mark);
- assert(buffer != NULL);
- buffer[0] = '\0';
- assert(size != NULL);
- assert(*size <= INI_BUFFERSIZE);
- while (pos < *size && pos != pos_prev) {
- pos_prev = pos; /* to guard against zero bytes in the INI file */
- (void)ini_read(buffer + pos, INI_BUFFERSIZE - pos, rfp);
- while (pos < *size && buffer[pos] != '\0')
- pos++; /* cannot use _tcslen() because buffer may not be zero-terminated */
- }
- if (buffer[0] != '\0') {
- assert(pos > 0 && pos <= INI_BUFFERSIZE);
- if (pos == INI_BUFFERSIZE)
- pos--;
- buffer[pos] = '\0'; /* force zero-termination (may be left unterminated in the above while loop) */
- (void)ini_write(buffer, wfp);
- }
- ini_tell(rfp, mark); /* update mark */
- *size = 0;
- /* return whether the buffer ended with a line termination */
- return (pos > terminator_len) && (_tcscmp(buffer + pos - terminator_len, INI_LINETERM) == 0);
- }
- static int close_rename(INI_FILETYPE *rfp, INI_FILETYPE *wfp, const TCHAR *filename, TCHAR *buffer)
- {
- (void)ini_close(rfp);
- (void)ini_close(wfp);
- (void)ini_tempname(buffer, filename, INI_BUFFERSIZE);
- #if defined ini_remove || defined INI_REMOVE
- (void)ini_remove(filename);
- #endif
- (void)ini_rename(buffer, filename);
- return 1;
- }
- /** ini_puts()
- * \param Section the name of the section to write the string in
- * \param Key the name of the entry to write, or NULL to erase all keys in the section
- * \param Value a pointer to the buffer the string, or NULL to erase the key
- * \param Filename the name and full path of the .ini file to write to
- *
- * \return 1 if successful, otherwise 0
- */
- int ini_puts(const TCHAR *Section, const TCHAR *Key, const TCHAR *Value, const TCHAR *Filename)
- {
- INI_FILETYPE rfp;
- INI_FILETYPE wfp;
- INI_FILEPOS mark;
- INI_FILEPOS head, tail;
- TCHAR *sp, *ep;
- TCHAR LocalBuffer[INI_BUFFERSIZE];
- int len, match, flag, cachelen;
- assert(Filename != NULL);
- if (!ini_openread(Filename, &rfp)) {
- /* If the .ini file doesn't exist, make a new file */
- if (Key != NULL && Value != NULL) {
- if (!ini_openwrite(Filename, &wfp))
- return 0;
- writesection(LocalBuffer, Section, &wfp);
- writekey(LocalBuffer, Key, Value, &wfp);
- (void)ini_close(&wfp);
- }
- return 1;
- }
- /* If parameters Key and Value are valid (so this is not an "erase" request)
- * and the setting already exists, there are two short-cuts to avoid rewriting
- * the INI file.
- */
- if (Key != NULL && Value != NULL) {
- match = getkeystring(&rfp, Section, Key, -1, -1, LocalBuffer, sizearray(LocalBuffer), &head);
- if (match) {
- /* if the current setting is identical to the one to write, there is
- * nothing to do.
- */
- if (_tcscmp(LocalBuffer,Value) == 0) {
- (void)ini_close(&rfp);
- return 1;
- }
- /* if the new setting has the same length as the current setting, and the
- * glue file permits file read/write access, we can modify in place.
- */
- #if defined ini_openrewrite || defined INI_OPENREWRITE
- /* we already have the start of the (raw) line, get the end too */
- ini_tell(&rfp, &tail);
- /* create new buffer (without writing it to file) */
- writekey(LocalBuffer, Key, Value, NULL);
- if (_tcslen(LocalBuffer) == (size_t)(tail - head)) {
- /* length matches, close the file & re-open for read/write, then
- * write at the correct position
- */
- (void)ini_close(&rfp);
- if (!ini_openrewrite(Filename, &wfp))
- return 0;
- (void)ini_seek(&wfp, &head);
- (void)ini_write(LocalBuffer, &wfp);
- (void)ini_close(&wfp);
- return 1;
- }
- #endif
- }
- /* key not found, or different value & length -> proceed */
- } else if (Key != NULL && Value == NULL) {
- /* Conversely, for a request to delete a setting; if that setting isn't
- present, just return */
- match = getkeystring(&rfp, Section, Key, -1, -1, LocalBuffer, sizearray(LocalBuffer), NULL);
- if (!match) {
- (void)ini_close(&rfp);
- return 1;
- }
- /* key found -> proceed to delete it */
- }
- /* Get a temporary file name to copy to. Use the existing name, but with
- * the last character set to a '~'.
- */
- (void)ini_close(&rfp);
- ini_tempname(LocalBuffer, Filename, INI_BUFFERSIZE);
- if (!ini_openwrite(LocalBuffer, &wfp))
- return 0;
- /* In the case of (advisory) file locks, ini_openwrite() may have been blocked
- * on the open, and after the block is lifted, the original file may have been
- * renamed, which is why the original file was closed and is now reopened */
- if (!ini_openread(Filename, &rfp)) {
- /* If the .ini file doesn't exist any more, make a new file */
- assert(Key != NULL && Value != NULL);
- writesection(LocalBuffer, Section, &wfp);
- writekey(LocalBuffer, Key, Value, &wfp);
- (void)ini_close(&wfp);
- return 1;
- }
- (void)ini_tell(&rfp, &mark);
- cachelen = 0;
- /* Move through the file one line at a time until a section is
- * matched or until EOF. Copy to temp file as it is read.
- */
- len = (Section != NULL) ? (int)_tcslen(Section) : 0;
- if (len > 0) {
- do {
- if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) {
- /* Failed to find section, so add one to the end */
- flag = cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
- if (Key!=NULL && Value!=NULL) {
- if (!flag)
- (void)ini_write(INI_LINETERM, &wfp); /* force a new line behind the last line of the INI file */
- writesection(LocalBuffer, Section, &wfp);
- writekey(LocalBuffer, Key, Value, &wfp);
- }
- return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */
- }
- /* Check whether this line is a section */
- sp = skipleading(LocalBuffer);
- ep = _tcsrchr(sp, ']');
- match = (*sp == '[' && ep != NULL);
- if (match) {
- /* A section was found, skip leading and trailing whitespace */
- assert(sp != NULL && *sp == '[');
- sp = skipleading(sp + 1);
- assert(ep != NULL && *ep == ']');
- ep = skiptrailing(ep, sp);
- match = ((int)(ep-sp) == len && _tcsnicmp(sp, Section, len) == 0);
- }
- /* Copy the line from source to dest, but not if this is the section that
- * we are looking for and this section must be removed
- */
- if (!match || Key != NULL) {
- if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) {
- cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
- (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp);
- cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE);
- }
- }
- } while (!match);
- }
- cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
- /* when deleting a section, the section head that was just found has not been
- * copied to the output file, but because this line was not "accumulated" in
- * the cache, the position in the input file was reset to the point just
- * before the section; this must now be skipped (again)
- */
- if (Key == NULL) {
- (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp);
- (void)ini_tell(&rfp, &mark);
- }
- /* Now that the section has been found, find the entry. Stop searching
- * upon leaving the section's area. Copy the file as it is read
- * and create an entry if one is not found.
- */
- len = (Key != NULL) ? (int)_tcslen(Key) : 0;
- for( ;; ) {
- if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) {
- /* EOF without an entry so make one */
- flag = cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
- if (Key!=NULL && Value!=NULL) {
- if (!flag)
- (void)ini_write(INI_LINETERM, &wfp); /* force a new line behind the last line of the INI file */
- writekey(LocalBuffer, Key, Value, &wfp);
- }
- return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */
- }
- sp = skipleading(LocalBuffer);
- ep = _tcschr(sp, '='); /* Parse out the equal sign */
- if (ep == NULL)
- ep = _tcschr(sp, ':');
- match = (ep != NULL && len > 0 && (int)(skiptrailing(ep,sp)-sp) == len && _tcsnicmp(sp,Key,len) == 0);
- if ((Key != NULL && match) || *sp == '[')
- break; /* found the key, or found a new section */
- /* copy other keys in the section */
- if (Key == NULL) {
- (void)ini_tell(&rfp, &mark); /* we are deleting the entire section, so update the read position */
- } else {
- if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) {
- cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
- (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp);
- cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE);
- }
- }
- }
- /* the key was found, or we just dropped on the next section (meaning that it
- * wasn't found); in both cases we need to write the key, but in the latter
- * case, we also need to write the line starting the new section after writing
- * the key
- */
- flag = (*sp == '[');
- cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
- if (Key != NULL && Value != NULL)
- writekey(LocalBuffer, Key, Value, &wfp);
- /* cache_flush() reset the "read pointer" to the start of the line with the
- * previous key or the new section; read it again (because writekey() destroyed
- * the buffer)
- */
- (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp);
- if (flag) {
- /* the new section heading needs to be copied to the output file */
- cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE);
- } else {
- /* forget the old key line */
- (void)ini_tell(&rfp, &mark);
- }
- /* Copy the rest of the INI file */
- while (ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) {
- if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) {
- cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
- (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp);
- cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE);
- }
- }
- cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark);
- return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */
- }
- /* Ansi C "itoa" based on Kernighan & Ritchie's "Ansi C" book. */
- #define ABS(v) ((v) < 0 ? -(v) : (v))
- static void strreverse(TCHAR *str)
- {
- int i, j;
- for (i = 0, j = (int)_tcslen(str) - 1; i < j; i++, j--) {
- TCHAR t = str[i];
- str[i] = str[j];
- str[j] = t;
- }
- }
- static void long2str(long value, TCHAR *str)
- {
- int i = 0;
- long sign = value;
- /* generate digits in reverse order */
- do {
- int n = (int)(value % 10); /* get next lowest digit */
- str[i++] = (TCHAR)(ABS(n) + '0'); /* handle case of negative digit */
- } while (value /= 10); /* delete the lowest digit */
- if (sign < 0)
- str[i++] = '-';
- str[i] = '\0';
- strreverse(str);
- }
- /** ini_putl()
- * \param Section the name of the section to write the value in
- * \param Key the name of the entry to write
- * \param Value the value to write
- * \param Filename the name and full path of the .ini file to write to
- *
- * \return 1 if successful, otherwise 0
- */
- int ini_putl(const TCHAR *Section, const TCHAR *Key, long Value, const TCHAR *Filename)
- {
- TCHAR LocalBuffer[32];
- long2str(Value, LocalBuffer);
- return ini_puts(Section, Key, LocalBuffer, Filename);
- }
- #if defined INI_REAL
- /** ini_putf()
- * \param Section the name of the section to write the value in
- * \param Key the name of the entry to write
- * \param Value the value to write
- * \param Filename the name and full path of the .ini file to write to
- *
- * \return 1 if successful, otherwise 0
- */
- int ini_putf(const TCHAR *Section, const TCHAR *Key, INI_REAL Value, const TCHAR *Filename)
- {
- TCHAR LocalBuffer[64];
- ini_ftoa(LocalBuffer, Value);
- return ini_puts(Section, Key, LocalBuffer, Filename);
- }
- #endif /* INI_REAL */
- #endif /* !INI_READONLY */
|