| /* |
| libparted - a library for manipulating disk partitions |
| Copyright (C) 2004, 2005 Free Software Foundation, Inc. |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will 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. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program; if not, write to the Free Software |
| Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
| */ |
| |
| #ifndef DISCOVER_ONLY |
| |
| #include <config.h> |
| |
| #include <parted/parted.h> |
| #include <parted/endian.h> |
| #include <parted/debug.h> |
| |
| #if ENABLE_NLS |
| # include <libintl.h> |
| # define _(String) dgettext (PACKAGE, String) |
| #else |
| # define _(String) (String) |
| #endif /* ENABLE_NLS */ |
| |
| #include "hfs.h" |
| #include "advfs.h" |
| #include "file_plus.h" |
| |
| #include "advfs_plus.h" |
| |
| /* - if a < b, 0 if a == b, + if a > b */ |
| /* Comparaison is done in the following order : */ |
| /* CNID, then fork type, then start block */ |
| static int |
| hfsplus_extent_key_cmp(HfsPPrivateGenericKey* a, HfsPPrivateGenericKey* b) |
| { |
| HfsPExtentKey* key1 = (HfsPExtentKey*) a; |
| HfsPExtentKey* key2 = (HfsPExtentKey*) b; |
| |
| if (key1->file_ID != key2->file_ID) |
| return PED_BE32_TO_CPU(key1->file_ID) < |
| PED_BE32_TO_CPU(key2->file_ID) ? |
| -1 : +1; |
| |
| if (key1->type != key2->type) |
| return (int)(key1->type - key2->type); |
| |
| if (key1->start == key2->start) |
| return 0; |
| return PED_BE32_TO_CPU(key1->start) < |
| PED_BE32_TO_CPU(key2->start) ? |
| -1 : +1; |
| } |
| |
| /* do a B-Tree lookup */ |
| /* read the first record immediatly inferior or egal to the given key */ |
| /* return 0 on error */ |
| /* record_out _must_ be large enough to receive the whole record (key + data) */ |
| /* WARNING : the search function called only handle Extents BTree */ |
| /* so modify this function if you want to do lookup in */ |
| /* other BTrees has well */ |
| int |
| hfsplus_btree_search (HfsPPrivateFile* b_tree_file, HfsPPrivateGenericKey* key, |
| void *record_out, unsigned int record_size, |
| HfsCPrivateLeafRec* record_ref) |
| { |
| uint8_t node_1[PED_SECTOR_SIZE_DEFAULT]; |
| uint8_t* node; |
| HfsPHeaderRecord* header; |
| HfsPNodeDescriptor* desc = (HfsPNodeDescriptor*) node_1; |
| HfsPPrivateGenericKey* record_key = NULL; |
| unsigned int node_number, record_number, size, bsize; |
| int i; |
| |
| /* Read the header node */ |
| if (!hfsplus_file_read_sector(b_tree_file, node_1, 0)) |
| return 0; |
| header = (HfsPHeaderRecord*) (node_1 + HFS_FIRST_REC); |
| |
| /* Get the node number of the root */ |
| node_number = PED_BE32_TO_CPU (header->root_node); |
| if (!node_number) |
| return 0; |
| |
| /* Get the size of a node in sectors and allocate buffer */ |
| size = (bsize = PED_BE16_TO_CPU (header->node_size)) / PED_SECTOR_SIZE_DEFAULT; |
| node = (uint8_t*) ped_malloc (bsize); |
| if (!node) |
| return 0; |
| desc = (HfsPNodeDescriptor*) node; |
| |
| /* Read the root node */ |
| if (!hfsplus_file_read (b_tree_file, node, |
| (PedSector) node_number * size, size)) |
| return 0; |
| |
| /* Follow the white rabbit */ |
| while (1) { |
| record_number = PED_BE16_TO_CPU (desc->rec_nb); |
| for (i = record_number; i; i--) { |
| record_key = (HfsPPrivateGenericKey*) |
| (node + PED_BE16_TO_CPU(*((uint16_t *) |
| (node+(bsize - 2*i))))); |
| /* check for obvious error in FS */ |
| if (((uint8_t*)record_key - node < HFS_FIRST_REC) |
| || ((uint8_t*)record_key - node |
| >= (signed)bsize |
| - 2 * (signed)(record_number+1))) { |
| ped_exception_throw ( |
| PED_EXCEPTION_ERROR, |
| PED_EXCEPTION_CANCEL, |
| _("The file system contains errors.")); |
| ped_free (node); |
| return 0; |
| } |
| if (hfsplus_extent_key_cmp(record_key, key) <= 0) |
| break; |
| } |
| if (!i) { ped_free (node); return 0; } |
| if (desc->type == HFS_IDX_NODE) { |
| unsigned int skip; |
| |
| skip = ( 2 + PED_BE16_TO_CPU (record_key->key_length) |
| + 1 ) & ~1; |
| node_number = PED_BE32_TO_CPU (*((uint32_t *) |
| (((uint8_t *) record_key) + skip))); |
| if (!hfsplus_file_read(b_tree_file, node, |
| (PedSector) node_number * size, |
| size)) { |
| ped_free (node); |
| return 0; |
| } |
| } else |
| break; |
| } |
| |
| /* copy the result if needed */ |
| if (record_size) |
| memcpy (record_out, record_key, record_size); |
| |
| /* send record reference if needed */ |
| if (record_ref) { |
| record_ref->node_size = size; /* in sectors */ |
| record_ref->node_number = node_number; |
| record_ref->record_pos = (uint8_t*)record_key - node; |
| record_ref->record_number = i; |
| } |
| |
| /* success */ |
| ped_free (node); |
| return 1; |
| } |
| |
| /* free the bad blocks linked list */ |
| void |
| hfsplus_free_bad_blocks_list(HfsPPrivateLinkExtent* first) |
| { |
| HfsPPrivateLinkExtent* next; |
| |
| while (first) { |
| next = first->next; |
| ped_free (first); |
| first = next; |
| } |
| } |
| |
| /* This function reads bad blocks extents in the extents file |
| and store it in f.s. specific data of fs */ |
| int |
| hfsplus_read_bad_blocks (const PedFileSystem *fs) |
| { |
| HfsPPrivateFSData* priv_data = (HfsPPrivateFSData*) |
| fs->type_specific; |
| |
| if (priv_data->bad_blocks_loaded) |
| return 1; |
| |
| { |
| uint8_t record[sizeof (HfsPExtentKey) |
| + sizeof (HfsPExtDataRec)]; |
| HfsPExtentKey search; |
| HfsPExtentKey* ret_key = (HfsPExtentKey*) record; |
| HfsPExtDescriptor* ret_cache = (HfsPExtDescriptor*) |
| (record + sizeof (HfsPExtentKey)); |
| int block, first_pass = 1; |
| unsigned int last_start; |
| |
| search.key_length = sizeof (HfsExtentKey) - 2; |
| search.type = HFS_DATA_FORK; |
| search.pad = 0; |
| search.file_ID = PED_CPU_TO_BE32 (HFS_BAD_BLOCK_ID); |
| |
| last_start = -1; block = 0; |
| while (1) { |
| int i; |
| |
| search.start = PED_CPU_TO_BE32 (block); |
| if (!hfsplus_btree_search (priv_data->extents_file, |
| (HfsPPrivateGenericKey*) &search, |
| record, sizeof (record), NULL) |
| || ret_key->file_ID != search.file_ID |
| || ret_key->type != search.type) { |
| if (first_pass) |
| break; |
| else |
| goto errbbp; |
| } |
| if (PED_BE32_TO_CPU (ret_key->start) == last_start) |
| break; |
| |
| last_start = PED_BE32_TO_CPU (ret_key->start); |
| for (i = 0; i < HFSP_EXT_NB; i++) { |
| if (ret_cache[i].block_count) { |
| HfsPPrivateLinkExtent* new_xt = |
| (HfsPPrivateLinkExtent*) ped_malloc ( |
| sizeof (HfsPPrivateLinkExtent)); |
| if (!new_xt) |
| goto errbbp; |
| new_xt->next = priv_data->bad_blocks_xtent_list; |
| memcpy (&(new_xt->extent), ret_cache+i, |
| sizeof (HfsPExtDescriptor)); |
| priv_data->bad_blocks_xtent_list = new_xt; |
| priv_data->bad_blocks_xtent_nb++; |
| block += PED_BE32_TO_CPU ( |
| ret_cache[i].block_count); |
| } |
| } |
| first_pass = 0; |
| } |
| |
| priv_data->bad_blocks_loaded = 1; |
| return 1;} |
| |
| errbbp: hfsplus_free_bad_blocks_list(priv_data->bad_blocks_xtent_list); |
| priv_data->bad_blocks_xtent_list=NULL; |
| priv_data->bad_blocks_xtent_nb=0; |
| return 0; |
| } |
| |
| /* This function check if fblock is a bad block */ |
| int |
| hfsplus_is_bad_block (const PedFileSystem *fs, unsigned int fblock) |
| { |
| HfsPPrivateFSData* priv_data = (HfsPPrivateFSData*) |
| fs->type_specific; |
| HfsPPrivateLinkExtent* walk; |
| |
| for (walk = priv_data->bad_blocks_xtent_list; walk; walk = walk->next) { |
| /* Won't compile without the strange cast ! gcc bug ? */ |
| /* or maybe C subtilties... */ |
| if ((fblock >= PED_BE32_TO_CPU (walk->extent.start_block)) && |
| (fblock < (unsigned int)(PED_BE32_TO_CPU ( |
| walk->extent.start_block) |
| + PED_BE32_TO_CPU (walk->extent.block_count)))) |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* This function returns the first sector of the last free block of |
| an HFS+ volume we can get after a hfsplus_pack_free_space_from_block call */ |
| PedSector |
| hfsplus_get_empty_end (const PedFileSystem *fs) |
| { |
| HfsPPrivateFSData* priv_data = (HfsPPrivateFSData*) |
| fs->type_specific; |
| HfsPVolumeHeader* vh = priv_data->vh; |
| HfsPPrivateLinkExtent* link; |
| unsigned int block, last_bad, end_free_blocks; |
| |
| /* find the next block to the last bad block of the volume */ |
| if (!hfsplus_read_bad_blocks (fs)) { |
| ped_exception_throw ( |
| PED_EXCEPTION_ERROR, |
| PED_EXCEPTION_CANCEL, |
| _("Bad blocks could not be read.")); |
| return 0; |
| } |
| |
| last_bad = 0; |
| for (link = priv_data->bad_blocks_xtent_list; link; link = link->next) { |
| if ((unsigned int) PED_BE32_TO_CPU (link->extent.start_block) |
| + PED_BE32_TO_CPU (link->extent.block_count) > last_bad) |
| last_bad = PED_BE32_TO_CPU (link->extent.start_block) |
| + PED_BE32_TO_CPU (link->extent.block_count); |
| } |
| |
| /* Count the free blocks from last_bad to the end of the volume */ |
| end_free_blocks = 0; |
| for (block = last_bad; |
| block < PED_BE32_TO_CPU (vh->total_blocks); |
| block++) { |
| if (!TST_BLOC_OCCUPATION(priv_data->alloc_map,block)) |
| end_free_blocks++; |
| } |
| |
| /* Calculate the block that will by the first free at |
| the end of the volume */ |
| block = PED_BE32_TO_CPU (vh->total_blocks) - end_free_blocks; |
| |
| return (PedSector) block * ( PED_BE32_TO_CPU (vh->block_size) |
| / PED_SECTOR_SIZE_DEFAULT ); |
| } |
| |
| /* On error, returns 0 */ |
| PedSector |
| hfsplus_get_min_size (const PedFileSystem *fs) |
| { |
| HfsPPrivateFSData* priv_data = (HfsPPrivateFSData*) |
| fs->type_specific; |
| PedSector min_size; |
| |
| /* don't need to add anything because every sector |
| can be part of allocation blocks in HFS+, and |
| the last block _must_ be reserved */ |
| min_size = hfsplus_get_empty_end(fs); |
| if (!min_size) return 0; |
| |
| if (priv_data->wrapper) { |
| HfsPrivateFSData* hfs_priv_data = (HfsPrivateFSData*) |
| priv_data->wrapper->type_specific; |
| unsigned int hfs_sect_block; |
| PedSector hgee; |
| hfs_sect_block = |
| PED_BE32_TO_CPU (hfs_priv_data->mdb->block_size) |
| / PED_SECTOR_SIZE_DEFAULT; |
| /* |
| * if hfs+ is embedded in an hfs wrapper then the new size is : |
| * the new size of the hfs+ volume rounded up to the size |
| * of hfs blocks |
| * + the minimum size of the hfs wrapper without any hfs+ |
| * modification |
| * - the current size of the hfs+ volume in the hfs wrapper |
| */ |
| hgee = hfs_get_empty_end(priv_data->wrapper); |
| if (!hgee) return 0; |
| min_size = ((min_size + hfs_sect_block - 1) / hfs_sect_block) |
| * hfs_sect_block |
| + hgee + 2 |
| - (PedSector) PED_BE16_TO_CPU ( hfs_priv_data->mdb |
| ->old_new.embedded |
| .location.block_count ) |
| * hfs_sect_block; |
| } |
| |
| return min_size; |
| } |
| |
| /* return the block which should be used to pack data to have |
| at least free fblock blocks at the end of the volume */ |
| unsigned int |
| hfsplus_find_start_pack (const PedFileSystem *fs, unsigned int fblock) |
| { |
| HfsPPrivateFSData* priv_data = (HfsPPrivateFSData*) |
| fs->type_specific; |
| unsigned int block; |
| |
| for (block = PED_BE32_TO_CPU (priv_data->vh->total_blocks) - 1; |
| block && fblock; |
| block--) { |
| if (!TST_BLOC_OCCUPATION(priv_data->alloc_map,block)) |
| fblock--; |
| } |
| |
| while (block && !TST_BLOC_OCCUPATION(priv_data->alloc_map,block)) |
| block--; |
| if (TST_BLOC_OCCUPATION(priv_data->alloc_map,block)) |
| block++; |
| |
| return block; |
| } |
| |
| #endif /* !DISCOVER_ONLY */ |