Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependents: Smart-WiFly-WebServer SignalGenerator WattEye X10Svr
IniManager.cpp
- Committer:
- WiredHome
- Date:
- 2020-04-30
- Revision:
- 26:e8d7b09a77a3
- Parent:
- 25:1362b843de86
- Child:
- 28:4e7fc08a0fea
File content as of revision 26:e8d7b09a77a3:
// Simple INI file manager.
//
#ifdef WIN32
#include "string.h"
#include "stdlib.h"
#include "stdio.h"
#else
#include "mbed.h"
#endif
#include "IniManager.h"
//#include "Utility.h" // private memory manager
#ifndef UTILITY_H
#define swMalloc malloc // use the standard
#define swFree free
#endif
//#define DEBUG "INI " //Debug is disabled by default
#include <cstdio>
#if (defined(DEBUG) && !defined(TARGET_LPC11U24))
#define DBG(x, ...) std::printf("[DBG %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
#define WARN(x, ...) std::printf("[WRN %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
#define ERR(x, ...) std::printf("[ERR %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
#define INFO(x, ...) std::printf("[INF %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
#else
#define DBG(x, ...)
#define WARN(x, ...)
#define ERR(x, ...)
#define INFO(x, ...)
#endif
// 2 versions, to translate new return values to old format
// return RetXLate[new value][version]
INI::INI_Return RetXLate[INI::INI_INTERNAL_ERROR+1][2] = {
// Ver1, Ver2 return values.
INI::INI_V1_SUCCESS, INI::INI_SUCCESS, /// Success - operation succeeded
INI::INI_V1_FAIL, INI::INI_NO_FILE_SPEC, /// Fail - no file was specified
INI::INI_V1_FAIL, INI::INI_FILE_NOT_FOUND, /// Fail - ini file not found, or failed to open
INI::INI_V1_FAIL, INI::INI_SECTION_NOT_FOUND, /// Fail - section not found
INI::INI_V1_FAIL, INI::INI_KEY_NOT_FOUND, /// Fail - key not found
INI::INI_V1_FAIL, INI::INI_BUF_TOO_SMALL, /// Fail - buffer to small for value
INI::INI_V1_FAIL, INI::INI_INTERNAL_ERROR /// Fail - internal error - can't alloc buffers
};
INI::INI(const char * file, int Version)
: iniFile(0)
{
SetFile(file);
version = (Version == 2) ? 1 : 0; // Version 1 or 2 is return value index 0 or 1
}
INI::~INI(void)
{
if (iniFile)
swFree(iniFile);
}
bool INI::GetNextSection(const char * after, char * buffer, size_t bufferSize) {
bool returnNext = false;
bool found = false;
if (!iniFile)
return found;
CleanUp();
INFO("GetNextSection after [%s]", after);
FILE * fp = fopen(iniFile,"rt");
if (fp) {
char buf[INTERNAL_BUF_SIZE];
if (after == NULL || *after == '\0')
returnNext = true;
while(fgets(buf, sizeof(buf), fp)) {
int x = strlen(buf) - 1; // remove trailing \r\n combinations
while (x >= 0 && buf[x] < ' ')
buf[x--] = '\0';
INFO(" reading \"%s\"", buf);
if (buf[0] == '[') {
char * pStart = buf + 1;
char * pRBrkt = strchr(buf, ']');
if (pRBrkt) {
*pRBrkt = '\0';
if (returnNext) {
// Guard against "[]" which would cause it to recycle from the start
if (strlen(pStart) > 0 && strlen(pStart) < bufferSize) {
strcpy(buffer, pStart);
found = true;
break;
}
} else if (strcmp(after, pStart) == 0) {
returnNext = true;
}
}
}
}
fclose(fp);
}
return found;
}
bool INI::GetNextKey(const char * Section, const char * after, char * buffer, size_t bufferSize) {
bool returnNext = false;
bool inSection = false;
bool found = false;
if (!iniFile)
return found;
CleanUp();
INFO("GetNextLey after [%s]", after);
FILE * fp = fopen(iniFile,"rt");
if (fp) {
char buf[INTERNAL_BUF_SIZE];
if (after == NULL || *after == '\0')
returnNext = true;
while(fgets(buf, sizeof(buf), fp)) {
int x = strlen(buf) - 1; // remove trailing \r\n combinations
while (x >= 0 && buf[x] < ' ')
buf[x--] = '\0';
INFO(" reading \"%s\"", buf);
if (!(buf[0] == '[' || (buf[0] >= 'A' && buf[0] <= 'Z') || (buf[0] >= 'a' && buf[0] <= 'z')))
continue;
if (buf[0] == '[') {
char * pStart = buf + 1;
char * pRBrkt = strchr(buf, ']');
if (pRBrkt) {
*pRBrkt = '\0';
if (inSection == true) { // section after wanted, so done.
break;
} else if (strcmp(pStart, Section) == 0) {
inSection = true;
continue;
}
}
} else if (inSection) {
char * pStart = buf;
char * pEqual = strchr(pStart, '=');
if (pEqual) {
*pEqual = '\0';
if (returnNext) {
if (strlen(pStart) < bufferSize) {
strcpy(buffer, pStart);
found = true;
break;
}
} else if (strcmp(after, pStart) == 0) {
returnNext = true;
}
}
}
}
fclose(fp);
}
return found;
}
bool INI::Exists(const char * file)
{
if (file == NULL)
file = iniFile;
INFO("Exists(%s)", file);
FILE * fp = fopen(file, "r");
if (fp) {
fclose(fp);
INFO(" [%s] exists", file);
return true;
} else {
INFO(" [%s] does not exist", file);
return false;
}
}
bool INI::SetFile(const char * file, int Version)
{
INFO("SetFile(%s,%d)", file, Version);
version = (Version == 2) ? 1 : 0; // Version 1 or 2 is return value index 0 or 1
if (file) {
if (iniFile)
swFree(iniFile);
iniFile = (char *)swMalloc(strlen(file)+1);
if (iniFile) {
strcpy(iniFile, file);
INFO(" SetFile(%s) success", iniFile);
return true;
}
else {
iniFile = NULL;
ERR(" SetFile(%s) failed to allocate memory", file);
}
}
return false;
}
INI::INI_Return INI::ReadString(const char * section, const char * key, char * buffer, size_t bufferSize, const char * defaultString)
{
INI_Return retVal;
bool found = false;
retVal = RetXLate[INI_SECTION_NOT_FOUND][version]; // assume we won't find the section, until we do.
if (!iniFile)
return RetXLate[INI_NO_FILE_SPEC][version];
CleanUp();
INFO("ReadString from %s", iniFile);
FILE * fp = fopen(iniFile,"rt");
if (!fp) {
if (defaultString == NULL) {
return RetXLate[INI_FILE_NOT_FOUND][version];
}
} else {
char buf[INTERNAL_BUF_SIZE];
bool inSection = (section == NULL) ? true : false;
while(fgets(buf, sizeof(buf), fp)) {
int x = strlen(buf) - 1; // remove trailing \r\n combinations
while (x >= 0 && buf[x] < ' ')
buf[x--] = '\0';
INFO(" reading \"%s\"", buf);
if (!(buf[0] == '[' || (buf[0] >= 'A' && buf[0] <= 'Z') || (buf[0] >= 'a' && buf[0] <= 'z')))
continue;
if (inSection && buf[0] != '[') {
char * eq = strchr(buf, '=');
if (eq) {
*eq++ = '\0';
if (strcmp(buf,key) == 0) { // Found the key of interest
if (strlen(eq) < bufferSize) {
strcpy(buffer, eq);
memset(buf, 0, INTERNAL_BUF_SIZE); // secure the memory space
found = true;
retVal = RetXLate[INI_SUCCESS][version];
} else {
retVal = RetXLate[INI_BUF_TOO_SMALL][version];
}
break;
}
}
} else {
if (buf[0] == '[') {
char * br = strchr(buf, ']');
if (inSection) { // we were in the section of interest and just hit the next section...
break;
} else {
inSection = false;
if (br) {
*br = '\0';
if (strcmp(buf+1, section) == 0) {
inSection = true;
retVal = RetXLate[INI_KEY_NOT_FOUND][version]; // assume we won't find the key, until we do
}
}
}
}
}
}
fclose(fp);
}
if (!found && defaultString != NULL && *defaultString) {
if (strlen(defaultString) < bufferSize) {
strcpy(buffer, defaultString);
retVal = RetXLate[INI_SUCCESS][version];
} else {
retVal = RetXLate[INI_BUF_TOO_SMALL][version];
}
}
return retVal;
}
long int INI::ReadLongInt(const char * section, const char * key, long int defaultValue)
{
char localBuf[16];
if (INI::INI_SUCCESS == ReadString(section, key, localBuf, sizeof(localBuf))) {
return atol(localBuf);
} else {
return defaultValue;
}
}
bool INI::CleanUp()
{
char * newFile = (char *)swMalloc(strlen(iniFile)+1);
char * bakFile = (char *)swMalloc(strlen(iniFile)+1);
if (newFile && bakFile) {
INFO("CleanUp");
strcpy(bakFile, iniFile);
strcpy(newFile, iniFile);
strcpy(bakFile + strlen(bakFile) - 4, ".bak");
strcpy(newFile + strlen(newFile) - 4, ".new");
if (Exists(newFile)) {
int i;
i = i; // suppress warning about i not used when !DEBUG
// helps recover if the system crashed before it could swap in the new file
INFO(" *** found %s, repairing ...", newFile);
i = remove(bakFile); // remove an old .bak
INFO(" remove(%s) returned %d", bakFile, i);
i = Rename(iniFile, bakFile); // move the existing .ini to .bak
INFO(" rename(%s,%s) returned %d", iniFile, bakFile, i);
i = Rename(newFile, iniFile); // move the new .new to .ini
INFO(" rename(%s,%s) returned %d", newFile, iniFile, i);
} else {
// nothing to do, move on silently.
}
}
swFree(newFile);
swFree(bakFile);
return true;
}
INI::INI_Return INI::WriteLongInt(const char * section, const char * key, long int value)
{
char buf[20];
snprintf(buf, 20, "%ld", value);
return WriteString(section, key, buf);
}
// Create the new version as .new
// once complete, if something actually changed, then rename the .ini to .bak and rename the .new to .ini
// once complete, if nothing actually changed, then delete the .new
//
INI::INI_Return INI::WriteString(const char * section, const char * key, const char * value, int len)
{
bool found = false;
bool fileChanged = false;
INI_Return retVal;
if (len == -1)
len = strlen(value);
INFO("WriteString(%s,%s,%s)", section, key, value);
if (!iniFile)
return RetXLate[INI_NO_FILE_SPEC][version];
if (strlen(value) > INTERNAL_BUF_SIZE)
return RetXLate[INI_INTERNAL_ERROR][version];
char * newFile = (char *)swMalloc(strlen(iniFile)+1);
if (!newFile)
return RetXLate[INI_INTERNAL_ERROR][version]; // no memory
char * bakFile = (char *)swMalloc(strlen(iniFile)+1);
if (!bakFile) {
swFree(newFile);
return RetXLate[INI_INTERNAL_ERROR][version];
}
char * valBuf = (char *)swMalloc(len+1);
if (!valBuf) {
swFree(bakFile);
swFree(newFile);
return RetXLate[INI_INTERNAL_ERROR][version];
}
strcpy(bakFile, iniFile);
strcpy(newFile, iniFile);
strcpy(bakFile + strlen(bakFile) - 4, ".bak");
strcpy(newFile + strlen(newFile) - 4, ".new");
strncpy(valBuf, value, len);
valBuf[len] = '\0';
CleanUp();
INFO(" Opening [%s] and [%s]", iniFile, newFile);
FILE * fi = fopen(iniFile, "rt");
FILE * fo = fopen(newFile, "wt");
if (fo) {
char buf[INTERNAL_BUF_SIZE];
bool inSection = (section == NULL) ? true : false;
if (fi) {
INFO(" %s opened for reading", iniFile);
while(fgets(buf, sizeof(buf), fi)) {
// if not inSection, copy across
// if inSection and not key, copy across
// if InSection and key, write new value (or skip if value is null)
int x = strlen(buf) - 1; // remove trailing \r\n combinations
while (x >= 0 && buf[x] < ' ')
buf[x--] = '\0';
if (inSection && buf[0] != '[') {
char * eq = strchr(buf, '=');
if (eq) {
*eq++ = '\0';
if (strcmp(buf,key) == 0) {
// delete, or replace the old record
if (valBuf != NULL && strcmp(eq, valBuf) != 0) {
// replace the old record
if (valBuf != NULL) {
fprintf(fo, "%s=%s\r\n", key, valBuf);
INFO(" write: %s=%s", key, valBuf);
}
}
retVal = RetXLate[INI_SUCCESS][version];
fileChanged = true;
inSection = false;
found = true;
} else {
// write old record
fprintf(fo, "%s=%s\r\n", buf, eq);
INFO(" write: %s=%s", buf, eq);
}
} else {
// what to do with unknown record(s)?
// fprintf(fo, "%s\n", buf); // eliminate them
}
} else {
if (buf[0] == '[') {
char * br = strchr(buf, ']');
if (inSection) { // found next section while in good section
// Append new record to desired section
if (valBuf != NULL) {
fprintf(fo, "%s=%s\r\n", key, valBuf);
INFO(" write: %s=%s", key, valBuf);
fileChanged = true;
}
found = true;
retVal = RetXLate[INI_SUCCESS][version];
}
inSection = false;
// write old record
fprintf(fo, "%s\r\n", buf);
INFO(" write: %s", buf);
if (br) {
*br = '\0';
if (strcmp(buf+1, section) == 0)
inSection = true;
}
} else {
// copy unaltered records across
if (buf[0]) {
fprintf(fo, "%s\r\n", buf);
INFO(" write: %s", buf);
}
}
}
}
INFO("close %s", iniFile);
fclose(fi);
} else {
INFO(" %s did not previously exist.", iniFile);
}
if (!found) {
// No old file, just create it now
if (valBuf != NULL) {
if (!inSection) {
fprintf(fo, "\r\n[%s]\r\n", section);
INFO(" write: [%s]", section);
}
fprintf(fo, "%s=%s\r\n", key, valBuf);
INFO(" write: %s=%s", key, valBuf);
fileChanged = true;
}
found = true;
retVal = RetXLate[INI_SUCCESS][version];
}
INFO(" close %s", newFile);
fclose(fo);
} else {
ERR("*** Failed to open %s", newFile);
retVal = RetXLate[INI_FILE_NOT_FOUND][version];
}
if (fileChanged) {
INFO(" File changed: remove bak, rename ini to bak, rename new to ini");
remove(bakFile); // remove an old .bak
INFO(" a");
Rename(iniFile, bakFile); // move the existing .ini to .bak
INFO(" b");
Rename(newFile, iniFile); // move the new .new to .ini
INFO(" c");
#ifdef RTOS_H
Thread::wait(1000);
#else
wait(1);
#endif
INFO(" d");
}
swFree(valBuf);
swFree(newFile);
swFree(bakFile);
return retVal;
}
//***********************************************************
// Private version that also works with local file system
// by copying one file to the other.
// Returns -1 = error; 0 = success
//***********************************************************
int INI::Rename(const char *oldfname, const char *newfname)
{
int retval = 0;
INFO("Rename(%s,%s)", oldfname, newfname);
if (Copy(oldfname, newfname) == 0) {
remove(oldfname);
retval = 0;
} else {
retval = -1;
}
return (retval);
}
//***********************************************************
// Private version that also works with local file system
// Returns -1 = error; 0 = success
//***********************************************************
int INI::Copy(const char *src, const char *dst)
{
int retval = 0;
int ch;
INFO("Copy(%s,%s)", src, dst);
FILE *fpsrc = fopen(src, "r"); // src file
FILE *fpdst = fopen(dst, "w"); // dest file
if (fpsrc) {
INFO(" c1a");
if (fpdst) {
INFO(" c1b");
while (1) { // Copy src to dest
ch = fgetc(fpsrc); // until src EOF read.
if (ch == EOF) break;
fputc(ch, fpdst);
}
INFO(" c2");
fclose(fpsrc);
fclose(fpdst);
}
}
INFO(" c3");
if (Exists(dst)) {
retval = 0;
} else {
retval = -1;
}
INFO(" c4");
return (retval);
}
const char * INI::GetReturnMessage(INI_Return retVal) {
if (version == 0) {
switch (retVal) {
default:
case INI_V1_FAIL: return "INI Fail";
case INI_V1_SUCCESS: return "INI Success";
}
} else {
switch (retVal) {
case INI_SUCCESS: return "INI Success - operation succeeded";
case INI_NO_FILE_SPEC: return "INI Fail - no file was specified";
case INI_FILE_NOT_FOUND: return "INI Fail - ini file not found, or failed to open";
case INI_SECTION_NOT_FOUND: return "INI Fail - section not found";
case INI_KEY_NOT_FOUND: return "INI Fail - key not found";
case INI_BUF_TOO_SMALL: return "INI Fail - buffer to small for value";
case INI_INTERNAL_ERROR: return "INI Fail - internal error - can't malloc";
default: return "INI Fail - Code Unknown";
}
}
}
#if 0
// Test code for basic regression testing
//
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include "INI.h"
#define TESTFILE "test.ini"
//INI_V1_FAIL = 0, ///< Version 1 return value - Fail
//INI_V1_SUCCESS = 1, ///< Version 1 return value - Success
//INI_SUCCESS = 0, ///< Success - operation succeeded
//INI_NO_FILE_SPEC, ///< Fail - no file was specified
//INI_FILE_NOT_FOUND, ///< Fail - ini file not found, or failed to open
//INI_SECTION_NOT_FOUND, ///< Fail - section not found
//INI_KEY_NOT_FOUND, ///< Fail - key not found
//INI_BUF_TOO_SMALL, ///< Fail - buffer to small for value
//INI_INTERNAL_ERROR ///< Fail - internal error - can't alloc buffers
int main(int argc, char * argv[])
{
FILE * fp;
char buffer[100];
INI ini(TESTFILE, 2);
// Start testing
_unlink(TESTFILE);
assert(INI::INI_FILE_NOT_FOUND == ini.ReadString("Section 1", "Name 1", buffer, sizeof(buffer)));
fp = fopen(TESTFILE, "wt");
assert(fp);
fprintf(fp, "[Section 1]\n");
fprintf(fp, "Name 1=Value 1\n");
fprintf(fp, "Name 2=Value 2\n");
fprintf(fp, "\n");
fprintf(fp, "[Section 2]\n");
fprintf(fp, "Name 1=Value 2\n");
fprintf(fp, "Name 2=Value 2\n");
fprintf(fp, "Name 3=Value 3\n");
fprintf(fp, "\n");
fclose(fp);
assert(INI::INI_SUCCESS == ini.ReadString("Section 2", "Name 2", buffer, sizeof(buffer)));
assert(strcmp("Value 2", buffer) == 0);
assert(INI::INI_SECTION_NOT_FOUND == ini.ReadString("Section 3", "Name", buffer, sizeof(buffer)));
assert(INI::INI_KEY_NOT_FOUND == ini.ReadString("Section 1", "Name 3", buffer, sizeof(buffer)));
assert(INI::INI_SUCCESS == ini.WriteString("Section 1", "Name 4", "Value 4"));
assert(INI::INI_SUCCESS == ini.ReadString("Section 1", "Name 2", buffer, sizeof(buffer)));
assert(INI::INI_KEY_NOT_FOUND == ini.ReadString("Section 1", "Name 3", buffer, sizeof(buffer)));
assert(INI::INI_SUCCESS == ini.ReadString("Section 1", "Name 4", buffer, sizeof(buffer)));
assert(strcmp("Value 4", buffer) == 0);
assert(INI::INI_SUCCESS == ini.WriteString("Section 1", "Name 4", NULL));
assert(INI::INI_KEY_NOT_FOUND == ini.ReadString("Section 1", "Name 4", buffer, sizeof(buffer)));
return 0;
}
#endif