headers/bsd: Add sys/queue.h.
[haiku.git] / src / system / kernel / cache / file_map.cpp
blob0e79bb5670af307f5080ca4fd4ee67a04889b1cc
1 /*
2 * Copyright 2004-2012, Axel Dörfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
7 #include <new>
8 #include <stdlib.h>
9 #include <string.h>
11 #ifdef FS_SHELL
12 # include "vfs.h"
13 # include "fssh_api_wrapper.h"
15 using namespace FSShell;
16 #else
17 # include <unistd.h>
19 # include <KernelExport.h>
20 # include <fs_cache.h>
22 # include <condition_variable.h>
23 # include <file_cache.h>
24 # include <generic_syscall.h>
25 # include <util/AutoLock.h>
26 # include <util/DoublyLinkedList.h>
27 # include <vfs.h>
28 # include <vm/vm.h>
29 # include <vm/vm_page.h>
30 # include <vm/VMCache.h>
32 # include "kernel_debug_config.h"
33 #endif
36 //#define TRACE_FILE_MAP
37 #ifdef TRACE_FILE_MAP
38 # define TRACE(x...) dprintf_no_syslog(x)
39 #else
40 # define TRACE(x...) ;
41 #endif
43 // TODO: use a sparse array - eventually, the unused BlockMap would be something
44 // to reuse for this. We could also have an upperbound of memory consumption
45 // for the whole map.
46 // TODO: it would be nice if we could free a file map in low memory situations.
49 #define CACHED_FILE_EXTENTS 2
50 // must be smaller than MAX_FILE_IO_VECS
51 // TODO: find out how much of these are typically used
53 struct file_extent {
54 off_t offset;
55 file_io_vec disk;
58 struct file_extent_array {
59 file_extent* array;
60 size_t max_count;
63 class FileMap
64 #if DEBUG_FILE_MAP
65 : public DoublyLinkedListLinkImpl<FileMap>
66 #endif
68 public:
69 FileMap(struct vnode* vnode, off_t size);
70 ~FileMap();
72 void Invalidate(off_t offset, off_t size);
73 void SetSize(off_t size);
75 status_t Translate(off_t offset, size_t size,
76 file_io_vec* vecs, size_t* _count,
77 size_t align);
79 file_extent* ExtentAt(uint32 index);
81 size_t Count() const { return fCount; }
82 struct vnode* Vnode() const { return fVnode; }
83 off_t Size() const { return fSize; }
85 status_t SetMode(uint32 mode);
87 private:
88 file_extent* _FindExtent(off_t offset, uint32* _index);
89 status_t _MakeSpace(size_t count);
90 status_t _Add(file_io_vec* vecs, size_t vecCount,
91 off_t& lastOffset);
92 status_t _Cache(off_t offset, off_t size);
93 void _InvalidateAfter(off_t offset);
94 void _Free();
96 union {
97 file_extent fDirect[CACHED_FILE_EXTENTS];
98 file_extent_array fIndirect;
100 mutex fLock;
101 size_t fCount;
102 struct vnode* fVnode;
103 off_t fSize;
104 bool fCacheAll;
107 #if DEBUG_FILE_MAP
108 typedef DoublyLinkedList<FileMap> FileMapList;
110 static FileMapList sList;
111 static mutex sLock;
112 #endif
115 FileMap::FileMap(struct vnode* vnode, off_t size)
117 fCount(0),
118 fVnode(vnode),
119 fSize(size),
120 fCacheAll(false)
122 mutex_init(&fLock, "file map");
124 #if DEBUG_FILE_MAP
125 MutexLocker _(sLock);
126 sList.Add(this);
127 #endif
131 FileMap::~FileMap()
133 _Free();
134 mutex_destroy(&fLock);
136 #if DEBUG_FILE_MAP
137 MutexLocker _(sLock);
138 sList.Remove(this);
139 #endif
143 file_extent*
144 FileMap::ExtentAt(uint32 index)
146 if (index >= fCount)
147 return NULL;
149 if (fCount > CACHED_FILE_EXTENTS)
150 return &fIndirect.array[index];
152 return &fDirect[index];
156 file_extent*
157 FileMap::_FindExtent(off_t offset, uint32 *_index)
159 int32 left = 0;
160 int32 right = fCount - 1;
162 while (left <= right) {
163 int32 index = (left + right) / 2;
164 file_extent* extent = ExtentAt(index);
166 if (extent->offset > offset) {
167 // search in left part
168 right = index - 1;
169 } else if (extent->offset + extent->disk.length <= offset) {
170 // search in right part
171 left = index + 1;
172 } else {
173 // found extent
174 if (_index)
175 *_index = index;
177 return extent;
181 return NULL;
185 status_t
186 FileMap::_MakeSpace(size_t count)
188 if (count <= CACHED_FILE_EXTENTS) {
189 // just use the reserved area in the file_cache_ref structure
190 if (fCount > CACHED_FILE_EXTENTS) {
191 // the new size is smaller than the minimal array size
192 file_extent *array = fIndirect.array;
193 memcpy(fDirect, array, sizeof(file_extent) * count);
194 free(array);
196 } else {
197 // resize array if needed
198 file_extent* oldArray = NULL;
199 size_t maxCount = CACHED_FILE_EXTENTS;
200 if (fCount > CACHED_FILE_EXTENTS) {
201 oldArray = fIndirect.array;
202 maxCount = fIndirect.max_count;
205 if (count > maxCount) {
206 // allocate new array
207 while (maxCount < count) {
208 if (maxCount < 32768)
209 maxCount <<= 1;
210 else
211 maxCount += 32768;
214 file_extent* newArray = (file_extent *)realloc(oldArray,
215 maxCount * sizeof(file_extent));
216 if (newArray == NULL)
217 return B_NO_MEMORY;
219 if (fCount > 0 && fCount <= CACHED_FILE_EXTENTS)
220 memcpy(newArray, fDirect, sizeof(file_extent) * fCount);
222 fIndirect.array = newArray;
223 fIndirect.max_count = maxCount;
227 fCount = count;
228 return B_OK;
232 status_t
233 FileMap::_Add(file_io_vec* vecs, size_t vecCount, off_t& lastOffset)
235 TRACE("FileMap@%p::Add(vecCount = %ld)\n", this, vecCount);
237 uint32 start = fCount;
238 off_t offset = 0;
240 status_t status = _MakeSpace(fCount + vecCount);
241 if (status != B_OK)
242 return status;
244 file_extent* lastExtent = NULL;
245 if (start != 0) {
246 lastExtent = ExtentAt(start - 1);
247 offset = lastExtent->offset + lastExtent->disk.length;
250 for (uint32 i = 0; i < vecCount; i++) {
251 if (lastExtent != NULL) {
252 if (lastExtent->disk.offset + lastExtent->disk.length
253 == vecs[i].offset
254 || (lastExtent->disk.offset == -1 && vecs[i].offset == -1)) {
256 lastExtent->disk.length += vecs[i].length;
257 offset += vecs[i].length;
259 _MakeSpace(fCount - 1);
260 if (fCount == CACHED_FILE_EXTENTS) {
261 // We moved the indirect array into the direct one, making
262 // lastExtent a stale pointer, re-get it.
263 lastExtent = ExtentAt(start - 1);
266 continue;
270 file_extent* extent = ExtentAt(start++);
271 extent->offset = offset;
272 extent->disk = vecs[i];
274 offset += extent->disk.length;
275 lastExtent = extent;
278 #ifdef TRACE_FILE_MAP
279 for (uint32 i = 0; i < fCount; i++) {
280 file_extent* extent = ExtentAt(i);
281 TRACE("[%ld] extent offset %Ld, disk offset %Ld, length %Ld\n",
282 i, extent->offset, extent->disk.offset, extent->disk.length);
284 #endif
286 lastOffset = offset;
287 return B_OK;
291 void
292 FileMap::_InvalidateAfter(off_t offset)
294 uint32 index;
295 file_extent* extent = _FindExtent(offset, &index);
296 if (extent != NULL) {
297 uint32 resizeTo = index + 1;
299 if (extent->offset + extent->disk.length > offset) {
300 extent->disk.length = offset - extent->offset;
301 if (extent->disk.length == 0)
302 resizeTo = index;
305 _MakeSpace(resizeTo);
310 /*! Invalidates or removes the specified part of the file map.
312 void
313 FileMap::Invalidate(off_t offset, off_t size)
315 MutexLocker _(fLock);
317 // TODO: honour size, we currently always remove everything after "offset"
318 if (offset == 0) {
319 _Free();
320 return;
323 _InvalidateAfter(offset);
327 void
328 FileMap::SetSize(off_t size)
330 MutexLocker _(fLock);
332 if (size < fSize)
333 _InvalidateAfter(size);
335 fSize = size;
339 void
340 FileMap::_Free()
342 if (fCount > CACHED_FILE_EXTENTS)
343 free(fIndirect.array);
345 fCount = 0;
349 status_t
350 FileMap::_Cache(off_t offset, off_t size)
352 file_extent* lastExtent = NULL;
353 if (fCount > 0)
354 lastExtent = ExtentAt(fCount - 1);
356 off_t mapEnd = 0;
357 if (lastExtent != NULL)
358 mapEnd = lastExtent->offset + lastExtent->disk.length;
360 off_t end = offset + size;
362 if (fCacheAll && mapEnd < end)
363 return B_ERROR;
365 status_t status = B_OK;
366 file_io_vec vecs[8];
367 const size_t kMaxVecs = 8;
369 while (status == B_OK && mapEnd < end) {
370 // We don't have the requested extents yet, retrieve them
371 size_t vecCount = kMaxVecs;
372 status = vfs_get_file_map(Vnode(), mapEnd, ~0UL, vecs, &vecCount);
373 if (status == B_OK || status == B_BUFFER_OVERFLOW)
374 status = _Add(vecs, vecCount, mapEnd);
377 return status;
381 status_t
382 FileMap::SetMode(uint32 mode)
384 if (mode != FILE_MAP_CACHE_ALL && mode != FILE_MAP_CACHE_ON_DEMAND)
385 return B_BAD_VALUE;
387 MutexLocker _(fLock);
389 if ((mode == FILE_MAP_CACHE_ALL && fCacheAll)
390 || (mode == FILE_MAP_CACHE_ON_DEMAND && !fCacheAll))
391 return B_OK;
393 if (mode == FILE_MAP_CACHE_ALL) {
394 status_t status = _Cache(0, fSize);
395 if (status != B_OK)
396 return status;
398 fCacheAll = true;
399 } else
400 fCacheAll = false;
402 return B_OK;
406 status_t
407 FileMap::Translate(off_t offset, size_t size, file_io_vec* vecs, size_t* _count,
408 size_t align)
410 if (offset < 0)
411 return B_BAD_VALUE;
413 MutexLocker _(fLock);
415 size_t maxVecs = *_count;
416 size_t padLastVec = 0;
418 if (offset >= Size()) {
419 *_count = 0;
420 return B_OK;
422 if ((off_t)(offset + size) > fSize) {
423 if (align > 1) {
424 off_t alignedSize = (fSize + align - 1) & ~(off_t)(align - 1);
425 if ((off_t)(offset + size) >= alignedSize)
426 padLastVec = alignedSize - fSize;
428 size = fSize - offset;
431 // First, we need to make sure that we have already cached all file
432 // extents needed for this request.
434 status_t status = _Cache(offset, size);
435 if (status != B_OK)
436 return status;
438 // We now have cached the map of this file as far as we need it, now
439 // we need to translate it for the requested access.
441 uint32 index;
442 file_extent* fileExtent = _FindExtent(offset, &index);
444 offset -= fileExtent->offset;
445 if (fileExtent->disk.offset != -1)
446 vecs[0].offset = fileExtent->disk.offset + offset;
447 else
448 vecs[0].offset = -1;
449 vecs[0].length = fileExtent->disk.length - offset;
451 if (vecs[0].length >= (off_t)size) {
452 vecs[0].length = size + padLastVec;
453 *_count = 1;
454 return B_OK;
457 // copy the rest of the vecs
459 size -= vecs[0].length;
460 uint32 vecIndex = 1;
462 while (true) {
463 fileExtent++;
465 vecs[vecIndex++] = fileExtent->disk;
467 if ((off_t)size <= fileExtent->disk.length) {
468 vecs[vecIndex - 1].length = size + padLastVec;
469 break;
472 if (vecIndex >= maxVecs) {
473 *_count = vecIndex;
474 return B_BUFFER_OVERFLOW;
477 size -= fileExtent->disk.length;
480 *_count = vecIndex;
481 return B_OK;
485 // #pragma mark -
488 #if DEBUG_FILE_MAP
490 static int
491 dump_file_map(int argc, char** argv)
493 if (argc < 2) {
494 print_debugger_command_usage(argv[0]);
495 return 0;
498 bool printExtents = false;
499 if (argc > 2 && !strcmp(argv[1], "-p"))
500 printExtents = true;
502 FileMap* map = (FileMap*)parse_expression(argv[argc - 1]);
503 if (map == NULL) {
504 kprintf("invalid file map!\n");
505 return 0;
508 kprintf("FileMap %p\n", map);
509 kprintf(" size %" B_PRIdOFF "\n", map->Size());
510 kprintf(" count %lu\n", map->Count());
512 if (!printExtents)
513 return 0;
515 for (uint32 i = 0; i < map->Count(); i++) {
516 file_extent* extent = map->ExtentAt(i);
518 kprintf(" [%" B_PRIu32 "] offset %" B_PRIdOFF ", disk offset %"
519 B_PRIdOFF ", length %" B_PRIdOFF "\n", i, extent->offset,
520 extent->disk.offset, extent->disk.length);
523 return 0;
527 static int
528 dump_file_map_stats(int argc, char** argv)
530 off_t minSize = 0;
531 off_t maxSize = -1;
533 if (argc == 2) {
534 maxSize = parse_expression(argv[1]);
535 } else if (argc > 2) {
536 minSize = parse_expression(argv[1]);
537 maxSize = parse_expression(argv[2]);
540 FileMapList::Iterator iterator = sList.GetIterator();
541 off_t size = 0;
542 off_t mapSize = 0;
543 uint32 extents = 0;
544 uint32 count = 0;
545 uint32 emptyCount = 0;
547 while (iterator.HasNext()) {
548 FileMap* map = iterator.Next();
550 if (minSize > map->Size() || (maxSize != -1 && maxSize < map->Size()))
551 continue;
553 if (map->Count() != 0) {
554 file_extent* extent = map->ExtentAt(map->Count() - 1);
555 if (extent != NULL)
556 mapSize += extent->offset + extent->disk.length;
558 extents += map->Count();
559 } else
560 emptyCount++;
562 size += map->Size();
563 count++;
566 kprintf("%" B_PRId32 " file maps (%" B_PRIu32 " empty), %" B_PRIdOFF " file"
567 " bytes in total, %" B_PRIdOFF " bytes cached, %" B_PRIu32 " extents\n",
568 count, emptyCount, size, mapSize, extents);
569 kprintf("average %" B_PRIu32 " extents per map for %" B_PRIdOFF " bytes.\n",
570 extents / (count - emptyCount), mapSize / (count - emptyCount));
572 return 0;
575 #endif // DEBUG_FILE_MAP
578 // #pragma mark - private kernel API
581 extern "C" status_t
582 file_map_init(void)
584 #if DEBUG_FILE_MAP
585 add_debugger_command_etc("file_map", &dump_file_map,
586 "Dumps the specified file map.",
587 "[-p] <file-map>\n"
588 " -p - causes the file extents to be printed as well.\n"
589 " <file-map> - pointer to the file map.\n", 0);
590 add_debugger_command("file_map_stats", &dump_file_map_stats,
591 "Dumps some file map statistics.");
593 mutex_init(&sLock, "file map list");
594 #endif
595 return B_OK;
599 // #pragma mark - public FS API
602 extern "C" void*
603 file_map_create(dev_t mountID, ino_t vnodeID, off_t size)
605 TRACE("file_map_create(mountID = %ld, vnodeID = %Ld, size = %Ld)\n",
606 mountID, vnodeID, size);
608 // Get the vnode for the object
609 // (note, this does not grab a reference to the node)
610 struct vnode* vnode;
611 if (vfs_lookup_vnode(mountID, vnodeID, &vnode) != B_OK)
612 return NULL;
614 return new(std::nothrow) FileMap(vnode, size);
618 extern "C" void
619 file_map_delete(void* _map)
621 FileMap* map = (FileMap*)_map;
622 if (map == NULL)
623 return;
625 TRACE("file_map_delete(map = %p)\n", map);
626 delete map;
630 extern "C" void
631 file_map_set_size(void* _map, off_t size)
633 FileMap* map = (FileMap*)_map;
634 if (map == NULL)
635 return;
637 map->SetSize(size);
641 extern "C" void
642 file_map_invalidate(void* _map, off_t offset, off_t size)
644 FileMap* map = (FileMap*)_map;
645 if (map == NULL)
646 return;
648 map->Invalidate(offset, size);
652 extern "C" status_t
653 file_map_set_mode(void* _map, uint32 mode)
655 FileMap* map = (FileMap*)_map;
656 if (map == NULL)
657 return B_BAD_VALUE;
659 return map->SetMode(mode);
663 extern "C" status_t
664 file_map_translate(void* _map, off_t offset, size_t size, file_io_vec* vecs,
665 size_t* _count, size_t align)
667 TRACE("file_map_translate(map %p, offset %Ld, size %ld)\n",
668 _map, offset, size);
670 FileMap* map = (FileMap*)_map;
671 if (map == NULL)
672 return B_BAD_VALUE;
674 return map->Translate(offset, size, vecs, _count, align);