bincimap

Log | Files | Refs | LICENSE

storage.cc (15101B)


      1 /* -*- Mode: c++; -*- */
      2 /*  --------------------------------------------------------------------
      3  *  Filename:
      4  *    storage.cc
      5  *  
      6  *  Description:
      7  *    Implementation of the Binc::Storage format.
      8  *
      9  *  Authors:
     10  *    Andreas Aardal Hanssen <andreas-binc@bincimap.org>
     11  *
     12  *  Bugs:
     13  *
     14  *  ChangeLog:
     15  *
     16  *  --------------------------------------------------------------------
     17  *  Copyright 2002-2005 Andreas Aardal Hanssen
     18  *
     19  *  This program is free software; you can redistribute it and/or modify
     20  *  it under the terms of the GNU General Public License as published by
     21  *  the Free Software Foundation; either version 2 of the License, or
     22  *  (at your option) any later version.
     23  *
     24  *  This program is distributed in the hope that it will be useful,
     25  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     26  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     27  *  GNU General Public License for more details.
     28  *
     29  *  You should have received a copy of the GNU General Public License
     30  *  along with this program; if not, write to the Free Software
     31  *  Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
     32  *  --------------------------------------------------------------------
     33  */
     34 #ifdef HAVE_CONFIG_H
     35 #include <config.h>
     36 #endif
     37 #include "storage.h"
     38 #include <fcntl.h>
     39 #include <errno.h>
     40 #include <stdio.h>
     41 #include <unistd.h>
     42 #include <map>
     43 #include <string>
     44 #include "../src/convert.h"
     45 
     46 using namespace ::std;
     47 using namespace ::Binc;
     48 
     49 class Lexer {
     50 public:
     51   Lexer(FILE *fp);
     52 
     53   unsigned int getCurrentColumn() const { return col; }
     54   unsigned int getCurrentLine() const { return line; }
     55   const string &getLastError() const { return lastError; }
     56 
     57   enum Type {
     58     LeftCurly, RightCurly, Equals, Comma, QMark, Colon, Semicolon,
     59     Text, EndOfFile, Error
     60   };
     61 
     62   bool nextToken(string *token, Type *type);
     63 
     64 protected:
     65   enum State {
     66     Searching, RestOfLineComment, CStyleComment,
     67     QuotedString, HexString, TextString
     68   };
     69 
     70 private:
     71   FILE *fp;
     72   State state;
     73   unsigned int line;
     74   unsigned int col;
     75   string lastError;
     76 };
     77 
     78 //------------------------------------------------------------------------
     79 Lexer::Lexer(FILE *f) : fp(f), state(Searching), line(0), col(0)
     80 {
     81 }
     82 
     83 //------------------------------------------------------------------------
     84 bool Lexer::nextToken(string *token, Type *type)
     85 {
     86   int lastc = '\0';
     87 
     88   string buffer;
     89   bool escaped = false;
     90 
     91   for (;;) {
     92     int c = fgetc(fp);
     93     if (c == EOF) {
     94       if (ferror(fp)) {
     95 	lastError = strerror(errno);
     96 	*type = Error;
     97 	return false;
     98       }
     99 
    100       if (state != Searching) {
    101 	lastError = "unexpected end of file";
    102 	*type = Error;
    103 	return false;
    104       }
    105 
    106       *type = EndOfFile;
    107       return true;
    108     }
    109 
    110     if (c == '\n') {
    111       ++line;
    112       col = 0;
    113     } else
    114       ++col;
    115 
    116     switch (state) {
    117     case Searching:
    118       // If whitespace, keep searching
    119       if (c == ' ' || c == '\t' || c == '\r' || c == '\n'
    120 	  || c == '\f' || c == '\v');
    121       else if (col == 0 && (c == '#' || c == ';'))
    122 	state = RestOfLineComment;
    123       else if (lastc == '/' && c == '/')
    124 	state = RestOfLineComment;
    125       else if (lastc == '/' && c == '*')
    126 	state = CStyleComment;
    127       else if (lastc == '*' && c == '/' && state == CStyleComment) {
    128 	state = Searching;
    129 	c = '\0'; // avoid ambiguity
    130       } else if (c == '?') {
    131 	*token = "?";
    132 	*type = QMark;
    133 	return true;
    134       } else if (c == ':') {
    135 	*token = ":";
    136 	*type = Colon;
    137 	return true;
    138       } else if (c == ';') {
    139 	*token = ";";
    140 	*type = Semicolon;
    141 	return true;
    142       } else if (c == '{') {
    143 	*token = "{";
    144 	*type = LeftCurly;
    145 	return true;
    146       } else if (c == '}') {
    147 	*token = "}";
    148 	*type = RightCurly;
    149 	return true;
    150       } else if (c == ',') {
    151 	*token = ",";
    152 	*type = Comma;
    153 	return true;
    154       } else if (c == '=') {
    155 	*token = "=";
    156 	*type = Equals;
    157 	return true;
    158       } else if (c == '\"') {
    159 	state = QuotedString;
    160       } else if (c == '<') {
    161 	state = HexString;
    162       } else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
    163 		 || (c >= '0' && c <= '9') || c == '.' || c == '_'
    164 		 || c == '-' || c == '^') {
    165 	state = TextString;
    166 	buffer = "";
    167 	buffer += (char) c;
    168       } else if (c != '/') {
    169 	// Syntax error
    170 	lastError = "unexpected character '";
    171 	lastError += (char) c;
    172 	lastError += "'";
    173 	return false;
    174       }
    175 
    176       break;
    177     case RestOfLineComment:
    178       if (c == '\n')
    179 	state = Searching;
    180       break;
    181     case CStyleComment:
    182       if (c == '/' && lastc == '*')
    183 	state = Searching;
    184       break;
    185     case QuotedString:
    186       if (escaped) {
    187 	buffer += (char) c;
    188 	escaped = false;
    189       } else if (c == '\\') {
    190 	escaped = true;
    191       } else if (c == '\"') {
    192 	*token = buffer;
    193 	*type = Text;
    194 	buffer = "";
    195 	state = Searching;
    196 	return true;
    197       } else {
    198 	buffer += (char) c;
    199       }
    200 
    201       break;
    202     case TextString:
    203       if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
    204 	  || (c >= '0' && c <= '9') || c == '_' 
    205 	  || c == '.' || c == '-' || c == '^')
    206 	buffer += (char) c;
    207       else {
    208 	*token = buffer;
    209 	buffer = "";
    210 	*type = Text;
    211 	ungetc(c, fp);
    212 	state = Searching;
    213 	return true;
    214       }
    215       break;
    216     case HexString:
    217       if ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')
    218 	  || (c >= '0' && c <= '9'))
    219 	buffer += (char) c;
    220       else if (c == '>') {
    221 	*token = fromHex(buffer);
    222 	buffer = "";
    223 	*type = Text;
    224 	state = Searching;
    225 	return true;
    226       } else {
    227 	lastError = "expected a-f, A-F, 0-9 or '>'";
    228 	return false;
    229       }
    230       break;
    231     };
    232 
    233     lastc = c;
    234   }
    235 }
    236 
    237 //------------------------------------------------------------------------
    238 Storage::Storage(const string &f, Mode m) 
    239   : fp(0), fd(0), fileName(f), state(Searching), mode(m),
    240     lastError("unknown error"), atEndOfFile(false), firstSection(true)
    241 {
    242   if (m == ReadOnly) {
    243     if ((fp = fopen(f.c_str(), m == ReadOnly ? "r" : "w")) == 0) {
    244       lastError = "when opening \"" + f + "\": ";
    245       lastError += strerror(errno);
    246     }
    247   } else {
    248     // create a safe file name
    249     string tpl = fileName + "XXXXXX";
    250     char *ftemplate = new char[tpl.length() + 1];
    251     if (ftemplate == 0) {
    252       lastError = "out of memory, couldn't create safe filename";
    253       return;
    254     }
    255 
    256     if (ftemplate) {
    257       strcpy(ftemplate, tpl.c_str());
    258       while ((fd = mkstemp(ftemplate)) == 0 && errno == EEXIST) {
    259       }
    260 
    261       if (fd == 0) {
    262 	lastError = "when opening \"" + f + "\": ";
    263 	lastError += strerror(errno);
    264       }
    265     }
    266 
    267     fileName = ftemplate;
    268   }
    269 }
    270 
    271 //------------------------------------------------------------------------
    272 Storage::~Storage(void)
    273 {
    274   if (fp)
    275     fclose(fp);
    276   if (fd)
    277     close(fd);
    278 }
    279 
    280 //------------------------------------------------------------------------
    281 bool Storage::ok(void) const
    282 {
    283   return fp != 0;
    284 }
    285 
    286 //------------------------------------------------------------------------
    287 bool Storage::get(string *section, string *key, string *value)
    288 {
    289   if (mode == WriteOnly) {
    290     lastError = "unable to read from \"" + fileName + "\" when"
    291       " in WriteOnly mode";
    292     return false;
    293   }
    294 
    295   if (!fp)
    296     return false;
    297 
    298   string keytmp;
    299   string valuetmp;
    300   
    301   Lexer lex(fp);
    302   Lexer::Type type;
    303   string token;
    304   while (lex.nextToken(&token, &type) && type != Lexer::Error) {
    305     if (type == Lexer::EndOfFile) {
    306       if (state != Searching) {
    307 	lastError = "unexpected end of file";
    308 	return false;
    309       }
    310 
    311       atEndOfFile = true;
    312       return false;
    313     }
    314 
    315     // convert from alias to token
    316     if (type == Lexer::Text && aliases.find(token) != aliases.end())
    317 	token = aliases[token];
    318 
    319     switch (state) {
    320     case Searching:
    321       if (type == Lexer::QMark) {
    322 	state = AliasKey;
    323       } else if (type != Lexer::Text) {
    324 	lastError = "expected text";
    325 	lastError += " at line " + toString(lex.getCurrentLine());
    326 	lastError += ", col " + toString(lex.getCurrentColumn());
    327   	return false;
    328       } else {
    329 	curSection = token;
    330 	state = Section;
    331       }
    332       break;
    333     case AliasKey: 
    334       if (type != Lexer::Text) {
    335 	lastError = "expected text";
    336 	lastError += " at line " + toString(lex.getCurrentLine());
    337 	lastError += ", col " + toString(lex.getCurrentColumn());
    338   	return false;
    339       }
    340       
    341       keytmp = token;
    342       state = AliasColon;
    343       break;
    344     case AliasColon:
    345       if (type != Lexer::Colon) {
    346 	lastError = "expected colon";
    347 	lastError += " at line " + toString(lex.getCurrentLine());
    348 	lastError += ", col " + toString(lex.getCurrentColumn());
    349   	return false;
    350       }
    351       state = AliasValue;
    352       break;
    353     case AliasValue:
    354       if (type != Lexer::Text) {
    355 	lastError = "expected text";
    356 	lastError += " at line " + toString(lex.getCurrentLine());
    357 	lastError += ", col " + toString(lex.getCurrentColumn());
    358   	return false;
    359       }
    360       
    361       valuetmp = token;
    362       state = AliasEnd;
    363       break;
    364     case AliasEnd:
    365       if (type != Lexer::Semicolon) {
    366 	lastError = "expected text";
    367 	lastError += " at line " + toString(lex.getCurrentLine());
    368 	lastError += ", col " + toString(lex.getCurrentColumn());
    369   	return false;
    370       }
    371 
    372       aliases[keytmp] = valuetmp;
    373       state = Searching;
    374       break;
    375     case Section:
    376       if (type != Lexer::LeftCurly) {
    377 	lastError = "expected '{'";
    378 	lastError += " at line " + toString(lex.getCurrentLine());
    379 	lastError += ", col " + toString(lex.getCurrentColumn());
    380 	return false;
    381       }
    382 
    383       state = Key;
    384       keytmp = "";
    385       break;
    386     case Key:
    387       if (type == Lexer::Text) {
    388 	if (keytmp != "")
    389 	  keytmp += " ";
    390 	keytmp += token;
    391       } else if (type == Lexer::Equals) {
    392 	state = Value;
    393       } else {
    394 	if (type == Lexer::RightCurly) {
    395 	  if (keytmp != "") {
    396 	    lastError = "unexpected '}'";
    397 	    lastError += " at line " + toString(lex.getCurrentLine());
    398 	    lastError += ", col " + toString(lex.getCurrentColumn());
    399 	    return false;
    400 	  } else
    401 	    state = Searching;
    402 	} else {
    403 	  lastError = "expected text or '='";
    404 	  lastError += " at line " + toString(lex.getCurrentLine());
    405 	  lastError += ", col " + toString(lex.getCurrentColumn());
    406 	  return false;
    407 	}
    408       }
    409       break;
    410     case Value:
    411       if (type == Lexer::Text) {
    412 	if (valuetmp != "")
    413 	  valuetmp += " ";
    414 	valuetmp += token;
    415       } else if (type == Lexer::Comma || type == Lexer::RightCurly) {
    416 	*section = curSection;
    417 	*key = keytmp;
    418 	*value = valuetmp;
    419 	keytmp = "";
    420 	valuetmp = "";
    421 
    422 	if (type == Lexer::RightCurly)
    423 	  state = Searching;
    424 	else
    425 	  state = Key;
    426 
    427 	return true;
    428       }
    429     };
    430   }
    431 
    432   lastError = lex.getLastError();
    433   lastError += " at line " + toString(lex.getCurrentLine() + 1);
    434   lastError += ", col " + toString(lex.getCurrentColumn() + 1);
    435   return false;
    436 }
    437 
    438 namespace {
    439   //----------------------------------------------------------------------
    440   inline bool islabelchar(char c)
    441   {
    442     switch (c) {
    443     case 0x2D: case 0x2E:
    444     case 0x30: case 0x31: case 0x32: case 0x33: case 0x34:
    445     case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: 
    446     case 0x40: case 0x41: case 0x42: case 0x43: case 0x44:
    447     case 0x45: case 0x46: case 0x47: case 0x48: case 0x49:
    448     case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4E:
    449     case 0x4F: case 0x50: case 0x51: case 0x52: case 0x53:
    450     case 0x54: case 0x55: case 0x56: case 0x57: case 0x58:
    451     case 0x59: case 0x5A:
    452       //    case 0x5B:
    453       //    case 0x5C:
    454       //    case 0x5D:
    455     case 0x5E: case 0x5F:
    456       //    case 0x60:
    457     case 0x61: case 0x62: case 0x63: case 0x64: case 0x65:
    458     case 0x66: case 0x67: case 0x68: case 0x69: case 0x6A:
    459     case 0x6B: case 0x6C: case 0x6D: case 0x6E: case 0x6F:
    460     case 0x70: case 0x71: case 0x72: case 0x73: case 0x74:
    461     case 0x75: case 0x76: case 0x77: case 0x78: case 0x79:
    462     case 0x7A:
    463       return true;
    464     default: return false;
    465     }
    466   }
    467 
    468   //----------------------------------------------------------------------
    469   inline const string &encode(const string &s)
    470   {
    471     static string output;
    472     output = "";
    473 
    474     for (string::const_iterator i = s.begin(); i != s.end(); ++i)
    475       if ((unsigned char)*i < 32 || (unsigned char)*i > 127) {
    476 	output += "<";
    477 	output += toHex(s);
    478 	output += ">";
    479 	return output;
    480       }
    481 
    482     for (string::const_iterator i = s.begin(); i != s.end(); ++i)
    483       if (!islabelchar(*i)) {
    484 	output = "\"";
    485 	char c;
    486 	for (string::const_iterator i = s.begin(); i != s.end(); ++i)
    487 	  switch ((c = *i)) {
    488 	  case '\"': output += "\\\""; break;
    489 	  case '\\': output += "\\\\"; break;
    490 	  default: output += c; break;
    491 	  }
    492 	output += "\"";
    493 	return output;
    494       }
    495 
    496     return output = s;
    497   }
    498 
    499   //----------------------------------------------------------------------
    500   inline bool writeData(int fd, const string &out)
    501   {
    502     static string buffer;
    503 
    504     buffer += out;
    505     if (buffer.size() >= 4096 || out == "") {
    506       int written = 0;
    507       while (1) {
    508 	int retval = write(fd, buffer.c_str() + written,
    509 			   buffer.length() - written);
    510 	if (retval == (int) buffer.length())
    511 	  break;
    512 	
    513 	if (retval == -1) {
    514 	  if (errno == EINTR) continue;
    515 	  return false;
    516 	}
    517 	
    518 	written += retval;
    519       }
    520 
    521       buffer = "";
    522     }
    523 
    524     return true;
    525   }
    526 }
    527 
    528 //------------------------------------------------------------------------
    529 bool Storage::put(const string &section, const string &key,
    530 		  const string &value)
    531 {
    532   if (!fd)
    533     return false;
    534 
    535   if (mode == ReadOnly) {
    536     lastError = "unable to write to \"" + fileName
    537       + "\" when in ReadOnly mode";
    538     return false;
    539   }
    540 
    541   string data;
    542   if (curSection != section) {
    543     if (firstSection)
    544       firstSection = false;
    545     else
    546       data = "\n}\n";
    547 
    548     data += section + " {";
    549     curSection = section;
    550   } else
    551     data = ",";
    552 
    553   data += "\n\t";
    554   data += encode(key);
    555   data += " = ";
    556   data += encode(value);
    557 
    558   if (!writeData(fd, data)) {
    559     lastError = "when writing to \"" + fileName + "\": ";
    560     lastError += strerror(errno);
    561     return false;
    562   }  
    563   
    564   return true;
    565 }
    566 
    567 //------------------------------------------------------------------------
    568 bool Storage::commit(void)
    569 {
    570   if (!writeData(fd, "\n}\n")) {
    571     lastError = "when writing to \"" + fileName + "\":";
    572     lastError += strerror(errno);
    573     unlink(fileName.c_str());
    574   }
    575 
    576   if (!writeData(fd, "")) {
    577     lastError = "when writing to \"" + fileName + "\":";
    578     lastError += strerror(errno);
    579     unlink(fileName.c_str());
    580   }
    581 
    582   if (fsync(fd) != 0 && errno != EINVAL) {
    583     lastError = "when syncing \"" + fileName + "\":";
    584     lastError += strerror(errno);
    585     return false;
    586   }
    587 
    588   if (rename(fileName.c_str(), 
    589 	     fileName.substr(0, fileName.length() - 6).c_str()) != 0) {
    590     lastError = "when renaming \"" + fileName + "\" to \""
    591       + fileName.substr(0, fileName.length() - 6) + "\": ";
    592     lastError += strerror(errno);
    593     unlink(fileName.c_str());
    594     return false;
    595   }
    596 
    597   if (close(fd) != 0) {
    598     lastError = "when committing \"" + fileName + "\":";
    599     lastError += strerror(errno);
    600     return false;
    601   }
    602 
    603   fd = 0;
    604 
    605   string::size_type pos = fileName.rfind('/');
    606   string dirName = pos ? fileName.substr(0, pos) : ".";
    607   int dfd = open(dirName.c_str(), O_RDONLY);
    608   if (dfd == -1 || fsync(dfd) != 0 && errno != EINVAL || close(dfd) != 0) {
    609     lastError = "when syncing \"" + dirName + "\":";
    610     lastError += strerror(errno);
    611     return false;
    612   }
    613 
    614   return true;
    615 }