# ini.cpp

Go to the documentation of this file.

    00001 /* $Id$ */
    00002
    00003 /*
    00004  * This file is part of OpenTTD.
    00005  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
    00006  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
    00008  */
    00009
    00012 #include "stdafx.h"
    00013 #include "core/alloc_func.hpp"
    00014 #include "core/mem_func.hpp"
    00015 #include "debug.h"
    00016 #include "ini_type.h"
    00017 #include "string_func.h"
    00018 #include "fileio_func.h"
    00019
    00020 #if (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L) || (defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 500)
    00021 # define WITH_FDATASYNC
    00022 # include <unistd.h>
    00023 #endif
    00024
    00025 #ifdef WIN32
    00026 # include <shellapi.h>
    00027 #endif
    00028
    00035 IniItem::IniItem(IniGroup *parent, const char *name, size_t len) : next(NULL), value(NULL), comment(NULL)
    00036 {
    00037   if (len == 0) len = strlen(name);
    00038
    00039   this->name = strndup(name, len);
    00040   *parent->last_item = this;
    00041   parent->last_item = &this->next;
    00042 }
    00043
    00045 IniItem::~IniItem()
    00046 {
    00047   free(this->name);
    00048   free(this->value);
    00049   free(this->comment);
    00050
    00051   delete this->next;
    00052 }
    00053
    00058 void IniItem::SetValue(const char *value)
    00059 {
    00060   free(this->value);
    00061   this->value = strdup(value);
    00062 }
    00063
    00070 IniGroup::IniGroup(IniFile *parent, const char *name, size_t len) : next(NULL), type(IGT_VARIABLES), item(NULL), comment(NULL)
    00071 {
    00072   if (len == 0) len = strlen(name);
    00073
    00074   this->name = strndup(name, len);
    00075   this->last_item = &this->item;
    00076   *parent->last_group = this;
    00077   parent->last_group = &this->next;
    00078
    00079   if (parent->list_group_names == NULL) return;
    00080
    00081   for (uint i = 0; parent->list_group_names[i] != NULL; i++) {
    00082     if (strcmp(this->name, parent->list_group_names[i]) == 0) {
    00083       this->type = IGT_LIST;
    00084       return;
    00085     }
    00086   }
    00087 }
    00088
    00090 IniGroup::~IniGroup()
    00091 {
    00092   free(this->name);
    00093   free(this->comment);
    00094
    00095   delete this->item;
    00096   delete this->next;
    00097 }
    00098
    00106 IniItem *IniGroup::GetItem(const char *name, bool create)
    00107 {
    00108   for (IniItem *item = this->item; item != NULL; item = item->next) {
    00109     if (strcmp(item->name, name) == 0) return item;
    00110   }
    00111
    00112   if (!create) return NULL;
    00113
    00114   /* otherwise make a new one */
    00115   return new IniItem(this, name, strlen(name));
    00116 }
    00117
    00121 void IniGroup::Clear()
    00122 {
    00123   delete this->item;
    00124   this->item = NULL;
    00125   this->last_item = &this->item;
    00126 }
    00127
    00133 IniFile::IniFile(const char * const *list_group_names) : group(NULL), comment(NULL), list_group_names(list_group_names)
    00134 {
    00135   this->last_group = &this->group;
    00136 }
    00137
    00139 IniFile::~IniFile()
    00140 {
    00141   free(this->comment);
    00142   delete this->group;
    00143 }
    00144
    00152 IniGroup *IniFile::GetGroup(const char *name, size_t len)
    00153 {
    00154   if (len == 0) len = strlen(name);
    00155
    00156   /* does it exist already? */
    00157   for (IniGroup *group = this->group; group != NULL; group = group->next) {
    00158     if (!strncmp(group->name, name, len) && group->name[len] == 0) {
    00159       return group;
    00160     }
    00161   }
    00162
    00163   /* otherwise make a new one */
    00164   IniGroup *group = new IniGroup(this, name, len);
    00165   group->comment = strdup("\n");
    00166   return group;
    00167 }
    00168
    00173 void IniFile::RemoveGroup(const char *name)
    00174 {
    00175   size_t len = strlen(name);
    00176   IniGroup *prev = NULL;
    00177   IniGroup *group;
    00178
    00179   /* does it exist already? */
    00180   for (group = this->group; group != NULL; prev = group, group = group->next) {
    00181     if (strncmp(group->name, name, len) == 0) {
    00182       break;
    00183     }
    00184   }
    00185
    00186   if (group == NULL) return;
    00187
    00188   if (prev != NULL) {
    00189     prev->next = prev->next->next;
    00190     if (this->last_group == &group->next) this->last_group = &prev->next;
    00191   } else {
    00192     this->group = this->group->next;
    00193     if (this->last_group == &group->next) this->last_group = &this->group;
    00194   }
    00195
    00196   group->next = NULL;
    00197   delete group;
    00198 }
    00199
    00205 void IniFile::LoadFromDisk(const char *filename)
    00206 {
    00207   assert(this->last_group == &this->group);
    00208
    00209   char buffer[1024];
    00210   IniGroup *group = NULL;
    00211
    00212   char *comment = NULL;
    00213   uint comment_size = 0;
    00214   uint comment_alloc = 0;
    00215
    00216   size_t end;
    00217   /*
    00218    * Now we are going to open a file that contains no more than simple
    00219    * plain text. That would raise the question: "why open the file as
    00220    * if it is a binary file?". That's simple... Microsoft, in all
    00221    * their greatness and wisdom decided it would be useful if ftell
    00222    * is aware of '\r\n' and "sees" that as a single character. The
    00223    * easiest way to test for that situation is by searching for '\n'
    00224    * and decrease the value every time you encounter a '\n'. This will
    00225    * thus also make ftell "see" the '\r' when it is not there, so the
    00226    * result of ftell will be highly unreliable. So to work around this
    00227    * marvel of wisdom we have to open in as a binary file.
    00228    */
    00229   FILE *in = FioFOpenFile(filename, "rb", DATA_DIR, &end);
    00230   if (in == NULL) return;
    00231
    00232   end += ftell(in);
    00233
    00234   /* for each line in the file */
    00235   while ((size_t)ftell(in) < end && fgets(buffer, sizeof(buffer), in)) {
    00236     char c, *s;
    00237     /* trim whitespace from the left side */
    00238     for (s = buffer; *s == ' ' || *s == '\t'; s++) {}
    00239
    00240     /* trim whitespace from right side. */
    00241     char *e = s + strlen(s);
    00242     while (e > s && ((c = e[-1]) == '\n' || c == '\r' || c == ' ' || c == '\t')) e--;
    00243     *e = '\0';
    00244
    00245     /* skip comments and empty lines */
    00246     if (*s == '#' || *s == ';' || *s == '\0') {
    00247       uint ns = comment_size + (e - s + 1);
    00248       uint a = comment_alloc;
    00249       /* add to comment */
    00250       if (ns > a) {
    00251         a = max(a, 128U);
    00252         do a *= 2; while (a < ns);
    00253         comment = ReallocT(comment, comment_alloc = a);
    00254       }
    00255       uint pos = comment_size;
    00256       comment_size += (e - s + 1);
    00257       comment[pos + e - s] = '\n'; // comment newline
    00258       memcpy(comment + pos, s, e - s); // copy comment contents
    00259       continue;
    00260     }
    00261
    00262     /* it's a group? */
    00263     if (s[0] == '[') {
    00264       if (e[-1] != ']') {
    00265         ShowInfoF("ini: invalid group name '%s'", buffer);
    00266       } else {
    00267         e--;
    00268       }
    00269       s++; // skip [
    00270       group = new IniGroup(this, s, e - s);
    00271       if (comment_size) {
    00272         group->comment = strndup(comment, comment_size);
    00273         comment_size = 0;
    00274       }
    00275     } else if (group) {
    00276       char *t;
    00277       /* find end of keyname */
    00278       if (*s == '\"') {
    00279         s++;
    00280         for (t = s; *t != '\0' && *t != '\"'; t++) {}
    00281         if (*t == '\"') *t = ' ';
    00282       } else {
    00283         for (t = s; *t != '\0' && *t != '=' && *t != '\t' && *t != ' '; t++) {}
    00284       }
    00285
    00286       /* it's an item in an existing group */
    00287       IniItem *item = new IniItem(group, s, t - s);
    00288       if (comment_size) {
    00289         item->comment = strndup(comment, comment_size);
    00290         comment_size = 0;
    00291       }
    00292
    00293       /* find start of parameter */
    00294       while (*t == '=' || *t == ' ' || *t == '\t') t++;
    00295
    00296       bool quoted = (*t == '\"');
    00297       /* remove starting quotation marks */
    00298       if (*t == '\"') t++;
    00299       /* remove ending quotation marks */
    00300       e = t + strlen(t);
    00301       if (e > t && e[-1] == '\"') e--;
    00302       *e = '\0';
    00303
    00304       /* If the value was not quoted and empty, it must be NULL */
    00305       item->value = (!quoted && e == t) ? NULL : strndup(t, e - t);
    00306     } else {
    00307       /* it's an orphan item */
    00308       ShowInfoF("ini: '%s' outside of group", buffer);
    00309     }
    00310   }
    00311
    00312   if (comment_size > 0) {
    00313     this->comment = strndup(comment, comment_size);
    00314     comment_size = 0;
    00315   }
    00316
    00317   free(comment);
    00318   fclose(in);
    00319 }
    00320
    00326 bool IniFile::SaveToDisk(const char *filename)
    00327 {
    00328   /*
    00329    * First write the configuration to a (temporary) file and then rename
    00330    * that file. This to prevent that when OpenTTD crashes during the save
    00331    * you end up with a truncated configuration file.
    00332    */
    00333   char file_new[MAX_PATH];
    00334
    00335   strecpy(file_new, filename, lastof(file_new));
    00336   strecat(file_new, ".new", lastof(file_new));
    00337   FILE *f = fopen(file_new, "w");
    00338   if (f == NULL) return false;
    00339
    00340   for (const IniGroup *group = this->group; group != NULL; group = group->next) {
    00341     if (group->comment) fputs(group->comment, f);
    00342     fprintf(f, "[%s]\n", group->name);
    00343     for (const IniItem *item = group->item; item != NULL; item = item->next) {
    00344       if (item->comment != NULL) fputs(item->comment, f);
    00345
    00346       /* protect item->name with quotes if needed */
    00347       if (strchr(item->name, ' ') != NULL ||
    00348           item->name[0] == '[') {
    00349         fprintf(f, "\"%s\"", item->name);
    00350       } else {
    00351         fprintf(f, "%s", item->name);
    00352       }
    00353
    00354       fprintf(f, " = %s\n", item->value == NULL ? "" : item->value);
    00355     }
    00356   }
    00357   if (this->comment) fputs(this->comment, f);
    00358
    00359 /*
    00360  * POSIX (and friends) do not guarantee that when a file is closed it is
    00361  * flushed to the disk. So we manually flush it do disk if we have the
    00362  * APIs to do so. We only need to flush the data as the metadata itself
    00363  * (modification date etc.) is not important to us; only the real data is.
    00364  */
    00365 #ifdef WITH_FDATASYNC
    00366   int ret = fdatasync(fileno(f));
    00367   fclose(f);
    00368   if (ret != 0) return false;
    00369 #else
    00370   fclose(f);
    00371 #endif
    00372
    00373 #if defined(WIN32) || defined(WIN64)
    00374   /* Allocate space for one more \0 character. */
    00375   TCHAR tfilename[MAX_PATH + 1], tfile_new[MAX_PATH + 1];
    00376   _tcsncpy(tfilename, OTTD2FS(filename), MAX_PATH);
    00377   _tcsncpy(tfile_new, OTTD2FS(file_new), MAX_PATH);
    00378   /* SHFileOperation wants a double '\0' terminated string. */
    00379   tfilename[MAX_PATH - 1] = '\0';
    00380   tfile_new[MAX_PATH - 1] = '\0';
    00381   tfilename[_tcslen(tfilename) + 1] = '\0';
    00382   tfile_new[_tcslen(tfile_new) + 1] = '\0';
    00383
    00384   /* Rename file without any user confirmation. */
    00385   SHFILEOPSTRUCT shfopt;
    00386   MemSetT(&shfopt, 0);
    00387   shfopt.wFunc  = FO_MOVE;
    00388   shfopt.fFlags = FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI | FOF_SILENT;
    00389   shfopt.pFrom  = tfile_new;
    00390   shfopt.pTo    = tfilename;
    00391   SHFileOperation(&shfopt);
    00392 #else
    00393   rename(file_new, filename);
    00394 #endif
    00395
    00396   return true;
    00397 }