1 /*
   2     libparted - a library for manipulating disk partitions
   3     Copyright (C) 2004, 2005, 2007 Free Software Foundation, Inc.
   4 
   5     This program is free software; you can redistribute it and/or modify
   6     it under the terms of the GNU General Public License as published by
   7     the Free Software Foundation; either version 3 of the License, or
   8     (at your option) any later version.
   9 
  10     This program is distributed in the hope that it will be useful,
  11     but WITHOUT ANY WARRANTY; without even the implied warranty of
  12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13     GNU General Public License for more details.
  14 
  15     You should have received a copy of the GNU General Public License
  16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
  17 */
  18 
  19 #ifndef DISCOVER_ONLY
  20 
  21 #include <config.h>
  22 
  23 #include <parted/parted.h>
  24 #include <parted/endian.h>
  25 #include <parted/debug.h>
  26 #include <stdint.h>
  27 
  28 #if ENABLE_NLS
  29 #  include <libintl.h>
  30 #  define _(String) dgettext (PACKAGE, String)
  31 #else
  32 #  define _(String) (String)
  33 #endif /* ENABLE_NLS */
  34 
  35 #include "hfs.h"
  36 #include "reloc_plus.h"
  37 
  38 #include "journal.h"
  39 
  40 static int hfsj_vh_replayed = 0;
  41 static int is_le = 0;
  42 
  43 static uint32_t
  44 hfsj_calc_checksum(uint8_t *ptr, int len)
  45 {
  46         int      i;
  47         uint32_t cksum=0;
  48 
  49         for (i=0; i < len; i++, ptr++) {
  50                 cksum = (cksum << 8) ^ (cksum + *ptr);
  51         }
  52 
  53         return (~cksum);
  54 }
  55 
  56 int
  57 hfsj_update_jib(PedFileSystem* fs, uint32_t block)
  58 {
  59         HfsPPrivateFSData*      priv_data = (HfsPPrivateFSData*)
  60                                                     fs->type_specific;
  61 
  62         priv_data->vh->journal_info_block = PED_CPU_TO_BE32(block);
  63 
  64         if (!hfsplus_update_vh (fs))
  65                 return 0;
  66 
  67         priv_data->jib_start_block = block;
  68         return 1;
  69 }
  70 
  71 int
  72 hfsj_update_jl(PedFileSystem* fs, uint32_t block)
  73 {
  74         uint8_t                 buf[PED_SECTOR_SIZE_DEFAULT];
  75         PedSector               sector;
  76         uint64_t                offset;
  77         HfsPPrivateFSData*      priv_data = (HfsPPrivateFSData*)
  78                                                     fs->type_specific;
  79         HfsJJournalInfoBlock*   jib;
  80         int                     binsect;
  81 
  82         binsect = HFS_32_TO_CPU(priv_data->vh->block_size, is_le) / PED_SECTOR_SIZE_DEFAULT;
  83         sector = (PedSector) priv_data->jib_start_block * binsect;
  84         if (!ped_geometry_read(priv_data->plus_geom, buf, sector, 1))
  85                 return 0;
  86         jib = (HfsJJournalInfoBlock*) buf;
  87 
  88         offset = (uint64_t)block * PED_SECTOR_SIZE_DEFAULT * binsect;
  89         jib->offset = HFS_CPU_TO_64(offset, is_le);
  90 
  91         if (!ped_geometry_write(priv_data->plus_geom, buf, sector, 1)
  92             || !ped_geometry_sync(priv_data->plus_geom))
  93                 return 0;
  94 
  95         priv_data->jl_start_block = block;
  96         return 1;
  97 }
  98 
  99 /* Return the sector in the journal that is after the area read */
 100 /* or 0 on error */
 101 static PedSector
 102 hfsj_journal_read(PedGeometry* geom, HfsJJournalHeader* jh,
 103                   PedSector journ_sect, PedSector journ_length,
 104                   PedSector read_sect, unsigned int nb_sect,
 105                   void* buf)
 106 {
 107         int r;
 108 
 109         while (nb_sect--) {
 110                 r = ped_geometry_read(geom, buf, journ_sect + read_sect, 1);
 111                 if (!r) return 0;
 112 
 113                 buf = ((uint8_t*)buf) + PED_SECTOR_SIZE_DEFAULT;
 114                 read_sect++;
 115                 if (read_sect == journ_length)
 116                         read_sect = 1; /* skip journal header
 117                                           which is asserted to be 
 118                                           1 sector long */
 119         }
 120 
 121         return read_sect;
 122 }
 123 
 124 static int
 125 hfsj_replay_transaction(PedFileSystem* fs, HfsJJournalHeader* jh,
 126                         PedSector jsector, PedSector jlength)
 127 {
 128         PedSector               start, sector;
 129         HfsPPrivateFSData*      priv_data = (HfsPPrivateFSData*)
 130                                                 fs->type_specific;
 131         HfsJBlockListHeader*    blhdr;
 132         uint8_t*                block;
 133         unsigned int            blhdr_nbsect;
 134         int                     i, r;
 135         uint32_t                cksum, size;
 136 
 137         blhdr_nbsect = HFS_32_TO_CPU(jh->blhdr_size, is_le) / PED_SECTOR_SIZE_DEFAULT;
 138         blhdr = (HfsJBlockListHeader*)
 139                   ped_malloc (blhdr_nbsect * PED_SECTOR_SIZE_DEFAULT);
 140         if (!blhdr) return 0;
 141 
 142         start = HFS_64_TO_CPU(jh->start, is_le) / PED_SECTOR_SIZE_DEFAULT;
 143         do {
 144                 start = hfsj_journal_read(priv_data->plus_geom, jh, jsector,
 145                                           jlength, start, blhdr_nbsect, blhdr);
 146                 if (!start) goto err_replay;
 147                 
 148                 cksum = HFS_32_TO_CPU(blhdr->checksum, is_le);
 149                 blhdr->checksum = 0;
 150                 if (cksum!=hfsj_calc_checksum((uint8_t*)blhdr, sizeof(*blhdr))){
 151                         ped_exception_throw (
 152                                 PED_EXCEPTION_ERROR,
 153                                 PED_EXCEPTION_CANCEL,
 154                                 _("Bad block list header checksum."));
 155                         goto err_replay;
 156                 }
 157                 blhdr->checksum = HFS_CPU_TO_32(cksum, is_le);
 158                 
 159                 for (i=1; i < HFS_16_TO_CPU(blhdr->num_blocks, is_le); ++i) {
 160                         size = HFS_32_TO_CPU(blhdr->binfo[i].bsize, is_le);
 161                         sector = HFS_64_TO_CPU(blhdr->binfo[i].bnum, is_le);
 162                         if (!size) continue;
 163                         if (size % PED_SECTOR_SIZE_DEFAULT) {
 164                                 ped_exception_throw(
 165                                         PED_EXCEPTION_ERROR,
 166                                         PED_EXCEPTION_CANCEL,
 167                                         _("Invalid size of a transaction "
 168                                           "block while replaying the journal "
 169                                           "(%i bytes)."),
 170                                         size);
 171                                 goto err_replay;
 172                         }
 173                         block = (uint8_t*) ped_malloc(size);
 174                         if (!block) goto err_replay;
 175                         start = hfsj_journal_read(priv_data->plus_geom, jh,
 176                                                   jsector, jlength, start,
 177                                                   size / PED_SECTOR_SIZE_DEFAULT,
 178                                                   block);
 179                         if (!start) {
 180                                 ped_free (block);
 181                                 goto err_replay;
 182                         }
 183                         /* the sector stored in the journal seems to be
 184                            relative to the begin of the block device which
 185                            contains the hfs+ journaled volume */
 186                         if (sector != ~0LL)
 187                                 r = ped_geometry_write (fs->geom, block, sector,
 188                                                         size / PED_SECTOR_SIZE_DEFAULT);
 189                         else
 190                                 r = 1;
 191                         ped_free (block);
 192                         /* check if wrapper mdb or vh with no wrapper has
 193                            changed */
 194                         if (   (sector != ~0LL)
 195                             && (2 >= sector)
 196                             && (2 < sector + size / PED_SECTOR_SIZE_DEFAULT) )
 197                                 hfsj_vh_replayed = 1;
 198                         /* check if vh of embedded hfs+ has changed */
 199                         if (   (sector != ~0LL)
 200                             && (priv_data->plus_geom != fs->geom)
 201                             && (sector
 202                                 + fs->geom->start
 203                                 - priv_data->plus_geom->start <= 2)
 204                             && (sector
 205                                 + size / PED_SECTOR_SIZE_DEFAULT
 206                                 + fs->geom->start
 207                                 - priv_data->plus_geom->start > 2) )
 208                                 hfsj_vh_replayed = 1;
 209                         if (!r) goto err_replay;
 210                 }
 211         } while (blhdr->binfo[0].next);
 212 
 213         jh->start = HFS_CPU_TO_64(start * PED_SECTOR_SIZE_DEFAULT, is_le);
 214 
 215         ped_free (blhdr);
 216         return (ped_geometry_sync (fs->geom));
 217 
 218 err_replay:
 219         ped_free (blhdr);
 220         return 0;
 221 }
 222 
 223 /* 0 => Failure, don't continue to open ! */
 224 /* 1 => Success, the journal has been completly replayed, or don't need to */
 225 int
 226 hfsj_replay_journal(PedFileSystem* fs)
 227 {
 228         uint8_t                 buf[PED_SECTOR_SIZE_DEFAULT];
 229         PedSector               sector, length;
 230         HfsPPrivateFSData*      priv_data = (HfsPPrivateFSData*)
 231                                                     fs->type_specific;
 232         HfsJJournalInfoBlock*   jib;
 233         HfsJJournalHeader*      jh;
 234         int                     binsect;
 235         uint32_t                cksum;
 236 
 237         binsect = PED_BE32_TO_CPU(priv_data->vh->block_size) / PED_SECTOR_SIZE_DEFAULT;
 238         priv_data->jib_start_block =
 239                 PED_BE32_TO_CPU(priv_data->vh->journal_info_block);
 240         sector  = (PedSector) priv_data->jib_start_block * binsect;
 241         if (!ped_geometry_read(priv_data->plus_geom, buf, sector, 1))
 242                 return 0;
 243         jib = (HfsJJournalInfoBlock*) buf;
 244         
 245         if (    (jib->flags & PED_CPU_TO_BE32(1 << HFSJ_JOURN_IN_FS))
 246             && !(jib->flags & PED_CPU_TO_BE32(1 << HFSJ_JOURN_OTHER_DEV)) ) {
 247                 priv_data->jl_start_block = HFS_64_TO_CPU(jib->offset, is_le) 
 248                                             / ( PED_SECTOR_SIZE_DEFAULT * binsect );
 249         }
 250 
 251         if (jib->flags & PED_CPU_TO_BE32(1 << HFSJ_JOURN_NEED_INIT))
 252                 return 1;
 253 
 254         if (  !(jib->flags & PED_CPU_TO_BE32(1 << HFSJ_JOURN_IN_FS))
 255             || (jib->flags & PED_CPU_TO_BE32(1 << HFSJ_JOURN_OTHER_DEV)) ) {
 256                 ped_exception_throw (
 257                         PED_EXCEPTION_NO_FEATURE,
 258                         PED_EXCEPTION_CANCEL,
 259                         _("Journal stored outside of the volume are "
 260                           "not supported.  Try to desactivate the "
 261                           "journal and run Parted again."));
 262                 return 0;
 263         }
 264 
 265         if (   (PED_BE64_TO_CPU(jib->offset) % PED_SECTOR_SIZE_DEFAULT)
 266             || (PED_BE64_TO_CPU(jib->size)   % PED_SECTOR_SIZE_DEFAULT) ) {
 267                 ped_exception_throw (
 268                         PED_EXCEPTION_NO_FEATURE,
 269                         PED_EXCEPTION_CANCEL,
 270                         _("Journal offset or size is not multiple of "
 271                           "the sector size."));
 272                 return 0;
 273         }
 274 
 275         sector = PED_BE64_TO_CPU(jib->offset) / PED_SECTOR_SIZE_DEFAULT;
 276         length = PED_BE64_TO_CPU(jib->size)   / PED_SECTOR_SIZE_DEFAULT;
 277 
 278         jib = NULL;
 279         if (!ped_geometry_read(priv_data->plus_geom, buf, sector, 1))
 280                 return 0;
 281         jh = (HfsJJournalHeader*) buf;
 282 
 283         if (jh->endian == PED_LE32_TO_CPU(HFSJ_ENDIAN_MAGIC))
 284             is_le = 1;
 285 
 286         if (   (jh->magic  != HFS_32_TO_CPU(HFSJ_HEADER_MAGIC, is_le))
 287             || (jh->endian != HFS_32_TO_CPU(HFSJ_ENDIAN_MAGIC, is_le)) ) {
 288                 ped_exception_throw (
 289                         PED_EXCEPTION_ERROR,
 290                         PED_EXCEPTION_CANCEL,
 291                         _("Incorrect magic values in the journal header."));
 292                 return 0;
 293         }
 294 
 295         if ( (HFS_64_TO_CPU(jh->size, is_le)%PED_SECTOR_SIZE_DEFAULT)
 296           || (HFS_64_TO_CPU(jh->size, is_le)/PED_SECTOR_SIZE_DEFAULT
 297                   != (uint64_t)length) ) {
 298                 ped_exception_throw (
 299                         PED_EXCEPTION_ERROR,
 300                         PED_EXCEPTION_CANCEL,
 301                         _("Journal size mismatch between journal info block "
 302                           "and journal header."));
 303                 return 0;
 304         }
 305 
 306         if (   (HFS_64_TO_CPU(jh->start, is_le) % PED_SECTOR_SIZE_DEFAULT)
 307             || (HFS_64_TO_CPU(jh->end, is_le)   % PED_SECTOR_SIZE_DEFAULT)
 308             || (HFS_32_TO_CPU(jh->blhdr_size, is_le) % PED_SECTOR_SIZE_DEFAULT)
 309             || (HFS_32_TO_CPU(jh->jhdr_size, is_le)  % PED_SECTOR_SIZE_DEFAULT) ) {
 310                 ped_exception_throw (
 311                         PED_EXCEPTION_ERROR,
 312                         PED_EXCEPTION_CANCEL,
 313                         _("Some header fields are not multiple of the sector "
 314                           "size."));
 315                 return 0;
 316         }
 317 
 318         if (HFS_32_TO_CPU(jh->jhdr_size, is_le) != PED_SECTOR_SIZE_DEFAULT) {
 319                 ped_exception_throw (
 320                         PED_EXCEPTION_ERROR,
 321                         PED_EXCEPTION_CANCEL,
 322                         _("The sector size stored in the journal is not 512 "
 323                           "bytes.  Parted only supports 512 bytes length "
 324                           "sectors."));
 325                 return 0;       
 326         }
 327 
 328         cksum = HFS_32_TO_CPU(jh->checksum, is_le);
 329         jh->checksum = 0;
 330         if (cksum != hfsj_calc_checksum((uint8_t*)jh, sizeof(*jh))) {
 331                 ped_exception_throw (
 332                         PED_EXCEPTION_ERROR,
 333                         PED_EXCEPTION_CANCEL,
 334                         _("Bad journal checksum."));
 335                 return 0;
 336         }
 337         jh->checksum = HFS_CPU_TO_32(cksum, is_le);
 338 
 339         /* The 2 following test are in the XNU Darwin source code */
 340         /* so I assume they're needed */
 341         if (jh->start == jh->size)
 342                 jh->start = HFS_CPU_TO_64(PED_SECTOR_SIZE_DEFAULT, is_le);
 343         if (jh->end   == jh->size)
 344                 jh->start = HFS_CPU_TO_64(PED_SECTOR_SIZE_DEFAULT, is_le);
 345 
 346         if (jh->start == jh->end)
 347                 return 1;
 348 
 349         if (ped_exception_throw (
 350                 PED_EXCEPTION_WARNING,
 351                 PED_EXCEPTION_FIX | PED_EXCEPTION_CANCEL,
 352                 _("The journal is not empty.  Parted must replay the "
 353                   "transactions before opening the file system.  This will "
 354                   "modify the file system."))
 355                         != PED_EXCEPTION_FIX)
 356                 return 0;
 357 
 358         while (jh->start != jh->end) {
 359                 /* Replay one complete transaction */
 360                 if (!hfsj_replay_transaction(fs, jh, sector, length))
 361                         return 0;
 362 
 363                 /* Recalculate cksum of the journal header */
 364                 jh->checksum = 0; /* need to be 0 while calculating the cksum */
 365                 cksum = hfsj_calc_checksum((uint8_t*)jh, sizeof(*jh));
 366                 jh->checksum = HFS_CPU_TO_32(cksum, is_le);
 367 
 368                 /* Update the Journal Header */
 369                 if (!ped_geometry_write(priv_data->plus_geom, buf, sector, 1)
 370                     || !ped_geometry_sync(priv_data->plus_geom))
 371                         return 0;
 372         }
 373 
 374         if (hfsj_vh_replayed) {
 375                 /* probe could have reported incorrect info ! */
 376                 /* is there a way to ask parted to quit ? */
 377                 ped_exception_throw(
 378                         PED_EXCEPTION_WARNING,
 379                         PED_EXCEPTION_OK,
 380                         _("The volume header or the master directory block has "
 381                           "changed while replaying the journal.  You should "
 382                           "restart Parted."));
 383                 return 0;
 384         }
 385 
 386         return 1;
 387 }
 388 
 389 #endif /* DISCOVER_ONLY */