extern struct backing_dev_info *afs_backing_dev_info;
extern struct vcache *afs_globalVp;
-extern int afs_notify_change(struct dentry *dp, struct iattr *iattrp);
/* This function converts a positive error code from AFS into a negative
* code suitable for passing into the Linux VFS layer. It checks that the
}
#ifdef HAVE_LINUX_GENERIC_FILE_AIO_READ
+# ifdef LINUX_HAS_NONVECTOR_AIO
static ssize_t
-afs_linux_aio_read(struct kiocb *iocb, const struct iovec *iov, unsigned long segs, loff_t pos)
+afs_linux_aio_read(struct kiocb *iocb, char __user *buf, size_t bufsize,
+ loff_t pos)
+# else
+static ssize_t
+afs_linux_aio_read(struct kiocb *iocb, const struct iovec *buf,
+ unsigned long bufsize, loff_t pos)
+# endif
{
struct file *fp = iocb->ki_filp;
ssize_t code = 0;
AFS_GLOCK();
afs_Trace4(afs_iclSetp, CM_TRACE_AIOREADOP, ICL_TYPE_POINTER, vcp,
- ICL_TYPE_OFFSET, ICL_HANDLE_OFFSET(pos), ICL_TYPE_INT32, segs, ICL_TYPE_INT32,
- 99999);
+ ICL_TYPE_OFFSET, ICL_HANDLE_OFFSET(pos), ICL_TYPE_INT32,
+ (afs_int32)bufsize, ICL_TYPE_INT32, 99999);
code = afs_linux_VerifyVCache(vcp, NULL);
if (code == 0) {
* so we optimise by not using it */
osi_FlushPages(vcp, NULL); /* ensure stale pages are gone */
AFS_GUNLOCK();
- code = generic_file_aio_read(iocb, iov, segs, pos);
+ code = generic_file_aio_read(iocb, buf, bufsize, pos);
AFS_GLOCK();
}
afs_Trace4(afs_iclSetp, CM_TRACE_AIOREADOP, ICL_TYPE_POINTER, vcp,
- ICL_TYPE_OFFSET, ICL_HANDLE_OFFSET(pos), ICL_TYPE_INT32, segs, ICL_TYPE_INT32,
- code);
+ ICL_TYPE_OFFSET, ICL_HANDLE_OFFSET(pos), ICL_TYPE_INT32,
+ (afs_int32)bufsize, ICL_TYPE_INT32, code);
AFS_GUNLOCK();
return code;
}
* mode. Call fake open/close to ensure we do writes of core dumps.
*/
#ifdef HAVE_LINUX_GENERIC_FILE_AIO_READ
+# ifdef LINUX_HAS_NONVECTOR_AIO
static ssize_t
-afs_linux_aio_write(struct kiocb *iocb, const struct iovec *iov, unsigned long segs, loff_t pos)
+afs_linux_aio_write(struct kiocb *iocb, const char __user *buf, size_t bufsize,
+ loff_t pos)
+# else
+static ssize_t
+afs_linux_aio_write(struct kiocb *iocb, const struct iovec *buf,
+ unsigned long bufsize, loff_t pos)
+# endif
{
ssize_t code = 0;
struct vcache *vcp = VTOAFS(iocb->ki_filp->f_dentry->d_inode);
AFS_GLOCK();
afs_Trace4(afs_iclSetp, CM_TRACE_AIOWRITEOP, ICL_TYPE_POINTER, vcp,
- ICL_TYPE_OFFSET, ICL_HANDLE_OFFSET(pos), ICL_TYPE_INT32, segs, ICL_TYPE_INT32,
+ ICL_TYPE_OFFSET, ICL_HANDLE_OFFSET(pos), ICL_TYPE_INT32,
+ (afs_int32)bufsize, ICL_TYPE_INT32,
(iocb->ki_filp->f_flags & O_APPEND) ? 99998 : 99999);
code = afs_linux_VerifyVCache(vcp, &credp);
ReleaseWriteLock(&vcp->lock);
if (code == 0) {
AFS_GUNLOCK();
- code = generic_file_aio_write(iocb, iov, segs, pos);
+ code = generic_file_aio_write(iocb, buf, bufsize, pos);
AFS_GLOCK();
}
ReleaseWriteLock(&vcp->lock);
afs_Trace4(afs_iclSetp, CM_TRACE_AIOWRITEOP, ICL_TYPE_POINTER, vcp,
- ICL_TYPE_OFFSET, ICL_HANDLE_OFFSET(pos), ICL_TYPE_INT32, segs, ICL_TYPE_INT32,
- code);
+ ICL_TYPE_OFFSET, ICL_HANDLE_OFFSET(pos), ICL_TYPE_INT32,
+ (afs_int32)bufsize, ICL_TYPE_INT32, code);
if (credp)
crfree(credp);
code = -ENOENT;
goto out;
}
- ObtainSharedLock(&avc->lock, 810);
- UpgradeSToWLock(&avc->lock, 811);
+ ObtainWriteLock(&avc->lock, 811);
ObtainReadLock(&tdc->lock);
/*
* Make sure that the data in the cache is current. There are two
&& (tdc->dflags & DFFetching)
&& hsame(avc->f.m.DataVersion, tdc->f.versionNo)) {
ReleaseReadLock(&tdc->lock);
- ReleaseSharedLock(&avc->lock);
+ ReleaseWriteLock(&avc->lock);
afs_osi_Sleep(&tdc->validPos);
- ObtainSharedLock(&avc->lock, 812);
+ ObtainWriteLock(&avc->lock, 812);
ObtainReadLock(&tdc->lock);
}
if (!(avc->f.states & CStatd)
|| !hsame(avc->f.m.DataVersion, tdc->f.versionNo)) {
ReleaseReadLock(&tdc->lock);
- ReleaseSharedLock(&avc->lock);
+ ReleaseWriteLock(&avc->lock);
afs_PutDCache(tdc);
goto tagain;
}
if (!dirpos)
break;
- code = afs_dir_GetVerifiedBlob(tdc, dirpos, &de);
+ code = afs_dir_GetVerifiedBlob(tdc, dirpos, &entry);
if (code) {
afs_warn("Corrupt directory (inode %lx, dirpos %d)",
(unsigned long)&tdc->f.inode, dirpos);
goto out;
}
+ de = (struct DirEntry *)entry.data;
ino = afs_calc_inum (avc->f.fid.Cell, avc->f.fid.Fid.Volume,
ntohl(de->fid.vnode));
len = strlen(de->name);
static int
#if defined(FOP_FSYNC_TAKES_DENTRY)
afs_linux_fsync(struct file *fp, struct dentry *dp, int datasync)
+#elif defined(FOP_FSYNC_TAKES_RANGE)
+afs_linux_fsync(struct file *fp, loff_t start, loff_t end, int datasync)
#else
afs_linux_fsync(struct file *fp, int datasync)
#endif
struct inode *ip = FILE_INODE(fp);
cred_t *credp = crref();
+#if defined(FOP_FSYNC_TAKES_RANGE)
+ mutex_lock(&ip->i_mutex);
+#endif
AFS_GLOCK();
code = afs_fsync(VTOAFS(ip), credp);
AFS_GUNLOCK();
+#if defined(FOP_FSYNC_TAKES_RANGE)
+ mutex_unlock(&ip->i_mutex);
+#endif
crfree(credp);
return afs_convert_code(code);
#endif /* F_GETLK64 && F_GETLK != F_GETLK64 */
AFS_GLOCK();
+ if ((vcp->f.states & CRO)) {
+ if (flp->fl_type == F_WRLCK) {
+ code = EBADF;
+ } else {
+ code = 0;
+ }
+ AFS_GUNLOCK();
+ crfree(credp);
+ return code;
+ }
code = afs_convert_code(afs_lockctl(vcp, &flock, cmd, credp));
AFS_GUNLOCK();
.open = afs_linux_open,
.release = afs_linux_release,
.llseek = default_llseek,
+#ifdef HAVE_LINUX_NOOP_FSYNC
+ .fsync = noop_fsync,
+#else
+ .fsync = simple_sync_file,
+#endif
};
struct file_operations afs_file_fops = {
.llseek = default_llseek,
};
+static struct dentry *
+canonical_dentry(struct inode *ip)
+{
+ struct vcache *vcp = VTOAFS(ip);
+ struct dentry *first = NULL, *ret = NULL, *cur;
+#if defined(D_ALIAS_IS_HLIST)
+ struct hlist_node *p;
+#endif
+
+ /* general strategy:
+ * if vcp->target_link is set, and can be found in ip->i_dentry, use that.
+ * otherwise, use the first dentry in ip->i_dentry.
+ * if ip->i_dentry is empty, use the 'dentry' argument we were given.
+ */
+ /* note that vcp->target_link specifies which dentry to use, but we have
+ * no reference held on that dentry. so, we cannot use or dereference
+ * vcp->target_link itself, since it may have been freed. instead, we only
+ * use it to compare to pointers in the ip->i_dentry list. */
+
+ d_prune_aliases(ip);
+
+# ifdef HAVE_DCACHE_LOCK
+ spin_lock(&dcache_lock);
+# else
+ spin_lock(&ip->i_lock);
+# endif
+
+#if defined(D_ALIAS_IS_HLIST)
+ hlist_for_each_entry(cur, p, &ip->i_dentry, d_alias) {
+#else
+ list_for_each_entry_reverse(cur, &ip->i_dentry, d_alias) {
+#endif
+
+ if (!vcp->target_link || cur == vcp->target_link) {
+ ret = cur;
+ break;
+ }
+
+ if (!first) {
+ first = cur;
+ }
+ }
+ if (!ret && first) {
+ ret = first;
+ }
+
+ vcp->target_link = ret;
+
+# ifdef HAVE_DCACHE_LOCK
+ if (ret) {
+ dget_locked(ret);
+ }
+ spin_unlock(&dcache_lock);
+# else
+ if (ret) {
+ dget(ret);
+ }
+ spin_unlock(&ip->i_lock);
+# endif
+
+ return ret;
+}
/**********************************************************************
* AFS Linux dentry operations
return afs_convert_code(code);
}
+/* vattr_setattr
+ * Set iattr data into vattr. Assume vattr cleared before call.
+ */
+static void
+iattr2vattr(struct vattr *vattrp, struct iattr *iattrp)
+{
+ vattrp->va_mask = iattrp->ia_valid;
+ if (iattrp->ia_valid & ATTR_MODE)
+ vattrp->va_mode = iattrp->ia_mode;
+ if (iattrp->ia_valid & ATTR_UID)
+ vattrp->va_uid = iattrp->ia_uid;
+ if (iattrp->ia_valid & ATTR_GID)
+ vattrp->va_gid = iattrp->ia_gid;
+ if (iattrp->ia_valid & ATTR_SIZE)
+ vattrp->va_size = iattrp->ia_size;
+ if (iattrp->ia_valid & ATTR_ATIME) {
+ vattrp->va_atime.tv_sec = iattrp->ia_atime.tv_sec;
+ vattrp->va_atime.tv_usec = 0;
+ }
+ if (iattrp->ia_valid & ATTR_MTIME) {
+ vattrp->va_mtime.tv_sec = iattrp->ia_mtime.tv_sec;
+ vattrp->va_mtime.tv_usec = 0;
+ }
+ if (iattrp->ia_valid & ATTR_CTIME) {
+ vattrp->va_ctime.tv_sec = iattrp->ia_ctime.tv_sec;
+ vattrp->va_ctime.tv_usec = 0;
+ }
+}
+
+/* vattr2inode
+ * Rewrite the inode cache from the attr. Assumes all vattr fields are valid.
+ */
+void
+vattr2inode(struct inode *ip, struct vattr *vp)
+{
+ ip->i_ino = vp->va_nodeid;
+#ifdef HAVE_LINUX_SET_NLINK
+ set_nlink(ip, vp->va_nlink);
+#else
+ ip->i_nlink = vp->va_nlink;
+#endif
+ ip->i_blocks = vp->va_blocks;
+#ifdef STRUCT_INODE_HAS_I_BLKBITS
+ ip->i_blkbits = AFS_BLKBITS;
+#endif
+#ifdef STRUCT_INODE_HAS_I_BLKSIZE
+ ip->i_blksize = vp->va_blocksize;
+#endif
+ ip->i_rdev = vp->va_rdev;
+ ip->i_mode = vp->va_mode;
+ ip->i_uid = vp->va_uid;
+ ip->i_gid = vp->va_gid;
+ i_size_write(ip, vp->va_size);
+ ip->i_atime.tv_sec = vp->va_atime.tv_sec;
+ ip->i_atime.tv_nsec = 0;
+ ip->i_mtime.tv_sec = vp->va_mtime.tv_sec;
+ /* Set the mtime nanoseconds to the sysname generation number.
+ * This convinces NFS clients that all directories have changed
+ * any time the sysname list changes.
+ */
+ ip->i_mtime.tv_nsec = afs_sysnamegen;
+ ip->i_ctime.tv_sec = vp->va_ctime.tv_sec;
+ ip->i_ctime.tv_nsec = 0;
+}
+
+/* afs_notify_change
+ * Linux version of setattr call. What to change is in the iattr struct.
+ * We need to set bits in both the Linux inode as well as the vcache.
+ */
+static int
+afs_notify_change(struct dentry *dp, struct iattr *iattrp)
+{
+ struct vattr vattr;
+ cred_t *credp = crref();
+ struct inode *ip = dp->d_inode;
+ int code;
+
+ VATTR_NULL(&vattr);
+ iattr2vattr(&vattr, iattrp); /* Convert for AFS vnodeops call. */
+
+ AFS_GLOCK();
+ code = afs_setattr(VTOAFS(ip), &vattr, credp);
+ if (!code) {
+ afs_getattr(VTOAFS(ip), &vattr, credp);
+ vattr2inode(ip, &vattr);
+ }
+ AFS_GUNLOCK();
+ crfree(credp);
+ return afs_convert_code(code);
+}
+
static int
afs_linux_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)
{
* The code here assumes that on entry the global lock is not held
*/
static int
-#ifdef DOP_REVALIDATE_TAKES_NAMEIDATA
+#if defined(DOP_REVALIDATE_TAKES_UNSIGNED)
+afs_linux_dentry_revalidate(struct dentry *dp, unsigned int flags)
+#elif defined(DOP_REVALIDATE_TAKES_NAMEIDATA)
afs_linux_dentry_revalidate(struct dentry *dp, struct nameidata *nd)
#else
afs_linux_dentry_revalidate(struct dentry *dp, int flags)
#ifdef LOOKUP_RCU
/* We don't support RCU path walking */
+# if defined(DOP_REVALIDATE_TAKES_UNSIGNED)
+ if (flags & LOOKUP_RCU)
+# else
if (nd->flags & LOOKUP_RCU)
+# endif
return -ECHILD;
#endif
}
static int
+#if defined(DOP_D_DELETE_TAKES_CONST)
+afs_dentry_delete(const struct dentry *dp)
+#else
afs_dentry_delete(struct dentry *dp)
+#endif
{
if (dp->d_inode && (VTOAFS(dp->d_inode)->f.states & CUnlinked))
return 1; /* bad inode? */
return 0;
}
+#ifdef STRUCT_DENTRY_OPERATIONS_HAS_D_AUTOMOUNT
+static struct vfsmount *
+afs_dentry_automount(afs_linux_path_t *path)
+{
+ struct dentry *target;
+
+ /* avoid symlink resolution limits when resolving; we cannot contribute to
+ * an infinite symlink loop */
+ current->total_link_count--;
+
+ target = canonical_dentry(path->dentry->d_inode);
+
+ if (target == path->dentry) {
+ dput(target);
+ target = NULL;
+ }
+
+ if (target) {
+ dput(path->dentry);
+ path->dentry = target;
+
+ } else {
+ spin_lock(&path->dentry->d_lock);
+ path->dentry->d_flags &= ~DCACHE_NEED_AUTOMOUNT;
+ spin_unlock(&path->dentry->d_lock);
+ }
+
+ return NULL;
+}
+#endif /* STRUCT_DENTRY_OPERATIONS_HAS_D_AUTOMOUNT */
+
struct dentry_operations afs_dentry_operations = {
.d_revalidate = afs_linux_dentry_revalidate,
.d_delete = afs_dentry_delete,
.d_iput = afs_dentry_iput,
+#ifdef STRUCT_DENTRY_OPERATIONS_HAS_D_AUTOMOUNT
+ .d_automount = afs_dentry_automount,
+#endif /* STRUCT_DENTRY_OPERATIONS_HAS_D_AUTOMOUNT */
};
/**********************************************************************
* name is in kernel space at this point.
*/
static int
-#ifdef IOP_CREATE_TAKES_NAMEIDATA
+#if defined(IOP_CREATE_TAKES_BOOL)
+afs_linux_create(struct inode *dip, struct dentry *dp, umode_t mode,
+ bool excl)
+#elif defined(IOP_CREATE_TAKES_UMODE_T)
+afs_linux_create(struct inode *dip, struct dentry *dp, umode_t mode,
+ struct nameidata *nd)
+#elif defined(IOP_CREATE_TAKES_NAMEIDATA)
afs_linux_create(struct inode *dip, struct dentry *dp, int mode,
struct nameidata *nd)
#else
/* afs_linux_lookup */
static struct dentry *
-#ifdef IOP_LOOKUP_TAKES_NAMEIDATA
+#if defined(IOP_LOOKUP_TAKES_UNSIGNED)
+afs_linux_lookup(struct inode *dip, struct dentry *dp,
+ unsigned flags)
+#elif defined(IOP_LOOKUP_TAKES_NAMEIDATA)
afs_linux_lookup(struct inode *dip, struct dentry *dp,
struct nameidata *nd)
#else
if (vcp) {
struct vattr vattr;
+ struct vcache *parent_vc = VTOAFS(dip);
+
+ if (parent_vc == vcp) {
+ /* This is possible if the parent dir is a mountpoint to a volume,
+ * and the dir entry we looked up is a mountpoint to the same
+ * volume. Linux cannot cope with this, so return an error instead
+ * of risking a deadlock or panic. */
+ afs_PutVCache(vcp);
+ code = EDEADLK;
+ AFS_GUNLOCK();
+ goto done;
+ }
ip = AFSTOV(vcp);
afs_getattr(vcp, &vattr, credp);
AFS_GUNLOCK();
if (ip && S_ISDIR(ip->i_mode)) {
+ int retry = 1;
struct dentry *alias;
- /* Try to invalidate an existing alias in favor of our new one */
- alias = d_find_alias(ip);
- /* But not if it's disconnected; then we want d_splice_alias below */
- if (alias && !(alias->d_flags & DCACHE_DISCONNECTED)) {
- if (d_invalidate(alias) == 0) {
- dput(alias);
- } else {
- iput(ip);
- crfree(credp);
- return alias;
+ while (retry) {
+ retry = 0;
+
+ /* Try to invalidate an existing alias in favor of our new one */
+ alias = d_find_alias(ip);
+ /* But not if it's disconnected; then we want d_splice_alias below */
+ if (alias && !(alias->d_flags & DCACHE_DISCONNECTED)) {
+ if (d_invalidate(alias) == 0) {
+ /* there may be more aliases; try again until we run out */
+ retry = 1;
+ }
}
+
+ dput(alias);
}
+
+#ifdef STRUCT_DENTRY_OPERATIONS_HAS_D_AUTOMOUNT
+ ip->i_flags |= S_AUTOMOUNT;
+#endif
}
newdp = d_splice_alias(ip, dp);
+ done:
crfree(credp);
/* It's ok for the file to not be found. That's noted by the caller by
}
static int
+#if defined(IOP_MKDIR_TAKES_UMODE_T)
+afs_linux_mkdir(struct inode *dip, struct dentry *dp, umode_t mode)
+#else
afs_linux_mkdir(struct inode *dip, struct dentry *dp, int mode)
+#endif
{
int code;
cred_t *credp = crref();
if(page_ix == 0) {
offset = page_offset(pp);
- auio->uio_offset = offset;
+ ancr->offset = auio->uio_offset = offset;
base_index = pp->index;
}
iovecp[page_ix].iov_len = PAGE_SIZE;
static inline int
afs_linux_can_bypass(struct inode *ip) {
+
switch(cache_bypass_strategy) {
case NEVER_BYPASS_CACHE:
return 0;
case ALWAYS_BYPASS_CACHE:
return 1;
case LARGE_FILES_BYPASS_CACHE:
- if(i_size_read(ip) > cache_bypass_threshold)
+ if (i_size_read(ip) > cache_bypass_threshold)
return 1;
default:
return 0;
cred_t *credp;
int tmp = 0;
+ /* Check for RCU path walking */
#if defined(IOP_PERMISSION_TAKES_FLAGS)
- /* We don't support RCU path walking */
if (flags & IPERM_FLAG_RCU)
return -ECHILD;
+#elif defined(MAY_NOT_BLOCK)
+ if (mode & MAY_NOT_BLOCK)
+ return -ECHILD;
#endif
credp = crref();
}
#endif
+#ifndef STRUCT_DENTRY_OPERATIONS_HAS_D_AUTOMOUNT
+static void *
+afs_linux_dir_follow_link(struct dentry *dentry, struct nameidata *nd)
+{
+ struct dentry **dpp;
+ struct dentry *target;
+
+ if (current->total_link_count > 0) {
+ /* avoid symlink resolution limits when resolving; we cannot contribute to
+ * an infinite symlink loop */
+ /* only do this for follow_link when total_link_count is positive to be
+ * on the safe side; there is at least one code path in the Linux
+ * kernel where it seems like it may be possible to get here without
+ * total_link_count getting incremented. it is not clear on how that
+ * path is actually reached, but guard against it just to be safe */
+ current->total_link_count--;
+ }
+
+ target = canonical_dentry(dentry->d_inode);
+
+# ifdef STRUCT_NAMEIDATA_HAS_PATH
+ dpp = &nd->path.dentry;
+# else
+ dpp = &nd->dentry;
+# endif
+
+ dput(*dpp);
+
+ if (target) {
+ *dpp = target;
+ } else {
+ *dpp = dget(dentry);
+ }
+
+ nd->last_type = LAST_BIND;
+
+ return NULL;
+}
+#endif /* !STRUCT_DENTRY_OPERATIONS_HAS_D_AUTOMOUNT */
+
static struct inode_operations afs_file_iops = {
.permission = afs_linux_permission,
.rename = afs_linux_rename,
.getattr = afs_linux_getattr,
.permission = afs_linux_permission,
+#ifndef STRUCT_DENTRY_OPERATIONS_HAS_D_AUTOMOUNT
+ .follow_link = afs_linux_dir_follow_link,
+#endif
};
/* We really need a separate symlink set of ops, since do_follow_link()