/* * linux/fs/hfsplus/btree.c * * Copyright (C) 2001 * Brad Boyer (flar@allandria.com) * (C) 2003 Ardis Technologies * * Handle opening/closing btree */ #include #include #include #include "hfsplus_fs.h" #include "hfsplus_raw.h" /* Release resources used by a btree */ void hfsplus_close_btree(struct hfsplus_btree *tree) { hfsplus_bnode *node; int i; if (!tree) return; for (i = 0; i < NODE_HASH_SIZE; i++) { while ((node = tree->node_hash[i])) { tree->node_hash[i] = node->next_hash; if (atomic_read(&node->refcnt)) printk("HFS+: node %d:%d still has %d user(s)!\n", node->tree->cnid, node->this, atomic_read(&node->refcnt)); hfsplus_bnode_free(node); tree->node_hash_cnt--; } } iput(tree->inode); kfree(tree); } /* Fill in extra data in tree structure from header node */ static void hfsplus_read_treeinfo(hfsplus_btree *tree, hfsplus_btree_head *hdr) { unsigned int shift, size; if (!tree || !hdr) return; tree->root = be32_to_cpu(hdr->root); tree->leaf_count = be32_to_cpu(hdr->leaf_count); tree->leaf_head = be32_to_cpu(hdr->leaf_head); tree->leaf_tail = be32_to_cpu(hdr->leaf_tail); tree->node_count = be32_to_cpu(hdr->node_count); tree->free_nodes = be32_to_cpu(hdr->free_nodes); tree->attributes = be32_to_cpu(hdr->attributes); tree->node_size = be16_to_cpu(hdr->node_size); tree->max_key_len = be16_to_cpu(hdr->max_key_len); tree->depth = be16_to_cpu(hdr->depth); size = tree->node_size; if (size & (size - 1)) /* panic */; for (shift = 0; size >>= 1; shift += 1) ; tree->node_size_shift = shift; tree->pages_per_bnode = (tree->node_size + PAGE_CACHE_SIZE - 1) >> PAGE_CACHE_SHIFT; } static void hfsplus_write_treeinfo(hfsplus_btree *tree, hfsplus_btree_head *hdr) { hdr->root = cpu_to_be32(tree->root); hdr->leaf_count = cpu_to_be32(tree->leaf_count); hdr->leaf_head = cpu_to_be32(tree->leaf_head); hdr->leaf_tail = cpu_to_be32(tree->leaf_tail); hdr->node_count = cpu_to_be32(tree->node_count); hdr->free_nodes = cpu_to_be32(tree->free_nodes); hdr->attributes = cpu_to_be32(tree->attributes); hdr->depth = cpu_to_be16(tree->depth); } /* Get a reference to a B*Tree and do some initial checks */ hfsplus_btree *hfsplus_open_btree(struct super_block *sb, u32 id) { hfsplus_btree *tree; hfsplus_btree_head *head; struct address_space *mapping; struct page *page; tree = kmalloc(sizeof(struct hfsplus_btree), GFP_KERNEL); if (!tree) return NULL; memset(tree, 0, sizeof(struct hfsplus_btree)); init_MUTEX(&tree->tree_lock); spin_lock_init(&tree->hash_lock); /* Set the correct compare function */ tree->sb = sb; tree->cnid = id; if (id == HFSPLUS_EXT_CNID) { tree->keycmp = hfsplus_cmp_ext_key; } else if (id == HFSPLUS_CAT_CNID) { tree->keycmp = hfsplus_cmp_cat_key; } else { printk("HFS+-fs: unknown B*Tree requested\n"); goto free_tree; } tree->inode = iget(sb, id); if (!tree->inode) goto free_tree; mapping = tree->inode->i_mapping; page = grab_cache_page(mapping, 0); if (!page) goto free_tree; if (!PageUptodate(page)) { if (mapping->a_ops->readpage(NULL, page)) goto fail_page; wait_on_page_locked(page); if (!PageUptodate(page)) goto fail_page; } else unlock_page(page); /* Load the header */ head = (hfsplus_btree_head *)(kmap(page) + sizeof(hfsplus_btree_node_desc)); hfsplus_read_treeinfo(tree, head); kunmap(page); page_cache_release(page); return tree; fail_page: page_cache_release(page); free_tree: iput(tree->inode); kfree(tree); return NULL; } void hfsplus_write_btree(struct hfsplus_btree *tree) { hfsplus_btree_head *head; hfsplus_bnode *node; struct page *page; node = hfsplus_find_bnode(tree, 0); if (!node) /* panic? */ return; /* Load the header */ page = node->page[0]; head = (hfsplus_btree_head *)(kmap(page) + sizeof(hfsplus_btree_node_desc)); hfsplus_write_treeinfo(tree, head); kunmap(page); set_page_dirty(page); hfsplus_put_bnode(node); } hfsplus_bnode *hfsplus_btree_alloc_node(hfsplus_btree *tree) { hfsplus_bnode *node; struct page **pagep; u32 nidx; u16 idx, off, len; u8 *data, byte, m; int i; while (!tree->free_nodes) { loff_t size; int res; res = hfsplus_extend_file(tree->inode); if (res) return ERR_PTR(res); HFSPLUS_I(tree->inode).total_blocks = HFSPLUS_I(tree->inode).alloc_blocks; size = HFSPLUS_I(tree->inode).total_blocks; size <<= tree->sb->s_blocksize_bits; tree->inode->i_size = size; do_div(size, (u32)tree->node_size); tree->free_nodes = (u32)size - tree->node_count; tree->node_count = size; } nidx = 0; node = hfsplus_find_bnode(tree, nidx); len = hfsplus_brec_lenoff(node, 2, &off); off += node->page_offset; pagep = node->page + (off >> PAGE_CACHE_SHIFT); data = hfsplus_kmap(*pagep); off &= ~PAGE_CACHE_MASK; idx = 0; for (;;) { while (len) { byte = data[off]; if (byte != 0xff) { for (m = 0x80, i = 0; i < 8; m >>= 1, i++) { if (!(byte & m)) { idx += i; data[off] |= m; set_page_dirty(*pagep); hfsplus_kunmap(*pagep); tree->free_nodes--; mark_inode_dirty(tree->inode); hfsplus_put_bnode(node); return hfsplus_create_bnode(tree, idx); } } } if (++off >= PAGE_CACHE_SIZE) { hfsplus_kunmap(*pagep++); data = hfsplus_kmap(*pagep); off = 0; } idx += 8; len--; } nidx = node->next; hfsplus_put_bnode(node); if (!nidx) { printk("need new bmap node...\n"); hfsplus_kunmap(*pagep); return ERR_PTR(-ENOSPC); } node = hfsplus_find_bnode(tree, nidx); len = hfsplus_brec_lenoff(node, 0, &off); off += node->page_offset; pagep = node->page + (off >> PAGE_CACHE_SHIFT); data = hfsplus_kmap(*pagep); off &= ~PAGE_CACHE_MASK; } } void hfsplus_btree_remove_node(hfsplus_bnode *node) { hfsplus_btree *tree; hfsplus_bnode *tmp; u32 cnid; tree = node->tree; if (node->prev) { tmp = hfsplus_find_bnode(tree, node->prev); tmp->next = node->next; cnid = cpu_to_be32(tmp->next); hfsplus_bnode_writebytes(tmp, &cnid, offsetof(hfsplus_btree_node_desc, next), 4); hfsplus_put_bnode(tmp); } else if (node->kind == HFSPLUS_NODE_LEAF) tree->leaf_head = node->next; if (node->next) { tmp = hfsplus_find_bnode(tree, node->next); tmp->prev = node->prev; cnid = cpu_to_be32(tmp->prev); hfsplus_bnode_writebytes(tmp, &cnid, offsetof(hfsplus_btree_node_desc, prev), 4); hfsplus_put_bnode(tmp); } else if (node->kind == HFSPLUS_NODE_LEAF) tree->leaf_tail = node->prev; // move down? if (!node->prev && !node->next) { printk("hfsplus_btree_del_level\n"); } if (!node->parent) { tree->root = 0; tree->depth = 0; } set_bit(HFSPLUS_BNODE_DELETED, &node->flags); } void hfsplus_btree_free_node(hfsplus_bnode *node) { hfsplus_btree *tree; struct page *page; u16 off, len; u32 nidx; u8 *data, byte, m; dprint(DBG_BNODE_MOD, "btree_free_node: %u\n", node->this); tree = node->tree; nidx = node->this; node = hfsplus_find_bnode(tree, 0); len = hfsplus_brec_lenoff(node, 2, &off); while (nidx >= len * 8) { u32 i; nidx -= len * 8; i = node->next; hfsplus_put_bnode(node); if (!nidx) /* panic */; node = hfsplus_find_bnode(tree, nidx); if (node->kind != HFSPLUS_NODE_MAP) /* panic */; len = hfsplus_brec_lenoff(node, 0, &off); } off += node->page_offset + nidx / 8; page = node->page[off >> PAGE_CACHE_SHIFT]; data = hfsplus_kmap(page); off &= ~PAGE_CACHE_MASK; m = 1 << (~nidx & 7); byte = data[off]; if (!(byte & m)) { BUG(); /* panic */ hfsplus_kunmap(page); hfsplus_put_bnode(node); return; } data[off] = byte & ~m; set_page_dirty(page); hfsplus_kunmap(page); hfsplus_put_bnode(node); tree->free_nodes++; mark_inode_dirty(tree->inode); }