source: FTPfs/sshfs-fuse-1.9/cache.c @ 157

Last change on this file since 157 was 10, checked in by zsjheng, 17 years ago
File size: 14.0 KB
RevLine 
[10]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 <stdlib.h>
12#include <string.h>
13#include <errno.h>
14#include <glib.h>
15#include <pthread.h>
16
17#define DEFAULT_CACHE_TIMEOUT 20
18#define MAX_CACHE_SIZE 10000
19#define MIN_CACHE_CLEAN_INTERVAL 5
20#define CACHE_CLEAN_INTERVAL 60
21
22struct cache {
23  int on;
24  unsigned stat_timeout;
25  unsigned dir_timeout;
26  unsigned link_timeout;
27  struct fuse_cache_operations *next_oper;
28  GHashTable *table;
29  pthread_mutex_t lock;
30  time_t last_cleaned;
31};
32
33static struct cache cache;
34
35struct node {
36  struct stat stat;
37  time_t stat_valid;
38  char **dir;
39  time_t dir_valid;
40  char *link;
41  time_t link_valid;
42  time_t valid;
43};
44
45struct fuse_cache_dirhandle {
46  const char *path;
47  fuse_dirh_t h;
48  fuse_dirfil_t filler;
49  GPtrArray *dir;
50};
51
52static void free_node(gpointer node_)
53{
54  struct node *node = (struct node *) node_;
55  g_strfreev(node->dir);
56  g_free(node);
57}
58
59static int cache_clean_entry(void *key_, struct node *node, time_t *now)
60{
61  (void) key_;
62  if (*now > node->valid)
63    return TRUE;
64  else
65    return FALSE;
66}
67
68static void cache_clean(void)
69{
70  time_t now = time(NULL);
71  if (now > cache.last_cleaned + MIN_CACHE_CLEAN_INTERVAL &&
72      (g_hash_table_size(cache.table) > MAX_CACHE_SIZE ||
73       now > cache.last_cleaned + CACHE_CLEAN_INTERVAL)) {
74    g_hash_table_foreach_remove(cache.table,
75              (GHRFunc) cache_clean_entry, &now);
76    cache.last_cleaned = now;
77  }
78}
79
80static struct node *cache_lookup(const char *path)
81{
82  return (struct node *) g_hash_table_lookup(cache.table, path);
83}
84
85static void cache_purge(const char *path)
86{
87  g_hash_table_remove(cache.table, path);
88}
89
90static void cache_purge_parent(const char *path)
91{
92  const char *s = strrchr(path, '/');
93  if (s) {
94    if (s == path)
95      g_hash_table_remove(cache.table, "/");
96    else {
97      char *parent = g_strndup(path, s - path);
98      cache_purge(parent);
99      g_free(parent);
100    }
101  }
102}
103
104void cache_invalidate(const char *path)
105{
106  pthread_mutex_lock(&cache.lock);
107  cache_purge(path);
108  pthread_mutex_unlock(&cache.lock);
109}
110
111static void cache_invalidate_dir(const char *path)
112{
113  pthread_mutex_lock(&cache.lock);
114  cache_purge(path);
115  cache_purge_parent(path);
116  pthread_mutex_unlock(&cache.lock);
117}
118
119static int cache_del_children(const char *key, void *val_, const char *path)
120{
121  (void) val_;
122  if (strncmp(key, path, strlen(path)) == 0)
123    return TRUE;
124  else
125    return FALSE;
126}
127
128static void cache_do_rename(const char *from, const char *to)
129{
130  pthread_mutex_lock(&cache.lock);
131  g_hash_table_foreach_remove(cache.table, (GHRFunc) cache_del_children,
132            (char *) from);
133  cache_purge(from);
134  cache_purge(to);
135  cache_purge_parent(from);
136  cache_purge_parent(to);
137  pthread_mutex_unlock(&cache.lock);
138}
139
140static struct node *cache_get(const char *path)
141{
142  struct node *node = cache_lookup(path);
143  if (node == NULL) {
144    char *pathcopy = g_strdup(path);
145    node = g_new0(struct node, 1);
146    g_hash_table_insert(cache.table, pathcopy, node);
147  }
148  return node;
149}
150
151void cache_add_attr(const char *path, const struct stat *stbuf)
152{
153  struct node *node;
154  time_t now;
155
156  pthread_mutex_lock(&cache.lock);
157  node = cache_get(path);
158  now = time(NULL);
159  node->stat = *stbuf;
160  node->stat_valid = time(NULL) + cache.stat_timeout;
161  if (node->stat_valid > node->valid)
162    node->valid = node->stat_valid;
163  cache_clean();
164  pthread_mutex_unlock(&cache.lock);
165}
166
167static void cache_add_dir(const char *path, char **dir)
168{
169  struct node *node;
170  time_t now;
171
172  pthread_mutex_lock(&cache.lock);
173  node = cache_get(path);
174  now = time(NULL);
175  g_strfreev(node->dir);
176  node->dir = dir;
177  node->dir_valid = time(NULL) + cache.dir_timeout;
178  if (node->dir_valid > node->valid)
179    node->valid = node->dir_valid;
180  cache_clean();
181  pthread_mutex_unlock(&cache.lock);
182}
183
184static size_t my_strnlen(const char *s, size_t maxsize)
185{
186  const char *p;
187  for (p = s; maxsize && *p; maxsize--, p++);
188  return p - s;
189}
190
191static void cache_add_link(const char *path, const char *link, size_t size)
192{
193  struct node *node;
194  time_t now;
195
196  pthread_mutex_lock(&cache.lock);
197  node = cache_get(path);
198  now = time(NULL);
199  g_free(node->link);
200  node->link = g_strndup(link, my_strnlen(link, size-1));
201  node->link_valid = time(NULL) + cache.link_timeout;
202  if (node->link_valid > node->valid)
203    node->valid = node->link_valid;
204  cache_clean();
205  pthread_mutex_unlock(&cache.lock);
206}
207
208static int cache_get_attr(const char *path, struct stat *stbuf)
209{
210  struct node *node;
211  int err = -EAGAIN;
212  pthread_mutex_lock(&cache.lock);
213  node = cache_lookup(path);
214  if (node != NULL) {
215    time_t now = time(NULL);
216    if (node->stat_valid - now >= 0) {
217      *stbuf = node->stat;
218      err = 0;
219    }
220  }
221  pthread_mutex_unlock(&cache.lock);
222  return err;
223}
224
225static int cache_getattr(const char *path, struct stat *stbuf)
226{
227  int err = cache_get_attr(path, stbuf);
228  if (err) {
229    err = cache.next_oper->oper.getattr(path, stbuf);
230    if (!err)
231      cache_add_attr(path, stbuf);
232  }
233  return err;
234}
235
236static int cache_readlink(const char *path, char *buf, size_t size)
237{
238  struct node *node;
239  int err;
240
241  pthread_mutex_lock(&cache.lock);
242  node = cache_lookup(path);
243  if (node != NULL) {
244    time_t now = time(NULL);
245    if (node->link_valid - now >= 0) {
246      strncpy(buf, node->link, size-1);
247      buf[size-1] = '\0';
248      pthread_mutex_unlock(&cache.lock);
249      return 0;
250    }
251  }
252  pthread_mutex_unlock(&cache.lock);
253  err = cache.next_oper->oper.readlink(path, buf, size);
254  if (!err)
255    cache_add_link(path, buf, size);
256
257  return err;
258}
259
260static int cache_dirfill(fuse_cache_dirh_t ch, const char *name,
261                         const struct stat *stbuf)
262{
263  int err = ch->filler(ch->h, name, 0, 0);
264  if (!err) {
265    g_ptr_array_add(ch->dir, g_strdup(name));
266    if (stbuf->st_mode & S_IFMT) {
267      char *fullpath;
268      const char *basepath = !ch->path[1] ? "" : ch->path;
269
270      fullpath = g_strdup_printf("%s/%s", basepath, name);
271      cache_add_attr(fullpath, stbuf);
272      g_free(fullpath);
273    }
274  }
275  return err;
276}
277
278static int cache_getdir(const char *path, fuse_dirh_t h, fuse_dirfil_t filler)
279{
280  struct fuse_cache_dirhandle ch;
281  int err;
282  char **dir;
283  struct node *node;
284
285  pthread_mutex_lock(&cache.lock);
286  node = cache_lookup(path);
287  if (node != NULL && node->dir != NULL) {
288    time_t now = time(NULL);
289    if (node->dir_valid - now >= 0) {
290      for(dir = node->dir; *dir != NULL; dir++)
291        filler(h, *dir, 0, 0);
292      pthread_mutex_unlock(&cache.lock);
293      return 0;
294    }
295  }
296  pthread_mutex_unlock(&cache.lock);
297
298  ch.path = path;
299  ch.h = h;
300  ch.filler = filler;
301  ch.dir = g_ptr_array_new();
302  err = cache.next_oper->cache_getdir(path, &ch, cache_dirfill);
303  g_ptr_array_add(ch.dir, NULL);
304  dir = (char **) ch.dir->pdata;
305  if (!err)
306    cache_add_dir(path, dir);
307  else
308    g_strfreev(dir);
309  g_ptr_array_free(ch.dir, FALSE);
310  return err;
311}
312
313static int cache_unity_dirfill(fuse_cache_dirh_t ch, const char *name,
314                               const struct stat *stbuf)
315{
316  (void) stbuf;
317  return ch->filler(ch->h, name, 0, 0);
318}
319
320static int cache_unity_getdir(const char *path, fuse_dirh_t h,
321                              fuse_dirfil_t filler)
322{
323  struct fuse_cache_dirhandle ch;
324  ch.h = h;
325  ch.filler = filler;
326  return cache.next_oper->cache_getdir(path, &ch, cache_unity_dirfill);
327}
328
329static int cache_mknod(const char *path, mode_t mode, dev_t rdev)
330{
331  int err = cache.next_oper->oper.mknod(path, mode, rdev);
332  if (!err)
333    cache_invalidate_dir(path);
334  return err;
335}
336
337static int cache_mkdir(const char *path, mode_t mode)
338{
339  int err = cache.next_oper->oper.mkdir(path, mode);
340  if (!err)
341    cache_invalidate_dir(path);
342  return err;
343}
344
345static int cache_unlink(const char *path)
346{
347  int err = cache.next_oper->oper.unlink(path);
348  if (!err)
349    cache_invalidate_dir(path);
350  return err;
351}
352
353static int cache_rmdir(const char *path)
354{
355  int err = cache.next_oper->oper.rmdir(path);
356  if (!err)
357    cache_invalidate_dir(path);
358  return err;
359}
360
361static int cache_symlink(const char *from, const char *to)
362{
363  int err = cache.next_oper->oper.symlink(from, to);
364  if (!err)
365    cache_invalidate_dir(to);
366  return err;
367}
368
369static int cache_rename(const char *from, const char *to)
370{
371  int err = cache.next_oper->oper.rename(from, to);
372  if (!err)
373    cache_do_rename(from, to);
374  return err;
375}
376
377static int cache_link(const char *from, const char *to)
378{
379  int err = cache.next_oper->oper.link(from, to);
380  if (!err) {
381    cache_invalidate(from);
382    cache_invalidate_dir(to);
383  }
384  return err;
385}
386
387static int cache_chmod(const char *path, mode_t mode)
388{
389  int err = cache.next_oper->oper.chmod(path, mode);
390  if (!err)
391    cache_invalidate(path);
392  return err;
393}
394
395static int cache_chown(const char *path, uid_t uid, gid_t gid)
396{
397  int err = cache.next_oper->oper.chown(path, uid, gid);
398  if (!err)
399    cache_invalidate(path);
400  return err;
401}
402
403static int cache_truncate(const char *path, off_t size)
404{
405  int err = cache.next_oper->oper.truncate(path, size);
406  if (!err)
407    cache_invalidate(path);
408  return err;
409}
410
411static int cache_utime(const char *path, struct utimbuf *buf)
412{
413  int err = cache.next_oper->oper.utime(path, buf);
414  if (!err)
415    cache_invalidate(path);
416  return err;
417}
418
419static int cache_write(const char *path, const char *buf, size_t size,
420                       off_t offset, struct fuse_file_info *fi)
421{
422  int res = cache.next_oper->oper.write(path, buf, size, offset, fi);
423  if (res >= 0)
424    cache_invalidate(path);
425  return res;
426}
427
428#if FUSE_VERSION >= 25
429static int cache_create(const char *path, mode_t mode,
430                        struct fuse_file_info *fi)
431{
432  int err = cache.next_oper->oper.create(path, mode, fi);
433  if (!err)
434    cache_invalidate_dir(path);
435  return err;
436}
437
438static int cache_ftruncate(const char *path, off_t size,
439                           struct fuse_file_info *fi)
440{
441  int err = cache.next_oper->oper.ftruncate(path, size, fi);
442  if (!err)
443    cache_invalidate(path);
444  return err;
445}
446
447static int cache_fgetattr(const char *path, struct stat *stbuf,
448                          struct fuse_file_info *fi)
449{
450  int err = cache_get_attr(path, stbuf);
451  if (err) {
452    err = cache.next_oper->oper.fgetattr(path, stbuf, fi);
453    if (!err)
454      cache_add_attr(path, stbuf);
455  }
456  return err;
457}
458#endif
459
460static void cache_unity_fill(struct fuse_cache_operations *oper,
461                             struct fuse_operations *cache_oper)
462{
463#if FUSE_VERSION >= 23
464  cache_oper->init        = oper->oper.init;
465#endif
466  cache_oper->getattr     = oper->oper.getattr;
467  cache_oper->readlink    = oper->oper.readlink;
468  cache_oper->getdir      = cache_unity_getdir;
469  cache_oper->mknod       = oper->oper.mknod;
470  cache_oper->mkdir       = oper->oper.mkdir;
471  cache_oper->symlink     = oper->oper.symlink;
472  cache_oper->unlink      = oper->oper.unlink;
473  cache_oper->rmdir       = oper->oper.rmdir;
474  cache_oper->rename      = oper->oper.rename;
475  cache_oper->link        = oper->oper.link;
476  cache_oper->chmod       = oper->oper.chmod;
477  cache_oper->chown       = oper->oper.chown;
478  cache_oper->truncate    = oper->oper.truncate;
479  cache_oper->utime       = oper->oper.utime;
480  cache_oper->open        = oper->oper.open;
481  cache_oper->read        = oper->oper.read;
482  cache_oper->write       = oper->oper.write;
483  cache_oper->flush       = oper->oper.flush;
484  cache_oper->release     = oper->oper.release;
485  cache_oper->fsync       = oper->oper.fsync;
486  cache_oper->statfs      = oper->oper.statfs;
487  cache_oper->setxattr    = oper->oper.setxattr;
488  cache_oper->getxattr    = oper->oper.getxattr;
489  cache_oper->listxattr   = oper->oper.listxattr;
490  cache_oper->removexattr = oper->oper.removexattr;
491#if FUSE_VERSION >= 25
492  cache_oper->create      = oper->oper.create;
493  cache_oper->ftruncate   = oper->oper.ftruncate;
494  cache_oper->fgetattr    = oper->oper.fgetattr;
495#endif
496}
497
498static void cache_fill(struct fuse_cache_operations *oper,
499           struct fuse_operations *cache_oper)
500{
501  cache_oper->getattr  = oper->oper.getattr ? cache_getattr : NULL;
502  cache_oper->readlink = oper->oper.readlink ? cache_readlink : NULL;
503  cache_oper->getdir   = oper->cache_getdir ? cache_getdir : NULL;
504  cache_oper->mknod    = oper->oper.mknod ? cache_mknod : NULL;
505  cache_oper->mkdir    = oper->oper.mkdir ? cache_mkdir : NULL;
506  cache_oper->symlink  = oper->oper.symlink ? cache_symlink : NULL;
507  cache_oper->unlink   = oper->oper.unlink ? cache_unlink : NULL;
508  cache_oper->rmdir    = oper->oper.rmdir ? cache_rmdir : NULL;
509  cache_oper->rename   = oper->oper.rename ? cache_rename : NULL;
510  cache_oper->link     = oper->oper.link ? cache_link : NULL;
511  cache_oper->chmod    = oper->oper.chmod ? cache_chmod : NULL;
512  cache_oper->chown    = oper->oper.chown ? cache_chown : NULL;
513  cache_oper->truncate = oper->oper.truncate ? cache_truncate : NULL;
514  cache_oper->utime    = oper->oper.utime ? cache_utime : NULL;
515  cache_oper->write    = oper->oper.write ? cache_write : NULL;
516#if FUSE_VERSION >= 25
517  cache_oper->create   = oper->oper.create ? cache_create : NULL;
518  cache_oper->ftruncate = oper->oper.ftruncate ? cache_ftruncate : NULL;
519  cache_oper->fgetattr = oper->oper.fgetattr ? cache_fgetattr : NULL;
520#endif
521
522}
523
524struct fuse_operations *cache_init(struct fuse_cache_operations *oper)
525{
526  static struct fuse_operations cache_oper;
527  cache.next_oper = oper;
528
529  cache_unity_fill(oper, &cache_oper);
530  if (cache.on) {
531    cache_fill(oper, &cache_oper);
532    pthread_mutex_init(&cache.lock, NULL);
533    cache.table = g_hash_table_new_full(g_str_hash, g_str_equal,
534                g_free, free_node);
535    if (cache.table == NULL) {
536      fprintf(stderr, "failed to create cache\n");
537      return NULL;
538    }
539  }
540  return &cache_oper;
541}
542
543static const struct fuse_opt cache_opts[] = {
544  { "cache=yes", offsetof(struct cache, on), 1 },
545  { "cache=no", offsetof(struct cache, on), 0 },
546  { "cache_timeout=%u", offsetof(struct cache, stat_timeout), 0 },
547  { "cache_timeout=%u", offsetof(struct cache, dir_timeout), 0 },
548  { "cache_timeout=%u", offsetof(struct cache, link_timeout), 0 },
549  { "cache_stat_timeout=%u", offsetof(struct cache, stat_timeout), 0 },
550  { "cache_dir_timeout=%u", offsetof(struct cache, dir_timeout), 0 },
551  { "cache_link_timeout=%u", offsetof(struct cache, link_timeout), 0 },
552  FUSE_OPT_END
553};
554
555int cache_parse_options(struct fuse_args *args)
556{
557  cache.stat_timeout = DEFAULT_CACHE_TIMEOUT;
558  cache.dir_timeout = DEFAULT_CACHE_TIMEOUT;
559  cache.link_timeout = DEFAULT_CACHE_TIMEOUT;
560  cache.on = 1;
561
562  return fuse_opt_parse(args, &cache, cache_opts, NULL);
563}
Note: See TracBrowser for help on using the repository browser.