xref: /haiku/src/add-ons/kernel/file_systems/ramfs/DataContainer.cpp (revision e1c4049fed1047bdb957b0529e1921e97ef94770)
1 /*
2  * Copyright 2007, Ingo Weinhold, ingo_weinhold@gmx.de.
3  * Copyright 2019, Haiku, Inc.
4  * All rights reserved. Distributed under the terms of the MIT license.
5  */
6 #include "DataContainer.h"
7 
8 #include <AutoDeleter.h>
9 #include <util/AutoLock.h>
10 #include <util/BitUtils.h>
11 
12 #include <vm/VMCache.h>
13 #include <vm/vm_page.h>
14 
15 #include "AllocationInfo.h"
16 #include "DebugSupport.h"
17 #include "Misc.h"
18 #include "Volume.h"
19 
20 
21 // Initial size of the DataContainer's small buffer. If it contains data up to
22 // this size, nothing is allocated, but the small buffer is used instead.
23 // 16 bytes are for free, since they are shared with the block list.
24 // (actually even more, since the list has an initial size).
25 // I ran a test analyzing what sizes the attributes in my system have:
26 //     size   percentage   bytes used in average
27 //   <=   0         0.00                   93.45
28 //   <=   4        25.46                   75.48
29 //   <=   8        30.54                   73.02
30 //   <=  16        52.98                   60.37
31 //   <=  32        80.19                   51.74
32 //   <=  64        94.38                   70.54
33 //   <= 126        96.90                  128.23
34 //
35 // For average memory usage it is assumed, that attributes larger than 126
36 // bytes have size 127, that the list has an initial capacity of 10 entries
37 // (40 bytes), that the block reference consumes 4 bytes and the block header
38 // 12 bytes. The optimal length is actually 35, with 51.05 bytes per
39 // attribute, but I conservatively rounded to 32.
40 static const off_t kMinimumSmallBufferSize = 32;
41 static const off_t kMaximumSmallBufferSize = (B_PAGE_SIZE / 4);
42 
43 
44 // constructor
45 DataContainer::DataContainer(Volume *volume)
46 	: fVolume(volume),
47 	  fSize(0),
48 	  fCache(NULL),
49 	  fSmallBuffer(NULL),
50 	  fSmallBufferSize(0)
51 {
52 }
53 
54 // destructor
55 DataContainer::~DataContainer()
56 {
57 	if (fCache != NULL) {
58 		fCache->Lock();
59 		fCache->ReleaseRefAndUnlock();
60 		fCache = NULL;
61 	}
62 	if (fSmallBuffer != NULL) {
63 		free(fSmallBuffer);
64 		fSmallBuffer = NULL;
65 	}
66 }
67 
68 // InitCheck
69 status_t
70 DataContainer::InitCheck() const
71 {
72 	return (fVolume != NULL ? B_OK : B_ERROR);
73 }
74 
75 // GetCache
76 VMCache*
77 DataContainer::GetCache()
78 {
79 	// TODO: Because we always get the cache for files on creation vs. on demand,
80 	// this means files (no matter how small) always use cache mode at present.
81 	if (!_IsCacheMode())
82 		_SwitchToCacheMode();
83 	return fCache;
84 }
85 
86 // Resize
87 status_t
88 DataContainer::Resize(off_t newSize)
89 {
90 //	PRINT("DataContainer::Resize(%lld), fSize: %lld\n", newSize, fSize);
91 
92 	status_t error = B_OK;
93 	if (_RequiresCacheMode(newSize)) {
94 		if (newSize < fSize) {
95 			// shrink
96 			// resize the VMCache, which will automatically free pages
97 			AutoLocker<VMCache> _(fCache);
98 			error = fCache->Resize(newSize, VM_PRIORITY_SYSTEM);
99 			if (error != B_OK)
100 				return error;
101 		} else {
102 			// grow
103 			if (!_IsCacheMode())
104 				error = _SwitchToCacheMode();
105 			if (error != B_OK)
106 				return error;
107 
108 			AutoLocker<VMCache> _(fCache);
109 			fCache->Resize(newSize, VM_PRIORITY_SYSTEM);
110 
111 			// pages will be added as they are written to; so nothing else
112 			// needs to be done here.
113 		}
114 	} else if (fSmallBufferSize < newSize
115 			|| (fSmallBufferSize - newSize) > (kMaximumSmallBufferSize / 2)) {
116 		const size_t newBufferSize = max_c(next_power_of_2(newSize),
117 			kMinimumSmallBufferSize);
118 		void* newBuffer = realloc(fSmallBuffer, newBufferSize);
119 		if (newBuffer == NULL)
120 			return B_NO_MEMORY;
121 
122 		fSmallBufferSize = newBufferSize;
123 		fSmallBuffer = (uint8*)newBuffer;
124 	}
125 
126 	fSize = newSize;
127 
128 //	PRINT("DataContainer::Resize() done: %lx, fSize: %lld\n", error, fSize);
129 	return error;
130 }
131 
132 // ReadAt
133 status_t
134 DataContainer::ReadAt(off_t offset, void *_buffer, size_t size,
135 	size_t *bytesRead)
136 {
137 	uint8 *buffer = (uint8*)_buffer;
138 	status_t error = (buffer && offset >= 0 &&
139 		bytesRead ? B_OK : B_BAD_VALUE);
140 	if (error != B_OK)
141 		return error;
142 
143 	// read not more than we have to offer
144 	offset = min(offset, fSize);
145 	size = min(size, size_t(fSize - offset));
146 
147 	if (!_IsCacheMode()) {
148 		// in non-cache mode, use the "small buffer"
149 		if (IS_USER_ADDRESS(buffer)) {
150 			error = user_memcpy(buffer, fSmallBuffer + offset, size);
151 			if (error != B_OK)
152 				size = 0;
153 		} else {
154 			memcpy(buffer, fSmallBuffer + offset, size);
155 		}
156 
157 		if (bytesRead != NULL)
158 			*bytesRead = size;
159 		return error;
160 	}
161 
162 	// cache mode
163 	error = _DoCacheIO(offset, buffer, size, bytesRead, false);
164 
165 	return error;
166 }
167 
168 // WriteAt
169 status_t
170 DataContainer::WriteAt(off_t offset, const void *_buffer, size_t size,
171 	size_t *bytesWritten)
172 {
173 	PRINT("DataContainer::WriteAt(%lld, %p, %lu, %p), fSize: %lld\n", offset, _buffer, size, bytesWritten, fSize);
174 
175 	const uint8 *buffer = (const uint8*)_buffer;
176 	status_t error = (buffer && offset >= 0 && bytesWritten
177 		? B_OK : B_BAD_VALUE);
178 	if (error != B_OK)
179 		return error;
180 
181 	// resize the container, if necessary
182 	if ((offset + (off_t)size) > fSize)
183 		error = Resize(offset + size);
184 	if (error != B_OK)
185 		return error;
186 
187 	if (!_IsCacheMode()) {
188 		// in non-cache mode, use the "small buffer"
189 		if (IS_USER_ADDRESS(buffer)) {
190 			error = user_memcpy(fSmallBuffer + offset, buffer, size);
191 			if (error != B_OK)
192 				size = 0;
193 		} else {
194 			memcpy(fSmallBuffer + offset, buffer, size);
195 		}
196 
197 		if (bytesWritten != NULL)
198 			*bytesWritten = size;
199 		return error;
200 	}
201 
202 	// cache mode
203 	error = _DoCacheIO(offset, (uint8*)buffer, size, bytesWritten, true);
204 
205 	PRINT("DataContainer::WriteAt() done: %lx, fSize: %lld\n", error, fSize);
206 	return error;
207 }
208 
209 // GetAllocationInfo
210 void
211 DataContainer::GetAllocationInfo(AllocationInfo &info)
212 {
213 	if (_IsCacheMode()) {
214 		info.AddAreaAllocation(fCache->committed_size);
215 	} else {
216 		// ...
217 	}
218 }
219 
220 // _RequiresCacheMode
221 inline bool
222 DataContainer::_RequiresCacheMode(size_t size)
223 {
224 	// we cannot back out of cache mode after entering it,
225 	// as there may be other consumers of our VMCache
226 	return _IsCacheMode() || (size > kMaximumSmallBufferSize);
227 }
228 
229 // _IsCacheMode
230 inline bool
231 DataContainer::_IsCacheMode() const
232 {
233 	return fCache != NULL;
234 }
235 
236 // _CountBlocks
237 inline int32
238 DataContainer::_CountBlocks() const
239 {
240 	if (_IsCacheMode())
241 		return fCache->page_count;
242 	else if (fSize == 0)	// small buffer mode, empty buffer
243 		return 0;
244 	return 1;	// small buffer mode, non-empty buffer
245 }
246 
247 // _SwitchToCacheMode
248 status_t
249 DataContainer::_SwitchToCacheMode()
250 {
251 	status_t error = VMCacheFactory::CreateAnonymousCache(fCache, false, 0,
252 		0, false, VM_PRIORITY_SYSTEM);
253 	if (error != B_OK)
254 		return error;
255 
256 	fCache->temporary = 1;
257 	fCache->virtual_end = fSize;
258 
259 	error = fCache->Commit(fSize, VM_PRIORITY_SYSTEM);
260 	if (error != B_OK)
261 		return error;
262 
263 	if (fSize != 0)
264 		error = _DoCacheIO(0, fSmallBuffer, fSize, NULL, true);
265 
266 	free(fSmallBuffer);
267 	fSmallBuffer = NULL;
268 	fSmallBufferSize = 0;
269 
270 	return error;
271 }
272 
273 // _DoCacheIO
274 status_t
275 DataContainer::_DoCacheIO(const off_t offset, uint8* buffer, ssize_t length,
276 	size_t* bytesProcessed, bool isWrite)
277 {
278 	const size_t originalLength = length;
279 	const bool user = IS_USER_ADDRESS(buffer);
280 
281 	const off_t rounded_offset = ROUNDDOWN(offset, B_PAGE_SIZE);
282 	const size_t rounded_len = ROUNDUP((length) + (offset - rounded_offset),
283 		B_PAGE_SIZE);
284 	vm_page** pages = new(std::nothrow) vm_page*[rounded_len / B_PAGE_SIZE];
285 	if (pages == NULL)
286 		return B_NO_MEMORY;
287 	ArrayDeleter<vm_page*> pagesDeleter(pages);
288 
289 	_GetPages(rounded_offset, rounded_len, isWrite, pages);
290 
291 	status_t error = B_OK;
292 	size_t index = 0;
293 
294 	while (length > 0) {
295 		vm_page* page = pages[index];
296 		phys_addr_t at = (page != NULL)
297 			? (page->physical_page_number * B_PAGE_SIZE) : 0;
298 		ssize_t bytes = B_PAGE_SIZE;
299 		if (index == 0) {
300 			const uint32 pageoffset = (offset % B_PAGE_SIZE);
301 			at += pageoffset;
302 			bytes -= pageoffset;
303 		}
304 		bytes = min(length, bytes);
305 
306 		if (isWrite) {
307 			page->modified = true;
308 			error = vm_memcpy_to_physical(at, buffer, bytes, user);
309 		} else {
310 			if (page != NULL) {
311 				error = vm_memcpy_from_physical(buffer, at, bytes, user);
312 			} else {
313 				if (user) {
314 					error = user_memset(buffer, 0, bytes);
315 				} else {
316 					memset(buffer, 0, bytes);
317 				}
318 			}
319 		}
320 		if (error != B_OK)
321 			break;
322 
323 		buffer += bytes;
324 		length -= bytes;
325 		index++;
326 	}
327 
328 	_PutPages(rounded_offset, rounded_len, pages, error == B_OK);
329 
330 	if (bytesProcessed != NULL)
331 		*bytesProcessed = length > 0 ? originalLength - length : originalLength;
332 
333 	return error;
334 }
335 
336 // _GetPages
337 void
338 DataContainer::_GetPages(off_t offset, off_t length, bool isWrite,
339 	vm_page** pages)
340 {
341 	// TODO: This method is duplicated in the ram_disk. Perhaps it
342 	// should be put into a common location?
343 
344 	// get the pages, we already have
345 	AutoLocker<VMCache> locker(fCache);
346 
347 	size_t pageCount = length / B_PAGE_SIZE;
348 	size_t index = 0;
349 	size_t missingPages = 0;
350 
351 	while (length > 0) {
352 		vm_page* page = fCache->LookupPage(offset);
353 		if (page != NULL) {
354 			if (page->busy) {
355 				fCache->WaitForPageEvents(page, PAGE_EVENT_NOT_BUSY, true);
356 				continue;
357 			}
358 
359 			DEBUG_PAGE_ACCESS_START(page);
360 			page->busy = true;
361 		} else
362 			missingPages++;
363 
364 		pages[index++] = page;
365 		offset += B_PAGE_SIZE;
366 		length -= B_PAGE_SIZE;
367 	}
368 
369 	locker.Unlock();
370 
371 	// For a write we need to reserve the missing pages.
372 	if (isWrite && missingPages > 0) {
373 		vm_page_reservation reservation;
374 		vm_page_reserve_pages(&reservation, missingPages,
375 			VM_PRIORITY_SYSTEM);
376 
377 		for (size_t i = 0; i < pageCount; i++) {
378 			if (pages[i] != NULL)
379 				continue;
380 
381 			pages[i] = vm_page_allocate_page(&reservation,
382 				PAGE_STATE_WIRED | VM_PAGE_ALLOC_BUSY);
383 
384 			if (--missingPages == 0)
385 				break;
386 		}
387 
388 		vm_page_unreserve_pages(&reservation);
389 	}
390 }
391 
392 void
393 DataContainer::_PutPages(off_t offset, off_t length, vm_page** pages,
394 	bool success)
395 {
396 	// TODO: This method is duplicated in the ram_disk. Perhaps it
397 	// should be put into a common location?
398 
399 	AutoLocker<VMCache> locker(fCache);
400 
401 	// Mark all pages unbusy. On error free the newly allocated pages.
402 	size_t index = 0;
403 
404 	while (length > 0) {
405 		vm_page* page = pages[index++];
406 		if (page != NULL) {
407 			if (page->CacheRef() == NULL) {
408 				if (success) {
409 					fCache->InsertPage(page, offset);
410 					fCache->MarkPageUnbusy(page);
411 					DEBUG_PAGE_ACCESS_END(page);
412 				} else
413 					vm_page_free(NULL, page);
414 			} else {
415 				fCache->MarkPageUnbusy(page);
416 				DEBUG_PAGE_ACCESS_END(page);
417 			}
418 		}
419 
420 		offset += B_PAGE_SIZE;
421 		length -= B_PAGE_SIZE;
422 	}
423 }
424