src/Ottoman.cpp
author Jens Alfke <jens@mooseyard.com>
Sun Sep 20 15:14:12 2009 -0700 (2009-09-20)
changeset 0 31a43d94cc26
child 4 715d6147ba3a
permissions -rw-r--r--
First official checkin.
     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      _filename(NULL),
    46      _lastVersion(NULL),
    47      _current( new OverlayDictionary(&Dictionary::kEmpty) )
    48     {
    49     }
    50     
    51     Ottoman::Ottoman (const char *filename, bool writeable)
    52     :_writeable(writeable),
    53      _filename(strdup(filename)),
    54      _lastVersion(),
    55      _current()
    56     {
    57         _file = new File(_filename, writeable ?O_RDWR :O_RDONLY);
    58         _lastVersion = new VersionDictionary(_file);
    59         if (writeable)
    60             _current = new OverlayDictionary(_lastVersion);
    61     }
    62     
    63     Ottoman::~Ottoman() {
    64         delete _current;
    65         delete _lastVersion;
    66         delete _file;
    67         free(_filename);
    68     }
    69     
    70     
    71     bool Ottoman::needsSync() const {
    72         return _file && (!_lastVersion->isLatestVersion() || !_file->hasPath(_filename));
    73     }
    74     
    75     bool Ottoman::sync() {
    76         if (!needsSync())
    77             return false;
    78         File *curFile = _file;
    79         if (!curFile->hasPath(_filename))
    80             curFile = new File(_filename, _writeable ?O_RDWR :O_RDONLY);
    81         
    82         VersionDictionary *newest = new VersionDictionary(curFile);
    83         bool changed = (newest->generation() != _lastVersion->generation());
    84         if (changed) {
    85             if (newest->generation() < _lastVersion->generation())
    86                 throw File::Error("Versions got lost in file update");
    87             
    88             if (_current)
    89                 _current->rebase(newest);
    90             
    91             delete _lastVersion;
    92             _lastVersion = newest;
    93         }
    94         
    95         if (_file != curFile) {
    96             delete _file;
    97             _file = curFile;
    98         }
    99         return changed;
   100     }
   101     
   102     
   103     ChunkIterator* Ottoman::chunkIterator() const {
   104         return _file ?new ChunkIterator(_file, sizeof(DictionaryFileHeader)) :NULL;
   105     }
   106     
   107     bool Ottoman::scavenge (bool repair) {
   108         if (!_file)
   109             return true;
   110         
   111         const off_t actualEOF = _file->length();
   112         fprintf(stderr, "Ottoman::scavenge: Scanning %s (%llu / 0x%llX bytes) ...\n", 
   113                 _filename, actualEOF, actualEOF);
   114         _file->setPosition(0);
   115         ChunkIterator it(_file, 0);
   116         const DictionaryFileHeader *header = (const DictionaryFileHeader *) it.chunk();
   117         if (!header || !header->valid()) {
   118             fprintf(stderr, "Ottoman::scavenge: No valid file header at all!\n");
   119             return false;
   120         }
   121         
   122         // Scan through all the chunks:
   123         FilePosition lastValidTrailer = 0;
   124         FilePosition validEOF = 0;
   125         int lastType = -1;
   126         int count = 0;
   127         try{
   128             for (; it; ++it) {
   129                 const Chunk *chunk = it.chunk();
   130 
   131                 uint16_t type = it.chunk()->type();
   132                 if (type != lastType) {
   133                     if (count > 0)
   134                         printf("%6d\n", count);
   135                     fprintf(stderr, "Ottoman::scavenge:    at 0x%08X: type %u ... ", 
   136                             it.position(), type);
   137                     lastType = type;
   138                     count = 0;
   139                 }
   140                 count++;
   141                 
   142                 switch (type) {
   143                     case KeyValueChunk::kChunkType:
   144                         ((const KeyValueChunk*)chunk)->validate();
   145                         break;
   146                     case Index::kChunkType:
   147                         ((const Index*)chunk)->validateEntries(_file);
   148                         break;
   149                     case VersionDictionary::kChunkType: {
   150                         fprintf(stderr, "Found dictionary trailer at %u!\n", it.position());
   151                         count = 0;
   152                         // Validate by instantiating the VersionDictionary:
   153                         VersionDictionary tempDict(_file, it.position());
   154                         // If constructor succeeded, it's valid, so remember it:
   155                         lastValidTrailer = it.position();
   156                         validEOF = lastValidTrailer + chunk->size();
   157                         fprintf(stderr, "Ottoman::scavenge:    Valid dictionary trailer at %X--%X\n",
   158                                 lastValidTrailer, validEOF);
   159                         break;
   160                     }
   161                 }
   162             }
   163             if (count > 0)
   164                 fprintf(stderr, "%6d\n", count);
   165         } catch (File::Error &err) {
   166             fprintf(stderr, "Ottoman::scavenge caught File::Error(%i,\"%s\") at pos %llX\n",
   167                     err.code, err.message, _file->position());
   168             // Keep going; we can recover the dictionary(ies) before the bad one.
   169         }
   170         
   171         if (lastValidTrailer == 0) {
   172             fprintf(stderr, "Ottoman::scavenge: No valid dictionaries found!\n");
   173             return false;
   174         } else if (validEOF < actualEOF) {
   175             fprintf(stderr, "Ottoman::scavenge: Need to truncate to 0x%X (0x%llX bytes)\n",
   176                     lastValidTrailer, actualEOF-lastValidTrailer);
   177             if (repair) {
   178                 _file->setLength(validEOF);
   179                 _file->flushDisk();
   180             }
   181             return false;
   182         }
   183         
   184         fprintf(stderr, "Ottoman::scavenge: File is OK!\n");
   185         return true;
   186     }
   187     
   188     
   189 #pragma mark -
   190 #pragma mark SAVING:
   191     
   192     
   193     // low-level write. Does not lock or check for conflicts!
   194     void Ottoman::_append (File *dstFile) {
   195         VersionDictionary *lastVersion = _lastVersion;
   196         if (!lastVersion)
   197             lastVersion = new VersionDictionary(dstFile);
   198         VersionDictionary *saved = lastVersion->_appendAndOpen(_current->overlay(), 
   199                                                             dstFile,
   200                                                             _current->baseReplaced());
   201         // (don't delete _lastVersion: saved->_previousVersion now points to it.)
   202         _lastVersion = saved;
   203         _current->revertTo(_lastVersion);
   204     }
   205     
   206     bool Ottoman::save() {
   207         if (!_file)
   208             return false;
   209         if (_current && _current->isChanged()) {
   210             File::Lock lock(_file);
   211             if (needsSync())
   212                 return false;       // conflict!
   213             _append(_file);
   214         }
   215         return true;
   216     }
   217     
   218     File* Ottoman::_writeTo (const char *dstFileName, bool overwriteAllowed) {
   219         int mode = O_RDWR | O_CREAT | O_TRUNC | O_EXLOCK;
   220         if (!overwriteAllowed)
   221             mode |= O_EXCL;
   222         File *dstFile = new File(dstFileName, mode);
   223         try {
   224             dstFile->write(DictionaryFileHeader());
   225             _append(dstFile);
   226         } catch (...) {
   227             delete dstFile;
   228             File::unlink(dstFileName);
   229             throw;
   230         }
   231         return dstFile;
   232     }
   233     
   234     void Ottoman::saveAs (const char *dstFileName, bool overwriteAllowed) {
   235         File *dstFile = _writeTo(dstFileName, overwriteAllowed);
   236         free(_filename);
   237         _filename = strdup(dstFileName);
   238         _file = dstFile;
   239     }
   240 
   241     bool Ottoman::saveAndCompact() {
   242         if (!_file)
   243             return false;
   244         char tempFileName[1024];
   245         strlcpy(tempFileName, _filename, sizeof(tempFileName)-1);
   246         strlcat(tempFileName, "~", sizeof(tempFileName));
   247         File *tempFile;
   248         {
   249             // Check for conflict in existing file:
   250             File::Lock lock(_file);
   251             if (needsSync())
   252                 return false;
   253             tempFile = _writeTo(tempFileName, false);
   254             File::rename(tempFileName, _filename);
   255         }
   256         delete _file;
   257         _file = tempFile;
   258         return true;
   259     }
   260 
   261 }