1 /*
2   Caching file system proxy
3   Copyright (C) 2004  Miklos Szeredi <miklos@szeredi.hu>
4 
5   This program can be distributed under the terms of the GNU GPL.
6   See the file COPYING.
7 */
8 
9 #include "cache.h"
10 #include <stdio.h>
11 #include <assert.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <errno.h>
15 #include <glib.h>
16 #include <pthread.h>
17 
18 #define DEFAULT_CACHE_TIMEOUT_SECS 20
19 #define DEFAULT_MAX_CACHE_SIZE 10000
20 #define DEFAULT_CACHE_CLEAN_INTERVAL_SECS 60
21 #define DEFAULT_MIN_CACHE_CLEAN_INTERVAL_SECS 5
22 
23 struct cache {
24 	int on;
25 	unsigned int stat_timeout_secs;
26 	unsigned int dir_timeout_secs;
27 	unsigned int link_timeout_secs;
28 	unsigned int max_size;
29 	unsigned int clean_interval_secs;
30 	unsigned int min_clean_interval_secs;
31 	struct fuse_operations *next_oper;
32 	GHashTable *table;
33 	pthread_mutex_t lock;
34 	time_t last_cleaned;
35 	uint64_t write_ctr;
36 };
37 
38 static struct cache cache;
39 
40 struct node {
41 	struct stat stat;
42 	time_t stat_valid;
43 	char **dir;
44 	time_t dir_valid;
45 	char *link;
46 	time_t link_valid;
47 	time_t valid;
48 };
49 
50 struct readdir_handle {
51 	const char *path;
52 	void *buf;
53 	fuse_fill_dir_t filler;
54 	GPtrArray *dir;
55 	uint64_t wrctr;
56 };
57 
58 struct file_handle {
59 	/* Did we send an open request to the underlying fs? */
60 	int is_open;
61 
62 	/* If so, this will hold its handle */
63 	unsigned long fs_fh;
64 };
65 
free_node(gpointer node_)66 static void free_node(gpointer node_)
67 {
68 	struct node *node = (struct node *) node_;
69 	g_strfreev(node->dir);
70 	g_free(node);
71 }
72 
cache_clean_entry(void * key_,struct node * node,time_t * now)73 static int cache_clean_entry(void *key_, struct node *node, time_t *now)
74 {
75 	(void) key_;
76 	if (*now > node->valid)
77 		return TRUE;
78 	else
79 		return FALSE;
80 }
81 
cache_clean(void)82 static void cache_clean(void)
83 {
84 	time_t now = time(NULL);
85 	if (now > cache.last_cleaned + cache.min_clean_interval_secs &&
86 	    (g_hash_table_size(cache.table) > cache.max_size ||
87 	     now > cache.last_cleaned + cache.clean_interval_secs)) {
88 		g_hash_table_foreach_remove(cache.table,
89 					    (GHRFunc) cache_clean_entry, &now);
90 		cache.last_cleaned = now;
91 	}
92 }
93 
cache_lookup(const char * path)94 static struct node *cache_lookup(const char *path)
95 {
96 	return (struct node *) g_hash_table_lookup(cache.table, path);
97 }
98 
cache_purge(const char * path)99 static void cache_purge(const char *path)
100 {
101 	g_hash_table_remove(cache.table, path);
102 }
103 
cache_purge_parent(const char * path)104 static void cache_purge_parent(const char *path)
105 {
106 	const char *s = strrchr(path, '/');
107 	if (s) {
108 		if (s == path)
109 			g_hash_table_remove(cache.table, "/");
110 		else {
111 			char *parent = g_strndup(path, s - path);
112 			cache_purge(parent);
113 			g_free(parent);
114 		}
115 	}
116 }
117 
cache_invalidate(const char * path)118 void cache_invalidate(const char *path)
119 {
120 	pthread_mutex_lock(&cache.lock);
121 	cache_purge(path);
122 	pthread_mutex_unlock(&cache.lock);
123 }
124 
cache_invalidate_write(const char * path)125 static void cache_invalidate_write(const char *path)
126 {
127 	pthread_mutex_lock(&cache.lock);
128 	cache_purge(path);
129 	cache.write_ctr++;
130 	pthread_mutex_unlock(&cache.lock);
131 }
132 
cache_invalidate_dir(const char * path)133 static void cache_invalidate_dir(const char *path)
134 {
135 	pthread_mutex_lock(&cache.lock);
136 	cache_purge(path);
137 	cache_purge_parent(path);
138 	pthread_mutex_unlock(&cache.lock);
139 }
140 
cache_del_children(const char * key,void * val_,const char * path)141 static int cache_del_children(const char *key, void *val_, const char *path)
142 {
143 	(void) val_;
144 	if (strncmp(key, path, strlen(path)) == 0)
145 		return TRUE;
146 	else
147 		return FALSE;
148 }
149 
cache_do_rename(const char * from,const char * to)150 static void cache_do_rename(const char *from, const char *to)
151 {
152 	pthread_mutex_lock(&cache.lock);
153 	g_hash_table_foreach_remove(cache.table, (GHRFunc) cache_del_children,
154 				    (char *) from);
155 	cache_purge(from);
156 	cache_purge(to);
157 	cache_purge_parent(from);
158 	cache_purge_parent(to);
159 	pthread_mutex_unlock(&cache.lock);
160 }
161 
cache_get(const char * path)162 static struct node *cache_get(const char *path)
163 {
164 	struct node *node = cache_lookup(path);
165 	if (node == NULL) {
166 		char *pathcopy = g_strdup(path);
167 		node = g_new0(struct node, 1);
168 		g_hash_table_insert(cache.table, pathcopy, node);
169 	}
170 	return node;
171 }
172 
cache_add_attr(const char * path,const struct stat * stbuf,uint64_t wrctr)173 void cache_add_attr(const char *path, const struct stat *stbuf, uint64_t wrctr)
174 {
175 	struct node *node;
176 
177 	pthread_mutex_lock(&cache.lock);
178 	if (wrctr == cache.write_ctr) {
179 		node = cache_get(path);
180 		node->stat = *stbuf;
181 		node->stat_valid = time(NULL) + cache.stat_timeout_secs;
182 		if (node->stat_valid > node->valid)
183 			node->valid = node->stat_valid;
184 		cache_clean();
185 	}
186 	pthread_mutex_unlock(&cache.lock);
187 }
188 
cache_add_dir(const char * path,char ** dir)189 static void cache_add_dir(const char *path, char **dir)
190 {
191 	struct node *node;
192 
193 	pthread_mutex_lock(&cache.lock);
194 	node = cache_get(path);
195 	g_strfreev(node->dir);
196 	node->dir = dir;
197 	node->dir_valid = time(NULL) + cache.dir_timeout_secs;
198 	if (node->dir_valid > node->valid)
199 		node->valid = node->dir_valid;
200 	cache_clean();
201 	pthread_mutex_unlock(&cache.lock);
202 }
203 
my_strnlen(const char * s,size_t maxsize)204 static size_t my_strnlen(const char *s, size_t maxsize)
205 {
206 	const char *p;
207 	for (p = s; maxsize && *p; maxsize--, p++);
208 	return p - s;
209 }
210 
cache_add_link(const char * path,const char * link,size_t size)211 static void cache_add_link(const char *path, const char *link, size_t size)
212 {
213 	struct node *node;
214 
215 	pthread_mutex_lock(&cache.lock);
216 	node = cache_get(path);
217 	g_free(node->link);
218 	node->link = g_strndup(link, my_strnlen(link, size-1));
219 	node->link_valid = time(NULL) + cache.link_timeout_secs;
220 	if (node->link_valid > node->valid)
221 		node->valid = node->link_valid;
222 	cache_clean();
223 	pthread_mutex_unlock(&cache.lock);
224 }
225 
cache_get_attr(const char * path,struct stat * stbuf)226 static int cache_get_attr(const char *path, struct stat *stbuf)
227 {
228 	struct node *node;
229 	int err = -EAGAIN;
230 	pthread_mutex_lock(&cache.lock);
231 	node = cache_lookup(path);
232 	if (node != NULL) {
233 		time_t now = time(NULL);
234 		if (node->stat_valid - now >= 0) {
235 			*stbuf = node->stat;
236 			err = 0;
237 		}
238 	}
239 	pthread_mutex_unlock(&cache.lock);
240 	return err;
241 }
242 
cache_get_write_ctr(void)243 uint64_t cache_get_write_ctr(void)
244 {
245 	uint64_t res;
246 
247 	pthread_mutex_lock(&cache.lock);
248 	res = cache.write_ctr;
249 	pthread_mutex_unlock(&cache.lock);
250 
251 	return res;
252 }
253 
cache_init(struct fuse_conn_info * conn,struct fuse_config * cfg)254 static void *cache_init(struct fuse_conn_info *conn,
255                         struct fuse_config *cfg)
256 {
257 	void *res;
258 	res = cache.next_oper->init(conn, cfg);
259 
260 	// Cache requires a path for each request
261 	cfg->nullpath_ok = 0;
262 
263 	return res;
264 }
265 
cache_getattr(const char * path,struct stat * stbuf,struct fuse_file_info * fi)266 static int cache_getattr(const char *path, struct stat *stbuf,
267 			 struct fuse_file_info *fi)
268 {
269 	int err = cache_get_attr(path, stbuf);
270 	if (err) {
271 		uint64_t wrctr = cache_get_write_ctr();
272 		err = cache.next_oper->getattr(path, stbuf, fi);
273 		if (!err)
274 			cache_add_attr(path, stbuf, wrctr);
275 	}
276 	return err;
277 }
278 
cache_readlink(const char * path,char * buf,size_t size)279 static int cache_readlink(const char *path, char *buf, size_t size)
280 {
281 	struct node *node;
282 	int err;
283 
284 	pthread_mutex_lock(&cache.lock);
285 	node = cache_lookup(path);
286 	if (node != NULL) {
287 		time_t now = time(NULL);
288 		if (node->link_valid - now >= 0) {
289 			strncpy(buf, node->link, size-1);
290 			buf[size-1] = '\0';
291 			pthread_mutex_unlock(&cache.lock);
292 			return 0;
293 		}
294 	}
295 	pthread_mutex_unlock(&cache.lock);
296 	err = cache.next_oper->readlink(path, buf, size);
297 	if (!err)
298 		cache_add_link(path, buf, size);
299 
300 	return err;
301 }
302 
303 
cache_opendir(const char * path,struct fuse_file_info * fi)304 static int cache_opendir(const char *path, struct fuse_file_info *fi)
305 {
306 	(void) path;
307 	struct file_handle *cfi;
308 
309 	cfi = malloc(sizeof(struct file_handle));
310 	if(cfi == NULL)
311 		return -ENOMEM;
312 	cfi->is_open = 0;
313 	fi->fh = (unsigned long) cfi;
314 	return 0;
315 }
316 
cache_releasedir(const char * path,struct fuse_file_info * fi)317 static int cache_releasedir(const char *path, struct fuse_file_info *fi)
318 {
319 	int err;
320 	struct file_handle *cfi;
321 
322 	cfi = (struct file_handle*) fi->fh;
323 
324 	if(cfi->is_open) {
325 		fi->fh = cfi->fs_fh;
326 		err = cache.next_oper->releasedir(path, fi);
327 	} else
328 		err = 0;
329 
330 	free(cfi);
331 	return err;
332 }
333 
cache_dirfill(void * buf,const char * name,const struct stat * stbuf,off_t off,enum fuse_fill_dir_flags flags)334 static int cache_dirfill (void *buf, const char *name,
335 			  const struct stat *stbuf, off_t off,
336 			  enum fuse_fill_dir_flags flags)
337 {
338 	int err;
339 	struct readdir_handle *ch;
340 
341 	ch = (struct readdir_handle*) buf;
342 	err = ch->filler(ch->buf, name, stbuf, off, flags);
343 	if (!err) {
344 		g_ptr_array_add(ch->dir, g_strdup(name));
345 		if (stbuf->st_mode & S_IFMT) {
346 			char *fullpath;
347 			const char *basepath = !ch->path[1] ? "" : ch->path;
348 
349 			fullpath = g_strdup_printf("%s/%s", basepath, name);
350 			cache_add_attr(fullpath, stbuf, ch->wrctr);
351 			g_free(fullpath);
352 		}
353 	}
354 	return err;
355 }
356 
cache_readdir(const char * path,void * buf,fuse_fill_dir_t filler,off_t offset,struct fuse_file_info * fi,enum fuse_readdir_flags flags)357 static int cache_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
358 			 off_t offset, struct fuse_file_info *fi,
359 			 enum fuse_readdir_flags flags)
360 {
361 	struct readdir_handle ch;
362 	struct file_handle *cfi;
363 	int err;
364 	char **dir;
365 	struct node *node;
366 
367 	assert(offset == 0);
368 
369 	pthread_mutex_lock(&cache.lock);
370 	node = cache_lookup(path);
371 	if (node != NULL && node->dir != NULL) {
372 		time_t now = time(NULL);
373 		if (node->dir_valid - now >= 0) {
374 			for(dir = node->dir; *dir != NULL; dir++)
375 				// FIXME: What about st_mode?
376 				filler(buf, *dir, NULL, 0, 0);
377 			pthread_mutex_unlock(&cache.lock);
378 			return 0;
379 		}
380 	}
381 	pthread_mutex_unlock(&cache.lock);
382 
383 	cfi = (struct file_handle*) fi->fh;
384 	if(cfi->is_open)
385 		fi->fh = cfi->fs_fh;
386 	else {
387 		if(cache.next_oper->opendir) {
388 			err = cache.next_oper->opendir(path, fi);
389 			if(err)
390 				return err;
391 		}
392 		cfi->is_open = 1;
393 		cfi->fs_fh = fi->fh;
394 	}
395 
396 	ch.path = path;
397 	ch.buf = buf;
398 	ch.filler = filler;
399 	ch.dir = g_ptr_array_new();
400 	ch.wrctr = cache_get_write_ctr();
401 	err = cache.next_oper->readdir(path, &ch, cache_dirfill, offset, fi, flags);
402 	g_ptr_array_add(ch.dir, NULL);
403 	dir = (char **) ch.dir->pdata;
404 	if (!err) {
405 		cache_add_dir(path, dir);
406 	} else {
407 		g_strfreev(dir);
408 	}
409 	g_ptr_array_free(ch.dir, FALSE);
410 
411 	return err;
412 }
413 
cache_mknod(const char * path,mode_t mode,dev_t rdev)414 static int cache_mknod(const char *path, mode_t mode, dev_t rdev)
415 {
416 	int err = cache.next_oper->mknod(path, mode, rdev);
417 	if (!err)
418 		cache_invalidate_dir(path);
419 	return err;
420 }
421 
cache_mkdir(const char * path,mode_t mode)422 static int cache_mkdir(const char *path, mode_t mode)
423 {
424 	int err = cache.next_oper->mkdir(path, mode);
425 	if (!err)
426 		cache_invalidate_dir(path);
427 	return err;
428 }
429 
cache_unlink(const char * path)430 static int cache_unlink(const char *path)
431 {
432 	int err = cache.next_oper->unlink(path);
433 	if (!err)
434 		cache_invalidate_dir(path);
435 	return err;
436 }
437 
cache_rmdir(const char * path)438 static int cache_rmdir(const char *path)
439 {
440 	int err = cache.next_oper->rmdir(path);
441 	if (!err)
442 		cache_invalidate_dir(path);
443 	return err;
444 }
445 
cache_symlink(const char * from,const char * to)446 static int cache_symlink(const char *from, const char *to)
447 {
448 	int err = cache.next_oper->symlink(from, to);
449 	if (!err)
450 		cache_invalidate_dir(to);
451 	return err;
452 }
453 
cache_rename(const char * from,const char * to,unsigned int flags)454 static int cache_rename(const char *from, const char *to, unsigned int flags)
455 {
456 	int err = cache.next_oper->rename(from, to, flags);
457 	if (!err)
458 		cache_do_rename(from, to);
459 	return err;
460 }
461 
cache_link(const char * from,const char * to)462 static int cache_link(const char *from, const char *to)
463 {
464 	int err = cache.next_oper->link(from, to);
465 	if (!err) {
466 		cache_invalidate(from);
467 		cache_invalidate_dir(to);
468 	}
469 	return err;
470 }
471 
cache_chmod(const char * path,mode_t mode,struct fuse_file_info * fi)472 static int cache_chmod(const char *path, mode_t mode,
473                        struct fuse_file_info *fi)
474 {
475 	int err = cache.next_oper->chmod(path, mode, fi);
476 	if (!err)
477 		cache_invalidate(path);
478 	return err;
479 }
480 
cache_chown(const char * path,uid_t uid,gid_t gid,struct fuse_file_info * fi)481 static int cache_chown(const char *path, uid_t uid, gid_t gid,
482                        struct fuse_file_info *fi)
483 {
484 	int err = cache.next_oper->chown(path, uid, gid, fi);
485 	if (!err)
486 		cache_invalidate(path);
487 	return err;
488 }
489 
cache_utimens(const char * path,const struct timespec tv[2],struct fuse_file_info * fi)490 static int cache_utimens(const char *path, const struct timespec tv[2],
491 			 struct fuse_file_info *fi)
492 {
493 	int err = cache.next_oper->utimens(path, tv, fi);
494 	if (!err)
495 		cache_invalidate(path);
496 	return err;
497 }
498 
cache_write(const char * path,const char * buf,size_t size,off_t offset,struct fuse_file_info * fi)499 static int cache_write(const char *path, const char *buf, size_t size,
500                        off_t offset, struct fuse_file_info *fi)
501 {
502 	int res = cache.next_oper->write(path, buf, size, offset, fi);
503 	if (res >= 0)
504 		cache_invalidate_write(path);
505 	return res;
506 }
507 
cache_create(const char * path,mode_t mode,struct fuse_file_info * fi)508 static int cache_create(const char *path, mode_t mode,
509                         struct fuse_file_info *fi)
510 {
511 	int err = cache.next_oper->create(path, mode, fi);
512 	if (!err)
513 		cache_invalidate_dir(path);
514 	return err;
515 }
516 
cache_truncate(const char * path,off_t size,struct fuse_file_info * fi)517 static int cache_truncate(const char *path, off_t size,
518 			  struct fuse_file_info *fi)
519 {
520 	int err = cache.next_oper->truncate(path, size, fi);
521 	if (!err)
522 		cache_invalidate(path);
523 	return err;
524 }
525 
cache_fill(struct fuse_operations * oper,struct fuse_operations * cache_oper)526 static void cache_fill(struct fuse_operations *oper,
527 		       struct fuse_operations *cache_oper)
528 {
529 	cache_oper->access   = oper->access;
530 	cache_oper->chmod    = oper->chmod ? cache_chmod : NULL;
531 	cache_oper->chown    = oper->chown ? cache_chown : NULL;
532 	cache_oper->create   = oper->create ? cache_create : NULL;
533 	cache_oper->flush    = oper->flush;
534 	cache_oper->fsync    = oper->fsync;
535 	cache_oper->getattr  = oper->getattr ? cache_getattr : NULL;
536 	cache_oper->getxattr = oper->getxattr;
537 	cache_oper->init     = cache_init;
538 	cache_oper->link     = oper->link ? cache_link : NULL;
539 	cache_oper->listxattr = oper->listxattr;
540 	cache_oper->mkdir    = oper->mkdir ? cache_mkdir : NULL;
541 	cache_oper->mknod    = oper->mknod ? cache_mknod : NULL;
542 	cache_oper->open     = oper->open;
543 	cache_oper->opendir  = cache_opendir;
544 	cache_oper->read     = oper->read;
545 	cache_oper->readdir  = oper->readdir ? cache_readdir : NULL;
546 	cache_oper->readlink = oper->readlink ? cache_readlink : NULL;
547 	cache_oper->release  = oper->release;
548 	cache_oper->releasedir = cache_releasedir;
549 	cache_oper->removexattr = oper->removexattr;
550 	cache_oper->rename   = oper->rename ? cache_rename : NULL;
551 	cache_oper->rmdir    = oper->rmdir ? cache_rmdir : NULL;
552 	cache_oper->setxattr = oper->setxattr;
553 	cache_oper->statfs   = oper->statfs;
554 	cache_oper->symlink  = oper->symlink ? cache_symlink : NULL;
555 	cache_oper->truncate = oper->truncate ? cache_truncate : NULL;
556 	cache_oper->unlink   = oper->unlink ? cache_unlink : NULL;
557 	cache_oper->utimens  = oper->utimens ? cache_utimens : NULL;
558 	cache_oper->write    = oper->write ? cache_write : NULL;
559 }
560 
cache_wrap(struct fuse_operations * oper)561 struct fuse_operations *cache_wrap(struct fuse_operations *oper)
562 {
563 	static struct fuse_operations cache_oper;
564 	cache.next_oper = oper;
565 
566 	cache_fill(oper, &cache_oper);
567 	pthread_mutex_init(&cache.lock, NULL);
568 	cache.table = g_hash_table_new_full(g_str_hash, g_str_equal,
569 					    g_free, free_node);
570 	if (cache.table == NULL) {
571 		fprintf(stderr, "failed to create cache\n");
572 		return NULL;
573 	}
574 	return &cache_oper;
575 }
576 
577 static const struct fuse_opt cache_opts[] = {
578 	{ "dcache_timeout=%u", offsetof(struct cache, stat_timeout_secs), 0 },
579 	{ "dcache_timeout=%u", offsetof(struct cache, dir_timeout_secs), 0 },
580 	{ "dcache_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 },
581 	{ "dcache_stat_timeout=%u", offsetof(struct cache, stat_timeout_secs), 0 },
582 	{ "dcache_dir_timeout=%u", offsetof(struct cache, dir_timeout_secs), 0 },
583 	{ "dcache_link_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 },
584 	{ "dcache_max_size=%u", offsetof(struct cache, max_size), 0 },
585 	{ "dcache_clean_interval=%u", offsetof(struct cache,
586 					       clean_interval_secs), 0 },
587 	{ "dcache_min_clean_interval=%u", offsetof(struct cache,
588 						   min_clean_interval_secs), 0 },
589 
590 	/* For backwards compatibility */
591 	{ "cache_timeout=%u", offsetof(struct cache, stat_timeout_secs), 0 },
592 	{ "cache_timeout=%u", offsetof(struct cache, dir_timeout_secs), 0 },
593 	{ "cache_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 },
594 	{ "cache_stat_timeout=%u", offsetof(struct cache, stat_timeout_secs), 0 },
595 	{ "cache_dir_timeout=%u", offsetof(struct cache, dir_timeout_secs), 0 },
596 	{ "cache_link_timeout=%u", offsetof(struct cache, link_timeout_secs), 0 },
597 	{ "cache_max_size=%u", offsetof(struct cache, max_size), 0 },
598 	{ "cache_clean_interval=%u", offsetof(struct cache,
599 					       clean_interval_secs), 0 },
600 	{ "cache_min_clean_interval=%u", offsetof(struct cache,
601 						   min_clean_interval_secs), 0 },
602 	FUSE_OPT_END
603 };
604 
cache_parse_options(struct fuse_args * args)605 int cache_parse_options(struct fuse_args *args)
606 {
607 	cache.stat_timeout_secs = DEFAULT_CACHE_TIMEOUT_SECS;
608 	cache.dir_timeout_secs = DEFAULT_CACHE_TIMEOUT_SECS;
609 	cache.link_timeout_secs = DEFAULT_CACHE_TIMEOUT_SECS;
610 	cache.max_size = DEFAULT_MAX_CACHE_SIZE;
611 	cache.clean_interval_secs = DEFAULT_CACHE_CLEAN_INTERVAL_SECS;
612 	cache.min_clean_interval_secs = DEFAULT_MIN_CACHE_CLEAN_INTERVAL_SECS;
613 
614 	return fuse_opt_parse(args, &cache, cache_opts, NULL);
615 }
616