jens@0: /* jens@0: * Ottoman.cpp jens@0: * Ottoman jens@0: * jens@0: * Created by Jens Alfke on 8/31/09. jens@0: * Copyright 2009 Jens Alfke. All rights reserved. jens@0: * BSD-Licensed: See the file "LICENSE.txt" for details. jens@0: */ jens@0: jens@0: #include "Ottoman.h" jens@0: #include "VersionDictionary.h" jens@0: #include "Chunk.h" jens@0: #include "Index.h" jens@0: #include "File.h" jens@0: #include jens@0: #include jens@0: #include jens@0: jens@0: namespace Mooseyard { jens@0: jens@0: class DictionaryFileHeader :public Chunk { jens@0: public: jens@0: DictionaryFileHeader() jens@0: :Chunk(sizeof(DictionaryFileHeader),kChunkType) jens@0: { jens@0: memcpy(&magicNumber, &kMagicNumber, sizeof(magicNumber)); jens@0: } jens@0: jens@0: bool valid() const { jens@0: return type()==kChunkType && memcmp(&magicNumber,&kMagicNumber,sizeof(magicNumber))==0; jens@0: } jens@0: jens@0: uint8_t magicNumber[8]; jens@0: jens@0: static const uint16_t kChunkType = 4; jens@0: static const uint8_t kMagicNumber[8]; jens@0: }; jens@0: jens@0: const uint8_t DictionaryFileHeader::kMagicNumber[8] = {0x4A, 0x54, 0xF1, 0x1E, 'h', 'A', 's', 'H'}; jens@0: jens@0: jens@0: jens@0: Ottoman::Ottoman() jens@0: :_writeable(true), jens@0: _filename(NULL), jens@0: _lastVersion(NULL), jens@0: _current( new OverlayDictionary(&Dictionary::kEmpty) ) jens@0: { jens@0: } jens@0: jens@0: Ottoman::Ottoman (const char *filename, bool writeable) jens@0: :_writeable(writeable), jens@0: _filename(strdup(filename)), jens@0: _lastVersion(), jens@0: _current() jens@0: { jens@0: _file = new File(_filename, writeable ?O_RDWR :O_RDONLY); jens@0: _lastVersion = new VersionDictionary(_file); jens@0: if (writeable) jens@0: _current = new OverlayDictionary(_lastVersion); jens@0: } jens@0: jens@0: Ottoman::~Ottoman() { jens@0: delete _current; jens@0: delete _lastVersion; jens@0: delete _file; jens@0: free(_filename); jens@0: } jens@0: jens@0: jens@0: bool Ottoman::needsSync() const { jens@0: return _file && (!_lastVersion->isLatestVersion() || !_file->hasPath(_filename)); jens@0: } jens@0: jens@0: bool Ottoman::sync() { jens@0: if (!needsSync()) jens@0: return false; jens@0: File *curFile = _file; jens@0: if (!curFile->hasPath(_filename)) jens@0: curFile = new File(_filename, _writeable ?O_RDWR :O_RDONLY); jens@0: jens@0: VersionDictionary *newest = new VersionDictionary(curFile); jens@0: bool changed = (newest->generation() != _lastVersion->generation()); jens@0: if (changed) { jens@0: if (newest->generation() < _lastVersion->generation()) jens@0: throw File::Error("Versions got lost in file update"); jens@0: jens@0: if (_current) jens@0: _current->rebase(newest); jens@0: jens@0: delete _lastVersion; jens@0: _lastVersion = newest; jens@0: } jens@0: jens@0: if (_file != curFile) { jens@0: delete _file; jens@0: _file = curFile; jens@0: } jens@0: return changed; jens@0: } jens@0: jens@0: jens@0: ChunkIterator* Ottoman::chunkIterator() const { jens@0: return _file ?new ChunkIterator(_file, sizeof(DictionaryFileHeader)) :NULL; jens@0: } jens@0: jens@0: bool Ottoman::scavenge (bool repair) { jens@0: if (!_file) jens@0: return true; jens@0: jens@0: const off_t actualEOF = _file->length(); jens@0: fprintf(stderr, "Ottoman::scavenge: Scanning %s (%llu / 0x%llX bytes) ...\n", jens@0: _filename, actualEOF, actualEOF); jens@0: _file->setPosition(0); jens@0: ChunkIterator it(_file, 0); jens@0: const DictionaryFileHeader *header = (const DictionaryFileHeader *) it.chunk(); jens@0: if (!header || !header->valid()) { jens@0: fprintf(stderr, "Ottoman::scavenge: No valid file header at all!\n"); jens@0: return false; jens@0: } jens@0: jens@0: // Scan through all the chunks: jens@0: FilePosition lastValidTrailer = 0; jens@0: FilePosition validEOF = 0; jens@0: int lastType = -1; jens@0: int count = 0; jens@0: try{ jens@0: for (; it; ++it) { jens@0: const Chunk *chunk = it.chunk(); jens@0: jens@0: uint16_t type = it.chunk()->type(); jens@0: if (type != lastType) { jens@0: if (count > 0) jens@0: printf("%6d\n", count); jens@0: fprintf(stderr, "Ottoman::scavenge: at 0x%08X: type %u ... ", jens@0: it.position(), type); jens@0: lastType = type; jens@0: count = 0; jens@0: } jens@0: count++; jens@0: jens@0: switch (type) { jens@0: case KeyValueChunk::kChunkType: jens@0: ((const KeyValueChunk*)chunk)->validate(); jens@0: break; jens@0: case Index::kChunkType: jens@0: ((const Index*)chunk)->validateEntries(_file); jens@0: break; jens@0: case VersionDictionary::kChunkType: { jens@0: fprintf(stderr, "Found dictionary trailer at %u!\n", it.position()); jens@0: count = 0; jens@0: // Validate by instantiating the VersionDictionary: jens@0: VersionDictionary tempDict(_file, it.position()); jens@0: // If constructor succeeded, it's valid, so remember it: jens@0: lastValidTrailer = it.position(); jens@0: validEOF = lastValidTrailer + chunk->size(); jens@0: fprintf(stderr, "Ottoman::scavenge: Valid dictionary trailer at %X--%X\n", jens@0: lastValidTrailer, validEOF); jens@0: break; jens@0: } jens@0: } jens@0: } jens@0: if (count > 0) jens@0: fprintf(stderr, "%6d\n", count); jens@0: } catch (File::Error &err) { jens@0: fprintf(stderr, "Ottoman::scavenge caught File::Error(%i,\"%s\") at pos %llX\n", jens@0: err.code, err.message, _file->position()); jens@0: // Keep going; we can recover the dictionary(ies) before the bad one. jens@0: } jens@0: jens@0: if (lastValidTrailer == 0) { jens@0: fprintf(stderr, "Ottoman::scavenge: No valid dictionaries found!\n"); jens@0: return false; jens@0: } else if (validEOF < actualEOF) { jens@0: fprintf(stderr, "Ottoman::scavenge: Need to truncate to 0x%X (0x%llX bytes)\n", jens@0: lastValidTrailer, actualEOF-lastValidTrailer); jens@0: if (repair) { jens@0: _file->setLength(validEOF); jens@0: _file->flushDisk(); jens@0: } jens@0: return false; jens@0: } jens@0: jens@0: fprintf(stderr, "Ottoman::scavenge: File is OK!\n"); jens@0: return true; jens@0: } jens@0: jens@0: jens@0: #pragma mark - jens@0: #pragma mark SAVING: jens@0: jens@0: jens@0: // low-level write. Does not lock or check for conflicts! jens@0: void Ottoman::_append (File *dstFile) { jens@0: VersionDictionary *lastVersion = _lastVersion; jens@0: if (!lastVersion) jens@0: lastVersion = new VersionDictionary(dstFile); jens@0: VersionDictionary *saved = lastVersion->_appendAndOpen(_current->overlay(), jens@0: dstFile, jens@0: _current->baseReplaced()); jens@0: // (don't delete _lastVersion: saved->_previousVersion now points to it.) jens@0: _lastVersion = saved; jens@0: _current->revertTo(_lastVersion); jens@0: } jens@0: jens@0: bool Ottoman::save() { jens@0: if (!_file) jens@0: return false; jens@0: if (_current && _current->isChanged()) { jens@0: File::Lock lock(_file); jens@0: if (needsSync()) jens@0: return false; // conflict! jens@0: _append(_file); jens@0: } jens@0: return true; jens@0: } jens@0: jens@0: File* Ottoman::_writeTo (const char *dstFileName, bool overwriteAllowed) { jens@0: int mode = O_RDWR | O_CREAT | O_TRUNC | O_EXLOCK; jens@0: if (!overwriteAllowed) jens@0: mode |= O_EXCL; jens@0: File *dstFile = new File(dstFileName, mode); jens@0: try { jens@0: dstFile->write(DictionaryFileHeader()); jens@0: _append(dstFile); jens@0: } catch (...) { jens@0: delete dstFile; jens@0: File::unlink(dstFileName); jens@0: throw; jens@0: } jens@0: return dstFile; jens@0: } jens@0: jens@0: void Ottoman::saveAs (const char *dstFileName, bool overwriteAllowed) { jens@0: File *dstFile = _writeTo(dstFileName, overwriteAllowed); jens@0: free(_filename); jens@0: _filename = strdup(dstFileName); jens@0: _file = dstFile; jens@0: } jens@0: jens@0: bool Ottoman::saveAndCompact() { jens@0: if (!_file) jens@0: return false; jens@0: char tempFileName[1024]; jens@0: strlcpy(tempFileName, _filename, sizeof(tempFileName)-1); jens@0: strlcat(tempFileName, "~", sizeof(tempFileName)); jens@0: File *tempFile; jens@0: { jens@0: // Check for conflict in existing file: jens@0: File::Lock lock(_file); jens@0: if (needsSync()) jens@0: return false; jens@0: tempFile = _writeTo(tempFileName, false); jens@0: File::rename(tempFileName, _filename); jens@0: } jens@0: delete _file; jens@0: _file = tempFile; jens@0: return true; jens@0: } jens@0: jens@0: }