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 §ion, 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 }