1// DataContainer.cpp
2
3#include "AllocationInfo.h"
4#include "Attribute.h"	// for debugging only
5#include "Block.h"
6#include "DataContainer.h"
7#include "DebugSupport.h"
8#include "Misc.h"
9#include "Node.h"		// for debugging only
10#include "Volume.h"
11
12// constructor
13DataContainer::DataContainer(Volume *volume)
14	: fVolume(volume),
15	  fSize(0)
16{
17}
18
19// destructor
20DataContainer::~DataContainer()
21{
22	Resize(0);
23}
24
25// InitCheck
26status_t
27DataContainer::InitCheck() const
28{
29	return (fVolume ? B_OK : B_ERROR);
30}
31
32// Resize
33status_t
34DataContainer::Resize(off_t newSize)
35{
36	status_t error = B_OK;
37	if (newSize < 0)
38		newSize = 0;
39	if (newSize != fSize) {
40		// Shrinking should never fail. Growing can fail, if we run out of
41		// memory. Then we try to shrink back to the original size.
42		off_t oldSize = fSize;
43		error = _Resize(newSize);
44		if (error == B_NO_MEMORY && newSize > fSize)
45			_Resize(oldSize);
46	}
47	return error;
48}
49
50// ReadAt
51status_t
52DataContainer::ReadAt(off_t offset, void *_buffer, size_t size,
53					  size_t *bytesRead)
54{
55	uint8 *buffer = (uint8*)_buffer;
56	status_t error = (buffer && offset >= 0 && bytesRead ? B_OK : B_BAD_VALUE);
57	if (error == B_OK) {
58		// read not more than we have to offer
59		offset = min(offset, fSize);
60		size = min(size, size_t(fSize - offset));
61		// iterate through the blocks, reading as long as there's something
62		// left to read
63		size_t blockSize = fVolume->GetBlockSize();
64		*bytesRead = 0;
65		while (size > 0) {
66			size_t inBlockOffset = offset % blockSize;
67			size_t toRead = min(size, size_t(blockSize - inBlockOffset));
68			void *blockData = _GetBlockDataAt(offset / blockSize,
69											  inBlockOffset, toRead);
70D(
71if (!blockData) {
72	Node *node = NULL;
73	if (Attribute *attribute = dynamic_cast<Attribute*>(this)) {
74		FATAL(("attribute `%s' of\n", attribute->GetName()));
75		node = attribute->GetNode();
76	} else {
77		node = dynamic_cast<Node*>(this);
78	}
79	if (node)
80//		FATAL(("node `%s'\n", node->GetName()));
81		FATAL(("container size: %Ld, offset: %Ld, buffer size: %lu\n",
82		   fSize, offset, size));
83		return B_ERROR;
84}
85);
86			memcpy(buffer, blockData, toRead);
87			buffer += toRead;
88			size -= toRead;
89			offset += toRead;
90			*bytesRead += toRead;
91		}
92	}
93	return error;
94}
95
96// WriteAt
97status_t
98DataContainer::WriteAt(off_t offset, const void *_buffer, size_t size,
99					   size_t *bytesWritten)
100{
101//PRINT(("DataContainer::WriteAt(%Ld, %p, %lu, %p), fSize: %Ld\n", offset, _buffer, size, bytesWritten, fSize));
102	const uint8 *buffer = (const uint8*)_buffer;
103	status_t error = (buffer && offset >= 0 && bytesWritten
104					  ? B_OK : B_BAD_VALUE);
105	// resize the container, if necessary
106	if (error == B_OK) {
107		off_t newSize = offset + size;
108		off_t oldSize = fSize;
109		if (newSize > fSize) {
110			error = Resize(newSize);
111			// pad with zero, if necessary
112			if (error == B_OK && offset > oldSize)
113				_ClearArea(offset, oldSize - offset);
114		}
115	}
116	if (error == B_OK) {
117		// iterate through the blocks, writing as long as there's something
118		// left to write
119		size_t blockSize = fVolume->GetBlockSize();
120		*bytesWritten = 0;
121		while (size > 0) {
122			size_t inBlockOffset = offset % blockSize;
123			size_t toWrite = min(size, size_t(blockSize - inBlockOffset));
124			void *blockData = _GetBlockDataAt(offset / blockSize,
125											  inBlockOffset, toWrite);
126D(if (!blockData) return B_ERROR;);
127			memcpy(blockData, buffer, toWrite);
128			buffer += toWrite;
129			size -= toWrite;
130			offset += toWrite;
131			*bytesWritten += toWrite;
132		}
133	}
134//PRINT(("DataContainer::WriteAt() done: %lx, fSize: %Ld\n", error, fSize));
135	return error;
136}
137
138// GetFirstDataBlock
139void
140DataContainer::GetFirstDataBlock(const uint8 **data, size_t *length)
141{
142	if (data && length) {
143		if (_IsBlockMode()) {
144			BlockReference *block = _GetBlockList()->ItemAt(0);
145			*data = (const uint8*)block->GetData();
146			*length = min(fSize, fVolume->GetBlockSize());
147		} else {
148			*data = fSmallBuffer;
149			*length = fSize;
150		}
151	}
152}
153
154// GetAllocationInfo
155void
156DataContainer::GetAllocationInfo(AllocationInfo &info)
157{
158	if (_IsBlockMode()) {
159		BlockList *blocks = _GetBlockList();
160		info.AddListAllocation(blocks->GetCapacity(), sizeof(BlockReference*));
161		int32 blockCount = blocks->CountItems();
162		for (int32 i = 0; i < blockCount; i++)
163			info.AddBlockAllocation(blocks->ItemAt(i)->GetBlock()->GetSize());
164	} else {
165		// ...
166	}
167}
168
169// _RequiresBlockMode
170inline
171bool
172DataContainer::_RequiresBlockMode(size_t size)
173{
174	return (size > kSmallDataContainerSize);
175}
176
177// _IsBlockMode
178inline
179bool
180DataContainer::_IsBlockMode() const
181{
182	return (fSize > kSmallDataContainerSize);
183}
184
185// _Resize
186status_t
187DataContainer::_Resize(off_t newSize)
188{
189//PRINT(("DataContainer::_Resize(%Ld), fSize: %Ld\n", newSize, fSize));
190	status_t error = B_OK;
191	if (newSize != fSize) {
192		size_t blockSize = fVolume->GetBlockSize();
193		int32 blockCount = _CountBlocks();
194		int32 newBlockCount = (newSize + blockSize - 1) / blockSize;
195		if (newBlockCount == blockCount) {
196			// only the last block needs to be resized
197			if (_IsBlockMode() && _RequiresBlockMode(newSize)) {
198				// keep block mode
199				error = _ResizeLastBlock((newSize - 1) % blockSize + 1);
200			} else if (!_IsBlockMode() && !_RequiresBlockMode(newSize)) {
201				// keep small buffer mode
202				fSize = newSize;
203			} else if (fSize < newSize) {
204				// switch to block mode
205				_SwitchToBlockMode(newSize);
206			} else {
207				// switch to small buffer mode
208				_SwitchToSmallBufferMode(newSize);
209			}
210		} else if (newBlockCount < blockCount) {
211			// shrink
212			if (_IsBlockMode()) {
213				// remove the last blocks
214				BlockList *blocks = _GetBlockList();
215				for (int32 i = blockCount - 1; i >= newBlockCount; i--) {
216					BlockReference *block = blocks->ItemAt(i);
217					blocks->RemoveItem(i);
218					fVolume->FreeBlock(block);
219					fSize = (fSize - 1) / blockSize * blockSize;
220				}
221				// resize the last block to the correct size, respectively
222				// switch to small buffer mode
223				if (_RequiresBlockMode(newSize))
224					error = _ResizeLastBlock((newSize - 1) % blockSize + 1);
225				else
226					_SwitchToSmallBufferMode(newSize);
227			} else {
228				// small buffer mode: just set the new size
229				fSize = newSize;
230			}
231		} else {
232			// grow
233			if (_RequiresBlockMode(newSize)) {
234				// resize the first block to the correct size, respectively
235				// switch to block mode
236				if (_IsBlockMode())
237					error = _ResizeLastBlock(blockSize);
238				else {
239					error = _SwitchToBlockMode(min((size_t)newSize,
240												   blockSize));
241				}
242				// add new blocks
243				BlockList *blocks = _GetBlockList();
244				while (error == B_OK && fSize < newSize) {
245					size_t newBlockSize = min(size_t(newSize - fSize),
246											  blockSize);
247					BlockReference *block = NULL;
248					error = fVolume->AllocateBlock(newBlockSize, &block);
249					if (error == B_OK) {
250						if (blocks->AddItem(block))
251							fSize += newBlockSize;
252						else {
253							SET_ERROR(error, B_NO_MEMORY);
254							fVolume->FreeBlock(block);
255						}
256					}
257				}
258			} else {
259				// no need to switch to block mode: just set the new size
260				fSize = newSize;
261			}
262		}
263	}
264//PRINT(("DataContainer::_Resize() done: %lx, fSize: %Ld\n", error, fSize));
265	return error;
266}
267
268// _GetBlockList
269inline
270DataContainer::BlockList *
271DataContainer::_GetBlockList()
272{
273	return (BlockList*)fBlocks;
274}
275
276// _GetBlockList
277inline
278const DataContainer::BlockList *
279DataContainer::_GetBlockList() const
280{
281	return (BlockList*)fBlocks;
282}
283
284// _CountBlocks
285inline
286int32
287DataContainer::_CountBlocks() const
288{
289	if (_IsBlockMode())
290		return _GetBlockList()->CountItems();
291	else if (fSize == 0)	// small buffer mode, empty buffer
292		return 0;
293	return 1;	// small buffer mode, non-empty buffer
294}
295
296// _GetBlockDataAt
297inline
298void *
299DataContainer::_GetBlockDataAt(int32 index, size_t offset, size_t DARG(size))
300{
301	if (_IsBlockMode()) {
302		BlockReference *block = _GetBlockList()->ItemAt(index);
303D(if (!fVolume->CheckBlock(block, offset + size)) return NULL;);
304		return block->GetDataAt(offset);
305	} else {
306D(
307if (offset + size > kSmallDataContainerSize) {
308	FATAL(("DataContainer: Data access exceeds small buffer.\n"));
309	PANIC("DataContainer: Data access exceeds small buffer.");
310	return NULL;
311}
312);
313		return fSmallBuffer + offset;
314	}
315}
316
317// _ClearArea
318void
319DataContainer::_ClearArea(off_t offset, off_t size)
320{
321	// constrain the area to the data area
322	offset = min(offset, fSize);
323	size = min(size, fSize - offset);
324	// iterate through the blocks, clearing as long as there's something
325	// left to clear
326	size_t blockSize = fVolume->GetBlockSize();
327	while (size > 0) {
328		size_t inBlockOffset = offset % blockSize;
329		size_t toClear = min(size_t(size), blockSize - inBlockOffset);
330		void *blockData = _GetBlockDataAt(offset / blockSize, inBlockOffset,
331										  toClear);
332D(if (!blockData) return;);
333		memset(blockData, 0, toClear);
334		size -= toClear;
335		offset += toClear;
336	}
337}
338
339// _ResizeLastBlock
340status_t
341DataContainer::_ResizeLastBlock(size_t newSize)
342{
343//PRINT(("DataContainer::_ResizeLastBlock(%lu), fSize: %Ld\n", newSize, fSize));
344	int32 blockCount = _CountBlocks();
345	status_t error = (fSize > 0 && blockCount > 0 && newSize > 0
346					  ? B_OK : B_BAD_VALUE);
347D(
348if (!_IsBlockMode()) {
349	FATAL(("Call of _ResizeLastBlock() in small buffer mode.\n"));
350	PANIC("Call of _ResizeLastBlock() in small buffer mode.");
351	return B_ERROR;
352}
353);
354	if (error == B_OK) {
355		size_t blockSize = fVolume->GetBlockSize();
356		size_t oldSize = (fSize - 1) % blockSize + 1;
357		if (newSize != oldSize) {
358			BlockList *blocks = _GetBlockList();
359			BlockReference *block = blocks->ItemAt(blockCount - 1);
360			BlockReference *newBlock = fVolume->ResizeBlock(block, newSize);
361			if (newBlock) {
362				if (newBlock != block)
363					blocks->ReplaceItem(blockCount - 1, newBlock);
364				fSize += off_t(newSize) - oldSize;
365			} else
366				SET_ERROR(error, B_NO_MEMORY);
367		}
368	}
369//PRINT(("DataContainer::_ResizeLastBlock() done: %lx, fSize: %Ld\n", error, fSize));
370	return error;
371}
372
373// _SwitchToBlockMode
374status_t
375DataContainer::_SwitchToBlockMode(size_t newBlockSize)
376{
377	// allocate a new block
378	BlockReference *block = NULL;
379	status_t error = fVolume->AllocateBlock(newBlockSize, &block);
380	if (error == B_OK) {
381		// copy the data from the small buffer into the block
382		if (fSize > 0)
383			memcpy(block->GetData(), fSmallBuffer, fSize);
384		// construct the block list and add the block
385		new (fBlocks) BlockList(10);
386		BlockList *blocks = _GetBlockList();
387		if (blocks->AddItem(block)) {
388			fSize = newBlockSize;
389		} else {
390			// error: destroy the block list and free the block
391			SET_ERROR(error, B_NO_MEMORY);
392			blocks->~BlockList();
393			if (fSize > 0)
394				memcpy(fSmallBuffer, block->GetData(), fSize);
395			fVolume->FreeBlock(block);
396		}
397	}
398	return error;
399}
400
401// _SwitchToSmallBufferMode
402void
403DataContainer::_SwitchToSmallBufferMode(size_t newSize)
404{
405	// remove the first (and only) block
406	BlockList *blocks = _GetBlockList();
407	BlockReference *block = blocks->ItemAt(0);
408	blocks->RemoveItem((int32)0L);
409	// destroy the block list and copy the data into the small buffer
410	blocks->~BlockList();
411	if (newSize > 0)
412		memcpy(fSmallBuffer, block->GetData(), newSize);
413	// free the block and set the new size
414	fVolume->FreeBlock(block);
415	fSize = newSize;
416}
417
418