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