/* -*- 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) { uint8_t *t = NULL; if (data_size_ < size) { if (data_) { t = data_; } data_ = new uint8_t[size]; data_size_ = size; } if (t && size < size_) { memcpy(data_, t, size); } delete [] t; 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) { CHECK_LE(blkno, Blocks()); 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; pid_t pid = getpid(); char buf[64]; snprintf(buf, 64, "/tmp/regtest.%d.%d", pid, 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_; };