#include <sys/queue.h>
#include <sys/stdint.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "partition_map.h"
#include "io.h"
#include "file_media.h"
#define APPLE_HFS_FLAGS_VALUE 0x4000037f
const char *kFreeType = "Apple_Free";
const char *kMapType = "Apple_partition_map";
const char *kUnixType = "OpenBSD";
const char *kHFSType = "Apple_HFS";
void combine_entry(struct entry *);
struct entry *create_entry(struct partition_map *, long, const char *,
const char *, uint32_t, uint32_t);
void delete_entry(struct entry *);
void insert_in_base_order(struct entry *);
void insert_in_disk_order(struct entry *);
int read_partition_map(struct partition_map *);
void remove_driver(struct entry *);
void renumber_disk_addresses(struct partition_map *);
struct partition_map *
open_partition_map(int fd, char *name, uint64_t mediasz, uint32_t sectorsz)
{
struct partition_map *map;
int ok;
map = malloc(sizeof(struct partition_map));
if (map == NULL)
errx(1, "No memory to open partition map");
map->fd = fd;
map->name = name;
map->changed = 0;
LIST_INIT(&map->disk_order);
LIST_INIT(&map->base_order);
map->blocks_in_map = 0;
map->maximum_in_map = -1;
if (mediasz > UINT32_MAX)
map->media_size = UINT32_MAX;
else
map->media_size = mediasz;
if (read_block0(map->fd, map) == 0) {
warnx("Can't read block 0 from '%s'", name);
free_partition_map(map);
return NULL;
}
if (map->sbSig == BLOCK0_SIGNATURE &&
map->sbBlkSize == sectorsz &&
map->sbBlkCount <= mediasz) {
if (read_partition_map(map) == 0)
return map;
} else {
if (map->sbSig != BLOCK0_SIGNATURE)
warnx("Block 0 signature: Expected 0x%04x, "
"got 0x%04x", BLOCK0_SIGNATURE,
map->sbSig);
else if (map->sbBlkSize != sectorsz)
warnx("Block 0 sbBlkSize (%u) != sector size (%u)",
map->sbBlkSize, sectorsz);
else if (map->sbBlkCount > mediasz)
warnx("Block 0 sbBlkCount (%u) > media size (%llu)",
map->sbBlkCount,
(unsigned long long)mediasz);
}
if (!lflag) {
my_ungetch('\n');
printf("No valid partition map found on '%s'.\n", name);
ok = get_okay("Create default map? [n/y]: ", 0);
flush_to_newline(0);
if (ok == 1) {
free_partition_map(map);
map = create_partition_map(fd, name, mediasz, sectorsz);
if (map)
return map;
}
}
free_partition_map(map);
return NULL;
}
void
free_partition_map(struct partition_map *map)
{
struct entry *entry;
if (map == NULL)
return;
while (!LIST_EMPTY(&map->disk_order)) {
entry = LIST_FIRST(&map->disk_order);
LIST_REMOVE(entry, disk_entry);
free(entry);
}
free(map);
}
int
read_partition_map(struct partition_map *map)
{
struct entry *cur, *nextcur;
struct entry *entry;
int ix;
uint32_t limit, base, next, nextbase;
limit = 1;
for (ix = 1; ix <= limit; ix++) {
entry = malloc(sizeof(struct entry));
if (entry == NULL)
errx(1, "No memory for partition entry");
if (read_dpme(map->fd, ix, entry) == 0) {
warnx("Can't read block %u from '%s'", ix, map->name);
free(entry);
return 1;
}
if (entry->dpme_signature != DPME_SIGNATURE) {
warnx("Invalid signature on block %d. Expected %x, "
"got %x", ix, DPME_SIGNATURE,
entry->dpme_signature);
free(entry);
return 1;
}
if (ix == 1) {
if (entry->dpme_map_entries > entry->dpme_pblocks) {
warnx("Map entry count (%u) > # of physical "
"blocks (%u)", entry->dpme_map_entries,
entry->dpme_pblocks);
free(entry);
return 1;
}
if (entry->dpme_map_entries == 0) {
warnx("Map entry count == 0. Must be > 0");
free(entry);
return 1;
}
map->maximum_in_map = entry->dpme_pblocks;
limit = entry->dpme_map_entries;
}
if (limit != entry->dpme_map_entries) {
warnx("Invalid entry count on block %d. "
"Expected %d, got %d", ix, limit,
entry->dpme_map_entries);
free(entry);
return 1;
}
if (entry->dpme_lblock_start >= entry->dpme_pblocks) {
warnx("\tlogical start (%u) >= block count"
"count (%u).", entry->dpme_lblock_start,
entry->dpme_pblocks);
free(entry);
return 1;
}
if (entry->dpme_lblocks > entry->dpme_pblocks -
entry->dpme_lblock_start) {
warnx("\tlogical blocks (%u) > available blocks (%u).",
entry->dpme_lblocks,
entry->dpme_pblocks - entry->dpme_lblock_start);
free(entry);
return 1;
}
entry->the_map = map;
entry->disk_address = ix;
insert_in_disk_order(entry);
insert_in_base_order(entry);
map->blocks_in_map++;
}
LIST_FOREACH(cur, &map->base_order, base_entry) {
base = cur->dpme_pblock_start;
next = base + cur->dpme_pblocks;
if (base >= map->media_size ||
next < base ||
next > map->media_size) {
warnx("Partition extends past end of disk: %u -> %u",
base, next);
}
nextcur = LIST_NEXT(cur, base_entry);
if (nextcur)
nextbase = nextcur->dpme_pblock_start;
else
nextbase = map->media_size;
if (next != nextbase)
warnx("Unmapped pblocks: %u -> %u", next, nextbase);
if (next > nextbase)
warnx("Partition %ld overlaps next partition",
cur->disk_address);
}
return 0;
}
void
write_partition_map(struct partition_map *map)
{
struct entry *entry;
int result;
result = write_block0(map->fd, map);
if (result == 0)
warn("Unable to write block zero");
LIST_FOREACH(entry, &map->disk_order, disk_entry) {
result = write_dpme(map->fd, entry->disk_address, entry);
if (result == 0)
warn("Unable to write block %ld", entry->disk_address);
}
}
struct partition_map *
create_partition_map(int fd, char *name, u_int64_t mediasz, uint32_t sectorsz)
{
struct partition_map *map;
struct entry *entry;
map = malloc(sizeof(struct partition_map));
if (map == NULL)
errx(1, "No memory to create partition map");
map->name = name;
map->fd = fd;
map->changed = 1;
LIST_INIT(&map->disk_order);
LIST_INIT(&map->base_order);
map->blocks_in_map = 0;
map->maximum_in_map = -1;
map->media_size = mediasz;
map->sbSig = BLOCK0_SIGNATURE;
map->sbBlkSize = sectorsz;
map->sbBlkCount = map->media_size;
entry = create_entry(map, 1, "", kFreeType, 1, mediasz - 1);
if (entry == NULL)
errx(1, "No memory for new dpme");
add_partition_to_map("Apple", kMapType, 1,
(map->media_size <= 128 ? 2 : 63), map);
return map;
}
int
add_partition_to_map(const char *name, const char *dptype, uint32_t base,
uint32_t length, struct partition_map *map)
{
struct entry *cur;
int limit, new_entries;
uint32_t old_base, old_length, old_address;
uint32_t new_base, new_length;
if (map->maximum_in_map < 0)
limit = map->media_size;
else
limit = map->maximum_in_map;
LIST_FOREACH(cur, &map->base_order, base_entry) {
if (strncasecmp(cur->dpme_type, kFreeType, DPISTRLEN))
continue;
if (cur->dpme_pblock_start <= base &&
(base + length) <=
(cur->dpme_pblock_start + cur->dpme_pblocks))
break;
}
if (cur == NULL) {
printf("requested base and length is not "
"within an existing free partition\n");
return 0;
}
old_base = cur->dpme_pblock_start;
old_length = cur->dpme_pblocks;
old_address = cur->disk_address;
if (base == old_base && length == old_length)
new_entries = 0;
else if (base == old_base)
new_entries = 1;
else if (base - old_base < old_length - length)
new_entries = 2;
else
new_entries = 1;
if (map->blocks_in_map + new_entries > limit) {
printf("the map is not big enough\n");
return 0;
}
delete_entry(cur);
new_base = base + length;
new_length = (old_base + old_length) - new_base;
if (new_length > 0) {
cur = create_entry(map, old_address, "", kFreeType, new_base,
new_length);
if (cur == NULL)
errx(1, "No memory for new dpme");
}
cur = create_entry(map, old_address, name, dptype, base, length);
if (cur == NULL)
errx(1, "No memory for new entry");
new_length = base - old_base;
if (new_length > 0) {
cur = create_entry(map, old_address, "", kFreeType, old_base,
new_length);
if (cur == NULL)
errx(1, "No memory for new entry");
}
renumber_disk_addresses(map);
map->changed = 1;
return 1;
}
struct entry*
create_entry(struct partition_map *map, long ix, const char *name,
const char *dptype, uint32_t base, uint32_t length)
{
struct entry *entry;
entry = calloc(1, sizeof(struct entry));
if (entry == NULL)
errx(1, "No memory for new entry");
entry->dpme_signature = DPME_SIGNATURE;
entry->dpme_map_entries = 1;
entry->dpme_pblock_start = base;
entry->dpme_pblocks = length;
strlcpy(entry->dpme_name, name, sizeof(entry->dpme_name));
strlcpy(entry->dpme_type, dptype, sizeof(entry->dpme_type));
if (strncasecmp(dptype, kFreeType, DPISTRLEN)) {
entry->dpme_lblocks = entry->dpme_pblocks;
}
dpme_init_flags(entry);
entry->disk_address = ix;
entry->the_map = map;
insert_in_disk_order(entry);
insert_in_base_order(entry);
map->blocks_in_map++;
if (map->maximum_in_map < 0) {
if (strncasecmp(entry->dpme_type, kMapType, DPISTRLEN) == 0)
map->maximum_in_map = entry->dpme_pblocks;
}
return entry;
}
void
dpme_init_flags(struct entry *entry)
{
if (strncasecmp(entry->dpme_type, kFreeType, DPISTRLEN) == 0)
entry->dpme_flags = 0;
else if (strncasecmp(entry->dpme_type, kMapType, DPISTRLEN) == 0)
entry->dpme_flags = DPME_VALID | DPME_ALLOCATED;
else if (strncasecmp(entry->dpme_type, kHFSType, DPISTRLEN) == 0)
entry->dpme_flags = APPLE_HFS_FLAGS_VALUE;
else
entry->dpme_flags = DPME_VALID | DPME_ALLOCATED |
DPME_READABLE | DPME_WRITABLE;
}
void
renumber_disk_addresses(struct partition_map *map)
{
struct entry *cur;
long ix;
ix = 1;
LIST_FOREACH(cur, &map->disk_order, disk_entry) {
cur->disk_address = ix++;
cur->dpme_map_entries = map->blocks_in_map;
}
}
void
delete_partition_from_map(struct entry *entry)
{
struct partition_map *map;
uint32_t base, length, address;
if (strncasecmp(entry->dpme_type, kMapType, DPISTRLEN) == 0) {
printf("Can't delete entry for the map itself\n");
return;
}
if (strncasecmp(entry->dpme_type, kFreeType, DPISTRLEN) == 0) {
printf("Can't delete entry for free space\n");
return;
}
if (contains_driver(entry)) {
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;
}
remove_driver(entry);
}
map = entry->the_map;
base = entry->dpme_pblock_start;
length = entry->dpme_pblocks;
address = entry->disk_address;
delete_entry(entry);
entry = create_entry(map, address, "" , kFreeType, base, length);
combine_entry(entry);
renumber_disk_addresses(entry->the_map);
entry->the_map->changed = 1;
}
int
contains_driver(struct entry *entry)
{
struct partition_map *map;
struct ddmap *m;
int i;
uint32_t start;
map = entry->the_map;
m = map->sbDDMap;
for (i = 0; i < map->sbDrvrCount; i++) {
start = m[i].ddBlock;
if (entry->dpme_pblock_start <= start &&
(start + m[i].ddSize) <= (entry->dpme_pblock_start +
entry->dpme_pblocks))
return 1;
}
return 0;
}
void
combine_entry(struct entry *entry)
{
struct entry *p;
uint32_t end;
if (entry == NULL ||
strncasecmp(entry->dpme_type, kFreeType, DPISTRLEN) != 0)
return;
p = LIST_NEXT(entry, base_entry);
if (p != NULL) {
if (strncasecmp(p->dpme_type, kFreeType, DPISTRLEN) !=
0) {
} else if (entry->dpme_pblock_start +
entry->dpme_pblocks != p->dpme_pblock_start) {
printf("next entry is not contiguous\n");
end = p->dpme_pblock_start + p->dpme_pblocks;
if (end > entry->dpme_pblock_start +
entry->dpme_pblocks) {
entry->dpme_pblocks = end -
entry->dpme_pblock_start;
}
delete_entry(p);
} else {
entry->dpme_pblocks += p->dpme_pblocks;
delete_entry(p);
}
}
LIST_FOREACH(p, &entry->the_map->base_order, base_entry) {
if (LIST_NEXT(p, base_entry) == entry)
break;
}
if (p != NULL) {
if (strncasecmp(p->dpme_type, kFreeType, DPISTRLEN) != 0) {
} else if (p->dpme_pblock_start + p->dpme_pblocks !=
entry->dpme_pblock_start) {
printf("previous entry is not contiguous\n");
end = p->dpme_pblock_start + p->dpme_pblocks;
if (end < entry->dpme_pblock_start +
entry->dpme_pblocks) {
end = entry->dpme_pblock_start +
entry->dpme_pblocks;
}
entry->dpme_pblocks = end - p->dpme_pblock_start;
entry->dpme_pblock_start = p->dpme_pblock_start;
delete_entry(p);
} else {
entry->dpme_pblock_start = p->dpme_pblock_start;
entry->dpme_pblocks += p->dpme_pblocks;
delete_entry(p);
}
}
}
void
delete_entry(struct entry *entry)
{
struct partition_map *map;
map = entry->the_map;
map->blocks_in_map--;
LIST_REMOVE(entry, disk_entry);
LIST_REMOVE(entry, base_entry);
free(entry);
}
struct entry *
find_entry_by_disk_address(long ix, struct partition_map *map)
{
struct entry *cur;
LIST_FOREACH(cur, &map->disk_order, disk_entry) {
if (cur->disk_address == ix)
break;
}
return cur;
}
struct entry *
find_entry_by_type(const char *type_name, struct partition_map *map)
{
struct entry *cur;
LIST_FOREACH(cur, &map->base_order, base_entry) {
if (strncasecmp(cur->dpme_type, type_name, DPISTRLEN) == 0)
break;
}
return cur;
}
struct entry *
find_entry_by_base(uint32_t base, struct partition_map *map)
{
struct entry *cur;
LIST_FOREACH(cur, &map->base_order, base_entry) {
if (cur->dpme_pblock_start == base)
break;
}
return cur;
}
void
move_entry_in_map(long index1, long index2, struct partition_map *map)
{
struct entry *p1, *p2;
if (index1 == index2)
return;
if (index1 == 1 || index2 == 1) {
printf("Partition #1 cannot be moved\n");
return;
}
p1 = find_entry_by_disk_address(index1, map);
if (p1 == NULL) {
printf("Partition #%ld not found\n", index1);
return;
}
p2 = find_entry_by_disk_address(index2, map);
if (p2 == NULL) {
printf("Partition #%ld not found\n", index2);
return;
}
LIST_REMOVE(p1, disk_entry);
LIST_REMOVE(p2, disk_entry);
p1->disk_address = index2;
p2->disk_address = index1;
insert_in_disk_order(p1);
insert_in_disk_order(p2);
renumber_disk_addresses(map);
map->changed = 1;
}
void
insert_in_disk_order(struct entry *entry)
{
struct partition_map *map;
struct entry *cur;
map = entry->the_map;
if (LIST_EMPTY(&map->disk_order)) {
LIST_INSERT_HEAD(&map->disk_order, entry, disk_entry);
return;
}
LIST_FOREACH(cur, &map->disk_order, disk_entry) {
if (cur->disk_address >= entry->disk_address) {
LIST_INSERT_BEFORE(cur, entry, disk_entry);
return;
}
if (LIST_NEXT(cur, disk_entry) == NULL) {
LIST_INSERT_AFTER(cur, entry, disk_entry);
return;
}
}
}
void
insert_in_base_order(struct entry *entry)
{
struct partition_map *map;
struct entry *cur;
uint32_t start;
map = entry->the_map;
if (LIST_EMPTY(&map->base_order)) {
LIST_INSERT_HEAD(&map->base_order, entry, base_entry);
return;
}
start = entry->dpme_pblock_start;
LIST_FOREACH(cur, &map->base_order, base_entry) {
if (start <= cur->dpme_pblock_start) {
LIST_INSERT_BEFORE(cur, entry, base_entry);
return;
}
if (LIST_NEXT(cur, base_entry) == NULL) {
LIST_INSERT_AFTER(cur, entry, base_entry);
return;
}
}
}
void
resize_map(long new_size, struct partition_map *map)
{
struct entry *entry;
struct entry *next;
int incr;
entry = find_entry_by_type(kMapType, map);
if (entry == NULL) {
printf("Couldn't find entry for map!\n");
return;
}
if (new_size == entry->dpme_pblocks)
return;
next = LIST_NEXT(entry, base_entry);
if (new_size < entry->dpme_pblocks) {
if (next == NULL ||
strncasecmp(next->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;
}
if (next == NULL ||
strncasecmp(next->dpme_type, kFreeType, DPISTRLEN) != 0) {
printf("No free space to expand into\n");
return;
}
if (entry->dpme_pblock_start + entry->dpme_pblocks
!= next->dpme_pblock_start) {
printf("No contiguous free space to expand into\n");
return;
}
if (new_size > entry->dpme_pblocks + next->dpme_pblocks) {
printf("No enough free space\n");
return;
}
doit:
entry->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(struct entry *entry)
{
struct partition_map *map;
struct ddmap *m;
int i, j;
uint32_t start;
map = entry->the_map;
m = map->sbDDMap;
for (i = 0; i < map->sbDrvrCount; i++) {
start = m[i].ddBlock;
if (entry->dpme_pblock_start <= start && (start +
m[i].ddSize) <= (entry->dpme_pblock_start +
entry->dpme_pblocks)) {
for (j = i + 1; j < map->sbDrvrCount; j++, i++) {
m[i].ddBlock = m[i].ddBlock;
m[i].ddSize = m[j].ddSize;
m[i].ddType = m[j].ddType;
}
m[i].ddBlock = 0;
m[i].ddSize = 0;
m[i].ddType = 0;
map->sbDrvrCount -= 1;
return;
}
}
}