diff options
Diffstat (limited to 'nxcomp/src/Split.cpp')
-rw-r--r-- | nxcomp/src/Split.cpp | 1839 |
1 files changed, 1839 insertions, 0 deletions
diff --git a/nxcomp/src/Split.cpp b/nxcomp/src/Split.cpp new file mode 100644 index 000000000..e2fea97cc --- /dev/null +++ b/nxcomp/src/Split.cpp @@ -0,0 +1,1839 @@ +/**************************************************************************/ +/* */ +/* Copyright (c) 2001, 2011 NoMachine (http://www.nomachine.com) */ +/* Copyright (c) 2008-2014 Oleksandr Shneyder <o.shneyder@phoca-gmbh.de> */ +/* Copyright (c) 2014-2016 Ulrich Sibiller <uli42@gmx.de> */ +/* Copyright (c) 2014-2016 Mihai Moldovan <ionic@ionic.de> */ +/* Copyright (c) 2011-2016 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>*/ +/* Copyright (c) 2015-2016 Qindel Group (http://www.qindel.com) */ +/* */ +/* NXCOMP, NX protocol compression and NX extensions to this software */ +/* are copyright of the aforementioned persons and companies. */ +/* */ +/* Redistribution and use of the present software is allowed according */ +/* to terms specified in the file LICENSE.nxcomp which comes in the */ +/* source distribution. */ +/* */ +/* All rights reserved. */ +/* */ +/* NOTE: This software has received contributions from various other */ +/* contributors, only the core maintainers and supporters are listed as */ +/* copyright holders. Please contact us, if you feel you should be listed */ +/* as copyright holder, as well. */ +/* */ +/**************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <unistd.h> +#include <cstring> +#include <sys/stat.h> +#include <sys/types.h> +#include <utime.h> + +#include "Misc.h" + +#include "Split.h" + +#include "Control.h" +#include "Statistics.h" + +#include "EncodeBuffer.h" +#include "DecodeBuffer.h" + +#include "StaticCompressor.h" + +#include "Unpack.h" + +// +// Set the verbosity level. +// + +#define PANIC +#define WARNING +#undef TEST +#undef DEBUG +#undef DUMP + +// +// Define this to trace elements +// allocated and deallocated. +// + +#undef REFERENCES + +// +// Counters used for store control. +// + +int SplitStore::totalSplitSize_; +int SplitStore::totalSplitStorageSize_; + +// +// This is used for reference count. +// + +#ifdef REFERENCES + +int Split::references_ = 0; + +#endif + +Split::Split() +{ + resource_ = nothing; + position_ = nothing; + + store_ = NULL; + + d_size_ = 0; + i_size_ = 0; + c_size_ = 0; + r_size_ = 0; + + next_ = 0; + load_ = 0; + save_ = 0; + + checksum_ = NULL; + state_ = split_undefined; + mode_ = split_none; + action_ = is_discarded; + + #ifdef REFERENCES + + references_++; + + *logofs << "Split: Created new Split at " + << this << " out of " << references_ + << " allocated references.\n" << logofs_flush; + #endif +} + +Split::~Split() +{ + delete [] checksum_; + + #ifdef REFERENCES + + references_--; + + *logofs << "Split: Deleted Split at " + << this << " out of " << references_ + << " allocated references.\n" << logofs_flush; + #endif +} + +SplitStore::SplitStore(StaticCompressor *compressor, CommitStore *commits, int resource) + + : compressor_(compressor), commits_(commits), resource_(resource) +{ + splits_ = new T_splits(); + + current_ = splits_ -> end(); + + splitStorageSize_ = 0; + + #ifdef TEST + *logofs << "SplitStore: Created new store ["; + + if (resource_ != nothing) + { + *logofs << resource_; + } + else + { + *logofs << "commit"; + } + + *logofs << "].\n" << logofs_flush; + + *logofs << "SplitStore: Total messages in stores are " + << totalSplitSize_ << " with total storage size " + << totalSplitStorageSize_ << ".\n" + << logofs_flush; + #endif +} + +SplitStore::~SplitStore() +{ + totalSplitSize_ -= splits_ -> size(); + + totalSplitStorageSize_ -= splitStorageSize_; + + for (T_splits::iterator i = splits_ -> begin(); + i != splits_ -> end(); i++) + { + delete *i; + } + + delete splits_; + + #ifdef TEST + *logofs << "SplitStore: Deleted store ["; + + if (resource_ != nothing) + { + *logofs << resource_; + } + else + { + *logofs << "commit"; + } + + *logofs << "] with storage size " << splitStorageSize_ + << ".\n" << logofs_flush; + + *logofs << "SplitStore: Total messages in stores are " + << totalSplitSize_ << " with total storage size " + << totalSplitStorageSize_ << ".\n" + << logofs_flush; + #endif +} + +// +// This is called at the encoding side. +// + +Split *SplitStore::add(MessageStore *store, int resource, T_split_mode mode, + int position, T_store_action action, T_checksum checksum, + const unsigned char *buffer, const int size) +{ + #ifdef TEST + *logofs << "SplitStore: Adding message [" << (unsigned int) store -> + opcode() << "] resource " << resource << " mode " << mode + << " position " << position << " action [" << DumpAction(action) + << "] and checksum [" << DumpChecksum(checksum) << "]" + << ".\n" << logofs_flush; + #endif + + Split *split = new Split(); + + if (split == NULL) + { + #ifdef PANIC + *logofs << "SplitStore: PANIC! Can't allocate " + << "memory for the split.\n" + << logofs_flush; + #endif + + cerr << "Error" << ": Can't allocate memory " + << "for the split.\n"; + + HandleAbort(); + } + + split -> store_ = store; + split -> resource_ = resource; + split -> mode_ = mode; + split -> position_ = position; + split -> action_ = action; + + split -> store_ -> validateSize(size); + + // + // The checksum is not provided if the + // message is cached. + // + + if (checksum != NULL) + { + split -> checksum_ = new md5_byte_t[MD5_LENGTH]; + + memcpy(split -> checksum_, checksum, MD5_LENGTH); + } + + // + // We don't need the identity data at the + // encoding side. This qualifies the split + // as a split generated at the encoding + // side. + // + + split -> i_size_ = store -> identitySize(buffer, size); + + split -> d_size_ = size - split -> i_size_; + + if (action == IS_ADDED || action == is_discarded) + { + // + // If the message was added to message + // store or discarded we need to save + // the real data so we can transfer it + // at later time. + // + + split -> data_.resize(split -> d_size_); + + memcpy(split -> data_.begin(), buffer + split -> i_size_, split -> d_size_); + + // + // If the message was added, lock it so + // it will not be used by the encoding + // side until it is recomposed. + // + + if (action == IS_ADDED) + { + split -> store_ -> lock(split -> position_); + + #ifdef TEST + + commits_ -> validate(split); + + #endif + } + } + #ifdef WARNING + else + { + *logofs << "SplitStore: WARNING! Not copying data for the cached message.\n" + << logofs_flush; + } + #endif + + push(split); + + return split; +} + +// +// This is called at decoding side. If checksum +// is provided, the message can be searched on +// disk, then, if message is found, an event is +// sent to abort the data transfer. +// + +Split *SplitStore::add(MessageStore *store, int resource, int position, + T_store_action action, T_checksum checksum, + unsigned char *buffer, const int size) +{ + #ifdef TEST + *logofs << "SplitStore: Adding message [" + << (unsigned int) store -> opcode() << "] resource " + << resource << " position " << position << " action [" + << DumpAction(action) << "] and checksum [" + << DumpChecksum(checksum) << "].\n" << logofs_flush; + #endif + + Split *split = new Split(); + + if (split == NULL) + { + #ifdef PANIC + *logofs << "SplitStore: PANIC! Can't allocate " + << "memory for the split.\n" + << logofs_flush; + #endif + + cerr << "Error" << ": Can't allocate memory " + << "for the split.\n"; + + HandleAbort(); + } + + split -> store_ = store; + split -> resource_ = resource; + split -> position_ = position; + split -> action_ = action; + + split -> store_ -> validateSize(size); + + // + // Check if the checksum was provided + // by the remote. + // + + if (checksum != NULL) + { + split -> checksum_ = new md5_byte_t[MD5_LENGTH]; + + memcpy(split -> checksum_, checksum, MD5_LENGTH); + } + + split -> i_size_ = store -> identitySize(buffer, size); + + // + // Copy the identity so we can expand the + // message when it is committed. + // + + split -> identity_.resize(split -> i_size_); + + memcpy(split -> identity_.begin(), buffer, split -> i_size_); + + split -> d_size_ = size - split -> i_size_; + + if (action == IS_ADDED || action == is_discarded) + { + // + // The unpack procedure will check if the + // first 2 bytes of the buffer contain the + // pattern and will not try to expand the + // image. + // + + split -> data_.resize(2); + + unsigned char *data = split -> data_.begin(); + + data[0] = SPLIT_PATTERN; + data[1] = SPLIT_PATTERN; + + // + // If the message was added to the store, + // we don't have the data part, yet, so + // we need to lock the message until it + // is recomposed. + // + + if (action == IS_ADDED) + { + split -> store_ -> lock(split -> position_); + + #ifdef TEST + + commits_ -> validate(split); + + #endif + } + } + else + { + #ifdef WARNING + *logofs << "SplitStore: WARNING! Copying data for the cached message.\n" + << logofs_flush; + #endif + + // + // We may optionally take the data from the + // message store in compressed form, but, + // as the data has been decompressed in the + // buffer, we save a further decompression. + // + + split -> data_.resize(split -> d_size_); + + memcpy(split -> data_.begin(), buffer + split -> i_size_, split -> d_size_); + } + + push(split); + + return split; +} + +void SplitStore::push(Split *split) +{ + splits_ -> push_back(split); + + splitStorageSize_ += getNodeSize(split); + + totalSplitSize_++; + + totalSplitStorageSize_ += getNodeSize(split); + + statistics -> addSplit(); + + #ifdef TEST + *logofs << "SplitStore: There are " << splits_ -> size() + << " messages in store [" << resource_ << "] with " + << "storage size " << splitStorageSize_ << ".\n" + << logofs_flush; + + *logofs << "SplitStore: Total messages in stores are " + << totalSplitSize_ << " with total storage size " + << totalSplitStorageSize_ << ".\n" + << logofs_flush; + #endif + + split -> state_ = split_added; +} + +void SplitStore::dump() +{ + #ifdef DUMP + + int n; + + Split *split; + + *logofs << "SplitStore: DUMP! Dumping content of "; + + if (commits_ == NULL) + { + *logofs << "[commits]"; + } + else + { + *logofs << "[splits] for store [" << resource_ << "]"; + } + + *logofs << " with [" << getSize() << "] elements " + << "in the store.\n" << logofs_flush; + + n = 0; + + for (T_splits::iterator i = splits_ -> begin(); i != splits_ -> end(); i++, n++) + { + split = *i; + + *logofs << "SplitStore: DUMP! Split [" << n << "] has action [" + << DumpAction(split -> action_) << "] state [" + << DumpState(split -> state_) << "] "; + + if (split -> resource_ >= 0) + { + *logofs << "resource " << split -> resource_; + } + + *logofs << " request " << (unsigned) split -> store_ -> opcode() + << " position " << split -> position_ << " size is " + << split -> data_.size() << " (" << split -> d_size_ + << "/" << split -> c_size_ << "/" << split -> r_size_ + << ") with " << split -> data_.size() - split -> next_ + << "] bytes to go.\n" << logofs_flush; + } + + #endif +} + +int SplitStore::send(EncodeBuffer &encodeBuffer, int packetSize) +{ + if (splits_ -> size() == 0) + { + #ifdef PANIC + *logofs << "SplitStore: PANIC! Function send called with no splits available.\n" + << logofs_flush; + #endif + + cerr << "Error" << ": Function send called with no splits available.\n"; + + HandleAbort(); + } + + // + // A start operation must always be executed on + // the split, even in the case the split will be + // later aborted. + // + + if (current_ == splits_ -> end()) + { + start(encodeBuffer); + } + + // + // If we have matched the checksum received from + // the remote side then we must abort the current + // split, else we can send another block of data + // to the remote peer. + // + + Split *split = *current_; + + unsigned int abort = 0; + + if (split -> state_ == split_loaded) + { + abort = 1; + } + + encodeBuffer.encodeBoolValue(abort); + + if (abort == 1) + { + #ifdef TEST + *logofs << "SplitStore: Aborting split for checksum [" + << DumpChecksum(split -> checksum_) << "] position " + << split -> position_ << " with " << (split -> + data_.size() - split -> next_) << " bytes to go " + << "out of " << split -> data_.size() + << ".\n" << logofs_flush; + #endif + + statistics -> addSplitAborted(); + + statistics -> addSplitAbortedBytesOut(split -> data_.size() - split -> next_); + + split -> next_ = split -> data_.size(); + + split -> state_ = split_aborted; + } + else + { + int count = (packetSize <= 0 || split -> next_ + + packetSize > (int) split -> data_.size() ? + split -> data_.size() - split -> next_ : packetSize); + + #ifdef TEST + *logofs << "SplitStore: Sending split for checksum [" + << DumpChecksum(split -> checksum_) << "] count " + << count << " position " << split -> position_ + << ". Data size is " << split -> data_.size() << " (" + << split -> d_size_ << "/" << split -> c_size_ << "), " + << split -> data_.size() - (split -> next_ + count) + << " to go.\n" << logofs_flush; + #endif + + encodeBuffer.encodeValue(count, 32, 10); + + encodeBuffer.encodeMemory(split -> data_.begin() + split -> next_, count); + + split -> next_ += count; + } + + // + // Was data completely transferred? We are the + // sending side. We must update the message in + // store, even if split was aborted. + // + + if (split -> next_ != ((int) split -> data_.size())) + { + return 0; + } + + // + // Move the split at the head of the + // list to the commits. + // + + remove(split); + + // + // Reset current position to the + // end of repository. + // + + current_ = splits_ -> end(); + + #ifdef TEST + *logofs << "SplitStore: Removed split at head of the list. " + << "Resource is " << split -> resource_ << " request " + << (unsigned) split -> store_ -> opcode() << " position " + << split -> position_ << ".\n" << logofs_flush; + #endif + + return 1; +} + +int SplitStore::start(EncodeBuffer &encodeBuffer) +{ + // + // Get the element at the top of the + // list. + // + + current_ = splits_ -> begin(); + + Split *split = *current_; + + #ifdef TEST + *logofs << "SplitStore: Starting split for checksum [" + << DumpChecksum(split -> checksum_) << "] position " + << split -> position_ << " with " << (split -> + data_.size() - split -> next_) << " bytes to go " + << "out of " << split -> data_.size() + << ".\n" << logofs_flush; + #endif + + // + // See if compression of the data part is + // enabled. + // + + if (split -> store_ -> enableCompress) + { + // + // If the split is going to be aborted don't + // compress the data and go straight to the + // send. The new data size will be assumed + // from the disk cache. + // + + if (split -> state_ != split_loaded) + { + unsigned int compressedSize = 0; + unsigned char *compressedData = NULL; + + if (control -> LocalDataCompression && + (compressor_ -> compressBuffer(split -> data_.begin(), split -> d_size_, + compressedData, compressedSize))) + { + // + // Replace the data with the one in + // compressed form. + // + + #ifdef TEST + *logofs << "SplitStore: Split data of size " << split -> d_size_ + << " has been compressed to " << compressedSize + << " bytes.\n" << logofs_flush; + #endif + + split -> data_.clear(); + + split -> data_.resize(compressedSize); + + memcpy(split -> data_.begin(), compressedData, compressedSize); + + split -> c_size_ = compressedSize; + + // + // Inform our peer that the data is + // compressed and send the new size. + // + + encodeBuffer.encodeBoolValue(1); + + encodeBuffer.encodeValue(compressedSize, 32, 14); + + #ifdef TEST + *logofs << "SplitStore: Signaled " << split -> c_size_ + << " bytes of compressed data for this message.\n" + << logofs_flush; + #endif + + return 1; + } + } + #ifdef TEST + else + { + *logofs << "SplitStore: Not trying to compress the " + << "loaded message.\n" << logofs_flush; + } + #endif + + // + // Tell to the remote that data will + // follow uncompressed. + // + + encodeBuffer.encodeBoolValue(0); + } + + return 1; +} + +int SplitStore::start(DecodeBuffer &decodeBuffer) +{ + #ifdef TEST + *logofs << "SplitStore: Going to receive a new split from the remote side.\n" + << logofs_flush; + #endif + + // + // Get the element at the head + // of the list. + // + + current_ = splits_ -> begin(); + + Split *split = *current_; + + unsigned int compressedSize = 0; + + // + // Save the data size known by the remote. + // This information will be needed if the + // remote will not have a chance to abort + // the split. + // + + split -> r_size_ = split -> d_size_; + + // + // Find out if data was compressed by the + // remote. + // + + if (split -> store_ -> enableCompress) + { + decodeBuffer.decodeBoolValue(compressedSize); + + if (compressedSize == 1) + { + // + // Get the compressed size. + // + + // Since ProtoStep7 (#issue 108) + decodeBuffer.decodeValue(compressedSize, 32, 14); + + split -> store_ -> validateSize(split -> d_size_, compressedSize); + + split -> r_size_ = compressedSize; + } + } + + // + // Update the size if the split + // was not already loaded. + // + + if (split -> state_ != split_loaded) + { + split -> data_.clear(); + + if (compressedSize > 0) + { + split -> c_size_ = compressedSize; + + #ifdef TEST + *logofs << "SplitStore: Split data of size " + << split -> d_size_ << " was compressed to " + << split -> c_size_ << " bytes.\n" + << logofs_flush; + #endif + + split -> data_.resize(split -> c_size_); + } + else + { + split -> data_.resize(split -> d_size_); + } + + unsigned char *data = split -> data_.begin(); + + data[0] = SPLIT_PATTERN; + data[1] = SPLIT_PATTERN; + } + #ifdef TEST + else + { + // + // The message had been already + // loaded from disk. + // + + if (compressedSize > 0) + { + if ((int) compressedSize != split -> c_size_) + { + *logofs << "SplitStore: WARNING! Compressed data size is " + << "different than the loaded compressed size.\n" + << logofs_flush; + } + + *logofs << "SplitStore: Ignoring the new size with " + << "loaded compressed size " << split -> c_size_ + << ".\n" << logofs_flush; + } + } + #endif + + return 1; +} + +int SplitStore::receive(DecodeBuffer &decodeBuffer) +{ + if (splits_ -> size() == 0) + { + #ifdef PANIC + *logofs << "SplitStore: PANIC! Function receive called with no splits available.\n" + << logofs_flush; + #endif + + cerr << "Error" << ": Function receive called with no splits available.\n"; + + HandleAbort(); + } + + if (current_ == splits_ -> end()) + { + start(decodeBuffer); + } + + // + // Check first if split was aborted, else add + // any new data to message being recomposed. + // + + Split *split = *current_; + + unsigned int abort = 0; + + decodeBuffer.decodeBoolValue(abort); + + if (abort == 1) + { + #ifdef TEST + *logofs << "SplitStore: Aborting split for checksum [" + << DumpChecksum(split -> checksum_) << "] position " + << split -> position_ << " with " << (split -> + data_.size() - split -> next_) << " bytes to go " + << "out of " << split -> data_.size() + << ".\n" << logofs_flush; + #endif + + statistics -> addSplitAborted(); + + statistics -> addSplitAbortedBytesOut(split -> r_size_ - split -> next_); + + split -> next_ = split -> r_size_; + + split -> state_ = split_aborted; + } + else + { + // + // Get the size of the packet. + // + + unsigned int count; + + decodeBuffer.decodeValue(count, 32, 10); + + // + // If the split was not already loaded from + // disk, decode the packet and update our + // copy of the data. The encoding side may + // have not received the abort event, yet, + // and may be unaware that the message is + // stored in compressed form at our side. + // + + #ifdef TEST + *logofs << "SplitStore: Receiving split for checksum [" + << DumpChecksum(split -> checksum_) << "] count " + << count << " position " << split -> position_ + << ". Data size is " << split -> data_.size() << " (" + << split -> d_size_ << "/" << split -> c_size_ << "/" + << split -> r_size_ << "), " << split -> r_size_ - + (split -> next_ + count) << " to go.\n" + << logofs_flush; + #endif + + if (split -> next_ + count > (unsigned) split -> r_size_) + { + #ifdef PANIC + *logofs << "SplitStore: PANIC! Invalid data count " + << count << "provided in the split.\n" + << logofs_flush; + + *logofs << "SplitStore: PANIC! While receiving split for " + << "checksum [" << DumpChecksum(split -> checksum_) + << "] with count " << count << " action [" + << DumpAction(split -> action_) << "] state [" + << DumpState(split -> state_) << "]. Data size is " + << split -> data_.size() << " (" << split -> d_size_ + << "/" << split -> c_size_ << "), " << split -> + data_.size() - (split -> next_ + count) + << " to go.\n" << logofs_flush; + #endif + + cerr << "Error" << ": Invalid data count " + << count << "provided in the split.\n"; + + HandleAbort(); + } + + if (split -> state_ != split_loaded) + { + #ifdef TEST + + if (split -> next_ + count > split -> data_.size()) + { + #ifdef PANIC + *logofs << "SplitStore: PANIC! Inconsistent split data size " + << split -> data_.size() << " with expected size " + << split -> r_size_ << ".\n" + << logofs_flush; + #endif + + HandleAbort(); + } + + #endif + + memcpy(split -> data_.begin() + split -> next_, + decodeBuffer.decodeMemory(count), count); + } + else + { + #ifdef TEST + *logofs << "SplitStore: WARNING! Data discarded with split " + << "loaded from disk.\n" << logofs_flush; + #endif + + decodeBuffer.decodeMemory(count); + } + + split -> next_ += count; + } + + // + // Is unsplit complete? + // + + if (split -> next_ != split -> r_size_) + { + return 0; + } + + // + // If the persistent cache is enabled, + // we have a valid checksum and the + // split was not originally retrieved + // from disk, save the message on disk. + // + + if (split -> state_ != split_loaded && + split -> state_ != split_aborted) + { + save(split); + } + + // + // Move the split at the head of the + // list to the commits. + // + + remove(split); + + // + // Reset the current position to the + // end of the repository. + // + + current_ = splits_ -> end(); + + #ifdef TEST + *logofs << "SplitStore: Removed split at head of the list. " + << "Resource is " << split -> resource_ << " request " + << (unsigned) split -> store_ -> opcode() << " position " + << split -> position_ << ".\n" << logofs_flush; + #endif + + return 1; +} + +Split *SplitStore::pop() +{ + if (splits_ -> size() == 0) + { + #ifdef TEST + *logofs << "SplitStore: The split store is empty.\n" + << logofs_flush; + #endif + + return NULL; + } + + // + // Move the pointer at the end of the list. + // The next send operation will eventually + // start a new split. + // + + current_ = splits_ -> end(); + + Split *split = *(splits_ -> begin()); + + splits_ -> pop_front(); + + #ifdef TEST + *logofs << "SplitStore: Removed split at the head of the " + << "list with resource " << split -> resource_ + << " request " << (unsigned) split -> store_ -> + opcode() << " position " << split -> position_ + << ".\n" << logofs_flush; + #endif + + splitStorageSize_ -= getNodeSize(split); + + totalSplitSize_--; + + totalSplitStorageSize_ -= getNodeSize(split); + + #ifdef TEST + *logofs << "SplitStore: There are " << splits_ -> size() + << " messages in store [" << resource_ << "] with " + << "storage size " << splitStorageSize_ << ".\n" + << logofs_flush; + + *logofs << "SplitStore: Total messages in stores are " + << totalSplitSize_ << " with total storage size " + << totalSplitStorageSize_ << ".\n" + << logofs_flush; + #endif + + return split; +} + +void SplitStore::remove(Split *split) +{ + #ifdef TEST + *logofs << "SplitStore: Going to remove the split from the list.\n" + << logofs_flush; + #endif + + #ifdef TEST + + if (split != getFirstSplit()) + { + #ifdef PANIC + *logofs << "SplitStore: PANIC! Trying to remove a split " + << "not at the head of the list.\n" + << logofs_flush; + #endif + + cerr << "Error" << ": Trying to remove a split " + << "not at the head of the list.\n"; + + HandleAbort(); + } + + #endif + + // + // Move the split to the commit store. + // + + splits_ -> pop_front(); + + commits_ -> splits_ -> push_back(split); + + splitStorageSize_ -= getNodeSize(split); + + totalSplitSize_--; + + totalSplitStorageSize_ -= getNodeSize(split); + + #ifdef TEST + *logofs << "SplitStore: There are " << splits_ -> size() + << " messages in store [" << resource_ << "] with " + << "storage size " << splitStorageSize_ << ".\n" + << logofs_flush; + + *logofs << "SplitStore: Total messages in stores are " + << totalSplitSize_ << " with total storage size " + << totalSplitStorageSize_ << ".\n" + << logofs_flush; + #endif + + #ifdef TEST + + if (splits_ -> size() == 0) + { + if (splitStorageSize_ != 0) + { + #ifdef PANIC + *logofs << "SplitStore: PANIC! Internal error calculating " + << "split data size. It is " << splitStorageSize_ + << " while should be 0.\n" << logofs_flush; + #endif + + cerr << "Error" << ": Internal error calculating " + << "split data size. It is " << splitStorageSize_ + << " while should be 0.\n"; + + HandleAbort(); + } + } + + #endif +} + +const char *SplitStore::name(const T_checksum checksum) +{ + if (checksum == NULL) + { + return NULL; + } + + char *pathName = control -> ImageCachePath; + + if (pathName == NULL) + { + #ifdef PANIC + *logofs << "SplitStore: PANIC! Cannot determine directory of " + << "NX image files.\n" << logofs_flush; + #endif + + return NULL; + } + + int pathSize = strlen(pathName); + + // + // File name is "[path][/I-c/I-][checksum][\0]", + // where c is the first hex digit of checksum. + // + + int nameSize = pathSize + 7 + MD5_LENGTH * 2 + 1; + + char *fileName = new char[nameSize]; + + if (fileName == NULL) + { + #ifdef PANIC + *logofs << "SplitStore: PANIC! Cannot allocate space for " + << "NX image file name.\n" << logofs_flush; + #endif + + return NULL; + } + + strcpy(fileName, pathName); + + sprintf(fileName + pathSize, "/I-%1X/I-", + *((unsigned char *) checksum) >> 4); + + for (unsigned int i = 0; i < MD5_LENGTH; i++) + { + sprintf(fileName + pathSize + 7 + (i * 2), "%02X", + ((unsigned char *) checksum)[i]); + } + + return fileName; +} + +int SplitStore::save(Split *split) +{ + // + // Check if saving the message on the + // persistent cache is enabled. + // + + if (split -> save_ == 0) + { + return 0; + } + + T_checksum checksum = split -> checksum_; + + const char *fileName = name(checksum); + + if (fileName == NULL) + { + return 0; + } + + unsigned int splitSize; + + ostream *fileStream = NULL; + + unsigned char *fileHeader = NULL; + + // + // Get the other data from the split. + // + + unsigned char opcode = split -> store_ -> opcode(); + + unsigned char *data = split -> data_.begin(); + + int dataSize = split -> d_size_; + int compressedSize = split -> c_size_; + + #ifdef DEBUG + *logofs << "SplitStore: Going to save split OPCODE#" + << (unsigned int) opcode << " to file '" << fileName + << "' with size " << dataSize << " and compressed size " + << compressedSize << ".\n" << logofs_flush; + #endif + + DisableSignals(); + + // + // Change the mask to make the file only + // readable by the user, then restore the + // old mask. + // + + mode_t fileMode; + + // + // Check if the file already exists. We try to + // load the message when the split is started + // and save it only if it is not found. Still + // the remote side may send the same image mul- + // tiple time and we may not have the time to + // notify the abort. + // + + struct stat fileStat; + + if (stat(fileName, &fileStat) == 0) + { + #ifdef TEST + *logofs << "SplitStore: Image file '" << fileName + << "' already present on disk.\n" + << logofs_flush; + #endif + + goto SplitStoreSaveError; + } + + fileMode = umask(0077); + + fileStream = new ofstream(fileName, ios::out | ios::binary); + + umask(fileMode); + + if (CheckData(fileStream) < 0) + { + #ifdef PANIC + *logofs << "SplitStore: PANIC! Cannot open file '" << fileName + << "' for output.\n" << logofs_flush; + #endif + + goto SplitStoreSaveError; + } + + fileHeader = new unsigned char[SPLIT_HEADER_SIZE]; + + if (fileHeader == NULL) + { + #ifdef PANIC + *logofs << "SplitStore: PANIC! Cannot allocate space for " + << "NX image header.\n" << logofs_flush; + #endif + + goto SplitStoreSaveError; + } + + // + // Leave 3 bytes for future use. Please note + // that, on some CPUs, we can't use PutULONG() + // to write integers that are not aligned to + // the word boundary. + // + + *fileHeader = opcode; + + *(fileHeader + 1) = 0; + *(fileHeader + 2) = 0; + *(fileHeader + 3) = 0; + + PutULONG(dataSize, fileHeader + 4, false); + PutULONG(compressedSize, fileHeader + 8, false); + + splitSize = (compressedSize > 0 ? compressedSize : dataSize); + + if (PutData(fileStream, fileHeader, SPLIT_HEADER_SIZE) < 0 || + PutData(fileStream, data, splitSize) < 0) + { + #ifdef PANIC + *logofs << "SplitStore: PANIC! Cannot write to NX " + << "image file '" << fileName << "'.\n" + << logofs_flush; + #endif + + goto SplitStoreSaveError; + } + + // + // Check if all the data was written on the + // disk and, if not, remove the faulty copy. + // + + FlushData(fileStream); + + if (CheckData(fileStream) < 0) + { + #ifdef PANIC + *logofs << "SplitStore: PANIC! Failed to write NX " + << "image file '" << fileName << "'.\n" + << logofs_flush; + #endif + + cerr << "Warning" << ": Failed to write NX " + << "image file '" << fileName << "'.\n"; + + goto SplitStoreSaveError; + } + + #ifdef TEST + *logofs << "SplitStore: Saved split to file '" << fileName + << "' with data size " << dataSize << " and " + << "compressed data size " << compressedSize + << ".\n" << logofs_flush; + #endif + + delete fileStream; + + delete [] fileName; + delete [] fileHeader; + + EnableSignals(); + + // + // Update the timestamp as the operation + // may have taken some time. + // + + getNewTimestamp(); + + return 1; + +SplitStoreSaveError: + + delete fileStream; + + if (fileName != NULL) + { + unlink(fileName); + } + + delete [] fileName; + delete [] fileHeader; + + EnableSignals(); + + return -1; +} + +int SplitStore::find(Split *split) +{ + const char *fileName = name(split -> checksum_); + + if (fileName == NULL) + { + return 0; + } + + #ifdef DEBUG + *logofs << "SplitStore: Going to find split OPCODE#" + << (unsigned) split -> store_ -> opcode() + << " in file '" << fileName << "'.\n" + << logofs_flush; + #endif + + // + // Check if the file exists and, at the + // same time, update the modification + // time to prevent its deletion. + // + + if (utime(fileName, NULL) == 0) + { + #ifdef TEST + *logofs << "SplitStore: Found split OPCODE#" + << (unsigned) split -> store_ -> opcode() + << " in file '" << fileName << "'.\n" + << logofs_flush; + #endif + + delete [] fileName; + + return 1; + } + + #ifdef TEST + *logofs << "SplitStore: WARNING! Can't find split " + << "OPCODE#" << (unsigned) split -> store_ -> + opcode() << " in file '" << fileName + << "'.\n" << logofs_flush; + #endif + + delete [] fileName; + + return 0; +} + +int SplitStore::load(Split *split) +{ + // + // Check if loading the image is enabled. + // + + if (split -> load_ == 0) + { + return 0; + } + + const char *fileName = name(split -> checksum_); + + if (fileName == NULL) + { + return 0; + } + + unsigned char fileOpcode; + + int fileSize; + int fileCSize; + + istream *fileStream = NULL; + + unsigned char *fileHeader = NULL; + + DisableSignals(); + + #ifdef DEBUG + *logofs << "SplitStore: Going to load split OPCODE#" + << (unsigned int) split -> store_ -> opcode() + << " from file '" << fileName << "'.\n" + << logofs_flush; + #endif + + fileStream = new ifstream(fileName, ios::in | ios::binary); + + if (CheckData(fileStream) < 0) + { + #ifdef TEST + *logofs << "SplitStore: WARNING! Can't open image file '" + << fileName << "' on disk.\n" << logofs_flush; + #endif + + goto SplitStoreLoadError; + } + + fileHeader = new unsigned char[SPLIT_HEADER_SIZE]; + + if (fileHeader == NULL) + { + #ifdef PANIC + *logofs << "SplitStore: PANIC! Cannot allocate space for " + << "NX image header.\n" << logofs_flush; + #endif + + cerr << "Error" << ": Cannot allocate space for " + << "NX image header.\n"; + + goto SplitStoreLoadError; + } + + if (GetData(fileStream, fileHeader, SPLIT_HEADER_SIZE) < 0) + { + #ifdef PANIC + *logofs << "SplitStore: PANIC! Cannot read header from " + << "NX image file '" << fileName << "'.\n" + << logofs_flush; + #endif + + cerr << "Warning" << ": Cannot read header from " + << "NX image file '" << fileName << "'.\n"; + + goto SplitStoreLoadError; + } + + fileOpcode = *fileHeader; + + fileSize = GetULONG(fileHeader + 4, false); + fileCSize = GetULONG(fileHeader + 8, false); + + // + // Don't complain if we find that data was saved + // in compressed form even if we were not aware + // of the compressed data size. The remote side + // compresses the data only at the time it starts + // the transferral of the split. We replace our + // copy of the data with whatever we find on the + // disk. + // + + if (fileOpcode != split -> store_ -> opcode() || + fileSize != split -> d_size_ || + fileSize > control -> MaximumRequestSize || + fileCSize > control -> MaximumRequestSize) + + { + #ifdef TEST + *logofs << "SplitStore: PANIC! Corrupted image file '" << fileName + << "'. Expected " << (unsigned int) split -> store_ -> opcode() + << "/" << split -> d_size_ << "/" << split -> c_size_ << " found " + << (unsigned int) fileOpcode << "/" << fileSize << "/" + << fileCSize << ".\n" << logofs_flush; + #endif + + cerr << "Warning" << ": Corrupted image file '" << fileName + << "'. Expected " << (unsigned int) split -> store_ -> opcode() + << "/" << split -> d_size_ << "/" << split -> c_size_ << " found " + << (unsigned int) fileOpcode << "/" << fileSize << "/" + << fileCSize << ".\n"; + + goto SplitStoreLoadError; + } + + // + // Update the data size with the size + // we got from the disk record. + // + + split -> d_size_ = fileSize; + split -> c_size_ = fileCSize; + + unsigned int splitSize; + + if (fileCSize > 0) + { + splitSize = fileCSize; + } + else + { + splitSize = fileSize; + } + + // + // Allocate a new buffer if we didn't + // do that already or if the size is + // different. + // + + if (split -> data_.size() != splitSize) + { + split -> data_.clear(); + + split -> data_.resize(splitSize); + } + + if (GetData(fileStream, split -> data_.begin(), splitSize) < 0) + { + #ifdef PANIC + *logofs << "SplitStore: PANIC! Cannot read data from " + << "NX image file '" << fileName << "'.\n" + << logofs_flush; + #endif + + cerr << "Warning" << ": Cannot read data from " + << "NX image file '" << fileName << "'.\n"; + + goto SplitStoreLoadError; + } + + delete fileStream; + + delete [] fileHeader; + delete [] fileName; + + EnableSignals(); + + // + // Update the timestamp as the operation + // may have taken some time. + // + + getNewTimestamp(); + + return 1; + +SplitStoreLoadError: + + delete fileStream; + + unlink(fileName); + + delete [] fileName; + delete [] fileHeader; + + EnableSignals(); + + return -1; +} + +Split *CommitStore::pop() +{ + if (splits_ -> size() == 0) + { + #ifdef TEST + *logofs << "CommitStore: The commit store is empty.\n" + << logofs_flush; + #endif + + return NULL; + } + + Split *split = *(splits_ -> begin()); + + splits_ -> pop_front(); + + #ifdef TEST + *logofs << "CommitStore: Removed commit split at the head " + << "of the list with resource " << split -> resource_ + << " request " << (unsigned) split -> store_ -> + opcode() << " position " << split -> position_ + << ".\n" << logofs_flush; + #endif + + return split; +} + +int CommitStore::expand(Split *split, unsigned char *buffer, const int size) +{ + #ifdef TEST + *logofs << "CommitStore: Expanding split data with " + << size << " bytes to write.\n" + << logofs_flush; + #endif + + #ifdef TEST + + if (size < split -> i_size_ + split -> d_size_) + { + #ifdef PANIC + *logofs << "CommitStore: PANIC! Wrong size of the provided " + << "buffer. It should be " << split -> i_size_ + + split -> d_size_ << " instead of " << size + << ".\n" << logofs_flush; + #endif + + cerr << "Error" << ": Wrong size of the provided " + << "buffer. It should be " << split -> i_size_ + + split -> d_size_ << " instead of " << size + << ".\n"; + + HandleAbort(); + } + + #endif + + #ifdef DEBUG + *logofs << "CommitStore: Copying " << split -> i_size_ + << " bytes of identity.\n" << logofs_flush; + #endif + + memcpy(buffer, split -> identity_.begin(), split -> i_size_); + + // + // Copy data, if any, to the buffer. + // + + if (size > split -> i_size_) + { + // + // Check if message has been stored + // in compressed format. + // + + if (split -> c_size_ == 0) + { + #ifdef DEBUG + *logofs << "CommitStore: Copying " << split -> d_size_ + << " bytes of plain data.\n" << logofs_flush; + #endif + + memcpy(buffer + split -> i_size_, split -> data_.begin(), split -> d_size_); + } + else + { + #ifdef DEBUG + *logofs << "CommitStore: Decompressing " << split -> c_size_ + << " bytes and copying " << split -> d_size_ + << " bytes of data.\n" << logofs_flush; + #endif + + if (compressor_ -> + decompressBuffer(buffer + split -> i_size_, + split -> d_size_, split -> data_.begin(), + split -> c_size_) < 0) + { + #ifdef PANIC + *logofs << "CommitStore: PANIC! Split data decompression failed.\n" + << logofs_flush; + #endif + + cerr << "Error" << ": Split data decompression failed.\n"; + + return -1; + } + } + } + + return 1; +} + +int CommitStore::update(Split *split) +{ + if (split -> action_ != IS_ADDED) + { + return 0; + } + + // + // We don't need the identity data at + // the encoding side. + // + + if (split -> identity_.size() == 0) + { + #ifdef TEST + *logofs << "SplitStore: Going to update the size " + << "for object at position " << split -> position_ + << " with data size " << split -> d_size_ + << " and compressed data size " << split -> + c_size_ << ".\n" << logofs_flush; + #endif + + split -> store_ -> updateData(split -> position_, split -> d_size_, + split -> c_size_); + } + else + { + #ifdef TEST + *logofs << "SplitStore: Going to update data and size " + << "for object at position " << split -> position_ + << " with data size " << split -> d_size_ + << " and compressed data size " << split -> + c_size_ << ".\n" << logofs_flush; + #endif + + split -> store_ -> updateData(split -> position_, split -> data_.begin(), + split -> d_size_, split -> c_size_); + } + + // + // Unlock message so that we can remove + // or save it on disk at shutdown. + // + + if (split -> action_ == IS_ADDED) + { + split -> store_ -> unlock(split -> position_); + + #ifdef TEST + + validate(split); + + #endif + } + + return 1; +} + +int CommitStore::validate(Split *split) +{ + MessageStore *store = split -> store_; + + int p, n, s; + + s = store -> cacheSlots; + + for (p = 0, n = 0; p < s; p++) + { + if (store -> getLocks(p) == 1) + { + n++; + } + else if (store -> getLocks(p) != 0) + { + #ifdef PANIC + *logofs << "CommitStore: PANIC! Repository for OPCODE#" + << (unsigned int) store -> opcode() << " has " + << store -> getLocks(p) << " locks for message " + << "at position " << p << ".\n" << logofs_flush; + #endif + + cerr << "Error" << ": Repository for OPCODE#" + << (unsigned int) store -> opcode() << " has " + << store -> getLocks(p) << " locks for message " + << "at position " << p << ".\n"; + + HandleAbort(); + } + } + + #ifdef TEST + *logofs << "CommitStore: Repository for OPCODE#" + << (unsigned int) store -> opcode() + << " has " << n << " locked messages.\n" + << logofs_flush; + #endif + + return 1; +} |