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