//
// 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.
*/
//
// 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;
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;
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);
// 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;
}
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
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);
}
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;
}
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);
}
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? */
}
}
}
}