/* * hfs_btree_free() * * Description: * This function frees a (struct hfs_btree) obtained from hfs_btree_init(). * Called by hfs_put_super(). * Input Variable(s): * struct hfs_btree *bt: pointer to the (struct hfs_btree) to free * Output Variable(s): * NONE * Returns: * void * Preconditions: * 'bt' is NULL or points to a "valid" (struct hfs_btree) * Postconditions: * If 'bt' points to a "valid" (struct hfs_btree) then all (struct * hfs_bnode)s associated with 'bt' are freed by calling * hfs_bnode_ditch() and the memory associated with the (struct * hfs_btree) is freed. * If 'bt' is NULL or not "valid" an error is printed and nothing * is changed. */ void hfs_btree_free(struct hfs_btree *bt) { int lcv; if (bt && (bt->magic == HFS_BTREE_MAGIC)) { hfs_extent_free(&bt->entry.u.file.data_fork); for (lcv=0; lcv<HFS_CACHELEN; ++lcv) { #if defined(DEBUG_BNODES) || defined(DEBUG_ALL) hfs_warn("deleting nodes from bucket %d:\n", lcv); #endif hfs_bnode_ditch(bt->cache[lcv]); } #if defined(DEBUG_BNODES) || defined(DEBUG_ALL) hfs_warn("deleting header and bitmap nodes\n"); #endif hfs_bnode_ditch(&bt->head); #if defined(DEBUG_BNODES) || defined(DEBUG_ALL) hfs_warn("deleting root node\n"); #endif hfs_bnode_ditch(bt->root); HFS_DELETE(bt); } else if (bt) { hfs_warn("hfs_btree_free: corrupted hfs_btree.\n"); } }
/* * 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); }
/* * hfs_put_inode() * * This is the put_inode() entry in the super_operations for HFS * filesystems. The purpose is to perform any filesystem-dependent * cleanup necessary when the use-count of an inode falls to zero. */ void hfs_put_inode(struct inode * inode) { struct hfs_cat_entry *entry = HFS_I(inode)->entry; hfs_cat_put(entry); if (inode->i_count == 1) { struct hfs_hdr_layout *tmp = HFS_I(inode)->layout; if (tmp) { HFS_I(inode)->layout = NULL; HFS_DELETE(tmp); } } }
/* * hfs_put_inode() * * This is the put_inode() entry in the super_operations for HFS * filesystems. The purpose is to perform any filesystem-dependent * cleanup necessary when the use-count of an inode falls to zero. */ void hfs_put_inode(struct inode * inode) { struct hfs_cat_entry *entry = HFS_I(inode)->entry; lock_kernel(); hfs_cat_put(entry); if (atomic_read(&inode->i_count) == 1) { struct hfs_hdr_layout *tmp = HFS_I(inode)->layout; if (tmp) { HFS_I(inode)->layout = NULL; HFS_DELETE(tmp); } } unlock_kernel(); }
/* * 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; }
/* * hfs_bnode_ditch() * * Description: * This function deletes an entire linked list of bnodes, so it * does not need to keep the linked list consistent as * hfs_bnode_delete() does. * Called by hfs_btree_init() for error cleanup and by hfs_btree_free(). * Input Variable(s): * struct hfs_bnode *bn: pointer to the first (struct hfs_bnode) in * the linked list to be deleted. * Output Variable(s): * NONE * Returns: * void * Preconditions: * 'bn' is NULL or points to a "valid" (struct hfs_bnode) with a 'prev' * field of NULL. * Postconditions: * 'bn' and all (struct hfs_bnode)s in the chain of 'next' pointers * are deleted, freeing the associated memory and hfs_buffer_put()ing * the associated buffer. */ static void hfs_bnode_ditch(struct hfs_bnode *bn) { struct hfs_bnode *tmp; #if defined(DEBUG_BNODES) || defined(DEBUG_ALL) extern int bnode_count; #endif while (bn != NULL) { tmp = bn->next; #if defined(DEBUG_BNODES) || defined(DEBUG_ALL) hfs_warn("deleting node %d from tree %d with count %d\n", bn->node, (int)ntohl(bn->tree->entry.cnid), bn->count); --bnode_count; #endif hfs_buffer_put(bn->buf); /* safe: checks for NULL argument */ /* free all but the header */ if (bn->node) { HFS_DELETE(bn); } bn = tmp; } }
/* * 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); }
/* * hfs_btree_init() * * Description: * Given some vital information from the MDB (HFS superblock), * initializes the fields of a (struct hfs_btree). * Input Variable(s): * struct hfs_mdb *mdb: pointer to the MDB * ino_t cnid: the CNID (HFS_CAT_CNID or HFS_EXT_CNID) of the B-tree * hfs_u32 tsize: the size, in bytes, of the B-tree * hfs_u32 csize: the size, in bytes, of the clump size for the B-tree * Output Variable(s): * NONE * Returns: * (struct hfs_btree *): pointer to the initialized hfs_btree on success, * or NULL on failure * Preconditions: * 'mdb' points to a "valid" (struct hfs_mdb) * Postconditions: * Assuming the inputs are what they claim to be, no errors occur * reading from disk, and no inconsistencies are noticed in the data * read from disk, the return value is a pointer to a "valid" * (struct hfs_btree). If there are errors reading from disk or * inconsistencies are noticed in the data read from disk, then and * all resources that were allocated are released and NULL is * returned. If the inputs are not what they claim to be or if they * are unnoticed inconsistencies in the data read from disk then the * returned hfs_btree is probably going to lead to errors when it is * used in a non-trivial way. */ struct hfs_btree * hfs_btree_init(struct hfs_mdb *mdb, ino_t cnid, hfs_byte_t ext[12], hfs_u32 tsize, hfs_u32 csize) { struct hfs_btree * bt; struct BTHdrRec * th; struct hfs_bnode * tmp; unsigned int next; #if defined(DEBUG_HEADER) || defined(DEBUG_ALL) unsigned char *p, *q; #endif if (!mdb || !ext || !HFS_NEW(bt)) { goto bail3; } bt->magic = HFS_BTREE_MAGIC; bt->sys_mdb = mdb->sys_mdb; bt->reserved = 0; bt->lock = 0; hfs_init_waitqueue(&bt->wait); bt->dirt = 0; memset(bt->cache, 0, sizeof(bt->cache)); #if 0 /* this is a fake entry. so we don't need to initialize it. */ memset(&bt->entry, 0, sizeof(bt->entry)); hfs_init_waitqueue(&bt->entry.wait); INIT_LIST_HEAD(&bt->entry.hash); INIT_LIST_HEAD(&bt->entry.list); #endif bt->entry.mdb = mdb; bt->entry.cnid = cnid; bt->entry.type = HFS_CDR_FIL; bt->entry.u.file.magic = HFS_FILE_MAGIC; bt->entry.u.file.clumpablks = (csize / mdb->alloc_blksz) >> HFS_SECTOR_SIZE_BITS; bt->entry.u.file.data_fork.entry = &bt->entry; bt->entry.u.file.data_fork.lsize = tsize; bt->entry.u.file.data_fork.psize = tsize >> HFS_SECTOR_SIZE_BITS; bt->entry.u.file.data_fork.fork = HFS_FK_DATA; hfs_extent_in(&bt->entry.u.file.data_fork, ext); hfs_bnode_read(&bt->head, bt, 0, HFS_STICKY); if (!hfs_buffer_ok(bt->head.buf)) { goto bail2; } th = (struct BTHdrRec *)((char *)hfs_buffer_data(bt->head.buf) + sizeof(struct NodeDescriptor)); /* read in the bitmap nodes (if any) */ tmp = &bt->head; while ((next = tmp->ndFLink)) { if (!HFS_NEW(tmp->next)) { goto bail2; } hfs_bnode_read(tmp->next, bt, next, HFS_STICKY); if (!hfs_buffer_ok(tmp->next->buf)) { goto bail2; } tmp->next->prev = tmp; tmp = tmp->next; } if (hfs_get_ns(th->bthNodeSize) != htons(HFS_SECTOR_SIZE)) { hfs_warn("hfs_btree_init: bthNodeSize!=512 not supported\n"); goto bail2; } if (cnid == htonl(HFS_CAT_CNID)) { bt->compare = (hfs_cmpfn)hfs_cat_compare; } else if (cnid == htonl(HFS_EXT_CNID)) { bt->compare = (hfs_cmpfn)hfs_ext_compare; } else { goto bail2; } bt->bthDepth = hfs_get_hs(th->bthDepth); bt->bthRoot = hfs_get_hl(th->bthRoot); bt->bthNRecs = hfs_get_hl(th->bthNRecs); bt->bthFNode = hfs_get_hl(th->bthFNode); bt->bthLNode = hfs_get_hl(th->bthLNode); bt->bthNNodes = hfs_get_hl(th->bthNNodes); bt->bthFree = hfs_get_hl(th->bthFree); bt->bthKeyLen = hfs_get_hs(th->bthKeyLen); #if defined(DEBUG_HEADER) || defined(DEBUG_ALL) hfs_warn("bthDepth %d\n", bt->bthDepth); hfs_warn("bthRoot %d\n", bt->bthRoot); hfs_warn("bthNRecs %d\n", bt->bthNRecs); hfs_warn("bthFNode %d\n", bt->bthFNode); hfs_warn("bthLNode %d\n", bt->bthLNode); hfs_warn("bthKeyLen %d\n", bt->bthKeyLen); hfs_warn("bthNNodes %d\n", bt->bthNNodes); hfs_warn("bthFree %d\n", bt->bthFree); p = (unsigned char *)hfs_buffer_data(bt->head.buf); q = p + HFS_SECTOR_SIZE; while (p < q) { hfs_warn("%02x %02x %02x %02x %02x %02x %02x %02x " "%02x %02x %02x %02x %02x %02x %02x %02x\n", *p++, *p++, *p++, *p++, *p++, *p++, *p++, *p++, *p++, *p++, *p++, *p++, *p++, *p++, *p++, *p++); } #endif /* Read in the root if it exists. The header always exists, but the root exists only if the tree is non-empty */ if (bt->bthDepth && bt->bthRoot) { if (!HFS_NEW(bt->root)) { goto bail2; } hfs_bnode_read(bt->root, bt, bt->bthRoot, HFS_STICKY); if (!hfs_buffer_ok(bt->root->buf)) { goto bail1; } } else { bt->root = NULL; } return bt; bail1: hfs_bnode_ditch(bt->root); bail2: hfs_bnode_ditch(&bt->head); HFS_DELETE(bt); bail3: return NULL; }
/* * 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; }