/* libparted - a library for manipulating disk partitions Copyright (C) 2004-2005, 2007, 2009-2014, 2019-2022 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 3 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, see . */ #ifndef DISCOVER_ONLY #include #include #include #include #include #if ENABLE_NLS # include # define _(String) dgettext (PACKAGE, String) #else # define _(String) (String) #endif /* ENABLE_NLS */ #include "hfs.h" #include "file.h" #include "advfs.h" #include "cache.h" #include "reloc.h" /* This function moves data of size blocks starting at block *ptr_fblock to block *ptr_to_fblock */ /* return new start or -1 on failure */ static int hfs_effect_move_extent (PedFileSystem *fs, unsigned int *ptr_fblock, unsigned int *ptr_to_fblock, unsigned int size) { HfsPrivateFSData* priv_data = (HfsPrivateFSData*) fs->type_specific; unsigned int i, ok = 0; unsigned int next_to_fblock; unsigned int start, stop; PED_ASSERT (hfs_block != NULL); PED_ASSERT (*ptr_to_fblock <= *ptr_fblock); /* quiet gcc */ start = stop = 0; /* Try to fit the extent AT or _BEFORE_ the wanted place, or then in the gap between dest and source. If failed try to fit the extent after source, for 2 pass relocation The extent is always copied in a non overlapping way */ /* Backward search */ /* 1 pass relocation AT or BEFORE *ptr_to_fblock */ if (*ptr_to_fblock != *ptr_fblock) { start = stop = *ptr_fblock < *ptr_to_fblock+size ? *ptr_fblock : *ptr_to_fblock+size; while (start && stop-start != size) { --start; if (TST_BLOC_OCCUPATION(priv_data->alloc_map,start)) stop = start; } ok = (stop-start == size); } /* Forward search */ /* 1 pass relocation in the gap merged with 2 pass reloc after source */ if (!ok && *ptr_to_fblock != *ptr_fblock) { start = stop = *ptr_to_fblock+1; while (stop < PED_BE16_TO_CPU(priv_data->mdb->total_blocks) && stop-start != size) { if (TST_BLOC_OCCUPATION(priv_data->alloc_map,stop)) start = stop + 1; ++stop; } ok = (stop-start == size); } /* new non overlapping room has been found ? */ if (ok) { /* enough room */ unsigned int j; unsigned int start_block = PED_BE16_TO_CPU (priv_data->mdb->start_block ); unsigned int block_sz = (PED_BE32_TO_CPU (priv_data->mdb->block_size) / PED_SECTOR_SIZE_DEFAULT); if (stop > *ptr_to_fblock && stop <= *ptr_fblock) /* Fit in the gap */ next_to_fblock = stop; else /* Before or after the gap */ next_to_fblock = *ptr_to_fblock; /* move blocks */ for (i = 0; i < size; /*i+=j*/) { PedSector abs_sector; unsigned int ai; j = size - i; j = (j < hfs_block_count) ? j : hfs_block_count ; abs_sector = start_block + (PedSector) (*ptr_fblock + i) * block_sz; if (!ped_geometry_read (fs->geom, hfs_block, abs_sector, block_sz * j)) return -1; abs_sector = start_block + (PedSector) (start + i) * block_sz; if (!ped_geometry_write (fs->geom,hfs_block,abs_sector, block_sz * j)) return -1; for (ai = i+j; i < ai; i++) { /* free source block */ CLR_BLOC_OCCUPATION(priv_data->alloc_map, *ptr_fblock + i); /* set dest block */ SET_BLOC_OCCUPATION(priv_data->alloc_map, start + i); } } if (!ped_geometry_sync_fast (fs->geom)) return -1; *ptr_fblock += size; *ptr_to_fblock = next_to_fblock; } else { if (*ptr_fblock != *ptr_to_fblock) /* not enough room, but try to continue */ ped_exception_throw (PED_EXCEPTION_WARNING, PED_EXCEPTION_IGNORE, _("An extent has not been relocated.")); start = *ptr_fblock; *ptr_fblock = *ptr_to_fblock = start + size; } return start; } /* Update MDB */ /* Return 0 if an error occurred */ /* Return 1 if everything ok */ int hfs_update_mdb (PedFileSystem *fs) { HfsPrivateFSData* priv_data = (HfsPrivateFSData*) fs->type_specific; uint8_t node[PED_SECTOR_SIZE_DEFAULT]; if (!ped_geometry_read (fs->geom, node, 2, 1)) return 0; memcpy (node, priv_data->mdb, sizeof (HfsMasterDirectoryBlock)); if ( !ped_geometry_write (fs->geom, node, 2, 1) || !ped_geometry_write (fs->geom, node, fs->geom->length - 2, 1) || !ped_geometry_sync_fast (fs->geom)) return 0; return 1; } /* Generic relocator */ /* replace previous hfs_do_move_* */ static int hfs_do_move (PedFileSystem* fs, unsigned int *ptr_src, unsigned int *ptr_dest, HfsCPrivateCache* cache, HfsCPrivateExtent* ref) { uint8_t node[PED_SECTOR_SIZE_DEFAULT]; HfsPrivateFSData* priv_data = (HfsPrivateFSData*) fs->type_specific; HfsPrivateFile* file; HfsExtDescriptor* extent; HfsCPrivateExtent* move; int new_start; new_start = hfs_effect_move_extent (fs, ptr_src, ptr_dest, ref->ext_length); if (new_start == -1) return -1; if (ref->ext_start != (unsigned) new_start) { /* Load, modify & save */ switch (ref->where) { /******** MDB *********/ case CR_PRIM_CAT : priv_data->catalog_file ->first[ref->ref_index].start_block = PED_CPU_TO_BE16(new_start); goto CR_PRIM; case CR_PRIM_EXT : priv_data->extent_file ->first[ref->ref_index].start_block = PED_CPU_TO_BE16(new_start); CR_PRIM : extent = ( HfsExtDescriptor* ) ( (uint8_t*)priv_data->mdb + ref->ref_offset ); extent[ref->ref_index].start_block = PED_CPU_TO_BE16(new_start); if (!hfs_update_mdb(fs)) return -1; break; /********* BTREE *******/ case CR_BTREE_EXT_CAT : if (priv_data->catalog_file ->cache[ref->ref_index].start_block == PED_CPU_TO_BE16(ref->ext_start)) priv_data->catalog_file ->cache[ref->ref_index].start_block = PED_CPU_TO_BE16(new_start); /* FALLTHROUGH */ case CR_BTREE_EXT_0 : file = priv_data->extent_file; goto CR_BTREE; case CR_BTREE_CAT : file = priv_data->catalog_file; CR_BTREE: PED_ASSERT(ref->sect_by_block == 1 && ref->ref_offset < PED_SECTOR_SIZE_DEFAULT); if (!hfs_file_read_sector(file, node, ref->ref_block)) return -1; extent = ( HfsExtDescriptor* ) (node + ref->ref_offset); extent[ref->ref_index].start_block = PED_CPU_TO_BE16(new_start); if (!hfs_file_write_sector(file, node, ref->ref_block) || !ped_geometry_sync_fast (fs->geom)) return -1; break; /********** BUG ********/ default : ped_exception_throw ( PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL, _("A reference to an extent comes from a place " "it should not. You should check the file " "system!")); return -1; break; } /* Update the cache */ move = hfsc_cache_move_extent(cache, ref->ext_start, new_start); if (!move) return -1; /* "cleanly" fail */ PED_ASSERT(move == ref); /* generate a bug */ } return new_start; } /* 0 error, 1 ok */ static int hfs_save_allocation(PedFileSystem* fs) { HfsPrivateFSData* priv_data = (HfsPrivateFSData*) fs->type_specific; unsigned int map_sectors; map_sectors = ( PED_BE16_TO_CPU (priv_data->mdb->total_blocks) + PED_SECTOR_SIZE_DEFAULT * 8 - 1 ) / (PED_SECTOR_SIZE_DEFAULT * 8); return ( ped_geometry_write (fs->geom, priv_data->alloc_map, PED_BE16_TO_CPU (priv_data->mdb->volume_bitmap_block), map_sectors) ); } /* This function moves an extent starting at block fblock to block to_fblock if there's enough room */ /* Return 1 if everything was fine */ /* Return -1 if an error occurred */ /* Return 0 if no extent was found */ /* Generic search thanks to the file system cache */ static int hfs_move_extent_starting_at (PedFileSystem *fs, unsigned int *ptr_fblock, unsigned int *ptr_to_fblock, HfsCPrivateCache* cache) { HfsCPrivateExtent* ref; unsigned int old_start, new_start; /* Reference search powered by the cache... */ /* This is the optimisation secret :) */ ref = hfsc_cache_search_extent(cache, *ptr_fblock); if (!ref) return 0; /* not found */ old_start = *ptr_fblock; new_start = hfs_do_move(fs, ptr_fblock, ptr_to_fblock, cache, ref); if (new_start == (unsigned int) -1) return -1; if (new_start > old_start) { /* detect 2 pass reloc */ new_start = hfs_do_move(fs,&new_start,ptr_to_fblock,cache,ref); if (new_start == (unsigned int) -1 || new_start > old_start) return -1; } /* allocation bitmap save is not atomic with data relocation */ /* so we only do it a few times, and without syncing */ /* The unmounted bit protect us anyway */ hfs_save_allocation(fs); return 1; } static int hfs_cache_from_mdb(HfsCPrivateCache* cache, PedFileSystem* fs, PedTimer* timer) { HfsPrivateFSData* priv_data = (HfsPrivateFSData*) fs->type_specific; HfsExtDescriptor* extent; unsigned int j; extent = priv_data->mdb->extents_file_rec; for (j = 0; j < HFS_EXT_NB; ++j) { if (!extent[j].block_count) break; if (!hfsc_cache_add_extent( cache, PED_BE16_TO_CPU(extent[j].start_block), PED_BE16_TO_CPU(extent[j].block_count), 0, /* unused for mdb */ ((uint8_t*)extent) - ((uint8_t*)priv_data->mdb), 1, /* load/save only 1 sector */ CR_PRIM_EXT, j ) ) return 0; } extent = priv_data->mdb->catalog_file_rec; for (j = 0; j < HFS_EXT_NB; ++j) { if (!extent[j].block_count) break; if (!hfsc_cache_add_extent( cache, PED_BE16_TO_CPU(extent[j].start_block), PED_BE16_TO_CPU(extent[j].block_count), 0, ((uint8_t*)extent) - ((uint8_t*)priv_data->mdb), 1, CR_PRIM_CAT, j ) ) return 0; } return 1; } static int hfs_cache_from_catalog(HfsCPrivateCache* cache, PedFileSystem* fs, PedTimer* timer) { HfsPrivateFSData* priv_data = (HfsPrivateFSData*) fs->type_specific; uint8_t node[PED_SECTOR_SIZE_DEFAULT]; HfsHeaderRecord* header; HfsNodeDescriptor* desc = (HfsNodeDescriptor*) node; HfsCatalogKey* catalog_key; HfsCatalog* catalog_data; HfsExtDescriptor* extent; unsigned int leaf_node, record_number; unsigned int i, j; uint16_t catalog_pos; if (!priv_data->catalog_file->sect_nb) { ped_exception_throw ( PED_EXCEPTION_INFORMATION, PED_EXCEPTION_OK, _("This HFS volume has no catalog file. " "This is very unusual!")); return 1; } if (!hfs_file_read_sector (priv_data->catalog_file, node, 0)) return 0; uint16_t offset; memcpy(&offset, node+(PED_SECTOR_SIZE_DEFAULT-2), sizeof(uint16_t)); header = (HfsHeaderRecord*) (node + PED_BE16_TO_CPU(offset)); for (leaf_node = PED_BE32_TO_CPU (header->first_leaf_node); leaf_node; leaf_node = PED_BE32_TO_CPU (desc->next)) { if (!hfs_file_read_sector (priv_data->catalog_file, node, leaf_node)) return 0; record_number = PED_BE16_TO_CPU (desc->rec_nb); for (i = 1; i <= record_number; ++i) { /* undocumented alignement */ uint16_t value; memcpy(&value, node+(PED_SECTOR_SIZE_DEFAULT - (2*i)), sizeof(uint16_t)); catalog_pos = PED_BE16_TO_CPU(value); catalog_key = (HfsCatalogKey*) (node + catalog_pos); unsigned int skip; skip = (1 + catalog_key->key_length + 1) & ~1; catalog_data = (HfsCatalog*)(node+catalog_pos+skip); /* check for obvious error in FS */ if ((catalog_pos < HFS_FIRST_REC) || ((uint8_t*)catalog_data - node >= PED_SECTOR_SIZE_DEFAULT - 2 * (signed)(record_number+1))) { ped_exception_throw ( PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL, _("The file system contains errors.")); return 0; } if (catalog_data->type != HFS_CAT_FILE) continue; extent = catalog_data->sel.file.extents_data; for (j = 0; j < HFS_EXT_NB; ++j) { if (!extent[j].block_count) break; if (!hfsc_cache_add_extent( cache, PED_BE16_TO_CPU(extent[j].start_block), PED_BE16_TO_CPU(extent[j].block_count), leaf_node, (uint8_t*)extent - node, 1, /* hfs => btree block = 512 b */ CR_BTREE_CAT, j ) ) return 0; } extent = catalog_data->sel.file.extents_res; for (j = 0; j < HFS_EXT_NB; ++j) { if (!extent[j].block_count) break; if (!hfsc_cache_add_extent( cache, PED_BE16_TO_CPU(extent[j].start_block), PED_BE16_TO_CPU(extent[j].block_count), leaf_node, (uint8_t*)extent - node, 1, /* hfs => btree block = 512 b */ CR_BTREE_CAT, j ) ) return 0; } } } return 1; } static int hfs_cache_from_extent(HfsCPrivateCache* cache, PedFileSystem* fs, PedTimer* timer) { HfsPrivateFSData* priv_data = (HfsPrivateFSData*) fs->type_specific; uint8_t node[PED_SECTOR_SIZE_DEFAULT]; HfsHeaderRecord* header; HfsNodeDescriptor* desc = (HfsNodeDescriptor*) node; HfsExtentKey* extent_key; HfsExtDescriptor* extent; unsigned int leaf_node, record_number; unsigned int i, j; uint16_t extent_pos; if (!priv_data->extent_file->sect_nb) { ped_exception_throw ( PED_EXCEPTION_INFORMATION, PED_EXCEPTION_OK, _("This HFS volume has no extents overflow " "file. This is quite unusual!")); return 1; } if (!hfs_file_read_sector (priv_data->extent_file, node, 0)) return 0; uint16_t offset; memcpy(&offset, node+(PED_SECTOR_SIZE_DEFAULT-2), sizeof(uint16_t)); header = (HfsHeaderRecord*) (node + PED_BE16_TO_CPU(offset)); for (leaf_node = PED_BE32_TO_CPU (header->first_leaf_node); leaf_node; leaf_node = PED_BE32_TO_CPU (desc->next)) { if (!hfs_file_read_sector (priv_data->extent_file, node, leaf_node)) return 0; record_number = PED_BE16_TO_CPU (desc->rec_nb); for (i = 1; i <= record_number; i++) { uint8_t where; uint16_t value; memcpy(&value, node+(PED_SECTOR_SIZE_DEFAULT - (2*i)), sizeof(uint16_t)); extent_pos = PED_BE16_TO_CPU(value); extent_key = (HfsExtentKey*)(node + extent_pos); /* size is cst */ extent = (HfsExtDescriptor*)(node+extent_pos+sizeof(HfsExtentKey)); /* check for obvious error in FS */ if ((extent_pos < HFS_FIRST_REC) || ((uint8_t*)extent - node >= PED_SECTOR_SIZE_DEFAULT - 2 * (signed)(record_number+1))) { ped_exception_throw ( PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL, _("The file system contains errors.")); return 0; } switch (extent_key->file_ID) { case PED_CPU_TO_BE32 (HFS_XTENT_ID) : if (ped_exception_throw ( PED_EXCEPTION_WARNING, PED_EXCEPTION_IGNORE_CANCEL, _("The extents overflow file should not" " contain its own extents! You " "should check the file system.")) != PED_EXCEPTION_IGNORE) return 0; where = CR_BTREE_EXT_EXT; break; case PED_CPU_TO_BE32 (HFS_CATALOG_ID) : where = CR_BTREE_EXT_CAT; break; default : where = CR_BTREE_EXT_0; break; } for (j = 0; j < HFS_EXT_NB; ++j) { if (!extent[j].block_count) break; if (!hfsc_cache_add_extent( cache, PED_BE16_TO_CPU(extent[j].start_block), PED_BE16_TO_CPU(extent[j].block_count), leaf_node, (uint8_t*)extent - node, 1, /* hfs => btree block = 512 b */ where, j ) ) return 0; } } } return 1; } /* This function cache every extents start and length stored in any fs structure into the adt defined in cache.[ch] Returns NULL on failure */ static HfsCPrivateCache* hfs_cache_extents(PedFileSystem *fs, PedTimer* timer) { HfsPrivateFSData* priv_data = (HfsPrivateFSData*) fs->type_specific; HfsCPrivateCache* ret; unsigned int file_number, block_number; file_number = PED_BE32_TO_CPU(priv_data->mdb->file_count); block_number = PED_BE16_TO_CPU(priv_data->mdb->total_blocks); ret = hfsc_new_cache(block_number, file_number); if (!ret) return NULL; if (!hfs_cache_from_mdb(ret, fs, timer) || !hfs_cache_from_catalog(ret, fs, timer) || !hfs_cache_from_extent(ret, fs, timer)) { ped_exception_throw( PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL, _("Could not cache the file system in memory.")); hfsc_delete_cache(ret); return NULL; } return ret; } /* This function moves file's data to compact used and free space, starting at fblock block */ /* return 0 on error */ int hfs_pack_free_space_from_block (PedFileSystem *fs, unsigned int fblock, PedTimer* timer, unsigned int to_free) { PedSector bytes_buff; HfsPrivateFSData* priv_data = (HfsPrivateFSData*) fs->type_specific; HfsMasterDirectoryBlock* mdb = priv_data->mdb; HfsCPrivateCache* cache; unsigned int to_fblock = fblock; unsigned int start = fblock; unsigned int divisor = PED_BE16_TO_CPU (mdb->total_blocks) + 1 - start - to_free; int ret; PED_ASSERT (!hfs_block); cache = hfs_cache_extents (fs, timer); if (!cache) return 0; /* Calculate the size of the copy buffer : * Takes BLOCK_MAX_BUFF HFS blocks, but if > BYTES_MAX_BUFF * takes the maximum number of HFS blocks so that the buffer * will remain smaller than or equal to BYTES_MAX_BUFF, with * a minimum of 1 HFS block */ bytes_buff = PED_BE32_TO_CPU (priv_data->mdb->block_size) * (PedSector) BLOCK_MAX_BUFF; if (bytes_buff > BYTES_MAX_BUFF) { hfs_block_count = BYTES_MAX_BUFF / PED_BE32_TO_CPU (priv_data->mdb->block_size); if (!hfs_block_count) hfs_block_count = 1; bytes_buff = (PedSector) hfs_block_count * PED_BE32_TO_CPU (priv_data->mdb->block_size); } else hfs_block_count = BLOCK_MAX_BUFF; /* If the cache code requests more space, give it to him */ if (bytes_buff < hfsc_cache_needed_buffer (cache)) bytes_buff = hfsc_cache_needed_buffer (cache); hfs_block = (uint8_t*) ped_malloc (bytes_buff); if (!hfs_block) goto error_cache; if (!hfs_read_bad_blocks (fs)) { ped_exception_throw ( PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL, _("Bad blocks list could not be loaded.")); goto error_alloc; } while (fblock < PED_BE16_TO_CPU (mdb->total_blocks)) { if (TST_BLOC_OCCUPATION(priv_data->alloc_map,fblock) && (!hfs_is_bad_block (fs, fblock))) { if (!(ret = hfs_move_extent_starting_at (fs, &fblock, &to_fblock, cache))) to_fblock = ++fblock; else if (ret == -1) { ped_exception_throw ( PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL, _("An error occurred during extent " "relocation.")); goto error_alloc; } } else { fblock++; } ped_timer_update(timer, (float)(to_fblock - start)/divisor); } free (hfs_block); hfs_block = NULL; hfs_block_count = 0; hfsc_delete_cache (cache); return 1; error_alloc: free (hfs_block); hfs_block = NULL; hfs_block_count = 0; error_cache: hfsc_delete_cache (cache); return 0; } #endif /* !DISCOVER_ONLY */