#define MAX_ERRNO 1000L
#endif
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,34)
+/* Enable our workaround for a race with d_splice_alias. The race was fixed in
+ * 2.6.34, so don't do it after that point. */
+# define D_SPLICE_ALIAS_RACE
+#endif
+
int cachefs_noreadpage = 0;
extern struct backing_dev_info *afs_backing_dev_info;
if (code) {
if (!(avc->f.states & CCorrupt)) {
struct cell *tc = afs_GetCellStale(avc->f.fid.Cell, READ_LOCK);
- afs_warn("Corrupt directory (%d.%d.%d.%d [%s] @%lx, pos %d)",
+ afs_warn("afs: Corrupt directory (%d.%d.%d.%d [%s] @%lx, pos %d)\n",
avc->f.fid.Cell, avc->f.fid.Fid.Volume,
avc->f.fid.Fid.Vnode, avc->f.fid.Fid.Unique,
tc ? tc->cellName : "",
#ifdef STRUCT_FILE_OPERATIONS_HAS_READ_ITER
.read_iter = afs_linux_read_iter,
.write_iter = afs_linux_write_iter,
-# if !defined(HAVE_LINUX___VFS_READ)
+# if !defined(HAVE_LINUX___VFS_WRITE) && !defined(HAVE_LINUX_KERNEL_WRITE)
.read = new_sync_read,
.write = new_sync_write,
# endif
return afs_convert_code(code);
}
+#if defined(IOP_GETATTR_TAKES_PATH_STRUCT)
+static int
+afs_linux_getattr(const struct path *path, struct kstat *stat, u32 request_mask, unsigned int sync_mode)
+{
+ int err = afs_linux_revalidate(path->dentry);
+ if (!err) {
+ generic_fillattr(path->dentry->d_inode, stat);
+ }
+ return err;
+}
+#else
static int
afs_linux_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)
{
int err = afs_linux_revalidate(dentry);
if (!err) {
generic_fillattr(dentry->d_inode, stat);
+ }
+ return err;
}
- return err;
-}
+#endif
static afs_uint32
-parent_vcache_dv(struct inode *inode, cred_t *credp, int locked)
+parent_vcache_dv(struct inode *inode, cred_t *credp)
{
int free_cred = 0;
struct vcache *pvcp;
struct vrequest treq;
struct afs_fakestat_state fakestate;
- if (!locked) {
- AFS_GLOCK();
- }
if (!credp) {
credp = crref();
free_cred = 1;
if (free_cred)
crfree(credp);
afs_PutFakeStat(&fakestate);
- if (!locked) {
- AFS_GUNLOCK();
- }
}
return hgetlo(pvcp->f.m.DataVersion);
}
+#ifdef D_SPLICE_ALIAS_RACE
+/* Leave some trace that this code is enabled; otherwise it's pretty hard to
+ * tell. */
+static __attribute__((used)) const char dentry_race_marker[] = "d_splice_alias race workaround enabled";
+
+static int
+check_dentry_race(struct dentry *dp)
+{
+ int raced = 0;
+ if (!dp->d_inode) {
+ struct dentry *parent = dget_parent(dp);
+
+ /* In Linux, before commit 4919c5e45a91b5db5a41695fe0357fbdff0d5767,
+ * d_splice_alias can momentarily hash a dentry before it's fully
+ * populated. This only happens for a moment, since it's unhashed again
+ * right after (in d_move), but this can make the dentry be found by
+ * __d_lookup, and then given to us.
+ *
+ * So check if the dentry is unhashed; if it is, then the dentry is not
+ * valid. We lock the parent inode to ensure that d_splice_alias is no
+ * longer running (the inode mutex will be held during
+ * afs_linux_lookup). Locking d_lock is required to check the dentry's
+ * flags, so lock that, too.
+ */
+ afs_linux_lock_inode(parent->d_inode);
+ spin_lock(&dp->d_lock);
+ if (d_unhashed(dp)) {
+ raced = 1;
+ }
+ spin_unlock(&dp->d_lock);
+ afs_linux_unlock_inode(parent->d_inode);
+
+ dput(parent);
+ }
+ return raced;
+}
+#endif /* D_SPLICE_ALIAS_RACE */
+
/* Validate a dentry. Return 1 if unchanged, 0 if VFS layer should re-evaluate.
* In kernels 2.2.10 and above, we are passed an additional flags var which
* may have either the LOOKUP_FOLLOW OR LOOKUP_DIRECTORY set in which case
return -ECHILD;
#endif
+#ifdef D_SPLICE_ALIAS_RACE
+ if (check_dentry_race(dp)) {
+ valid = 0;
+ return valid;
+ }
+#endif
+
AFS_GLOCK();
afs_InitFakeStat(&fakestate);
parent = dget_parent(dp);
pvcp = VTOAFS(parent->d_inode);
- parent_dv = parent_vcache_dv(parent->d_inode, credp, 1);
+ parent_dv = parent_vcache_dv(parent->d_inode, credp);
/* If the parent's DataVersion has changed or the vnode
* is longer valid, we need to do a full lookup. VerifyVCache
parent = dget_parent(dp);
pvcp = VTOAFS(parent->d_inode);
- parent_dv = parent_vcache_dv(parent->d_inode, credp, 1);
+ parent_dv = parent_vcache_dv(parent->d_inode, credp);
if (parent_dv > dp->d_time || !(pvcp->f.states & CStatd)
|| afs_IsDynroot(pvcp)) {
good_dentry:
valid = 1;
+ goto done;
+
+ bad_dentry:
+ valid = 0;
+#ifndef D_INVALIDATE_IS_VOID
+ /* When (v3.18) d_invalidate was converted to void, it also started
+ * being called automatically from revalidate, and automatically
+ * handled:
+ * - shrink_dcache_parent
+ * - automatic detach of submounts
+ * - d_drop
+ * Therefore, after that point, OpenAFS revalidate logic no longer needs
+ * to do any of those things itself for invalid dentry structs. We only need
+ * to tell VFS it's invalid (by returning 0), and VFS will handle the rest.
+ */
+ if (have_submounts(dp))
+ valid = 1;
+#endif
done:
/* Clean up */
if (credp)
crfree(credp);
+#ifndef D_INVALIDATE_IS_VOID
if (!valid) {
/*
* If we had a negative lookup for the name we want to forcibly
} else
d_invalidate(dp);
}
-
+#endif
return valid;
- bad_dentry:
- if (have_submounts(dp))
- valid = 1;
- else
- valid = 0;
- goto done;
}
static void
#if !defined(STRUCT_SUPER_BLOCK_HAS_S_D_OP)
dp->d_op = &afs_dentry_operations;
#endif
- dp->d_time = parent_vcache_dv(dip, credp, 1);
+ dp->d_time = parent_vcache_dv(dip, credp);
d_instantiate(dp, ip);
}
int code;
AFS_GLOCK();
+
code = afs_lookup(VTOAFS(dip), (char *)comp, &vcp, credp);
+ if (code == ENOENT) {
+ /* It's ok for the file to not be found. That's noted by the caller by
+ * seeing that the dp->d_inode field is NULL (set by d_splice_alias or
+ * d_add, below). */
+ code = 0;
+ osi_Assert(vcp == NULL);
+ }
+ if (code) {
+ AFS_GUNLOCK();
+ goto done;
+ }
- if (!code) {
+ if (vcp) {
struct vattr *vattr = NULL;
struct vcache *parent_vc = VTOAFS(dip);
#if !defined(STRUCT_SUPER_BLOCK_HAS_S_D_OP)
dp->d_op = &afs_dentry_operations;
#endif
- dp->d_time = parent_vcache_dv(dip, credp, 1);
+ dp->d_time = parent_vcache_dv(dip, credp);
AFS_GUNLOCK();
done:
crfree(credp);
- /* It's ok for the file to not be found. That's noted by the caller by
- * seeing that the dp->d_inode field is NULL.
- */
- if (!code || code == ENOENT) {
- /*
- * d_splice_alias can return an error (EIO) if there is an existing
- * connected directory alias for this dentry.
- */
- if (!IS_ERR(newdp)) {
- iput(ip);
- return newdp;
- } else {
- d_add(dp, ip);
- /*
- * Depending on the kernel version, d_splice_alias may or may
- * not drop the inode reference on error. If it didn't, do it
- * here.
- */
+ if (IS_ERR(newdp)) {
+ /* d_splice_alias can return an error (EIO) if there is an existing
+ * connected directory alias for this dentry. Add our dentry manually
+ * ourselves if this happens. */
+ d_add(dp, ip);
+
#if defined(D_SPLICE_ALIAS_LEAK_ON_ERROR)
- iput(ip);
+ /* Depending on the kernel version, d_splice_alias may or may not drop
+ * the inode reference on error. If it didn't, do it here. */
+ iput(ip);
#endif
- return NULL;
- }
- } else {
+ return NULL;
+ }
+
+ if (code) {
if (ip)
iput(ip);
return ERR_PTR(afs_convert_code(code));
}
+
+ iput(ip);
+ return newdp;
}
static int
#if !defined(STRUCT_SUPER_BLOCK_HAS_S_D_OP)
dp->d_op = &afs_dentry_operations;
#endif
- dp->d_time = parent_vcache_dv(dip, credp, 1);
+ dp->d_time = parent_vcache_dv(dip, credp);
d_instantiate(dp, ip);
}
afs_DestroyAttr(vattr);
static int
afs_linux_rename(struct inode *oldip, struct dentry *olddp,
- struct inode *newip, struct dentry *newdp)
+ struct inode *newip, struct dentry *newdp
+#ifdef HAVE_LINUX_INODE_OPERATIONS_RENAME_TAKES_FLAGS
+ , unsigned int flags
+#endif
+ )
{
int code;
cred_t *credp = crref();
const char *newname = newdp->d_name.name;
struct dentry *rehash = NULL;
+#ifdef HAVE_LINUX_INODE_OPERATIONS_RENAME_TAKES_FLAGS
+ if (flags)
+ return -EINVAL; /* no support for new flags yet */
+#endif
+
/* Prevent any new references during rename operation. */
if (!d_unhashed(newdp)) {
cachepage = find_get_page(cachemapping, pageindex);
if (!cachepage) {
if (!newpage)
- newpage = page_cache_alloc_cold(cachemapping);
+ newpage = page_cache_alloc(cachemapping);
if (!newpage) {
code = -ENOMEM;
goto out;
AFS_GLOCK();
goto out;
}
+#if defined(PAGEVEC_INIT_COLD_ARG)
pagevec_init(&lrupv, 0);
+#else
+ pagevec_init(&lrupv);
+#endif
code = afs_linux_read_cache(cacheFp, pp, tdc->f.chunk, &lrupv, NULL);
ancr->offset = auio->uio_offset;
ancr->length = auio->uio_resid;
+#if defined(PAGEVEC_INIT_COLD_ARG)
pagevec_init(&lrupv, 0);
+#else
+ pagevec_init(&lrupv);
+#endif
for(page_ix = 0; page_ix < num_pages; ++page_ix) {
task = afs_pagecopy_init_task();
tdc = NULL;
+#if defined(PAGEVEC_INIT_COLD_ARG)
pagevec_init(&lrupv, 0);
+#else
+ pagevec_init(&lrupv);
+#endif
for (page_idx = 0; page_idx < num_pages; page_idx++) {
struct page *page = list_entry(page_list->prev, struct page, lru);
list_del(&page->lru);
int code;
unsigned int from = pos & (PAGE_SIZE - 1);
- code = afs_linux_commit_write(file, page, from, from + len);
+ code = afs_linux_commit_write(file, page, from, from + copied);
unlock_page(page);
put_page(page);