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