Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

afs: Fix missing subdir edit when renamed between parent dirs

When rename moves an AFS subdirectory between parent directories, the
subdir also needs a bit of editing: the ".." entry needs updating to point
to the new parent (though I don't make use of the info) and the DV needs
incrementing by 1 to reflect the change of content. The server also sends
a callback break notification on the subdirectory if we have one, but we
can take care of recovering the promise next time we access the subdir.

This can be triggered by something like:

mount -t afs %example.com:xfstest.test20 /xfstest.test/
mkdir /xfstest.test/{aaa,bbb,aaa/ccc}
touch /xfstest.test/bbb/ccc/d
mv /xfstest.test/{aaa/ccc,bbb/ccc}
touch /xfstest.test/bbb/ccc/e

When the pathwalk for the second touch hits "ccc", kafs spots that the DV
is incorrect and downloads it again (so the fix is not critical).

Fix this, if the rename target is a directory and the old and new
parents are different, by:

(1) Incrementing the DV number of the target locally.

(2) Editing the ".." entry in the target to refer to its new parent's
vnode ID and uniquifier.

Link: https://lore.kernel.org/r/3340431.1729680010@warthog.procyon.org.uk
Fixes: 63a4681ff39c ("afs: Locally edit directory data for mkdir/create/unlink/...")
cc: David Howells <dhowells@redhat.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: linux-afs@lists.infradead.org
Signed-off-by: David Howells <dhowells@redhat.com>
Signed-off-by: Christian Brauner <brauner@kernel.org>

authored by

David Howells and committed by
Christian Brauner
247d65fb 6b51b9f6

+122 -3
+25
fs/afs/dir.c
··· 12 12 #include <linux/swap.h> 13 13 #include <linux/ctype.h> 14 14 #include <linux/sched.h> 15 + #include <linux/iversion.h> 15 16 #include <linux/task_io_accounting_ops.h> 16 17 #include "internal.h" 17 18 #include "afs_fs.h" ··· 1824 1823 1825 1824 static void afs_rename_success(struct afs_operation *op) 1826 1825 { 1826 + struct afs_vnode *vnode = AFS_FS_I(d_inode(op->dentry)); 1827 + 1827 1828 _enter("op=%08x", op->debug_id); 1828 1829 1829 1830 op->ctime = op->file[0].scb.status.mtime_client; ··· 1834 1831 if (op->file[1].vnode != op->file[0].vnode) { 1835 1832 op->ctime = op->file[1].scb.status.mtime_client; 1836 1833 afs_vnode_commit_status(op, &op->file[1]); 1834 + } 1835 + 1836 + /* If we're moving a subdir between dirs, we need to update 1837 + * its DV counter too as the ".." will be altered. 1838 + */ 1839 + if (S_ISDIR(vnode->netfs.inode.i_mode) && 1840 + op->file[0].vnode != op->file[1].vnode) { 1841 + u64 new_dv; 1842 + 1843 + write_seqlock(&vnode->cb_lock); 1844 + 1845 + new_dv = vnode->status.data_version + 1; 1846 + vnode->status.data_version = new_dv; 1847 + inode_set_iversion_raw(&vnode->netfs.inode, new_dv); 1848 + 1849 + write_sequnlock(&vnode->cb_lock); 1837 1850 } 1838 1851 } 1839 1852 ··· 1891 1872 afs_edit_dir_add(new_dvnode, &new_dentry->d_name, 1892 1873 &vnode->fid, afs_edit_dir_for_rename_2); 1893 1874 } 1875 + 1876 + if (S_ISDIR(vnode->netfs.inode.i_mode) && 1877 + new_dvnode != orig_dvnode && 1878 + test_bit(AFS_VNODE_DIR_VALID, &vnode->flags)) 1879 + afs_edit_dir_update_dotdot(vnode, new_dvnode, 1880 + afs_edit_dir_for_rename_sub); 1894 1881 1895 1882 new_inode = d_inode(new_dentry); 1896 1883 if (new_inode) {
+89 -2
fs/afs/dir_edit.c
··· 127 127 /* 128 128 * Scan a directory block looking for a dirent of the right name. 129 129 */ 130 - static int afs_dir_scan_block(union afs_xdr_dir_block *block, struct qstr *name, 130 + static int afs_dir_scan_block(const union afs_xdr_dir_block *block, const struct qstr *name, 131 131 unsigned int blocknum) 132 132 { 133 - union afs_xdr_dirent *de; 133 + const union afs_xdr_dirent *de; 134 134 u64 bitmap; 135 135 int d, len, n; 136 136 ··· 491 491 0, 0, 0, 0, name->name); 492 492 clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags); 493 493 goto out_unmap; 494 + } 495 + 496 + /* 497 + * Edit a subdirectory that has been moved between directories to update the 498 + * ".." entry. 499 + */ 500 + void afs_edit_dir_update_dotdot(struct afs_vnode *vnode, struct afs_vnode *new_dvnode, 501 + enum afs_edit_dir_reason why) 502 + { 503 + union afs_xdr_dir_block *block; 504 + union afs_xdr_dirent *de; 505 + struct folio *folio; 506 + unsigned int nr_blocks, b; 507 + pgoff_t index; 508 + loff_t i_size; 509 + int slot; 510 + 511 + _enter(""); 512 + 513 + i_size = i_size_read(&vnode->netfs.inode); 514 + if (i_size < AFS_DIR_BLOCK_SIZE) { 515 + clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags); 516 + return; 517 + } 518 + nr_blocks = i_size / AFS_DIR_BLOCK_SIZE; 519 + 520 + /* Find a block that has sufficient slots available. Each folio 521 + * contains two or more directory blocks. 522 + */ 523 + for (b = 0; b < nr_blocks; b++) { 524 + index = b / AFS_DIR_BLOCKS_PER_PAGE; 525 + folio = afs_dir_get_folio(vnode, index); 526 + if (!folio) 527 + goto error; 528 + 529 + block = kmap_local_folio(folio, b * AFS_DIR_BLOCK_SIZE - folio_pos(folio)); 530 + 531 + /* Abandon the edit if we got a callback break. */ 532 + if (!test_bit(AFS_VNODE_DIR_VALID, &vnode->flags)) 533 + goto invalidated; 534 + 535 + slot = afs_dir_scan_block(block, &dotdot_name, b); 536 + if (slot >= 0) 537 + goto found_dirent; 538 + 539 + kunmap_local(block); 540 + folio_unlock(folio); 541 + folio_put(folio); 542 + } 543 + 544 + /* Didn't find the dirent to clobber. Download the directory again. */ 545 + trace_afs_edit_dir(vnode, why, afs_edit_dir_update_nodd, 546 + 0, 0, 0, 0, ".."); 547 + clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags); 548 + goto out; 549 + 550 + found_dirent: 551 + de = &block->dirents[slot]; 552 + de->u.vnode = htonl(new_dvnode->fid.vnode); 553 + de->u.unique = htonl(new_dvnode->fid.unique); 554 + 555 + trace_afs_edit_dir(vnode, why, afs_edit_dir_update_dd, b, slot, 556 + ntohl(de->u.vnode), ntohl(de->u.unique), ".."); 557 + 558 + kunmap_local(block); 559 + folio_unlock(folio); 560 + folio_put(folio); 561 + inode_set_iversion_raw(&vnode->netfs.inode, vnode->status.data_version); 562 + 563 + out: 564 + _leave(""); 565 + return; 566 + 567 + invalidated: 568 + kunmap_local(block); 569 + folio_unlock(folio); 570 + folio_put(folio); 571 + trace_afs_edit_dir(vnode, why, afs_edit_dir_update_inval, 572 + 0, 0, 0, 0, ".."); 573 + clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags); 574 + goto out; 575 + 576 + error: 577 + trace_afs_edit_dir(vnode, why, afs_edit_dir_update_error, 578 + 0, 0, 0, 0, ".."); 579 + clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags); 580 + goto out; 494 581 }
+2
fs/afs/internal.h
··· 1072 1072 extern void afs_edit_dir_add(struct afs_vnode *, struct qstr *, struct afs_fid *, 1073 1073 enum afs_edit_dir_reason); 1074 1074 extern void afs_edit_dir_remove(struct afs_vnode *, struct qstr *, enum afs_edit_dir_reason); 1075 + void afs_edit_dir_update_dotdot(struct afs_vnode *vnode, struct afs_vnode *new_dvnode, 1076 + enum afs_edit_dir_reason why); 1075 1077 1076 1078 /* 1077 1079 * dir_silly.c
+6 -1
include/trace/events/afs.h
··· 331 331 EM(afs_edit_dir_delete, "delete") \ 332 332 EM(afs_edit_dir_delete_error, "d_err ") \ 333 333 EM(afs_edit_dir_delete_inval, "d_invl") \ 334 - E_(afs_edit_dir_delete_noent, "d_nent") 334 + EM(afs_edit_dir_delete_noent, "d_nent") \ 335 + EM(afs_edit_dir_update_dd, "u_ddot") \ 336 + EM(afs_edit_dir_update_error, "u_fail") \ 337 + EM(afs_edit_dir_update_inval, "u_invl") \ 338 + E_(afs_edit_dir_update_nodd, "u_nodd") 335 339 336 340 #define afs_edit_dir_reasons \ 337 341 EM(afs_edit_dir_for_create, "Create") \ ··· 344 340 EM(afs_edit_dir_for_rename_0, "Renam0") \ 345 341 EM(afs_edit_dir_for_rename_1, "Renam1") \ 346 342 EM(afs_edit_dir_for_rename_2, "Renam2") \ 343 + EM(afs_edit_dir_for_rename_sub, "RnmSub") \ 347 344 EM(afs_edit_dir_for_rmdir, "RmDir ") \ 348 345 EM(afs_edit_dir_for_silly_0, "S_Ren0") \ 349 346 EM(afs_edit_dir_for_silly_1, "S_Ren1") \