# ini.cpp

Go to the documentation of this file.

    00001 /* $Id: ini.cpp 15707 2009-03-14 00:26:03Z michi_cc $ */
    00002
    00005 #include "stdafx.h"
    00006 #include "core/alloc_func.hpp"
    00007 #include "core/math_func.hpp"
    00008 #include "core/mem_func.hpp"
    00009 #include "debug.h"
    00010 #include "ini_type.h"
    00011 #include "string_func.h"
    00012 #include "fileio_func.h"
    00013
    00014 #if (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L) || (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 500)
    00015 # define WITH_FDATASYNC
    00016 # include <unistd.h>
    00017 #endif
    00018
    00019 #ifdef WIN32
    00020 # include <shellapi.h>
    00021 #endif
    00022
    00023 IniItem::IniItem(IniGroup *parent, const char *name, size_t len) : next(NULL), value(NULL), comment(NULL)
    00024 {
    00025   if (len == 0) len = strlen(name);
    00026
    00027   this->name = strndup(name, len);
    00028   *parent->last_item = this;
    00029   parent->last_item = &this->next;
    00030 }
    00031
    00032 IniItem::~IniItem()
    00033 {
    00034   free(this->name);
    00035   free(this->value);
    00036   free(this->comment);
    00037
    00038   delete this->next;
    00039 }
    00040
    00041 void IniItem::SetValue(const char *value)
    00042 {
    00043   free(this->value);
    00044   this->value = strdup(value);
    00045 }
    00046
    00047 IniGroup::IniGroup(IniFile *parent, const char *name, size_t len) : next(NULL), type(IGT_VARIABLES), item(NULL), comment(NULL)
    00048 {
    00049   if (len == 0) len = strlen(name);
    00050
    00051   this->name = strndup(name, len);
    00052   this->last_item = &this->item;
    00053   *parent->last_group = this;
    00054   parent->last_group = &this->next;
    00055
    00056   if (parent->list_group_names == NULL) return;
    00057
    00058   for (uint i = 0; parent->list_group_names[i] != NULL; i++) {
    00059     if (strcmp(this->name, parent->list_group_names[i]) == 0) {
    00060       this->type = IGT_LIST;
    00061       return;
    00062     }
    00063   }
    00064 }
    00065
    00066 IniGroup::~IniGroup()
    00067 {
    00068   free(this->name);
    00069   free(this->comment);
    00070
    00071   delete this->item;
    00072   delete this->next;
    00073 }
    00074
    00075 IniItem *IniGroup::GetItem(const char *name, bool create)
    00076 {
    00077   for (IniItem *item = this->item; item != NULL; item = item->next) {
    00078     if (strcmp(item->name, name) == 0) return item;
    00079   }
    00080
    00081   if (!create) return NULL;
    00082
    00083   /* otherwise make a new one */
    00084   return new IniItem(this, name, strlen(name));
    00085 }
    00086
    00087 void IniGroup::Clear()
    00088 {
    00089   delete this->item;
    00090   this->item = NULL;
    00091   this->last_item = &this->item;
    00092 }
    00093
    00094 IniFile::IniFile(const char **list_group_names) : group(NULL), comment(NULL), list_group_names(list_group_names)
    00095 {
    00096   this->last_group = &this->group;
    00097 }
    00098
    00099 IniFile::~IniFile()
    00100 {
    00101   free(this->comment);
    00102   delete this->group;
    00103 }
    00104
    00105 IniGroup *IniFile::GetGroup(const char *name, size_t len)
    00106 {
    00107   if (len == 0) len = strlen(name);
    00108
    00109   /* does it exist already? */
    00110   for (IniGroup *group = this->group; group != NULL; group = group->next) {
    00111     if (!memcmp(group->name, name, len) && group->name[len] == 0) {
    00112       return group;
    00113     }
    00114   }
    00115
    00116   /* otherwise make a new one */
    00117   IniGroup *group = new IniGroup(this, name, len);
    00118   group->comment = strdup("\n");
    00119   return group;
    00120 }
    00121
    00122 void IniFile::RemoveGroup(const char *name)
    00123 {
    00124   size_t len = strlen(name);
    00125   IniGroup *prev = NULL;
    00126   IniGroup *group;
    00127
    00128   /* does it exist already? */
    00129   for (group = this->group; group != NULL; prev = group, group = group->next) {
    00130     if (memcmp(group->name, name, len) == 0) {
    00131       break;
    00132     }
    00133   }
    00134
    00135   if (group == NULL) return;
    00136
    00137   if (prev != NULL) {
    00138     prev->next = prev->next->next;
    00139     if (this->last_group == &group->next) this->last_group = &prev->next;
    00140   } else {
    00141     this->group = this->group->next;
    00142     if (this->last_group == &group->next) this->last_group = &this->group;
    00143   }
    00144
    00145   group->next = NULL;
    00146   delete group;
    00147 }
    00148
    00149 void IniFile::LoadFromDisk(const char *filename)
    00150 {
    00151   assert(this->last_group == &this->group);
    00152
    00153   char buffer[1024];
    00154   IniGroup *group = NULL;
    00155
    00156   char *comment = NULL;
    00157   uint comment_size = 0;
    00158   uint comment_alloc = 0;
    00159
    00160   size_t end;
    00161   /*
    00162    * Now we are going to open a file that contains no more than simple
    00163    * plain text. That would raise the question: "why open the file as
    00164    * if it is a binary file?". That's simple... Microsoft, in all
    00165    * their greatness and wisdom decided it would be useful if ftell
    00166    * is aware of '\r\n' and "sees" that as a single character. The
    00167    * easiest way to test for that situation is by searching for '\n'
    00168    * and decrease the value every time you encounter a '\n'. This will
    00169    * thus also make ftell "see" the '\r' when it is not there, so the
    00170    * result of ftell will be highly unreliable. So to work around this
    00171    * marvel of wisdom we have to open in as a binary file.
    00172    */
    00173   FILE *in = FioFOpenFile(filename, "rb", DATA_DIR, &end);
    00174   if (in == NULL) return;
    00175
    00176   end += ftell(in);
    00177
    00178   /* for each line in the file */
    00179   while ((size_t)ftell(in) < end && fgets(buffer, sizeof(buffer), in)) {
    00180     char c, *s;
    00181     /* trim whitespace from the left side */
    00182     for (s = buffer; *s == ' ' || *s == '\t'; s++) {}
    00183
    00184     /* trim whitespace from right side. */
    00185     char *e = s + strlen(s);
    00186     while (e > s && ((c = e[-1]) == '\n' || c == '\r' || c == ' ' || c == '\t')) e--;
    00187     *e = '\0';
    00188
    00189     /* skip comments and empty lines */
    00190     if (*s == '#' || *s == ';' || *s == '\0') {
    00191       uint ns = comment_size + (e - s + 1);
    00192       uint a = comment_alloc;
    00193       /* add to comment */
    00194       if (ns > a) {
    00195         a = max(a, 128U);
    00196         do a *= 2; while (a < ns);
    00197         comment = ReallocT(comment, comment_alloc = a);
    00198       }
    00199       uint pos = comment_size;
    00200       comment_size += (e - s + 1);
    00201       comment[pos + e - s] = '\n'; // comment newline
    00202       memcpy(comment + pos, s, e - s); // copy comment contents
    00203       continue;
    00204     }
    00205
    00206     /* it's a group? */
    00207     if (s[0] == '[') {
    00208       if (e[-1] != ']') {
    00209         ShowInfoF("ini: invalid group name '%s'", buffer);
    00210       } else {
    00211         e--;
    00212       }
    00213       s++; // skip [
    00214       group = new IniGroup(this, s, e - s);
    00215       if (comment_size) {
    00216         group->comment = strndup(comment, comment_size);
    00217         comment_size = 0;
    00218       }
    00219     } else if (group) {
    00220       char *t;
    00221       /* find end of keyname */
    00222       if (*s == '\"') {
    00223         s++;
    00224         for (t = s; *t != '\0' && *t != '\"'; t++) {}
    00225         if (*t == '\"') *t = ' ';
    00226       } else {
    00227         for (t = s; *t != '\0' && *t != '=' && *t != '\t' && *t != ' '; t++) {}
    00228       }
    00229
    00230       /* it's an item in an existing group */
    00231       IniItem *item = new IniItem(group, s, t - s);
    00232       if (comment_size) {
    00233         item->comment = strndup(comment, comment_size);
    00234         comment_size = 0;
    00235       }
    00236
    00237       /* find start of parameter */
    00238       while (*t == '=' || *t == ' ' || *t == '\t') t++;
    00239
    00240
    00241       /* remove starting quotation marks */
    00242       if (*t == '\"') t++;
    00243       /* remove ending quotation marks */
    00244       e = t + strlen(t);
    00245       if (e > t && e[-1] == '\"') e--;
    00246       *e = '\0';
    00247
    00248       item->value = strndup(t, e - t);
    00249     } else {
    00250       /* it's an orphan item */
    00251       ShowInfoF("ini: '%s' outside of group", buffer);
    00252     }
    00253   }
    00254
    00255   if (comment_size > 0) {
    00256     this->comment = strndup(comment, comment_size);
    00257     comment_size = 0;
    00258   }
    00259
    00260   free(comment);
    00261   fclose(in);
    00262 }
    00263
    00264 bool IniFile::SaveToDisk(const char *filename)
    00265 {
    00266   /*
    00267    * First write the configuration to a (temporary) file and then rename
    00268    * that file. This to prevent that when OpenTTD crashes during the save
    00269    * you end up with a truncated configuration file.
    00270    */
    00271   char file_new[MAX_PATH];
    00272
    00273   strecpy(file_new, filename, lastof(file_new));
    00274   strecat(file_new, ".new", lastof(file_new));
    00275   FILE *f = fopen(file_new, "w");
    00276   if (f == NULL) return false;
    00277
    00278   for (const IniGroup *group = this->group; group != NULL; group = group->next) {
    00279     if (group->comment) fputs(group->comment, f);
    00280     fprintf(f, "[%s]\n", group->name);
    00281     for (const IniItem *item = group->item; item != NULL; item = item->next) {
    00282       assert(item->value != NULL);
    00283       if (item->comment != NULL) fputs(item->comment, f);
    00284
    00285       /* protect item->name with quotes if needed */
    00286       if (strchr(item->name, ' ') != NULL) {
    00287         fprintf(f, "\"%s\"", item->name);
    00288       } else {
    00289         fprintf(f, "%s", item->name);
    00290       }
    00291
    00292       fprintf(f, " = %s\n", item->value);
    00293     }
    00294   }
    00295   if (this->comment) fputs(this->comment, f);
    00296
    00297 /*
    00298  * POSIX (and friends) do not guarantee that when a file is closed it is
    00299  * flushed to the disk. So we manually flush it do disk if we have the
    00300  * APIs to do so. We only need to flush the data as the metadata itself
    00301  * (modification date etc.) is not important to us; only the real data is.
    00302  */
    00303 #ifdef WITH_FDATASYNC
    00304   int ret = fdatasync(fileno(f));
    00305   fclose(f);
    00306   if (ret != 0) return false;
    00307 #else
    00308   fclose(f);
    00309 #endif
    00310
    00311 #if defined(WIN32) || defined(WIN64)
    00312   /* Allocate space for one more \0 character. */
    00313   TCHAR tfilename[MAX_PATH + 1], tfile_new[MAX_PATH + 1];
    00314   _tcsncpy(tfilename, OTTD2FS(filename), MAX_PATH);
    00315   _tcsncpy(tfile_new, OTTD2FS(file_new), MAX_PATH);
    00316   /* SHFileOperation wants a double '\0' terminated string. */
    00317   tfilename[MAX_PATH - 1] = '\0';
    00318   tfile_new[MAX_PATH - 1] = '\0';
    00319   tfilename[_tcslen(tfilename) + 1] = '\0';
    00320   tfile_new[_tcslen(tfile_new) + 1] = '\0';
    00321
    00322   /* Rename file without any user confirmation. */
    00323   SHFILEOPSTRUCT shfopt;
    00324   MemSetT(&shfopt, 0);
    00325   shfopt.wFunc  = FO_MOVE;
    00326   shfopt.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI | FOF_SILENT;
    00327   shfopt.pFrom  = tfile_new;
    00328   shfopt.pTo    = tfilename;
    00329   SHFileOperation(&shfopt);
    00330 #else
    00331   rename(file_new, filename);
    00332 #endif
    00333
    00334   return true;
    00335 }