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.
40static const off_t kMinimumSmallBufferSize = 32;
41static const off_t kMaximumSmallBufferSize = (B_PAGE_SIZE / 4);
42
43
44// constructor
45DataContainer::DataContainer(Volume *volume)
46	: fVolume(volume),
47	  fSize(0),
48	  fCache(NULL),
49	  fSmallBuffer(NULL),
50	  fSmallBufferSize(0)
51{
52}
53
54// destructor
55DataContainer::~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
69status_t
70DataContainer::InitCheck() const
71{
72	return (fVolume != NULL ? B_OK : B_ERROR);
73}
74
75// GetCache
76VMCache*
77DataContainer::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
87status_t
88DataContainer::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
133status_t
134DataContainer::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
169status_t
170DataContainer::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
210void
211DataContainer::GetAllocationInfo(AllocationInfo &info)
212{
213	if (_IsCacheMode()) {
214		info.AddAreaAllocation(fCache->committed_size);
215	} else {
216		// ...
217	}
218}
219
220// _RequiresCacheMode
221inline bool
222DataContainer::_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
230inline bool
231DataContainer::_IsCacheMode() const
232{
233	return fCache != NULL;
234}
235
236// _CountBlocks
237inline int32
238DataContainer::_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
248status_t
249DataContainer::_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
274status_t
275DataContainer::_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
337void
338DataContainer::_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
392void
393DataContainer::_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