xref: /openbmc/linux/fs/afs/dir_edit.c (revision 5f8b7d4b2e9604d03ae06f1a2dd5a1f34c33e533)
1b4d0d230SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
263a4681fSDavid Howells /* AFS filesystem directory editing
363a4681fSDavid Howells  *
463a4681fSDavid Howells  * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved.
563a4681fSDavid Howells  * Written by David Howells (dhowells@redhat.com)
663a4681fSDavid Howells  */
763a4681fSDavid Howells 
863a4681fSDavid Howells #include <linux/kernel.h>
963a4681fSDavid Howells #include <linux/fs.h>
1063a4681fSDavid Howells #include <linux/namei.h>
1163a4681fSDavid Howells #include <linux/pagemap.h>
1263a4681fSDavid Howells #include <linux/iversion.h>
1363a4681fSDavid Howells #include "internal.h"
1463a4681fSDavid Howells #include "xdr_fs.h"
1563a4681fSDavid Howells 
1663a4681fSDavid Howells /*
1763a4681fSDavid Howells  * Find a number of contiguous clear bits in a directory block bitmask.
1863a4681fSDavid Howells  *
1963a4681fSDavid Howells  * There are 64 slots, which means we can load the entire bitmap into a
2063a4681fSDavid Howells  * variable.  The first bit doesn't count as it corresponds to the block header
2163a4681fSDavid Howells  * slot.  nr_slots is between 1 and 9.
2263a4681fSDavid Howells  */
afs_find_contig_bits(union afs_xdr_dir_block * block,unsigned int nr_slots)2363a4681fSDavid Howells static int afs_find_contig_bits(union afs_xdr_dir_block *block, unsigned int nr_slots)
2463a4681fSDavid Howells {
2563a4681fSDavid Howells 	u64 bitmap;
2663a4681fSDavid Howells 	u32 mask;
2763a4681fSDavid Howells 	int bit, n;
2863a4681fSDavid Howells 
2963a4681fSDavid Howells 	bitmap  = (u64)block->hdr.bitmap[0] << 0 * 8;
3063a4681fSDavid Howells 	bitmap |= (u64)block->hdr.bitmap[1] << 1 * 8;
3163a4681fSDavid Howells 	bitmap |= (u64)block->hdr.bitmap[2] << 2 * 8;
3263a4681fSDavid Howells 	bitmap |= (u64)block->hdr.bitmap[3] << 3 * 8;
3363a4681fSDavid Howells 	bitmap |= (u64)block->hdr.bitmap[4] << 4 * 8;
3463a4681fSDavid Howells 	bitmap |= (u64)block->hdr.bitmap[5] << 5 * 8;
3563a4681fSDavid Howells 	bitmap |= (u64)block->hdr.bitmap[6] << 6 * 8;
3663a4681fSDavid Howells 	bitmap |= (u64)block->hdr.bitmap[7] << 7 * 8;
3763a4681fSDavid Howells 	bitmap >>= 1; /* The first entry is metadata */
3863a4681fSDavid Howells 	bit = 1;
3963a4681fSDavid Howells 	mask = (1 << nr_slots) - 1;
4063a4681fSDavid Howells 
4163a4681fSDavid Howells 	do {
4263a4681fSDavid Howells 		if (sizeof(unsigned long) == 8)
4363a4681fSDavid Howells 			n = ffz(bitmap);
4463a4681fSDavid Howells 		else
4563a4681fSDavid Howells 			n = ((u32)bitmap) != 0 ?
4663a4681fSDavid Howells 				ffz((u32)bitmap) :
4763a4681fSDavid Howells 				ffz((u32)(bitmap >> 32)) + 32;
4863a4681fSDavid Howells 		bitmap >>= n;
4963a4681fSDavid Howells 		bit += n;
5063a4681fSDavid Howells 
5163a4681fSDavid Howells 		if ((bitmap & mask) == 0) {
5263a4681fSDavid Howells 			if (bit > 64 - nr_slots)
5363a4681fSDavid Howells 				return -1;
5463a4681fSDavid Howells 			return bit;
5563a4681fSDavid Howells 		}
5663a4681fSDavid Howells 
5763a4681fSDavid Howells 		n = __ffs(bitmap);
5863a4681fSDavid Howells 		bitmap >>= n;
5963a4681fSDavid Howells 		bit += n;
6063a4681fSDavid Howells 	} while (bitmap);
6163a4681fSDavid Howells 
6263a4681fSDavid Howells 	return -1;
6363a4681fSDavid Howells }
6463a4681fSDavid Howells 
6563a4681fSDavid Howells /*
6663a4681fSDavid Howells  * Set a number of contiguous bits in the directory block bitmap.
6763a4681fSDavid Howells  */
afs_set_contig_bits(union afs_xdr_dir_block * block,int bit,unsigned int nr_slots)6863a4681fSDavid Howells static void afs_set_contig_bits(union afs_xdr_dir_block *block,
6963a4681fSDavid Howells 				int bit, unsigned int nr_slots)
7063a4681fSDavid Howells {
7151590df4Szhengbin 	u64 mask;
7263a4681fSDavid Howells 
7363a4681fSDavid Howells 	mask = (1 << nr_slots) - 1;
7463a4681fSDavid Howells 	mask <<= bit;
7563a4681fSDavid Howells 
7663a4681fSDavid Howells 	block->hdr.bitmap[0] |= (u8)(mask >> 0 * 8);
7763a4681fSDavid Howells 	block->hdr.bitmap[1] |= (u8)(mask >> 1 * 8);
7863a4681fSDavid Howells 	block->hdr.bitmap[2] |= (u8)(mask >> 2 * 8);
7963a4681fSDavid Howells 	block->hdr.bitmap[3] |= (u8)(mask >> 3 * 8);
8063a4681fSDavid Howells 	block->hdr.bitmap[4] |= (u8)(mask >> 4 * 8);
8163a4681fSDavid Howells 	block->hdr.bitmap[5] |= (u8)(mask >> 5 * 8);
8263a4681fSDavid Howells 	block->hdr.bitmap[6] |= (u8)(mask >> 6 * 8);
8363a4681fSDavid Howells 	block->hdr.bitmap[7] |= (u8)(mask >> 7 * 8);
8463a4681fSDavid Howells }
8563a4681fSDavid Howells 
8663a4681fSDavid Howells /*
8763a4681fSDavid Howells  * Clear a number of contiguous bits in the directory block bitmap.
8863a4681fSDavid Howells  */
afs_clear_contig_bits(union afs_xdr_dir_block * block,int bit,unsigned int nr_slots)8963a4681fSDavid Howells static void afs_clear_contig_bits(union afs_xdr_dir_block *block,
9063a4681fSDavid Howells 				  int bit, unsigned int nr_slots)
9163a4681fSDavid Howells {
9251590df4Szhengbin 	u64 mask;
9363a4681fSDavid Howells 
9463a4681fSDavid Howells 	mask = (1 << nr_slots) - 1;
9563a4681fSDavid Howells 	mask <<= bit;
9663a4681fSDavid Howells 
9763a4681fSDavid Howells 	block->hdr.bitmap[0] &= ~(u8)(mask >> 0 * 8);
9863a4681fSDavid Howells 	block->hdr.bitmap[1] &= ~(u8)(mask >> 1 * 8);
9963a4681fSDavid Howells 	block->hdr.bitmap[2] &= ~(u8)(mask >> 2 * 8);
10063a4681fSDavid Howells 	block->hdr.bitmap[3] &= ~(u8)(mask >> 3 * 8);
10163a4681fSDavid Howells 	block->hdr.bitmap[4] &= ~(u8)(mask >> 4 * 8);
10263a4681fSDavid Howells 	block->hdr.bitmap[5] &= ~(u8)(mask >> 5 * 8);
10363a4681fSDavid Howells 	block->hdr.bitmap[6] &= ~(u8)(mask >> 6 * 8);
10463a4681fSDavid Howells 	block->hdr.bitmap[7] &= ~(u8)(mask >> 7 * 8);
10563a4681fSDavid Howells }
10663a4681fSDavid Howells 
10763a4681fSDavid Howells /*
108255ed636SDavid Howells  * Get a new directory folio.
109255ed636SDavid Howells  */
afs_dir_get_folio(struct afs_vnode * vnode,pgoff_t index)110255ed636SDavid Howells static struct folio *afs_dir_get_folio(struct afs_vnode *vnode, pgoff_t index)
111255ed636SDavid Howells {
112874c8ca1SDavid Howells 	struct address_space *mapping = vnode->netfs.inode.i_mapping;
113255ed636SDavid Howells 	struct folio *folio;
114255ed636SDavid Howells 
115255ed636SDavid Howells 	folio = __filemap_get_folio(mapping, index,
116255ed636SDavid Howells 				    FGP_LOCK | FGP_ACCESSED | FGP_CREAT,
117255ed636SDavid Howells 				    mapping->gfp_mask);
11858f5f669SChristoph Hellwig 	if (IS_ERR(folio)) {
119255ed636SDavid Howells 		clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags);
12058f5f669SChristoph Hellwig 		return NULL;
12158f5f669SChristoph Hellwig 	}
12258f5f669SChristoph Hellwig 	if (!folio_test_private(folio))
123255ed636SDavid Howells 		folio_attach_private(folio, (void *)1);
124255ed636SDavid Howells 	return folio;
125255ed636SDavid Howells }
126255ed636SDavid Howells 
127255ed636SDavid Howells /*
12863a4681fSDavid Howells  * Scan a directory block looking for a dirent of the right name.
12963a4681fSDavid Howells  */
afs_dir_scan_block(const union afs_xdr_dir_block * block,const struct qstr * name,unsigned int blocknum)130*790dc90bSDavid Howells static int afs_dir_scan_block(const union afs_xdr_dir_block *block, const struct qstr *name,
13163a4681fSDavid Howells 			      unsigned int blocknum)
13263a4681fSDavid Howells {
133*790dc90bSDavid Howells 	const union afs_xdr_dirent *de;
13463a4681fSDavid Howells 	u64 bitmap;
13563a4681fSDavid Howells 	int d, len, n;
13663a4681fSDavid Howells 
13763a4681fSDavid Howells 	_enter("");
13863a4681fSDavid Howells 
13963a4681fSDavid Howells 	bitmap  = (u64)block->hdr.bitmap[0] << 0 * 8;
14063a4681fSDavid Howells 	bitmap |= (u64)block->hdr.bitmap[1] << 1 * 8;
14163a4681fSDavid Howells 	bitmap |= (u64)block->hdr.bitmap[2] << 2 * 8;
14263a4681fSDavid Howells 	bitmap |= (u64)block->hdr.bitmap[3] << 3 * 8;
14363a4681fSDavid Howells 	bitmap |= (u64)block->hdr.bitmap[4] << 4 * 8;
14463a4681fSDavid Howells 	bitmap |= (u64)block->hdr.bitmap[5] << 5 * 8;
14563a4681fSDavid Howells 	bitmap |= (u64)block->hdr.bitmap[6] << 6 * 8;
14663a4681fSDavid Howells 	bitmap |= (u64)block->hdr.bitmap[7] << 7 * 8;
14763a4681fSDavid Howells 
14863a4681fSDavid Howells 	for (d = (blocknum == 0 ? AFS_DIR_RESV_BLOCKS0 : AFS_DIR_RESV_BLOCKS);
14963a4681fSDavid Howells 	     d < AFS_DIR_SLOTS_PER_BLOCK;
15063a4681fSDavid Howells 	     d++) {
15163a4681fSDavid Howells 		if (!((bitmap >> d) & 1))
15263a4681fSDavid Howells 			continue;
15363a4681fSDavid Howells 		de = &block->dirents[d];
15463a4681fSDavid Howells 		if (de->u.valid != 1)
15563a4681fSDavid Howells 			continue;
15663a4681fSDavid Howells 
15763a4681fSDavid Howells 		/* The block was NUL-terminated by afs_dir_check_page(). */
15863a4681fSDavid Howells 		len = strlen(de->u.name);
15963a4681fSDavid Howells 		if (len == name->len &&
16063a4681fSDavid Howells 		    memcmp(de->u.name, name->name, name->len) == 0)
16163a4681fSDavid Howells 			return d;
16263a4681fSDavid Howells 
16363a4681fSDavid Howells 		n = round_up(12 + len + 1 + 4, AFS_DIR_DIRENT_SIZE);
16463a4681fSDavid Howells 		n /= AFS_DIR_DIRENT_SIZE;
16563a4681fSDavid Howells 		d += n - 1;
16663a4681fSDavid Howells 	}
16763a4681fSDavid Howells 
16863a4681fSDavid Howells 	return -1;
16963a4681fSDavid Howells }
17063a4681fSDavid Howells 
17163a4681fSDavid Howells /*
17263a4681fSDavid Howells  * Initialise a new directory block.  Note that block 0 is special and contains
17363a4681fSDavid Howells  * some extra metadata.
17463a4681fSDavid Howells  */
afs_edit_init_block(union afs_xdr_dir_block * meta,union afs_xdr_dir_block * block,int block_num)17563a4681fSDavid Howells static void afs_edit_init_block(union afs_xdr_dir_block *meta,
17663a4681fSDavid Howells 				union afs_xdr_dir_block *block, int block_num)
17763a4681fSDavid Howells {
17863a4681fSDavid Howells 	memset(block, 0, sizeof(*block));
17963a4681fSDavid Howells 	block->hdr.npages = htons(1);
18063a4681fSDavid Howells 	block->hdr.magic = AFS_DIR_MAGIC;
18163a4681fSDavid Howells 	block->hdr.bitmap[0] = 1;
18263a4681fSDavid Howells 
18363a4681fSDavid Howells 	if (block_num == 0) {
18463a4681fSDavid Howells 		block->hdr.bitmap[0] = 0xff;
18563a4681fSDavid Howells 		block->hdr.bitmap[1] = 0x1f;
18663a4681fSDavid Howells 		memset(block->meta.alloc_ctrs,
18763a4681fSDavid Howells 		       AFS_DIR_SLOTS_PER_BLOCK,
18863a4681fSDavid Howells 		       sizeof(block->meta.alloc_ctrs));
18963a4681fSDavid Howells 		meta->meta.alloc_ctrs[0] =
19063a4681fSDavid Howells 			AFS_DIR_SLOTS_PER_BLOCK - AFS_DIR_RESV_BLOCKS0;
19163a4681fSDavid Howells 	}
19263a4681fSDavid Howells 
19363a4681fSDavid Howells 	if (block_num < AFS_DIR_BLOCKS_WITH_CTR)
19463a4681fSDavid Howells 		meta->meta.alloc_ctrs[block_num] =
19563a4681fSDavid Howells 			AFS_DIR_SLOTS_PER_BLOCK - AFS_DIR_RESV_BLOCKS;
19663a4681fSDavid Howells }
19763a4681fSDavid Howells 
19863a4681fSDavid Howells /*
19963a4681fSDavid Howells  * Edit a directory's file data to add a new directory entry.  Doing this after
20063a4681fSDavid Howells  * create, mkdir, symlink, link or rename if the data version number is
20163a4681fSDavid Howells  * incremented by exactly one avoids the need to re-download the entire
20263a4681fSDavid Howells  * directory contents.
20363a4681fSDavid Howells  *
20463a4681fSDavid Howells  * The caller must hold the inode locked.
20563a4681fSDavid Howells  */
afs_edit_dir_add(struct afs_vnode * vnode,struct qstr * name,struct afs_fid * new_fid,enum afs_edit_dir_reason why)20663a4681fSDavid Howells void afs_edit_dir_add(struct afs_vnode *vnode,
20763a4681fSDavid Howells 		      struct qstr *name, struct afs_fid *new_fid,
20863a4681fSDavid Howells 		      enum afs_edit_dir_reason why)
20963a4681fSDavid Howells {
21063a4681fSDavid Howells 	union afs_xdr_dir_block *meta, *block;
21163a4681fSDavid Howells 	union afs_xdr_dirent *de;
212255ed636SDavid Howells 	struct folio *folio0, *folio;
21363a4681fSDavid Howells 	unsigned int need_slots, nr_blocks, b;
21463a4681fSDavid Howells 	pgoff_t index;
21563a4681fSDavid Howells 	loff_t i_size;
21663a4681fSDavid Howells 	int slot;
21763a4681fSDavid Howells 
21863a4681fSDavid Howells 	_enter(",,{%d,%s},", name->len, name->name);
21963a4681fSDavid Howells 
220874c8ca1SDavid Howells 	i_size = i_size_read(&vnode->netfs.inode);
22163a4681fSDavid Howells 	if (i_size > AFS_DIR_BLOCK_SIZE * AFS_DIR_MAX_BLOCKS ||
22263a4681fSDavid Howells 	    (i_size & (AFS_DIR_BLOCK_SIZE - 1))) {
22363a4681fSDavid Howells 		clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags);
22463a4681fSDavid Howells 		return;
22563a4681fSDavid Howells 	}
22663a4681fSDavid Howells 
227255ed636SDavid Howells 	folio0 = afs_dir_get_folio(vnode, 0);
228255ed636SDavid Howells 	if (!folio0) {
22963a4681fSDavid Howells 		_leave(" [fgp]");
23063a4681fSDavid Howells 		return;
23163a4681fSDavid Howells 	}
23263a4681fSDavid Howells 
23363a4681fSDavid Howells 	/* Work out how many slots we're going to need. */
234366911cdSDavid Howells 	need_slots = afs_dir_calc_slots(name->len);
23563a4681fSDavid Howells 
236255ed636SDavid Howells 	meta = kmap_local_folio(folio0, 0);
23763a4681fSDavid Howells 	if (i_size == 0)
23863a4681fSDavid Howells 		goto new_directory;
23963a4681fSDavid Howells 	nr_blocks = i_size / AFS_DIR_BLOCK_SIZE;
24063a4681fSDavid Howells 
241255ed636SDavid Howells 	/* Find a block that has sufficient slots available.  Each folio
24263a4681fSDavid Howells 	 * contains two or more directory blocks.
24363a4681fSDavid Howells 	 */
24463a4681fSDavid Howells 	for (b = 0; b < nr_blocks + 1; b++) {
245255ed636SDavid Howells 		/* If the directory extended into a new folio, then we need to
246255ed636SDavid Howells 		 * tack a new folio on the end.
24763a4681fSDavid Howells 		 */
24863a4681fSDavid Howells 		index = b / AFS_DIR_BLOCKS_PER_PAGE;
24963a4681fSDavid Howells 		if (nr_blocks >= AFS_DIR_MAX_BLOCKS)
25063a4681fSDavid Howells 			goto error;
251255ed636SDavid Howells 		if (index >= folio_nr_pages(folio0)) {
252255ed636SDavid Howells 			folio = afs_dir_get_folio(vnode, index);
253255ed636SDavid Howells 			if (!folio)
25463a4681fSDavid Howells 				goto error;
255255ed636SDavid Howells 		} else {
256255ed636SDavid Howells 			folio = folio0;
25763a4681fSDavid Howells 		}
25863a4681fSDavid Howells 
259255ed636SDavid Howells 		block = kmap_local_folio(folio, b * AFS_DIR_BLOCK_SIZE - folio_file_pos(folio));
260255ed636SDavid Howells 
26163a4681fSDavid Howells 		/* Abandon the edit if we got a callback break. */
26263a4681fSDavid Howells 		if (!test_bit(AFS_VNODE_DIR_VALID, &vnode->flags))
26363a4681fSDavid Howells 			goto invalidated;
26463a4681fSDavid Howells 
26563a4681fSDavid Howells 		_debug("block %u: %2u %3u %u",
26663a4681fSDavid Howells 		       b,
26763a4681fSDavid Howells 		       (b < AFS_DIR_BLOCKS_WITH_CTR) ? meta->meta.alloc_ctrs[b] : 99,
26863a4681fSDavid Howells 		       ntohs(block->hdr.npages),
26963a4681fSDavid Howells 		       ntohs(block->hdr.magic));
27063a4681fSDavid Howells 
27163a4681fSDavid Howells 		/* Initialise the block if necessary. */
27263a4681fSDavid Howells 		if (b == nr_blocks) {
27363a4681fSDavid Howells 			_debug("init %u", b);
27463a4681fSDavid Howells 			afs_edit_init_block(meta, block, b);
2759d37e1caSDavid Howells 			afs_set_i_size(vnode, (b + 1) * AFS_DIR_BLOCK_SIZE);
27663a4681fSDavid Howells 		}
27763a4681fSDavid Howells 
278255ed636SDavid Howells 		/* Only lower dir blocks have a counter in the header. */
27963a4681fSDavid Howells 		if (b >= AFS_DIR_BLOCKS_WITH_CTR ||
28063a4681fSDavid Howells 		    meta->meta.alloc_ctrs[b] >= need_slots) {
28163a4681fSDavid Howells 			/* We need to try and find one or more consecutive
28263a4681fSDavid Howells 			 * slots to hold the entry.
28363a4681fSDavid Howells 			 */
28463a4681fSDavid Howells 			slot = afs_find_contig_bits(block, need_slots);
28563a4681fSDavid Howells 			if (slot >= 0) {
28663a4681fSDavid Howells 				_debug("slot %u", slot);
28763a4681fSDavid Howells 				goto found_space;
28863a4681fSDavid Howells 			}
28963a4681fSDavid Howells 		}
29063a4681fSDavid Howells 
291255ed636SDavid Howells 		kunmap_local(block);
292255ed636SDavid Howells 		if (folio != folio0) {
293255ed636SDavid Howells 			folio_unlock(folio);
294255ed636SDavid Howells 			folio_put(folio);
29563a4681fSDavid Howells 		}
29663a4681fSDavid Howells 	}
29763a4681fSDavid Howells 
29863a4681fSDavid Howells 	/* There are no spare slots of sufficient size, yet the operation
29963a4681fSDavid Howells 	 * succeeded.  Download the directory again.
30063a4681fSDavid Howells 	 */
30163a4681fSDavid Howells 	trace_afs_edit_dir(vnode, why, afs_edit_dir_create_nospc, 0, 0, 0, 0, name->name);
30263a4681fSDavid Howells 	clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags);
30363a4681fSDavid Howells 	goto out_unmap;
30463a4681fSDavid Howells 
30563a4681fSDavid Howells new_directory:
30663a4681fSDavid Howells 	afs_edit_init_block(meta, meta, 0);
30763a4681fSDavid Howells 	i_size = AFS_DIR_BLOCK_SIZE;
3089d37e1caSDavid Howells 	afs_set_i_size(vnode, i_size);
30963a4681fSDavid Howells 	slot = AFS_DIR_RESV_BLOCKS0;
310255ed636SDavid Howells 	folio = folio0;
311255ed636SDavid Howells 	block = kmap_local_folio(folio, 0);
31263a4681fSDavid Howells 	nr_blocks = 1;
31363a4681fSDavid Howells 	b = 0;
31463a4681fSDavid Howells 
31563a4681fSDavid Howells found_space:
31663a4681fSDavid Howells 	/* Set the dirent slot. */
31763a4681fSDavid Howells 	trace_afs_edit_dir(vnode, why, afs_edit_dir_create, b, slot,
31863a4681fSDavid Howells 			   new_fid->vnode, new_fid->unique, name->name);
31963a4681fSDavid Howells 	de = &block->dirents[slot];
32063a4681fSDavid Howells 	de->u.valid	= 1;
32163a4681fSDavid Howells 	de->u.unused[0]	= 0;
32263a4681fSDavid Howells 	de->u.hash_next	= 0; // TODO: Really need to maintain this
32363a4681fSDavid Howells 	de->u.vnode	= htonl(new_fid->vnode);
32463a4681fSDavid Howells 	de->u.unique	= htonl(new_fid->unique);
32563a4681fSDavid Howells 	memcpy(de->u.name, name->name, name->len + 1);
32663a4681fSDavid Howells 	de->u.name[name->len] = 0;
32763a4681fSDavid Howells 
32863a4681fSDavid Howells 	/* Adjust the bitmap. */
32963a4681fSDavid Howells 	afs_set_contig_bits(block, slot, need_slots);
330255ed636SDavid Howells 	kunmap_local(block);
331255ed636SDavid Howells 	if (folio != folio0) {
332255ed636SDavid Howells 		folio_unlock(folio);
333255ed636SDavid Howells 		folio_put(folio);
33463a4681fSDavid Howells 	}
33563a4681fSDavid Howells 
33663a4681fSDavid Howells 	/* Adjust the allocation counter. */
33763a4681fSDavid Howells 	if (b < AFS_DIR_BLOCKS_WITH_CTR)
33863a4681fSDavid Howells 		meta->meta.alloc_ctrs[b] -= need_slots;
33963a4681fSDavid Howells 
340874c8ca1SDavid Howells 	inode_inc_iversion_raw(&vnode->netfs.inode);
34163a4681fSDavid Howells 	afs_stat_v(vnode, n_dir_cr);
34263a4681fSDavid Howells 	_debug("Insert %s in %u[%u]", name->name, b, slot);
34363a4681fSDavid Howells 
34463a4681fSDavid Howells out_unmap:
345255ed636SDavid Howells 	kunmap_local(meta);
346255ed636SDavid Howells 	folio_unlock(folio0);
347255ed636SDavid Howells 	folio_put(folio0);
34863a4681fSDavid Howells 	_leave("");
34963a4681fSDavid Howells 	return;
35063a4681fSDavid Howells 
35163a4681fSDavid Howells invalidated:
35263a4681fSDavid Howells 	trace_afs_edit_dir(vnode, why, afs_edit_dir_create_inval, 0, 0, 0, 0, name->name);
35363a4681fSDavid Howells 	clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags);
354255ed636SDavid Howells 	kunmap_local(block);
355255ed636SDavid Howells 	if (folio != folio0) {
356255ed636SDavid Howells 		folio_unlock(folio);
357255ed636SDavid Howells 		folio_put(folio);
35863a4681fSDavid Howells 	}
35963a4681fSDavid Howells 	goto out_unmap;
36063a4681fSDavid Howells 
36163a4681fSDavid Howells error:
36263a4681fSDavid Howells 	trace_afs_edit_dir(vnode, why, afs_edit_dir_create_error, 0, 0, 0, 0, name->name);
36363a4681fSDavid Howells 	clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags);
36463a4681fSDavid Howells 	goto out_unmap;
36563a4681fSDavid Howells }
36663a4681fSDavid Howells 
36763a4681fSDavid Howells /*
36863a4681fSDavid Howells  * Edit a directory's file data to remove a new directory entry.  Doing this
36963a4681fSDavid Howells  * after unlink, rmdir or rename if the data version number is incremented by
37063a4681fSDavid Howells  * exactly one avoids the need to re-download the entire directory contents.
37163a4681fSDavid Howells  *
37263a4681fSDavid Howells  * The caller must hold the inode locked.
37363a4681fSDavid Howells  */
afs_edit_dir_remove(struct afs_vnode * vnode,struct qstr * name,enum afs_edit_dir_reason why)37463a4681fSDavid Howells void afs_edit_dir_remove(struct afs_vnode *vnode,
37563a4681fSDavid Howells 			 struct qstr *name, enum afs_edit_dir_reason why)
37663a4681fSDavid Howells {
37763a4681fSDavid Howells 	union afs_xdr_dir_block *meta, *block;
37863a4681fSDavid Howells 	union afs_xdr_dirent *de;
379255ed636SDavid Howells 	struct folio *folio0, *folio;
38063a4681fSDavid Howells 	unsigned int need_slots, nr_blocks, b;
38163a4681fSDavid Howells 	pgoff_t index;
38263a4681fSDavid Howells 	loff_t i_size;
38363a4681fSDavid Howells 	int slot;
38463a4681fSDavid Howells 
38563a4681fSDavid Howells 	_enter(",,{%d,%s},", name->len, name->name);
38663a4681fSDavid Howells 
387874c8ca1SDavid Howells 	i_size = i_size_read(&vnode->netfs.inode);
38863a4681fSDavid Howells 	if (i_size < AFS_DIR_BLOCK_SIZE ||
38963a4681fSDavid Howells 	    i_size > AFS_DIR_BLOCK_SIZE * AFS_DIR_MAX_BLOCKS ||
39063a4681fSDavid Howells 	    (i_size & (AFS_DIR_BLOCK_SIZE - 1))) {
39163a4681fSDavid Howells 		clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags);
39263a4681fSDavid Howells 		return;
39363a4681fSDavid Howells 	}
39463a4681fSDavid Howells 	nr_blocks = i_size / AFS_DIR_BLOCK_SIZE;
39563a4681fSDavid Howells 
396255ed636SDavid Howells 	folio0 = afs_dir_get_folio(vnode, 0);
397255ed636SDavid Howells 	if (!folio0) {
39863a4681fSDavid Howells 		_leave(" [fgp]");
39963a4681fSDavid Howells 		return;
40063a4681fSDavid Howells 	}
40163a4681fSDavid Howells 
40263a4681fSDavid Howells 	/* Work out how many slots we're going to discard. */
403366911cdSDavid Howells 	need_slots = afs_dir_calc_slots(name->len);
40463a4681fSDavid Howells 
405255ed636SDavid Howells 	meta = kmap_local_folio(folio0, 0);
40663a4681fSDavid Howells 
407255ed636SDavid Howells 	/* Find a block that has sufficient slots available.  Each folio
40863a4681fSDavid Howells 	 * contains two or more directory blocks.
40963a4681fSDavid Howells 	 */
41063a4681fSDavid Howells 	for (b = 0; b < nr_blocks; b++) {
41163a4681fSDavid Howells 		index = b / AFS_DIR_BLOCKS_PER_PAGE;
412255ed636SDavid Howells 		if (index >= folio_nr_pages(folio0)) {
413255ed636SDavid Howells 			folio = afs_dir_get_folio(vnode, index);
414255ed636SDavid Howells 			if (!folio)
41563a4681fSDavid Howells 				goto error;
41663a4681fSDavid Howells 		} else {
417255ed636SDavid Howells 			folio = folio0;
41863a4681fSDavid Howells 		}
41963a4681fSDavid Howells 
420255ed636SDavid Howells 		block = kmap_local_folio(folio, b * AFS_DIR_BLOCK_SIZE - folio_file_pos(folio));
421255ed636SDavid Howells 
42263a4681fSDavid Howells 		/* Abandon the edit if we got a callback break. */
42363a4681fSDavid Howells 		if (!test_bit(AFS_VNODE_DIR_VALID, &vnode->flags))
42463a4681fSDavid Howells 			goto invalidated;
42563a4681fSDavid Howells 
42663a4681fSDavid Howells 		if (b > AFS_DIR_BLOCKS_WITH_CTR ||
42763a4681fSDavid Howells 		    meta->meta.alloc_ctrs[b] <= AFS_DIR_SLOTS_PER_BLOCK - 1 - need_slots) {
42863a4681fSDavid Howells 			slot = afs_dir_scan_block(block, name, b);
42963a4681fSDavid Howells 			if (slot >= 0)
43063a4681fSDavid Howells 				goto found_dirent;
43163a4681fSDavid Howells 		}
43263a4681fSDavid Howells 
433255ed636SDavid Howells 		kunmap_local(block);
434255ed636SDavid Howells 		if (folio != folio0) {
435255ed636SDavid Howells 			folio_unlock(folio);
436255ed636SDavid Howells 			folio_put(folio);
43763a4681fSDavid Howells 		}
43863a4681fSDavid Howells 	}
43963a4681fSDavid Howells 
44063a4681fSDavid Howells 	/* Didn't find the dirent to clobber.  Download the directory again. */
44163a4681fSDavid Howells 	trace_afs_edit_dir(vnode, why, afs_edit_dir_delete_noent,
44263a4681fSDavid Howells 			   0, 0, 0, 0, name->name);
44363a4681fSDavid Howells 	clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags);
44463a4681fSDavid Howells 	goto out_unmap;
44563a4681fSDavid Howells 
44663a4681fSDavid Howells found_dirent:
44763a4681fSDavid Howells 	de = &block->dirents[slot];
44863a4681fSDavid Howells 
44963a4681fSDavid Howells 	trace_afs_edit_dir(vnode, why, afs_edit_dir_delete, b, slot,
45063a4681fSDavid Howells 			   ntohl(de->u.vnode), ntohl(de->u.unique),
45163a4681fSDavid Howells 			   name->name);
45263a4681fSDavid Howells 
45363a4681fSDavid Howells 	memset(de, 0, sizeof(*de) * need_slots);
45463a4681fSDavid Howells 
45563a4681fSDavid Howells 	/* Adjust the bitmap. */
45663a4681fSDavid Howells 	afs_clear_contig_bits(block, slot, need_slots);
457255ed636SDavid Howells 	kunmap_local(block);
458255ed636SDavid Howells 	if (folio != folio0) {
459255ed636SDavid Howells 		folio_unlock(folio);
460255ed636SDavid Howells 		folio_put(folio);
46163a4681fSDavid Howells 	}
46263a4681fSDavid Howells 
46363a4681fSDavid Howells 	/* Adjust the allocation counter. */
46463a4681fSDavid Howells 	if (b < AFS_DIR_BLOCKS_WITH_CTR)
46563a4681fSDavid Howells 		meta->meta.alloc_ctrs[b] += need_slots;
46663a4681fSDavid Howells 
467874c8ca1SDavid Howells 	inode_set_iversion_raw(&vnode->netfs.inode, vnode->status.data_version);
46863a4681fSDavid Howells 	afs_stat_v(vnode, n_dir_rm);
46963a4681fSDavid Howells 	_debug("Remove %s from %u[%u]", name->name, b, slot);
47063a4681fSDavid Howells 
47163a4681fSDavid Howells out_unmap:
472255ed636SDavid Howells 	kunmap_local(meta);
473255ed636SDavid Howells 	folio_unlock(folio0);
474255ed636SDavid Howells 	folio_put(folio0);
47563a4681fSDavid Howells 	_leave("");
47663a4681fSDavid Howells 	return;
47763a4681fSDavid Howells 
47863a4681fSDavid Howells invalidated:
47963a4681fSDavid Howells 	trace_afs_edit_dir(vnode, why, afs_edit_dir_delete_inval,
48063a4681fSDavid Howells 			   0, 0, 0, 0, name->name);
48163a4681fSDavid Howells 	clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags);
482255ed636SDavid Howells 	kunmap_local(block);
483255ed636SDavid Howells 	if (folio != folio0) {
484255ed636SDavid Howells 		folio_unlock(folio);
485255ed636SDavid Howells 		folio_put(folio);
48663a4681fSDavid Howells 	}
48763a4681fSDavid Howells 	goto out_unmap;
48863a4681fSDavid Howells 
48963a4681fSDavid Howells error:
49063a4681fSDavid Howells 	trace_afs_edit_dir(vnode, why, afs_edit_dir_delete_error,
49163a4681fSDavid Howells 			   0, 0, 0, 0, name->name);
49263a4681fSDavid Howells 	clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags);
49363a4681fSDavid Howells 	goto out_unmap;
49463a4681fSDavid Howells }
495*790dc90bSDavid Howells 
496*790dc90bSDavid Howells /*
497*790dc90bSDavid Howells  * Edit a subdirectory that has been moved between directories to update the
498*790dc90bSDavid Howells  * ".." entry.
499*790dc90bSDavid Howells  */
afs_edit_dir_update_dotdot(struct afs_vnode * vnode,struct afs_vnode * new_dvnode,enum afs_edit_dir_reason why)500*790dc90bSDavid Howells void afs_edit_dir_update_dotdot(struct afs_vnode *vnode, struct afs_vnode *new_dvnode,
501*790dc90bSDavid Howells 				enum afs_edit_dir_reason why)
502*790dc90bSDavid Howells {
503*790dc90bSDavid Howells 	union afs_xdr_dir_block *block;
504*790dc90bSDavid Howells 	union afs_xdr_dirent *de;
505*790dc90bSDavid Howells 	struct folio *folio;
506*790dc90bSDavid Howells 	unsigned int nr_blocks, b;
507*790dc90bSDavid Howells 	pgoff_t index;
508*790dc90bSDavid Howells 	loff_t i_size;
509*790dc90bSDavid Howells 	int slot;
510*790dc90bSDavid Howells 
511*790dc90bSDavid Howells 	_enter("");
512*790dc90bSDavid Howells 
513*790dc90bSDavid Howells 	i_size = i_size_read(&vnode->netfs.inode);
514*790dc90bSDavid Howells 	if (i_size < AFS_DIR_BLOCK_SIZE) {
515*790dc90bSDavid Howells 		clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags);
516*790dc90bSDavid Howells 		return;
517*790dc90bSDavid Howells 	}
518*790dc90bSDavid Howells 	nr_blocks = i_size / AFS_DIR_BLOCK_SIZE;
519*790dc90bSDavid Howells 
520*790dc90bSDavid Howells 	/* Find a block that has sufficient slots available.  Each folio
521*790dc90bSDavid Howells 	 * contains two or more directory blocks.
522*790dc90bSDavid Howells 	 */
523*790dc90bSDavid Howells 	for (b = 0; b < nr_blocks; b++) {
524*790dc90bSDavid Howells 		index = b / AFS_DIR_BLOCKS_PER_PAGE;
525*790dc90bSDavid Howells 		folio = afs_dir_get_folio(vnode, index);
526*790dc90bSDavid Howells 		if (!folio)
527*790dc90bSDavid Howells 			goto error;
528*790dc90bSDavid Howells 
529*790dc90bSDavid Howells 		block = kmap_local_folio(folio, b * AFS_DIR_BLOCK_SIZE - folio_pos(folio));
530*790dc90bSDavid Howells 
531*790dc90bSDavid Howells 		/* Abandon the edit if we got a callback break. */
532*790dc90bSDavid Howells 		if (!test_bit(AFS_VNODE_DIR_VALID, &vnode->flags))
533*790dc90bSDavid Howells 			goto invalidated;
534*790dc90bSDavid Howells 
535*790dc90bSDavid Howells 		slot = afs_dir_scan_block(block, &dotdot_name, b);
536*790dc90bSDavid Howells 		if (slot >= 0)
537*790dc90bSDavid Howells 			goto found_dirent;
538*790dc90bSDavid Howells 
539*790dc90bSDavid Howells 		kunmap_local(block);
540*790dc90bSDavid Howells 		folio_unlock(folio);
541*790dc90bSDavid Howells 		folio_put(folio);
542*790dc90bSDavid Howells 	}
543*790dc90bSDavid Howells 
544*790dc90bSDavid Howells 	/* Didn't find the dirent to clobber.  Download the directory again. */
545*790dc90bSDavid Howells 	trace_afs_edit_dir(vnode, why, afs_edit_dir_update_nodd,
546*790dc90bSDavid Howells 			   0, 0, 0, 0, "..");
547*790dc90bSDavid Howells 	clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags);
548*790dc90bSDavid Howells 	goto out;
549*790dc90bSDavid Howells 
550*790dc90bSDavid Howells found_dirent:
551*790dc90bSDavid Howells 	de = &block->dirents[slot];
552*790dc90bSDavid Howells 	de->u.vnode  = htonl(new_dvnode->fid.vnode);
553*790dc90bSDavid Howells 	de->u.unique = htonl(new_dvnode->fid.unique);
554*790dc90bSDavid Howells 
555*790dc90bSDavid Howells 	trace_afs_edit_dir(vnode, why, afs_edit_dir_update_dd, b, slot,
556*790dc90bSDavid Howells 			   ntohl(de->u.vnode), ntohl(de->u.unique), "..");
557*790dc90bSDavid Howells 
558*790dc90bSDavid Howells 	kunmap_local(block);
559*790dc90bSDavid Howells 	folio_unlock(folio);
560*790dc90bSDavid Howells 	folio_put(folio);
561*790dc90bSDavid Howells 	inode_set_iversion_raw(&vnode->netfs.inode, vnode->status.data_version);
562*790dc90bSDavid Howells 
563*790dc90bSDavid Howells out:
564*790dc90bSDavid Howells 	_leave("");
565*790dc90bSDavid Howells 	return;
566*790dc90bSDavid Howells 
567*790dc90bSDavid Howells invalidated:
568*790dc90bSDavid Howells 	kunmap_local(block);
569*790dc90bSDavid Howells 	folio_unlock(folio);
570*790dc90bSDavid Howells 	folio_put(folio);
571*790dc90bSDavid Howells 	trace_afs_edit_dir(vnode, why, afs_edit_dir_update_inval,
572*790dc90bSDavid Howells 			   0, 0, 0, 0, "..");
573*790dc90bSDavid Howells 	clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags);
574*790dc90bSDavid Howells 	goto out;
575*790dc90bSDavid Howells 
576*790dc90bSDavid Howells error:
577*790dc90bSDavid Howells 	trace_afs_edit_dir(vnode, why, afs_edit_dir_update_error,
578*790dc90bSDavid Howells 			   0, 0, 0, 0, "..");
579*790dc90bSDavid Howells 	clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags);
580*790dc90bSDavid Howells 	goto out;
581*790dc90bSDavid Howells }
582