/* 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 "reloc_plus.h" #include "journal.h" static int hfsj_vh_replayed = 0; static int is_le = 0; static uint32_t hfsj_calc_checksum(uint8_t *ptr, int len) { int i; uint32_t cksum=0; for (i=0; i < len; i++, ptr++) { cksum = (cksum << 8) ^ (cksum + *ptr); } return (~cksum); } int hfsj_update_jib(PedFileSystem* fs, uint32_t block) { HfsPPrivateFSData* priv_data = (HfsPPrivateFSData*) fs->type_specific; priv_data->vh->journal_info_block = PED_CPU_TO_BE32(block); if (!hfsplus_update_vh (fs)) return 0; priv_data->jib_start_block = block; return 1; } int hfsj_update_jl(PedFileSystem* fs, uint32_t block) { uint8_t buf[PED_SECTOR_SIZE_DEFAULT]; PedSector sector; uint64_t offset; HfsPPrivateFSData* priv_data = (HfsPPrivateFSData*) fs->type_specific; HfsJJournalInfoBlock* jib; int binsect; binsect = HFS_32_TO_CPU(priv_data->vh->block_size, is_le) / PED_SECTOR_SIZE_DEFAULT; sector = (PedSector) priv_data->jib_start_block * binsect; if (!ped_geometry_read(priv_data->plus_geom, buf, sector, 1)) return 0; jib = (HfsJJournalInfoBlock*) buf; offset = (uint64_t)block * PED_SECTOR_SIZE_DEFAULT * binsect; jib->offset = HFS_CPU_TO_64(offset, is_le); if (!ped_geometry_write(priv_data->plus_geom, buf, sector, 1) || !ped_geometry_sync(priv_data->plus_geom)) return 0; priv_data->jl_start_block = block; return 1; } /* Return the sector in the journal that is after the area read */ /* or 0 on error */ static PedSector hfsj_journal_read(PedGeometry* geom, HfsJJournalHeader* jh, PedSector journ_sect, PedSector journ_length, PedSector read_sect, unsigned int nb_sect, void* buf) { int r; while (nb_sect--) { r = ped_geometry_read(geom, buf, journ_sect + read_sect, 1); if (!r) return 0; buf = ((uint8_t*)buf) + PED_SECTOR_SIZE_DEFAULT; read_sect++; if (read_sect == journ_length) read_sect = 1; /* skip journal header which is asserted to be 1 sector long */ } return read_sect; } static int hfsj_replay_transaction(PedFileSystem* fs, HfsJJournalHeader* jh, PedSector jsector, PedSector jlength) { PedSector start, sector; HfsPPrivateFSData* priv_data = (HfsPPrivateFSData*) fs->type_specific; HfsJBlockListHeader* blhdr; uint8_t* block; unsigned int blhdr_nbsect; int i, r; uint32_t cksum, size; blhdr_nbsect = HFS_32_TO_CPU(jh->blhdr_size, is_le) / PED_SECTOR_SIZE_DEFAULT; blhdr = (HfsJBlockListHeader*) ped_malloc (blhdr_nbsect * PED_SECTOR_SIZE_DEFAULT); if (!blhdr) return 0; start = HFS_64_TO_CPU(jh->start, is_le) / PED_SECTOR_SIZE_DEFAULT; do { start = hfsj_journal_read(priv_data->plus_geom, jh, jsector, jlength, start, blhdr_nbsect, blhdr); if (!start) goto err_replay; cksum = HFS_32_TO_CPU(blhdr->checksum, is_le); blhdr->checksum = 0; if (cksum!=hfsj_calc_checksum((uint8_t*)blhdr, sizeof(*blhdr))){ ped_exception_throw ( PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL, _("Bad block list header checksum.")); goto err_replay; } blhdr->checksum = HFS_CPU_TO_32(cksum, is_le); for (i=1; i < HFS_16_TO_CPU(blhdr->num_blocks, is_le); ++i) { size = HFS_32_TO_CPU(blhdr->binfo[i].bsize, is_le); sector = HFS_64_TO_CPU(blhdr->binfo[i].bnum, is_le); if (!size) continue; if (size % PED_SECTOR_SIZE_DEFAULT) { ped_exception_throw( PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL, _("Invalid size of a transaction " "block while replaying the journal " "(%i bytes)."), size); goto err_replay; } block = (uint8_t*) ped_malloc(size); if (!block) goto err_replay; start = hfsj_journal_read(priv_data->plus_geom, jh, jsector, jlength, start, size / PED_SECTOR_SIZE_DEFAULT, block); if (!start) { free (block); goto err_replay; } /* the sector stored in the journal seems to be relative to the begin of the block device which contains the hfs+ journaled volume */ if (sector != ~0LL) r = ped_geometry_write (fs->geom, block, sector, size / PED_SECTOR_SIZE_DEFAULT); else r = 1; free (block); /* check if wrapper mdb or vh with no wrapper has changed */ if ( (sector != ~0LL) && (2 >= sector) && (2 < sector + size / PED_SECTOR_SIZE_DEFAULT) ) hfsj_vh_replayed = 1; /* check if vh of embedded hfs+ has changed */ if ( (sector != ~0LL) && (priv_data->plus_geom != fs->geom) && (sector + fs->geom->start - priv_data->plus_geom->start <= 2) && (sector + size / PED_SECTOR_SIZE_DEFAULT + fs->geom->start - priv_data->plus_geom->start > 2) ) hfsj_vh_replayed = 1; if (!r) goto err_replay; } } while (blhdr->binfo[0].next); jh->start = HFS_CPU_TO_64(start * PED_SECTOR_SIZE_DEFAULT, is_le); free (blhdr); return (ped_geometry_sync (fs->geom)); err_replay: free (blhdr); return 0; } /* 0 => Failure, don't continue to open ! */ /* 1 => Success, the journal has been completly replayed, or don't need to */ int hfsj_replay_journal(PedFileSystem* fs) { uint8_t buf[PED_SECTOR_SIZE_DEFAULT]; PedSector sector, length; HfsPPrivateFSData* priv_data = (HfsPPrivateFSData*) fs->type_specific; HfsJJournalInfoBlock* jib; HfsJJournalHeader* jh; int binsect; uint32_t cksum; binsect = PED_BE32_TO_CPU(priv_data->vh->block_size) / PED_SECTOR_SIZE_DEFAULT; priv_data->jib_start_block = PED_BE32_TO_CPU(priv_data->vh->journal_info_block); sector = (PedSector) priv_data->jib_start_block * binsect; if (!ped_geometry_read(priv_data->plus_geom, buf, sector, 1)) return 0; jib = (HfsJJournalInfoBlock*) buf; if ( (jib->flags & PED_CPU_TO_BE32(1 << HFSJ_JOURN_IN_FS)) && !(jib->flags & PED_CPU_TO_BE32(1 << HFSJ_JOURN_OTHER_DEV)) ) { priv_data->jl_start_block = HFS_64_TO_CPU(jib->offset, is_le) / ( PED_SECTOR_SIZE_DEFAULT * binsect ); } if (jib->flags & PED_CPU_TO_BE32(1 << HFSJ_JOURN_NEED_INIT)) return 1; if ( !(jib->flags & PED_CPU_TO_BE32(1 << HFSJ_JOURN_IN_FS)) || (jib->flags & PED_CPU_TO_BE32(1 << HFSJ_JOURN_OTHER_DEV)) ) { ped_exception_throw ( PED_EXCEPTION_NO_FEATURE, PED_EXCEPTION_CANCEL, _("Journal stored outside of the volume are " "not supported. Try to deactivate the " "journal and run Parted again.")); return 0; } if ( (PED_BE64_TO_CPU(jib->offset) % PED_SECTOR_SIZE_DEFAULT) || (PED_BE64_TO_CPU(jib->size) % PED_SECTOR_SIZE_DEFAULT) ) { ped_exception_throw ( PED_EXCEPTION_NO_FEATURE, PED_EXCEPTION_CANCEL, _("Journal offset or size is not multiple of " "the sector size.")); return 0; } sector = PED_BE64_TO_CPU(jib->offset) / PED_SECTOR_SIZE_DEFAULT; length = PED_BE64_TO_CPU(jib->size) / PED_SECTOR_SIZE_DEFAULT; jib = NULL; if (!ped_geometry_read(priv_data->plus_geom, buf, sector, 1)) return 0; jh = (HfsJJournalHeader*) buf; if (jh->endian == PED_LE32_TO_CPU(HFSJ_ENDIAN_MAGIC)) is_le = 1; if ( (jh->magic != HFS_32_TO_CPU(HFSJ_HEADER_MAGIC, is_le)) || (jh->endian != HFS_32_TO_CPU(HFSJ_ENDIAN_MAGIC, is_le)) ) { ped_exception_throw ( PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL, _("Incorrect magic values in the journal header.")); return 0; } if ( (HFS_64_TO_CPU(jh->size, is_le)%PED_SECTOR_SIZE_DEFAULT) || (HFS_64_TO_CPU(jh->size, is_le)/PED_SECTOR_SIZE_DEFAULT != (uint64_t)length) ) { ped_exception_throw ( PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL, _("Journal size mismatch between journal info block " "and journal header.")); return 0; } if ( (HFS_64_TO_CPU(jh->start, is_le) % PED_SECTOR_SIZE_DEFAULT) || (HFS_64_TO_CPU(jh->end, is_le) % PED_SECTOR_SIZE_DEFAULT) || (HFS_32_TO_CPU(jh->blhdr_size, is_le) % PED_SECTOR_SIZE_DEFAULT) || (HFS_32_TO_CPU(jh->jhdr_size, is_le) % PED_SECTOR_SIZE_DEFAULT) ) { ped_exception_throw ( PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL, _("Some header fields are not multiple of the sector " "size.")); return 0; } if (HFS_32_TO_CPU(jh->jhdr_size, is_le) != PED_SECTOR_SIZE_DEFAULT) { ped_exception_throw ( PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL, _("The sector size stored in the journal is not 512 " "bytes. Parted only supports 512 bytes length " "sectors.")); return 0; } cksum = HFS_32_TO_CPU(jh->checksum, is_le); jh->checksum = 0; if (cksum != hfsj_calc_checksum((uint8_t*)jh, sizeof(*jh))) { ped_exception_throw ( PED_EXCEPTION_ERROR, PED_EXCEPTION_CANCEL, _("Bad journal checksum.")); return 0; } jh->checksum = HFS_CPU_TO_32(cksum, is_le); /* https://github.com/apple-opensource/hfs/blob/master/core/hfs_journal.c#L1167 * indicates that this is: * wrap the start ptr if it points to the very end of the journal */ if (jh->start == jh->size) jh->start = HFS_CPU_TO_64(PED_SECTOR_SIZE_DEFAULT, is_le); if (jh->end == jh->size) jh->end = HFS_CPU_TO_64(PED_SECTOR_SIZE_DEFAULT, is_le); if (jh->start == jh->end) return 1; if (ped_exception_throw ( PED_EXCEPTION_WARNING, PED_EXCEPTION_FIX | PED_EXCEPTION_CANCEL, _("The journal is not empty. Parted must replay the " "transactions before opening the file system. This will " "modify the file system.")) != PED_EXCEPTION_FIX) return 0; while (jh->start != jh->end) { /* Replay one complete transaction */ if (!hfsj_replay_transaction(fs, jh, sector, length)) return 0; /* Recalculate cksum of the journal header */ jh->checksum = 0; /* need to be 0 while calculating the cksum */ cksum = hfsj_calc_checksum((uint8_t*)jh, sizeof(*jh)); jh->checksum = HFS_CPU_TO_32(cksum, is_le); /* Update the Journal Header */ if (!ped_geometry_write(priv_data->plus_geom, buf, sector, 1) || !ped_geometry_sync(priv_data->plus_geom)) return 0; } if (hfsj_vh_replayed) { /* probe could have reported incorrect info ! */ /* is there a way to ask parted to quit ? */ ped_exception_throw( PED_EXCEPTION_WARNING, PED_EXCEPTION_OK, _("The volume header or the master directory block has " "changed while replaying the journal. You should " "restart Parted.")); return 0; } return 1; } #endif /* DISCOVER_ONLY */