/* * UNFS3 file descriptor cache * (C) 2004, Pascal Schmidt * see file LICENSE for license details */ #include "config.h" #include #include #include #include #include #include #include #include #include "nfs.h" #include "mount.h" #include "fh.h" #include "daemon.h" #include "Config/exports.h" #include "fd_cache.h" #include "backend.h" /* * intention of the file descriptor cache * * for READ operations, the intent is to open() the file on the first * access and to close() it when we hit EOF * * for WRITE operations, the intent is to open() the file on the first * UNSTABLE access and to close() it when COMMIT is called */ /* number of entries in fd cache */ #define FD_ENTRIES 256 typedef struct { int fd; /* open file descriptor */ int kind; /* read or write */ time_t use; /* last use */ uint32 dev; /* device */ uint32 ino; /* inode */ uint32 gen; /* inode generation */ } fd_cache_t; static fd_cache_t fd_cache[FD_ENTRIES]; /* statistics */ int fd_cache_readers = 0; int fd_cache_writers = 0; /* counter for LRU */ static int fd_cache_time = 0; /* * return next pseudo-time value for LRU counter */ static int fd_cache_next(void) { return ++fd_cache_time; } /* * initialize the fd cache */ void fd_cache_init(void) { int i; for (i = 0; i < FD_ENTRIES; i++) { fd_cache[i].fd = -1; fd_cache[i].kind = FD_READ; fd_cache[i].use = 0; fd_cache[i].dev = 0; fd_cache[i].ino = 0; fd_cache[i].gen = 0; } } /* * find cache index to use for new entry * returns an empty slot if found, else return the least recently used slot * * only returns a used WRITE slot if opt_expire_writers is set */ static int fd_cache_lru(void) { int best = INT_MAX; int i; int idx = -1; for (i = 0; i < FD_ENTRIES; i++) { if (fd_cache[i].use == 0) return i; if (fd_cache[i].use < best) { if (opt_expire_writers) { best = fd_cache[i].use; idx = i; } else if (fd_cache[i].kind == FD_READ) { best = fd_cache[i].use; idx = i; } } } if (idx == -1) logmsg(LOG_WARNING, "fd cache full due to UNSTABLE writers"); return idx; } /* * remove an entry from the cache */ static int fd_cache_del(int idx) { int res1, res2; fd_cache[idx].use = 0; if (fd_cache[idx].fd != -1) { if (fd_cache[idx].kind == FD_WRITE) { /* sync file data if writing descriptor */ fd_cache_writers--; res1 = backend_fsync(fd_cache[idx].fd); } else { fd_cache_readers--; res1 = 0; } res2 = backend_close(fd_cache[idx].fd); /* return -1 if something went wrong during sync or close */ if (res1 == -1 || res2 == -1) res1 = -1; else res1 = 0; } else res1 = 0; fd_cache[idx].fd = -1; fd_cache[idx].dev = 0; fd_cache[idx].ino = 0; fd_cache[idx].gen = 0; return res1; } /* * add an entry to the cache */ static void fd_cache_add(int fd, unfs3_fh_t * ufh, int kind) { int idx, res; uint32 dev, ino; idx = fd_cache_lru(); if (idx != -1) { if (fd_cache[idx].kind == FD_READ) fd_cache_del(idx); else { /* if expiring a WRITE fd, report errors to log */ dev = fd_cache[idx].dev; ino = fd_cache[idx].ino; res = fd_cache_del(idx); if (res != 0) logmsg(LOG_CRIT, "silent write failure for dev %li, inode %li", dev, ino); } /* update statistics */ if (kind == FD_READ) fd_cache_readers++; else fd_cache_writers++; fd_cache[idx].fd = fd; fd_cache[idx].kind = kind; fd_cache[idx].use = fd_cache_next(); fd_cache[idx].dev = ufh->dev; fd_cache[idx].ino = ufh->ino; fd_cache[idx].gen = ufh->gen; } } /* * find entry by operating system fd number */ static int idx_by_fd(int fd, int kind) { int i; int idx = -1; for (i = 0; i < FD_ENTRIES; i++) if (fd_cache[i].fd == fd && fd_cache[i].kind == kind) { idx = i; break; } return idx; } /* * find entry by fh (device, inode, and generation number) */ static int idx_by_fh(unfs3_fh_t * ufh, int kind) { int i; int idx = -1; for (i = 0; i < FD_ENTRIES; i++) if (fd_cache[i].kind == kind) { if (fd_cache[i].dev == ufh->dev && fd_cache[i].ino == ufh->ino && fd_cache[i].gen == ufh->gen) { idx = i; break; } } return idx; } /* * open a file descriptor * uses fd from cache if possible */ int fd_open(const char *path, nfs_fh3 nfh, int kind) { int idx, res, fd; struct stat buf; unfs3_fh_t *fh = (void *) nfh.data.data_val; idx = idx_by_fh(fh, kind); if (idx != -1) return fd_cache[idx].fd; else { /* call open to obtain new fd */ if (kind == FD_READ) fd = backend_open(path, O_RDONLY); else fd = backend_open(path, O_WRONLY); if (fd == -1) return -1; /* check for local fs race */ res = backend_fstat(fd, &buf); if ((res == -1) || (fh->dev != buf.st_dev || fh->ino != buf.st_ino || fh->gen != backend_get_gen(buf, fd, path))) { /* * local fs changed meaning of path between * calling NFS operation doing fh_decomp and * arriving here * * set errno to ELOOP to make calling NFS * operation return NFS3ERR_STALE */ backend_close(fd); errno = ELOOP; return -1; } /* * success, add to cache for later use if not marked * as removable medium */ if (!(exports_opts & OPT_REMOVABLE)) fd_cache_add(fd, fh, kind); return fd; } } /* * close a file descriptor * returns error number from real close() if applicable */ int fd_close(int fd, int kind, int really_close) { int idx, res1 = 0, res2 = 0; idx = idx_by_fd(fd, kind); if (idx != -1) { /* update usage time of cache entry */ fd_cache[idx].use = fd_cache_next(); if (really_close == FD_CLOSE_REAL) /* delete entry on real close, will close() fd */ return fd_cache_del(idx); else return 0; } else { /* not in cache, sync and close directly */ if (kind == FD_WRITE) res1 = backend_fsync(fd); res2 = backend_close(fd); if (res1 != 0) return res1; else return res2; } } /* * sync file descriptor data to disk */ int fd_sync(nfs_fh3 nfh) { int idx; unfs3_fh_t *fh = (void *) nfh.data.data_val; idx = idx_by_fh(fh, FD_WRITE); if (idx != -1) /* delete entry, will fsync() and close() the fd */ return fd_cache_del(idx); else return 0; } /* * purge/shutdown the cache */ void fd_cache_purge(void) { int i; /* close any open file descriptors we still have */ for (i = 0; i < FD_ENTRIES; i++) { if (fd_cache[i].fd != -1) fd_cache_del(i); } }