src/Ottoman.cpp
author Jens Alfke <jens@mooseyard.com>
Tue Sep 29 15:46:42 2009 -0700 (2009-09-29)
changeset 9 629f61203db1
parent 8 21a6c17f4e3e
permissions -rw-r--r--
* Merged in jbm's changes for Linux compatibility, fixing Mac compatibility in the process :)
* Fixed two regressions having to do with the _previousTrailerPosition in VersionDictionary.cpp.
* Made sure RTTI is enabled in the OttomanTest target because gtest requires it.
     1 /*
     2  *  Ottoman.cpp
     3  *  Ottoman
     4  *
     5  *  Created by Jens Alfke on 8/31/09.
     6  *  Copyright 2009 Jens Alfke. All rights reserved.
     7  *  BSD-Licensed: See the file "LICENSE.txt" for details.
     8  */
     9 
    10 #include "Ottoman.h"
    11 #include "VersionDictionary.h"
    12 #include "Chunk.h"
    13 #include "Index.h"
    14 #include "File.h"
    15 #include <fcntl.h>
    16 #include <stdio.h>
    17 #include <unistd.h>
    18 
    19 namespace Mooseyard {
    20     
    21     class DictionaryFileHeader :public Chunk {
    22     public:
    23         DictionaryFileHeader()                      
    24             :Chunk(sizeof(DictionaryFileHeader),kChunkType) 
    25         {
    26             memcpy(&magicNumber, &kMagicNumber, sizeof(magicNumber));
    27         }
    28         
    29         bool valid() const {
    30             return type()==kChunkType && memcmp(&magicNumber,&kMagicNumber,sizeof(magicNumber))==0;
    31         }
    32         
    33         uint8_t magicNumber[8];
    34         
    35         static const uint16_t kChunkType = 4;
    36         static const uint8_t kMagicNumber[8];
    37     };
    38 
    39     const uint8_t DictionaryFileHeader::kMagicNumber[8] = {0x4A, 0x54, 0xF1, 0x1E, 'h', 'A', 's', 'H'};
    40 
    41     
    42     
    43     Ottoman::Ottoman()
    44     :_writeable(true),
    45      _file(NULL),
    46      _filename(NULL),
    47      _lastVersion(NULL),
    48      _current( new OverlayDictionary(&Dictionary::kEmpty) )
    49     {
    50     }
    51     
    52     Ottoman::Ottoman (const char *filename, bool writeable)
    53     :_writeable(writeable),
    54      _filename(strdup(filename)),
    55      _lastVersion(),
    56      _current()
    57     {
    58         _file = new File(_filename, writeable ?O_RDWR :O_RDONLY);
    59         _lastVersion = new VersionDictionary(_file);
    60         if (writeable)
    61             _current = new OverlayDictionary(_lastVersion);
    62     }
    63     
    64     Ottoman::~Ottoman() {
    65         delete _current;
    66         delete _lastVersion;
    67         delete _file;
    68         free(_filename);
    69     }
    70     
    71     
    72     bool Ottoman::needsSync() const {
    73         return _file && (!_lastVersion->isLatestVersion() || !_file->hasPath(_filename));
    74     }
    75     
    76     bool Ottoman::sync() {
    77         if (!needsSync())
    78             return false;
    79         File *curFile = _file;
    80         if (!curFile->hasPath(_filename))
    81             curFile = new File(_filename, _writeable ?O_RDWR :O_RDONLY);
    82         
    83         VersionDictionary *newest = new VersionDictionary(curFile);
    84         bool changed = (newest->generation() != _lastVersion->generation());
    85         if (changed) {
    86             if (newest->generation() < _lastVersion->generation())
    87                 throw File::Error("Versions got lost in file update");
    88             
    89             if (_current)
    90                 _current->rebase(newest);
    91             
    92             delete _lastVersion;
    93             _lastVersion = newest;
    94         }
    95         
    96         if (_file != curFile) {
    97             delete _file;
    98             _file = curFile;
    99         }
   100         
   101         if (changed)
   102             versionsChanged();
   103         return changed;
   104     }
   105     
   106     
   107     ChunkIterator* Ottoman::chunkIterator() const {
   108         return _file ?new ChunkIterator(_file, sizeof(DictionaryFileHeader)) :NULL;
   109     }
   110     
   111     bool Ottoman::scavenge (bool repair) {
   112         if (!_file)
   113             return true;
   114         
   115         const off_t actualEOF = _file->length();
   116         fprintf(stderr, "Ottoman::scavenge: Scanning %s (%llu / 0x%llX bytes) ...\n", 
   117                 _filename, actualEOF, actualEOF);
   118         _file->setPosition(0);
   119         ChunkIterator it(_file, 0);
   120         const DictionaryFileHeader *header = (const DictionaryFileHeader *) it.chunk();
   121         if (!header || !header->valid()) {
   122             fprintf(stderr, "Ottoman::scavenge: No valid file header at all!\n");
   123             return false;
   124         }
   125         
   126         // Scan through all the chunks:
   127         FilePosition lastValidTrailer = 0;
   128         FilePosition validEOF = 0;
   129         int lastType = -1;
   130         int count = 0;
   131         try{
   132             for (; it; ++it) {
   133                 const Chunk *chunk = it.chunk();
   134 
   135                 uint16_t type = it.chunk()->type();
   136                 if (type != lastType) {
   137                     if (count > 0)
   138                         printf("%6d\n", count);
   139                     fprintf(stderr, "Ottoman::scavenge:    at 0x%08X: type %u ... ", 
   140                             it.position(), type);
   141                     lastType = type;
   142                     count = 0;
   143                 }
   144                 count++;
   145                 
   146                 switch (type) {
   147                     case KeyValueChunk::kChunkType:
   148                         ((const KeyValueChunk*)chunk)->validate();
   149                         break;
   150                     case Index::kChunkType:
   151                         ((const Index*)chunk)->validateEntries(_file);
   152                         break;
   153                     case VersionDictionary::kChunkType: {
   154                         fprintf(stderr, "Found dictionary trailer at %u!\n", it.position());
   155                         count = 0;
   156                         // Validate by instantiating the VersionDictionary:
   157                         VersionDictionary tempDict(_file, it.position());
   158                         // If constructor succeeded, it's valid, so remember it:
   159                         lastValidTrailer = it.position();
   160                         validEOF = lastValidTrailer + chunk->size();
   161                         fprintf(stderr, "Ottoman::scavenge:    Valid dictionary trailer at %X--%X\n",
   162                                 lastValidTrailer, validEOF);
   163                         break;
   164                     }
   165                 }
   166             }
   167             if (count > 0)
   168                 fprintf(stderr, "%6d\n", count);
   169         } catch (File::Error &err) {
   170             fprintf(stderr, "Ottoman::scavenge caught File::Error(%i,\"%s\") at pos %llX\n",
   171                     err.code, err.message, _file->position());
   172             // Keep going; we can recover the dictionary(ies) before the bad one.
   173         }
   174         
   175         if (lastValidTrailer == 0) {
   176             fprintf(stderr, "Ottoman::scavenge: No valid dictionaries found!\n");
   177             return false;
   178         } else if (validEOF < actualEOF) {
   179             fprintf(stderr, "Ottoman::scavenge: Need to truncate to 0x%X (0x%llX bytes)\n",
   180                     lastValidTrailer, actualEOF-lastValidTrailer);
   181             if (repair) {
   182                 _file->setLength(validEOF);
   183                 _file->flushDisk();
   184             }
   185             return false;
   186         }
   187         
   188         fprintf(stderr, "Ottoman::scavenge: File is OK!\n");
   189         return true;
   190     }
   191     
   192     
   193 #pragma mark -
   194 #pragma mark SAVING:
   195     
   196     
   197     // low-level write. Does not lock or check for conflicts!
   198     void Ottoman::_append (File *dstFile) {
   199         VersionDictionary *lastVersion = _lastVersion;
   200         if (!lastVersion)
   201             lastVersion = new VersionDictionary(dstFile);
   202         VersionDictionary *saved = lastVersion->_appendAndOpen(_current->overlay(), 
   203                                                                dstFile,
   204                                                                _current->baseReplaced());
   205         // (don't delete _lastVersion: saved->_previousVersion now points to it.)
   206         _lastVersion = saved;
   207         _current->revertTo(_lastVersion);
   208         versionsChanged();
   209     }
   210     
   211     bool Ottoman::save() {
   212         if (!_file)
   213             return false;
   214         if (_current && _current->isChanged()) {
   215             File::Lock lock(_file);
   216             if (needsSync())
   217                 return false;       // conflict!
   218             _append(_file);
   219         }
   220         return true;
   221     }
   222     
   223     File* Ottoman::_writeTo (const char *dstFileName, bool overwriteAllowed) {
   224         int mode = O_RDWR | O_CREAT | O_TRUNC;
   225         if (!overwriteAllowed)
   226             mode |= O_EXCL;
   227         File *dstFile = new File(dstFileName, mode, true);
   228         try {
   229             dstFile->write(DictionaryFileHeader());
   230             _append(dstFile);
   231         } catch (...) {
   232             delete dstFile;
   233             File::unlink(dstFileName);
   234             throw;
   235         }
   236         return dstFile;
   237     }
   238     
   239     void Ottoman::saveAs (const char *dstFileName, bool overwriteAllowed) {
   240         File *dstFile = _writeTo(dstFileName, overwriteAllowed);
   241         free(_filename);
   242         _filename = strdup(dstFileName);
   243         delete _file;
   244         _file = dstFile;
   245     }
   246 
   247     bool Ottoman::saveAndCompact() {
   248         if (!_file)
   249             return false;
   250         char tempFileName[1024];
   251 #ifdef _DARWIN_C_SOURCE
   252         ::strlcpy(tempFileName, _filename, sizeof(tempFileName)-1);
   253         ::strlcat(tempFileName, "~", sizeof(tempFileName));
   254 #else
   255         ::strncpy(tempFileName, _filename, sizeof(tempFileName)-1);
   256         ::strncat(tempFileName, "~", sizeof(tempFileName));
   257 #endif
   258         File *tempFile;
   259         {
   260             // Check for conflict in existing file:
   261             File::Lock lock(_file);
   262             if (needsSync())
   263                 return false;
   264             tempFile = _writeTo(tempFileName, false);
   265             File::rename(tempFileName, _filename);
   266         }
   267         delete _file;
   268         _file = tempFile;
   269         return true;
   270     }
   271 
   272 }