A simple .ini file interface.
Dependents: Smart-WiFly-WebServer SignalGenerator WattEye X10Svr
IniManager.cpp
- Committer:
- WiredHome
- Date:
- 2018-04-11
- Revision:
- 24:ba5fa9548f59
- Parent:
- 23:18b5f3d7ef14
- Child:
- 25:1362b843de86
File content as of revision 24:ba5fa9548f59:
// 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) { if (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; 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; retVal = RetXLate[INI_SECTION_NOT_FOUND][version]; // assume we won't find the section, until we do. 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