/*- * Copyright (c) 2011 Andrey V. Elsukov * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include "geom_vdi.h" FEATURE(geom_vdi, "GEOM Virtual Box Images read-only support"); MALLOC_DEFINE(M_GEOM_VDI, "geom_vdi", "GEOM VDI data structures"); static g_ctl_destroy_geom_t g_vdi_destroy_geom; static g_access_t g_vdi_access; static g_orphan_t g_vdi_orphan; static g_spoiled_t g_vdi_spoiled; static g_start_t g_vdi_start; static g_taste_t g_vdi_taste; static g_dumpconf_t g_vdi_dumpconf; static struct g_class g_vdi_class = { .name = "VDI", .version = G_VERSION, /* Class methods. */ .destroy_geom = g_vdi_destroy_geom, .taste = g_vdi_taste, /* Geom methods. */ .access = g_vdi_access, .dumpconf = g_vdi_dumpconf, .orphan = g_vdi_orphan, .spoiled = g_vdi_spoiled, .start = g_vdi_start, }; DECLARE_GEOM_CLASS(g_vdi_class, g_vdi); struct g_vdi_softc { uint64_t sc_mediasize; uint32_t sc_blocksize; uint32_t sc_block_extrasize; uint32_t sc_blocks_count; uint32_t sc_blocks_allocated; uint32_t sc_blocks_offset; uint32_t sc_data_offset; uint32_t sc_version; uint32_t sc_type; uint32_t *sc_blocks; }; static void g_vdi_softc_free(struct g_geom *gp) { struct g_vdi_softc *sc; sc = gp->softc; if (sc == NULL) return; gp->softc = NULL; free(sc->sc_blocks, M_GEOM_VDI); free(sc, M_GEOM_VDI); } static int g_vdi_destroy_geom(struct gctl_req *req, struct g_class *mp, struct g_geom *gp) { g_topology_assert(); g_vdi_softc_free(gp); g_wither_geom(gp, ENXIO); return (0); } static void g_vdi_start(struct bio *bp) { TAILQ_HEAD(, bio) queue = TAILQ_HEAD_INITIALIZER(queue); struct g_provider *pp; struct g_vdi_softc *sc; struct g_consumer *cp; struct g_geom *gp; struct bio *cbp; char *addr; uint32_t bidx; off_t offset, length, len; pp = bp->bio_to; gp = pp->geom; if (bp->bio_cmd != BIO_READ) { g_io_deliver(bp, EOPNOTSUPP); return; } cp = LIST_FIRST(&gp->consumer); sc = gp->softc; addr = bp->bio_data; length = bp->bio_length; offset = bp->bio_offset % sc->sc_blocksize; do { bidx = (bp->bio_offset + bp->bio_length - length) / sc->sc_blocksize; if (sc->sc_blocksize - offset > MAXPHYS) len = MIN(length, MAXPHYS); else len = MIN(length, sc->sc_blocksize - offset); if (le32toh(sc->sc_blocks[bidx]) == G_VDI_ZERO_BLOCK) { bp->bio_completed += len; memset(addr, 0, len); } else if (le32toh(sc->sc_blocks[bidx]) == G_VDI_FREE_BLOCK) { bp->bio_completed += len; } else { cbp = g_clone_bio(bp); if (cbp == NULL) goto fail; TAILQ_INSERT_TAIL(&queue, cbp, bio_queue); cbp->bio_to = cp->provider; cbp->bio_done = g_std_done; cbp->bio_data = addr; cbp->bio_length = len; cbp->bio_offset = sc->sc_data_offset + offset + le32toh(sc->sc_blocks[bidx]) * sc->sc_blocksize; } addr += len; length -= len; offset = (offset + len) % sc->sc_blocksize; } while (length > 0); /* XXX: sanity checks needed */ if (TAILQ_EMPTY(&queue)) { g_io_deliver(bp, 0); } else { while ((cbp = TAILQ_FIRST(&queue)) != NULL) { TAILQ_REMOVE(&queue, cbp, bio_queue); g_io_request(cbp, cp); } } return; fail: while ((cbp = TAILQ_FIRST(&queue)) != NULL) { TAILQ_REMOVE(&queue, cbp, bio_queue); bp->bio_children--; g_destroy_bio(cbp); } bp->bio_completed = 0; bp->bio_error = ENOMEM; g_io_deliver(bp, bp->bio_error); } static int g_vdi_access(struct g_provider *pp, int dr, int dw, int de) { struct g_consumer *cp; cp = LIST_FIRST(&pp->geom->consumer); if (cp->acw + dw > 0) return (EROFS); return (g_access(cp, dr, dw, de)); } static void g_vdi_orphan(struct g_consumer *cp) { g_trace(G_T_TOPOLOGY, "%s(%s)", __func__, cp->geom->name); g_topology_assert(); g_vdi_softc_free(cp->geom); g_wither_geom(cp->geom, cp->provider->error); } static void g_vdi_spoiled(struct g_consumer *cp) { g_trace(G_T_TOPOLOGY, "%s(%s)", __func__, cp->geom->name); g_topology_assert(); g_vdi_softc_free(cp->geom); g_wither_geom(cp->geom, ENXIO); } static int g_vdi_read_metadata(struct g_consumer *cp) { struct g_vdi_metadata *md; struct g_vdi_softc *sc; struct g_provider *pp; struct g_geom *gp; char *buf, *p; uint32_t count, size; int error; gp = cp->geom; pp = cp->provider; buf = g_read_data(cp, 0, pp->sectorsize, &error); if (buf == NULL) return (error); md = (struct g_vdi_metadata *)buf; if (le32toh(md->md_signature) != G_VDI_SIGNATURE) goto fail; DEB("valid signature detected.\n"); if (le32toh(md->md_version) != G_VDI_VERSION) { DEB("unsupported version 0x%08x.\n", le32toh(md->md_version)); goto fail; } #define hdr md->md_header if (le32toh(hdr.type) != G_VDI_T_NORMAL && le32toh(hdr.type) != G_VDI_T_FIXED) { DEB("unsupported image type: %u.\n", le32toh(hdr.type)); goto fail; } if (le32toh(hdr.size) < sizeof(struct g_vdi_header)) { DEB("wrong header size (%u < %u)\n", (u_int)le32toh(hdr.size), (u_int)sizeof(struct g_vdi_header)); goto fail; } if (le32toh(hdr.blocks_offset) < sizeof(struct g_vdi_metadata)) { DEB("wrong blocks offset (%u < %u).\n", (u_int)le32toh(hdr.blocks_offset), (u_int)sizeof(struct g_vdi_metadata)); goto fail; } if (le32toh(hdr.data_offset) < le32toh(hdr.blocks_offset) + sizeof(uint32_t) * le32toh(hdr.blocks)) { DEB("wrong data offset (%u < %u).\n", (u_int)le32toh(hdr.data_offset), (u_int)le32toh(hdr.blocks_offset) + (u_int)sizeof(uint32_t) * le32toh(hdr.blocks)); goto fail; } sc = malloc(sizeof(*sc), M_GEOM_VDI, M_WAITOK | M_ZERO); sc->sc_mediasize = le64toh(hdr.mediasize); sc->sc_blocksize = le32toh(hdr.blocksize); sc->sc_block_extrasize = le32toh(hdr.block_extrasize); sc->sc_blocks_count = le32toh(hdr.blocks); sc->sc_blocks_allocated = le32toh(hdr.blocks_allocated); sc->sc_blocks_offset = le32toh(hdr.blocks_offset); sc->sc_data_offset = le32toh(hdr.data_offset); sc->sc_type = le32toh(hdr.type); sc->sc_version = le32toh(md->md_version); error = 0; if (sc->sc_blocks_count == 0) { DEB("invalid blocks count: %u.\n", (u_int)sc->sc_blocks_count); error = EINVAL; } if (sc->sc_blocksize == 0 || sc->sc_blocksize % G_VDI_SECTOR_SIZE != 0) { DEB("invalid blocksize: %u.\n", (u_int)sc->sc_blocksize); free(sc, M_GEOM_VDI); goto fail; } if (sc->sc_mediasize % G_VDI_SECTOR_SIZE != 0) { DEB("invalid mediasize: %ju (not multiple of %u).\n", (uintmax_t)sc->sc_mediasize, G_VDI_SECTOR_SIZE); error = EINVAL; } if (sc->sc_mediasize > (uint64_t)sc->sc_blocks_count * sc->sc_blocksize) { DEB("invalid mediasize: %ju (%u blocks).\n", (uintmax_t)sc->sc_mediasize, (u_int)sc->sc_blocks_count) error = EINVAL; } if (sc->sc_blocks_offset % G_VDI_SECTOR_SIZE) { DEB("invalid blocks offset: %u.\n", (u_int)sc->sc_blocks_offset); error = EINVAL; } if (sc->sc_data_offset % G_VDI_SECTOR_SIZE) { DEB("invalid data offset: %u.\n", (u_int)sc->sc_data_offset); error = EINVAL; } if (error != 0) { free(sc, M_GEOM_VDI); goto fail; } gp->softc = sc; sc->sc_blocks = malloc(sizeof(uint32_t) * sc->sc_blocks_count, M_GEOM_VDI, M_WAITOK); /* Read the blocks map array */ for (count = 0; count < sc->sc_blocks_count; count += MAXPHYS / sizeof(uint32_t)) { if (sc->sc_blocks_count - count > MAXPHYS / sizeof(uint32_t)) size = MAXPHYS; else size = (sc->sc_blocks_count - count) * sizeof(uint32_t); p = g_read_data(cp, hdr.blocks_offset + count * pp->sectorsize, size, &error); if (p == NULL) goto fail2; memcpy((char *)sc->sc_blocks + count * sizeof(uint32_t), p, size); g_free(p); } #undef hdr g_free(buf); return (0); fail2: g_vdi_softc_free(gp); fail: g_free(buf); return (1); } static struct g_geom* g_vdi_taste(struct g_class *mp, struct g_provider *pp, int flags) { struct g_geom *gp; struct g_consumer *cp; struct g_provider *npp; struct g_vdi_softc *sc; int error; g_trace(G_T_TOPOLOGY, "g_vdi_taste(%s,%s)", mp->name, pp->name); g_topology_assert(); if (pp->acw > 0 || pp->sectorsize != G_VDI_SECTOR_SIZE) return (NULL); gp = g_new_geomf(mp, "%s.vdi", pp->name); cp = g_new_consumer(gp); error = g_attach(cp, pp); if (error != 0) { g_destroy_consumer(cp); g_destroy_geom(gp); return (NULL); } error = g_access(cp, 1, 0, 0); if (error != 0) goto fail; g_topology_unlock(); error = g_vdi_read_metadata(cp); g_topology_lock(); if (error != 0) { g_access(cp, -1, 0, 0); goto fail; } sc = (struct g_vdi_softc *)gp->softc; npp = g_new_providerf(gp, "%s", gp->name); npp->sectorsize = G_VDI_SECTOR_SIZE; npp->mediasize = sc->sc_mediasize; npp->flags = pp->flags & G_PF_CANDELETE; #if 0 npp->stripesize = pp->stripesize; npp->stripeoffset = pp->stripeoffset + sc->sc_data_offset; if (npp->stripesize > 0) npp->stripeoffset %= npp->stripesize; #endif g_error_provider(npp, 0); g_access(cp, -1, 0, 0); printf("GEOM_VDI: Provider %s created.\n", npp->name); return (gp); fail: g_detach(cp); g_destroy_consumer(cp); g_destroy_geom(gp); return (NULL); } static void g_vdi_dumpconf(struct sbuf *sb, const char *indent, struct g_geom *gp, struct g_consumer *cp, struct g_provider *pp) { struct g_vdi_softc *sc; if (pp != NULL || cp != NULL) return; sc = gp->softc; sbuf_printf(sb, "%s%jd\n", indent, (intmax_t)sc->sc_mediasize); sbuf_printf(sb, "%s%jd\n", indent, (intmax_t)sc->sc_blocksize); sbuf_printf(sb, "%s%jd\n", indent, (intmax_t)sc->sc_blocks_count); sbuf_printf(sb, "%s%jd\n", indent, (intmax_t)sc->sc_blocks_offset); sbuf_printf(sb, "%s%jd\n", indent, (intmax_t)sc->sc_data_offset); sbuf_printf(sb, "%s%d.%d\n", indent, (int)sc->sc_version >> 16, (int)sc->sc_version & 0xFFFF); sbuf_printf(sb, "%s%s\n", indent, sc->sc_type == G_VDI_T_NORMAL ? "Normal": "Fixed"); }