xfs_dir2_sf.c 36.3 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1
/*
2 3
 * Copyright (c) 2000-2003,2005 Silicon Graphics, Inc.
 * All Rights Reserved.
Linus Torvalds's avatar
Linus Torvalds committed
4
 *
5 6
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
Linus Torvalds's avatar
Linus Torvalds committed
7 8
 * published by the Free Software Foundation.
 *
9 10 11 12
 * This program is distributed in the hope that it would be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
Linus Torvalds's avatar
Linus Torvalds committed
13
 *
14 15 16
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write the Free Software Foundation,
 * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
Linus Torvalds's avatar
Linus Torvalds committed
17 18
 */
#include "xfs.h"
19
#include "xfs_fs.h"
20
#include "xfs_format.h"
21 22
#include "xfs_log_format.h"
#include "xfs_trans_resv.h"
Linus Torvalds's avatar
Linus Torvalds committed
23
#include "xfs_sb.h"
24
#include "xfs_ag.h"
Linus Torvalds's avatar
Linus Torvalds committed
25
#include "xfs_mount.h"
26
#include "xfs_da_format.h"
27
#include "xfs_da_btree.h"
Linus Torvalds's avatar
Linus Torvalds committed
28
#include "xfs_inode.h"
29
#include "xfs_trans.h"
30
#include "xfs_inode_item.h"
Linus Torvalds's avatar
Linus Torvalds committed
31
#include "xfs_error.h"
32
#include "xfs_dir2.h"
33
#include "xfs_dir2_priv.h"
34
#include "xfs_trace.h"
35
#include "xfs_dinode.h"
Linus Torvalds's avatar
Linus Torvalds committed
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58

/*
 * Prototypes for internal functions.
 */
static void xfs_dir2_sf_addname_easy(xfs_da_args_t *args,
				     xfs_dir2_sf_entry_t *sfep,
				     xfs_dir2_data_aoff_t offset,
				     int new_isize);
static void xfs_dir2_sf_addname_hard(xfs_da_args_t *args, int objchange,
				     int new_isize);
static int xfs_dir2_sf_addname_pick(xfs_da_args_t *args, int objchange,
				    xfs_dir2_sf_entry_t **sfepp,
				    xfs_dir2_data_aoff_t *offsetp);
#ifdef DEBUG
static void xfs_dir2_sf_check(xfs_da_args_t *args);
#else
#define	xfs_dir2_sf_check(args)
#endif /* DEBUG */
#if XFS_BIG_INUMS
static void xfs_dir2_sf_toino4(xfs_da_args_t *args);
static void xfs_dir2_sf_toino8(xfs_da_args_t *args);
#endif /* XFS_BIG_INUMS */

59 60 61 62
/*
 * Inode numbers in short-form directories can come in two versions,
 * either 4 bytes or 8 bytes wide.  These helpers deal with the
 * two forms transparently by looking at the headers i8count field.
63 64
 *
 * For 64-bit inode number the most significant byte must be zero.
65 66 67
 */
static xfs_ino_t
xfs_dir2_sf_get_ino(
68
	struct xfs_dir2_sf_hdr	*hdr,
69 70
	xfs_dir2_inou_t		*from)
{
71
	if (hdr->i8count)
72
		return get_unaligned_be64(&from->i8.i) & 0x00ffffffffffffffULL;
73
	else
74
		return get_unaligned_be32(&from->i4.i);
75 76 77 78
}

static void
xfs_dir2_sf_put_ino(
79
	struct xfs_dir2_sf_hdr	*hdr,
80 81 82
	xfs_dir2_inou_t		*to,
	xfs_ino_t		ino)
{
83 84
	ASSERT((ino & 0xff00000000000000ULL) == 0);

85
	if (hdr->i8count)
86
		put_unaligned_be64(ino, &to->i8.i);
87
	else
88
		put_unaligned_be32(ino, &to->i4.i);
89 90 91 92
}

xfs_ino_t
xfs_dir2_sf_get_parent_ino(
93
	struct xfs_dir2_sf_hdr	*hdr)
94
{
95
	return xfs_dir2_sf_get_ino(hdr, &hdr->parent);
96 97
}

98
void
99
xfs_dir2_sf_put_parent_ino(
100
	struct xfs_dir2_sf_hdr	*hdr,
101 102
	xfs_ino_t		ino)
{
103
	xfs_dir2_sf_put_ino(hdr, &hdr->parent, ino);
104 105 106 107
}

/*
 * In short-form directory entries the inode numbers are stored at variable
108 109 110
 * offset behind the entry name. If the entry stores a filetype value, then it
 * sits between the name and the inode number. Hence the inode numbers may only
 * be accessed through the helpers below.
111 112
 */
static xfs_dir2_inou_t *
113 114
xfs_dir3_sfe_inop(
	struct xfs_mount	*mp,
115 116
	struct xfs_dir2_sf_entry *sfep)
{
117 118 119 120
	__uint8_t	*ptr = &sfep->name[sfep->namelen];
	if (xfs_sb_version_hasftype(&mp->m_sb))
		ptr++;
	return (xfs_dir2_inou_t *)ptr;
121 122 123
}

xfs_ino_t
124 125
xfs_dir3_sfe_get_ino(
	struct xfs_mount	*mp,
126
	struct xfs_dir2_sf_hdr	*hdr,
127 128
	struct xfs_dir2_sf_entry *sfep)
{
129
	return xfs_dir2_sf_get_ino(hdr, xfs_dir3_sfe_inop(mp, sfep));
130 131
}

132
void
133 134
xfs_dir3_sfe_put_ino(
	struct xfs_mount	*mp,
135
	struct xfs_dir2_sf_hdr	*hdr,
136 137 138
	struct xfs_dir2_sf_entry *sfep,
	xfs_ino_t		ino)
{
139
	xfs_dir2_sf_put_ino(hdr, xfs_dir3_sfe_inop(mp, sfep), ino);
140 141
}

Linus Torvalds's avatar
Linus Torvalds committed
142 143 144 145 146 147 148 149 150
/*
 * Given a block directory (dp/block), calculate its size as a shortform (sf)
 * directory and a header for the sf directory, if it will fit it the
 * space currently present in the inode.  If it won't fit, the output
 * size is too big (but not accurate).
 */
int						/* size for sf form */
xfs_dir2_block_sfsize(
	xfs_inode_t		*dp,		/* incore inode pointer */
151
	xfs_dir2_data_hdr_t	*hdr,		/* block directory data */
Linus Torvalds's avatar
Linus Torvalds committed
152 153 154 155 156 157 158 159 160 161 162 163 164
	xfs_dir2_sf_hdr_t	*sfhp)		/* output: header for sf form */
{
	xfs_dir2_dataptr_t	addr;		/* data entry address */
	xfs_dir2_leaf_entry_t	*blp;		/* leaf area of the block */
	xfs_dir2_block_tail_t	*btp;		/* tail area of the block */
	int			count;		/* shortform entry count */
	xfs_dir2_data_entry_t	*dep;		/* data entry in the block */
	int			i;		/* block entry index */
	int			i8count;	/* count of big-inode entries */
	int			isdot;		/* entry is "." */
	int			isdotdot;	/* entry is ".." */
	xfs_mount_t		*mp;		/* mount structure pointer */
	int			namelen;	/* total name bytes */
165
	xfs_ino_t		parent = 0;	/* parent inode number */
Linus Torvalds's avatar
Linus Torvalds committed
166
	int			size=0;		/* total computed size */
167
	int			has_ftype;
Linus Torvalds's avatar
Linus Torvalds committed
168 169 170

	mp = dp->i_mount;

171 172 173 174 175 176
	/*
	 * if there is a filetype field, add the extra byte to the namelen
	 * for each entry that we see.
	 */
	has_ftype = xfs_sb_version_hasftype(&mp->m_sb) ? 1 : 0;

Linus Torvalds's avatar
Linus Torvalds committed
177
	count = i8count = namelen = 0;
178
	btp = xfs_dir2_block_tail_p(mp, hdr);
179
	blp = xfs_dir2_block_leaf_p(btp);
Linus Torvalds's avatar
Linus Torvalds committed
180 181 182 183

	/*
	 * Iterate over the block's data entries by using the leaf pointers.
	 */
184
	for (i = 0; i < be32_to_cpu(btp->count); i++) {
185
		if ((addr = be32_to_cpu(blp[i].address)) == XFS_DIR2_NULL_DATAPTR)
Linus Torvalds's avatar
Linus Torvalds committed
186 187 188 189 190
			continue;
		/*
		 * Calculate the pointer to the entry at hand.
		 */
		dep = (xfs_dir2_data_entry_t *)
191
		      ((char *)hdr + xfs_dir2_dataptr_to_off(mp, addr));
Linus Torvalds's avatar
Linus Torvalds committed
192 193 194 195 196 197 198 199 200 201 202
		/*
		 * Detect . and .., so we can special-case them.
		 * . is not included in sf directories.
		 * .. is included by just the parent inode number.
		 */
		isdot = dep->namelen == 1 && dep->name[0] == '.';
		isdotdot =
			dep->namelen == 2 &&
			dep->name[0] == '.' && dep->name[1] == '.';
#if XFS_BIG_INUMS
		if (!isdot)
203
			i8count += be64_to_cpu(dep->inumber) > XFS_DIR2_MAX_SHORT_INUM;
Linus Torvalds's avatar
Linus Torvalds committed
204
#endif
205
		/* take into account the file type field */
Linus Torvalds's avatar
Linus Torvalds committed
206 207
		if (!isdot && !isdotdot) {
			count++;
208
			namelen += dep->namelen + has_ftype;
Linus Torvalds's avatar
Linus Torvalds committed
209
		} else if (isdotdot)
210
			parent = be64_to_cpu(dep->inumber);
Linus Torvalds's avatar
Linus Torvalds committed
211 212 213
		/*
		 * Calculate the new size, see if we should give up yet.
		 */
214
		size = xfs_dir2_sf_hdr_size(i8count) +		/* header */
Linus Torvalds's avatar
Linus Torvalds committed
215 216 217 218 219 220 221 222 223 224 225 226 227 228
		       count +					/* namelen */
		       count * (uint)sizeof(xfs_dir2_sf_off_t) + /* offset */
		       namelen +				/* name */
		       (i8count ?				/* inumber */
				(uint)sizeof(xfs_dir2_ino8_t) * count :
				(uint)sizeof(xfs_dir2_ino4_t) * count);
		if (size > XFS_IFORK_DSIZE(dp))
			return size;		/* size value is a failure */
	}
	/*
	 * Create the output header, if it worked.
	 */
	sfhp->count = count;
	sfhp->i8count = i8count;
229
	xfs_dir2_sf_put_parent_ino(sfhp, parent);
Linus Torvalds's avatar
Linus Torvalds committed
230 231 232 233 234 235 236 237 238 239
	return size;
}

/*
 * Convert a block format directory to shortform.
 * Caller has already checked that it will fit, and built us a header.
 */
int						/* error */
xfs_dir2_block_to_sf(
	xfs_da_args_t		*args,		/* operation arguments */
240
	struct xfs_buf		*bp,
Linus Torvalds's avatar
Linus Torvalds committed
241 242 243
	int			size,		/* shortform directory size */
	xfs_dir2_sf_hdr_t	*sfhp)		/* shortform directory hdr */
{
244
	xfs_dir2_data_hdr_t	*hdr;		/* block header */
Linus Torvalds's avatar
Linus Torvalds committed
245 246 247 248 249 250 251 252 253 254
	xfs_dir2_block_tail_t	*btp;		/* block tail pointer */
	xfs_dir2_data_entry_t	*dep;		/* data entry pointer */
	xfs_inode_t		*dp;		/* incore directory inode */
	xfs_dir2_data_unused_t	*dup;		/* unused data pointer */
	char			*endptr;	/* end of data entries */
	int			error;		/* error return value */
	int			logflags;	/* inode logging flags */
	xfs_mount_t		*mp;		/* filesystem mount point */
	char			*ptr;		/* current data pointer */
	xfs_dir2_sf_entry_t	*sfep;		/* shortform entry */
255
	xfs_dir2_sf_hdr_t	*sfp;		/* shortform directory header */
Linus Torvalds's avatar
Linus Torvalds committed
256

257 258
	trace_xfs_dir2_block_to_sf(args);

Linus Torvalds's avatar
Linus Torvalds committed
259 260 261 262 263 264 265
	dp = args->dp;
	mp = dp->i_mount;

	/*
	 * Make a copy of the block data, so we can shrink the inode
	 * and add local data.
	 */
266
	hdr = kmem_alloc(mp->m_dirblksize, KM_SLEEP);
267
	memcpy(hdr, bp->b_addr, mp->m_dirblksize);
Linus Torvalds's avatar
Linus Torvalds committed
268 269 270 271 272
	logflags = XFS_ILOG_CORE;
	if ((error = xfs_dir2_shrink_inode(args, mp->m_dirdatablk, bp))) {
		ASSERT(error != ENOSPC);
		goto out;
	}
273

Linus Torvalds's avatar
Linus Torvalds committed
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
	/*
	 * The buffer is now unconditionally gone, whether
	 * xfs_dir2_shrink_inode worked or not.
	 *
	 * Convert the inode to local format.
	 */
	dp->i_df.if_flags &= ~XFS_IFEXTENTS;
	dp->i_df.if_flags |= XFS_IFINLINE;
	dp->i_d.di_format = XFS_DINODE_FMT_LOCAL;
	ASSERT(dp->i_df.if_bytes == 0);
	xfs_idata_realloc(dp, size, XFS_DATA_FORK);
	logflags |= XFS_ILOG_DDATA;
	/*
	 * Copy the header into the newly allocate local space.
	 */
289
	sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
290
	memcpy(sfp, sfhp, xfs_dir2_sf_hdr_size(sfhp->i8count));
Linus Torvalds's avatar
Linus Torvalds committed
291 292 293 294
	dp->i_d.di_size = size;
	/*
	 * Set up to loop over the block's entries.
	 */
295
	btp = xfs_dir2_block_tail_p(mp, hdr);
296
	ptr = (char *)xfs_dir3_data_entry_p(hdr);
297 298
	endptr = (char *)xfs_dir2_block_leaf_p(btp);
	sfep = xfs_dir2_sf_firstentry(sfp);
Linus Torvalds's avatar
Linus Torvalds committed
299 300 301 302 303 304 305 306 307
	/*
	 * Loop over the active and unused entries.
	 * Stop when we reach the leaf/tail portion of the block.
	 */
	while (ptr < endptr) {
		/*
		 * If it's unused, just skip over it.
		 */
		dup = (xfs_dir2_data_unused_t *)ptr;
308 309
		if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
			ptr += be16_to_cpu(dup->length);
Linus Torvalds's avatar
Linus Torvalds committed
310 311 312 313 314 315 316
			continue;
		}
		dep = (xfs_dir2_data_entry_t *)ptr;
		/*
		 * Skip .
		 */
		if (dep->namelen == 1 && dep->name[0] == '.')
317
			ASSERT(be64_to_cpu(dep->inumber) == dp->i_ino);
Linus Torvalds's avatar
Linus Torvalds committed
318 319 320 321 322
		/*
		 * Skip .., but make sure the inode number is right.
		 */
		else if (dep->namelen == 2 &&
			 dep->name[0] == '.' && dep->name[1] == '.')
323
			ASSERT(be64_to_cpu(dep->inumber) ==
324
			       xfs_dir2_sf_get_parent_ino(sfp));
Linus Torvalds's avatar
Linus Torvalds committed
325 326 327 328 329
		/*
		 * Normal entry, copy it into shortform.
		 */
		else {
			sfep->namelen = dep->namelen;
330
			xfs_dir2_sf_put_offset(sfep,
Linus Torvalds's avatar
Linus Torvalds committed
331
				(xfs_dir2_data_aoff_t)
332
				((char *)dep - (char *)hdr));
Linus Torvalds's avatar
Linus Torvalds committed
333
			memcpy(sfep->name, dep->name, dep->namelen);
334
			xfs_dir3_sfe_put_ino(mp, sfp, sfep,
335
					     be64_to_cpu(dep->inumber));
336 337
			xfs_dir3_sfe_put_ftype(mp, sfp, sfep,
					xfs_dir3_dirent_get_ftype(mp, dep));
338

339
			sfep = dp->d_ops->sf_nextentry(sfp, sfep);
Linus Torvalds's avatar
Linus Torvalds committed
340
		}
341
		ptr += xfs_dir3_data_entsize(mp, dep->namelen);
Linus Torvalds's avatar
Linus Torvalds committed
342 343 344 345 346
	}
	ASSERT((char *)sfep - (char *)sfp == size);
	xfs_dir2_sf_check(args);
out:
	xfs_trans_log_inode(args->trans, dp, logflags);
347
	kmem_free(hdr);
Linus Torvalds's avatar
Linus Torvalds committed
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
	return error;
}

/*
 * Add a name to a shortform directory.
 * There are two algorithms, "easy" and "hard" which we decide on
 * before changing anything.
 * Convert to block form if necessary, if the new entry won't fit.
 */
int						/* error */
xfs_dir2_sf_addname(
	xfs_da_args_t		*args)		/* operation arguments */
{
	int			add_entsize;	/* size of the new entry */
	xfs_inode_t		*dp;		/* incore directory inode */
	int			error;		/* error return value */
	int			incr_isize;	/* total change in size */
	int			new_isize;	/* di_size after adding name */
	int			objchange;	/* changing to 8-byte inodes */
367
	xfs_dir2_data_aoff_t	offset = 0;	/* offset for new entry */
Linus Torvalds's avatar
Linus Torvalds committed
368 369
	int			old_isize;	/* di_size before adding name */
	int			pick;		/* which algorithm to use */
370
	xfs_dir2_sf_hdr_t	*sfp;		/* shortform structure */
371
	xfs_dir2_sf_entry_t	*sfep = NULL;	/* shortform entry */
Linus Torvalds's avatar
Linus Torvalds committed
372

373 374
	trace_xfs_dir2_sf_addname(args);

Linus Torvalds's avatar
Linus Torvalds committed
375 376 377 378 379 380 381 382 383 384 385 386
	ASSERT(xfs_dir2_sf_lookup(args) == ENOENT);
	dp = args->dp;
	ASSERT(dp->i_df.if_flags & XFS_IFINLINE);
	/*
	 * Make sure the shortform value has some of its header.
	 */
	if (dp->i_d.di_size < offsetof(xfs_dir2_sf_hdr_t, parent)) {
		ASSERT(XFS_FORCED_SHUTDOWN(dp->i_mount));
		return XFS_ERROR(EIO);
	}
	ASSERT(dp->i_df.if_bytes == dp->i_d.di_size);
	ASSERT(dp->i_df.if_u1.if_data != NULL);
387 388
	sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
	ASSERT(dp->i_d.di_size >= xfs_dir2_sf_hdr_size(sfp->i8count));
Linus Torvalds's avatar
Linus Torvalds committed
389 390 391
	/*
	 * Compute entry (and change in) size.
	 */
392
	add_entsize = dp->d_ops->sf_entsize(sfp, args->namelen);
Linus Torvalds's avatar
Linus Torvalds committed
393 394 395 396 397 398
	incr_isize = add_entsize;
	objchange = 0;
#if XFS_BIG_INUMS
	/*
	 * Do we have to change to 8 byte inodes?
	 */
399
	if (args->inumber > XFS_DIR2_MAX_SHORT_INUM && sfp->i8count == 0) {
Linus Torvalds's avatar
Linus Torvalds committed
400 401 402 403 404 405 406
		/*
		 * Yes, adjust the entry size and the total size.
		 */
		add_entsize +=
			(uint)sizeof(xfs_dir2_ino8_t) -
			(uint)sizeof(xfs_dir2_ino4_t);
		incr_isize +=
407
			(sfp->count + 2) *
Linus Torvalds's avatar
Linus Torvalds committed
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
			((uint)sizeof(xfs_dir2_ino8_t) -
			 (uint)sizeof(xfs_dir2_ino4_t));
		objchange = 1;
	}
#endif
	old_isize = (int)dp->i_d.di_size;
	new_isize = old_isize + incr_isize;
	/*
	 * Won't fit as shortform any more (due to size),
	 * or the pick routine says it won't (due to offset values).
	 */
	if (new_isize > XFS_IFORK_DSIZE(dp) ||
	    (pick =
	     xfs_dir2_sf_addname_pick(args, objchange, &sfep, &offset)) == 0) {
		/*
		 * Just checking or no space reservation, it doesn't fit.
		 */
425
		if ((args->op_flags & XFS_DA_OP_JUSTCHECK) || args->total == 0)
Linus Torvalds's avatar
Linus Torvalds committed
426 427 428 429 430 431 432 433 434 435 436 437
			return XFS_ERROR(ENOSPC);
		/*
		 * Convert to block form then add the name.
		 */
		error = xfs_dir2_sf_to_block(args);
		if (error)
			return error;
		return xfs_dir2_block_addname(args);
	}
	/*
	 * Just checking, it fits.
	 */
438
	if (args->op_flags & XFS_DA_OP_JUSTCHECK)
Linus Torvalds's avatar
Linus Torvalds committed
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
		return 0;
	/*
	 * Do it the easy way - just add it at the end.
	 */
	if (pick == 1)
		xfs_dir2_sf_addname_easy(args, sfep, offset, new_isize);
	/*
	 * Do it the hard way - look for a place to insert the new entry.
	 * Convert to 8 byte inode numbers first if necessary.
	 */
	else {
		ASSERT(pick == 2);
#if XFS_BIG_INUMS
		if (objchange)
			xfs_dir2_sf_toino8(args);
#endif
		xfs_dir2_sf_addname_hard(args, objchange, new_isize);
	}
	xfs_trans_log_inode(args->trans, dp, XFS_ILOG_CORE | XFS_ILOG_DDATA);
	return 0;
}

/*
 * Add the new entry the "easy" way.
 * This is copying the old directory and adding the new entry at the end.
 * Since it's sorted by "offset" we need room after the last offset
 * that's already there, and then room to convert to a block directory.
 * This is already checked by the pick routine.
 */
static void
xfs_dir2_sf_addname_easy(
	xfs_da_args_t		*args,		/* operation arguments */
	xfs_dir2_sf_entry_t	*sfep,		/* pointer to new entry */
	xfs_dir2_data_aoff_t	offset,		/* offset to use for new ent */
	int			new_isize)	/* new directory size */
{
	int			byteoff;	/* byte offset in sf dir */
	xfs_inode_t		*dp;		/* incore directory inode */
477
	xfs_dir2_sf_hdr_t	*sfp;		/* shortform structure */
Linus Torvalds's avatar
Linus Torvalds committed
478 479 480

	dp = args->dp;

481
	sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
Linus Torvalds's avatar
Linus Torvalds committed
482 483 484 485
	byteoff = (int)((char *)sfep - (char *)sfp);
	/*
	 * Grow the in-inode space.
	 */
486
	xfs_idata_realloc(dp, dp->d_ops->sf_entsize(sfp, args->namelen),
487
			  XFS_DATA_FORK);
Linus Torvalds's avatar
Linus Torvalds committed
488 489 490
	/*
	 * Need to set up again due to realloc of the inode data.
	 */
491
	sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
Linus Torvalds's avatar
Linus Torvalds committed
492 493 494 495 496
	sfep = (xfs_dir2_sf_entry_t *)((char *)sfp + byteoff);
	/*
	 * Fill in the new entry.
	 */
	sfep->namelen = args->namelen;
497
	xfs_dir2_sf_put_offset(sfep, offset);
Linus Torvalds's avatar
Linus Torvalds committed
498
	memcpy(sfep->name, args->name, sfep->namelen);
499
	xfs_dir3_sfe_put_ino(dp->i_mount, sfp, sfep, args->inumber);
500 501
	xfs_dir3_sfe_put_ftype(dp->i_mount, sfp, sfep, args->filetype);

Linus Torvalds's avatar
Linus Torvalds committed
502 503 504
	/*
	 * Update the header and inode.
	 */
505
	sfp->count++;
Linus Torvalds's avatar
Linus Torvalds committed
506 507
#if XFS_BIG_INUMS
	if (args->inumber > XFS_DIR2_MAX_SHORT_INUM)
508
		sfp->i8count++;
Linus Torvalds's avatar
Linus Torvalds committed
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537
#endif
	dp->i_d.di_size = new_isize;
	xfs_dir2_sf_check(args);
}

/*
 * Add the new entry the "hard" way.
 * The caller has already converted to 8 byte inode numbers if necessary,
 * in which case we need to leave the i8count at 1.
 * Find a hole that the new entry will fit into, and copy
 * the first part of the entries, the new entry, and the last part of
 * the entries.
 */
/* ARGSUSED */
static void
xfs_dir2_sf_addname_hard(
	xfs_da_args_t		*args,		/* operation arguments */
	int			objchange,	/* changing inode number size */
	int			new_isize)	/* new directory size */
{
	int			add_datasize;	/* data size need for new ent */
	char			*buf;		/* buffer for old */
	xfs_inode_t		*dp;		/* incore directory inode */
	int			eof;		/* reached end of old dir */
	int			nbytes;		/* temp for byte copies */
	xfs_dir2_data_aoff_t	new_offset;	/* next offset value */
	xfs_dir2_data_aoff_t	offset;		/* current offset value */
	int			old_isize;	/* previous di_size */
	xfs_dir2_sf_entry_t	*oldsfep;	/* entry in original dir */
538
	xfs_dir2_sf_hdr_t	*oldsfp;	/* original shortform dir */
Linus Torvalds's avatar
Linus Torvalds committed
539
	xfs_dir2_sf_entry_t	*sfep;		/* entry in new dir */
540
	xfs_dir2_sf_hdr_t	*sfp;		/* new shortform dir */
541
	struct xfs_mount	*mp;
Linus Torvalds's avatar
Linus Torvalds committed
542 543 544 545 546

	/*
	 * Copy the old directory to the stack buffer.
	 */
	dp = args->dp;
547
	mp = dp->i_mount;
Linus Torvalds's avatar
Linus Torvalds committed
548

549
	sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
Linus Torvalds's avatar
Linus Torvalds committed
550 551
	old_isize = (int)dp->i_d.di_size;
	buf = kmem_alloc(old_isize, KM_SLEEP);
552
	oldsfp = (xfs_dir2_sf_hdr_t *)buf;
Linus Torvalds's avatar
Linus Torvalds committed
553 554 555 556 557 558
	memcpy(oldsfp, sfp, old_isize);
	/*
	 * Loop over the old directory finding the place we're going
	 * to insert the new entry.
	 * If it's going to end up at the end then oldsfep will point there.
	 */
559
	for (offset = xfs_dir3_data_first_offset(mp),
560
	      oldsfep = xfs_dir2_sf_firstentry(oldsfp),
561
	      add_datasize = xfs_dir3_data_entsize(mp, args->namelen),
Linus Torvalds's avatar
Linus Torvalds committed
562 563
	      eof = (char *)oldsfep == &buf[old_isize];
	     !eof;
564
	     offset = new_offset + xfs_dir3_data_entsize(mp, oldsfep->namelen),
565
	      oldsfep = dp->d_ops->sf_nextentry(oldsfp, oldsfep),
Linus Torvalds's avatar
Linus Torvalds committed
566
	      eof = (char *)oldsfep == &buf[old_isize]) {
567
		new_offset = xfs_dir2_sf_get_offset(oldsfep);
Linus Torvalds's avatar
Linus Torvalds committed
568 569 570 571 572 573 574 575 576 577 578 579 580
		if (offset + add_datasize <= new_offset)
			break;
	}
	/*
	 * Get rid of the old directory, then allocate space for
	 * the new one.  We do this so xfs_idata_realloc won't copy
	 * the data.
	 */
	xfs_idata_realloc(dp, -old_isize, XFS_DATA_FORK);
	xfs_idata_realloc(dp, new_isize, XFS_DATA_FORK);
	/*
	 * Reset the pointer since the buffer was reallocated.
	 */
581
	sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
Linus Torvalds's avatar
Linus Torvalds committed
582 583 584 585 586 587 588 589 590 591
	/*
	 * Copy the first part of the directory, including the header.
	 */
	nbytes = (int)((char *)oldsfep - (char *)oldsfp);
	memcpy(sfp, oldsfp, nbytes);
	sfep = (xfs_dir2_sf_entry_t *)((char *)sfp + nbytes);
	/*
	 * Fill in the new entry, and update the header counts.
	 */
	sfep->namelen = args->namelen;
592
	xfs_dir2_sf_put_offset(sfep, offset);
Linus Torvalds's avatar
Linus Torvalds committed
593
	memcpy(sfep->name, args->name, sfep->namelen);
594
	xfs_dir3_sfe_put_ino(mp, sfp, sfep, args->inumber);
595
	xfs_dir3_sfe_put_ftype(mp, sfp, sfep, args->filetype);
596
	sfp->count++;
Linus Torvalds's avatar
Linus Torvalds committed
597 598
#if XFS_BIG_INUMS
	if (args->inumber > XFS_DIR2_MAX_SHORT_INUM && !objchange)
599
		sfp->i8count++;
Linus Torvalds's avatar
Linus Torvalds committed
600 601 602 603 604
#endif
	/*
	 * If there's more left to copy, do that.
	 */
	if (!eof) {
605
		sfep = dp->d_ops->sf_nextentry(sfp, sfep);
Linus Torvalds's avatar
Linus Torvalds committed
606 607
		memcpy(sfep, oldsfep, old_isize - nbytes);
	}
608
	kmem_free(buf);
Linus Torvalds's avatar
Linus Torvalds committed
609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632
	dp->i_d.di_size = new_isize;
	xfs_dir2_sf_check(args);
}

/*
 * Decide if the new entry will fit at all.
 * If it will fit, pick between adding the new entry to the end (easy)
 * or somewhere else (hard).
 * Return 0 (won't fit), 1 (easy), 2 (hard).
 */
/*ARGSUSED*/
static int					/* pick result */
xfs_dir2_sf_addname_pick(
	xfs_da_args_t		*args,		/* operation arguments */
	int			objchange,	/* inode # size changes */
	xfs_dir2_sf_entry_t	**sfepp,	/* out(1): new entry ptr */
	xfs_dir2_data_aoff_t	*offsetp)	/* out(1): new offset */
{
	xfs_inode_t		*dp;		/* incore directory inode */
	int			holefit;	/* found hole it will fit in */
	int			i;		/* entry number */
	xfs_mount_t		*mp;		/* filesystem mount point */
	xfs_dir2_data_aoff_t	offset;		/* data block offset */
	xfs_dir2_sf_entry_t	*sfep;		/* shortform entry */
633
	xfs_dir2_sf_hdr_t	*sfp;		/* shortform structure */
Linus Torvalds's avatar
Linus Torvalds committed
634 635 636 637 638 639
	int			size;		/* entry's data size */
	int			used;		/* data bytes used */

	dp = args->dp;
	mp = dp->i_mount;

640
	sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
641
	size = xfs_dir3_data_entsize(mp, args->namelen);
642
	offset = xfs_dir3_data_first_offset(mp);
643
	sfep = xfs_dir2_sf_firstentry(sfp);
Linus Torvalds's avatar
Linus Torvalds committed
644 645 646 647 648 649
	holefit = 0;
	/*
	 * Loop over sf entries.
	 * Keep track of data offset and whether we've seen a place
	 * to insert the new entry.
	 */
650
	for (i = 0; i < sfp->count; i++) {
Linus Torvalds's avatar
Linus Torvalds committed
651
		if (!holefit)
652 653
			holefit = offset + size <= xfs_dir2_sf_get_offset(sfep);
		offset = xfs_dir2_sf_get_offset(sfep) +
654
			 xfs_dir3_data_entsize(mp, sfep->namelen);
655
		sfep = dp->d_ops->sf_nextentry(sfp, sfep);
Linus Torvalds's avatar
Linus Torvalds committed
656 657 658 659 660 661
	}
	/*
	 * Calculate data bytes used excluding the new entry, if this
	 * was a data block (block form directory).
	 */
	used = offset +
662
	       (sfp->count + 3) * (uint)sizeof(xfs_dir2_leaf_entry_t) +
Linus Torvalds's avatar
Linus Torvalds committed
663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707
	       (uint)sizeof(xfs_dir2_block_tail_t);
	/*
	 * If it won't fit in a block form then we can't insert it,
	 * we'll go back, convert to block, then try the insert and convert
	 * to leaf.
	 */
	if (used + (holefit ? 0 : size) > mp->m_dirblksize)
		return 0;
	/*
	 * If changing the inode number size, do it the hard way.
	 */
#if XFS_BIG_INUMS
	if (objchange) {
		return 2;
	}
#else
	ASSERT(objchange == 0);
#endif
	/*
	 * If it won't fit at the end then do it the hard way (use the hole).
	 */
	if (used + size > mp->m_dirblksize)
		return 2;
	/*
	 * Do it the easy way.
	 */
	*sfepp = sfep;
	*offsetp = offset;
	return 1;
}

#ifdef DEBUG
/*
 * Check consistency of shortform directory, assert if bad.
 */
static void
xfs_dir2_sf_check(
	xfs_da_args_t		*args)		/* operation arguments */
{
	xfs_inode_t		*dp;		/* incore directory inode */
	int			i;		/* entry number */
	int			i8count;	/* number of big inode#s */
	xfs_ino_t		ino;		/* entry inode number */
	int			offset;		/* data offset */
	xfs_dir2_sf_entry_t	*sfep;		/* shortform dir entry */
708
	xfs_dir2_sf_hdr_t	*sfp;		/* shortform structure */
709
	struct xfs_mount	*mp;
Linus Torvalds's avatar
Linus Torvalds committed
710 711

	dp = args->dp;
712
	mp = dp->i_mount;
Linus Torvalds's avatar
Linus Torvalds committed
713

714
	sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
715
	offset = xfs_dir3_data_first_offset(mp);
716
	ino = xfs_dir2_sf_get_parent_ino(sfp);
Linus Torvalds's avatar
Linus Torvalds committed
717 718
	i8count = ino > XFS_DIR2_MAX_SHORT_INUM;

719
	for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp);
720
	     i < sfp->count;
721
	     i++, sfep = dp->d_ops->sf_nextentry(sfp, sfep)) {
722
		ASSERT(xfs_dir2_sf_get_offset(sfep) >= offset);
723
		ino = xfs_dir3_sfe_get_ino(mp, sfp, sfep);
Linus Torvalds's avatar
Linus Torvalds committed
724 725
		i8count += ino > XFS_DIR2_MAX_SHORT_INUM;
		offset =
726
			xfs_dir2_sf_get_offset(sfep) +
727 728 729
			xfs_dir3_data_entsize(mp, sfep->namelen);
		ASSERT(xfs_dir3_sfe_get_ftype(mp, sfp, sfep) <
							XFS_DIR3_FT_MAX);
Linus Torvalds's avatar
Linus Torvalds committed
730
	}
731
	ASSERT(i8count == sfp->i8count);
Linus Torvalds's avatar
Linus Torvalds committed
732 733 734
	ASSERT(XFS_BIG_INUMS || i8count == 0);
	ASSERT((char *)sfep - (char *)sfp == dp->i_d.di_size);
	ASSERT(offset +
735
	       (sfp->count + 2) * (uint)sizeof(xfs_dir2_leaf_entry_t) +
736
	       (uint)sizeof(xfs_dir2_block_tail_t) <= mp->m_dirblksize);
Linus Torvalds's avatar
Linus Torvalds committed
737 738 739 740 741 742 743 744 745 746 747 748 749
}
#endif	/* DEBUG */

/*
 * Create a new (shortform) directory.
 */
int					/* error, always 0 */
xfs_dir2_sf_create(
	xfs_da_args_t	*args,		/* operation arguments */
	xfs_ino_t	pino)		/* parent inode number */
{
	xfs_inode_t	*dp;		/* incore directory inode */
	int		i8count;	/* parent inode is an 8-byte number */
750
	xfs_dir2_sf_hdr_t *sfp;		/* shortform structure */
Linus Torvalds's avatar
Linus Torvalds committed
751 752
	int		size;		/* directory size */

753 754
	trace_xfs_dir2_sf_create(args);

Linus Torvalds's avatar
Linus Torvalds committed
755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771
	dp = args->dp;

	ASSERT(dp != NULL);
	ASSERT(dp->i_d.di_size == 0);
	/*
	 * If it's currently a zero-length extent file,
	 * convert it to local format.
	 */
	if (dp->i_d.di_format == XFS_DINODE_FMT_EXTENTS) {
		dp->i_df.if_flags &= ~XFS_IFEXTENTS;	/* just in case */
		dp->i_d.di_format = XFS_DINODE_FMT_LOCAL;
		xfs_trans_log_inode(args->trans, dp, XFS_ILOG_CORE);
		dp->i_df.if_flags |= XFS_IFINLINE;
	}
	ASSERT(dp->i_df.if_flags & XFS_IFINLINE);
	ASSERT(dp->i_df.if_bytes == 0);
	i8count = pino > XFS_DIR2_MAX_SHORT_INUM;
772
	size = xfs_dir2_sf_hdr_size(i8count);
Linus Torvalds's avatar
Linus Torvalds committed
773 774 775 776 777 778 779
	/*
	 * Make a buffer for the data.
	 */
	xfs_idata_realloc(dp, size, XFS_DATA_FORK);
	/*
	 * Fill in the header,
	 */
780 781
	sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
	sfp->i8count = i8count;
Linus Torvalds's avatar
Linus Torvalds committed
782 783 784
	/*
	 * Now can put in the inode number, since i8count is set.
	 */
785
	xfs_dir2_sf_put_parent_ino(sfp, pino);
786
	sfp->count = 0;
Linus Torvalds's avatar
Linus Torvalds committed
787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802
	dp->i_d.di_size = size;
	xfs_dir2_sf_check(args);
	xfs_trans_log_inode(args->trans, dp, XFS_ILOG_CORE | XFS_ILOG_DDATA);
	return 0;
}

/*
 * Lookup an entry in a shortform directory.
 * Returns EEXIST if found, ENOENT if not found.
 */
int						/* error */
xfs_dir2_sf_lookup(
	xfs_da_args_t		*args)		/* operation arguments */
{
	xfs_inode_t		*dp;		/* incore directory inode */
	int			i;		/* entry index */
803
	int			error;
Linus Torvalds's avatar
Linus Torvalds committed
804
	xfs_dir2_sf_entry_t	*sfep;		/* shortform directory entry */
805
	xfs_dir2_sf_hdr_t	*sfp;		/* shortform structure */
806
	enum xfs_dacmp		cmp;		/* comparison result */
807
	xfs_dir2_sf_entry_t	*ci_sfep;	/* case-insens. entry */
Linus Torvalds's avatar
Linus Torvalds committed
808

809 810
	trace_xfs_dir2_sf_lookup(args);

Linus Torvalds's avatar
Linus Torvalds committed
811 812 813 814 815 816 817 818 819 820 821 822 823
	xfs_dir2_sf_check(args);
	dp = args->dp;

	ASSERT(dp->i_df.if_flags & XFS_IFINLINE);
	/*
	 * Bail out if the directory is way too short.
	 */
	if (dp->i_d.di_size < offsetof(xfs_dir2_sf_hdr_t, parent)) {
		ASSERT(XFS_FORCED_SHUTDOWN(dp->i_mount));
		return XFS_ERROR(EIO);
	}
	ASSERT(dp->i_df.if_bytes == dp->i_d.di_size);
	ASSERT(dp->i_df.if_u1.if_data != NULL);
824 825
	sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
	ASSERT(dp->i_d.di_size >= xfs_dir2_sf_hdr_size(sfp->i8count));
Linus Torvalds's avatar
Linus Torvalds committed
826 827 828 829 830
	/*
	 * Special case for .
	 */
	if (args->namelen == 1 && args->name[0] == '.') {
		args->inumber = dp->i_ino;
831
		args->cmpresult = XFS_CMP_EXACT;
832
		args->filetype = XFS_DIR3_FT_DIR;
Linus Torvalds's avatar
Linus Torvalds committed
833 834 835 836 837 838 839
		return XFS_ERROR(EEXIST);
	}
	/*
	 * Special case for ..
	 */
	if (args->namelen == 2 &&
	    args->name[0] == '.' && args->name[1] == '.') {
840
		args->inumber = xfs_dir2_sf_get_parent_ino(sfp);
841
		args->cmpresult = XFS_CMP_EXACT;
842
		args->filetype = XFS_DIR3_FT_DIR;
Linus Torvalds's avatar
Linus Torvalds committed
843 844 845 846 847
		return XFS_ERROR(EEXIST);
	}
	/*
	 * Loop over all the entries trying to match ours.
	 */
848
	ci_sfep = NULL;
849
	for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp); i < sfp->count;
850
	     i++, sfep = dp->d_ops->sf_nextentry(sfp, sfep)) {
851 852 853 854 855 856 857 858 859
		/*
		 * Compare name and if it's an exact match, return the inode
		 * number. If it's the first case-insensitive match, store the
		 * inode number and continue looking for an exact match.
		 */
		cmp = dp->i_mount->m_dirnameops->compname(args, sfep->name,
								sfep->namelen);
		if (cmp != XFS_CMP_DIFFERENT && cmp != args->cmpresult) {
			args->cmpresult = cmp;
860 861
			args->inumber = xfs_dir3_sfe_get_ino(dp->i_mount,
							     sfp, sfep);
862 863
			args->filetype = xfs_dir3_sfe_get_ftype(dp->i_mount,
								sfp, sfep);
864 865
			if (cmp == XFS_CMP_EXACT)
				return XFS_ERROR(EEXIST);
866
			ci_sfep = sfep;
Linus Torvalds's avatar
Linus Torvalds committed
867 868
		}
	}
869
	ASSERT(args->op_flags & XFS_DA_OP_OKNOENT);
870 871
	/*
	 * Here, we can only be doing a lookup (not a rename or replace).
872
	 * If a case-insensitive match was not found, return ENOENT.
873
	 */
874 875 876 877 878
	if (!ci_sfep)
		return XFS_ERROR(ENOENT);
	/* otherwise process the CI match as required by the caller */
	error = xfs_dir_cilookup_result(args, ci_sfep->name, ci_sfep->namelen);
	return XFS_ERROR(error);
Linus Torvalds's avatar
Linus Torvalds committed
879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894
}

/*
 * Remove an entry from a shortform directory.
 */
int						/* error */
xfs_dir2_sf_removename(
	xfs_da_args_t		*args)
{
	int			byteoff;	/* offset of removed entry */
	xfs_inode_t		*dp;		/* incore directory inode */
	int			entsize;	/* this entry's size */
	int			i;		/* shortform entry index */
	int			newsize;	/* new inode size */
	int			oldsize;	/* old inode size */
	xfs_dir2_sf_entry_t	*sfep;		/* shortform directory entry */
895
	xfs_dir2_sf_hdr_t	*sfp;		/* shortform structure */
Linus Torvalds's avatar
Linus Torvalds committed
896

897 898
	trace_xfs_dir2_sf_removename(args);

Linus Torvalds's avatar
Linus Torvalds committed
899 900 901 902 903 904 905 906 907 908 909 910 911
	dp = args->dp;

	ASSERT(dp->i_df.if_flags & XFS_IFINLINE);
	oldsize = (int)dp->i_d.di_size;
	/*
	 * Bail out if the directory is way too short.
	 */
	if (oldsize < offsetof(xfs_dir2_sf_hdr_t, parent)) {
		ASSERT(XFS_FORCED_SHUTDOWN(dp->i_mount));
		return XFS_ERROR(EIO);
	}
	ASSERT(dp->i_df.if_bytes == oldsize);
	ASSERT(dp->i_df.if_u1.if_data != NULL);
912 913
	sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
	ASSERT(oldsize >= xfs_dir2_sf_hdr_size(sfp->i8count));
Linus Torvalds's avatar
Linus Torvalds committed
914 915 916 917
	/*
	 * Loop over the old directory entries.
	 * Find the one we're deleting.
	 */
918
	for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp); i < sfp->count;
919
	     i++, sfep = dp->d_ops->sf_nextentry(sfp, sfep)) {
920 921
		if (xfs_da_compname(args, sfep->name, sfep->namelen) ==
								XFS_CMP_EXACT) {
922
			ASSERT(xfs_dir3_sfe_get_ino(dp->i_mount, sfp, sfep) ==
923
			       args->inumber);
Linus Torvalds's avatar
Linus Torvalds committed
924 925 926 927 928 929
			break;
		}
	}
	/*
	 * Didn't find it.
	 */
930
	if (i == sfp->count)
Linus Torvalds's avatar
Linus Torvalds committed
931 932 933 934 935
		return XFS_ERROR(ENOENT);
	/*
	 * Calculate sizes.
	 */
	byteoff = (int)((char *)sfep - (char *)sfp);
936
	entsize = dp->d_ops->sf_entsize(sfp, args->namelen);
Linus Torvalds's avatar
Linus Torvalds committed
937 938 939 940 941 942 943 944 945 946
	newsize = oldsize - entsize;
	/*
	 * Copy the part if any after the removed entry, sliding it down.
	 */
	if (byteoff + entsize < oldsize)
		memmove((char *)sfp + byteoff, (char *)sfp + byteoff + entsize,
			oldsize - (byteoff + entsize));
	/*
	 * Fix up the header and file size.
	 */
947
	sfp->count--;
Linus Torvalds's avatar
Linus Torvalds committed
948 949 950 951 952
	dp->i_d.di_size = newsize;
	/*
	 * Reallocate, making it smaller.
	 */
	xfs_idata_realloc(dp, newsize - oldsize, XFS_DATA_FORK);
953
	sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
Linus Torvalds's avatar
Linus Torvalds committed
954 955 956 957 958
#if XFS_BIG_INUMS
	/*
	 * Are we changing inode number size?
	 */
	if (args->inumber > XFS_DIR2_MAX_SHORT_INUM) {
959
		if (sfp->i8count == 1)
Linus Torvalds's avatar
Linus Torvalds committed
960 961
			xfs_dir2_sf_toino4(args);
		else
962
			sfp->i8count--;
Linus Torvalds's avatar
Linus Torvalds committed
963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985
	}
#endif
	xfs_dir2_sf_check(args);
	xfs_trans_log_inode(args->trans, dp, XFS_ILOG_CORE | XFS_ILOG_DDATA);
	return 0;
}

/*
 * Replace the inode number of an entry in a shortform directory.
 */
int						/* error */
xfs_dir2_sf_replace(
	xfs_da_args_t		*args)		/* operation arguments */
{
	xfs_inode_t		*dp;		/* incore directory inode */
	int			i;		/* entry index */
#if XFS_BIG_INUMS || defined(DEBUG)
	xfs_ino_t		ino=0;		/* entry old inode number */
#endif
#if XFS_BIG_INUMS
	int			i8elevated;	/* sf_toino8 set i8count=1 */
#endif
	xfs_dir2_sf_entry_t	*sfep;		/* shortform directory entry */
986
	xfs_dir2_sf_hdr_t	*sfp;		/* shortform structure */
Linus Torvalds's avatar
Linus Torvalds committed
987

988 989
	trace_xfs_dir2_sf_replace(args);

Linus Torvalds's avatar
Linus Torvalds committed
990 991 992 993 994 995 996 997 998 999 1000 1001
	dp = args->dp;

	ASSERT(dp->i_df.if_flags & XFS_IFINLINE);
	/*
	 * Bail out if the shortform directory is way too small.
	 */
	if (dp->i_d.di_size < offsetof(xfs_dir2_sf_hdr_t, parent)) {
		ASSERT(XFS_FORCED_SHUTDOWN(dp->i_mount));
		return XFS_ERROR(EIO);
	}
	ASSERT(dp->i_df.if_bytes == dp->i_d.di_size);
	ASSERT(dp->i_df.if_u1.if_data != NULL);
1002 1003
	sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
	ASSERT(dp->i_d.di_size >= xfs_dir2_sf_hdr_size(sfp->i8count));
Linus Torvalds's avatar
Linus Torvalds committed
1004 1005 1006 1007
#if XFS_BIG_INUMS
	/*
	 * New inode number is large, and need to convert to 8-byte inodes.
	 */
1008
	if (args->inumber > XFS_DIR2_MAX_SHORT_INUM && sfp->i8count == 0) {
Linus Torvalds's avatar
Linus Torvalds committed
1009 1010 1011 1012 1013
		int	error;			/* error return value */
		int	newsize;		/* new inode size */

		newsize =
			dp->i_df.if_bytes +
1014
			(sfp->count + 1) *
Linus Torvalds's avatar
Linus Torvalds committed
1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031
			((uint)sizeof(xfs_dir2_ino8_t) -
			 (uint)sizeof(xfs_dir2_ino4_t));
		/*
		 * Won't fit as shortform, convert to block then do replace.
		 */
		if (newsize > XFS_IFORK_DSIZE(dp)) {
			error = xfs_dir2_sf_to_block(args);
			if (error) {
				return error;
			}
			return xfs_dir2_block_replace(args);
		}
		/*
		 * Still fits, convert to 8-byte now.
		 */
		xfs_dir2_sf_toino8(args);
		i8elevated = 1;
1032
		sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
Linus Torvalds's avatar
Linus Torvalds committed
1033 1034 1035 1036 1037 1038 1039 1040 1041 1042
	} else
		i8elevated = 0;
#endif
	ASSERT(args->namelen != 1 || args->name[0] != '.');
	/*
	 * Replace ..'s entry.
	 */
	if (args->namelen == 2 &&
	    args->name[0] == '.' && args->name[1] == '.') {
#if XFS_BIG_INUMS || defined(DEBUG)
1043
		ino = xfs_dir2_sf_get_parent_ino(sfp);
Linus Torvalds's avatar
Linus Torvalds committed
1044 1045
		ASSERT(args->inumber != ino);
#endif
1046
		xfs_dir2_sf_put_parent_ino(sfp, args->inumber);
Linus Torvalds's avatar
Linus Torvalds committed
1047 1048 1049 1050 1051
	}
	/*
	 * Normal entry, look for the name.
	 */
	else {
1052
		for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp); i < sfp->count;
1053
		     i++, sfep = dp->d_ops->sf_nextentry(sfp, sfep)) {
1054 1055
			if (xfs_da_compname(args, sfep->name, sfep->namelen) ==
								XFS_CMP_EXACT) {
Linus Torvalds's avatar
Linus Torvalds committed
1056
#if XFS_BIG_INUMS || defined(DEBUG)
1057 1058
				ino = xfs_dir3_sfe_get_ino(dp->i_mount,
							   sfp, sfep);
Linus Torvalds's avatar
Linus Torvalds committed
1059 1060
				ASSERT(args->inumber != ino);
#endif
1061 1062
				xfs_dir3_sfe_put_ino(dp->i_mount, sfp, sfep,
						     args->inumber);
1063 1064
				xfs_dir3_sfe_put_ftype(dp->i_mount, sfp, sfep,
						       args->filetype);
Linus Torvalds's avatar
Linus Torvalds committed
1065 1066 1067 1068 1069 1070
				break;
			}
		}
		/*
		 * Didn't find it.
		 */
1071
		if (i == sfp->count) {
1072
			ASSERT(args->op_flags & XFS_DA_OP_OKNOENT);
Linus Torvalds's avatar
Linus Torvalds committed
1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088
#if XFS_BIG_INUMS
			if (i8elevated)
				xfs_dir2_sf_toino4(args);
#endif
			return XFS_ERROR(ENOENT);
		}
	}
#if XFS_BIG_INUMS
	/*
	 * See if the old number was large, the new number is small.
	 */
	if (ino > XFS_DIR2_MAX_SHORT_INUM &&
	    args->inumber <= XFS_DIR2_MAX_SHORT_INUM) {
		/*
		 * And the old count was one, so need to convert to small.
		 */
1089
		if (sfp->i8count == 1)
Linus Torvalds's avatar
Linus Torvalds committed
1090 1091
			xfs_dir2_sf_toino4(args);
		else
1092
			sfp->i8count--;
Linus Torvalds's avatar
Linus Torvalds committed
1093 1094 1095 1096 1097 1098 1099 1100 1101 1102
	}
	/*
	 * See if the old number was small, the new number is large.
	 */
	if (ino <= XFS_DIR2_MAX_SHORT_INUM &&
	    args->inumber > XFS_DIR2_MAX_SHORT_INUM) {
		/*
		 * add to the i8count unless we just converted to 8-byte
		 * inodes (which does an implied i8count = 1)
		 */
1103
		ASSERT(sfp->i8count != 0);
Linus Torvalds's avatar
Linus Torvalds committed
1104
		if (!i8elevated)
1105
			sfp->i8count++;
Linus Torvalds's avatar
Linus Torvalds committed
1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126
	}
#endif
	xfs_dir2_sf_check(args);
	xfs_trans_log_inode(args->trans, dp, XFS_ILOG_DDATA);
	return 0;
}

#if XFS_BIG_INUMS
/*
 * Convert from 8-byte inode numbers to 4-byte inode numbers.
 * The last 8-byte inode number is gone, but the count is still 1.
 */
static void
xfs_dir2_sf_toino4(
	xfs_da_args_t		*args)		/* operation arguments */
{
	char			*buf;		/* old dir's buffer */
	xfs_inode_t		*dp;		/* incore directory inode */
	int			i;		/* entry index */
	int			newsize;	/* new inode size */
	xfs_dir2_sf_entry_t	*oldsfep;	/* old sf entry */
1127
	xfs_dir2_sf_hdr_t	*oldsfp;	/* old sf directory */
Linus Torvalds's avatar
Linus Torvalds committed
1128 1129
	int			oldsize;	/* old inode size */
	xfs_dir2_sf_entry_t	*sfep;		/* new sf entry */
1130
	xfs_dir2_sf_hdr_t	*sfp;		/* new sf directory */
1131
	struct xfs_mount	*mp;
Linus Torvalds's avatar
Linus Torvalds committed
1132

1133 1134
	trace_xfs_dir2_sf_toino4(args);

Linus Torvalds's avatar
Linus Torvalds committed
1135
	dp = args->dp;
1136
	mp = dp->i_mount;
Linus Torvalds's avatar
Linus Torvalds committed
1137 1138 1139 1140 1141 1142 1143 1144

	/*
	 * Copy the old directory to the buffer.
	 * Then nuke it from the inode, and add the new buffer to the inode.
	 * Don't want xfs_idata_realloc copying the data here.
	 */
	oldsize = dp->i_df.if_bytes;
	buf = kmem_alloc(oldsize, KM_SLEEP);
1145 1146
	oldsfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
	ASSERT(oldsfp->i8count == 1);
Linus Torvalds's avatar
Linus Torvalds committed
1147 1148 1149 1150 1151 1152
	memcpy(buf, oldsfp, oldsize);
	/*
	 * Compute the new inode size.
	 */
	newsize =
		oldsize -
1153
		(oldsfp->count + 1) *
Linus Torvalds's avatar
Linus Torvalds committed
1154 1155 1156 1157 1158 1159
		((uint)sizeof(xfs_dir2_ino8_t) - (uint)sizeof(xfs_dir2_ino4_t));
	xfs_idata_realloc(dp, -oldsize, XFS_DATA_FORK);
	xfs_idata_realloc(dp, newsize, XFS_DATA_FORK);
	/*
	 * Reset our pointers, the data has moved.
	 */
1160 1161
	oldsfp = (xfs_dir2_sf_hdr_t *)buf;
	sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
Linus Torvalds's avatar
Linus Torvalds committed
1162 1163 1164
	/*
	 * Fill in the new header.
	 */
1165 1166
	sfp->count = oldsfp->count;
	sfp->i8count = 0;
1167
	xfs_dir2_sf_put_parent_ino(sfp, xfs_dir2_sf_get_parent_ino(oldsfp));
Linus Torvalds's avatar
Linus Torvalds committed
1168 1169 1170
	/*
	 * Copy the entries field by field.
	 */
1171 1172
	for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp),
		    oldsfep = xfs_dir2_sf_firstentry(oldsfp);
1173
	     i < sfp->count;
1174 1175
	     i++, sfep = dp->d_ops->sf_nextentry(sfp, sfep),
		  oldsfep = dp->d_ops->sf_nextentry(oldsfp, oldsfep)) {
Linus Torvalds's avatar
Linus Torvalds committed
1176 1177 1178
		sfep->namelen = oldsfep->namelen;
		sfep->offset = oldsfep->offset;
		memcpy(sfep->name, oldsfep->name, sfep->namelen);
1179 1180 1181 1182
		xfs_dir3_sfe_put_ino(mp, sfp, sfep,
			xfs_dir3_sfe_get_ino(mp, oldsfp, oldsfep));
		xfs_dir3_sfe_put_ftype(mp, sfp, sfep,
			xfs_dir3_sfe_get_ftype(mp, oldsfp, oldsfep));
Linus Torvalds's avatar
Linus Torvalds committed
1183 1184 1185 1186
	}
	/*
	 * Clean up the inode.
	 */
1187
	kmem_free(buf);
Linus Torvalds's avatar
Linus Torvalds committed
1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205
	dp->i_d.di_size = newsize;
	xfs_trans_log_inode(args->trans, dp, XFS_ILOG_CORE | XFS_ILOG_DDATA);
}

/*
 * Convert from 4-byte inode numbers to 8-byte inode numbers.
 * The new 8-byte inode number is not there yet, we leave with the
 * count 1 but no corresponding entry.
 */
static void
xfs_dir2_sf_toino8(
	xfs_da_args_t		*args)		/* operation arguments */
{
	char			*buf;		/* old dir's buffer */
	xfs_inode_t		*dp;		/* incore directory inode */
	int			i;		/* entry index */
	int			newsize;	/* new inode size */
	xfs_dir2_sf_entry_t	*oldsfep;	/* old sf entry */
1206
	xfs_dir2_sf_hdr_t	*oldsfp;	/* old sf directory */
Linus Torvalds's avatar
Linus Torvalds committed
1207 1208
	int			oldsize;	/* old inode size */
	xfs_dir2_sf_entry_t	*sfep;		/* new sf entry */
1209
	xfs_dir2_sf_hdr_t	*sfp;		/* new sf directory */
1210
	struct xfs_mount	*mp;
Linus Torvalds's avatar
Linus Torvalds committed
1211

1212 1213
	trace_xfs_dir2_sf_toino8(args);

Linus Torvalds's avatar
Linus Torvalds committed
1214
	dp = args->dp;
1215
	mp = dp->i_mount;
Linus Torvalds's avatar
Linus Torvalds committed
1216 1217 1218 1219 1220 1221 1222 1223

	/*
	 * Copy the old directory to the buffer.
	 * Then nuke it from the inode, and add the new buffer to the inode.
	 * Don't want xfs_idata_realloc copying the data here.
	 */
	oldsize = dp->i_df.if_bytes;
	buf = kmem_alloc(oldsize, KM_SLEEP);
1224 1225
	oldsfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
	ASSERT(oldsfp->i8count == 0);
Linus Torvalds's avatar
Linus Torvalds committed
1226 1227 1228 1229 1230 1231
	memcpy(buf, oldsfp, oldsize);
	/*
	 * Compute the new inode size.
	 */
	newsize =
		oldsize +
1232
		(oldsfp->count + 1) *
Linus Torvalds's avatar
Linus Torvalds committed
1233 1234 1235 1236 1237 1238
		((uint)sizeof(xfs_dir2_ino8_t) - (uint)sizeof(xfs_dir2_ino4_t));
	xfs_idata_realloc(dp, -oldsize, XFS_DATA_FORK);
	xfs_idata_realloc(dp, newsize, XFS_DATA_FORK);
	/*
	 * Reset our pointers, the data has moved.
	 */
1239 1240
	oldsfp = (xfs_dir2_sf_hdr_t *)buf;
	sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
Linus Torvalds's avatar
Linus Torvalds committed
1241 1242 1243
	/*
	 * Fill in the new header.
	 */
1244 1245
	sfp->count = oldsfp->count;
	sfp->i8count = 1;
1246
	xfs_dir2_sf_put_parent_ino(sfp, xfs_dir2_sf_get_parent_ino(oldsfp));
Linus Torvalds's avatar
Linus Torvalds committed
1247 1248 1249
	/*
	 * Copy the entries field by field.
	 */
1250 1251
	for (i = 0, sfep = xfs_dir2_sf_firstentry(sfp),
		    oldsfep = xfs_dir2_sf_firstentry(oldsfp);
1252
	     i < sfp->count;
1253 1254
	     i++, sfep = dp->d_ops->sf_nextentry(sfp, sfep),
		  oldsfep = dp->d_ops->sf_nextentry(oldsfp, oldsfep)) {
Linus Torvalds's avatar
Linus Torvalds committed
1255 1256 1257
		sfep->namelen = oldsfep->namelen;
		sfep->offset = oldsfep->offset;
		memcpy(sfep->name, oldsfep->name, sfep->namelen);
1258 1259 1260 1261
		xfs_dir3_sfe_put_ino(mp, sfp, sfep,
			xfs_dir3_sfe_get_ino(mp, oldsfp, oldsfep));
		xfs_dir3_sfe_put_ftype(mp, sfp, sfep,
			xfs_dir3_sfe_get_ftype(mp, oldsfp, oldsfep));
Linus Torvalds's avatar
Linus Torvalds committed
1262 1263 1264 1265
	}
	/*
	 * Clean up the inode.
	 */
1266
	kmem_free(buf);
Linus Torvalds's avatar
Linus Torvalds committed
1267 1268 1269 1270
	dp->i_d.di_size = newsize;
	xfs_trans_log_inode(args->trans, dp, XFS_ILOG_CORE | XFS_ILOG_DDATA);
}
#endif	/* XFS_BIG_INUMS */