//
// partition_map.c - partition map routines
//
// Written by Eryk Vershen
//

/*
* Copyright 1996,1997,1998 by Apple Computer, Inc.
*              All Rights Reserved
*
* Permission to use, copy, modify, and distribute this software and
* its documentation for any purpose and without fee is hereby granted,
* provided that the above copyright notice appears in all copies and
* that both the copyright notice and this permission notice appear in
* supporting documentation.
*
* APPLE COMPUTER DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE.
*
* IN NO EVENT SHALL APPLE COMPUTER BE LIABLE FOR ANY SPECIAL, INDIRECT, OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN ACTION OF CONTRACT,
* NEGLIGENCE, OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
* WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

// for *printf()
#include <stdio.h>

// for malloc(), calloc() & free()
#ifndef __linux__
#include <stdlib.h>
#else
#include <malloc.h>
#endif

// for strncpy() & strcmp()
#include <string.h>
// for O_RDONLY & O_RDWR
#include <fcntl.h>
// for errno
#include <errno.h>

#include <inttypes.h>

#include "partition_map.h"
#include "pathname.h"
#include "hfs_misc.h"
#include "deblock_media.h"
#include "io.h"
#include "convert.h"
#include "util.h"
#include "errors.h"


//
// Defines
//
#define APPLE_HFS_FLAGS_VALUE   0x4000037f
#define get_align_long(x)       (*(x))
#define put_align_long(y, x)    ((*(x)) = (y))
// #define TEST_COMPUTE


//
// Types
//


//
// Global Constants
//
const char * kFreeType  = "Apple_Free";
const char * kMapType   = "Apple_partition_map";
const char * kUnixType  = "Apple_UNIX_SVR2";
const char * kHFSType   = "Apple_HFS";
const char * kPatchType = "Apple_Patches";

const char * kFreeName  = "Extra";

enum add_action {
   kReplace = 0,
   kAdd = 1,
   kSplit = 2
};

//
// Global Variables
//
extern int cflag;


//
// Forward declarations
//
int add_data_to_map(struct dpme *, long, partition_map_header *);
int coerce_block0(partition_map_header *map);
int contains_driver(partition_map *entry);
void combine_entry(partition_map *entry);
long compute_device_size(partition_map_header *map, partition_map_header *oldmap);
DPME* create_data(const char *name, const char *dptype, uint32_t base, uint32_t length);
void delete_entry(partition_map *entry);
char *get_HFS_name(partition_map *entry, int *kind);
void insert_in_base_order(partition_map *entry);
void insert_in_disk_order(partition_map *entry);
int read_block(partition_map_header *map, uint32_t num, char *buf);
int read_partition_map(partition_map_header *map);
void remove_driver(partition_map *entry);
void remove_from_disk_order(partition_map *entry);
void renumber_disk_addresses(partition_map_header *map);
void sync_device_size(partition_map_header *map);
int write_block(partition_map_header *map, uint32_t num, char *buf);


//
// Routines
//
partition_map_header *
open_partition_map(char *name, int *valid_file, int ask_logical_size)
{
   MEDIA m;
   partition_map_header * map;
   int writable;
   long size;

   m = open_pathname_as_media(name, (rflag)?O_RDONLY:O_RDWR);
   if (m == 0) {
       m = open_pathname_as_media(name, O_RDONLY);
       if (m == 0) {
           error(errno, "can't open file '%s'", name);
           *valid_file = 0;
           return NULL;
       } else {
           writable = 0;
       }
   } else {
       writable = 1;
   }
   *valid_file = 1;

   map = (partition_map_header *) malloc(sizeof(partition_map_header));
   if (map == NULL) {
       error(errno, "can't allocate memory for open partition map");
       close_media(m);
       return NULL;
   }
   map->name = name;
   map->writable = (rflag)?0:writable;
   map->changed = 0;
   map->written = 0;
   map->disk_order = NULL;
   map->base_order = NULL;

   map->physical_block = media_granularity(m); /* preflight */
   m = open_deblock_media(PBLOCK_SIZE, m);
   map->m = m;
   map->misc = (Block0 *) malloc(PBLOCK_SIZE);
   if (map->misc == NULL) {
       error(errno, "can't allocate memory for block zero buffer");
       close_media(map->m);
       free(map);
       return NULL;
   } else if (read_media(map->m, (long long) 0, PBLOCK_SIZE, (char *)map->misc) == 0
           || convert_block0(map->misc, 1)
           || coerce_block0(map)) {
       // if I can't read block 0 I might as well give up
       error(-1, "Can't read block 0 from '%s'", name);
       close_partition_map(map);
       return NULL;
   }
   map->physical_block = map->misc->sbBlkSize;
   //printf("physical block size is %d\n", map->physical_block);

   if (ask_logical_size && interactive) {
       size = PBLOCK_SIZE;
       printf("A logical block is %ld bytes: ", size);
       flush_to_newline(0);
       get_number_argument("what should be the logical block size? ",
               &size, size);
       size = (size / PBLOCK_SIZE) * PBLOCK_SIZE;
       if (size < PBLOCK_SIZE) {
           size = PBLOCK_SIZE;
       }
       map->logical_block = size;
   } else {
       map->logical_block = PBLOCK_SIZE;
   }
   if (map->logical_block > MAXIOSIZE) {
       map->logical_block = MAXIOSIZE;
   }
   if (map->logical_block > map->physical_block) {
       map->physical_block = map->logical_block;
   }
   map->blocks_in_map = 0;
   map->maximum_in_map = -1;
   map->media_size = compute_device_size(map, map);
   sync_device_size(map);

   if (read_partition_map(map) < 0) {
       // some sort of failure reading the map
   } else {
       // got it!
       ;
       return map;
   }
   close_partition_map(map);
   return NULL;
}


void
close_partition_map(partition_map_header *map)
{
   partition_map * entry;
   partition_map * next;

   if (map == NULL) {
       return;
   }

   free(map->misc);

   for (entry = map->disk_order; entry != NULL; entry = next) {
       next = entry->next_on_disk;
       free(entry->data);
       free(entry->HFS_name);
       free(entry);
   }
   close_media(map->m);
   free(map);
}


int
read_partition_map(partition_map_header *map)
{
   DPME *data;
   uint32_t limit;
   uint32_t ix;
   int old_logical;
   double d;

//printf("called read_partition_map\n");
//printf("logical = %d, physical = %d\n", map->logical_block, map->physical_block);
   data = (DPME *) malloc(PBLOCK_SIZE);
   if (data == NULL) {
       error(errno, "can't allocate memory for disk buffers");
       return -1;
   }

   if (read_block(map, 1, (char *)data) == 0) {
       error(-1, "Can't read block 1 from '%s'", map->name);
       free(data);
       return -1;
   } else if (convert_dpme(data, 1)
           || data->dpme_signature != DPME_SIGNATURE) {
       old_logical = map->logical_block;
       map->logical_block = 512;
       while (map->logical_block <= map->physical_block) {
           if (read_block(map, 1, (char *)data) == 0) {
               error(-1, "Can't read block 1 from '%s'", map->name);
               free(data);
               return -1;
           } else if (convert_dpme(data, 1) == 0
                   && data->dpme_signature == DPME_SIGNATURE) {
               d = map->media_size;
               map->media_size =  (d * old_logical) / map->logical_block;
               break;
           }
           map->logical_block *= 2;
       }
       if (map->logical_block > map->physical_block) {
           error(-1, "No valid block 1 on '%s'", map->name);
           free(data);
           return -1;
       }
   }
//printf("logical = %d, physical = %d\n", map->logical_block, map->physical_block);

   limit = data->dpme_map_entries;
   ix = 1;
   while (1) {
       if (add_data_to_map(data, ix, map) == 0) {
           free(data);
           return -1;
       }

       if (ix >= limit) {
           break;
       } else {
           ix++;
       }

       data = (DPME *) malloc(PBLOCK_SIZE);
       if (data == NULL) {
           error(errno, "can't allocate memory for disk buffers");
           return -1;
       }

       if (read_block(map, ix, (char *)data) == 0) {
           error(-1, "Can't read block %u from '%s'", ix, map->name);
           free(data);
           return -1;
       } else if (convert_dpme(data, 1)
               || (data->dpme_signature != DPME_SIGNATURE && dflag == 0)
               || (data->dpme_map_entries != limit && dflag == 0)) {
           error(-1, "Bad data in block %u from '%s'", ix, map->name);
           free(data);
           return -1;
       }
   }
   return 0;
}


void
write_partition_map(partition_map_header *map)
{
   char *block;
   partition_map * entry;
   int i = 0;
   int result = 0;

   if (map->misc != NULL) {
       convert_block0(map->misc, 0);
       result = write_block(map, 0, (char *)map->misc);
       convert_block0(map->misc, 1);
   } else {
       block = (char *) calloc(1, PBLOCK_SIZE);
       if (block != NULL) {
           result = write_block(map, 0, block);
           free(block);
       }
   }
   if (result == 0) {
       error(errno, "Unable to write block zero");
   }
   for (entry = map->disk_order; entry != NULL; entry = entry->next_on_disk) {
       convert_dpme(entry->data, 0);
       result = write_block(map, entry->disk_address, (char *)entry->data);
       convert_dpme(entry->data, 1);
       i = entry->disk_address;
       if (result == 0) {
           error(errno, "Unable to write block %d", i);
       }
   }

#ifdef __linux__
       // zap the block after the map (if possible) to get around a bug.
   if (map->maximum_in_map > 0 &&  i < map->maximum_in_map) {
       i += 1;
       block = (char *) malloc(PBLOCK_SIZE);
       if (block != NULL) {
           if (read_block(map, i, block)) {
               block[0] = 0;
               write_block(map, i, block);
           }
           free(block);
       }
   }
#endif

   if (interactive)
       printf("The partition table has been altered!\n\n");

   os_reload_media(map->m);
}


int
add_data_to_map(struct dpme *data, long ix, partition_map_header *map)
{
   partition_map *entry;

//printf("add data %d to map\n", ix);
   entry = (partition_map *) malloc(sizeof(partition_map));
   if (entry == NULL) {
       error(errno, "can't allocate memory for map entries");
       return 0;
   }
   entry->next_on_disk = NULL;
   entry->prev_on_disk = NULL;
   entry->next_by_base = NULL;
   entry->prev_by_base = NULL;
   entry->disk_address = ix;
   entry->the_map = map;
   entry->data = data;
   entry->contains_driver = contains_driver(entry);
   entry->HFS_name = get_HFS_name(entry, &entry->HFS_kind);

   insert_in_disk_order(entry);
   insert_in_base_order(entry);

   map->blocks_in_map++;
   if (map->maximum_in_map < 0) {
       if (istrncmp(data->dpme_type, kMapType, DPISTRLEN) == 0) {
           map->maximum_in_map = data->dpme_pblocks;
       }
   }

   return 1;
}


partition_map_header *
init_partition_map(char *name, partition_map_header* oldmap)
{
   partition_map_header *map;

   if (oldmap != NULL) {
       printf("map already exists\n");
       if (get_okay("do you want to reinit? [n/y]: ", 0) != 1) {
           return oldmap;
       }
   }

   map = create_partition_map(name, oldmap);
   if (map == NULL) {
       return oldmap;
   }
   close_partition_map(oldmap);

   add_partition_to_map("Apple", kMapType,
           1, (map->media_size <= 128? 2: 63), map);
   return map;
}


partition_map_header *
create_partition_map(char *name, partition_map_header *oldmap)
{
   MEDIA m;
   partition_map_header * map;
   DPME *data;
   uint32_t default_number;
   uint32_t number;
   long size;
   uint32_t multiple;

   m = open_pathname_as_media(name, (rflag)?O_RDONLY:O_RDWR);
   if (m == 0) {
       error(errno, "can't open file '%s' for %sing", name,
               (rflag)?"read":"writ");
       return NULL;
   }

   map = (partition_map_header *) malloc(sizeof(partition_map_header));
   if (map == NULL) {
       error(errno, "can't allocate memory for open partition map");
       close_media(m);
       return NULL;
   }
   map->name = name;
   map->writable = (rflag)?0:1;
   map->changed = 1;
   map->disk_order = NULL;
   map->base_order = NULL;

   if (oldmap != NULL) {
       size = oldmap->physical_block;
   } else {
       size = media_granularity(m);
   }
   m = open_deblock_media(PBLOCK_SIZE, m);
   map->m = m;
   if (interactive) {
       printf("A physical block is %ld bytes: ", size);
       flush_to_newline(0);
       get_number_argument("what should be the physical block size? ",
               &size, size);
       size = (size / PBLOCK_SIZE) * PBLOCK_SIZE;
       if (size < PBLOCK_SIZE) {
           size = PBLOCK_SIZE;
       }
   }
   if (map->physical_block > MAXIOSIZE) {
       map->physical_block = MAXIOSIZE;
   }
   map->physical_block = size;
   // printf("block size is %d\n", map->physical_block);

   if (oldmap != NULL) {
       size = oldmap->logical_block;
   } else {
       size = PBLOCK_SIZE;
   }
   if (interactive) {
       printf("A logical block is %ld bytes: ", size);
       flush_to_newline(0);
       get_number_argument("what should be the logical block size? ",
               &size, size);
       size = (size / PBLOCK_SIZE) * PBLOCK_SIZE;
       if (size < PBLOCK_SIZE) {
           size = PBLOCK_SIZE;
       }
   }
#if 0
   if (size > map->physical_block) {
       size = map->physical_block;
   }
#endif
   map->logical_block = size;

   map->blocks_in_map = 0;
   map->maximum_in_map = -1;

   number = compute_device_size(map, oldmap);
   if (interactive) {
       printf("size of 'device' is %"PRIu32" blocks (%d byte blocks): ",
               number, map->logical_block);
       default_number = number;
       flush_to_newline(0);
       do {
           long long_number = number;
           if (get_number_argument("what should be the size? ",
                   &long_number, default_number) == 0) {
               printf("Not a number\n");
               flush_to_newline(1);
               number = 0;
           } else {
               number = long_number;
               multiple = get_multiplier(map->logical_block);
               if (multiple == 0) {
                   printf("Bad multiplier\n");
                   number = 0;
               } else if (multiple != 1) {
                   if (0xFFFFFFFF/multiple < number) {
                       printf("Number too large\n");
                       number = 0;
                   } else {
                       number *= multiple;
                   }
               }
           }
           default_number = kDefault;
       } while (number == 0);

       if (number < 4) {
           number = 4;
       }
       printf("new size of 'device' is %"PRIu32" blocks (%d byte blocks)\n",
               number, map->logical_block);
   }
   map->media_size = number;

   map->misc = (Block0 *) calloc(1, PBLOCK_SIZE);
   if (map->misc == NULL) {
       error(errno, "can't allocate memory for block zero buffer");
   } else {
       // got it!
       coerce_block0(map);
       sync_device_size(map);

       data = (DPME *) calloc(1, PBLOCK_SIZE);
       if (data == NULL) {
           error(errno, "can't allocate memory for disk buffers");
       } else {
           // set data into entry
           data->dpme_signature = DPME_SIGNATURE;
           data->dpme_map_entries = 1;
           data->dpme_pblock_start = 1;
           data->dpme_pblocks = map->media_size - 1;
           strncpy(data->dpme_name, kFreeName, DPISTRLEN);
           strncpy(data->dpme_type, kFreeType, DPISTRLEN);
           data->dpme_lblock_start = 0;
           data->dpme_lblocks = data->dpme_pblocks;
           dpme_writable_set(data, 1);
           dpme_readable_set(data, 1);
           dpme_bootable_set(data, 0);
           dpme_in_use_set(data, 0);
           dpme_allocated_set(data, 0);
           dpme_valid_set(data, 1);

           if (add_data_to_map(data, 1, map) == 0) {
               free(data);
           } else {
               return map;
           }
       }
   }
   close_partition_map(map);
   return NULL;
}


int
coerce_block0(partition_map_header *map)
{
   Block0 *p;

   p = map->misc;
   if (p == NULL) {
       return 1;
   }
   if (p->sbSig != BLOCK0_SIGNATURE) {
       p->sbSig = BLOCK0_SIGNATURE;
       if (map->physical_block == 1) {
           p->sbBlkSize = PBLOCK_SIZE;
       } else {
           p->sbBlkSize = map->physical_block;
       }
       p->sbBlkCount = 0;
       p->sbDevType = 0;
       p->sbDevId = 0;
       p->sbData = 0;
       p->sbDrvrCount = 0;
   }
   return 0;   // we do this simply to make it easier to call this function
}


int
add_partition_to_map(const char *name, const char *dptype, uint32_t base, uint32_t length,
       partition_map_header *map)
{
   partition_map * cur;
   DPME *data;
   enum add_action act;
   int limit;
   uint32_t adjusted_base = 0;
   uint32_t adjusted_length = 0;
   uint32_t new_base = 0;
   uint32_t new_length = 0;

       // find a block that starts includes base and length
   cur = map->base_order;
   while (cur != NULL) {
       if (cur->data->dpme_pblock_start <= base
               && (base + length) <=
                   (cur->data->dpme_pblock_start + cur->data->dpme_pblocks)) {
           break;
       } else {
         // check if request is past end of existing partitions, but on disk
         if ((cur->next_by_base == NULL) &&
             (base + length <= map->media_size)) {
           // Expand final free partition
           if ((istrncmp(cur->data->dpme_type, kFreeType, DPISTRLEN) == 0) &&
               base >= cur->data->dpme_pblock_start) {
             cur->data->dpme_pblocks =
               map->media_size - cur->data->dpme_pblock_start;
             break;
           }
           // create an extra free partition
           if (base >= cur->data->dpme_pblock_start + cur->data->dpme_pblocks) {
             if (map->maximum_in_map < 0) {
               limit = map->media_size;
             } else {
               limit = map->maximum_in_map;
             }
             if (map->blocks_in_map + 1 > limit) {
               printf("the map is not big enough\n");
               return 0;
             }
             data = create_data(kFreeName, kFreeType,
                 cur->data->dpme_pblock_start + cur->data->dpme_pblocks,
                 map->media_size - (cur->data->dpme_pblock_start + cur->data->dpme_pblocks));
             if (data != NULL) {
               if (add_data_to_map(data, cur->disk_address, map) == 0) {
                 free(data);
               }
             }
           }
         }
         cur = cur->next_by_base;
       }
   }
       // if it is not Extra then punt
   if (cur == NULL
           || istrncmp(cur->data->dpme_type, kFreeType, DPISTRLEN) != 0) {
       printf("requested base and length is not "
               "within an existing free partition\n");
       return 0;
   }
       // figure out what to do and sizes
   data = cur->data;
   if (data->dpme_pblock_start == base) {
       // replace or add
       if (data->dpme_pblocks == length) {
           act = kReplace;
       } else {
           act = kAdd;
           adjusted_base = base + length;
           adjusted_length = data->dpme_pblocks - length;
       }
   } else {
       // split or add
       if (data->dpme_pblock_start + data->dpme_pblocks == base + length) {
           act = kAdd;
           adjusted_base = data->dpme_pblock_start;
           adjusted_length = base - adjusted_base;
       } else {
           act = kSplit;
           new_base = data->dpme_pblock_start;
           new_length = base - new_base;
           adjusted_base = base + length;
           adjusted_length = data->dpme_pblocks - (length + new_length);
       }
   }
       // if the map will overflow then punt
   if (map->maximum_in_map < 0) {
       limit = map->media_size;
   } else {
       limit = map->maximum_in_map;
   }
   if (map->blocks_in_map + (int)act > limit) {
       printf("the map is not big enough\n");
       return 0;
   }

   data = create_data(name, dptype, base, length);
   if (data == NULL) {
       return 0;
   }
   if (act == kReplace) {
       free(cur->data);
       cur->data = data;
   } else {
           // adjust this block's size
       cur->data->dpme_pblock_start = adjusted_base;
       cur->data->dpme_pblocks = adjusted_length;
       cur->data->dpme_lblocks = adjusted_length;
           // insert new with block address equal to this one
       if (add_data_to_map(data, cur->disk_address, map) == 0) {
           free(data);
       } else if (act == kSplit) {
           data = create_data(kFreeName, kFreeType, new_base, new_length);
           if (data != NULL) {
                   // insert new with block address equal to this one
               if (add_data_to_map(data, cur->disk_address, map) == 0) {
                   free(data);
               }
           }
       }
   }
       // renumber disk addresses
   renumber_disk_addresses(map);
       // mark changed
   map->changed = 1;
   return 1;
}


DPME *
create_data(const char *name, const char *dptype, uint32_t base, uint32_t length)
{
   DPME *data;

   data = (DPME *) calloc(1, PBLOCK_SIZE);
   if (data == NULL) {
       error(errno, "can't allocate memory for disk buffers");
   } else {
       // set data into entry
       data->dpme_signature = DPME_SIGNATURE;
       data->dpme_map_entries = 1;
       data->dpme_pblock_start = base;
       data->dpme_pblocks = length;
       strncpy(data->dpme_name, name, DPISTRLEN);
       strncpy(data->dpme_type, dptype, DPISTRLEN);
       data->dpme_lblock_start = 0;
       data->dpme_lblocks = data->dpme_pblocks;
       dpme_init_flags(data);
   }
   return data;
}

void
dpme_init_flags(DPME *data)
{
   if (istrncmp(data->dpme_type, kHFSType, DPISTRLEN) == 0) { /* XXX this is gross, fix it! */
       data->dpme_flags = APPLE_HFS_FLAGS_VALUE;
   }
   else {
       dpme_writable_set(data, 1);
       dpme_readable_set(data, 1);
       dpme_bootable_set(data, 0);
       dpme_in_use_set(data, 0);
       dpme_allocated_set(data, 1);
       dpme_valid_set(data, 1);
   }
}

/* These bits are appropriate for Apple_UNIX_SVR2 partitions
* used by NetBSD.  They may be ok for A/UX, but have not been
* tested.
*/
void
bzb_init_slice(BZB *bp, int slice)
{
   memset(bp,0,sizeof(BZB));
   if ((slice >= 'A') && (slice <= 'Z')) {
       slice += 'a' - 'A';
   }
   if ((slice != 0) && ((slice < 'a') || (slice > 'z'))) {
       error(-1,"Bad bzb slice");
       slice = 0;
   }
   switch (slice) {
   case 0:
   case 'c':
       return;
   case 'a':
       bp->bzb_type = FST;
       strlcpy((char *)bp->bzb_mount_point, "/", sizeof(bp->bzb_mount_point));
       bp->bzb_inode = 1;
       bzb_root_set(bp,1);
       bzb_usr_set(bp,1);
       break;
   case 'b':
       bp->bzb_type = FSTSFS;
       strlcpy((char *)bp->bzb_mount_point, "(swap)", sizeof(bp->bzb_mount_point));
       break;
   case 'g':
       strlcpy((char *)bp->bzb_mount_point, "/usr", sizeof(bp->bzb_mount_point));
       /* Fall through */
   default:
       bp->bzb_type = FST;
       bp->bzb_inode = 1;
       bzb_usr_set(bp,1);
       break;
   }
   bzb_slice_set(bp,0);  // XXX NetBSD disksubr.c ignores slice
   //  bzb_slice_set(bp,slice-'a'+1);
   bp->bzb_magic = BZBMAGIC;
}

void
renumber_disk_addresses(partition_map_header *map)
{
   partition_map * cur;
   long ix;

       // reset disk addresses
   cur = map->disk_order;
   ix = 1;
   while (cur != NULL) {
       cur->disk_address = ix++;
       cur->data->dpme_map_entries = map->blocks_in_map;
       cur = cur->next_on_disk;
   }
}


long
compute_device_size(partition_map_header *map, partition_map_header *oldmap)
{
#ifdef TEST_COMPUTE
   uint32_t length;
   struct hd_geometry geometry;
   struct stat info;
   loff_t pos;
#endif
   char* data;
   uint32_t l, r, x = 0;
   long long size;
   int valid = 0;
#ifdef TEST_COMPUTE
   int fd;

   fd = map->fd->fd;
   printf("\n");
   if (fstat(fd, &info) < 0) {
       printf("stat of device failed\n");
   } else {
       printf("stat: mode = 0%o, type=%s\n", info.st_mode,
               (S_ISREG(info.st_mode)? "Regular":
               (S_ISBLK(info.st_mode)?"Block":"Other")));
       printf("size = %d, blocks = %d\n",
               info.st_size, info.st_size/map->logical_block);
   }

   if (ioctl(fd, BLKGETSIZE, &length) < 0) {
       printf("get device size failed\n");
   } else {
       printf("BLKGETSIZE:size in blocks = %u\n", length);
   }

   if (ioctl(fd, HDIO_GETGEO, &geometry) < 0) {
       printf("get device geometry failed\n");
   } else {
       printf("HDIO_GETGEO: heads=%d, sectors=%d, cylinders=%d, start=%d,  total=%d\n",
               geometry.heads, geometry.sectors,
               geometry.cylinders, geometry.start,
               geometry.heads*geometry.sectors*geometry.cylinders);
   }

   if ((pos = llseek(fd, (loff_t)0, SEEK_END)) < 0) {
       printf("llseek to end of device failed\n");
   } else if ((pos = llseek(fd, (loff_t)0, SEEK_CUR)) < 0) {
       printf("llseek to end of device failed on second try\n");
   } else {
       printf("llseek: pos = %d, blocks=%d\n", pos, pos/map->logical_block);
   }
#endif

   if (cflag == 0 && oldmap != NULL && oldmap->misc->sbBlkCount != 0) {
       return (oldmap->misc->sbBlkCount
               * (oldmap->physical_block / map->logical_block));
   }

   size = media_total_size(map->m);
   if (size != 0) {
       return (long)(size / map->logical_block);
   }

   // else case

   data = (char *) malloc(PBLOCK_SIZE);
   if (data == NULL) {
       error(errno, "can't allocate memory for try buffer");
       x = 0;
   } else {
       // double till off end
       l = 0;
       r = 1024;
       while (read_block(map, r, data) != 0) {
           l = r;
           if (r <= 1024) {
               r = r * 1024;
           } else {
               r = r * 2;
           }
           if (r >= 0x80000000) {
               r = 0xFFFFFFFE;
               break;
           }
       }
       // binary search for end
       while (l <= r) {
           x = (r - l) / 2 + l;
           if ((valid = read_block(map, x, data)) != 0) {
               l = x + 1;
           } else {
               if (x > 0) {
                   r = x - 1;
               } else {
                   break;
               }
           }
       }
       if (valid != 0) {
           x = x + 1;
       }
       // printf("size in blocks = %d\n", x);
       free(data);
   }

   return x;
}


void
sync_device_size(partition_map_header *map)
{
   Block0 *p;
   uint32_t size;
   double d;

   p = map->misc;
   if (p == NULL) {
       return;
   }
   d = map->media_size;
   size = (d * map->logical_block) / p->sbBlkSize;
   if (p->sbBlkCount != size) {
       p->sbBlkCount = size;
   }
}


void
delete_partition_from_map(partition_map *entry)
{
   partition_map_header *map;
   DPME *data;

   if (istrncmp(entry->data->dpme_type, kMapType, DPISTRLEN) == 0) {
       printf("Can't delete entry for the map itself\n");
       return;
   }
   if (entry->contains_driver) {
       printf("This program can't install drivers\n");
       if (get_okay("are you sure you want to delete this driver? [n/y]: ", 0) != 1) {
           return;
       }
   }
   // if past end of disk, delete it completely
   if (entry->next_by_base == NULL &&
       entry->data->dpme_pblock_start >= entry->the_map->media_size) {
     if (entry->contains_driver) {
       remove_driver(entry);   // update block0 if necessary
     }
     delete_entry(entry);
     return;
   }
   // If at end of disk, incorporate extra disk space to partition
   if (entry->next_by_base == NULL) {
     entry->data->dpme_pblocks =
        entry->the_map->media_size - entry->data->dpme_pblock_start;
   }
   data = create_data(kFreeName, kFreeType,
           entry->data->dpme_pblock_start, entry->data->dpme_pblocks);
   if (data == NULL) {
       return;
   }
   if (entry->contains_driver) {
       remove_driver(entry);   // update block0 if necessary
   }
   free(entry->data);
   free(entry->HFS_name);
   entry->HFS_kind = kHFS_not;
   entry->HFS_name = 0;
   entry->data = data;
   combine_entry(entry);
   map = entry->the_map;
   renumber_disk_addresses(map);
   map->changed = 1;
}


int
contains_driver(partition_map *entry)
{
   partition_map_header *map;
   Block0 *p;
   DDMap *m;
   int i;
   int f;
   uint32_t start;

   map = entry->the_map;
   p = map->misc;
   if (p == NULL) {
       return 0;
   }
   if (p->sbSig != BLOCK0_SIGNATURE) {
       return 0;
   }
   if (map->logical_block > p->sbBlkSize) {
       return 0;
   } else {
       f = p->sbBlkSize / map->logical_block;
   }
   if (p->sbDrvrCount > 0) {
       m = (DDMap *) p->sbMap;
       for (i = 0; i < p->sbDrvrCount; i++) {
           start = get_align_long(&m[i].ddBlock);
           if (entry->data->dpme_pblock_start <= f*start
                   && f*(start + m[i].ddSize)
                       <= (entry->data->dpme_pblock_start
                       + entry->data->dpme_pblocks)) {
               return 1;
           }
       }
   }
   return 0;
}


void
combine_entry(partition_map *entry)
{
   partition_map *p;
   uint32_t end;

   if (entry == NULL
           || istrncmp(entry->data->dpme_type, kFreeType, DPISTRLEN) != 0) {
       return;
   }
   if (entry->next_by_base != NULL) {
       p = entry->next_by_base;
       if (istrncmp(p->data->dpme_type, kFreeType, DPISTRLEN) != 0) {
           // next is not free
       } else if (entry->data->dpme_pblock_start + entry->data->dpme_pblocks
               != p->data->dpme_pblock_start) {
           // next is not contiguous (XXX this is bad)
           printf("next entry is not contiguous\n");
           // start is already minimum
           // new end is maximum of two ends
           end = p->data->dpme_pblock_start + p->data->dpme_pblocks;
           if (end > entry->data->dpme_pblock_start + entry->data->dpme_pblocks) {
               entry->data->dpme_pblocks = end - entry->data->dpme_pblock_start;
           }
           entry->data->dpme_lblocks = entry->data->dpme_pblocks;
           delete_entry(p);
       } else {
           entry->data->dpme_pblocks += p->data->dpme_pblocks;
           entry->data->dpme_lblocks = entry->data->dpme_pblocks;
           delete_entry(p);
       }
   }
   if (entry->prev_by_base != NULL) {
       p = entry->prev_by_base;
       if (istrncmp(p->data->dpme_type, kFreeType, DPISTRLEN) != 0) {
           // previous is not free
       } else if (p->data->dpme_pblock_start + p->data->dpme_pblocks
               != entry->data->dpme_pblock_start) {
           // previous is not contiguous (XXX this is bad)
           printf("previous entry is not contiguous\n");
           // new end is maximum of two ends
           end = p->data->dpme_pblock_start + p->data->dpme_pblocks;
           if (end < entry->data->dpme_pblock_start + entry->data->dpme_pblocks) {
               end = entry->data->dpme_pblock_start + entry->data->dpme_pblocks;
           }
           entry->data->dpme_pblocks = end - p->data->dpme_pblock_start;
           // new start is previous entry's start
           entry->data->dpme_pblock_start = p->data->dpme_pblock_start;
           entry->data->dpme_lblocks = entry->data->dpme_pblocks;
           delete_entry(p);
       } else {
           entry->data->dpme_pblock_start = p->data->dpme_pblock_start;
           entry->data->dpme_pblocks += p->data->dpme_pblocks;
           entry->data->dpme_lblocks = entry->data->dpme_pblocks;
           delete_entry(p);
       }
   }
   entry->contains_driver = contains_driver(entry);
}


void
delete_entry(partition_map *entry)
{
   partition_map_header *map;
   partition_map *p;

   map = entry->the_map;
   map->blocks_in_map--;

   remove_from_disk_order(entry);

   p = entry->next_by_base;
   if (map->base_order == entry) {
       map->base_order = p;
   }
   if (p != NULL) {
       p->prev_by_base = entry->prev_by_base;
   }
   if (entry->prev_by_base != NULL) {
       entry->prev_by_base->next_by_base = p;
   }

   free(entry->data);
   free(entry->HFS_name);
   free(entry);
}


partition_map *
find_entry_by_disk_address(int32_t ix, partition_map_header *map)
{
   partition_map * cur;

   cur = map->disk_order;
   while (cur != NULL) {
       if (cur->disk_address == ix) {
           break;
       }
       cur = cur->next_on_disk;
   }
   return cur;
}


partition_map *
find_entry_by_type(const char *type_name, partition_map_header *map)
{
   partition_map * cur;

   cur = map->base_order;
   while (cur != NULL) {
       if (istrncmp(cur->data->dpme_type, type_name, DPISTRLEN) == 0) {
           break;
       }
       cur = cur->next_by_base;
   }
   return cur;
}

partition_map *
find_entry_by_base(uint32_t base, partition_map_header *map)
{
   partition_map * cur;

   cur = map->base_order;
   while (cur != NULL) {
       if (cur->data->dpme_pblock_start == base) {
           break;
       }
       cur = cur->next_by_base;
   }
   return cur;
}


void
move_entry_in_map(int32_t old_index, int32_t ix, partition_map_header *map)
{
   partition_map * cur;

   cur = find_entry_by_disk_address(old_index, map);
   if (cur == NULL) {
       printf("No such partition\n");
   } else {
       remove_from_disk_order(cur);
       cur->disk_address = ix;
       insert_in_disk_order(cur);
       renumber_disk_addresses(map);
       map->changed = 1;
   }
}


void
remove_from_disk_order(partition_map *entry)
{
   partition_map_header *map;
   partition_map *p;

   map = entry->the_map;
   p = entry->next_on_disk;
   if (map->disk_order == entry) {
       map->disk_order = p;
   }
   if (p != NULL) {
       p->prev_on_disk = entry->prev_on_disk;
   }
   if (entry->prev_on_disk != NULL) {
       entry->prev_on_disk->next_on_disk = p;
   }
   entry->next_on_disk = NULL;
   entry->prev_on_disk = NULL;
}


void
insert_in_disk_order(partition_map *entry)
{
   partition_map_header *map;
   partition_map * cur;

   // find position in disk list & insert
   map = entry->the_map;
   cur = map->disk_order;
   if (cur == NULL || entry->disk_address <= cur->disk_address) {
       map->disk_order = entry;
       entry->next_on_disk = cur;
       if (cur != NULL) {
           cur->prev_on_disk = entry;
       }
       entry->prev_on_disk = NULL;
   } else {
       for (cur = map->disk_order; cur != NULL; cur = cur->next_on_disk) {
           if (cur->disk_address <= entry->disk_address
                   && (cur->next_on_disk == NULL
                   || entry->disk_address <= cur->next_on_disk->disk_address)) {
               entry->next_on_disk = cur->next_on_disk;
               cur->next_on_disk = entry;
               entry->prev_on_disk = cur;
               if (entry->next_on_disk != NULL) {
                   entry->next_on_disk->prev_on_disk = entry;
               }
               break;
           }
       }
   }
}


void
insert_in_base_order(partition_map *entry)
{
   partition_map_header *map;
   partition_map * cur;

   // find position in base list & insert
   map = entry->the_map;
   cur = map->base_order;
   if (cur == NULL
           || entry->data->dpme_pblock_start <= cur->data->dpme_pblock_start) {
       map->base_order = entry;
       entry->next_by_base = cur;
       if (cur != NULL) {
           cur->prev_by_base = entry;
       }
       entry->prev_by_base = NULL;
   } else {
       for (cur = map->base_order; cur != NULL; cur = cur->next_by_base) {
           if (cur->data->dpme_pblock_start <= entry->data->dpme_pblock_start
                   && (cur->next_by_base == NULL
                   || entry->data->dpme_pblock_start
                       <= cur->next_by_base->data->dpme_pblock_start)) {
               entry->next_by_base = cur->next_by_base;
               cur->next_by_base = entry;
               entry->prev_by_base = cur;
               if (entry->next_by_base != NULL) {
                   entry->next_by_base->prev_by_base = entry;
               }
               break;
           }
       }
   }
}


void
resize_map(uint32_t new_size, partition_map_header *map)
{
   partition_map * entry;
   partition_map * next;
   uint32_t incr;

   // find map entry
   entry = find_entry_by_type(kMapType, map);

   if (entry == NULL) {
       printf("Couldn't find entry for map!\n");
       return;
   }
   next = entry->next_by_base;

       // same size
   if (new_size == entry->data->dpme_pblocks) {
       // do nothing
       return;
   }

       // make it smaller
   if (new_size < entry->data->dpme_pblocks) {
       if (next == NULL
               || istrncmp(next->data->dpme_type, kFreeType, DPISTRLEN) != 0) {
           incr = 1;
       } else {
           incr = 0;
       }
       if (new_size < map->blocks_in_map + incr) {
           printf("New size would be too small\n");
           return;
       }
       goto doit;
   }

       // make it larger
   if (next == NULL
           || istrncmp(next->data->dpme_type, kFreeType, DPISTRLEN) != 0) {
       printf("No free space to expand into\n");
       return;
   }
   if (entry->data->dpme_pblock_start + entry->data->dpme_pblocks
           != next->data->dpme_pblock_start) {
       printf("No contiguous free space to expand into\n");
       return;
   }
   if (new_size > entry->data->dpme_pblocks + next->data->dpme_pblocks) {
       printf("No enough free space\n");
       return;
   }
doit:
   entry->data->dpme_type[0] = 0;
   delete_partition_from_map(entry);
   add_partition_to_map("Apple", kMapType, 1, new_size, map);
   map->maximum_in_map = new_size;
}


void
remove_driver(partition_map *entry)
{
   partition_map_header *map;
   Block0 *p;
   DDMap *m;
   int i;
   int j;
   int f;
   uint32_t start;

   map = entry->the_map;
   p = map->misc;
   if (p == NULL) {
       return;
   }
   if (p->sbSig != BLOCK0_SIGNATURE) {
       return;
   }
   if (map->logical_block > p->sbBlkSize) {
       /* this is not supposed to happen, but let's just ignore it. */
       return;
   } else {
       /*
        * compute the factor to convert the block numbers in block0
        * into partition map block numbers.
        */
       f = p->sbBlkSize / map->logical_block;
   }
   if (p->sbDrvrCount > 0) {
       m = (DDMap *) p->sbMap;
       for (i = 0; i < p->sbDrvrCount; i++) {
           start = get_align_long(&m[i].ddBlock);

           /* zap the driver if it is wholly contained in the partition */
           if (entry->data->dpme_pblock_start <= f*start
                   && f*(start + m[i].ddSize)
                       <= (entry->data->dpme_pblock_start
                       + entry->data->dpme_pblocks)) {
               // delete this driver
               // by copying down later ones and zapping the last
               for (j = i+1; j < p->sbDrvrCount; j++, i++) {
                  put_align_long(get_align_long(&m[j].ddBlock), &m[i].ddBlock);
                  m[i].ddSize = m[j].ddSize;
                  m[i].ddType = m[j].ddType;
               }
               put_align_long(0, &m[i].ddBlock);
               m[i].ddSize = 0;
               m[i].ddType = 0;
               p->sbDrvrCount -= 1;
               return; /* XXX if we continue we will delete other drivers? */
           }
       }
   }
}

int
read_block(partition_map_header *map, uint32_t num, char *buf)
{
//printf("read block %d\n", num);
   return read_media(map->m, ((long long) num) * map->logical_block,
               PBLOCK_SIZE, (void *)buf);
}


int
write_block(partition_map_header *map, uint32_t num, char *buf)
{
   return write_media(map->m, ((long long) num) * map->logical_block,
               PBLOCK_SIZE, (void *)buf);
}