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:
Init()53 status_t Init()
54 {
55 fVnode = NULL;
56 return VMAnonymousNoSwapCache::Init(false, 0, 0, 0);
57 }
58
AcquireUnreferencedStoreRef()59 status_t AcquireUnreferencedStoreRef() override
60 {
61 return B_NOT_SUPPORTED;
62 }
63
AcquireStoreRef()64 void AcquireStoreRef() override
65 {
66 vfs_acquire_vnode(fVnode);
67 }
68
ReleaseStoreRef()69 void ReleaseStoreRef() override
70 {
71 vfs_put_vnode(fVnode);
72 }
73
74 protected:
DeleteObject()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
DataContainer(Volume * volume)87 DataContainer::DataContainer(Volume *volume)
88 : fVolume(volume),
89 fSize(0),
90 fCache(NULL),
91 fSmallBuffer(NULL),
92 fSmallBufferSize(0)
93 {
94 }
95
96
~DataContainer()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
InitCheck() const112 DataContainer::InitCheck() const
113 {
114 return (fVolume != NULL ? B_OK : B_ERROR);
115 }
116
117
118 VMCache*
GetCache(struct vnode * vnode)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
Resize(off_t newSize)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
ReadAt(off_t offset,void * _buffer,size_t size,size_t * bytesRead)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
WriteAt(off_t offset,const void * _buffer,size_t size,size_t * bytesWritten)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
GetAllocationInfo(AllocationInfo & info)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
_RequiresCacheMode(size_t size)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
_IsCacheMode() const274 DataContainer::_IsCacheMode() const
275 {
276 return fCache != NULL;
277 }
278
279
280 inline int32
_CountBlocks() const281 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
_SwitchToCacheMode()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 AutoLocker<VMCache> locker(cache);
303
304 fCache = cache;
305 fCache->temporary = 1;
306 fCache->unmergeable = 1;
307 fCache->virtual_end = fSize;
308
309 error = fCache->Commit(fSize, VM_PRIORITY_USER);
310 if (error != B_OK)
311 return error;
312
313 if (fSize != 0)
314 error = _DoCacheIO(0, fSmallBuffer, fSize, NULL, true);
315
316 free(fSmallBuffer);
317 fSmallBuffer = NULL;
318 fSmallBufferSize = 0;
319
320 return error;
321 }
322
323
324 status_t
_DoCacheIO(const off_t offset,uint8 * buffer,ssize_t length,size_t * bytesProcessed,bool isWrite)325 DataContainer::_DoCacheIO(const off_t offset, uint8* buffer, ssize_t length,
326 size_t* bytesProcessed, bool isWrite)
327 {
328 const size_t originalLength = length;
329 const bool user = IS_USER_ADDRESS(buffer);
330
331 const off_t rounded_offset = ROUNDDOWN(offset, B_PAGE_SIZE);
332 const size_t rounded_len = ROUNDUP((length) + (offset - rounded_offset),
333 B_PAGE_SIZE);
334 BStackOrHeapArray<vm_page*, 16> pages(rounded_len / B_PAGE_SIZE);
335 if (!pages.IsValid())
336 return B_NO_MEMORY;
337
338 cache_get_pages(fCache, rounded_offset, rounded_len, isWrite, pages);
339
340 status_t error = B_OK;
341 size_t index = 0;
342
343 while (length > 0) {
344 vm_page* page = pages[index];
345 phys_addr_t at = (page != NULL)
346 ? (page->physical_page_number * B_PAGE_SIZE) : 0;
347 ssize_t bytes = B_PAGE_SIZE;
348 if (index == 0) {
349 const uint32 pageoffset = (offset % B_PAGE_SIZE);
350 at += pageoffset;
351 bytes -= pageoffset;
352 }
353 bytes = min(length, bytes);
354
355 if (isWrite) {
356 page->modified = true;
357 error = vm_memcpy_to_physical(at, buffer, bytes, user);
358 } else {
359 if (page != NULL) {
360 error = vm_memcpy_from_physical(buffer, at, bytes, user);
361 } else {
362 if (user) {
363 error = user_memset(buffer, 0, bytes);
364 } else {
365 memset(buffer, 0, bytes);
366 }
367 }
368 }
369 if (error != B_OK)
370 break;
371
372 buffer += bytes;
373 length -= bytes;
374 index++;
375 }
376
377 cache_put_pages(fCache, rounded_offset, rounded_len, pages, error == B_OK);
378
379 if (bytesProcessed != NULL)
380 *bytesProcessed = length > 0 ? originalLength - length : originalLength;
381
382 return error;
383 }
384