| /* -*- 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_; |
| }; |
| |