1213136Spjd/*- 2213136Spjd * Copyright (c) 2010 Pawel Jakub Dawidek <pjd@FreeBSD.org> 3213136Spjd * All rights reserved. 4213136Spjd * 5213136Spjd * Redistribution and use in source and binary forms, with or without 6213136Spjd * modification, are permitted provided that the following conditions 7213136Spjd * are met: 8213136Spjd * 1. Redistributions of source code must retain the above copyright 9213136Spjd * notice, this list of conditions and the following disclaimer. 10213136Spjd * 2. Redistributions in binary form must reproduce the above copyright 11213136Spjd * notice, this list of conditions and the following disclaimer in the 12213136Spjd * documentation and/or other materials provided with the distribution. 13213136Spjd * 14213136Spjd * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND 15213136Spjd * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16213136Spjd * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17213136Spjd * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE 18213136Spjd * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19213136Spjd * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20213136Spjd * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21213136Spjd * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22213136Spjd * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23213136Spjd * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24213136Spjd * SUCH DAMAGE. 25213136Spjd */ 26213136Spjd 27213136Spjd#include <sys/cdefs.h> 28213136Spjd__FBSDID("$FreeBSD$"); 29213136Spjd 30213136Spjd#include <sys/param.h> 31213136Spjd#include <sys/gpt.h> 32213136Spjd 33213136Spjd#ifndef LITTLE_ENDIAN 34213136Spjd#error gpt.c works only for little endian architectures 35213136Spjd#endif 36213136Spjd 37213136Spjd#include "crc32.h" 38213136Spjd#include "drv.h" 39213136Spjd#include "util.h" 40213136Spjd#include "gpt.h" 41213136Spjd 42213136Spjd#define MAXTBLENTS 128 43213136Spjd 44213136Spjdstatic struct gpt_hdr hdr_primary, hdr_backup, *gpthdr; 45213136Spjdstatic uint64_t hdr_primary_lba, hdr_backup_lba; 46213136Spjdstatic struct gpt_ent table_primary[MAXTBLENTS], table_backup[MAXTBLENTS]; 47213136Spjdstatic struct gpt_ent *gpttable; 48213136Spjdstatic int curent, bootonce; 49213136Spjd 50213136Spjd/* 51213136Spjd * Buffer below 64kB passed on gptread(), which can hold at least 52214114Spjd * one sector of data (512 bytes). 53213136Spjd */ 54213136Spjdstatic char *secbuf; 55213136Spjd 56213136Spjdstatic void 57213136Spjdgptupdate(const char *which, struct dsk *dskp, struct gpt_hdr *hdr, 58213136Spjd struct gpt_ent *table) 59213136Spjd{ 60213136Spjd int entries_per_sec, firstent; 61213136Spjd daddr_t slba; 62213136Spjd 63213136Spjd /* 64213136Spjd * We need to update the following for both primary and backup GPT: 65214114Spjd * 1. Sector on disk that contains current partition. 66213136Spjd * 2. Partition table checksum. 67213136Spjd * 3. Header checksum. 68213136Spjd * 4. Header on disk. 69213136Spjd */ 70213136Spjd 71213136Spjd entries_per_sec = DEV_BSIZE / hdr->hdr_entsz; 72213136Spjd slba = curent / entries_per_sec; 73213136Spjd firstent = slba * entries_per_sec; 74219083Spjd bcopy(&table[firstent], secbuf, DEV_BSIZE); 75213136Spjd slba += hdr->hdr_lba_table; 76213136Spjd if (drvwrite(dskp, secbuf, slba, 1)) { 77213136Spjd printf("%s: unable to update %s GPT partition table\n", 78213136Spjd BOOTPROG, which); 79213136Spjd return; 80213136Spjd } 81213136Spjd hdr->hdr_crc_table = crc32(table, hdr->hdr_entries * hdr->hdr_entsz); 82213136Spjd hdr->hdr_crc_self = 0; 83213136Spjd hdr->hdr_crc_self = crc32(hdr, hdr->hdr_size); 84213136Spjd bzero(secbuf, DEV_BSIZE); 85219083Spjd bcopy(hdr, secbuf, hdr->hdr_size); 86213136Spjd if (drvwrite(dskp, secbuf, hdr->hdr_lba_self, 1)) { 87213136Spjd printf("%s: unable to update %s GPT header\n", BOOTPROG, which); 88213136Spjd return; 89213136Spjd } 90213136Spjd} 91213136Spjd 92213136Spjdint 93213136Spjdgptfind(const uuid_t *uuid, struct dsk *dskp, int part) 94213136Spjd{ 95213136Spjd struct gpt_ent *ent; 96213136Spjd int firsttry; 97213136Spjd 98213136Spjd if (part >= 0) { 99213136Spjd if (part == 0 || part > gpthdr->hdr_entries) { 100213136Spjd printf("%s: invalid partition index\n", BOOTPROG); 101213136Spjd return (-1); 102213136Spjd } 103213136Spjd ent = &gpttable[part - 1]; 104213136Spjd if (bcmp(&ent->ent_type, uuid, sizeof(uuid_t)) != 0) { 105213136Spjd printf("%s: specified partition is not UFS\n", 106213136Spjd BOOTPROG); 107213136Spjd return (-1); 108213136Spjd } 109213136Spjd curent = part - 1; 110213136Spjd goto found; 111213136Spjd } 112213136Spjd 113213136Spjd firsttry = (curent == -1); 114213136Spjd curent++; 115213136Spjd if (curent >= gpthdr->hdr_entries) { 116213136Spjd curent = gpthdr->hdr_entries; 117213136Spjd return (-1); 118213136Spjd } 119213136Spjd if (bootonce) { 120213136Spjd /* 121213136Spjd * First look for partition with both GPT_ENT_ATTR_BOOTME and 122213136Spjd * GPT_ENT_ATTR_BOOTONCE flags. 123213136Spjd */ 124213136Spjd for (; curent < gpthdr->hdr_entries; curent++) { 125213136Spjd ent = &gpttable[curent]; 126213136Spjd if (bcmp(&ent->ent_type, uuid, sizeof(uuid_t)) != 0) 127213136Spjd continue; 128213136Spjd if (!(ent->ent_attr & GPT_ENT_ATTR_BOOTME)) 129213136Spjd continue; 130213136Spjd if (!(ent->ent_attr & GPT_ENT_ATTR_BOOTONCE)) 131213136Spjd continue; 132213136Spjd /* Ok, found one. */ 133213136Spjd goto found; 134213136Spjd } 135213136Spjd bootonce = 0; 136213136Spjd curent = 0; 137213136Spjd } 138213136Spjd for (; curent < gpthdr->hdr_entries; curent++) { 139213136Spjd ent = &gpttable[curent]; 140213136Spjd if (bcmp(&ent->ent_type, uuid, sizeof(uuid_t)) != 0) 141213136Spjd continue; 142213136Spjd if (!(ent->ent_attr & GPT_ENT_ATTR_BOOTME)) 143213136Spjd continue; 144213136Spjd if (ent->ent_attr & GPT_ENT_ATTR_BOOTONCE) 145213136Spjd continue; 146213136Spjd /* Ok, found one. */ 147213136Spjd goto found; 148213136Spjd } 149213136Spjd if (firsttry) { 150213136Spjd /* 151213136Spjd * No partition with BOOTME flag was found, try to boot from 152213136Spjd * first UFS partition. 153213136Spjd */ 154213136Spjd for (curent = 0; curent < gpthdr->hdr_entries; curent++) { 155213136Spjd ent = &gpttable[curent]; 156213136Spjd if (bcmp(&ent->ent_type, uuid, sizeof(uuid_t)) != 0) 157213136Spjd continue; 158213136Spjd /* Ok, found one. */ 159213136Spjd goto found; 160213136Spjd } 161213136Spjd } 162213136Spjd return (-1); 163213136Spjdfound: 164213136Spjd dskp->part = curent + 1; 165213136Spjd ent = &gpttable[curent]; 166213136Spjd dskp->start = ent->ent_lba_start; 167213136Spjd if (ent->ent_attr & GPT_ENT_ATTR_BOOTONCE) { 168213136Spjd /* 169213136Spjd * Clear BOOTME, but leave BOOTONCE set before trying to 170213136Spjd * boot from this partition. 171213136Spjd */ 172213136Spjd if (hdr_primary_lba > 0) { 173213136Spjd table_primary[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTME; 174213136Spjd gptupdate("primary", dskp, &hdr_primary, table_primary); 175213136Spjd } 176213136Spjd if (hdr_backup_lba > 0) { 177213136Spjd table_backup[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTME; 178213136Spjd gptupdate("backup", dskp, &hdr_backup, table_backup); 179213136Spjd } 180213136Spjd } 181213136Spjd return (0); 182213136Spjd} 183213136Spjd 184213136Spjdstatic int 185213136Spjdgptread_hdr(const char *which, struct dsk *dskp, struct gpt_hdr *hdr, 186213136Spjd uint64_t hdrlba) 187213136Spjd{ 188213136Spjd uint32_t crc; 189213136Spjd 190213136Spjd if (drvread(dskp, secbuf, hdrlba, 1)) { 191213136Spjd printf("%s: unable to read %s GPT header\n", BOOTPROG, which); 192213136Spjd return (-1); 193213136Spjd } 194219083Spjd bcopy(secbuf, hdr, sizeof(*hdr)); 195213136Spjd if (bcmp(hdr->hdr_sig, GPT_HDR_SIG, sizeof(hdr->hdr_sig)) != 0 || 196213136Spjd hdr->hdr_lba_self != hdrlba || hdr->hdr_revision < 0x00010000 || 197213136Spjd hdr->hdr_entsz < sizeof(struct gpt_ent) || 198213136Spjd hdr->hdr_entries > MAXTBLENTS || DEV_BSIZE % hdr->hdr_entsz != 0) { 199213136Spjd printf("%s: invalid %s GPT header\n", BOOTPROG, which); 200213136Spjd return (-1); 201213136Spjd } 202213136Spjd crc = hdr->hdr_crc_self; 203213136Spjd hdr->hdr_crc_self = 0; 204213136Spjd if (crc32(hdr, hdr->hdr_size) != crc) { 205213136Spjd printf("%s: %s GPT header checksum mismatch\n", BOOTPROG, 206213136Spjd which); 207213136Spjd return (-1); 208213136Spjd } 209213136Spjd hdr->hdr_crc_self = crc; 210213136Spjd return (0); 211213136Spjd} 212213136Spjd 213213136Spjdvoid 214213136Spjdgptbootfailed(struct dsk *dskp) 215213136Spjd{ 216213136Spjd 217213136Spjd if (!(gpttable[curent].ent_attr & GPT_ENT_ATTR_BOOTONCE)) 218213136Spjd return; 219213136Spjd 220213136Spjd if (hdr_primary_lba > 0) { 221213136Spjd table_primary[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTONCE; 222213136Spjd table_primary[curent].ent_attr |= GPT_ENT_ATTR_BOOTFAILED; 223213136Spjd gptupdate("primary", dskp, &hdr_primary, table_primary); 224213136Spjd } 225213136Spjd if (hdr_backup_lba > 0) { 226213136Spjd table_backup[curent].ent_attr &= ~GPT_ENT_ATTR_BOOTONCE; 227213136Spjd table_backup[curent].ent_attr |= GPT_ENT_ATTR_BOOTFAILED; 228213136Spjd gptupdate("backup", dskp, &hdr_backup, table_backup); 229213136Spjd } 230213136Spjd} 231213136Spjd 232213136Spjdstatic void 233213136Spjdgptbootconv(const char *which, struct dsk *dskp, struct gpt_hdr *hdr, 234213136Spjd struct gpt_ent *table) 235213136Spjd{ 236213136Spjd struct gpt_ent *ent; 237213136Spjd daddr_t slba; 238213136Spjd int table_updated, sector_updated; 239213136Spjd int entries_per_sec, nent, part; 240213136Spjd 241213136Spjd table_updated = 0; 242213136Spjd entries_per_sec = DEV_BSIZE / hdr->hdr_entsz; 243213136Spjd for (nent = 0, slba = hdr->hdr_lba_table; 244213136Spjd slba < hdr->hdr_lba_table + hdr->hdr_entries / entries_per_sec; 245213136Spjd slba++, nent += entries_per_sec) { 246213136Spjd sector_updated = 0; 247213136Spjd for (part = 0; part < entries_per_sec; part++) { 248213136Spjd ent = &table[nent + part]; 249213136Spjd if ((ent->ent_attr & (GPT_ENT_ATTR_BOOTME | 250213136Spjd GPT_ENT_ATTR_BOOTONCE | 251213136Spjd GPT_ENT_ATTR_BOOTFAILED)) != 252213136Spjd GPT_ENT_ATTR_BOOTONCE) { 253213136Spjd continue; 254213136Spjd } 255213136Spjd ent->ent_attr &= ~GPT_ENT_ATTR_BOOTONCE; 256213136Spjd ent->ent_attr |= GPT_ENT_ATTR_BOOTFAILED; 257213136Spjd table_updated = 1; 258213136Spjd sector_updated = 1; 259213136Spjd } 260213136Spjd if (!sector_updated) 261213136Spjd continue; 262219083Spjd bcopy(&table[nent], secbuf, DEV_BSIZE); 263213136Spjd if (drvwrite(dskp, secbuf, slba, 1)) { 264213136Spjd printf("%s: unable to update %s GPT partition table\n", 265213136Spjd BOOTPROG, which); 266213136Spjd } 267213136Spjd } 268213136Spjd if (!table_updated) 269213136Spjd return; 270213136Spjd hdr->hdr_crc_table = crc32(table, hdr->hdr_entries * hdr->hdr_entsz); 271213136Spjd hdr->hdr_crc_self = 0; 272213136Spjd hdr->hdr_crc_self = crc32(hdr, hdr->hdr_size); 273213136Spjd bzero(secbuf, DEV_BSIZE); 274219083Spjd bcopy(hdr, secbuf, hdr->hdr_size); 275213136Spjd if (drvwrite(dskp, secbuf, hdr->hdr_lba_self, 1)) 276213136Spjd printf("%s: unable to update %s GPT header\n", BOOTPROG, which); 277213136Spjd} 278213136Spjd 279213136Spjdstatic int 280213136Spjdgptread_table(const char *which, const uuid_t *uuid, struct dsk *dskp, 281213136Spjd struct gpt_hdr *hdr, struct gpt_ent *table) 282213136Spjd{ 283213136Spjd struct gpt_ent *ent; 284213136Spjd int entries_per_sec; 285213136Spjd int part, nent; 286213136Spjd daddr_t slba; 287213136Spjd 288213136Spjd if (hdr->hdr_entries == 0) 289213136Spjd return (0); 290213136Spjd 291213136Spjd entries_per_sec = DEV_BSIZE / hdr->hdr_entsz; 292213136Spjd slba = hdr->hdr_lba_table; 293213136Spjd nent = 0; 294213136Spjd for (;;) { 295213136Spjd if (drvread(dskp, secbuf, slba, 1)) { 296213136Spjd printf("%s: unable to read %s GPT partition table\n", 297213136Spjd BOOTPROG, which); 298213136Spjd return (-1); 299213136Spjd } 300213136Spjd ent = (struct gpt_ent *)secbuf; 301213136Spjd for (part = 0; part < entries_per_sec; part++, ent++) { 302219083Spjd bcopy(ent, &table[nent], sizeof(table[nent])); 303213136Spjd if (++nent >= hdr->hdr_entries) 304213136Spjd break; 305213136Spjd } 306213136Spjd if (nent >= hdr->hdr_entries) 307213136Spjd break; 308213136Spjd slba++; 309213136Spjd } 310213136Spjd if (crc32(table, nent * hdr->hdr_entsz) != hdr->hdr_crc_table) { 311213136Spjd printf("%s: %s GPT table checksum mismatch\n", BOOTPROG, which); 312213136Spjd return (-1); 313213136Spjd } 314213136Spjd return (0); 315213136Spjd} 316213136Spjd 317213136Spjdint 318213136Spjdgptread(const uuid_t *uuid, struct dsk *dskp, char *buf) 319213136Spjd{ 320213136Spjd uint64_t altlba; 321213136Spjd 322213136Spjd /* 323213136Spjd * Read and verify both GPT headers: primary and backup. 324213136Spjd */ 325213136Spjd 326213136Spjd secbuf = buf; 327213136Spjd hdr_primary_lba = hdr_backup_lba = 0; 328213136Spjd curent = -1; 329213136Spjd bootonce = 1; 330213136Spjd dskp->start = 0; 331213136Spjd 332213136Spjd if (gptread_hdr("primary", dskp, &hdr_primary, 1) == 0 && 333213136Spjd gptread_table("primary", uuid, dskp, &hdr_primary, 334213136Spjd table_primary) == 0) { 335213136Spjd hdr_primary_lba = hdr_primary.hdr_lba_self; 336213136Spjd gpthdr = &hdr_primary; 337213136Spjd gpttable = table_primary; 338213136Spjd } 339213136Spjd 340234176Sae if (hdr_primary_lba > 0) { 341213136Spjd /* 342234176Sae * If primary header is valid, we can get backup 343234176Sae * header location from there. 344213136Spjd */ 345213136Spjd altlba = hdr_primary.hdr_lba_alt; 346234176Sae } else { 347234176Sae altlba = drvsize(dskp); 348234176Sae if (altlba > 0) 349234176Sae altlba--; 350213136Spjd } 351213136Spjd if (altlba == 0) 352213136Spjd printf("%s: unable to locate backup GPT header\n", BOOTPROG); 353213136Spjd else if (gptread_hdr("backup", dskp, &hdr_backup, altlba) == 0 && 354213136Spjd gptread_table("backup", uuid, dskp, &hdr_backup, 355213136Spjd table_backup) == 0) { 356213136Spjd hdr_backup_lba = hdr_backup.hdr_lba_self; 357213136Spjd if (hdr_primary_lba == 0) { 358213136Spjd gpthdr = &hdr_backup; 359213136Spjd gpttable = table_backup; 360213136Spjd printf("%s: using backup GPT\n", BOOTPROG); 361213136Spjd } 362213136Spjd } 363213136Spjd 364213136Spjd /* 365213136Spjd * Convert all BOOTONCE without BOOTME flags into BOOTFAILED. 366213136Spjd * BOOTONCE without BOOTME means that we tried to boot from it, 367213136Spjd * but failed after leaving gptboot and machine was rebooted. 368213136Spjd * We don't want to leave partitions marked as BOOTONCE only, 369213136Spjd * because when we boot successfully start-up scripts should 370213136Spjd * find at most one partition with only BOOTONCE flag and this 371213136Spjd * will mean that we booted from that partition. 372213136Spjd */ 373213136Spjd if (hdr_primary_lba != 0) 374213136Spjd gptbootconv("primary", dskp, &hdr_primary, table_primary); 375213136Spjd if (hdr_backup_lba != 0) 376213136Spjd gptbootconv("backup", dskp, &hdr_backup, table_backup); 377213136Spjd 378213136Spjd if (hdr_primary_lba == 0 && hdr_backup_lba == 0) 379213136Spjd return (-1); 380213136Spjd return (0); 381213136Spjd} 382