/*
libparted - a library for manipulating disk partitions
Copyright (C) 2004-2005, 2007, 2009-2014, 2019-2023 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 */