/* * linux/fs/hfs/extent.c * * Copyright (C) 1995-1997 Paul H. Hargrove * This file may be distributed under the terms of the GNU General Public License. * * This file contains the functions related to the extents B-tree. * * "XXX" in a comment is a note to myself to consider changing something. * * In function preconditions the term "valid" applied to a pointer to * a structure means that the pointer is non-NULL and the structure it * points to has all fields initialized to consistent values. */ #include "hfs.h" /*================ File-local data type ================*/ /* An extent record on disk*/ struct hfs_raw_extent { hfs_word_t block1; hfs_word_t length1; hfs_word_t block2; hfs_word_t length2; hfs_word_t block3; hfs_word_t length3; }; /*================ File-local functions ================*/ /* * build_key */ static inline void build_key(struct hfs_ext_key *key, const struct hfs_fork *fork, hfs_u16 block) { key->KeyLen = 7; key->FkType = fork->fork; hfs_put_nl(fork->entry->cnid, key->FNum); hfs_put_hs(block, key->FABN); } /* * lock_bitmap() * * Get an exclusive lock on the B-tree bitmap. */ static inline void lock_bitmap(struct hfs_mdb *mdb) { while (mdb->bitmap_lock) { hfs_sleep_on(&mdb->bitmap_wait); } mdb->bitmap_lock = 1; } /* * unlock_bitmap() * * Relinquish an exclusive lock on the B-tree bitmap. */ static inline void unlock_bitmap(struct hfs_mdb *mdb) { mdb->bitmap_lock = 0; hfs_wake_up(&mdb->bitmap_wait); } /* * dump_ext() * * prints the content of a extent for debugging purposes. */ #if defined(DEBUG_EXTENTS) || defined(DEBUG_ALL) static void dump_ext(const char *msg, const struct hfs_extent *e) { if (e) { hfs_warn("%s (%d-%d) (%d-%d) (%d-%d)\n", msg, e->start, e->start + e->length[0] - 1, e->start + e->length[0], e->start + e->length[0] + e->length[1] - 1, e->start + e->length[0] + e->length[1], e->end); } else { hfs_warn("%s NULL\n", msg); } } #else #define dump_ext(A,B) {} #endif /* * read_extent() * * Initializes a (struct hfs_extent) from a (struct hfs_raw_extent) and * the number of the starting block for the extent. * * Note that the callers must check that to,from != NULL */ static void read_extent(struct hfs_extent *to, const struct hfs_raw_extent *from, hfs_u16 start) { to->start = start; to->block[0] = hfs_get_hs(from->block1); to->length[0] = hfs_get_hs(from->length1); to->block[1] = hfs_get_hs(from->block2); to->length[1] = hfs_get_hs(from->length2); to->block[2] = hfs_get_hs(from->block3); to->length[2] = hfs_get_hs(from->length3); to->end = start + to->length[0] + to->length[1] + to->length[2] - 1; to->next = to->prev = NULL; to->count = 0; } /* * write_extent() * * Initializes a (struct hfs_raw_extent) from a (struct hfs_extent). * * Note that the callers must check that to,from != NULL */ static void write_extent(struct hfs_raw_extent *to, const struct hfs_extent *from) { hfs_put_hs(from->block[0], to->block1); hfs_put_hs(from->length[0], to->length1); hfs_put_hs(from->block[1], to->block2); hfs_put_hs(from->length[1], to->length2); hfs_put_hs(from->block[2], to->block3); hfs_put_hs(from->length[2], to->length3); } /* * decode_extent() * * Given an extent record and allocation block offset into the file, * return the number of the corresponding allocation block on disk, * or -1 if the desired block is not mapped by the given extent. * * Note that callers must check that extent != NULL */ static int decode_extent(const struct hfs_extent * extent, int block) { if (!extent || (block < extent->start) || (block > extent->end) || (extent->end == (hfs_u16)(extent->start - 1))) { return -1; } block -= extent->start; if (block < extent->length[0]) { return block + extent->block[0]; } block -= extent->length[0]; if (block < extent->length[1]) { return block + extent->block[1]; } return block + extent->block[2] - extent->length[1]; } /* * relse_ext() * * Reduce the reference count of an in-core extent record by one, * removing it from memory if the count falls to zero. */ static void relse_ext(struct hfs_extent *ext) { if (--ext->count || !ext->start) { return; } ext->prev->next = ext->next; if (ext->next) { ext->next->prev = ext->prev; } HFS_DELETE(ext); } /* * set_cache() * * Changes the 'cache' field of the fork. */ static inline void set_cache(struct hfs_fork *fork, struct hfs_extent *ext) { struct hfs_extent *tmp = fork->cache; ++ext->count; fork->cache = ext; relse_ext(tmp); } /* * find_ext() * * Given a pointer to a (struct hfs_file) and an allocation block * number in the file, find the extent record containing that block. * Returns a pointer to the extent record on success or NULL on failure. * The 'cache' field of 'fil' also points to the extent so it has a * reference count of at least 2. * * Callers must check that fil != NULL */ static struct hfs_extent * find_ext(struct hfs_fork *fork, int alloc_block) { struct hfs_cat_entry *entry = fork->entry; struct hfs_btree *tr= entry->mdb->ext_tree; struct hfs_ext_key target, *key; struct hfs_brec brec; struct hfs_extent *ext, *ptr; int tmp; if (alloc_block < 0) { ext = &fork->first; goto found; } ext = fork->cache; if (!ext || (alloc_block < ext->start)) { ext = &fork->first; } while (ext->next && (alloc_block > ext->end)) { ext = ext->next; } if ((alloc_block <= ext->end) && (alloc_block >= ext->start)) { goto found; } /* time to read more extents */ if (!HFS_NEW(ext)) { goto bail3; } build_key(&target, fork, alloc_block); tmp = hfs_bfind(&brec, tr, HFS_BKEY(&target), HFS_BFIND_READ_LE); if (tmp < 0) { goto bail2; } key = (struct hfs_ext_key *)brec.key; if ((hfs_get_nl(key->FNum) != hfs_get_nl(target.FNum)) || (key->FkType != fork->fork)) { goto bail1; } read_extent(ext, brec.data, hfs_get_hs(key->FABN)); hfs_brec_relse(&brec, NULL); if ((alloc_block > ext->end) && (alloc_block < ext->start)) { /* something strange happened */ goto bail2; } ptr = fork->cache; if (!ptr || (alloc_block < ptr->start)) { ptr = &fork->first; } while (ptr->next && (alloc_block > ptr->end)) { ptr = ptr->next; } if (ext->start == ptr->start) { /* somebody beat us to it. */ HFS_DELETE(ext); ext = ptr; } else if (ext->start < ptr->start) { /* insert just before ptr */ ptr->prev->next = ext; ext->prev = ptr->prev; ext->next = ptr; ptr->prev = ext; } else { /* insert at end */ ptr->next = ext; ext->prev = ptr; } found: ++ext->count; /* for return value */ set_cache(fork, ext); return ext; bail1: hfs_brec_relse(&brec, NULL); bail2: HFS_DELETE(ext); bail3: return NULL; } /* * delete_extent() * * Description: * Deletes an extent record from a fork, reducing its physical length. * Input Variable(s): * struct hfs_fork *fork: the fork * struct hfs_extent *ext: the current last extent for 'fork' * Output Variable(s): * NONE * Returns: * void * Preconditions: * 'fork' points to a valid (struct hfs_fork) * 'ext' point to a valid (struct hfs_extent) which is the last in 'fork' * and which is not also the first extent in 'fork'. * Postconditions: * The extent record has been removed if possible, and a warning has been * printed otherwise. */ static void delete_extent(struct hfs_fork *fork, struct hfs_extent *ext) { struct hfs_mdb *mdb = fork->entry->mdb; struct hfs_ext_key key; int error; if (fork->cache == ext) { set_cache(fork, ext->prev); } ext->prev->next = NULL; if (ext->count != 1) { hfs_warn("hfs_truncate: extent has count %d.\n", ext->count); } lock_bitmap(mdb); error = hfs_clear_vbm_bits(mdb, ext->block[2], ext->length[2]); if (error) { hfs_warn("hfs_truncate: error %d freeing blocks.\n", error); } error = hfs_clear_vbm_bits(mdb, ext->block[1], ext->length[1]); if (error) { hfs_warn("hfs_truncate: error %d freeing blocks.\n", error); } error = hfs_clear_vbm_bits(mdb, ext->block[0], ext->length[0]); if (error) { hfs_warn("hfs_truncate: error %d freeing blocks.\n", error); } unlock_bitmap(mdb); build_key(&key, fork, ext->start); error = hfs_bdelete(mdb->ext_tree, HFS_BKEY(&key)); if (error) { hfs_warn("hfs_truncate: error %d deleting an extent.\n", error); } HFS_DELETE(ext); } /* * new_extent() * * Description: * Adds a new extent record to a fork, extending its physical length. * Input Variable(s): * struct hfs_fork *fork: the fork to extend * struct hfs_extent *ext: the current last extent for 'fork' * hfs_u16 ablock: the number of allocation blocks in 'fork'. * hfs_u16 start: first allocation block to add to 'fork'. * hfs_u16 len: the number of allocation blocks to add to 'fork'. * hfs_u32 ablksz: number of sectors in an allocation block. * Output Variable(s): * NONE * Returns: * (struct hfs_extent *) the new extent or NULL * Preconditions: * 'fork' points to a valid (struct hfs_fork) * 'ext' point to a valid (struct hfs_extent) which is the last in 'fork' * 'ablock', 'start', 'len' and 'ablksz' are what they claim to be. * Postconditions: * If NULL is returned then no changes have been made to 'fork'. * If the return value is non-NULL that it is the extent that has been * added to 'fork' both in memory and on disk. The 'psize' field of * 'fork' has been updated to reflect the new physical size. */ static struct hfs_extent *new_extent(struct hfs_fork *fork, struct hfs_extent *ext, hfs_u16 ablock, hfs_u16 start, hfs_u16 len, hfs_u16 ablksz) { struct hfs_raw_extent raw; struct hfs_ext_key key; int error; if (fork->entry->cnid == htonl(HFS_EXT_CNID)) { /* Limit extents tree to the record in the MDB */ return NULL; } if (!HFS_NEW(ext->next)) { return NULL; } ext->next->prev = ext; ext->next->next = NULL; ext = ext->next; relse_ext(ext->prev); ext->start = ablock; ext->block[0] = start; ext->length[0] = len; ext->block[1] = 0; ext->length[1] = 0; ext->block[2] = 0; ext->length[2] = 0; ext->end = ablock + len - 1; ext->count = 1; write_extent(&raw, ext); build_key(&key, fork, ablock); error = hfs_binsert(fork->entry->mdb->ext_tree, HFS_BKEY(&key), &raw, sizeof(raw)); if (error) { ext->prev->next = NULL; HFS_DELETE(ext); return NULL; } set_cache(fork, ext); return ext; } /* * update_ext() * * Given a (struct hfs_fork) write an extent record back to disk. */ static void update_ext(struct hfs_fork *fork, struct hfs_extent *ext) { struct hfs_ext_key target; struct hfs_brec brec; if (ext->start) { build_key(&target, fork, ext->start); if (!hfs_bfind(&brec, fork->entry->mdb->ext_tree, HFS_BKEY(&target), HFS_BFIND_WRITE)) { write_extent(brec.data, ext); hfs_brec_relse(&brec, NULL); } } } /* * zero_blocks() * * Zeros-out 'num' allocation blocks beginning with 'start'. */ static int zero_blocks(struct hfs_mdb *mdb, int start, int num) { hfs_buffer buf; int end; int j; start = mdb->fs_start + start * mdb->alloc_blksz; end = start + num * mdb->alloc_blksz; for (j=start; jsys_mdb, j, 0))) { memset(hfs_buffer_data(buf), 0, HFS_SECTOR_SIZE); hfs_buffer_dirty(buf); hfs_buffer_put(buf); } } return 0; } /* * shrink_fork() * * Try to remove enough allocation blocks from 'fork' * so that it is 'ablocks' allocation blocks long. */ static void shrink_fork(struct hfs_fork *fork, int ablocks) { struct hfs_mdb *mdb = fork->entry->mdb; struct hfs_extent *ext; int i, error, next, count; hfs_u32 ablksz = mdb->alloc_blksz; next = (fork->psize / ablksz) - 1; ext = find_ext(fork, next); while (ext && ext->start && (ext->start >= ablocks)) { next = ext->start - 1; delete_extent(fork, ext); ext = find_ext(fork, next); } if (!ext) { fork->psize = (next + 1) * ablksz; return; } if ((count = next + 1 - ablocks) > 0) { for (i=2; (i>=0) && !ext->length[i]; --i) {}; lock_bitmap(mdb); while (count && (ext->length[i] <= count)) { ext->end -= ext->length[i]; count -= ext->length[i]; error = hfs_clear_vbm_bits(mdb, ext->block[i], ext->length[i]); if (error) { hfs_warn("hfs_truncate: error %d freeing " "blocks.\n", error); } ext->block[i] = ext->length[i] = 0; --i; } if (count) { ext->end -= count; ext->length[i] -= count; error = hfs_clear_vbm_bits(mdb, ext->block[i] + ext->length[i], count); if (error) { hfs_warn("hfs_truncate: error %d freeing " "blocks.\n", error); } } unlock_bitmap(mdb); update_ext(fork, ext); } fork->psize = ablocks * ablksz; } /* * grow_fork() * * Try to add enough allocation blocks to 'fork' * so that it is 'ablock' allocation blocks long. */ static int grow_fork(struct hfs_fork *fork, int ablocks) { struct hfs_cat_entry *entry = fork->entry; struct hfs_mdb *mdb = entry->mdb; struct hfs_extent *ext; int i, start, err; hfs_u16 need, len=0; hfs_u32 ablksz = mdb->alloc_blksz; hfs_u32 blocks, clumpablks; blocks = fork->psize; need = ablocks - blocks/ablksz; if (need < 1) { /* no need to grow the fork */ return 0; } /* round up to clumpsize */ if (entry->u.file.clumpablks) { clumpablks = entry->u.file.clumpablks; } else { clumpablks = mdb->clumpablks; } need = ((need + clumpablks - 1) / clumpablks) * clumpablks; /* find last extent record and try to extend it */ if (!(ext = find_ext(fork, blocks/ablksz - 1))) { /* somehow we couldn't find the end of the file! */ return -1; } /* determine which is the last used extent in the record */ /* then try to allocate the blocks immediately following it */ for (i=2; (i>=0) && !ext->length[i]; --i) {}; if (i>=0) { /* try to extend the last extent */ start = ext->block[i] + ext->length[i]; err = 0; lock_bitmap(mdb); len = hfs_vbm_count_free(mdb, start); if (!len) { unlock_bitmap(mdb); goto more_extents; } if (need < len) { len = need; } err = hfs_set_vbm_bits(mdb, start, len); unlock_bitmap(mdb); if (err) { relse_ext(ext); return -1; } zero_blocks(mdb, start, len); ext->length[i] += len; ext->end += len; blocks = (fork->psize += len * ablksz); need -= len; update_ext(fork, ext); } more_extents: /* add some more extents */ while (need) { len = need; err = 0; lock_bitmap(mdb); start = hfs_vbm_search_free(mdb, &len); if (need < len) { len = need; } err = hfs_set_vbm_bits(mdb, start, len); unlock_bitmap(mdb); if (!len || err) { relse_ext(ext); return -1; } zero_blocks(mdb, start, len); /* determine which is the first free extent in the record */ for (i=0; (i<3) && ext->length[i]; ++i) {}; if (i < 3) { ext->block[i] = start; ext->length[i] = len; ext->end += len; update_ext(fork, ext); } else { if (!(ext = new_extent(fork, ext, blocks/ablksz, start, len, ablksz))) { lock_bitmap(mdb); hfs_clear_vbm_bits(mdb, start, len); unlock_bitmap(mdb); return -1; } } blocks = (fork->psize += len * ablksz); need -= len; } set_cache(fork, ext); relse_ext(ext); return 0; } /*================ Global functions ================*/ /* * hfs_ext_compare() * * Description: * This is the comparison function used for the extents B-tree. In * comparing extent B-tree entries, the file id is the most * significant field (compared as unsigned ints); the fork type is * the second most significant field (compared as unsigned chars); * and the allocation block number field is the least significant * (compared as unsigned ints). * Input Variable(s): * struct hfs_ext_key *key1: pointer to the first key to compare * struct hfs_ext_key *key2: pointer to the second key to compare * Output Variable(s): * NONE * Returns: * int: negative if key1key2, and 0 if key1==key2 * Preconditions: * key1 and key2 point to "valid" (struct hfs_ext_key)s. * Postconditions: * This function has no side-effects */ int hfs_ext_compare(const struct hfs_ext_key *key1, const struct hfs_ext_key *key2) { unsigned int tmp; int retval; tmp = hfs_get_hl(key1->FNum) - hfs_get_hl(key2->FNum); if (tmp != 0) { retval = (int)tmp; } else { tmp = (unsigned char)key1->FkType - (unsigned char)key2->FkType; if (tmp != 0) { retval = (int)tmp; } else { retval = (int)(hfs_get_hs(key1->FABN) - hfs_get_hs(key2->FABN)); } } return retval; } /* * hfs_extent_adj() * * Given an hfs_fork shrink or grow the fork to hold the * forks logical size. */ void hfs_extent_adj(struct hfs_fork *fork) { if (fork) { hfs_u32 blks, ablocks, ablksz; if (fork->lsize > HFS_FORK_MAX) { fork->lsize = HFS_FORK_MAX; } blks = (fork->lsize+HFS_SECTOR_SIZE-1) >> HFS_SECTOR_SIZE_BITS; ablksz = fork->entry->mdb->alloc_blksz; ablocks = (blks + ablksz - 1) / ablksz; if (blks > fork->psize) { grow_fork(fork, ablocks); if (blks > fork->psize) { fork->lsize = fork->psize >> HFS_SECTOR_SIZE_BITS; } } else if (blks < fork->psize) { shrink_fork(fork, ablocks); } } } /* * hfs_extent_map() * * Given an hfs_fork and a block number within the fork, return the * number of the corresponding physical block on disk, or zero on * error. */ int hfs_extent_map(struct hfs_fork *fork, int block, int create) { int ablksz, ablock, offset, tmp; struct hfs_extent *ext; if (!fork || !fork->entry || !fork->entry->mdb) { return 0; } #if defined(DEBUG_EXTENTS) || defined(DEBUG_ALL) hfs_warn("hfs_extent_map: ablock %d of file %d, fork %d\n", block, fork->entry->cnid, fork->fork); #endif if (block < 0) { hfs_warn("hfs_extent_map: block < 0\n"); return 0; } if (block > (HFS_FORK_MAX >> HFS_SECTOR_SIZE_BITS)) { hfs_warn("hfs_extent_map: block(0x%08x) > big; cnid=%d " "fork=%d\n", block, fork->entry->cnid, fork->fork); return 0; } ablksz = fork->entry->mdb->alloc_blksz; offset = fork->entry->mdb->fs_start + (block % ablksz); ablock = block / ablksz; if (block >= fork->psize) { if (!create || (grow_fork(fork, ablock + 1) < 0)) return 0; } #if defined(DEBUG_EXTENTS) || defined(DEBUG_ALL) hfs_warn("(lblock %d offset %d)\n", ablock, offset); #endif if ((ext = find_ext(fork, ablock))) { dump_ext("trying new: ", ext); tmp = decode_extent(ext, ablock); relse_ext(ext); if (tmp >= 0) { return tmp*ablksz + offset; } } return 0; } /* * hfs_extent_out() * * Copy the first extent record from a (struct hfs_fork) to a (struct * raw_extent), record (normally the one in the catalog entry). */ void hfs_extent_out(const struct hfs_fork *fork, hfs_byte_t dummy[12]) { struct hfs_raw_extent *ext = (struct hfs_raw_extent *)dummy; if (fork && ext) { write_extent(ext, &fork->first); dump_ext("extent out: ", &fork->first); } } /* * hfs_extent_in() * * Copy an raw_extent to the 'first' and 'cache' fields of an hfs_fork. */ void hfs_extent_in(struct hfs_fork *fork, const hfs_byte_t dummy[12]) { const struct hfs_raw_extent *ext = (const struct hfs_raw_extent *)dummy; if (fork && ext) { read_extent(&fork->first, ext, 0); fork->cache = &fork->first; fork->first.count = 2; dump_ext("extent in: ", &fork->first); } } /* * hfs_extent_free() * * Removes from memory all extents associated with 'fil'. */ void hfs_extent_free(struct hfs_fork *fork) { if (fork) { set_cache(fork, &fork->first); if (fork->first.next) { hfs_warn("hfs_extent_free: extents in use!\n"); } } }