* may use alarm() */
setitimer(ITIMER_REAL, &dsi->timer, NULL);
+ log_dircache_stat();
+
/* we got some traffic from the client since the previous timer
* tick. */
if ((child.flags & CHILD_DATA)) {
ALL dirsearch_byname will fail.
*/
int unlen = strlen(path.u_name);
- path.d_dir = dircache_search_by_name(vol, dstack[cidx].dir, path.u_name, unlen);
+ path.d_dir = dircache_search_by_name(vol, dstack[cidx].dir, path.u_name, unlen, path.st.st_ctime);
if (path.d_dir == NULL) {
/* path.m_name is set by adddir */
if (NULL == (path.d_dir = dir_add( vol, dstack[cidx].dir, &path, unlen) ) ) {
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
+#include <time.h>
#include <atalk/util.h>
#include <atalk/cnid.h>
* The dircache is a LRU cache, whenever it fills up we call dircache_evict internally which removes
* DIRCACHE_FREE_QUANTUM elements from the cache.
*
- * There is only one cache for all volumes, so of course we use the volume is in hashing calculations.
+ * There is only one cache for all volumes, so of course we use the volume id in hashing calculations.
+ *
+ * In order to avoid cache poisoning, we store the cached entries st_ctime from stat in
+ * struct dir.ctime_dircache. Later when we search the cache we compare the stored
+ * value with the result of a fresh stat. If the times differ, we remove the cached
+ * entry and return "no entry found in cache".
+ * A elements ctime changes when
+ * 1) the element is renamed
+ * (we loose the cached entry here, but it will expire when the cache fills)
+ * 2) its a directory and an object has been created therein
+ * 3) the element is deleted and recreated under the same name
+ * Using ctime leads to cache eviction in case 2) where it wouldn't be necessary, because
+ * the dir itself (name, CNID, ...) hasn't changed, but there's no other way.
*
* Indexes
* =======
static hash_t *dircache; /* The actual cache */
static unsigned int dircache_maxsize; /* cache maximum size */
+static struct dircache_stat {
+ unsigned long long lookups;
+ unsigned long long hits;
+ unsigned long long misses;
+ unsigned long long added;
+ unsigned long long removed;
+ unsigned long long expunged;
+ unsigned long long evicted;
+} dircache_stat;
+
/* FNV 1a */
static hash_val_t hash_vid_did(const void *key)
{
* queue index on dircache */
static q_t *index_queue; /* the index itself */
-static unsigned int queue_count;
+static unsigned long queue_count;
/*!
* @brief Remove a fixed number of (oldest) entries from the cache and indexes
dir_free(dir); /* 4 */
}
- AFP_ASSERT(queue_count == dircache->hash_nodecount);
-
+ AFP_ASSERT(queue_count == dircache->hash_nodecount);
+ dircache_stat.evicted += DIRCACHE_FREE_QUANTUM;
LOG(log_debug, logtype_afpd, "dircache: {finished cache eviction}");
}
********************************************************/
/*!
- * @brief Search the dircache via a CNID
+ * @brief Search the dircache via a CNID for a directory
+ *
+ * Found cache entries are expunged if both the parent directory st_ctime and the objects
+ * st_ctime are modified.
+ * This func builds on the fact, that all our code only ever needs to and does search
+ * the dircache by CNID expecting directories to be returned, but not files.
+ * Thus
+ * (1) if we find a file (d_fullpath == NULL) for a given CNID we
+ * (1a) remove it from the cache
+ * (1b) return NULL indicating nothing found
+ * (2) we can then use d_fullpath to stat the directory
*
- * @param vol (r) pointer to struct vol
- * @param cnid (r) CNID of the file or directory
+ * @param vol (r) pointer to struct vol
+ * @param cnid (r) CNID of the directory to search
*
- * @returns Pointer to struct dir if found, else NULL
+ * @returns Pointer to struct dir if found, else NULL
*/
struct dir *dircache_search_by_did(const struct vol *vol, cnid_t cnid)
{
struct dir *cdir = NULL;
struct dir key;
+ struct stat st;
hnode_t *hn;
- AFP_ASSERT(vol);
- AFP_ASSERT(ntohl(cnid) >= CNID_START);
+ AFP_ASSERT(vol);
+ AFP_ASSERT(ntohl(cnid) >= CNID_START);
+ dircache_stat.lookups++;
key.d_vid = vol->v_vid;
key.d_did = cnid;
if ((hn = hash_lookup(dircache, &key)))
cdir = hnode_get(hn);
- if (cdir)
- LOG(log_debug, logtype_afpd, "dircache(cnid:%u): {cached: path:'%s'}",
- ntohl(cnid), cfrombstr(cdir->d_u_name));
- else
- LOG(log_debug, logtype_afpd, "dircache(cnid:%u): {not in cache}", ntohl(cnid));
+ if (cdir) {
+ if (cdir->d_fullpath == NULL) { /* (1) */
+ LOG(log_debug, logtype_afpd, "dircache(cnid:%u): {not a directory:\"%s\"}",
+ ntohl(cnid), cfrombstr(cdir->d_u_name));
+ (void)dir_remove(vol, cdir); /* (1a) */
+ dircache_stat.expunged++;
+ return NULL; /* (1b) */
+ }
+ if (lstat(cfrombstr(cdir->d_fullpath), &st) != 0) {
+ LOG(log_debug, logtype_afpd, "dircache(cnid:%u): {missing:\"%s\"}",
+ ntohl(cnid), cfrombstr(cdir->d_fullpath));
+ (void)dir_remove(vol, cdir);
+ dircache_stat.expunged++;
+ return NULL;
+ }
+ if (cdir->ctime_dircache != st.st_ctime) {
+ LOG(log_debug, logtype_afpd, "dircache(cnid:%u): {modified:\"%s\"}",
+ ntohl(cnid), cfrombstr(cdir->d_u_name));
+ (void)dir_remove(vol, cdir);
+ dircache_stat.expunged++;
+ return NULL;
+ }
+ LOG(log_debug, logtype_afpd, "dircache(cnid:%u): {cached: path:\"%s\"}",
+ ntohl(cnid), cfrombstr(cdir->d_fullpath));
+ dircache_stat.hits++;
+ } else {
+ LOG(log_debug, logtype_afpd, "dircache(cnid:%u): {not in cache}", ntohl(cnid));
+ dircache_stat.hits++;
+ }
+
return cdir;
}
/*!
* @brief Search the cache via did/name hashtable
*
- * @param vol (r) volume
- * @param dir (r) directory
- * @param name (r) name (server side encoding)
- * @parma len (r) strlen of name
+ * Found cache entries are expunged if both the parent directory st_ctime and the objects
+ * st_ctime are modified.
+ *
+ * @param vol (r) volume
+ * @param dir (r) directory
+ * @param name (r) name (server side encoding)
+ * @parma len (r) strlen of name
+ * @param ctime (r) current st_ctime from stat
*
* @returns pointer to struct dir if found in cache, else NULL
*/
-struct dir *dircache_search_by_name(const struct vol *vol, const struct dir *dir, char *name, int len)
+struct dir *dircache_search_by_name(const struct vol *vol, const struct dir *dir, char *name, int len, time_t ctime)
{
struct dir *cdir = NULL;
struct dir key;
+
hnode_t *hn;
static_bstring uname = {-1, len, (unsigned char *)name};
AFP_ASSERT(len == strlen(name));
AFP_ASSERT(len < 256);
+ dircache_stat.lookups++;
LOG(log_debug, logtype_afpd, "dircache_search_by_name(did:%u, \"%s\")",
ntohl(dir->d_did), name);
cdir = hnode_get(hn);
}
- if (cdir)
- LOG(log_debug, logtype_afpd, "dircache(did:%u, '%s'): {found in cache}",
+ if (cdir) {
+ if (cdir->ctime_dircache != ctime) {
+ LOG(log_debug, logtype_afpd, "dircache(did:%u,\"%s\"): {modified}",
+ ntohl(dir->d_did), name);
+ (void)dir_remove(vol, cdir);
+ dircache_stat.expunged++;
+ return NULL;
+ }
+ LOG(log_debug, logtype_afpd, "dircache(did:%u,\"%s\"): {found in cache}",
ntohl(dir->d_did), name);
- else
- LOG(log_debug, logtype_afpd, "dircache(did:%u,'%s'): {not in cache}",
+ dircache_stat.hits++;
+ } else {
+ LOG(log_debug, logtype_afpd, "dircache(did:%u,\"%s\"): {not in cache}",
ntohl(dir->d_did), name);
+ dircache_stat.misses++;
+ }
return cdir;
}
queue_count++;
}
+ dircache_stat.added++;
LOG(log_debug, logtype_afpd, "dircache(did:%u,'%s'): {added}",
ntohl(dir->d_did), cfrombstr(dir->d_u_name));
LOG(log_debug, logtype_afpd, "dircache(did:%u,\"%s\"): {removed}",
ntohl(dir->d_did), cfrombstr(dir->d_u_name));
- AFP_ASSERT(queue_count == index_didname->hash_nodecount
- && queue_count == dircache->hash_nodecount);
+ dircache_stat.removed++;
+ AFP_ASSERT(queue_count == index_didname->hash_nodecount
+ && queue_count == dircache->hash_nodecount);
}
/*!
return 0;
}
+/*!
+ * Log dircache statistics
+ */
+void log_dircache_stat(void)
+{
+ LOG(log_debug, logtype_afpd, "dircache_stat: "
+ "entries: %lu, lookups: %llu, hits: %llu, misses: %llu, added: %llu, removed: %llu, expunged: %llu, evicted: %llu",
+ queue_count,
+ dircache_stat.lookups,
+ dircache_stat.hits,
+ dircache_stat.misses,
+ dircache_stat.added,
+ dircache_stat.removed,
+ dircache_stat.expunged,
+ dircache_stat.evicted);
+}
+
/*!
* @brief Dump dircache to /tmp/dircache.PID
*/
}
setbuf(dump, NULL);
- fprintf(dump, "Number of cache entries in LRU queue: %u\n", queue_count);
+ fprintf(dump, "Number of cache entries in LRU queue: %lu\n", queue_count);
fprintf(dump, "Configured maximum cache size: %u\n\n", dircache_maxsize);
fprintf(dump, "Primary CNID index:\n");
/*
- $Id: dircache.h,v 1.1.2.5 2010-02-11 14:13:06 franklahm Exp $
Copyright (c) 2010 Frank Lahm <franklahm@gmail.com>
This program is free software; you can redistribute it and/or modify
#ifndef DIRCACHE_H
#define DIRCACHE_H
+#include <sys/types.h>
+
#include <atalk/volume.h>
#include <atalk/directory.h>
#define DIRCACHE (1 << 0)
#define DIDNAME_INDEX (1 << 1)
#define QUEUE_INDEX (1 << 2)
+#define DIRCACHE_ALL (DIRCACHE|DIDNAME_INDEX|QUEUE_INDEX)
extern int dircache_init(int reqsize);
extern int dircache_add(struct dir *);
extern void dircache_remove(const struct vol *, struct dir *, int flag);
extern struct dir *dircache_search_by_did(const struct vol *vol, cnid_t did);
-extern struct dir *dircache_search_by_name(const struct vol *, const struct dir *dir, char *name, int len);
+extern struct dir *dircache_search_by_name(const struct vol *, const struct dir *dir, char *name, int len, time_t ctime);
extern void dircache_dump(void);
-
+extern void log_dircache_stat(void);
#endif /* DIRCACHE_H */
}
/* Create struct dir */
- if ((ret = dir_new(mpath, upath, vol, pdid, did, fullpath)) == NULL) { /* 6 */
+ if ((ret = dir_new(mpath, upath, vol, pdid, did, fullpath, st.st_ctime)) == NULL) { /* 6 */
LOG(log_error, logtype_afpd, "dirlookup(did: %u) {%s, %s}: %s", ntohl(did), mpath, upath, strerror(errno));
err = 1;
goto exit;
}
-
+
/* Add it to the cache only if it's a dir */
if (dircache_add(ret) != 0) { /* 7 */
err = 1;
* @param vol (r) pointer to struct vol
* @param pdid (r) Parent CNID
* @param did (r) CNID
- * @param fullpath (r) Full unix path to dir or NULL for files
+ * @param path (r) Full unix path to dir or NULL for files
+ * @param ctime (r) st_ctime from stat
*
* @returns pointer to new struct dir or NULL on error
*
const struct vol *vol,
cnid_t pdid,
cnid_t did,
- bstring path)
+ bstring path,
+ time_t ctime)
{
struct dir *dir;
dir->d_pdid = pdid;
dir->d_vid = vol->v_vid;
dir->d_fullpath = path;
+ dir->ctime_dircache = ctime;
return dir;
}
* @brief Create struct dir from struct path
*
* Create a new struct dir from struct path. Then add it to the cache.
- * The caller must have assured that the dir is not already in the cache,
- * cf theAFP_ASSERTion.
+ *
* 1. Open adouble file, get CNID from it.
* 2. Search the database, hinting with the CNID from (1).
* 3. Build fullpath and create struct dir.
AFP_ASSERT(path);
AFP_ASSERT(len > 0);
- if ((cdir = dircache_search_by_name(vol, dir, path->u_name, strlen(path->u_name))) != NULL) {
+ if ((cdir = dircache_search_by_name(vol, dir, path->u_name, strlen(path->u_name), path->st.st_ctime)) != NULL) {
/* there's a stray entry in the dircache */
LOG(log_debug, logtype_afpd, "dir_add(did:%u,'%s/%s'): {stray cache entry: did:%u,'%s', removing}",
ntohl(dir->d_did), cfrombstr(dir->d_fullpath), path->u_name,
}
/* Allocate and initialize struct dir */
- if ((cdir = dir_new( path->m_name, path->u_name, vol, dir->d_did, id, fullpath)) == NULL) { /* 3 */
+ if ((cdir = dir_new( path->m_name, path->u_name, vol, dir->d_did, id, fullpath, path->st.st_ctime)) == NULL) { /* 3 */
err = 4;
goto exit;
}
/* Search the cache */
int unamelen = strlen(ret.u_name);
- cdir = dircache_search_by_name(vol, dir, ret.u_name, unamelen); /* 14 */
+ cdir = dircache_search_by_name(vol, dir, ret.u_name, unamelen, ret.st.st_ctime); /* 14 */
if (cdir == NULL) {
/* Not in cache, create one */
if ((cdir = dir_add(vol, dir, &ret, unamelen)) == NULL) { /* 15 */
return AFPERR_PARAM;
LOG(log_debug, logtype_afpd, "afp_mapid: valid UUID request");
uuidtype_t type;
- len = getnamefromuuid( ibuf, &name, &type);
+ len = getnamefromuuid((unsigned char*) ibuf, &name, &type);
if (len != 0) /* its a error code, not len */
return AFPERR_NOITEM;
switch (type) {
break;
case 5 : /* username -> UUID */
LOG(log_debug, logtype_afpd, "afp_mapname: name: %s",ibuf);
- if (0 != getuuidfromname(ibuf, UUID_USER, rbuf))
+ if (0 != getuuidfromname(ibuf, UUID_USER, (unsigned char *)rbuf))
return AFPERR_NOITEM;
*rbuflen = UUID_BINSIZE;
break;
case 6 : /* groupname -> UUID */
LOG(log_debug, logtype_afpd, "afp_mapname: name: %s",ibuf);
- if (0 != getuuidfromname(ibuf, UUID_GROUP, rbuf))
+ if (0 != getuuidfromname(ibuf, UUID_GROUP, (unsigned char *)rbuf))
return AFPERR_NOITEM;
*rbuflen = UUID_BINSIZE;
break;
typedef int (*dir_loop)(struct dirent *, char *, void *);
extern struct dir *dir_new(const char *mname, const char *uname, const struct vol *,
- cnid_t pdid, cnid_t did, bstring fullpath); /* volume.c needs it once */
+ cnid_t pdid, cnid_t did, bstring fullpath, time_t ctime);
extern void dir_free (struct dir *);
extern struct dir *dir_add(struct vol *, const struct dir *, struct path *, int);
extern int dir_modify(const struct vol *vol, struct dir *dir, cnid_t pdid, cnid_t did,
#include <atalk/adouble.h>
#include <atalk/vfs.h>
#include <atalk/cnid.h>
+#include <atalk/util.h>
#include <atalk/bstrlib.h>
#include <atalk/bstradd.h>
return path_error(o_path, AFPERR_NODIR );
}
- LOG(log_debug, logtype_afpd, "enumerate(vid:%u, did:%u, cwddid:%u, cwd:'%s', name:'%s', f/d:%04x/%04x, rc:%u, i:%u, max:%u)",
- ntohs(vid), ntohl(did), ntohl(curdir->d_did),
- cfrombstr(curdir->d_fullpath), o_path->u_name,
- fbitmap, dbitmap, reqcnt, sindex, maxsz);
+ LOG(log_debug, logtype_afpd, "enumerate(\"%s/%s\", f/d:%04x/%04x, rc:%u, i:%u, max:%u)",
+ getcwdpath(), o_path->u_name, fbitmap, dbitmap, reqcnt, sindex, maxsz);
data = rbuf + 3 * sizeof( u_int16_t );
sz = 3 * sizeof( u_int16_t ); /* fbitmap, dbitmap, reqcount */
continue;
}
int len = strlen(s_path.u_name);
- if ((dir = dircache_search_by_name(vol, curdir, s_path.u_name, len)) == NULL) {
+ if ((dir = dircache_search_by_name(vol, curdir, s_path.u_name, len, s_path.st.st_ctime)) == NULL) {
if ((dir = dir_add(vol, curdir, &s_path, len)) == NULL) {
LOG(log_error, logtype_afpd, "enumerate(vid:%u, did:%u, name:'%s'): error adding dir: '%s'",
ntohs(vid), ntohl(did), o_path->u_name, s_path.u_name);
if ( fbitmap == 0 ) {
continue;
}
+ /* files are added to the dircache in getfilparams() -> getmetadata() */
if (AFP_OK != ( ret = getfilparams(vol, fbitmap, &s_path, curdir,
data + header , &esz )) ) {
return( ret );
if (!path->id) {
struct dir *cachedfile;
int len = strlen(upath);
- if ((cachedfile = dircache_search_by_name(vol, dir, upath, len)) != NULL)
+ if ((cachedfile = dircache_search_by_name(vol, dir, upath, len, st->st_ctime)) != NULL)
id = cachedfile->d_did;
else {
id = get_id(vol, adp, st, dir->d_did, upath, len);
}
}
- if ((cachedfile = dir_new(path->m_name, upath, vol, dir->d_did, id, NULL)) == NULL) {
+ if ((cachedfile = dir_new(path->m_name, upath, vol, dir->d_did, id, NULL, st->st_ctime)) == NULL) {
LOG(log_error, logtype_afpd, "getmetadata: error from dir_new");
exit(EXITERR_SYS);
}
+
if ((dircache_add(cachedfile)) != 0) {
LOG(log_error, logtype_afpd, "getmetadata: fatal dircache error");
exit(EXITERR_SYS);
/* id's need switching. src -> dest and dest -> src.
* we need to re-stat() if it was a cross device copy.
*/
- if (sid) {
- cnid_delete(vol->v_cdb, sid);
- }
- if (did) {
- cnid_delete(vol->v_cdb, did);
- }
+ if (sid)
+ cnid_delete(vol->v_cdb, sid);
+ if (did)
+ cnid_delete(vol->v_cdb, did);
+
if ((did && ( (crossdev && lstat( upath, &srcst) < 0) ||
cnid_update(vol->v_cdb, did, &srcst, curdir->d_did,upath, dlen) < 0))
||
ad_close(addp, ADFLAGS_HF);
}
+ struct dir *cached;
+ if ((cached = dircache_search_by_did(vol, sid)) != NULL)
+ (void)dir_remove(vol, cached);
+ if ((cached = dircache_search_by_did(vol, did)) != NULL)
+ (void)dir_remove(vol, cached);
+
return err;
}
/* Remove it from the cache */
struct dir *cacheddir = dircache_search_by_did(vol, id);
- if (cacheddir == NULL) {
- LOG(log_warning, logtype_afpd,"Not cached: \"%s/%s\"", getcwdpath(), upath);
- rc = AFPERR_MISC;
- goto exit;
+ if (cacheddir) {
+ LOG(log_warning, logtype_afpd,"Still cached: \"%s/%s\"", getcwdpath(), upath);
+ (void)dir_remove(vol, cacheddir);
}
- (void)dir_remove(vol, cacheddir);
/* fix up the catalog entry */
cnid_update(vol->v_cdb, id, st, curdir->d_did, upath, strlen(upath));
rc = deletefile(vol, -1, upath, 1);
struct dir *cachedfile;
- if ((cachedfile = dircache_search_by_name(vol, dir, upath, strlen(upath)))) {
+ if ((cachedfile = dircache_search_by_name(vol, dir, upath, strlen(upath), s_path->st.st_ctime))) {
dircache_remove(vol, cachedfile, DIRCACHE | DIDNAME_INDEX | QUEUE_INDEX);
dir_free(cachedfile);
}
volume,
DIRDID_ROOT_PARENT,
DIRDID_ROOT,
- bfromcstr(volume->v_path))
+ bfromcstr(volume->v_path),
+ st.st_ctime)
) == NULL) {
free(vol_mname);
LOG(log_error, logtype_afpd, "afp_openvol(%s): malloc: %s", volume->v_path, strerror(errno) );
goto openvol_err;
}
free(vol_mname);
-
volume->v_root = dir;
curdir = dir;
ucs2_t *d_m_name_ucs2; /* mac name as UCS2 */
qnode_t *qidx_node; /* pointer to position in queue index */
void *d_ofork; /* oforks using this directory. */
- time_t ctime; /* inode ctime */
+ time_t ctime; /* inode ctime, used and modified by reenumeration */
+ time_t ctime_dircache; /* inode ctime, used and modified by dircache */
int d_flags; /* directory flags */
cnid_t d_pdid; /* CNID of parent directory */
cnid_t d_did; /* CNID of directory */