/* -*- Mode: C++ -*- */
class Block;
class BlockIterator;
class TmpFile;
class Block {
public:
Block()
: data_(NULL),
data_size_(0),
size_(0) { }
~Block() {
if (data_) {
delete [] data_;
}
}
size_t Size() const {
return size_;
}
uint8_t operator[](size_t i) const {
CHECK_LT(i, size_);
return data_[i];
}
uint8_t* Data() const {
if (data_ == NULL) {
CHECK_EQ(0, size_);
data_size_ = 1;
data_ = new uint8_t[1];
}
return data_;
}
// For writing to blocks
void Append(const uint8_t *data, size_t size) {
if (data_ == NULL) {
CHECK_EQ(0, size_);
CHECK_EQ(0, data_size_);
data_ = new uint8_t[Constants::BLOCK_SIZE];
data_size_ = Constants::BLOCK_SIZE;
}
if (size_ + size > data_size_) {
uint8_t *tmp = data_;
while (size_ + size > data_size_) {
data_size_ *= 2;
}
data_ = new uint8_t[data_size_];
memcpy(data_, tmp, size_);
delete tmp;
}
memcpy(data_ + size_, data, size);
size_ += size;
}
// For cleaing a block
void Reset() {
size_ = 0;
}
void Print() const {
xoff_t pos = 0;
for (size_t i = 0; i < Size(); i++) {
if (pos % 16 == 0) {
DP(RINT "%5"Q"x: ", pos);
}
DP(RINT "%02x ", (*this)[i]);
if (pos % 16 == 15) {
DP(RINT "\n");
}
pos++;
}
DP(RINT "\n");
}
void WriteTmpFile(TmpFile *f) const {
f->Append(this);
}
void SetSize(size_t size) {
size_ = size;
if (data_size_ < size) {
if (data_) {
delete [] data_;
}
data_ = new uint8_t[size];
data_size_ = size;
}
}
private:
friend class BlockIterator;
mutable uint8_t *data_;
mutable size_t data_size_;
size_t size_;
};
class FileSpec {
public:
FileSpec(MTRandom *rand)
: rand_(rand) {
}
// Generates a file with a known size
void GenerateFixedSize(xoff_t size) {
Reset();
for (xoff_t p = 0; p < size; ) {
xoff_t t = min(Constants::BLOCK_SIZE, size - p);
table_.insert(make_pair(p, Segment(t, rand_)));
p += t;
}
}
// Generates a file with exponential-random distributed size
void GenerateRandomSize(xoff_t mean) {
GenerateFixedSize(rand_->ExpRand(mean));
}
// Returns the size of the file
xoff_t Size() const {
if (table_.empty()) {
return 0;
}
ConstSegmentMapIterator i = --table_.end();
return i->first + i->second.Size();
}
// Returns the number of blocks
xoff_t Blocks(size_t blksize = Constants::BLOCK_SIZE) const {
if (table_.empty()) {
return 0;
}
return ((Size() - 1) / blksize) + 1;
}
// Returns the number of segments
xoff_t Segments() const {
return table_.size();
}
// Create a mutation according to "what".
void ModifyTo(const Mutator &mutator,
FileSpec *modify) const {
modify->Reset();
mutator.Mutate(&modify->table_, &table_, rand_);
modify->CheckSegments();
}
void CheckSegments() const {
for (ConstSegmentMapIterator iter(table_.begin());
iter != table_.end(); ) {
ConstSegmentMapIterator iter0(iter++);
if (iter == table_.end()) {
break;
}
CHECK_EQ(iter0->first + iter0->second.Size(), iter->first);
}
}
void Reset() {
table_.clear();
}
void Print() const {
for (ConstSegmentMapIterator iter(table_.begin());
iter != table_.end();
++iter) {
const Segment &seg = iter->second;
cerr << "Segment at " << iter->first
<< " (" << seg.ToString() << ")" << endl;
}
}
void PrintData() const {
Block block;
for (BlockIterator iter(*this); !iter.Done(); iter.Next()) {
iter.Get(&block);
block.Print();
}
}
void WriteTmpFile(TmpFile *f) const {
Block block;
for (BlockIterator iter(*this); !iter.Done(); iter.Next()) {
iter.Get(&block);
f->Append(&block);
}
}
void Get(Block *block, xoff_t offset, size_t size) const {
size_t got = 0;
block->SetSize(size);
ConstSegmentMapIterator pos = table_.upper_bound(offset);
if (pos == table_.begin()) {
CHECK_EQ(0, Size());
return;
}
--pos;
while (got < size) {
CHECK(pos != table_.end());
CHECK_GE(offset, pos->first);
const Segment &seg = pos->second;
// The position of this segment may start before this block starts,
// and then the position of the data may be offset from the seeding
// position.
size_t seg_offset = offset - pos->first;
size_t advance = min(seg.Size() - seg_offset,
size - got);
seg.Fill(seg_offset, advance, block->Data() + got);
got += advance;
offset += advance;
++pos;
}
}
typedef BlockIterator iterator;
private:
friend class BlockIterator;
MTRandom *rand_;
SegmentMap table_;
};
class BlockIterator {
public:
explicit BlockIterator(const FileSpec& spec)
: spec_(spec),
blkno_(0),
blksize_(Constants::BLOCK_SIZE) { }
BlockIterator(const FileSpec& spec,
size_t blksize)
: spec_(spec),
blkno_(0),
blksize_(blksize) { }
bool Done() const {
return blkno_ >= spec_.Blocks(blksize_);
}
void Next() {
blkno_++;
}
xoff_t Blkno() const {
return blkno_;
}
xoff_t Blocks() const {
return spec_.Blocks(blksize_);
}
xoff_t Offset() const {
return blkno_ * blksize_;
}
void SetBlock(xoff_t blkno) {
blkno_ = blkno;
}
void Get(Block *block) const {
spec_.Get(block, blkno_ * blksize_, BytesOnBlock());
}
size_t BytesOnBlock() const {
xoff_t blocks = spec_.Blocks(blksize_);
xoff_t size = spec_.Size();
DCHECK((blkno_ < blocks) ||
(blkno_ == blocks && size % blksize_ == 0));
if (blkno_ == blocks) {
return 0;
}
if (blkno_ + 1 == blocks) {
return ((size - 1) % blksize_) + 1;
}
return blksize_;
}
size_t BlockSize() const {
return blksize_;
}
private:
const FileSpec& spec_;
xoff_t blkno_;
size_t blksize_;
};
class ExtFile {
public:
ExtFile() {
static int static_counter = 0;
char buf[32];
snprintf(buf, 32, "/tmp/regtest.%d", static_counter++);
filename_.append(buf);
unlink(filename_.c_str());
}
~ExtFile() {
unlink(filename_.c_str());
}
const char* Name() const {
return filename_.c_str();
}
// Check whether a real file matches a file spec.
bool EqualsSpec(const FileSpec &spec) const {
main_file t;
main_file_init(&t);
CHECK_EQ(0, main_file_open(&t, Name(), XO_READ));
Block tblock;
Block sblock;
for (BlockIterator iter(spec); !iter.Done(); iter.Next()) {
iter.Get(&sblock);
tblock.SetSize(sblock.Size());
size_t tread;
CHECK_EQ(0, main_file_read(&t,
tblock.Data(),
tblock.Size(), &tread, "read failed"));
CHECK_EQ(0, CmpDifferentBlockBytes(tblock, sblock));
}
CHECK_EQ(0, main_file_close(&t));
main_file_cleanup(&t);
return true;
}
protected:
string filename_;
};
class TmpFile : public ExtFile {
public:
TmpFile() {
main_file_init(&file_);
CHECK_EQ(0, main_file_open(&file_, Name(), XO_WRITE));
}
~TmpFile() {
main_file_cleanup(&file_);
}
void Append(const Block *block) {
CHECK_EQ(0, main_file_write(&file_,
block->Data(), block->Size(),
"tmpfile write failed"));
}
const char* Name() const {
if (main_file_isopen(&file_)) {
CHECK_EQ(0, main_file_close(&file_));
}
return ExtFile::Name();
}
private:
mutable main_file file_;
};