xref: /haiku/src/add-ons/kernel/file_systems/ramfs/DataContainer.cpp (revision c90684742e7361651849be4116d0e5de3a817194)
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 "Debug.h"
8 #include "Misc.h"
9 #include "Node.h"		// for debugging only
10 #include "Volume.h"
11 
12 // constructor
13 DataContainer::DataContainer(Volume *volume)
14 	: fVolume(volume),
15 	  fSize(0)
16 {
17 }
18 
19 // destructor
20 DataContainer::~DataContainer()
21 {
22 	Resize(0);
23 }
24 
25 // InitCheck
26 status_t
27 DataContainer::InitCheck() const
28 {
29 	return (fVolume ? B_OK : B_ERROR);
30 }
31 
32 // Resize
33 status_t
34 DataContainer::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
51 status_t
52 DataContainer::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);
70 D(
71 if (!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
97 status_t
98 DataContainer::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);
126 D(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
139 void
140 DataContainer::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
155 void
156 DataContainer::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
170 inline
171 bool
172 DataContainer::_RequiresBlockMode(size_t size)
173 {
174 	return (size > kSmallDataContainerSize);
175 }
176 
177 // _IsBlockMode
178 inline
179 bool
180 DataContainer::_IsBlockMode() const
181 {
182 	return (fSize > kSmallDataContainerSize);
183 }
184 
185 // _Resize
186 status_t
187 DataContainer::_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
269 inline
270 DataContainer::BlockList *
271 DataContainer::_GetBlockList()
272 {
273 	return (BlockList*)fBlocks;
274 }
275 
276 // _GetBlockList
277 inline
278 const DataContainer::BlockList *
279 DataContainer::_GetBlockList() const
280 {
281 	return (BlockList*)fBlocks;
282 }
283 
284 // _CountBlocks
285 inline
286 int32
287 DataContainer::_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
297 inline
298 void *
299 DataContainer::_GetBlockDataAt(int32 index, size_t offset, size_t DARG(size))
300 {
301 	if (_IsBlockMode()) {
302 		BlockReference *block = _GetBlockList()->ItemAt(index);
303 D(if (!fVolume->CheckBlock(block, offset + size)) return NULL;);
304 		return block->GetDataAt(offset);
305 	} else {
306 D(
307 if (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
318 void
319 DataContainer::_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);
332 D(if (!blockData) return;);
333 		memset(blockData, 0, toClear);
334 		size -= toClear;
335 		offset += toClear;
336 	}
337 }
338 
339 // _ResizeLastBlock
340 status_t
341 DataContainer::_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);
347 D(
348 if (!_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
374 status_t
375 DataContainer::_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
402 void
403 DataContainer::_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(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