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