/*
* UNFS3 file descriptor cache
* (C) 2004, Pascal Schmidt
* see file LICENSE for license details
*/
#include "config.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <rpc/rpc.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <syslog.h>
#include <unistd.h>
#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);
}
}
syntax highlighted by Code2HTML, v. 0.9.1