freebsd-src/lib/geom/virstor/geom_virstor.c
Ryan Libby e754909cb0 virstor: remove relation between chunk size and MAXPHYS
There's no reason why the virstor chunk size needs to relate to MAXPHYS.
Remove it.  Instead, just make sure that the chunk size is a multiple of
the sector size.

Reviewed by:	imp
Sponsored by:	Dell EMC Isilon
Differential Revision:	https://reviews.freebsd.org/D45518
2024-06-10 17:38:17 -07:00

542 lines
15 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2005 Ivan Voras <ivoras@freebsd.org>
*
* 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 AUTHORS 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 AUTHORS 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/param.h>
#include <errno.h>
#include <paths.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <strings.h>
#include <fcntl.h>
#include <unistd.h>
#include <libgeom.h>
#include <err.h>
#include <assert.h>
#include <core/geom.h>
#include <misc/subr.h>
#include <geom/virstor/g_virstor_md.h>
#include <geom/virstor/g_virstor.h>
uint32_t lib_version = G_LIB_VERSION;
uint32_t version = G_VIRSTOR_VERSION;
#define GVIRSTOR_CHUNK_SIZE "4M"
#define GVIRSTOR_VIR_SIZE "2T"
#if G_LIB_VERSION == 1
/* Support RELENG_6 */
#define G_TYPE_BOOL G_TYPE_NONE
#endif
/*
* virstor_main gets called by the geom(8) utility
*/
static void virstor_main(struct gctl_req *req, unsigned flags);
struct g_command class_commands[] = {
{ "clear", G_FLAG_VERBOSE, virstor_main, G_NULL_OPTS,
"[-v] prov ..."
},
{ "dump", 0, virstor_main, G_NULL_OPTS,
"prov ..."
},
{ "label", G_FLAG_VERBOSE | G_FLAG_LOADKLD, virstor_main,
{
{ 'h', "hardcode", NULL, G_TYPE_BOOL},
{ 'm', "chunk_size", GVIRSTOR_CHUNK_SIZE, G_TYPE_NUMBER},
{ 's', "vir_size", GVIRSTOR_VIR_SIZE, G_TYPE_NUMBER},
G_OPT_SENTINEL
},
"[-h] [-v] [-m chunk_size] [-s vir_size] name provider0 [provider1 ...]"
},
{ "destroy", G_FLAG_VERBOSE, NULL,
{
{ 'f', "force", NULL, G_TYPE_BOOL},
G_OPT_SENTINEL
},
"[-fv] name ..."
},
{ "stop", G_FLAG_VERBOSE, NULL,
{
{ 'f', "force", NULL, G_TYPE_BOOL},
G_OPT_SENTINEL
},
"[-fv] name ... (alias for \"destroy\")"
},
{ "add", G_FLAG_VERBOSE, NULL,
{
{ 'h', "hardcode", NULL, G_TYPE_BOOL},
G_OPT_SENTINEL
},
"[-vh] name prov [prov ...]"
},
{ "remove", G_FLAG_VERBOSE, NULL, G_NULL_OPTS,
"[-v] name ..."
},
G_CMD_SENTINEL
};
static int verbose = 0;
/* Helper functions' declarations */
static void virstor_clear(struct gctl_req *req);
static void virstor_dump(struct gctl_req *req);
static void virstor_label(struct gctl_req *req);
/* Dispatcher function (no real work done here, only verbose flag recorder) */
static void
virstor_main(struct gctl_req *req, unsigned flags)
{
const char *name;
if ((flags & G_FLAG_VERBOSE) != 0)
verbose = 1;
name = gctl_get_ascii(req, "verb");
if (name == NULL) {
gctl_error(req, "No '%s' argument.", "verb");
return;
}
if (strcmp(name, "label") == 0)
virstor_label(req);
else if (strcmp(name, "clear") == 0)
virstor_clear(req);
else if (strcmp(name, "dump") == 0)
virstor_dump(req);
else
gctl_error(req, "%s: Unknown command: %s.", __func__, name);
/* No CTASSERT in userland
CTASSERT(VIRSTOR_MAP_BLOCK_ENTRIES*VIRSTOR_MAP_ENTRY_SIZE == MAXPHYS);
*/
}
/*
* Labels a new geom Meaning: parses and checks the parameters, calculates &
* writes metadata to the relevant providers so when the next round of
* "tasting" comes (which will be just after the provider(s) are closed) geom
* can be instantiated with the tasted metadata.
*/
static void
virstor_label(struct gctl_req *req)
{
struct g_virstor_metadata md;
off_t msize;
unsigned char *sect;
unsigned int i;
size_t ssize, secsize;
const char *name;
char param[32];
int hardcode, nargs, error;
struct virstor_map_entry *map;
size_t total_chunks, write_max_map_entries;
unsigned int map_chunks; /* Chunks needed by the map (map size). */
size_t map_size; /* In bytes. */
ssize_t written;
int fd;
nargs = gctl_get_int(req, "nargs");
if (nargs < 2) {
gctl_error(req, "Too few arguments (%d): expecting: name "
"provider0 [provider1 ...]", nargs);
return;
}
hardcode = gctl_get_int(req, "hardcode");
/*
* Initialize constant parts of metadata: magic signature, version,
* name.
*/
bzero(&md, sizeof(md));
strlcpy(md.md_magic, G_VIRSTOR_MAGIC, sizeof(md.md_magic));
md.md_version = G_VIRSTOR_VERSION;
name = gctl_get_ascii(req, "arg0");
if (name == NULL) {
gctl_error(req, "No 'arg%u' argument.", 0);
return;
}
strlcpy(md.md_name, name, sizeof(md.md_name));
md.md_virsize = (off_t)gctl_get_intmax(req, "vir_size");
md.md_chunk_size = gctl_get_intmax(req, "chunk_size");
md.md_count = nargs - 1;
if (md.md_virsize == 0 || md.md_chunk_size == 0) {
gctl_error(req, "Virtual size and chunk size must be non-zero");
return;
}
msize = secsize = 0;
for (i = 1; i < (unsigned)nargs; i++) {
snprintf(param, sizeof(param), "arg%u", i);
name = gctl_get_ascii(req, "%s", param);
ssize = g_get_sectorsize(name);
if (ssize == 0)
fprintf(stderr, "%s for %s\n", strerror(errno), name);
msize += g_get_mediasize(name);
if (secsize == 0)
secsize = ssize;
else if (secsize != ssize) {
gctl_error(req, "Devices need to have same sector size "
"(%u on %s needs to be %u).",
(u_int)ssize, name, (u_int)secsize);
return;
}
}
if (secsize == 0) {
gctl_error(req, "Device not specified");
return;
}
if (md.md_chunk_size % secsize != 0) {
size_t new_size = roundup(md.md_chunk_size, secsize);
fprintf(stderr, "Resizing chunk size to be a multiple of "
"sector size (%zu bytes).\n", secsize);
fprintf(stderr, "New chunk size: %zu kB\n", new_size / 1024);
md.md_chunk_size = new_size;
}
if (md.md_virsize % md.md_chunk_size != 0) {
off_t chunk_count = md.md_virsize / md.md_chunk_size;
md.md_virsize = chunk_count * md.md_chunk_size;
fprintf(stderr, "Resizing virtual size to be a multiple of "
"chunk size.\n");
fprintf(stderr, "New virtual size: %zu MB\n",
(size_t)(md.md_virsize / (1024 * 1024)));
}
total_chunks = md.md_virsize / md.md_chunk_size;
map_size = total_chunks * sizeof(*map);
assert(md.md_virsize % md.md_chunk_size == 0);
ssize = map_size % secsize;
if (ssize != 0) {
size_t add_chunks = (secsize - ssize) / sizeof(*map);
total_chunks += add_chunks;
md.md_virsize = (off_t)total_chunks * (off_t)md.md_chunk_size;
map_size = total_chunks * sizeof(*map);
fprintf(stderr, "Resizing virtual size to fit virstor "
"structures.\n");
fprintf(stderr, "New virtual size: %ju MB (%zu new chunks)\n",
(uintmax_t)(md.md_virsize / (1024 * 1024)), add_chunks);
}
if (verbose)
printf("Total virtual chunks: %zu (%zu MB each), %ju MB total "
"virtual size.\n",
total_chunks, (size_t)(md.md_chunk_size / (1024 * 1024)),
md.md_virsize/(1024 * 1024));
if ((off_t)md.md_virsize < msize)
fprintf(stderr, "WARNING: Virtual storage size < Physical "
"available storage (%ju < %ju)\n", md.md_virsize, msize);
/* Clear last sector first to spoil all components if device exists. */
if (verbose)
printf("Clearing metadata on");
for (i = 1; i < (unsigned)nargs; i++) {
snprintf(param, sizeof(param), "arg%u", i);
name = gctl_get_ascii(req, "%s", param);
if (verbose)
printf(" %s", name);
msize = g_get_mediasize(name);
ssize = g_get_sectorsize(name);
if (msize == 0 || ssize == 0) {
gctl_error(req, "Can't retrieve information about "
"%s: %s.", name, strerror(errno));
return;
}
if (msize < (off_t) MAX(md.md_chunk_size*4, map_size))
gctl_error(req, "Device %s is too small", name);
error = g_metadata_clear(name, NULL);
if (error != 0) {
gctl_error(req, "Can't clear metadata on %s: %s.", name,
strerror(error));
return;
}
}
/* Write allocation table to the first provider - this needs to be done
* before metadata is written because when kernel tastes it it's too
* late */
name = gctl_get_ascii(req, "arg1"); /* device with metadata */
if (verbose)
printf(".\nWriting allocation table to %s...", name);
/* How many chunks does the map occupy? */
map_chunks = map_size/md.md_chunk_size;
if (map_size % md.md_chunk_size != 0)
map_chunks++;
if (verbose) {
printf(" (%zu MB, %d chunks) ", map_size/(1024*1024), map_chunks);
fflush(stdout);
}
if (strncmp(name, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
fd = open(name, O_RDWR);
else {
sprintf(param, "%s%s", _PATH_DEV, name);
fd = open(param, O_RDWR);
}
if (fd < 0) {
gctl_error(req, "Cannot open provider %s to write map", name);
return;
}
/*
* Initialize and write the map. Don't malloc the whole map at once,
* in case it's large. Use calloc because there might be a need to set
* up chunk flags in the future.
*/
write_max_map_entries = 1024 * 1024 / sizeof(*map);
if (write_max_map_entries > total_chunks)
write_max_map_entries = total_chunks;
map = calloc(write_max_map_entries, sizeof(*map));
if (map == NULL) {
gctl_error(req,
"Out of memory (need %zu bytes for allocation map)",
write_max_map_entries * sizeof(*map));
close(fd);
return;
}
for (size_t chunk = 0; chunk < total_chunks;
chunk += write_max_map_entries) {
size_t bytes_to_write, entries_to_write;
entries_to_write = total_chunks - chunk;
if (entries_to_write > write_max_map_entries)
entries_to_write = write_max_map_entries;
bytes_to_write = entries_to_write * sizeof(*map);
for (size_t off = 0; off < bytes_to_write; off += written) {
written = write(fd, ((char *)map) + off,
bytes_to_write - off);
if (written < 0) {
if (verbose) {
fprintf(stderr,
"\nError writing map at offset "
"%zu of %zu: %s\n",
chunk * sizeof(*map) + off,
map_size, strerror(errno));
}
gctl_error(req,
"Error writing out allocation map!");
free(map);
close(fd);
return;
}
}
}
free(map);
map = NULL;
close (fd);
if (verbose)
printf("\nStoring metadata on ");
/*
* ID is randomly generated, unique for a geom. This is used to
* recognize all providers belonging to one geom.
*/
md.md_id = arc4random();
/* Ok, store metadata. */
for (i = 1; i < (unsigned)nargs; i++) {
snprintf(param, sizeof(param), "arg%u", i);
name = gctl_get_ascii(req, "%s", param);
msize = g_get_mediasize(name);
ssize = g_get_sectorsize(name);
if (verbose)
printf("%s ", name);
/* this provider's position/type in geom */
md.no = i - 1;
/* this provider's size */
md.provsize = msize;
/* chunk allocation info */
md.chunk_count = md.provsize / md.md_chunk_size;
if (verbose)
printf("(%u chunks) ", md.chunk_count);
/* Check to make sure last sector is unused */
if ((off_t)(md.chunk_count * md.md_chunk_size) > (off_t)(msize-ssize))
md.chunk_count--;
md.chunk_next = 0;
if (i != 1) {
md.chunk_reserved = 0;
md.flags = 0;
} else {
md.chunk_reserved = map_chunks * 2;
md.flags = VIRSTOR_PROVIDER_ALLOCATED |
VIRSTOR_PROVIDER_CURRENT;
md.chunk_next = md.chunk_reserved;
if (verbose)
printf("(%u reserved) ", md.chunk_reserved);
}
if (!hardcode)
bzero(md.provider, sizeof(md.provider));
else {
/* convert "/dev/something" to "something" */
if (strncmp(name, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0) {
strlcpy(md.provider, name + sizeof(_PATH_DEV) - 1,
sizeof(md.provider));
} else
strlcpy(md.provider, name, sizeof(md.provider));
}
sect = calloc(ssize, sizeof(unsigned char));
if (sect == NULL)
err(1, "Cannot allocate sector of %zu bytes", ssize);
virstor_metadata_encode(&md, sect);
error = g_metadata_store(name, sect, ssize);
free(sect);
if (error != 0) {
if (verbose)
printf("\n");
fprintf(stderr, "Can't store metadata on %s: %s.\n",
name, strerror(error));
gctl_error(req,
"Not fully done (error storing metadata).");
return;
}
}
#if 0
if (verbose)
printf("\n");
#endif
}
/* Clears metadata on given provider(s) IF it's owned by us */
static void
virstor_clear(struct gctl_req *req)
{
const char *name;
char param[32];
unsigned i;
int nargs, error;
int fd;
nargs = gctl_get_int(req, "nargs");
if (nargs < 1) {
gctl_error(req, "Too few arguments.");
return;
}
for (i = 0; i < (unsigned)nargs; i++) {
snprintf(param, sizeof(param), "arg%u", i);
name = gctl_get_ascii(req, "%s", param);
error = g_metadata_clear(name, G_VIRSTOR_MAGIC);
if (error != 0) {
fprintf(stderr, "Can't clear metadata on %s: %s "
"(do I own it?)\n", name, strerror(error));
gctl_error(req,
"Not fully done (can't clear metadata).");
continue;
}
if (strncmp(name, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0)
fd = open(name, O_RDWR);
else {
sprintf(param, "%s%s", _PATH_DEV, name);
fd = open(param, O_RDWR);
}
if (fd < 0) {
gctl_error(req, "Cannot clear header sector for %s",
name);
continue;
}
if (verbose)
printf("Metadata cleared on %s.\n", name);
}
}
/* Print some metadata information */
static void
virstor_metadata_dump(const struct g_virstor_metadata *md)
{
printf(" Magic string: %s\n", md->md_magic);
printf(" Metadata version: %u\n", (u_int) md->md_version);
printf(" Device name: %s\n", md->md_name);
printf(" Device ID: %u\n", (u_int) md->md_id);
printf(" Provider index: %u\n", (u_int) md->no);
printf(" Active providers: %u\n", (u_int) md->md_count);
printf(" Hardcoded provider: %s\n",
md->provider[0] != '\0' ? md->provider : "(not hardcoded)");
printf(" Virtual size: %u MB\n",
(unsigned int)(md->md_virsize/(1024 * 1024)));
printf(" Chunk size: %u kB\n", md->md_chunk_size / 1024);
printf(" Chunks on provider: %u\n", md->chunk_count);
printf(" Chunks free: %u\n", md->chunk_count - md->chunk_next);
printf(" Reserved chunks: %u\n", md->chunk_reserved);
}
/* Called by geom(8) via gvirstor_main() to dump metadata information */
static void
virstor_dump(struct gctl_req *req)
{
struct g_virstor_metadata md;
u_char tmpmd[512]; /* temporary buffer */
const char *name;
char param[16];
int nargs, error, i;
assert(sizeof(tmpmd) >= sizeof(md));
nargs = gctl_get_int(req, "nargs");
if (nargs < 1) {
gctl_error(req, "Too few arguments.");
return;
}
for (i = 0; i < nargs; i++) {
snprintf(param, sizeof(param), "arg%u", i);
name = gctl_get_ascii(req, "%s", param);
error = g_metadata_read(name, (u_char *) & tmpmd, sizeof(tmpmd),
G_VIRSTOR_MAGIC);
if (error != 0) {
fprintf(stderr, "Can't read metadata from %s: %s.\n",
name, strerror(error));
gctl_error(req,
"Not fully done (error reading metadata).");
continue;
}
virstor_metadata_decode((u_char *) & tmpmd, &md);
printf("Metadata on %s:\n", name);
virstor_metadata_dump(&md);
printf("\n");
}
}