extents.c 13.5 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/*
 *  linux/fs/hfsplus/extents.c
 *
 * Copyright (C) 2001
 * Brad Boyer (flar@allandria.com)
 * (C) 2003 Ardis Technologies <roman@ardistech.com>
 *
 * Handling of Extents both in catalog and extents overflow trees
 */

#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/pagemap.h>

#include "hfsplus_fs.h"
#include "hfsplus_raw.h"

/* Compare two extents keys, returns 0 on same, pos/neg for difference */
D
David Elliott 已提交
19 20
int hfsplus_ext_cmp_key(const hfsplus_btree_key *k1,
			const hfsplus_btree_key *k2)
L
Linus Torvalds 已提交
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
{
	__be32 k1id, k2id;
	__be32 k1s, k2s;

	k1id = k1->ext.cnid;
	k2id = k2->ext.cnid;
	if (k1id != k2id)
		return be32_to_cpu(k1id) < be32_to_cpu(k2id) ? -1 : 1;

	if (k1->ext.fork_type != k2->ext.fork_type)
		return k1->ext.fork_type < k2->ext.fork_type ? -1 : 1;

	k1s = k1->ext.start_block;
	k2s = k2->ext.start_block;
	if (k1s == k2s)
		return 0;
	return be32_to_cpu(k1s) < be32_to_cpu(k2s) ? -1 : 1;
}

static void hfsplus_ext_build_key(hfsplus_btree_key *key, u32 cnid,
				  u32 block, u8 type)
{
	key->key_len = cpu_to_be16(HFSPLUS_EXT_KEYLEN - 2);
	key->ext.cnid = cpu_to_be32(cnid);
	key->ext.start_block = cpu_to_be32(block);
	key->ext.fork_type = type;
	key->ext.pad = 0;
}

static u32 hfsplus_ext_find_block(struct hfsplus_extent *ext, u32 off)
{
	int i;
	u32 count;

	for (i = 0; i < 8; ext++, i++) {
		count = be32_to_cpu(ext->block_count);
		if (off < count)
			return be32_to_cpu(ext->start_block) + off;
		off -= count;
	}
	/* panic? */
	return 0;
}

static int hfsplus_ext_block_count(struct hfsplus_extent *ext)
{
	int i;
	u32 count = 0;

	for (i = 0; i < 8; ext++, i++)
		count += be32_to_cpu(ext->block_count);
	return count;
}

static u32 hfsplus_ext_lastblock(struct hfsplus_extent *ext)
{
	int i;

	ext += 7;
	for (i = 0; i < 7; ext--, i++)
		if (ext->block_count)
			break;
	return be32_to_cpu(ext->start_block) + be32_to_cpu(ext->block_count);
}

static void __hfsplus_ext_write_extent(struct inode *inode, struct hfs_find_data *fd)
{
88
	struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
L
Linus Torvalds 已提交
89 90
	int res;

91 92
	WARN_ON(!mutex_is_locked(&hip->extents_lock));

93 94 95 96
	hfsplus_ext_build_key(fd->search_key, inode->i_ino, hip->cached_start,
			      HFSPLUS_IS_RSRC(inode) ?
				HFSPLUS_TYPE_RSRC : HFSPLUS_TYPE_DATA);

L
Linus Torvalds 已提交
97
	res = hfs_brec_find(fd);
98
	if (hip->flags & HFSPLUS_FLG_EXT_NEW) {
L
Linus Torvalds 已提交
99 100
		if (res != -ENOENT)
			return;
101 102 103
		hfs_brec_insert(fd, hip->cached_extents,
				sizeof(hfsplus_extent_rec));
		hip->flags &= ~(HFSPLUS_FLG_EXT_DIRTY | HFSPLUS_FLG_EXT_NEW);
L
Linus Torvalds 已提交
104 105 106
	} else {
		if (res)
			return;
107 108 109
		hfs_bnode_write(fd->bnode, hip->cached_extents,
				fd->entryoffset, fd->entrylength);
		hip->flags &= ~HFSPLUS_FLG_EXT_DIRTY;
L
Linus Torvalds 已提交
110 111 112
	}
}

113
static void hfsplus_ext_write_extent_locked(struct inode *inode)
L
Linus Torvalds 已提交
114
{
115
	if (HFSPLUS_I(inode)->flags & HFSPLUS_FLG_EXT_DIRTY) {
L
Linus Torvalds 已提交
116 117
		struct hfs_find_data fd;

118
		hfs_find_init(HFSPLUS_SB(inode->i_sb)->ext_tree, &fd);
L
Linus Torvalds 已提交
119 120 121 122 123
		__hfsplus_ext_write_extent(inode, &fd);
		hfs_find_exit(&fd);
	}
}

124 125 126 127 128 129 130
void hfsplus_ext_write_extent(struct inode *inode)
{
	mutex_lock(&HFSPLUS_I(inode)->extents_lock);
	hfsplus_ext_write_extent_locked(inode);
	mutex_unlock(&HFSPLUS_I(inode)->extents_lock);
}

L
Linus Torvalds 已提交
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
static inline int __hfsplus_ext_read_extent(struct hfs_find_data *fd,
					    struct hfsplus_extent *extent,
					    u32 cnid, u32 block, u8 type)
{
	int res;

	hfsplus_ext_build_key(fd->search_key, cnid, block, type);
	fd->key->ext.cnid = 0;
	res = hfs_brec_find(fd);
	if (res && res != -ENOENT)
		return res;
	if (fd->key->ext.cnid != fd->search_key->ext.cnid ||
	    fd->key->ext.fork_type != fd->search_key->ext.fork_type)
		return -ENOENT;
	if (fd->entrylength != sizeof(hfsplus_extent_rec))
		return -EIO;
	hfs_bnode_read(fd->bnode, extent, fd->entryoffset, sizeof(hfsplus_extent_rec));
	return 0;
}

static inline int __hfsplus_ext_cache_extent(struct hfs_find_data *fd, struct inode *inode, u32 block)
{
153
	struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
L
Linus Torvalds 已提交
154 155
	int res;

156 157
	WARN_ON(!mutex_is_locked(&hip->extents_lock));

158
	if (hip->flags & HFSPLUS_FLG_EXT_DIRTY)
L
Linus Torvalds 已提交
159 160
		__hfsplus_ext_write_extent(inode, fd);

161 162 163 164
	res = __hfsplus_ext_read_extent(fd, hip->cached_extents, inode->i_ino,
					block, HFSPLUS_IS_RSRC(inode) ?
						HFSPLUS_TYPE_RSRC :
						HFSPLUS_TYPE_DATA);
L
Linus Torvalds 已提交
165
	if (!res) {
166 167
		hip->cached_start = be32_to_cpu(fd->key->ext.start_block);
		hip->cached_blocks = hfsplus_ext_block_count(hip->cached_extents);
L
Linus Torvalds 已提交
168
	} else {
169 170
		hip->cached_start = hip->cached_blocks = 0;
		hip->flags &= ~(HFSPLUS_FLG_EXT_DIRTY | HFSPLUS_FLG_EXT_NEW);
L
Linus Torvalds 已提交
171 172 173 174 175 176
	}
	return res;
}

static int hfsplus_ext_read_extent(struct inode *inode, u32 block)
{
177
	struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
L
Linus Torvalds 已提交
178 179 180
	struct hfs_find_data fd;
	int res;

181 182
	if (block >= hip->cached_start &&
	    block < hip->cached_start + hip->cached_blocks)
L
Linus Torvalds 已提交
183 184
		return 0;

185
	hfs_find_init(HFSPLUS_SB(inode->i_sb)->ext_tree, &fd);
L
Linus Torvalds 已提交
186 187 188 189 190 191 192 193 194
	res = __hfsplus_ext_cache_extent(&fd, inode, block);
	hfs_find_exit(&fd);
	return res;
}

/* Get a block at iblock for inode, possibly allocating if create */
int hfsplus_get_block(struct inode *inode, sector_t iblock,
		      struct buffer_head *bh_result, int create)
{
195 196
	struct super_block *sb = inode->i_sb;
	struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
197
	struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
L
Linus Torvalds 已提交
198 199 200 201 202
	int res = -EIO;
	u32 ablock, dblock, mask;
	int shift;

	/* Convert inode block to disk allocation block */
203 204
	shift = sbi->alloc_blksz_shift - sb->s_blocksize_bits;
	ablock = iblock >> sbi->fs_shift;
L
Linus Torvalds 已提交
205

206 207
	if (iblock >= hip->fs_blocks) {
		if (iblock > hip->fs_blocks || !create)
L
Linus Torvalds 已提交
208
			return -EIO;
209
		if (ablock >= hip->alloc_blocks) {
L
Linus Torvalds 已提交
210 211 212 213 214 215 216
			res = hfsplus_file_extend(inode);
			if (res)
				return res;
		}
	} else
		create = 0;

217 218
	if (ablock < hip->first_blocks) {
		dblock = hfsplus_ext_find_block(hip->first_extents, ablock);
L
Linus Torvalds 已提交
219 220 221
		goto done;
	}

222 223 224
	if (inode->i_ino == HFSPLUS_EXT_CNID)
		return -EIO;

225
	mutex_lock(&hip->extents_lock);
L
Linus Torvalds 已提交
226 227
	res = hfsplus_ext_read_extent(inode, ablock);
	if (!res) {
228 229
		dblock = hfsplus_ext_find_block(hip->cached_extents,
						ablock - hip->cached_start);
L
Linus Torvalds 已提交
230
	} else {
231
		mutex_unlock(&hip->extents_lock);
L
Linus Torvalds 已提交
232 233
		return -EIO;
	}
234
	mutex_unlock(&hip->extents_lock);
L
Linus Torvalds 已提交
235 236 237

done:
	dprint(DBG_EXTENT, "get_block(%lu): %llu - %u\n", inode->i_ino, (long long)iblock, dblock);
238 239
	mask = (1 << sbi->fs_shift) - 1;
	map_bh(bh_result, sb, (dblock << sbi->fs_shift) + sbi->blockoffset + (iblock & mask));
L
Linus Torvalds 已提交
240 241
	if (create) {
		set_buffer_new(bh_result);
242 243
		hip->phys_size += sb->s_blocksize;
		hip->fs_blocks++;
L
Linus Torvalds 已提交
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
		inode_add_bytes(inode, sb->s_blocksize);
		mark_inode_dirty(inode);
	}
	return 0;
}

static void hfsplus_dump_extent(struct hfsplus_extent *extent)
{
	int i;

	dprint(DBG_EXTENT, "   ");
	for (i = 0; i < 8; i++)
		dprint(DBG_EXTENT, " %u:%u", be32_to_cpu(extent[i].start_block),
				 be32_to_cpu(extent[i].block_count));
	dprint(DBG_EXTENT, "\n");
}

static int hfsplus_add_extent(struct hfsplus_extent *extent, u32 offset,
			      u32 alloc_block, u32 block_count)
{
	u32 count, start;
	int i;

	hfsplus_dump_extent(extent);
	for (i = 0; i < 8; extent++, i++) {
		count = be32_to_cpu(extent->block_count);
		if (offset == count) {
			start = be32_to_cpu(extent->start_block);
			if (alloc_block != start + count) {
				if (++i >= 8)
					return -ENOSPC;
				extent++;
				extent->start_block = cpu_to_be32(alloc_block);
			} else
				block_count += count;
			extent->block_count = cpu_to_be32(block_count);
			return 0;
		} else if (offset < count)
			break;
		offset -= count;
	}
	/* panic? */
	return -EIO;
}

static int hfsplus_free_extents(struct super_block *sb,
				struct hfsplus_extent *extent,
				u32 offset, u32 block_nr)
{
	u32 count, start;
	int i;

	hfsplus_dump_extent(extent);
	for (i = 0; i < 8; extent++, i++) {
		count = be32_to_cpu(extent->block_count);
		if (offset == count)
			goto found;
		else if (offset < count)
			break;
		offset -= count;
	}
	/* panic? */
	return -EIO;
found:
	for (;;) {
		start = be32_to_cpu(extent->start_block);
		if (count <= block_nr) {
			hfsplus_block_free(sb, start, count);
			extent->block_count = 0;
			extent->start_block = 0;
			block_nr -= count;
		} else {
			count -= block_nr;
			hfsplus_block_free(sb, start + count, block_nr);
			extent->block_count = cpu_to_be32(count);
			block_nr = 0;
		}
		if (!block_nr || !i)
			return 0;
		i--;
		extent--;
		count = be32_to_cpu(extent->block_count);
	}
}

int hfsplus_free_fork(struct super_block *sb, u32 cnid, struct hfsplus_fork_raw *fork, int type)
{
	struct hfs_find_data fd;
	hfsplus_extent_rec ext_entry;
	u32 total_blocks, blocks, start;
	int res, i;

	total_blocks = be32_to_cpu(fork->total_blocks);
	if (!total_blocks)
		return 0;

	blocks = 0;
	for (i = 0; i < 8; i++)
		blocks += be32_to_cpu(fork->extents[i].block_count);

	res = hfsplus_free_extents(sb, fork->extents, blocks, blocks);
	if (res)
		return res;
	if (total_blocks == blocks)
		return 0;

350
	hfs_find_init(HFSPLUS_SB(sb)->ext_tree, &fd);
L
Linus Torvalds 已提交
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
	do {
		res = __hfsplus_ext_read_extent(&fd, ext_entry, cnid,
						total_blocks, type);
		if (res)
			break;
		start = be32_to_cpu(fd.key->ext.start_block);
		hfsplus_free_extents(sb, ext_entry,
				     total_blocks - start,
				     total_blocks);
		hfs_brec_remove(&fd);
		total_blocks = start;
	} while (total_blocks > blocks);
	hfs_find_exit(&fd);

	return res;
}

int hfsplus_file_extend(struct inode *inode)
{
	struct super_block *sb = inode->i_sb;
371
	struct hfsplus_sb_info *sbi = HFSPLUS_SB(sb);
372
	struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
L
Linus Torvalds 已提交
373 374 375
	u32 start, len, goal;
	int res;

376 377
	if (sbi->alloc_file->i_size * 8 <
	    sbi->total_blocks - sbi->free_blocks + 8) {
L
Linus Torvalds 已提交
378
		// extend alloc file
379 380 381
		printk(KERN_ERR "hfs: extend alloc file! (%Lu,%u,%u)\n",
				sbi->alloc_file->i_size * 8,
				sbi->total_blocks, sbi->free_blocks);
L
Linus Torvalds 已提交
382 383 384
		return -ENOSPC;
	}

385 386 387
	mutex_lock(&hip->extents_lock);
	if (hip->alloc_blocks == hip->first_blocks)
		goal = hfsplus_ext_lastblock(hip->first_extents);
L
Linus Torvalds 已提交
388
	else {
389
		res = hfsplus_ext_read_extent(inode, hip->alloc_blocks);
L
Linus Torvalds 已提交
390 391
		if (res)
			goto out;
392
		goal = hfsplus_ext_lastblock(hip->cached_extents);
L
Linus Torvalds 已提交
393 394
	}

395
	len = hip->clump_blocks;
396 397
	start = hfsplus_block_allocate(sb, sbi->total_blocks, goal, &len);
	if (start >= sbi->total_blocks) {
L
Linus Torvalds 已提交
398 399 400 401 402 403 404 405
		start = hfsplus_block_allocate(sb, goal, 0, &len);
		if (start >= goal) {
			res = -ENOSPC;
			goto out;
		}
	}

	dprint(DBG_EXTENT, "extend %lu: %u,%u\n", inode->i_ino, start, len);
406 407 408

	if (hip->alloc_blocks <= hip->first_blocks) {
		if (!hip->first_blocks) {
L
Linus Torvalds 已提交
409 410
			dprint(DBG_EXTENT, "first extents\n");
			/* no extents yet */
411 412
			hip->first_extents[0].start_block = cpu_to_be32(start);
			hip->first_extents[0].block_count = cpu_to_be32(len);
L
Linus Torvalds 已提交
413 414 415
			res = 0;
		} else {
			/* try to append to extents in inode */
416 417
			res = hfsplus_add_extent(hip->first_extents,
						 hip->alloc_blocks,
L
Linus Torvalds 已提交
418 419 420 421 422
						 start, len);
			if (res == -ENOSPC)
				goto insert_extent;
		}
		if (!res) {
423 424
			hfsplus_dump_extent(hip->first_extents);
			hip->first_blocks += len;
L
Linus Torvalds 已提交
425 426
		}
	} else {
427 428
		res = hfsplus_add_extent(hip->cached_extents,
					 hip->alloc_blocks - hip->cached_start,
L
Linus Torvalds 已提交
429 430
					 start, len);
		if (!res) {
431 432 433
			hfsplus_dump_extent(hip->cached_extents);
			hip->flags |= HFSPLUS_FLG_EXT_DIRTY;
			hip->cached_blocks += len;
L
Linus Torvalds 已提交
434 435 436 437
		} else if (res == -ENOSPC)
			goto insert_extent;
	}
out:
438
	mutex_unlock(&hip->extents_lock);
L
Linus Torvalds 已提交
439
	if (!res) {
440
		hip->alloc_blocks += len;
L
Linus Torvalds 已提交
441 442 443 444 445 446
		mark_inode_dirty(inode);
	}
	return res;

insert_extent:
	dprint(DBG_EXTENT, "insert new extent\n");
447
	hfsplus_ext_write_extent_locked(inode);
L
Linus Torvalds 已提交
448

449 450 451 452 453 454 455
	memset(hip->cached_extents, 0, sizeof(hfsplus_extent_rec));
	hip->cached_extents[0].start_block = cpu_to_be32(start);
	hip->cached_extents[0].block_count = cpu_to_be32(len);
	hfsplus_dump_extent(hip->cached_extents);
	hip->flags |= HFSPLUS_FLG_EXT_DIRTY | HFSPLUS_FLG_EXT_NEW;
	hip->cached_start = hip->alloc_blocks;
	hip->cached_blocks = len;
L
Linus Torvalds 已提交
456 457 458 459 460 461 462 463

	res = 0;
	goto out;
}

void hfsplus_file_truncate(struct inode *inode)
{
	struct super_block *sb = inode->i_sb;
464
	struct hfsplus_inode_info *hip = HFSPLUS_I(inode);
L
Linus Torvalds 已提交
465 466 467 468
	struct hfs_find_data fd;
	u32 alloc_cnt, blk_cnt, start;
	int res;

469 470 471 472
	dprint(DBG_INODE, "truncate: %lu, %Lu -> %Lu\n",
		inode->i_ino, (long long)hip->phys_size, inode->i_size);

	if (inode->i_size > hip->phys_size) {
L
Linus Torvalds 已提交
473 474
		struct address_space *mapping = inode->i_mapping;
		struct page *page;
N
Nick Piggin 已提交
475 476
		void *fsdata;
		u32 size = inode->i_size;
L
Linus Torvalds 已提交
477 478
		int res;

N
Nick Piggin 已提交
479 480 481
		res = pagecache_write_begin(NULL, mapping, size, 0,
						AOP_FLAG_UNINTERRUPTIBLE,
						&page, &fsdata);
L
Linus Torvalds 已提交
482
		if (res)
N
Nick Piggin 已提交
483 484 485 486
			return;
		res = pagecache_write_end(NULL, mapping, size, 0, 0, page, fsdata);
		if (res < 0)
			return;
L
Linus Torvalds 已提交
487 488
		mark_inode_dirty(inode);
		return;
489
	} else if (inode->i_size == hip->phys_size)
490 491
		return;

492 493
	blk_cnt = (inode->i_size + HFSPLUS_SB(sb)->alloc_blksz - 1) >>
			HFSPLUS_SB(sb)->alloc_blksz_shift;
494
	alloc_cnt = hip->alloc_blocks;
L
Linus Torvalds 已提交
495 496 497
	if (blk_cnt == alloc_cnt)
		goto out;

498
	mutex_lock(&hip->extents_lock);
499
	hfs_find_init(HFSPLUS_SB(sb)->ext_tree, &fd);
L
Linus Torvalds 已提交
500
	while (1) {
501 502
		if (alloc_cnt == hip->first_blocks) {
			hfsplus_free_extents(sb, hip->first_extents,
L
Linus Torvalds 已提交
503
					     alloc_cnt, alloc_cnt - blk_cnt);
504 505
			hfsplus_dump_extent(hip->first_extents);
			hip->first_blocks = blk_cnt;
L
Linus Torvalds 已提交
506 507 508 509 510
			break;
		}
		res = __hfsplus_ext_cache_extent(&fd, inode, alloc_cnt);
		if (res)
			break;
511 512
		start = hip->cached_start;
		hfsplus_free_extents(sb, hip->cached_extents,
L
Linus Torvalds 已提交
513
				     alloc_cnt - start, alloc_cnt - blk_cnt);
514
		hfsplus_dump_extent(hip->cached_extents);
L
Linus Torvalds 已提交
515
		if (blk_cnt > start) {
516
			hip->flags |= HFSPLUS_FLG_EXT_DIRTY;
L
Linus Torvalds 已提交
517 518 519
			break;
		}
		alloc_cnt = start;
520 521
		hip->cached_start = hip->cached_blocks = 0;
		hip->flags &= ~(HFSPLUS_FLG_EXT_DIRTY | HFSPLUS_FLG_EXT_NEW);
L
Linus Torvalds 已提交
522 523 524
		hfs_brec_remove(&fd);
	}
	hfs_find_exit(&fd);
525
	mutex_unlock(&hip->extents_lock);
L
Linus Torvalds 已提交
526

527
	hip->alloc_blocks = blk_cnt;
L
Linus Torvalds 已提交
528
out:
529 530 531
	hip->phys_size = inode->i_size;
	hip->fs_blocks = (inode->i_size + sb->s_blocksize - 1) >> sb->s_blocksize_bits;
	inode_set_bytes(inode, hip->fs_blocks << sb->s_blocksize_bits);
L
Linus Torvalds 已提交
532 533
	mark_inode_dirty(inode);
}