freebsd-nq/sys/geom/geom_gpt.c
2006-11-01 12:29:49 +00:00

1357 lines
37 KiB
C

/*-
* Copyright (c) 2002, 2005, 2006 Marcel Moolenaar
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/bio.h>
#include <sys/diskmbr.h>
#include <sys/endian.h>
#include <sys/gpt.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/queue.h>
#include <sys/sbuf.h>
#include <sys/systm.h>
#include <sys/uuid.h>
#include <geom/geom.h>
CTASSERT(offsetof(struct gpt_hdr, padding) == 92);
CTASSERT(sizeof(struct gpt_ent) == 128);
#define G_GPT_TRACE(args) /* g_trace args */
/*
* The GEOM GPT class. Nothing fancy...
*/
static g_ctl_req_t g_gpt_ctlreq;
static g_ctl_destroy_geom_t g_gpt_destroy_geom;
static g_taste_t g_gpt_taste;
static g_access_t g_gpt_access;
static g_dumpconf_t g_gpt_dumpconf;
static g_orphan_t g_gpt_orphan;
static g_spoiled_t g_gpt_spoiled;
static g_start_t g_gpt_start;
static struct g_class g_gpt_class = {
.name = "GPT",
.version = G_VERSION,
/* Class methods. */
.ctlreq = g_gpt_ctlreq,
.destroy_geom = g_gpt_destroy_geom,
.taste = g_gpt_taste,
/* Geom methods. */
.access = g_gpt_access,
.dumpconf = g_gpt_dumpconf,
.orphan = g_gpt_orphan,
.spoiled = g_gpt_spoiled,
.start = g_gpt_start,
};
DECLARE_GEOM_CLASS(g_gpt_class, g_gpt);
/*
* The GEOM GPT instance data.
*/
struct g_gpt_part {
LIST_ENTRY(g_gpt_part) parts;
struct g_provider *provider;
off_t offset;
struct gpt_ent ent;
int index;
};
enum gpt_hdr_type {
GPT_HDR_PRIMARY,
GPT_HDR_SECONDARY,
GPT_HDR_COUNT
};
enum gpt_hdr_state {
GPT_HDR_UNKNOWN,
GPT_HDR_MISSING,
GPT_HDR_CORRUPT,
GPT_HDR_INVALID,
GPT_HDR_OK
};
struct g_gpt_softc {
LIST_HEAD(, g_gpt_part) parts;
struct gpt_hdr hdr[GPT_HDR_COUNT];
enum gpt_hdr_state state[GPT_HDR_COUNT];
};
enum g_gpt_ctl {
G_GPT_CTL_NONE,
G_GPT_CTL_ADD,
G_GPT_CTL_CREATE,
G_GPT_CTL_DESTROY,
G_GPT_CTL_MODIFY,
G_GPT_CTL_RECOVER,
G_GPT_CTL_REMOVE
};
static struct uuid g_gpt_freebsd = GPT_ENT_TYPE_FREEBSD;
static struct uuid g_gpt_freebsd_swap = GPT_ENT_TYPE_FREEBSD_SWAP;
static struct uuid g_gpt_linux_swap = GPT_ENT_TYPE_LINUX_SWAP;
static struct uuid g_gpt_unused = GPT_ENT_TYPE_UNUSED;
/*
* Support functions.
*/
static void g_gpt_wither(struct g_geom *, int);
static void
g_gpt_ctl_add(struct gctl_req *req, const char *flags, struct g_geom *gp,
struct uuid *type, uint64_t start, uint64_t end, long entry)
{
char buf[16];
struct g_provider *pp;
struct g_gpt_softc *softc;
struct g_gpt_part *last, *part;
u_int idx;
G_GPT_TRACE((G_T_TOPOLOGY, "%s(%s)", __func__, gp->name));
g_topology_assert();
pp = LIST_FIRST(&gp->consumer)->provider;
softc = gp->softc;
last = NULL;
idx = (entry > 0) ? (u_int)(entry - 1) : 0;
LIST_FOREACH(part, &softc->parts, parts) {
if (part->index == idx) {
idx = part->index + 1;
last = part;
}
if ((start >= part->ent.ent_lba_start &&
start <= part->ent.ent_lba_end) ||
(end >= part->ent.ent_lba_start &&
end <= part->ent.ent_lba_end) ||
(start < part->ent.ent_lba_start &&
end > part->ent.ent_lba_end)) {
gctl_error(req, "%d start/end %jd/%jd", ENOSPC,
(intmax_t)start, (intmax_t)end);
return;
}
}
if (entry > 0 && (long)idx != entry - 1) {
gctl_error(req, "%d entry %ld", EEXIST, entry);
return;
}
snprintf(buf, sizeof(buf), "%u", idx + 1);
gctl_set_param(req, "entry", buf, strlen(buf) + 1);
part = g_malloc(sizeof(struct g_gpt_part), M_WAITOK | M_ZERO);
part->index = idx;
part->offset = start * pp->sectorsize;
if (last == NULL)
LIST_INSERT_HEAD(&softc->parts, part, parts);
else
LIST_INSERT_AFTER(last, part, parts);
part->ent.ent_type = *type;
kern_uuidgen(&part->ent.ent_uuid, 1);
part->ent.ent_lba_start = start;
part->ent.ent_lba_end = end;
/* XXX ent_attr */
/* XXX ent_name */
part->provider = g_new_providerf(gp, "%s%c%d", gp->name,
!memcmp(type, &g_gpt_freebsd, sizeof(struct uuid)) ? 's' : 'p',
idx + 1);
part->provider->index = idx;
part->provider->private = part; /* Close the circle. */
part->provider->mediasize = (end - start + 1) * pp->sectorsize;
part->provider->sectorsize = pp->sectorsize;
part->provider->flags = pp->flags & G_PF_CANDELETE;
if (pp->stripesize > 0) {
part->provider->stripesize = pp->stripesize;
part->provider->stripeoffset =
(pp->stripeoffset + part->offset) % pp->stripesize;
}
g_error_provider(part->provider, 0);
if (bootverbose) {
printf("GEOM: %s: partition ", part->provider->name);
printf_uuid(&part->ent.ent_uuid);
printf(".\n");
}
}
static struct g_geom *
g_gpt_ctl_create(struct gctl_req *req, const char *flags, struct g_class *mp,
struct g_provider *pp, uint32_t entries)
{
struct uuid uuid;
struct g_consumer *cp;
struct g_geom *gp;
struct g_gpt_softc *softc;
struct gpt_hdr *hdr;
uint64_t last;
size_t tblsz;
int error, i;
G_GPT_TRACE((G_T_TOPOLOGY, "%s(%s,%s)", __func__, mp->name, pp->name));
g_topology_assert();
tblsz = (entries * sizeof(struct gpt_ent) + pp->sectorsize - 1) /
pp->sectorsize;
/*
* Sanity-check the size of the provider. This test is very similar
* to the one in g_gpt_taste(). Here we want to make sure that the
* size of the provider is large enough to hold a GPT that has the
* requested number of entries, plus as many available sectors for
* partitions of minimal size. The latter test is not exactly needed
* but it helps keep the table size proportional to the media size.
* Thus, a GPT with 128 entries must at least have 128 sectors of
* usable partition space. Therefore, the absolute minimal size we
* allow is (1 + 2 * (1 + 32) + 128) = 195 sectors. This is more
* restrictive than what g_gpt_taste() requires.
*/
if (pp->sectorsize < 512 ||
pp->sectorsize % sizeof(struct gpt_ent) != 0 ||
pp->mediasize < (3 + 2 * tblsz + entries) * pp->sectorsize) {
gctl_error(req, "%d provider", ENOSPC);
return (NULL);
}
/* We don't nest. See also g_gpt_taste(). */
if (pp->geom->class == &g_gpt_class) {
gctl_error(req, "%d provider", ENODEV);
return (NULL);
}
/* Create a GEOM. */
gp = g_new_geomf(mp, "%s", pp->name);
softc = g_malloc(sizeof(struct g_gpt_softc), M_WAITOK | M_ZERO);
gp->softc = softc;
LIST_INIT(&softc->parts);
cp = g_new_consumer(gp);
error = g_attach(cp, pp);
if (error == 0)
error = g_access(cp, 1, 0, 0);
if (error != 0) {
g_gpt_wither(gp, error);
gctl_error(req, "%d geom '%s'", error, pp->name);
return (NULL);
}
last = (pp->mediasize / pp->sectorsize) - 1;
kern_uuidgen(&uuid, 1);
/* Construct an in-memory GPT. */
for (i = GPT_HDR_PRIMARY; i < GPT_HDR_COUNT; i++) {
hdr = softc->hdr + i;
bcopy(GPT_HDR_SIG, hdr->hdr_sig, sizeof(hdr->hdr_sig));
hdr->hdr_revision = GPT_HDR_REVISION;
hdr->hdr_size = offsetof(struct gpt_hdr, padding);
hdr->hdr_lba_self = (i == GPT_HDR_PRIMARY) ? 1 : last;
hdr->hdr_lba_alt = (i == GPT_HDR_PRIMARY) ? last : 1;
hdr->hdr_lba_start = 2 + tblsz;
hdr->hdr_lba_end = last - (1 + tblsz);
hdr->hdr_uuid = uuid;
hdr->hdr_lba_table = (i == GPT_HDR_PRIMARY) ? 2 : last - tblsz;
hdr->hdr_entries = entries;
hdr->hdr_entsz = sizeof(struct gpt_ent);
softc->state[i] = GPT_HDR_OK;
}
if (0)
goto fail;
if (bootverbose) {
printf("GEOM: %s: GPT ", pp->name);
printf_uuid(&softc->hdr[GPT_HDR_PRIMARY].hdr_uuid);
printf(".\n");
}
g_access(cp, -1, 0, 0);
return (gp);
fail:
g_access(cp, -1, 0, 0);
g_gpt_wither(gp, error);
gctl_error(req, "%d geom '%s'", error, pp->name);
return (NULL);
}
static void
g_gpt_ctl_destroy(struct gctl_req *req, const char *flags, struct g_geom *gp)
{
}
static void
g_gpt_ctl_modify(struct gctl_req *req, const char *flags, struct g_geom *gp,
long entry)
{
}
static void
g_gpt_ctl_recover(struct gctl_req *req, const char *flags, struct g_geom *gp)
{
}
static void
g_gpt_ctl_remove(struct gctl_req *req, const char *flags, struct g_geom *gp,
long entry)
{
struct g_provider *pp;
struct g_gpt_softc *softc;
struct g_gpt_part *part;
G_GPT_TRACE((G_T_TOPOLOGY, "%s(%s)", __func__, gp->name));
g_topology_assert();
softc = gp->softc;
LIST_FOREACH(part, &softc->parts, parts) {
if ((long)part->index == entry - 1)
break;
}
if (part == NULL) {
gctl_error(req, "%d entry %ld", ENOENT, entry);
return;
}
pp = part->provider;
if (pp->acr > 0 || pp->acw > 0 || pp->ace > 0) {
gctl_error(req, "%d", EBUSY);
return;
}
LIST_REMOVE(part, parts);
pp->private = NULL;
g_wither_provider(pp, ENXIO);
g_free(part);
}
static int
g_gpt_has_pmbr(struct g_consumer *cp, int *error)
{
char *buf;
uint8_t *typ;
uint64_t mediasize;
int i, pmbr, parts;
uint32_t dp_start, dp_size;
uint16_t magic;
buf = g_read_data(cp, 0L, cp->provider->sectorsize, error);
if (buf == NULL)
return (0);
pmbr = 0;
*error = 0;
magic = le16toh(*(uint16_t *)(uintptr_t)(buf + DOSMAGICOFFSET));
if (magic != DOSMAGIC)
goto out;
/*
* Check that there are at least one partition of type
* DOSPTYP_PMBR that covers the whole unit.
*/
parts = 0;
mediasize = cp->provider->mediasize / cp->provider->sectorsize;
for (i = 0; i < 4; i++) {
typ = buf + DOSPARTOFF + i * sizeof(struct dos_partition) +
offsetof(struct dos_partition, dp_typ);
if (*typ != 0)
parts++;
if (*typ != DOSPTYP_PMBR)
continue;
bcopy(buf + DOSPARTOFF + i * sizeof(struct dos_partition) +
offsetof(struct dos_partition, dp_start), &dp_start,
sizeof(dp_start));
if (le32toh(dp_start) != 1)
break;
bcopy(buf + DOSPARTOFF + i * sizeof(struct dos_partition) +
offsetof(struct dos_partition, dp_size), &dp_size,
sizeof(dp_size));
if (le32toh(dp_size) != ~0U &&
le32toh(dp_size) != mediasize - 1 &&
/* Catch old FreeBSD bug for backward compatibility. */
le32toh(dp_size) != mediasize)
break;
pmbr = 1;
}
/*
* Treat empty MBRs as PMBRs for increased flexibility. Note that an
* invalid entry of type DOSPTYP_PMBR counts towards the number of
* partitions and will prevent the MBR from being treated as a PMBR.
*/
if (!pmbr && parts == 0)
pmbr = 1;
out:
g_free(buf);
return (pmbr);
}
static void
g_gpt_load_hdr(struct g_gpt_softc *softc, struct g_provider *pp,
enum gpt_hdr_type type, void *buf)
{
struct uuid uuid;
struct gpt_hdr *hdr;
uint64_t lba, last;
uint32_t crc, sz;
softc->state[type] = GPT_HDR_MISSING;
hdr = softc->hdr + type;
bcopy(buf, hdr, sizeof(*hdr));
if (memcmp(hdr->hdr_sig, GPT_HDR_SIG, sizeof(hdr->hdr_sig)) != 0)
return;
softc->state[type] = GPT_HDR_CORRUPT;
sz = le32toh(hdr->hdr_size);
if (sz < 92 || sz > pp->sectorsize)
return;
crc = le32toh(hdr->hdr_crc_self);
hdr->hdr_crc_self = 0;
if (crc32(hdr, sz) != crc)
return;
hdr->hdr_size = sz;
hdr->hdr_crc_self = crc;
softc->state[type] = GPT_HDR_INVALID;
last = (pp->mediasize / pp->sectorsize) - 1;
hdr->hdr_revision = le32toh(hdr->hdr_revision);
if (hdr->hdr_revision < 0x00010000)
return;
hdr->hdr_lba_self = le64toh(hdr->hdr_lba_self);
if (hdr->hdr_lba_self != (type == GPT_HDR_PRIMARY ? 1 : last))
return;
hdr->hdr_lba_alt = le64toh(hdr->hdr_lba_alt);
if (hdr->hdr_lba_alt != (type == GPT_HDR_PRIMARY ? last : 1))
return;
/* Check the managed area. */
hdr->hdr_lba_start = le64toh(hdr->hdr_lba_start);
if (hdr->hdr_lba_start < 2 || hdr->hdr_lba_start >= last)
return;
hdr->hdr_lba_end = le64toh(hdr->hdr_lba_end);
if (hdr->hdr_lba_end < hdr->hdr_lba_start || hdr->hdr_lba_end >= last)
return;
/* Check the table location and size of the table. */
hdr->hdr_entries = le32toh(hdr->hdr_entries);
hdr->hdr_entsz = le32toh(hdr->hdr_entsz);
if (hdr->hdr_entries == 0 || hdr->hdr_entsz < 128 ||
(hdr->hdr_entsz & 7) != 0)
return;
hdr->hdr_lba_table = le64toh(hdr->hdr_lba_table);
if (hdr->hdr_lba_table < 2 || hdr->hdr_lba_table >= last)
return;
if (hdr->hdr_lba_table >= hdr->hdr_lba_start &&
hdr->hdr_lba_table <= hdr->hdr_lba_end)
return;
lba = hdr->hdr_lba_table +
(hdr->hdr_entries * hdr->hdr_entsz + pp->sectorsize - 1) /
pp->sectorsize - 1;
if (lba >= last)
return;
if (lba >= hdr->hdr_lba_start && lba <= hdr->hdr_lba_end)
return;
softc->state[type] = GPT_HDR_OK;
le_uuid_dec(&hdr->hdr_uuid, &uuid);
hdr->hdr_uuid = uuid;
hdr->hdr_crc_table = le32toh(hdr->hdr_crc_table);
}
static void
g_gpt_load_tbl(struct g_geom *gp, struct g_provider *pp, struct gpt_hdr *hdr,
char *tbl)
{
struct uuid uuid;
struct gpt_ent *ent;
struct g_gpt_part *last, *part;
struct g_gpt_softc *softc;
uint64_t part_start, part_end;
unsigned int ch, idx;
softc = gp->softc;
for (idx = 0, last = part = NULL;
idx < hdr->hdr_entries;
idx++, last = part, tbl += hdr->hdr_entsz) {
ent = (struct gpt_ent *)(uintptr_t)tbl;
le_uuid_dec(&ent->ent_type, &uuid);
if (!memcmp(&uuid, &g_gpt_unused, sizeof(struct uuid)))
continue;
part_start = le64toh(ent->ent_lba_start);
part_end = le64toh(ent->ent_lba_end);
if (part_start < hdr->hdr_lba_start || part_start > part_end ||
part_end > hdr->hdr_lba_end) {
printf("GEOM: %s: GPT partition %d is invalid -- "
"ignored.\n", gp->name, idx + 1);
continue;
}
part = g_malloc(sizeof(struct g_gpt_part), M_WAITOK | M_ZERO);
part->index = idx;
part->offset = part_start * pp->sectorsize;
if (last == NULL)
LIST_INSERT_HEAD(&softc->parts, part, parts);
else
LIST_INSERT_AFTER(last, part, parts);
part->ent.ent_type = uuid;
le_uuid_dec(&ent->ent_uuid, &part->ent.ent_uuid);
part->ent.ent_lba_start = part_start;
part->ent.ent_lba_end = part_end;
part->ent.ent_attr = le64toh(ent->ent_attr);
for (ch = 0; ch < sizeof(ent->ent_name)/2; ch++)
part->ent.ent_name[ch] = le16toh(ent->ent_name[ch]);
g_topology_lock();
part->provider = g_new_providerf(gp, "%s%c%d", gp->name,
!memcmp(&uuid, &g_gpt_freebsd, sizeof(struct uuid))
? 's' : 'p', idx + 1);
part->provider->index = idx;
part->provider->private = part; /* Close the circle. */
part->provider->mediasize = (part_end - part_start + 1) *
pp->sectorsize;
part->provider->sectorsize = pp->sectorsize;
part->provider->flags = pp->flags & G_PF_CANDELETE;
if (pp->stripesize > 0) {
part->provider->stripesize = pp->stripesize;
part->provider->stripeoffset =
(pp->stripeoffset + part->offset) % pp->stripesize;
}
g_error_provider(part->provider, 0);
g_topology_unlock();
if (bootverbose) {
printf("GEOM: %s: partition ", part->provider->name);
printf_uuid(&part->ent.ent_uuid);
printf(".\n");
}
}
}
static int
g_gpt_matched_hdrs(struct gpt_hdr *pri, struct gpt_hdr *sec)
{
if (memcmp(&pri->hdr_uuid, &sec->hdr_uuid, sizeof(struct uuid)) != 0)
return (0);
return ((pri->hdr_revision == sec->hdr_revision &&
pri->hdr_size == sec->hdr_size &&
pri->hdr_lba_start == sec->hdr_lba_start &&
pri->hdr_lba_end == sec->hdr_lba_end &&
pri->hdr_entries == sec->hdr_entries &&
pri->hdr_entsz == sec->hdr_entsz &&
pri->hdr_crc_table == sec->hdr_crc_table) ? 1 : 0);
}
static int
g_gpt_tbl_ok(struct gpt_hdr *hdr, char *tbl)
{
size_t sz;
uint32_t crc;
crc = hdr->hdr_crc_table;
sz = hdr->hdr_entries * hdr->hdr_entsz;
return ((crc32(tbl, sz) == crc) ? 1 : 0);
}
static void
g_gpt_to_utf8(struct sbuf *sb, uint16_t *str, size_t len)
{
u_int bo;
uint32_t ch;
uint16_t c;
bo = BYTE_ORDER;
while (len > 0 && *str != 0) {
ch = (bo == BIG_ENDIAN) ? be16toh(*str) : le16toh(*str);
str++, len--;
if ((ch & 0xf800) == 0xd800) {
if (len > 0) {
c = (bo == BIG_ENDIAN) ? be16toh(*str)
: le16toh(*str);
str++, len--;
} else
c = 0xfffd;
if ((ch & 0x400) == 0 && (c & 0xfc00) == 0xdc00) {
ch = ((ch & 0x3ff) << 10) + (c & 0x3ff);
ch += 0x10000;
} else
ch = 0xfffd;
} else if (ch == 0xfffe) { /* BOM (U+FEFF) swapped. */
bo = (bo == BIG_ENDIAN) ? LITTLE_ENDIAN : BIG_ENDIAN;
continue;
} else if (ch == 0xfeff) /* BOM (U+FEFF) unswapped. */
continue;
if (ch < 0x80)
sbuf_printf(sb, "%c", ch);
else if (ch < 0x800)
sbuf_printf(sb, "%c%c", 0xc0 | (ch >> 6),
0x80 | (ch & 0x3f));
else if (ch < 0x10000)
sbuf_printf(sb, "%c%c%c", 0xe0 | (ch >> 12),
0x80 | ((ch >> 6) & 0x3f), 0x80 | (ch & 0x3f));
else if (ch < 0x200000)
sbuf_printf(sb, "%c%c%c%c", 0xf0 | (ch >> 18),
0x80 | ((ch >> 12) & 0x3f),
0x80 | ((ch >> 6) & 0x3f), 0x80 | (ch & 0x3f));
}
}
static void
g_gpt_wither(struct g_geom *gp, int error)
{
struct g_gpt_part *part;
struct g_gpt_softc *softc;
softc = gp->softc;
if (softc != NULL) {
part = LIST_FIRST(&softc->parts);
while (part != NULL) {
LIST_REMOVE(part, parts);
g_free(part);
part = LIST_FIRST(&softc->parts);
}
g_free(softc);
gp->softc = NULL;
}
g_wither_geom(gp, error);
}
/*
* Class methods.
*/
static void
g_gpt_ctlreq(struct gctl_req *req, struct g_class *mp, const char *verb)
{
struct uuid type;
struct g_geom *gp;
struct g_provider *pp;
struct g_gpt_softc *softc;
const char *flags;
char const *s;
uint64_t start, end;
long entry, entries;
enum g_gpt_ctl ctlreq;
int error;
G_GPT_TRACE((G_T_TOPOLOGY, "%s(%s,%s)", __func__, mp->name, verb));
g_topology_assert();
/*
* Improve error reporting by first checking if the verb is
* a valid one. It also allows us to make assumptions down-
* stream about the validity of the verb.
*/
ctlreq = G_GPT_CTL_NONE;
switch (*verb) {
case 'a':
if (!strcmp(verb, "add"))
ctlreq = G_GPT_CTL_ADD;
break;
case 'c':
if (!strcmp(verb, "create"))
ctlreq = G_GPT_CTL_CREATE;
break;
case 'd':
if (!strcmp(verb, "destroy"))
ctlreq = G_GPT_CTL_DESTROY;
break;
case 'm':
if (!strcmp(verb, "modify"))
ctlreq = G_GPT_CTL_MODIFY;
break;
case 'r':
if (!strcmp(verb, "recover"))
ctlreq = G_GPT_CTL_RECOVER;
else if (!strcmp(verb, "remove"))
ctlreq = G_GPT_CTL_REMOVE;
break;
}
if (ctlreq == G_GPT_CTL_NONE) {
gctl_error(req, "%d verb '%s'", EINVAL, verb);
return;
}
/*
* All verbs take an optional flags parameter. The flags parameter
* is a string with each letter an independent flag. Each verb has
* it's own set of valid flags and the meaning of the flags is
* specific to the verb. Typically the presence of a letter (=flag)
* in the string means true and the absence means false.
*/
s = gctl_get_asciiparam(req, "flags");
flags = (s == NULL) ? "" : s;
/*
* Only the create verb takes a provider parameter. Make this a
* special case so that more code sharing is possible for the
* common case.
*/
if (ctlreq == G_GPT_CTL_CREATE) {
/*
* Create a GPT on a pristine disk-like provider.
* Required parameters/attributes:
* provider
* Optional parameters/attributes:
* entries
*/
s = gctl_get_asciiparam(req, "provider");
if (s == NULL) {
gctl_error(req, "%d provider", ENOATTR);
return;
}
pp = g_provider_by_name(s);
if (pp == NULL) {
gctl_error(req, "%d provider '%s'", EINVAL, s);
return;
}
/* Check that there isn't already a GPT on the provider. */
LIST_FOREACH(gp, &mp->geom, geom) {
if (!strcmp(s, gp->name)) {
gctl_error(req, "%d geom '%s'", EEXIST, s);
return;
}
}
s = gctl_get_asciiparam(req, "entries");
if (s != NULL) {
entries = strtol(s, (char **)(uintptr_t)&s, 0);
if (entries < 128 || *s != '\0') {
gctl_error(req, "%d entries %ld", EINVAL,
entries);
return;
}
} else
entries = 128; /* Documented mininum */
gp = g_gpt_ctl_create(req, flags, mp, pp, entries);
return;
}
/*
* All but the create verb, which is handled above, operate on an
* existing GPT geom. The geom parameter is non-optional, so get
* it here first.
*/
s = gctl_get_asciiparam(req, "geom");
if (s == NULL) {
gctl_error(req, "%d geom", ENOATTR);
return;
}
/* Get the GPT geom with the given name. */
LIST_FOREACH(gp, &mp->geom, geom) {
if (!strcmp(s, gp->name))
break;
}
if (gp == NULL) {
gctl_error(req, "%d geom '%s'", EINVAL, s);
return;
}
softc = gp->softc;
/*
* Now handle the verbs that can operate on a downgraded or
* partially corrupted GPT. In particular these are the verbs
* that don't deal with the table entries. We implement the
* policy that all table entry related requests require a
* valid GPT.
*/
if (ctlreq == G_GPT_CTL_DESTROY) {
/*
* Destroy a GPT completely.
*/
g_gpt_ctl_destroy(req, flags, gp);
return;
}
if (ctlreq == G_GPT_CTL_RECOVER) {
/*
* Recover a downgraded GPT.
*/
g_gpt_ctl_recover(req, flags, gp);
return;
}
/*
* Check that the GPT is complete and valid before we make changes
* to the table entries.
*/
if (softc->state[GPT_HDR_PRIMARY] != GPT_HDR_OK ||
softc->state[GPT_HDR_SECONDARY] != GPT_HDR_OK) {
gctl_error(req, "%d geom '%s'", ENXIO, s);
return;
}
/*
* The add verb is the only table entry related verb that doesn't
* require the entry parameter. All other verbs identify the table
* entry by the entry number. Handle the add here.
*/
if (ctlreq == G_GPT_CTL_ADD) {
/*
* Add a partition entry to a GPT.
* Required parameters/attributes:
* type
* start
* end
* Optional parameters/attributes:
* entry (read/write)
* label
*/
s = gctl_get_asciiparam(req, "type");
if (s == NULL) {
gctl_error(req, "%d type", ENOATTR);
return;
}
error = parse_uuid(s, &type);
if (error != 0) {
gctl_error(req, "%d type '%s'", error, s);
return;
}
s = gctl_get_asciiparam(req, "start");
if (s == NULL) {
gctl_error(req, "%d start", ENOATTR);
return;
}
start = strtoq(s, (char **)(uintptr_t)&s, 0);
if (start < softc->hdr[GPT_HDR_PRIMARY].hdr_lba_start ||
start > softc->hdr[GPT_HDR_PRIMARY].hdr_lba_end ||
*s != '\0') {
gctl_error(req, "%d start %jd", EINVAL,
(intmax_t)start);
return;
}
s = gctl_get_asciiparam(req, "end");
if (s == NULL) {
gctl_error(req, "%d end", ENOATTR);
return;
}
end = strtoq(s, (char **)(uintptr_t)&s, 0);
if (end < start ||
end > softc->hdr[GPT_HDR_PRIMARY].hdr_lba_end ||
*s != '\0') {
gctl_error(req, "%d end %jd", EINVAL,
(intmax_t)end);
return;
}
entry = 0;
s = gctl_get_asciiparam(req, "entry");
if (s != NULL && *s != '\0') {
entry = strtol(s, (char **)(uintptr_t)&s, 0);
if (*s != '\0' || entry <= 0 ||
entry > softc->hdr[GPT_HDR_PRIMARY].hdr_entries) {
gctl_error(req, "%d entry %ld", EINVAL, entry);
return;
}
}
g_gpt_ctl_add(req, flags, gp, &type, start, end, entry);
return;
}
/*
* Get the table entry number. Entry numbers run from 1 to the
* number of entries in the table.
*/
s = gctl_get_asciiparam(req, "entry");
if (s == NULL) {
gctl_error(req, "%d entry", ENOATTR);
return;
}
entry = strtol(s, (char **)(uintptr_t)&s, 0);
if (*s != '\0' || entry <= 0 ||
entry > softc->hdr[GPT_HDR_PRIMARY].hdr_entries) {
gctl_error(req, "%d entry %ld", EINVAL, entry);
return;
}
if (ctlreq == G_GPT_CTL_MODIFY) {
/*
* Modify a partition entry.
*/
g_gpt_ctl_modify(req, flags, gp, entry);
return;
}
if (ctlreq == G_GPT_CTL_REMOVE) {
/*
* Remove a partition entry.
*/
g_gpt_ctl_remove(req, flags, gp, entry);
return;
}
}
static int
g_gpt_destroy_geom(struct gctl_req *req, struct g_class *mp,
struct g_geom *gp)
{
G_GPT_TRACE((G_T_TOPOLOGY, "%s(%s,%s)", __func__, mp->name, gp->name));
g_topology_assert();
g_gpt_wither(gp, EINVAL);
return (0);
}
static struct g_geom *
g_gpt_taste(struct g_class *mp, struct g_provider *pp, int insist __unused)
{
struct g_consumer *cp;
struct g_geom *gp;
struct g_gpt_softc *softc;
struct gpt_hdr *hdr;
void *buf;
off_t ofs;
size_t nbytes;
int error;
G_GPT_TRACE((G_T_TOPOLOGY, "%s(%s,%s)", __func__, mp->name, pp->name));
g_topology_assert();
/*
* We don't nest. That is, we disallow nesting a GPT inside a GPT
* partition. We check only for direct nesting. Indirect nesting is
* not easy to determine. If you want, you can therefore nest GPT
* partitions by putting a dummy GEOM in between them. But I didn't
* say that...
*/
if (pp->geom->class == &g_gpt_class)
return (NULL);
/*
* Create a GEOM with consumer and hook it up to the provider.
* With that we become part of the topology. Optain read, write
* and exclusive access to the provider.
*/
gp = g_new_geomf(mp, "%s", pp->name);
softc = g_malloc(sizeof(struct g_gpt_softc), M_WAITOK | M_ZERO);
gp->softc = softc;
LIST_INIT(&softc->parts);
cp = g_new_consumer(gp);
error = g_attach(cp, pp);
if (error == 0)
error = g_access(cp, 1, 0, 0);
if (error != 0) {
g_gpt_wither(gp, error);
return (NULL);
}
g_topology_unlock();
/*
* Now that we have access permissions, we can sanity-check the
* provider. Since the first sector on the provider must be a PMBR
* and a PMBR is 512 bytes large, the sector size must be at least
* 512 bytes. We also require that the sector size is a multiple
* of the GPT entry size (which is 128 bytes). Lastly, since the
* theoretical minimum number of sectors needed by GPT is 6, any
* medium that has less than 6 sectors is never going to be able
* to hold a GPT. The number 6 comes from:
* 1 sector for the PMBR
* 2 sectors for the GPT headers (each 1 sector)
* 2 sectors for the GPT tables (each 1 sector)
* 1 sector for an actual partition
* It's better to catch this pathological case early than behaving
* pathologically later on by panicing...
*/
if (pp->sectorsize < 512 ||
pp->sectorsize % sizeof(struct gpt_ent) != 0 ||
pp->mediasize < 6 * pp->sectorsize)
goto fail;
/*
* Read both the primary and secondary GPT headers. We have all
* the information at our fingertips that way to determine if
* there's a GPT, including whether recovery is appropriate.
*/
buf = g_read_data(cp, pp->sectorsize, pp->sectorsize, &error);
if (buf == NULL)
goto fail;
g_gpt_load_hdr(softc, pp, GPT_HDR_PRIMARY, buf);
g_free(buf);
buf = g_read_data(cp, pp->mediasize - pp->sectorsize, pp->sectorsize,
&error);
if (buf == NULL)
goto fail;
g_gpt_load_hdr(softc, pp, GPT_HDR_SECONDARY, buf);
g_free(buf);
/* Bail out if there are no GPT headers at all. */
if (softc->state[GPT_HDR_PRIMARY] == GPT_HDR_MISSING &&
softc->state[GPT_HDR_SECONDARY] == GPT_HDR_MISSING) {
error = ENXIO; /* Device not configured for GPT. */
goto fail;
}
/*
* We have at least one GPT header (though that one may be corrupt
* or invalid). This disk supposedly has GPT in some shape or form.
* First check that there's a protective MBR. Complain if there
* is none and fail.
*/
if (!g_gpt_has_pmbr(cp, &error)) {
if (error != 0)
goto fail;
printf("GEOM: %s: GPT detected, but no protective MBR.\n",
pp->name);
error = ENXIO;
goto fail;
}
/*
* Now, catch the non-recoverable case where there's no good GPT
* header at all. That is, unrecoverable by us. The user may able
* to fix it up with some magic.
*/
if (softc->state[GPT_HDR_PRIMARY] != GPT_HDR_OK &&
softc->state[GPT_HDR_SECONDARY] != GPT_HDR_OK) {
printf("GEOM: %s: corrupt or invalid GPT detected.\n",
pp->name);
printf("GEOM: %s: GPT rejected -- may not be recoverable.\n",
pp->name);
error = EINVAL; /* No valid GPT header exists. */
goto fail;
}
/*
* Ok, at least one header is good. We can use the GPT. If there's
* a corrupt or invalid header, we'd like to user to know about it.
* Also catch the case where both headers appear to be good but are
* not mirroring each other. We only check superficially for that.
*/
if (softc->state[GPT_HDR_PRIMARY] != GPT_HDR_OK) {
printf("GEOM: %s: the primary GPT header is corrupt or "
"invalid.\n", pp->name);
printf("GEOM: %s: using the secondary instead -- recovery "
"strongly advised.\n", pp->name);
} else if (softc->state[GPT_HDR_SECONDARY] != GPT_HDR_OK) {
printf("GEOM: %s: the secondary GPT header is corrupt or "
"invalid.\n", pp->name);
printf("GEOM: %s: using the primary only -- recovery "
"suggested.\n", pp->name);
} else if (!g_gpt_matched_hdrs(softc->hdr + GPT_HDR_PRIMARY,
softc->hdr + GPT_HDR_SECONDARY)) {
printf("GEOM: %s: the primary and secondary GPT header do "
"not agree.\n", pp->name);
printf("GEOM: %s: GPT rejected -- recovery required.\n",
pp->name);
error = EINVAL; /* No consistent GPT exists. */
goto fail;
}
/* Always prefer the primary header. */
hdr = (softc->state[GPT_HDR_PRIMARY] == GPT_HDR_OK)
? softc->hdr + GPT_HDR_PRIMARY : softc->hdr + GPT_HDR_SECONDARY;
/*
* Now that we've got a GPT header, we have to deal with the table
* itself. Again there's a primary table and a secondary table and
* either or both may be corrupt or invalid. Redundancy is nice,
* but it's a combinatorial pain in the butt.
*/
nbytes = ((hdr->hdr_entries * hdr->hdr_entsz + pp->sectorsize - 1) /
pp->sectorsize) * pp->sectorsize;
ofs = hdr->hdr_lba_table * pp->sectorsize;
buf = g_read_data(cp, ofs, nbytes, &error);
if (buf == NULL)
goto fail;
/*
* If the table is corrupt, check if we can use the other one.
* Complain and bail if not.
*/
if (!g_gpt_tbl_ok(hdr, buf)) {
g_free(buf);
if (hdr != softc->hdr + GPT_HDR_PRIMARY ||
softc->state[GPT_HDR_SECONDARY] != GPT_HDR_OK) {
printf("GEOM: %s: the GPT table is corrupt -- "
"may not be recoverable.\n", pp->name);
goto fail;
}
softc->state[GPT_HDR_PRIMARY] = GPT_HDR_CORRUPT;
hdr = softc->hdr + GPT_HDR_SECONDARY;
ofs = hdr->hdr_lba_table * pp->sectorsize;
buf = g_read_data(cp, ofs, nbytes, &error);
if (buf == NULL)
goto fail;
if (!g_gpt_tbl_ok(hdr, buf)) {
g_free(buf);
printf("GEOM: %s: both primary and secondary GPT "
"tables are corrupt.\n", pp->name);
printf("GEOM: %s: GPT rejected -- may not be "
"recoverable.\n", pp->name);
goto fail;
}
printf("GEOM: %s: the primary GPT table is corrupt.\n",
pp->name);
printf("GEOM: %s: using the secondary table -- recovery "
"strongly advised.\n", pp->name);
}
if (bootverbose) {
printf("GEOM: %s: GPT ", pp->name);
printf_uuid(&hdr->hdr_uuid);
printf(".\n");
}
g_gpt_load_tbl(gp, pp, hdr, buf);
g_free(buf);
g_topology_lock();
g_access(cp, -1, 0, 0);
return (gp);
fail:
g_topology_lock();
g_access(cp, -1, 0, 0);
g_gpt_wither(gp, error);
return (NULL);
}
/*
* Geom methods.
*/
static int
g_gpt_access(struct g_provider *pp, int dr, int dw, int de)
{
struct g_consumer *cp;
G_GPT_TRACE((G_T_ACCESS, "%s(%s,%d,%d,%d)", __func__, pp->name, dr,
dw, de));
cp = LIST_FIRST(&pp->geom->consumer);
/* We always gain write-exclusive access. */
return (g_access(cp, dr, dw, dw + de));
}
static void
g_gpt_dumpconf(struct sbuf *sb, const char *indent, struct g_geom *gp,
struct g_consumer *cp, struct g_provider *pp)
{
static char *status[5] = {
"unknown", "missing", "corrupt", "invalid", "ok"
};
struct g_gpt_part *part;
struct g_gpt_softc *softc;
struct gpt_hdr *hdr;
KASSERT(sb != NULL && gp != NULL, (__func__));
if (indent == NULL) {
KASSERT(cp == NULL && pp != NULL, (__func__));
part = pp->private;
if (part == NULL)
return;
sbuf_printf(sb, " i %u o %ju ty ", pp->index,
(uintmax_t)part->offset);
sbuf_printf_uuid(sb, &part->ent.ent_type);
} else if (cp != NULL) { /* Consumer configuration. */
KASSERT(pp == NULL, (__func__));
/* none */
} else if (pp != NULL) { /* Provider configuration. */
part = pp->private;
if (part == NULL)
return;
sbuf_printf(sb, "%s<index>%u</index>\n", indent, pp->index);
sbuf_printf(sb, "%s<type>", indent);
sbuf_printf_uuid(sb, &part->ent.ent_type);
sbuf_printf(sb, "</type>\n");
sbuf_printf(sb, "%s<uuid>", indent);
sbuf_printf_uuid(sb, &part->ent.ent_uuid);
sbuf_printf(sb, "</uuid>\n");
sbuf_printf(sb, "%s<offset>%ju</offset>\n", indent,
(uintmax_t)part->offset);
sbuf_printf(sb, "%s<length>%ju</length>\n", indent,
(uintmax_t)pp->mediasize);
sbuf_printf(sb, "%s<attr>%ju</attr>\n", indent,
(uintmax_t)part->ent.ent_attr);
sbuf_printf(sb, "%s<label>", indent);
g_gpt_to_utf8(sb, part->ent.ent_name,
sizeof(part->ent.ent_name)/2);
sbuf_printf(sb, "</label>\n");
} else { /* Geom configuration. */
softc = gp->softc;
hdr = (softc->state[GPT_HDR_PRIMARY] == GPT_HDR_OK)
? softc->hdr + GPT_HDR_PRIMARY
: softc->hdr + GPT_HDR_SECONDARY;
sbuf_printf(sb, "%s<uuid>", indent);
sbuf_printf_uuid(sb, &hdr->hdr_uuid);
sbuf_printf(sb, "</uuid>\n");
sbuf_printf(sb, "%s<primary>%s</primary>\n", indent,
status[softc->state[GPT_HDR_PRIMARY]]);
sbuf_printf(sb, "%s<secondary>%s</secondary>\n", indent,
status[softc->state[GPT_HDR_SECONDARY]]);
sbuf_printf(sb, "%s<selected>%s</selected>\n", indent,
(hdr == softc->hdr + GPT_HDR_PRIMARY) ? "primary" :
"secondary");
sbuf_printf(sb, "%s<revision>%u</revision>\n", indent,
hdr->hdr_revision);
sbuf_printf(sb, "%s<header_size>%u</header_size>\n", indent,
hdr->hdr_size);
sbuf_printf(sb, "%s<crc_self>%u</crc_self>\n", indent,
hdr->hdr_crc_self);
sbuf_printf(sb, "%s<lba_self>%ju</lba_self>\n", indent,
(uintmax_t)hdr->hdr_lba_self);
sbuf_printf(sb, "%s<lba_other>%ju</lba_other>\n", indent,
(uintmax_t)hdr->hdr_lba_alt);
sbuf_printf(sb, "%s<lba_start>%ju</lba_start>\n", indent,
(uintmax_t)hdr->hdr_lba_start);
sbuf_printf(sb, "%s<lba_end>%ju</lba_end>\n", indent,
(uintmax_t)hdr->hdr_lba_end);
sbuf_printf(sb, "%s<lba_table>%ju</lba_table>\n", indent,
(uintmax_t)hdr->hdr_lba_table);
sbuf_printf(sb, "%s<crc_table>%u</crc_table>\n", indent,
hdr->hdr_crc_table);
sbuf_printf(sb, "%s<entries>%u</entries>\n", indent,
hdr->hdr_entries);
sbuf_printf(sb, "%s<entry_size>%u</entry_size>\n", indent,
hdr->hdr_entsz);
}
}
static void
g_gpt_orphan(struct g_consumer *cp)
{
struct g_provider *pp;
pp = cp->provider;
KASSERT(pp != NULL, (__func__));
G_GPT_TRACE((G_T_TOPOLOGY, "%s(%s)", __func__, pp->name));
g_topology_assert();
KASSERT(pp->error != 0, (__func__));
g_gpt_wither(cp->geom, pp->error);
}
static void
g_gpt_spoiled(struct g_consumer *cp)
{
G_GPT_TRACE((G_T_TOPOLOGY, "%s(%s)", __func__, cp->provider->name));
g_topology_assert();
g_gpt_wither(cp->geom, ENXIO);
}
static void
g_gpt_start(struct bio *bp)
{
struct bio *bp2;
struct g_consumer *cp;
struct g_geom *gp;
struct g_gpt_part *part;
struct g_kerneldump *gkd;
struct g_provider *pp;
pp = bp->bio_to;
gp = pp->geom;
cp = LIST_FIRST(&gp->consumer);
G_GPT_TRACE((G_T_BIO, "%s: cmd=%d, provider=%s", __func__, bp->bio_cmd,
pp->name));
part = pp->private;
if (part == NULL) {
g_io_deliver(bp, ENXIO);
return;
}
switch(bp->bio_cmd) {
case BIO_READ:
case BIO_WRITE:
case BIO_DELETE:
if (bp->bio_offset >= pp->mediasize) {
g_io_deliver(bp, EIO);
break;
}
bp2 = g_clone_bio(bp);
if (bp2 == NULL) {
g_io_deliver(bp, ENOMEM);
break;
}
if (bp2->bio_offset + bp2->bio_length > pp->mediasize)
bp2->bio_length = pp->mediasize - bp2->bio_offset;
bp2->bio_done = g_std_done;
bp2->bio_offset += part->offset;
g_io_request(bp2, cp);
break;
case BIO_GETATTR:
if (!strcmp("GEOM::kerneldump", bp->bio_attribute)) {
/*
* Refuse non-swap partitions to be used as kernel
* dumps.
*/
if (memcmp(&part->ent.ent_type, &g_gpt_freebsd_swap,
sizeof(struct uuid)) && memcmp(&part->ent.ent_type,
&g_gpt_linux_swap, sizeof(struct uuid))) {
g_io_deliver(bp, ENXIO);
break;
}
gkd = (struct g_kerneldump *)bp->bio_data;
if (gkd->offset >= pp->mediasize) {
g_io_deliver(bp, EIO);
break;
}
if (gkd->offset + gkd->length > pp->mediasize)
gkd->length = pp->mediasize - gkd->offset;
gkd->offset += part->offset;
/* FALLTHROUGH */
}
/* FALLTHROUGH */
case BIO_FLUSH:
bp2 = g_clone_bio(bp);
if (bp2 == NULL) {
g_io_deliver(bp, ENOMEM);
break;
}
bp2->bio_done = g_std_done;
g_io_request(bp2, cp);
break;
default:
g_io_deliver(bp, EOPNOTSUPP);
break;
}
}