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
35using 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
55namespace FSShell {
56
57struct file_cache_ref;
58
59typedef 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
63struct 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
72fssh_status_t
73file_cache_init()
74{
75	return FSSH_B_OK;
76}
77
78
79//	#pragma mark -
80
81
82static fssh_status_t
83read_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
101static fssh_status_t
102write_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
120static inline fssh_status_t
121satisfy_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
144static fssh_status_t
145cache_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
231using namespace FSShell;
232
233
234void *
235fssh_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
268void
269fssh_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
285void
286fssh_file_cache_enable(void *_cacheRef)
287{
288	fssh_panic("fssh_file_cache_enable() called");
289}
290
291
292fssh_status_t
293fssh_file_cache_disable(void *_cacheRef)
294{
295	fssh_panic("fssh_file_cache_disable() called");
296	return FSSH_B_ERROR;
297}
298
299
300bool
301fssh_file_cache_is_enabled(void *_cacheRef)
302{
303	return true;
304}
305
306
307fssh_status_t
308fssh_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
325fssh_status_t
326fssh_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
336fssh_status_t
337fssh_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
349fssh_status_t
350fssh_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