xref: /haiku/src/tools/fs_shell/file_cache.cpp (revision 83b1a68c52ba3e0e8796282759f694b7fdddf06d)
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