David Smart / IniManager

Dependents:   Smart-WiFly-WebServer SignalGenerator WattEye X10Svr

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers IniManager.cpp Source File

IniManager.cpp

00001 // Simple INI file manager.
00002 //
00003 #ifdef WIN32
00004 #include "string.h"
00005 #include "stdlib.h"
00006 #include "stdio.h"
00007 #else
00008 #include "mbed.h"
00009 #endif
00010 
00011 #include "IniManager.h"
00012 
00013 //#include "Utility.h"            // private memory manager
00014 #ifndef UTILITY_H
00015 #define swMalloc malloc         // use the standard
00016 #define swFree free
00017 #endif
00018 
00019 //#define DEBUG "INI "      //Debug is disabled by default
00020 
00021 #include <cstdio>
00022 #if (defined(DEBUG) && !defined(TARGET_LPC11U24))
00023 #define DBG(x, ...)  std::printf("[DBG %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
00024 #define WARN(x, ...) std::printf("[WRN %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
00025 #define ERR(x, ...)  std::printf("[ERR %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
00026 #define INFO(x, ...) std::printf("[INF %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
00027 #else
00028 #define DBG(x, ...)
00029 #define WARN(x, ...)
00030 #define ERR(x, ...)
00031 #define INFO(x, ...)
00032 #endif
00033 
00034 // 2 versions, to translate new return values to old format
00035 // return RetXLate[new value][version]
00036 INI::INI_Return RetXLate[INI::INI_INTERNAL_ERROR+1][2] = {
00037 //  Ver1,                   Ver2 return values.
00038     INI::INI_V1_SUCCESS,    INI::INI_SUCCESS,            /// Success - operation succeeded
00039     INI::INI_V1_FAIL,       INI::INI_NO_FILE_SPEC,       /// Fail - no file was specified
00040     INI::INI_V1_FAIL,       INI::INI_FILE_NOT_FOUND,     /// Fail - ini file not found, or failed to open
00041     INI::INI_V1_FAIL,       INI::INI_SECTION_NOT_FOUND,  /// Fail - section not found
00042     INI::INI_V1_FAIL,       INI::INI_KEY_NOT_FOUND,      /// Fail - key not found
00043     INI::INI_V1_FAIL,       INI::INI_BUF_TOO_SMALL,      /// Fail - buffer to small for value
00044     INI::INI_V1_FAIL,       INI::INI_INTERNAL_ERROR      /// Fail - internal error - can't alloc buffers
00045 };
00046 
00047 INI::INI(const char * file, int Version)
00048     : iniFile(0)
00049 {
00050     SetFile(file);
00051     version = (Version == 2) ? 1 : 0;   // Version 1 or 2 is return value index 0 or 1
00052 }
00053 
00054 
00055 INI::~INI(void)
00056 {
00057     if (iniFile)
00058         swFree(iniFile);
00059 }
00060 
00061 
00062 bool INI::GetNextSection(const char * after, char * buffer, size_t bufferSize) {
00063     bool returnNext = false;
00064     bool found = false;
00065     
00066     if (!iniFile)
00067         return found;
00068     CleanUp();
00069     INFO("GetNextSection after [%s]", after);
00070     FILE * fp = fopen(iniFile,"rt");
00071     if (fp) {
00072         char buf[INTERNAL_BUF_SIZE];
00073 
00074         if (after == NULL || *after == '\0')
00075             returnNext = true;
00076         while(fgets(buf, sizeof(buf), fp)) {
00077             int x = strlen(buf) - 1;        // remove trailing \r\n combinations
00078             while (x >= 0 && buf[x] < ' ')
00079                 buf[x--] = '\0';
00080             INFO("  reading \"%s\"", buf);
00081             if (buf[0] == '[') {
00082                 char * pStart = buf + 1;
00083                 char * pRBrkt = strchr(buf, ']');
00084                 if (pRBrkt) {
00085                     *pRBrkt = '\0';
00086                     if (returnNext) {
00087                         // Guard against "[]" which would cause it to recycle from the start
00088                         if (strlen(pStart) > 0 && strlen(pStart) < bufferSize) {
00089                             strcpy(buffer, pStart);
00090                             found = true;
00091                             break;
00092                         }
00093                     } else if (strcmp(after, pStart) == 0) {
00094                         returnNext = true;
00095                     }
00096                 }
00097             }
00098         }
00099         fclose(fp);
00100     }
00101     return found;
00102 }
00103 
00104 
00105 bool INI::GetNextKey(const char * Section, const char * after, char * buffer, size_t bufferSize) {
00106     bool returnNext = false;
00107     bool inSection = false;
00108     bool found = false;
00109     
00110     if (!iniFile)
00111         return found;
00112     CleanUp();
00113     INFO("GetNextKey after [%s]", after);
00114     FILE * fp = fopen(iniFile,"rt");
00115     if (fp) {
00116         char buf[INTERNAL_BUF_SIZE];
00117 
00118         if (after == NULL || *after == '\0')
00119             returnNext = true;
00120         while(fgets(buf, sizeof(buf), fp)) {
00121             int x = strlen(buf) - 1;        // remove trailing \r\n combinations
00122             while (x >= 0 && buf[x] < ' ')
00123                 buf[x--] = '\0';
00124             INFO("  reading \"%s\"", buf);
00125             if (!(buf[0] == '[' || (buf[0] >= 'A' && buf[0] <= 'Z') || (buf[0] >= 'a' && buf[0] <= 'z')))
00126                 continue;
00127             if (buf[0] == '[') {
00128                 char * pStart = buf + 1;
00129                 char * pRBrkt = strchr(buf, ']');
00130                 if (pRBrkt) {
00131                     *pRBrkt = '\0';
00132                     if (inSection == true) {        // section after wanted, so done.
00133                         break;
00134                     } else if (strcmp(pStart, Section) == 0) {
00135                         inSection = true;
00136                         continue;
00137                     }
00138                 }
00139             } else if (inSection) {
00140                 char * pStart = buf;
00141                 char * pEqual = strchr(pStart, '=');
00142                 if (pEqual) {
00143                     *pEqual = '\0';
00144                     if (returnNext) {
00145                         if (strlen(pStart) < bufferSize) {
00146                             strcpy(buffer, pStart);
00147                             found = true;
00148                             break;
00149                         }
00150                     } else if (strcmp(after, pStart) == 0) {
00151                         returnNext = true;
00152                     }
00153                 }
00154             }
00155         }
00156         fclose(fp);
00157     }
00158     return found;
00159 }
00160 
00161 
00162 bool INI::Exists(const char * file)
00163 {
00164     if (file == NULL)
00165         file = iniFile;
00166     INFO("Exists(%s)", file);
00167     FILE * fp = fopen(file, "r");
00168     if (fp) {
00169         fclose(fp);
00170         INFO("  [%s] exists", file);
00171         return true;
00172     } else {
00173         INFO("  [%s] does not exist", file);
00174         return false;
00175     }
00176 }
00177 
00178 
00179 bool INI::SetFile(const char * file, int Version)
00180 {
00181     INFO("SetFile(%s,%d)", file, Version);
00182     version = (Version == 2) ? 1 : 0;   // Version 1 or 2 is return value index 0 or 1
00183     if (file) {
00184         if (iniFile)
00185             swFree(iniFile);
00186         iniFile = (char *)swMalloc(strlen(file)+1);
00187         if (iniFile) {
00188             strcpy(iniFile, file);
00189             INFO("  SetFile(%s) success", iniFile);
00190             return true;
00191         }
00192         else {
00193             iniFile = NULL;
00194             ERR("  SetFile(%s) failed to allocate memory", file);
00195         }
00196     }
00197     return false;
00198 }
00199 
00200 INI::INI_Return INI::ReadString(const char * section, const char * key, char * buffer, size_t bufferSize, const char * defaultString)
00201 {
00202     INI_Return retVal;
00203     bool found = false;
00204     
00205     retVal = RetXLate[INI_SECTION_NOT_FOUND][version];     // assume we won't find the section, until we do.
00206     if (!iniFile)
00207         return RetXLate[INI_NO_FILE_SPEC][version];
00208     CleanUp();
00209     INFO("ReadString from %s", iniFile);
00210     FILE * fp = fopen(iniFile,"rt");
00211     if (!fp) {
00212         if (defaultString == NULL) {
00213             return RetXLate[INI_FILE_NOT_FOUND][version];
00214         }
00215     } else {
00216         char buf[INTERNAL_BUF_SIZE];
00217         bool inSection = (section == NULL) ? true : false;
00218         while(fgets(buf, sizeof(buf), fp)) {
00219             int x = strlen(buf) - 1;        // remove trailing \r\n combinations
00220             while (x >= 0 && buf[x] < ' ')
00221                 buf[x--] = '\0';
00222             INFO("  reading \"%s\"", buf);
00223             if (!(buf[0] == '[' || (buf[0] >= 'A' && buf[0] <= 'Z') || (buf[0] >= 'a' && buf[0] <= 'z')))
00224                 continue;
00225             
00226             if (inSection && buf[0] != '[') {
00227                 char * eq = strchr(buf, '=');
00228                 if (eq) {
00229                     *eq++ = '\0';
00230                     if (strcmp(buf,key) == 0) {        // Found the key of interest
00231                         if (strlen(eq) < bufferSize) {
00232                             strcpy(buffer, eq);
00233                             memset(buf, 0, INTERNAL_BUF_SIZE);  // secure the memory space
00234                             found = true;
00235                             retVal = RetXLate[INI_SUCCESS][version];
00236                         } else {
00237                             retVal = RetXLate[INI_BUF_TOO_SMALL][version];
00238                         }
00239                         break;
00240                     }
00241                 }
00242             } else {
00243                 if (buf[0] == '[') {
00244                     char * br = strchr(buf, ']');
00245                     if (inSection) {        // we were in the section of interest and just hit the next section...
00246                         break;
00247                     } else {
00248                         inSection = false;
00249                         if (br) {
00250                             *br = '\0';
00251                             if (strcmp(buf+1, section) == 0) {
00252                                 inSection = true;
00253                                 retVal = RetXLate[INI_KEY_NOT_FOUND][version];     // assume we won't find the key, until we do
00254                             }
00255                         }
00256                     }
00257                 }
00258             }
00259         }
00260         fclose(fp);
00261     }
00262     if (!found && defaultString != NULL && *defaultString) {
00263         if (strlen(defaultString) < bufferSize) {
00264             strcpy(buffer, defaultString);
00265             retVal = RetXLate[INI_SUCCESS][version];
00266         } else {
00267             retVal = RetXLate[INI_BUF_TOO_SMALL][version];
00268         }
00269     }
00270     return retVal;
00271 }
00272 
00273 
00274 long int INI::ReadLongInt(const char * section, const char * key, long int defaultValue)
00275 {
00276     char localBuf[16];
00277     
00278     if (INI::INI_SUCCESS == ReadString(section, key, localBuf, sizeof(localBuf))) {
00279         return atol(localBuf);
00280     } else {
00281         return defaultValue;
00282     }
00283 }
00284 
00285 bool INI::CleanUp()
00286 {
00287     char * newFile = (char *)swMalloc(strlen(iniFile)+1);
00288     char * bakFile = (char *)swMalloc(strlen(iniFile)+1);
00289 
00290     if (newFile && bakFile) {
00291         INFO("CleanUp");
00292         strcpy(bakFile, iniFile);
00293         strcpy(newFile, iniFile);
00294         strcpy(bakFile + strlen(bakFile) - 4, ".bak");
00295         strcpy(newFile + strlen(newFile) - 4, ".new");
00296 
00297         if (Exists(newFile)) {
00298             int i;
00299             i = i;    // suppress warning about i not used when !DEBUG
00300             // helps recover if the system crashed before it could swap in the new file
00301             INFO("  *** found %s, repairing ...", newFile);
00302             i = remove(bakFile);            // remove an old .bak
00303             INFO("  remove(%s) returned %d", bakFile, i);
00304             i = Rename(iniFile, bakFile);   // move the existing .ini to .bak
00305             INFO("  rename(%s,%s) returned %d", iniFile, bakFile, i);
00306             i = Rename(newFile, iniFile);   // move the new .new to .ini
00307             INFO("  rename(%s,%s) returned %d", newFile, iniFile, i);
00308         } else {
00309             // nothing to do, move on silently.
00310         }
00311     }
00312     swFree(newFile);
00313     swFree(bakFile);
00314     return true;
00315 }
00316 
00317 INI::INI_Return INI::WriteLongInt(const char * section, const char * key, long int value)
00318 {
00319     char buf[20];
00320     snprintf(buf, 20, "%ld", value);
00321     return WriteString(section, key, buf);
00322 }
00323 
00324 
00325 // Create the new version as .new
00326 // once complete, if something actually changed, then rename the .ini to .bak and rename the .new to .ini
00327 // once complete, if nothing actually changed, then delete the .new
00328 //
00329 INI::INI_Return INI::WriteString(const char * section, const char * key, const char * value, int len)
00330 {
00331     bool found = false;
00332     bool fileChanged = false;
00333     INI_Return retVal;
00334     
00335     if (len == -1)
00336         len = strlen(value);
00337     INFO("WriteString(%s,%s,%s)", section, key, value);
00338     if (!iniFile)
00339         return RetXLate[INI_NO_FILE_SPEC][version];
00340         
00341     if (strlen(value) > INTERNAL_BUF_SIZE)
00342         return RetXLate[INI_INTERNAL_ERROR][version];
00343 
00344     char * newFile = (char *)swMalloc(strlen(iniFile)+1);
00345     if (!newFile)
00346         return RetXLate[INI_INTERNAL_ERROR][version];       // no memory
00347     char * bakFile = (char *)swMalloc(strlen(iniFile)+1);
00348     if (!bakFile) {
00349         swFree(newFile);
00350         return RetXLate[INI_INTERNAL_ERROR][version];
00351     }
00352     char * valBuf = (char *)swMalloc(len+1);
00353     if (!valBuf) {
00354         swFree(bakFile);
00355         swFree(newFile);
00356         return RetXLate[INI_INTERNAL_ERROR][version];
00357     }
00358 
00359     strcpy(bakFile, iniFile);
00360     strcpy(newFile, iniFile);
00361     strcpy(bakFile + strlen(bakFile) - 4, ".bak");
00362     strcpy(newFile + strlen(newFile) - 4, ".new");
00363     strncpy(valBuf, value, len);
00364     valBuf[len] = '\0';
00365     CleanUp();
00366 
00367     INFO("  Opening [%s] and [%s]", iniFile, newFile);
00368     FILE * fi = fopen(iniFile, "rt");
00369     FILE * fo = fopen(newFile, "wt");
00370     if (fo) {
00371         char buf[INTERNAL_BUF_SIZE];
00372         bool inSection = (section == NULL) ? true : false;
00373 
00374         if (fi) {
00375             INFO("  %s opened for reading", iniFile);
00376             while(fgets(buf, sizeof(buf), fi)) {
00377                 // if not inSection, copy across
00378                 // if inSection and not key, copy across
00379                 // if InSection and key, write new value (or skip if value is null)
00380                 int x = strlen(buf) - 1;        // remove trailing \r\n combinations
00381                 while (x >= 0 && buf[x] < ' ')
00382                     buf[x--] = '\0';
00383                 
00384                 if (inSection && buf[0] != '[') {
00385                     char * eq = strchr(buf, '=');
00386                     if (eq) {
00387                         *eq++ = '\0';
00388                         if (strcmp(buf,key) == 0) {
00389                             // delete, or replace the old record
00390                             if (valBuf != NULL && strcmp(eq, valBuf) != 0) {
00391                                 // replace the old record
00392                                 if (valBuf != NULL) {
00393                                     fprintf(fo, "%s=%s\r\n", key, valBuf);
00394                                     INFO("  write: %s=%s", key, valBuf);
00395                                 }
00396                             }
00397                             retVal = RetXLate[INI_SUCCESS][version];
00398                             fileChanged = true;
00399                             inSection = false;
00400                             found = true;
00401                         } else {
00402                             // write old record
00403                             fprintf(fo, "%s=%s\r\n", buf, eq);
00404                             INFO("  write: %s=%s", buf, eq);
00405                         }
00406                     } else {
00407                         // what to do with unknown record(s)?
00408                         // fprintf(fo, "%s\n", buf);    // eliminate them
00409                     }
00410                 } else {
00411                     if (buf[0] == '[') {
00412                         char * br = strchr(buf, ']');
00413                         if (inSection) { // found next section while in good section
00414                             // Append new record to desired section
00415                             if (valBuf != NULL) {
00416                                 fprintf(fo, "%s=%s\r\n", key, valBuf);
00417                                 INFO("  write: %s=%s", key, valBuf);
00418                                 fileChanged = true;
00419                             }
00420                             found = true;
00421                             retVal = RetXLate[INI_SUCCESS][version];
00422                         }
00423                         inSection = false;
00424                         // write old record
00425                         fprintf(fo, "\r\n%s\r\n", buf);
00426                         INFO("  write: %s", buf);
00427                         if (br) {
00428                             *br = '\0';
00429                             if (strcmp(buf+1, section) == 0)
00430                                 inSection = true;
00431                         }
00432                     } else {
00433                         // copy unaltered records across
00434                         if (buf[0]) {
00435                             fprintf(fo, "%s\r\n", buf);
00436                             INFO("  write: %s", buf);
00437                         }
00438                     }
00439                 }
00440             }
00441             INFO("close %s", iniFile);
00442             fclose(fi);
00443         } else {
00444             INFO("  %s did not previously exist.", iniFile);
00445         }
00446         if (!found) {
00447             // No old file, just create it now
00448             if (valBuf != NULL) {
00449                 if (!inSection) {
00450                     fprintf(fo, "\r\n[%s]\r\n", section);
00451                     INFO("  write: [%s]", section);
00452                 }
00453                 fprintf(fo, "%s=%s\r\n", key, valBuf);
00454                 INFO("  write: %s=%s", key, valBuf);
00455                 fileChanged = true;
00456             }
00457             found = true;
00458             retVal = RetXLate[INI_SUCCESS][version];
00459         }
00460         INFO("  close %s", newFile);
00461         fclose(fo);
00462     } else {
00463         ERR("*** Failed to open %s", newFile);
00464         retVal = RetXLate[INI_FILE_NOT_FOUND][version];
00465     }
00466     if (fileChanged) {
00467         INFO("  File changed: remove bak, rename ini to bak, rename new to ini");
00468         remove(bakFile);            // remove an old .bak
00469         INFO("  a");
00470         Rename(iniFile, bakFile);   // move the existing .ini to .bak
00471         INFO("  b");
00472         Rename(newFile, iniFile);   // move the new .new to .ini
00473         INFO("  c");
00474         #ifdef RTOS_H
00475         Thread::wait(1000);     // this seems to help with file contention
00476         #else
00477         wait(1);
00478         #endif
00479         INFO("  d");
00480     }
00481     swFree(valBuf);
00482     swFree(newFile);
00483     swFree(bakFile);
00484     return retVal;
00485 }
00486 
00487 
00488 //***********************************************************
00489 // Private version that also works with local file system
00490 // by copying one file to the other.
00491 //    Returns -1 = error; 0 = success
00492 //***********************************************************
00493 int INI::Rename(const char *oldfname, const char *newfname)
00494 {
00495     int retval = 0;
00496 
00497     INFO("Rename(%s,%s)", oldfname, newfname);
00498     if (Copy(oldfname, newfname) == 0) {
00499         remove(oldfname);
00500         retval = 0;
00501     } else {
00502         retval = -1;
00503     }
00504     return (retval);
00505 }
00506 
00507 //***********************************************************
00508 // Private version that also works with local file system
00509 //            Returns -1 = error; 0 = success
00510 //***********************************************************
00511 int INI::Copy(const char *src, const char *dst)
00512 {
00513     int retval = 0;
00514     int ch;
00515 
00516     INFO("Copy(%s,%s)", src, dst);
00517     FILE *fpsrc = fopen(src, "r");   // src file
00518     FILE *fpdst = fopen(dst, "w");   // dest file
00519 
00520     if (fpsrc) {
00521         INFO("  c1a");
00522         if (fpdst) {
00523             INFO("  c1b");
00524             while (1) {                  // Copy src to dest
00525                 ch = fgetc(fpsrc);       // until src EOF read.
00526                 if (ch == EOF) break;
00527                 fputc(ch, fpdst);
00528             }
00529             INFO("  c2");
00530         fclose(fpsrc);
00531         fclose(fpdst);
00532         }
00533     }
00534     INFO("  c3");
00535 
00536     if (Exists(dst)) {
00537         retval = 0;
00538     } else {
00539         retval = -1;
00540     }
00541     INFO("  c4");
00542     return (retval);
00543 }
00544 
00545 
00546 const char * INI::GetReturnMessage(INI_Return retVal) {
00547     if (version == 0) {
00548         switch (retVal) {
00549             default:
00550             case INI_V1_FAIL:           return "INI Fail";
00551             case INI_V1_SUCCESS:        return "INI Success";
00552         }
00553     } else {
00554         switch (retVal) {
00555             case INI_SUCCESS:           return "INI Success - operation succeeded";
00556             case INI_NO_FILE_SPEC:      return "INI Fail - no file was specified";
00557             case INI_FILE_NOT_FOUND:    return "INI Fail - ini file not found, or failed to open";
00558             case INI_SECTION_NOT_FOUND: return "INI Fail - section not found";
00559             case INI_KEY_NOT_FOUND:     return "INI Fail - key not found";
00560             case INI_BUF_TOO_SMALL:     return "INI Fail - buffer to small for value";
00561             case INI_INTERNAL_ERROR:    return "INI Fail - internal error - can't malloc";
00562             default:                    return "INI Fail - Code Unknown";
00563         }
00564     }
00565 }
00566 
00567 #if 0
00568 // Test code for basic regression testing
00569 //
00570 #include <stdio.h>
00571 #include <assert.h>
00572 #include <string.h>
00573 
00574 #include "INI.h"
00575 
00576 #define TESTFILE "test.ini"
00577 
00578 
00579 //INI_V1_FAIL = 0,        ///< Version 1 return value - Fail
00580 //INI_V1_SUCCESS = 1,     ///< Version 1 return value - Success
00581 //INI_SUCCESS = 0,        ///< Success - operation succeeded
00582 //INI_NO_FILE_SPEC,       ///< Fail - no file was specified
00583 //INI_FILE_NOT_FOUND,     ///< Fail - ini file not found, or failed to open
00584 //INI_SECTION_NOT_FOUND,  ///< Fail - section not found
00585 //INI_KEY_NOT_FOUND,      ///< Fail - key not found
00586 //INI_BUF_TOO_SMALL,      ///< Fail - buffer to small for value
00587 //INI_INTERNAL_ERROR      ///< Fail - internal error - can't alloc buffers
00588 
00589 int main(int argc, char * argv[])
00590 {
00591     FILE * fp;
00592     char buffer[100];
00593     INI ini(TESTFILE, 2);
00594 
00595     // Start testing
00596     _unlink(TESTFILE);
00597     assert(INI::INI_FILE_NOT_FOUND == ini.ReadString("Section 1", "Name 1", buffer, sizeof(buffer)));
00598 
00599     fp = fopen(TESTFILE, "wt");
00600     assert(fp);
00601     fprintf(fp, "[Section 1]\n");
00602     fprintf(fp, "Name 1=Value 1\n");
00603     fprintf(fp, "Name 2=Value 2\n");
00604     fprintf(fp, "\n");
00605     fprintf(fp, "[Section 2]\n");
00606     fprintf(fp, "Name 1=Value 2\n");
00607     fprintf(fp, "Name 2=Value 2\n");
00608     fprintf(fp, "Name 3=Value 3\n");
00609     fprintf(fp, "\n");
00610     fclose(fp);
00611 
00612     assert(INI::INI_SUCCESS == ini.ReadString("Section 2", "Name 2", buffer, sizeof(buffer)));
00613     assert(strcmp("Value 2", buffer) == 0);
00614 
00615     assert(INI::INI_SECTION_NOT_FOUND == ini.ReadString("Section 3", "Name", buffer, sizeof(buffer)));
00616     assert(INI::INI_KEY_NOT_FOUND == ini.ReadString("Section 1", "Name 3", buffer, sizeof(buffer)));
00617 
00618     assert(INI::INI_SUCCESS == ini.WriteString("Section 1", "Name 4", "Value 4"));
00619     assert(INI::INI_SUCCESS == ini.ReadString("Section 1", "Name 2", buffer, sizeof(buffer)));
00620     assert(INI::INI_KEY_NOT_FOUND == ini.ReadString("Section 1", "Name 3", buffer, sizeof(buffer)));
00621     assert(INI::INI_SUCCESS == ini.ReadString("Section 1", "Name 4", buffer, sizeof(buffer)));
00622     assert(strcmp("Value 4", buffer) == 0);
00623 
00624     assert(INI::INI_SUCCESS == ini.WriteString("Section 1", "Name 4", NULL));
00625     assert(INI::INI_KEY_NOT_FOUND == ini.ReadString("Section 1", "Name 4", buffer, sizeof(buffer)));
00626 
00627     return 0;
00628 }
00629 #endif
00630 
00631 
00632