5d807a0e1a
Reviewed by: pjd
353 lines
11 KiB
C
353 lines
11 KiB
C
/*-
|
|
* Copyright (c) 2004, 2005, 2007 Lukas Ertl
|
|
* 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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/types.h>
|
|
#include <sys/endian.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/sbuf.h>
|
|
#include <sys/systm.h>
|
|
|
|
#include <geom/geom.h>
|
|
#include <geom/vinum/geom_vinum_var.h>
|
|
#include <geom/vinum/geom_vinum.h>
|
|
|
|
#define GV_LEGACY_I386 0
|
|
#define GV_LEGACY_AMD64 1
|
|
#define GV_LEGACY_SPARC64 2
|
|
#define GV_LEGACY_POWERPC 3
|
|
|
|
static int gv_legacy_header_type(uint8_t *, int);
|
|
|
|
/*
|
|
* Here are the "offset (size)" for the various struct gv_hdr fields,
|
|
* for the legacy i386 (or 32-bit powerpc), legacy amd64 (or sparc64), and
|
|
* current (cpu & endian agnostic) versions of the on-disk format of the vinum
|
|
* header structure:
|
|
*
|
|
* i386 amd64 current field
|
|
* -------- -------- -------- -----
|
|
* 0 ( 8) 0 ( 8) 0 ( 8) magic
|
|
* 8 ( 4) 8 ( 8) 8 ( 8) config_length
|
|
* 12 (32) 16 (32) 16 (32) label.sysname
|
|
* 44 (32) 48 (32) 48 (32) label.name
|
|
* 76 ( 4) 80 ( 8) 80 ( 8) label.date_of_birth.tv_sec
|
|
* 80 ( 4) 88 ( 8) 88 ( 8) label.date_of_birth.tv_usec
|
|
* 84 ( 4) 96 ( 8) 96 ( 8) label.last_update.tv_sec
|
|
* 88 ( 4) 104 ( 8) 104 ( 8) label.last_update.tv_usec
|
|
* 92 ( 8) 112 ( 8) 112 ( 8) label.drive_size
|
|
* ======== ======== ========
|
|
* 100 120 120 total size
|
|
*
|
|
* NOTE: i386 and amd64 formats are stored as little-endian; the current
|
|
* format uses big-endian (network order).
|
|
*/
|
|
|
|
|
|
/* Checks for legacy format depending on platform. */
|
|
static int
|
|
gv_legacy_header_type(uint8_t *hdr, int bigendian)
|
|
{
|
|
uint32_t *i32;
|
|
int arch_32, arch_64, i;
|
|
|
|
/* Set arch according to endianess. */
|
|
if (bigendian) {
|
|
arch_32 = GV_LEGACY_POWERPC;
|
|
arch_64 = GV_LEGACY_SPARC64;
|
|
} else {
|
|
arch_32 = GV_LEGACY_I386;
|
|
arch_64 = GV_LEGACY_AMD64;
|
|
}
|
|
|
|
/* if non-empty hostname overlaps 64-bit config_length */
|
|
i32 = (uint32_t *)(hdr + 12);
|
|
if (*i32 != 0)
|
|
return (arch_32);
|
|
/* check for non-empty hostname */
|
|
if (hdr[16] != 0)
|
|
return (arch_64);
|
|
/* check bytes past 32-bit structure */
|
|
for (i = 100; i < 120; i++)
|
|
if (hdr[i] != 0)
|
|
return (arch_32);
|
|
/* check for overlapping timestamp */
|
|
i32 = (uint32_t *)(hdr + 84);
|
|
|
|
if (*i32 == 0)
|
|
return (arch_64);
|
|
return (arch_32);
|
|
}
|
|
|
|
/*
|
|
* Read the header while taking magic number into account, and write it to
|
|
* destination pointer.
|
|
*/
|
|
int
|
|
gv_read_header(struct g_consumer *cp, struct gv_hdr *m_hdr)
|
|
{
|
|
struct g_provider *pp;
|
|
uint64_t magic_machdep;
|
|
uint8_t *d_hdr;
|
|
int be, off;
|
|
|
|
#define GV_GET32(endian) \
|
|
endian##32toh(*((uint32_t *)&d_hdr[off])); \
|
|
off += 4
|
|
#define GV_GET64(endian) \
|
|
endian##64toh(*((uint64_t *)&d_hdr[off])); \
|
|
off += 8
|
|
|
|
KASSERT(m_hdr != NULL, ("gv_read_header: null m_hdr"));
|
|
KASSERT(cp != NULL, ("gv_read_header: null cp"));
|
|
pp = cp->provider;
|
|
KASSERT(pp != NULL, ("gv_read_header: null pp"));
|
|
|
|
if ((GV_HDR_OFFSET % pp->sectorsize) != 0 ||
|
|
(GV_HDR_LEN % pp->sectorsize) != 0)
|
|
return (ENODEV);
|
|
|
|
d_hdr = g_read_data(cp, GV_HDR_OFFSET, pp->sectorsize, NULL);
|
|
if (d_hdr == NULL)
|
|
return (-1);
|
|
off = 0;
|
|
m_hdr->magic = GV_GET64(be);
|
|
magic_machdep = *((uint64_t *)&d_hdr[0]);
|
|
/*
|
|
* The big endian machines will have a reverse of GV_OLD_MAGIC, so we
|
|
* need to decide if we are running on a big endian machine as well as
|
|
* checking the magic against the reverse of GV_OLD_MAGIC.
|
|
*/
|
|
be = (m_hdr->magic == magic_machdep);
|
|
if (m_hdr->magic == GV_MAGIC) {
|
|
m_hdr->config_length = GV_GET64(be);
|
|
off = 16;
|
|
bcopy(d_hdr + off, m_hdr->label.sysname, GV_HOSTNAME_LEN);
|
|
off += GV_HOSTNAME_LEN;
|
|
bcopy(d_hdr + off, m_hdr->label.name, GV_MAXDRIVENAME);
|
|
off += GV_MAXDRIVENAME;
|
|
m_hdr->label.date_of_birth.tv_sec = GV_GET64(be);
|
|
m_hdr->label.date_of_birth.tv_usec = GV_GET64(be);
|
|
m_hdr->label.last_update.tv_sec = GV_GET64(be);
|
|
m_hdr->label.last_update.tv_usec = GV_GET64(be);
|
|
m_hdr->label.drive_size = GV_GET64(be);
|
|
} else if (m_hdr->magic != GV_OLD_MAGIC &&
|
|
m_hdr->magic != le64toh(GV_OLD_MAGIC)) {
|
|
/* Not a gvinum drive. */
|
|
g_free(d_hdr);
|
|
return (-1);
|
|
} else if (gv_legacy_header_type(d_hdr, be) == GV_LEGACY_SPARC64) {
|
|
G_VINUM_DEBUG(1, "detected legacy sparc64 header");
|
|
m_hdr->magic = GV_MAGIC;
|
|
/* Legacy sparc64 on-disk header */
|
|
m_hdr->config_length = GV_GET64(be);
|
|
bcopy(d_hdr + 16, m_hdr->label.sysname, GV_HOSTNAME_LEN);
|
|
off += GV_HOSTNAME_LEN;
|
|
bcopy(d_hdr + 48, m_hdr->label.name, GV_MAXDRIVENAME);
|
|
off += GV_MAXDRIVENAME;
|
|
m_hdr->label.date_of_birth.tv_sec = GV_GET64(be);
|
|
m_hdr->label.date_of_birth.tv_usec = GV_GET64(be);
|
|
m_hdr->label.last_update.tv_sec = GV_GET64(be);
|
|
m_hdr->label.last_update.tv_usec = GV_GET64(be);
|
|
m_hdr->label.drive_size = GV_GET64(be);
|
|
} else if (gv_legacy_header_type(d_hdr, be) == GV_LEGACY_POWERPC) {
|
|
G_VINUM_DEBUG(1, "detected legacy PowerPC header");
|
|
m_hdr->magic = GV_MAGIC;
|
|
/* legacy 32-bit big endian on-disk header */
|
|
m_hdr->config_length = GV_GET32(be);
|
|
bcopy(d_hdr + off, m_hdr->label.sysname, GV_HOSTNAME_LEN);
|
|
off += GV_HOSTNAME_LEN;
|
|
bcopy(d_hdr + off, m_hdr->label.name, GV_MAXDRIVENAME);
|
|
off += GV_MAXDRIVENAME;
|
|
m_hdr->label.date_of_birth.tv_sec = GV_GET32(be);
|
|
m_hdr->label.date_of_birth.tv_usec = GV_GET32(be);
|
|
m_hdr->label.last_update.tv_sec = GV_GET32(be);
|
|
m_hdr->label.last_update.tv_usec = GV_GET32(be);
|
|
m_hdr->label.drive_size = GV_GET64(be);
|
|
} else if (gv_legacy_header_type(d_hdr, be) == GV_LEGACY_I386) {
|
|
G_VINUM_DEBUG(1, "detected legacy i386 header");
|
|
m_hdr->magic = GV_MAGIC;
|
|
/* legacy i386 on-disk header */
|
|
m_hdr->config_length = GV_GET32(le);
|
|
bcopy(d_hdr + off, m_hdr->label.sysname, GV_HOSTNAME_LEN);
|
|
off += GV_HOSTNAME_LEN;
|
|
bcopy(d_hdr + off, m_hdr->label.name, GV_MAXDRIVENAME);
|
|
off += GV_MAXDRIVENAME;
|
|
m_hdr->label.date_of_birth.tv_sec = GV_GET32(le);
|
|
m_hdr->label.date_of_birth.tv_usec = GV_GET32(le);
|
|
m_hdr->label.last_update.tv_sec = GV_GET32(le);
|
|
m_hdr->label.last_update.tv_usec = GV_GET32(le);
|
|
m_hdr->label.drive_size = GV_GET64(le);
|
|
} else {
|
|
G_VINUM_DEBUG(1, "detected legacy amd64 header");
|
|
m_hdr->magic = GV_MAGIC;
|
|
/* legacy amd64 on-disk header */
|
|
m_hdr->config_length = GV_GET64(le);
|
|
bcopy(d_hdr + 16, m_hdr->label.sysname, GV_HOSTNAME_LEN);
|
|
off += GV_HOSTNAME_LEN;
|
|
bcopy(d_hdr + 48, m_hdr->label.name, GV_MAXDRIVENAME);
|
|
off += GV_MAXDRIVENAME;
|
|
m_hdr->label.date_of_birth.tv_sec = GV_GET64(le);
|
|
m_hdr->label.date_of_birth.tv_usec = GV_GET64(le);
|
|
m_hdr->label.last_update.tv_sec = GV_GET64(le);
|
|
m_hdr->label.last_update.tv_usec = GV_GET64(le);
|
|
m_hdr->label.drive_size = GV_GET64(le);
|
|
}
|
|
|
|
g_free(d_hdr);
|
|
return (0);
|
|
}
|
|
|
|
/* Write out the gvinum header. */
|
|
int
|
|
gv_write_header(struct g_consumer *cp, struct gv_hdr *m_hdr)
|
|
{
|
|
uint8_t d_hdr[GV_HDR_LEN];
|
|
int off, ret;
|
|
|
|
#define GV_SET64BE(field) \
|
|
do { \
|
|
*((uint64_t *)&d_hdr[off]) = htobe64(field); \
|
|
off += 8; \
|
|
} while (0)
|
|
|
|
KASSERT(m_hdr != NULL, ("gv_write_header: null m_hdr"));
|
|
|
|
off = 0;
|
|
memset(d_hdr, 0, GV_HDR_LEN);
|
|
GV_SET64BE(m_hdr->magic);
|
|
GV_SET64BE(m_hdr->config_length);
|
|
off = 16;
|
|
bcopy(m_hdr->label.sysname, d_hdr + off, GV_HOSTNAME_LEN);
|
|
off += GV_HOSTNAME_LEN;
|
|
bcopy(m_hdr->label.name, d_hdr + off, GV_MAXDRIVENAME);
|
|
off += GV_MAXDRIVENAME;
|
|
GV_SET64BE(m_hdr->label.date_of_birth.tv_sec);
|
|
GV_SET64BE(m_hdr->label.date_of_birth.tv_usec);
|
|
GV_SET64BE(m_hdr->label.last_update.tv_sec);
|
|
GV_SET64BE(m_hdr->label.last_update.tv_usec);
|
|
GV_SET64BE(m_hdr->label.drive_size);
|
|
|
|
ret = g_write_data(cp, GV_HDR_OFFSET, d_hdr, GV_HDR_LEN);
|
|
return (ret);
|
|
}
|
|
|
|
/* Save the vinum configuration back to each involved disk. */
|
|
void
|
|
gv_save_config(struct gv_softc *sc)
|
|
{
|
|
struct g_consumer *cp;
|
|
struct gv_drive *d;
|
|
struct gv_hdr *vhdr, *hdr;
|
|
struct sbuf *sb;
|
|
struct timeval last_update;
|
|
int error;
|
|
|
|
KASSERT(sc != NULL, ("gv_save_config: null sc"));
|
|
|
|
vhdr = g_malloc(GV_HDR_LEN, M_WAITOK | M_ZERO);
|
|
vhdr->magic = GV_MAGIC;
|
|
vhdr->config_length = GV_CFG_LEN;
|
|
microtime(&last_update);
|
|
|
|
sb = sbuf_new(NULL, NULL, GV_CFG_LEN, SBUF_FIXEDLEN);
|
|
gv_format_config(sc, sb, 1, NULL);
|
|
sbuf_finish(sb);
|
|
|
|
LIST_FOREACH(d, &sc->drives, drive) {
|
|
/*
|
|
* We can't save the config on a drive that isn't up, but
|
|
* drives that were just created aren't officially up yet, so
|
|
* we check a special flag.
|
|
*/
|
|
if (d->state != GV_DRIVE_UP)
|
|
continue;
|
|
|
|
cp = d->consumer;
|
|
if (cp == NULL) {
|
|
G_VINUM_DEBUG(0, "drive '%s' has no consumer!",
|
|
d->name);
|
|
continue;
|
|
}
|
|
|
|
hdr = d->hdr;
|
|
if (hdr == NULL) {
|
|
G_VINUM_DEBUG(0, "drive '%s' has no header",
|
|
d->name);
|
|
g_free(vhdr);
|
|
continue;
|
|
}
|
|
bcopy(&last_update, &hdr->label.last_update,
|
|
sizeof(struct timeval));
|
|
bcopy(&hdr->label, &vhdr->label, sizeof(struct gv_label));
|
|
g_topology_lock();
|
|
error = g_access(cp, 0, 1, 0);
|
|
if (error) {
|
|
G_VINUM_DEBUG(0, "g_access failed on "
|
|
"drive %s, errno %d", d->name, error);
|
|
g_topology_unlock();
|
|
continue;
|
|
}
|
|
g_topology_unlock();
|
|
|
|
error = gv_write_header(cp, vhdr);
|
|
if (error) {
|
|
G_VINUM_DEBUG(0, "writing vhdr failed on drive %s, "
|
|
"errno %d", d->name, error);
|
|
g_topology_lock();
|
|
g_access(cp, 0, -1, 0);
|
|
g_topology_unlock();
|
|
continue;
|
|
}
|
|
/* First config copy. */
|
|
error = g_write_data(cp, GV_CFG_OFFSET, sbuf_data(sb),
|
|
GV_CFG_LEN);
|
|
if (error) {
|
|
G_VINUM_DEBUG(0, "writing first config copy failed on "
|
|
"drive %s, errno %d", d->name, error);
|
|
g_topology_lock();
|
|
g_access(cp, 0, -1, 0);
|
|
g_topology_unlock();
|
|
continue;
|
|
}
|
|
/* Second config copy. */
|
|
error = g_write_data(cp, GV_CFG_OFFSET + GV_CFG_LEN,
|
|
sbuf_data(sb), GV_CFG_LEN);
|
|
if (error)
|
|
G_VINUM_DEBUG(0, "writing second config copy failed on "
|
|
"drive %s, errno %d", d->name, error);
|
|
|
|
g_topology_lock();
|
|
g_access(cp, 0, -1, 0);
|
|
g_topology_unlock();
|
|
}
|
|
|
|
sbuf_delete(sb);
|
|
g_free(vhdr);
|
|
}
|