1 /* 2 * Copyright 2004-2008, Haiku Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Axel Dörfler <axeld@pinc-software.de> 7 * Ingo Weinhold <bonefish@cs.tu-berlin.de> 8 */ 9 10 #include "fssh_fs_cache.h" 11 12 #include <new> 13 14 #include <stdlib.h> 15 16 #include "DoublyLinkedList.h" 17 #include "fssh_kernel_export.h" 18 #include "fssh_lock.h" 19 #include "fssh_stdio.h" 20 #include "fssh_string.h" 21 #include "fssh_uio.h" 22 #include "fssh_unistd.h" 23 #include "hash.h" 24 #include "vfs.h" 25 26 27 #undef TRACE 28 //#define TRACE_FILE_CACHE 29 #ifdef TRACE_FILE_CACHE 30 # define TRACE(x) fssh_dprintf x 31 #else 32 # define TRACE(x) ; 33 #endif 34 35 using std::nothrow; 36 37 38 // This is a hacked version of the kernel file cache implementation. The main 39 // part of the implementation didn't change that much -- some code not needed 40 // in userland has been removed, most notably everything on the page level. 41 // On the downside, the cache now never caches anything, but will always 42 // directly work on the underlying device. Since that is usually cached by the 43 // host operating system, it shouldn't hurt much, though. 44 45 // maximum number of iovecs per request 46 #define MAX_IO_VECS 64 // 256 kB 47 #define MAX_FILE_IO_VECS 32 48 #define MAX_TEMP_IO_VECS 8 49 50 #define user_memcpy(a, b, c) fssh_memcpy(a, b, c) 51 52 #define PAGE_ALIGN(x) (((x) + (FSSH_B_PAGE_SIZE - 1)) & ~(FSSH_B_PAGE_SIZE - 1)) 53 #define ASSERT(x) 54 55 namespace FSShell { 56 57 struct file_cache_ref; 58 59 typedef fssh_status_t (*cache_func)(file_cache_ref *ref, void *cookie, 60 fssh_off_t offset, int32_t pageOffset, fssh_addr_t buffer, 61 fssh_size_t bufferSize); 62 63 struct file_cache_ref { 64 fssh_mutex lock; 65 fssh_mount_id mountID; 66 fssh_vnode_id nodeID; 67 struct vnode* node; 68 fssh_off_t virtual_size; 69 }; 70 71 72 fssh_status_t 73 file_cache_init() 74 { 75 return FSSH_B_OK; 76 } 77 78 79 // #pragma mark - 80 81 82 static fssh_status_t 83 read_from_file(file_cache_ref *ref, void *cookie, fssh_off_t offset, 84 int32_t pageOffset, fssh_addr_t buffer, fssh_size_t bufferSize) 85 { 86 fssh_iovec vec; 87 vec.iov_base = (void *)buffer; 88 vec.iov_len = bufferSize; 89 90 fssh_mutex_unlock(&ref->lock); 91 92 fssh_status_t status = vfs_read_pages(ref->node, cookie, 93 offset + pageOffset, &vec, 1, &bufferSize); 94 95 fssh_mutex_lock(&ref->lock); 96 97 return status; 98 } 99 100 101 static fssh_status_t 102 write_to_file(file_cache_ref *ref, void *cookie, fssh_off_t offset, 103 int32_t pageOffset, fssh_addr_t buffer, fssh_size_t bufferSize) 104 { 105 fssh_iovec vec; 106 vec.iov_base = (void *)buffer; 107 vec.iov_len = bufferSize; 108 109 fssh_mutex_unlock(&ref->lock); 110 111 fssh_status_t status = vfs_write_pages(ref->node, cookie, 112 offset + pageOffset, &vec, 1, &bufferSize); 113 114 fssh_mutex_lock(&ref->lock); 115 116 return status; 117 } 118 119 120 static inline fssh_status_t 121 satisfy_cache_io(file_cache_ref *ref, void *cookie, cache_func function, 122 fssh_off_t offset, fssh_addr_t buffer, int32_t &pageOffset, 123 fssh_size_t bytesLeft, fssh_off_t &lastOffset, 124 fssh_addr_t &lastBuffer, int32_t &lastPageOffset, fssh_size_t &lastLeft) 125 { 126 if (lastBuffer == buffer) 127 return FSSH_B_OK; 128 129 fssh_size_t requestSize = buffer - lastBuffer; 130 131 fssh_status_t status = function(ref, cookie, lastOffset, lastPageOffset, 132 lastBuffer, requestSize); 133 if (status == FSSH_B_OK) { 134 lastBuffer = buffer; 135 lastLeft = bytesLeft; 136 lastOffset = offset; 137 lastPageOffset = 0; 138 pageOffset = 0; 139 } 140 return status; 141 } 142 143 144 static fssh_status_t 145 cache_io(void *_cacheRef, void *cookie, fssh_off_t offset, fssh_addr_t buffer, 146 fssh_size_t *_size, bool doWrite) 147 { 148 if (_cacheRef == NULL) 149 fssh_panic("cache_io() called with NULL ref!\n"); 150 151 file_cache_ref *ref = (file_cache_ref *)_cacheRef; 152 fssh_off_t fileSize = ref->virtual_size; 153 154 TRACE(("cache_io(ref = %p, offset = %Ld, buffer = %p, size = %u, %s)\n", 155 ref, offset, (void *)buffer, *_size, doWrite ? "write" : "read")); 156 157 // out of bounds access? 158 if (offset >= fileSize || offset < 0) { 159 *_size = 0; 160 return FSSH_B_OK; 161 } 162 163 int32_t pageOffset = offset & (FSSH_B_PAGE_SIZE - 1); 164 fssh_size_t size = *_size; 165 offset -= pageOffset; 166 167 if ((uint64_t)offset + pageOffset + size > (uint64_t)fileSize) { 168 // adapt size to be within the file's offsets 169 size = fileSize - pageOffset - offset; 170 *_size = size; 171 } 172 if (size == 0) 173 return FSSH_B_OK; 174 175 cache_func function; 176 if (doWrite) { 177 // in low memory situations, we bypass the cache beyond a 178 // certain I/O size 179 function = write_to_file; 180 } else { 181 function = read_from_file; 182 } 183 184 // "offset" and "lastOffset" are always aligned to B_PAGE_SIZE, 185 // the "last*" variables always point to the end of the last 186 // satisfied request part 187 188 const uint32_t kMaxChunkSize = MAX_IO_VECS * FSSH_B_PAGE_SIZE; 189 fssh_size_t bytesLeft = size, lastLeft = size; 190 int32_t lastPageOffset = pageOffset; 191 fssh_addr_t lastBuffer = buffer; 192 fssh_off_t lastOffset = offset; 193 194 MutexLocker locker(ref->lock); 195 196 while (bytesLeft > 0) { 197 // check if this page is already in memory 198 fssh_size_t bytesInPage = fssh_min_c( 199 fssh_size_t(FSSH_B_PAGE_SIZE - pageOffset), bytesLeft); 200 201 if (bytesLeft <= bytesInPage) 202 break; 203 204 buffer += bytesInPage; 205 bytesLeft -= bytesInPage; 206 pageOffset = 0; 207 offset += FSSH_B_PAGE_SIZE; 208 209 if (buffer - lastBuffer + lastPageOffset >= kMaxChunkSize) { 210 fssh_status_t status = satisfy_cache_io(ref, cookie, function, 211 offset, buffer, pageOffset, bytesLeft, lastOffset, 212 lastBuffer, lastPageOffset, lastLeft); 213 if (status != FSSH_B_OK) 214 return status; 215 } 216 } 217 218 // fill the last remaining bytes of the request (either write or read) 219 220 return function(ref, cookie, lastOffset, lastPageOffset, lastBuffer, 221 lastLeft); 222 } 223 224 225 } // namespace FSShell 226 227 228 // #pragma mark - public FS API 229 230 231 using namespace FSShell; 232 233 234 void * 235 fssh_file_cache_create(fssh_mount_id mountID, fssh_vnode_id vnodeID, 236 fssh_off_t size) 237 { 238 TRACE(("file_cache_create(mountID = %d, vnodeID = %Ld, size = %Ld)\n", 239 mountID, vnodeID, size)); 240 241 file_cache_ref *ref = new(nothrow) file_cache_ref; 242 if (ref == NULL) 243 return NULL; 244 245 ref->mountID = mountID; 246 ref->nodeID = vnodeID; 247 ref->virtual_size = size; 248 249 // get vnode 250 fssh_status_t error = vfs_lookup_vnode(mountID, vnodeID, &ref->node); 251 if (error != FSSH_B_OK) { 252 fssh_dprintf("file_cache_create(): Failed get vnode %d:%" FSSH_B_PRIdINO 253 ": %s\n", mountID, vnodeID, fssh_strerror(error)); 254 delete ref; 255 return NULL; 256 } 257 258 // create lock 259 char buffer[32]; 260 fssh_snprintf(buffer, sizeof(buffer), "file cache %d:%" FSSH_B_PRIdINO, 261 (int)mountID, vnodeID); 262 fssh_mutex_init(&ref->lock, buffer); 263 264 return ref; 265 } 266 267 268 void 269 fssh_file_cache_delete(void *_cacheRef) 270 { 271 file_cache_ref *ref = (file_cache_ref *)_cacheRef; 272 273 if (ref == NULL) 274 return; 275 276 TRACE(("file_cache_delete(ref = %p)\n", ref)); 277 278 fssh_mutex_lock(&ref->lock); 279 fssh_mutex_destroy(&ref->lock); 280 281 delete ref; 282 } 283 284 285 void 286 fssh_file_cache_enable(void *_cacheRef) 287 { 288 fssh_panic("fssh_file_cache_enable() called"); 289 } 290 291 292 fssh_status_t 293 fssh_file_cache_disable(void *_cacheRef) 294 { 295 fssh_panic("fssh_file_cache_disable() called"); 296 return FSSH_B_ERROR; 297 } 298 299 300 bool 301 fssh_file_cache_is_enabled(void *_cacheRef) 302 { 303 return true; 304 } 305 306 307 fssh_status_t 308 fssh_file_cache_set_size(void *_cacheRef, fssh_off_t size) 309 { 310 file_cache_ref *ref = (file_cache_ref *)_cacheRef; 311 312 TRACE(("file_cache_set_size(ref = %p, size = %Ld)\n", ref, size)); 313 314 if (ref == NULL) 315 return FSSH_B_OK; 316 317 fssh_mutex_lock(&ref->lock); 318 ref->virtual_size = size; 319 fssh_mutex_unlock(&ref->lock); 320 321 return FSSH_B_OK; 322 } 323 324 325 fssh_status_t 326 fssh_file_cache_sync(void *_cacheRef) 327 { 328 file_cache_ref *ref = (file_cache_ref *)_cacheRef; 329 if (ref == NULL) 330 return FSSH_B_BAD_VALUE; 331 332 return FSSH_B_OK; 333 } 334 335 336 fssh_status_t 337 fssh_file_cache_read(void *_cacheRef, void *cookie, fssh_off_t offset, 338 void *bufferBase, fssh_size_t *_size) 339 { 340 file_cache_ref *ref = (file_cache_ref *)_cacheRef; 341 342 TRACE(("file_cache_read(ref = %p, offset = %Ld, buffer = %p, size = %u)\n", 343 ref, offset, bufferBase, *_size)); 344 345 return cache_io(ref, cookie, offset, (fssh_addr_t)bufferBase, _size, false); 346 } 347 348 349 fssh_status_t 350 fssh_file_cache_write(void *_cacheRef, void *cookie, fssh_off_t offset, 351 const void *buffer, fssh_size_t *_size) 352 { 353 file_cache_ref *ref = (file_cache_ref *)_cacheRef; 354 355 fssh_status_t status = cache_io(ref, cookie, offset, 356 (fssh_addr_t)const_cast<void *>(buffer), _size, true); 357 TRACE(("file_cache_write(ref = %p, offset = %Ld, buffer = %p, size = %u) = %d\n", 358 ref, offset, buffer, *_size, status)); 359 360 return status; 361 } 362 363