2 * Copyright 2004-2012, Axel Dörfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
13 # include "fssh_api_wrapper.h"
15 using namespace FSShell
;
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>
29 # include <vm/vm_page.h>
30 # include <vm/VMCache.h>
32 # include "kernel_debug_config.h"
36 //#define TRACE_FILE_MAP
38 # define TRACE(x...) dprintf_no_syslog(x)
40 # define TRACE(x...) ;
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
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
58 struct file_extent_array
{
65 : public DoublyLinkedListLinkImpl
<FileMap
>
69 FileMap(struct vnode
* vnode
, off_t size
);
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
,
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
);
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
,
92 status_t
_Cache(off_t offset
, off_t size
);
93 void _InvalidateAfter(off_t offset
);
97 file_extent fDirect
[CACHED_FILE_EXTENTS
];
98 file_extent_array fIndirect
;
102 struct vnode
* fVnode
;
108 typedef DoublyLinkedList
<FileMap
> FileMapList
;
110 static FileMapList sList
;
115 FileMap::FileMap(struct vnode
* vnode
, off_t size
)
122 mutex_init(&fLock
, "file map");
125 MutexLocker
_(sLock
);
134 mutex_destroy(&fLock
);
137 MutexLocker
_(sLock
);
144 FileMap::ExtentAt(uint32 index
)
149 if (fCount
> CACHED_FILE_EXTENTS
)
150 return &fIndirect
.array
[index
];
152 return &fDirect
[index
];
157 FileMap::_FindExtent(off_t offset
, uint32
*_index
)
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
169 } else if (extent
->offset
+ extent
->disk
.length
<= offset
) {
170 // search in right part
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
);
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)
214 file_extent
* newArray
= (file_extent
*)realloc(oldArray
,
215 maxCount
* sizeof(file_extent
));
216 if (newArray
== NULL
)
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
;
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
;
240 status_t status
= _MakeSpace(fCount
+ vecCount
);
244 file_extent
* lastExtent
= NULL
;
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
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);
270 file_extent
* extent
= ExtentAt(start
++);
271 extent
->offset
= offset
;
272 extent
->disk
= vecs
[i
];
274 offset
+= extent
->disk
.length
;
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
);
292 FileMap::_InvalidateAfter(off_t offset
)
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)
305 _MakeSpace(resizeTo
);
310 /*! Invalidates or removes the specified part of the file map.
313 FileMap::Invalidate(off_t offset
, off_t size
)
315 MutexLocker
_(fLock
);
317 // TODO: honour size, we currently always remove everything after "offset"
323 _InvalidateAfter(offset
);
328 FileMap::SetSize(off_t size
)
330 MutexLocker
_(fLock
);
333 _InvalidateAfter(size
);
342 if (fCount
> CACHED_FILE_EXTENTS
)
343 free(fIndirect
.array
);
350 FileMap::_Cache(off_t offset
, off_t size
)
352 file_extent
* lastExtent
= NULL
;
354 lastExtent
= ExtentAt(fCount
- 1);
357 if (lastExtent
!= NULL
)
358 mapEnd
= lastExtent
->offset
+ lastExtent
->disk
.length
;
360 off_t end
= offset
+ size
;
362 if (fCacheAll
&& mapEnd
< end
)
365 status_t status
= B_OK
;
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
);
382 FileMap::SetMode(uint32 mode
)
384 if (mode
!= FILE_MAP_CACHE_ALL
&& mode
!= FILE_MAP_CACHE_ON_DEMAND
)
387 MutexLocker
_(fLock
);
389 if ((mode
== FILE_MAP_CACHE_ALL
&& fCacheAll
)
390 || (mode
== FILE_MAP_CACHE_ON_DEMAND
&& !fCacheAll
))
393 if (mode
== FILE_MAP_CACHE_ALL
) {
394 status_t status
= _Cache(0, fSize
);
407 FileMap::Translate(off_t offset
, size_t size
, file_io_vec
* vecs
, size_t* _count
,
413 MutexLocker
_(fLock
);
415 size_t maxVecs
= *_count
;
416 size_t padLastVec
= 0;
418 if (offset
>= Size()) {
422 if ((off_t
)(offset
+ size
) > fSize
) {
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
);
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.
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
;
449 vecs
[0].length
= fileExtent
->disk
.length
- offset
;
451 if (vecs
[0].length
>= (off_t
)size
) {
452 vecs
[0].length
= size
+ padLastVec
;
457 // copy the rest of the vecs
459 size
-= vecs
[0].length
;
465 vecs
[vecIndex
++] = fileExtent
->disk
;
467 if ((off_t
)size
<= fileExtent
->disk
.length
) {
468 vecs
[vecIndex
- 1].length
= size
+ padLastVec
;
472 if (vecIndex
>= maxVecs
) {
474 return B_BUFFER_OVERFLOW
;
477 size
-= fileExtent
->disk
.length
;
491 dump_file_map(int argc
, char** argv
)
494 print_debugger_command_usage(argv
[0]);
498 bool printExtents
= false;
499 if (argc
> 2 && !strcmp(argv
[1], "-p"))
502 FileMap
* map
= (FileMap
*)parse_expression(argv
[argc
- 1]);
504 kprintf("invalid file map!\n");
508 kprintf("FileMap %p\n", map
);
509 kprintf(" size %" B_PRIdOFF
"\n", map
->Size());
510 kprintf(" count %lu\n", map
->Count());
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
);
528 dump_file_map_stats(int argc
, char** argv
)
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();
545 uint32 emptyCount
= 0;
547 while (iterator
.HasNext()) {
548 FileMap
* map
= iterator
.Next();
550 if (minSize
> map
->Size() || (maxSize
!= -1 && maxSize
< map
->Size()))
553 if (map
->Count() != 0) {
554 file_extent
* extent
= map
->ExtentAt(map
->Count() - 1);
556 mapSize
+= extent
->offset
+ extent
->disk
.length
;
558 extents
+= map
->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
));
575 #endif // DEBUG_FILE_MAP
578 // #pragma mark - private kernel API
585 add_debugger_command_etc("file_map", &dump_file_map
,
586 "Dumps the specified file map.",
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");
599 // #pragma mark - public FS API
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)
611 if (vfs_lookup_vnode(mountID
, vnodeID
, &vnode
) != B_OK
)
614 return new(std::nothrow
) FileMap(vnode
, size
);
619 file_map_delete(void* _map
)
621 FileMap
* map
= (FileMap
*)_map
;
625 TRACE("file_map_delete(map = %p)\n", map
);
631 file_map_set_size(void* _map
, off_t size
)
633 FileMap
* map
= (FileMap
*)_map
;
642 file_map_invalidate(void* _map
, off_t offset
, off_t size
)
644 FileMap
* map
= (FileMap
*)_map
;
648 map
->Invalidate(offset
, size
);
653 file_map_set_mode(void* _map
, uint32 mode
)
655 FileMap
* map
= (FileMap
*)_map
;
659 return map
->SetMode(mode
);
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",
670 FileMap
* map
= (FileMap
*)_map
;
674 return map
->Translate(offset
, size
, vecs
, _count
, align
);