// SPDX-License-Identifier: GPL-2.0-or-later /* dir.c: AFS filesystem directory handling * * Copyright (C) 2002, 2018 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com)
*/
/* * check that a directory folio is valid
*/ staticbool afs_dir_check_block(struct afs_vnode *dvnode, size_t progress, union afs_xdr_dir_block *block)
{ if (block->hdr.magic != AFS_DIR_MAGIC) {
pr_warn("%s(%lx): [%zx] bad magic %04x\n",
__func__, dvnode->netfs.inode.i_ino,
progress, ntohs(block->hdr.magic));
trace_afs_dir_check_failed(dvnode, progress);
trace_afs_file_error(dvnode, -EIO, afs_file_error_dir_bad_magic); returnfalse;
}
/* Make sure each block is NUL terminated so we can reasonably * use string functions on it. The filenames in the folio * *should* be NUL-terminated anyway.
*/
((u8 *)block)[AFS_DIR_BLOCK_SIZE - 1] = 0;
afs_stat_v(dvnode, n_read_dir); returntrue;
}
/* * Iterate through a kmapped directory segment, checking the content.
*/ static size_t afs_dir_check_step(void *iter_base, size_t progress, size_t len, void *priv, void *priv2)
{ struct afs_vnode *dvnode = priv;
if (WARN_ON_ONCE(progress % AFS_DIR_BLOCK_SIZE ||
len % AFS_DIR_BLOCK_SIZE)) return len;
do { if (!afs_dir_check_block(dvnode, progress, iter_base)) break;
iter_base += AFS_DIR_BLOCK_SIZE;
len -= AFS_DIR_BLOCK_SIZE;
} while (len > 0);
return len;
}
/* * Check all the blocks in a directory.
*/ staticint afs_dir_check(struct afs_vnode *dvnode)
{ struct iov_iter iter; unsignedlonglong i_size = i_size_read(&dvnode->netfs.inode);
size_t checked = 0;
/* AFS requires us to perform the read of a directory synchronously as * a single unit to avoid issues with the directory contents being * changed between reads.
*/
ret = netfs_read_single(&dvnode->netfs.inode, file, &iter); if (ret >= 0) {
i_size = i_size_read(&dvnode->netfs.inode); if (i_size > ret) { /* The content has grown, so we need to expand the * buffer.
*/
ret = -ESTALE;
} elseif (is_dir) { int ret2 = afs_dir_check(dvnode);
if (ret2 < 0)
ret = ret2;
} elseif (i_size < folioq_folio_size(dvnode->directory, 0)) { /* NUL-terminate a symlink. */ char *symlink = kmap_local_folio(folioq_folio(dvnode->directory, 0), 0);
/* * Read the directory into a folio_queue buffer in one go, scrubbing the * previous contents. We return -ESTALE if the caller needs to call us again.
*/
ssize_t afs_read_dir(struct afs_vnode *dvnode, struct file *file)
__acquires(&dvnode->validate_lock)
{
ssize_t ret;
loff_t i_size;
i_size = i_size_read(&dvnode->netfs.inode);
ret = -ERESTARTSYS; if (down_read_killable(&dvnode->validate_lock) < 0) goto error;
/* We only need to reread the data if it became invalid - or if we * haven't read it yet.
*/ if (test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags) &&
test_bit(AFS_VNODE_DIR_READ, &dvnode->flags)) {
ret = i_size; goto valid;
}
up_read(&dvnode->validate_lock); if (down_write_killable(&dvnode->validate_lock) < 0) goto error;
if (!test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags))
afs_invalidate_cache(dvnode, 0);
if (!test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags) ||
!test_bit(AFS_VNODE_DIR_READ, &dvnode->flags)) {
trace_afs_reload_dir(dvnode);
ret = afs_read_single(dvnode, file); if (ret < 0) goto error_unlock;
/* walk through the block, an entry at a time */ for (unsignedint slot = hdr; slot < AFS_DIR_SLOTS_PER_BLOCK; slot = next) { /* skip entries marked unused in the bitmap */ if (!(block->hdr.bitmap[slot / 8] &
(1 << (slot % 8)))) {
_debug("ENT[%x]: Unused", base + slot);
next = slot + 1; if (next >= pos)
ctx->pos = (base + next) * sizeof(union afs_xdr_dirent); continue;
}
/* got a valid entry */
dire = &block->dirents[slot];
nlen = strnlen(dire->u.name,
(unsignedlong)(block + 1) - (unsignedlong)dire->u.name - 1); if (nlen > AFSNAMEMAX - 1) {
_debug("ENT[%x]: Name too long (len %zx)",
base + slot, nlen); return afs_bad(dvnode, afs_file_error_dir_name_too_long);
}
/* skip if starts before the current position */ if (slot < pos) { if (next > pos)
ctx->pos = (base + next) * sizeof(union afs_xdr_dirent); continue;
}
/* found the next entry */ if (!dir_emit(ctx, dire->u.name, nlen,
ntohl(dire->u.vnode),
(ctx->actor == afs_lookup_filldir ||
ctx->actor == afs_lookup_one_filldir)?
ntohl(dire->u.unique) : DT_UNKNOWN)) {
_leave(" = 0 [full]"); return 0;
}
if (ctx.error == -ESTALE)
afs_invalidate_dir(dvnode, afs_dir_invalid_iter_stale); return ctx.error;
}
/* * iterate through the data blob that lists the contents of an AFS directory
*/ staticint afs_dir_iterate(struct inode *dir, struct dir_context *ctx, struct file *file, afs_dataversion_t *_dir_version)
{ struct afs_vnode *dvnode = AFS_FS_I(dir); int retry_limit = 100; int ret;
_enter("{%lu},%llx,,", dir->i_ino, ctx->pos);
do { if (--retry_limit < 0) {
pr_warn("afs_read_dir(): Too many retries\n");
ret = -ESTALE; break;
}
ret = afs_read_dir(dvnode, file); if (ret < 0) { if (ret != -ESTALE) break; if (test_bit(AFS_VNODE_DELETED, &AFS_FS_I(dir)->flags)) {
ret = -ESTALE; break;
} continue;
}
*_dir_version = inode_peek_iversion_raw(dir);
ret = afs_dir_iterate_contents(dir, ctx);
up_read(&dvnode->validate_lock);
} while (ret == -ESTALE);
/* * Search the directory for a single name * - if afs_dir_iterate_block() spots this function, it'll pass the FID * uniquifier through dtype
*/ staticbool afs_lookup_one_filldir(struct dir_context *ctx, constchar *name, int nlen, loff_t fpos, u64 ino, unsigned dtype)
{ struct afs_lookup_one_cookie *cookie =
container_of(ctx, struct afs_lookup_one_cookie, ctx);
/* * Do a lookup of a single name in a directory * - just returns the FID the dentry name maps to if found
*/ staticint afs_do_lookup_one(struct inode *dir, conststruct qstr *name, struct afs_fid *fid,
afs_dataversion_t *_dir_version)
{ struct afs_super_info *as = dir->i_sb->s_fs_info; struct afs_lookup_one_cookie cookie = {
.ctx.actor = afs_lookup_one_filldir,
.name = *name,
.fid.vid = as->volume->vid
}; int ret;
/* * Deal with the result of a successful lookup operation. Turn all the files * into inodes and save the first one - which is the one we actually want.
*/ staticvoid afs_do_lookup_success(struct afs_operation *op)
{ struct afs_vnode_param *vp; struct afs_vnode *vnode; struct inode *inode;
u32 abort_code; int i;
_enter("");
for (i = 0; i < op->nr_files; i++) { switch (i) { case 0:
vp = &op->file[0];
abort_code = vp->scb.status.abort_code; if (abort_code != 0) {
op->call_abort_code = abort_code;
afs_op_set_error(op, afs_abort_to_error(abort_code));
op->cumul_error.abort_code = abort_code;
} break;
case 1:
vp = &op->file[1]; break;
default:
vp = &op->more_files[i - 2]; break;
}
if (vp->scb.status.abort_code)
trace_afs_bulkstat_error(op, &vp->fid, i, vp->scb.status.abort_code); if (!vp->scb.have_status && !vp->scb.have_error) continue;
/* * See if we know that the server we expect to use doesn't support * FS.InlineBulkStatus.
*/ staticbool afs_server_supports_ibulk(struct afs_vnode *dvnode)
{ struct afs_server_list *slist; struct afs_volume *volume = dvnode->volume; struct afs_server *server; bool ret = true; int i;
if (!test_bit(AFS_VOLUME_MAYBE_NO_IBULK, &volume->flags)) returntrue;
for (i = 0; i < slist->nr_servers; i++) {
server = slist->servers[i].server; if (server == dvnode->cb_server) { if (test_bit(AFS_SERVER_FL_NO_IBULK, &server->flags))
ret = false; break;
}
}
rcu_read_unlock(); return ret;
}
/* * Do a lookup in a directory. We make use of bulk lookup to query a slew of * files in one go and create inodes for them. The inode of the file we were * asked for is returned.
*/ staticstruct inode *afs_do_lookup(struct inode *dir, struct dentry *dentry)
{ struct afs_lookup_cookie *cookie; struct afs_vnode_param *vp; struct afs_operation *op; struct afs_vnode *dvnode = AFS_FS_I(dir), *vnode; struct inode *inode = NULL, *ti;
afs_dataversion_t data_version = READ_ONCE(dvnode->status.data_version); bool supports_ibulk; long ret; int i;
cookie = kzalloc(sizeof(struct afs_lookup_cookie), GFP_KERNEL); if (!cookie) return ERR_PTR(-ENOMEM);
for (i = 0; i < ARRAY_SIZE(cookie->fids); i++)
cookie->fids[i].vid = dvnode->fid.vid;
cookie->ctx.actor = afs_lookup_filldir;
cookie->name = dentry->d_name;
cookie->nr_fids = 2; /* slot 1 is saved for the fid we actually want
* and slot 0 for the directory */
/* Search the directory for the named entry using the hash table... */
ret = afs_dir_search(dvnode, &dentry->d_name, &cookie->fids[1], &data_version); if (ret < 0) goto out;
supports_ibulk = afs_server_supports_ibulk(dvnode); if (supports_ibulk) { /* ...then scan linearly from that point for entries to lookup-ahead. */
cookie->ctx.pos = (ret + 1) * AFS_DIR_DIRENT_SIZE;
afs_dir_iterate(dir, &cookie->ctx, NULL, &data_version);
}
/* Check to see if we already have an inode for the primary fid. */
inode = ilookup5(dir->i_sb, cookie->fids[1].vnode,
afs_ilookup5_test_by_fid, &cookie->fids[1]); if (inode) goto out; /* We do */
/* Okay, we didn't find it. We need to query the server - and whilst * we're doing that, we're going to attempt to look up a bunch of other * vnodes also.
*/
op = afs_alloc_operation(NULL, dvnode->volume); if (IS_ERR(op)) {
ret = PTR_ERR(op); goto out;
}
/* Need space for examining all the selected files */ if (op->nr_files > 2) {
op->more_files = kvcalloc(op->nr_files - 2, sizeof(struct afs_vnode_param),
GFP_KERNEL); if (!op->more_files) {
afs_op_nomem(op); goto out_op;
}
for (i = 2; i < op->nr_files; i++) {
vp = &op->more_files[i - 2];
vp->fid = cookie->fids[i];
/* Find any inodes that already exist and get their * callback counters.
*/
ti = ilookup5_nowait(dir->i_sb, vp->fid.vnode,
afs_ilookup5_test_by_fid, &vp->fid); if (!IS_ERR_OR_NULL(ti)) {
vnode = AFS_FS_I(ti);
vp->dv_before = vnode->status.data_version;
vp->cb_break_before = afs_calc_vnode_cb_break(vnode);
vp->vnode = vnode;
vp->put_vnode = true;
vp->speculative = true; /* vnode not locked */
}
}
}
/* Try FS.InlineBulkStatus first. Abort codes for the individual * lookups contained therein are stored in the reply without aborting * the whole operation.
*/
afs_op_set_error(op, -ENOTSUPP); if (supports_ibulk) {
op->ops = &afs_inline_bulk_status_operation;
afs_begin_vnode_operation(op);
afs_wait_for_operation(op);
}
if (afs_op_error(op) == -ENOTSUPP) { /* We could try FS.BulkStatus next, but this aborts the entire * op if any of the lookups fails - so, for the moment, revert * to FS.FetchStatus for op->file[1].
*/
op->fetch_status.which = 1;
op->ops = &afs_lookup_fetch_status_operation;
afs_begin_vnode_operation(op);
afs_wait_for_operation(op);
}
/* * Look up an entry in a directory with @sys substitution.
*/ staticstruct dentry *afs_lookup_atsys(struct inode *dir, struct dentry *dentry)
{ struct afs_sysnames *subs; struct afs_net *net = afs_i2net(dir); struct dentry *ret; char *buf, *p, *name; int len, i;
_enter("");
ret = ERR_PTR(-ENOMEM);
p = buf = kmalloc(AFSNAMEMAX, GFP_KERNEL); if (!buf) goto out_p; if (dentry->d_name.len > 4) {
memcpy(p, dentry->d_name.name, dentry->d_name.len - 4);
p += dentry->d_name.len - 4;
}
/* There is an ordered list of substitutes that we have to try. */
read_lock(&net->sysnames_lock);
subs = net->sysnames;
refcount_inc(&subs->usage);
read_unlock(&net->sysnames_lock);
for (i = 0; i < subs->nr; i++) {
name = subs->subs[i];
len = dentry->d_name.len - 4 + strlen(name); if (len >= AFSNAMEMAX) {
ret = ERR_PTR(-ENAMETOOLONG); goto out_s;
}
strcpy(p, name);
ret = lookup_noperm(&QSTR(buf), dentry->d_parent); if (IS_ERR(ret) || d_is_positive(ret)) goto out_s;
dput(ret);
}
/* We don't want to d_add() the @sys dentry here as we don't want to * the cached dentry to hide changes to the sysnames list.
*/
ret = NULL;
out_s:
afs_put_sysnames(subs);
kfree(buf);
out_p: return ret;
}
/* * look up an entry in a directory
*/ staticstruct dentry *afs_lookup(struct inode *dir, struct dentry *dentry, unsignedint flags)
{ struct afs_vnode *dvnode = AFS_FS_I(dir); struct afs_fid fid = {}; struct inode *inode; struct dentry *d; int ret;
/* * Check the validity of a dentry under RCU conditions.
*/ staticint afs_d_revalidate_rcu(struct afs_vnode *dvnode, struct dentry *dentry)
{ long dir_version, de_version;
_enter("%p", dentry);
if (test_bit(AFS_VNODE_DELETED, &dvnode->flags)) return -ECHILD;
if (!afs_check_validity(dvnode)) return -ECHILD;
/* We only need to invalidate a dentry if the server's copy changed * behind our back. If we made the change, it's no problem. Note that * on a 32-bit system, we only have 32 bits in the dentry to store the * version.
*/
dir_version = (long)READ_ONCE(dvnode->status.data_version);
de_version = (long)READ_ONCE(dentry->d_fsdata); if (de_version != dir_version) {
dir_version = (long)READ_ONCE(dvnode->invalid_before); if (de_version - dir_version < 0) return -ECHILD;
}
return 1; /* Still valid */
}
/* * check that a dentry lookup hit has found a valid entry * - NOTE! the hit can be a negative hit too, so we can't assume we have an * inode
*/ staticint afs_d_revalidate(struct inode *parent_dir, conststruct qstr *name, struct dentry *dentry, unsignedint flags)
{ struct afs_vnode *vnode, *dir = AFS_FS_I(parent_dir); struct afs_fid fid; struct inode *inode; struct key *key;
afs_dataversion_t dir_version, invalid_before; long de_version; int ret;
if (flags & LOOKUP_RCU) return afs_d_revalidate_rcu(dir, dentry);
key = afs_request_key(AFS_FS_S(dentry->d_sb)->volume->cell); if (IS_ERR(key))
key = NULL;
/* validate the parent directory */
ret = afs_validate(dir, key); if (ret == -ERESTARTSYS) {
key_put(key); return ret;
}
if (test_bit(AFS_VNODE_DELETED, &dir->flags)) {
_debug("%pd: parent dir deleted", dentry); goto not_found;
}
/* We only need to invalidate a dentry if the server's copy changed * behind our back. If we made the change, it's no problem. Note that * on a 32-bit system, we only have 32 bits in the dentry to store the * version.
*/
dir_version = dir->status.data_version;
de_version = (long)dentry->d_fsdata; if (de_version == (long)dir_version) goto out_valid_noupdate;
/* search the directory for this vnode */
ret = afs_do_lookup_one(&dir->netfs.inode, name, &fid, &dir_version); switch (ret) { case 0: /* the filename maps to something */ if (d_really_is_negative(dentry)) goto not_found;
inode = d_inode(dentry); if (is_bad_inode(inode)) {
printk("kAFS: afs_d_revalidate: %pd2 has bad inode\n",
dentry); goto not_found;
}
vnode = AFS_FS_I(inode);
/* if the vnode ID has changed, then the dirent points to a
* different file */ if (fid.vnode != vnode->fid.vnode) {
_debug("%pd: dirent changed [%llu != %llu]",
dentry, fid.vnode,
vnode->fid.vnode); goto not_found;
}
/* if the vnode ID uniqifier has changed, then the file has * been deleted and replaced, and the original vnode ID has
* been reused */ if (fid.unique != vnode->fid.unique) {
_debug("%pd: file deleted (uq %u -> %u I:%u)",
dentry, fid.unique,
vnode->fid.unique,
vnode->netfs.inode.i_generation); goto not_found;
} goto out_valid;
case -ENOENT: /* the filename is unknown */
_debug("%pd: dirent not found", dentry); if (d_really_is_positive(dentry)) goto not_found; goto out_valid;
/* * allow the VFS to enquire as to whether a dentry should be unhashed (mustn't * sleep) * - called from dput() when d_count is going to 0. * - return 1 to request dentry be unhashed, 0 otherwise
*/ staticint afs_d_delete(conststruct dentry *dentry)
{
_enter("%pd", dentry);
if (dentry->d_flags & DCACHE_NFSFS_RENAMED) goto zap;
if (d_really_is_positive(dentry) &&
(test_bit(AFS_VNODE_DELETED, &AFS_FS_I(d_inode(dentry))->flags) ||
test_bit(AFS_VNODE_PSEUDODIR, &AFS_FS_I(d_inode(dentry))->flags))) goto zap;
_leave(" = 0 [keep]"); return 0;
zap:
_leave(" = 1 [zap]"); return 1;
}
/* * Clean up sillyrename files on dentry removal.
*/ staticvoid afs_d_iput(struct dentry *dentry, struct inode *inode)
{ if (dentry->d_flags & DCACHE_NFSFS_RENAMED)
afs_silly_iput(dentry, inode);
iput(inode);
}
/* * Create a new inode for create/mkdir/symlink
*/ staticvoid afs_vnode_new_inode(struct afs_operation *op)
{ struct afs_vnode_param *dvp = &op->file[0]; struct afs_vnode_param *vp = &op->file[1]; struct afs_vnode *vnode; struct inode *inode;
_enter("");
ASSERTCMP(afs_op_error(op), ==, 0);
inode = afs_iget(op, vp); if (IS_ERR(inode)) { /* ENOMEM or EINTR at a really inconvenient time - just abandon * the new directory on the server.
*/
afs_op_accumulate_error(op, PTR_ERR(inode), 0); return;
}
/* Try to make sure we have a callback promise on the victim. */ if (d_really_is_positive(dentry)) {
vnode = AFS_FS_I(d_inode(dentry));
ret = afs_validate(vnode, op->key); if (ret < 0) goto error;
}
if (vnode) {
ret = down_write_killable(&vnode->rmdir_lock); if (ret < 0) goto error;
op->file[1].vnode = vnode;
}
ret = afs_do_sync_operation(op);
/* Not all systems that can host afs servers have ENOTEMPTY. */ if (ret == -EEXIST)
ret = -ENOTEMPTY;
out:
afs_dir_unuse_cookie(dvnode, ret); return ret;
error:
ret = afs_put_operation(op); goto out;
}
/* * Remove a link to a file or symlink from a directory. * * If the file was not deleted due to excess hard links, the fileserver will * break the callback promise on the file - if it had one - before it returns * to us, and if it was deleted, it won't * * However, if we didn't have a callback promise outstanding, or it was * outstanding on a different server, then it won't break it either...
*/ staticvoid afs_dir_remove_link(struct afs_operation *op)
{ struct afs_vnode *dvnode = op->file[0].vnode; struct afs_vnode *vnode = op->file[1].vnode; struct dentry *dentry = op->dentry; int ret;
if (afs_op_error(op) ||
(op->file[1].scb.have_status && op->file[1].scb.have_error)) return; if (d_really_is_positive(dentry)) return;
/* Try to make sure we have a callback promise on the victim. */
ret = afs_validate(vnode, op->key); if (ret < 0) {
afs_op_set_error(op, ret); goto error;
}
spin_lock(&dentry->d_lock); if (d_count(dentry) > 1) {
spin_unlock(&dentry->d_lock); /* Start asynchronous writeout of the inode */
write_inode_now(d_inode(dentry), 0);
afs_op_set_error(op, afs_sillyrename(dvnode, vnode, dentry, op->key)); goto error;
} if (!d_unhashed(dentry)) { /* Prevent a race with RCU lookup. */
__d_drop(dentry);
op->unlink.need_rehash = true;
}
spin_unlock(&dentry->d_lock);
/* If there was a conflict with a third party, check the status of the * unlinked vnode.
*/ if (afs_op_error(op) == 0 && (op->flags & AFS_OPERATION_DIR_CONFLICT)) {
op->file[1].update_ctime = false;
op->fetch_status.which = 1;
op->ops = &afs_fetch_status_operation;
afs_begin_vnode_operation(op);
afs_wait_for_operation(op);
}
error:
ret = afs_put_operation(op);
afs_dir_unuse_cookie(dvnode, ret); return ret;
}
/* If we're moving a subdir between dirs, we need to update * its DV counter too as the ".." will be altered.
*/ if (S_ISDIR(vnode->netfs.inode.i_mode) &&
op->file[0].vnode != op->file[1].vnode) {
u64 new_dv;
new_inode = d_inode(new_dentry); if (new_inode) {
spin_lock(&new_inode->i_lock); if (S_ISDIR(new_inode->i_mode))
clear_nlink(new_inode); elseif (new_inode->i_nlink > 0)
drop_nlink(new_inode);
spin_unlock(&new_inode->i_lock);
}
/* Now we can update d_fsdata on the dentries to reflect their * new parent's data_version. * * Note that if we ever implement RENAME_EXCHANGE, we'll have * to update both dentries with opposing dir versions.
*/
afs_update_dentry_version(op, new_dvp, op->dentry);
afs_update_dentry_version(op, new_dvp, op->dentry_2);
d_move(old_dentry, new_dentry);
up_write(&new_dvnode->validate_lock);
fscache_end_operation(&orig_cres); if (new_dvnode != orig_dvnode)
fscache_end_operation(&new_cres);
}
staticvoid afs_rename_put(struct afs_operation *op)
{
_enter("op=%08x", op->debug_id); if (op->rename.rehash)
d_rehash(op->rename.rehash);
dput(op->rename.tmp); if (afs_op_error(op))
d_rehash(op->dentry);
}
/* For non-directories, check whether the target is busy and if so, * make a copy of the dentry and then do a silly-rename. If the * silly-rename succeeds, the copied dentry is hashed and becomes the * new target.
*/ if (d_is_positive(new_dentry) && !d_is_dir(new_dentry)) { /* To prevent any new references to the target during the * rename, we unhash the dentry in advance.
*/ if (!d_unhashed(new_dentry)) {
d_drop(new_dentry);
op->rename.rehash = new_dentry;
}
if (d_count(new_dentry) > 2) { /* copy the target dentry's name */
op->rename.tmp = d_alloc(new_dentry->d_parent,
&new_dentry->d_name); if (!op->rename.tmp) {
afs_op_nomem(op); goto error;
}
ret = afs_sillyrename(new_dvnode,
AFS_FS_I(d_inode(new_dentry)),
new_dentry, op->key); if (ret) {
afs_op_set_error(op, ret); goto error;
}
/* This bit is potentially nasty as there's a potential race with * afs_d_revalidate{,_rcu}(). We have to change d_fsdata on the dentry * to reflect it's new parent's new data_version after the op, but * d_revalidate may see old_dentry between the op having taken place * and the version being updated. * * So drop the old_dentry for now to make other threads go through * lookup instead - which we hold a lock against.
*/
d_drop(old_dentry);
ret = afs_do_sync_operation(op);
out:
afs_dir_unuse_cookie(orig_dvnode, ret); if (new_dvnode != orig_dvnode)
afs_dir_unuse_cookie(new_dvnode, ret); return ret;
error:
ret = afs_put_operation(op); goto out;
}
/* * Write the file contents to the cache as a single blob.
*/ int afs_single_writepages(struct address_space *mapping, struct writeback_control *wbc)
{ struct afs_vnode *dvnode = AFS_FS_I(mapping->host); struct iov_iter iter; bool is_dir = (S_ISDIR(dvnode->netfs.inode.i_mode) &&
!test_bit(AFS_VNODE_MOUNTPOINT, &dvnode->flags)); int ret = 0;
/* Need to lock to prevent the folio queue and folios from being thrown * away.
*/
down_read(&dvnode->validate_lock);