diff options
Diffstat (limited to 'format/format.c')
-rw-r--r-- | format/format.c | 456 |
1 files changed, 456 insertions, 0 deletions
diff --git a/format/format.c b/format/format.c new file mode 100644 index 00000000..b3a47cdf --- /dev/null +++ b/format/format.c @@ -0,0 +1,456 @@ +/* virt-format + * Copyright (C) 2012 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <inttypes.h> +#include <unistd.h> +#include <getopt.h> +#include <locale.h> +#include <assert.h> +#include <libintl.h> + +#include "progname.h" +#include "c-ctype.h" + +#include "guestfs.h" +#include "options.h" + +/* These globals are shared with options.c. */ +guestfs_h *g; + +int read_only = 0; +int live = 0; +int verbose = 0; +int keys_from_stdin = 0; +int echo_keys = 0; +int inspector = 0; +const char *libvirt_uri = NULL; + +static const char *filesystem = NULL; +static const char *vg = NULL, *lv = NULL; +static const char *partition = "DEFAULT"; +static int wipe = 0; + +static void parse_vg_lv (const char *lvm); +static int do_format (void); +static int do_rescan (char **devices); + +static inline char * +bad_cast (char const *s) +{ + return (char *) s; +} + +static void __attribute__((noreturn)) +usage (int status) +{ + char *warning = + _("IMPORTANT NOTE: This program ERASES ALL DATA on disks."); + + if (status != EXIT_SUCCESS) + fprintf (stderr, _("Try `%s --help' for more information.\n%s\n"), + program_name, warning); + else { + fprintf (stdout, + _("%s: erase and make a blank disk\n" + "Copyright (C) 2012 Red Hat Inc.\n" + "\n" + "%s\n" + "\n" + "Usage:\n" + " %s [--options] -a disk.img [-a disk.img ...]\n" + "Options:\n" + " -a|--add image Add image\n" + " --filesystem=.. Create empty filesystem\n" + " --format[=raw|..] Force disk format for -a option\n" + " --help Display brief help\n" + " --lvm=.. Create Linux LVM2 logical volume\n" + " --partition=.. Create / set partition type\n" + " -v|--verbose Verbose messages\n" + " -V|--version Display version and exit\n" + " --wipe Write zeroes over whole disk\n" + " -x Trace libguestfs API calls\n" + "For more information, see the manpage %s(1).\n" + "\n" + "%s\n\n"), + program_name, warning, program_name, program_name, + warning); + } + exit (status); +} + +int +main (int argc, char *argv[]) +{ + /* Set global program name that is not polluted with libtool artifacts. */ + set_program_name (argv[0]); + + setlocale (LC_ALL, ""); + bindtextdomain (PACKAGE, LOCALEBASEDIR); + textdomain (PACKAGE); + + enum { HELP_OPTION = CHAR_MAX + 1 }; + + static const char *options = "a:c:d:qvVx"; + static const struct option long_options[] = { + { "add", 1, 0, 'a' }, + { "filesystem", 1, 0, 0 }, + { "format", 2, 0, 0 }, + { "help", 0, 0, HELP_OPTION }, + { "lvm", 2, 0, 0 }, + { "partition", 2, 0, 0 }, + { "verbose", 0, 0, 'v' }, + { "version", 0, 0, 'V' }, + { "wipe", 0, 0, 0 }, + { 0, 0, 0, 0 } + }; + struct drv *drvs = NULL; + struct drv *drv; + const char *format = NULL; + int c; + int option_index; + int retry, retries; + + g = guestfs_create (); + if (g == NULL) { + fprintf (stderr, _("guestfs_create: failed to create handle\n")); + exit (EXIT_FAILURE); + } + + argv[0] = bad_cast (program_name); + + for (;;) { + c = getopt_long (argc, argv, options, long_options, &option_index); + if (c == -1) break; + + switch (c) { + case 0: /* options which are long only */ + if (STREQ (long_options[option_index].name, "format")) { + if (!optarg || STREQ (optarg, "")) + format = NULL; + else + format = optarg; + } else if (STREQ (long_options[option_index].name, "filesystem")) { + if (STREQ (optarg, "none")) + filesystem = NULL; + else if (optarg[0] == '-') { /* eg: --filesystem --lvm */ + fprintf (stderr, _("%s: no filesystem was specified\n"), + program_name); + exit (EXIT_FAILURE); + } else + filesystem = optarg; + } else if (STREQ (long_options[option_index].name, "lvm")) { + if (vg || lv) { + fprintf (stderr, + _("%s: --lvm option cannot be given multiple times\n"), + program_name); + exit (EXIT_FAILURE); + } + if (optarg == NULL) { + vg = strdup ("VG"); + lv = strdup ("LV"); + if (!vg || !lv) { perror ("strdup"); exit (EXIT_FAILURE); } + } + else if (STREQ (optarg, "none")) + vg = lv = NULL; + else + parse_vg_lv (optarg); + } else if (STREQ (long_options[option_index].name, "partition")) { + if (optarg == NULL) + partition = "DEFAULT"; + else if (STREQ (optarg, "none")) + partition = NULL; + else + partition = optarg; + } else if (STREQ (long_options[option_index].name, "wipe")) { + wipe = 1; + } else { + fprintf (stderr, _("%s: unknown long option: %s (%d)\n"), + program_name, long_options[option_index].name, option_index); + exit (EXIT_FAILURE); + } + break; + + case 'a': + OPTION_a; + break; + + case 'v': + OPTION_v; + break; + + case 'V': + OPTION_V; + break; + + case 'x': + OPTION_x; + break; + + case HELP_OPTION: + usage (EXIT_SUCCESS); + + default: + usage (EXIT_FAILURE); + } + } + + /* These are really constants, but they have to be variables for the + * options parsing code. Assert here that they have known-good + * values. + */ + assert (read_only == 0); + assert (inspector == 0); + assert (live == 0); + + /* Must be no extra arguments on the command line. */ + if (optind != argc) + usage (EXIT_FAILURE); + + /* The user didn't specify any drives to format. */ + if (drvs == NULL) + usage (EXIT_FAILURE); + + /* Because the libguestfs kernel can get stuck rereading the + * partition table after things have been erased, we sometimes need + * to completely restart the guest. Hence this complex retry logic. + */ + for (retries = 0; retries <= 1; ++retries) { + /* Add domains/drives from the command line (for a single guest). */ + add_drives (drvs, 'a'); + + if (guestfs_launch (g) == -1) + exit (EXIT_FAILURE); + + /* Perform the format. */ + retry = do_format (); + if (!retry) + break; + + if (retries == 0) { + /* We're going to silently retry, after reopening the connection. */ + guestfs_h *g2; + + g2 = guestfs_create (); + guestfs_set_verbose (g2, guestfs_get_verbose (g)); + guestfs_set_trace (g2, guestfs_get_trace (g)); + + guestfs_close (g); + g = g2; + } + else { + /* Failed. */ + fprintf (stderr, + _("%s: failed to rescan the disks after two attempts. This\n" + "may mean there is some sort of partition table or disk\n" + "data which we are unable to remove. If you think this\n" + "is a bug, please file a bug at http://libguestfs.org/\n"), + program_name); + exit (EXIT_FAILURE); + } + } + + /* Free up data structures. */ + free_drives (drvs); + + guestfs_close (g); + + exit (EXIT_SUCCESS); +} + +/* Parse lvm string of the form "/dev/VG/LV" or "VG/LV". + * This sets the global variables 'vg' and 'lv', or exits on failure. + */ +static void +parse_vg_lv (const char *lvm) +{ + size_t i; + + if (STRPREFIX (lvm, "/dev/")) + lvm += 5; + + i = strcspn (lvm, "/"); + if (lvm[i]) { + vg = strndup (lvm, i); + lv = strdup (lvm + i + 1); + + if (!vg || !lv) { + perror ("strdup"); + exit (EXIT_FAILURE); + } + } else { + cannot_parse: + fprintf (stderr, _("%s: cannot parse --lvm option (%s)\n"), + program_name, lvm); + exit (EXIT_FAILURE); + } + + if (strchr (vg, '/') || strchr (lv, '/')) + goto cannot_parse; +} + +/* Returns 0 on success, 1 if we need to retry. */ +static int +do_format (void) +{ + char **devices; + size_t i, pass; + int ret; + + devices = guestfs_list_devices (g); + if (devices == NULL) + exit (EXIT_FAILURE); + + /* Erase the disks. */ + if (!wipe) { + char **parts; + + /* No wipe, but get rid of LVM metadata by erasing each partition. */ + parts = guestfs_list_partitions (g); + if (parts == NULL) + exit (EXIT_FAILURE); + + for (i = 0; parts[i] != NULL; ++i) { + if (guestfs_zero (g, parts[i]) == -1) + exit (EXIT_FAILURE); + free (parts[i]); + } + free (parts); + + /* Then erase the partition table on each device. */ + for (i = 0; devices[i] != NULL; ++i) { + if (guestfs_zero (g, devices[i]) == -1) + exit (EXIT_FAILURE); + } + } + else /* wipe */ { + for (i = 0; devices[i] != NULL; ++i) { + if (guestfs_zero_device (g, devices[i]) == -1) + exit (EXIT_FAILURE); + } + } + + if (do_rescan (devices)) { + ret = 1; /* which means, reopen the handle and retry */ + goto out; + } + + /* Format each disk. */ + for (i = 0; devices[i] != NULL; ++i) { + char *dev = devices[i]; + int free_dev = 0; + + if (partition) { + const char *ptype = partition; + int64_t dev_size; + + /* If partition has the magic value "DEFAULT", choose either MBR or GPT.*/ + if (STREQ (partition, "DEFAULT")) { + dev_size = guestfs_blockdev_getsize64 (g, devices[i]); + if (dev_size == -1) + exit (EXIT_FAILURE); + ptype = dev_size < INT64_C(2)*1024*1024*1024*1024 ? "mbr" : "gpt"; + } + + if (guestfs_part_disk (g, devices[i], ptype) == -1) + exit (EXIT_FAILURE); + if (asprintf (&dev, "%s1", devices[i]) == -1) { + perror ("asprintf"); + exit (EXIT_FAILURE); + } + free_dev = 1; + } + + if (vg && lv) { + char *devs[2] = { dev, NULL }; + + if (guestfs_pvcreate (g, dev) == -1) + exit (EXIT_FAILURE); + + if (guestfs_vgcreate (g, vg, devs) == -1) + exit (EXIT_FAILURE); + + if (guestfs_lvcreate (g, lv, vg, 32) == -1) /* 32 MB is smallest LV */ + exit (EXIT_FAILURE); + + if (free_dev) + free (dev); + if (asprintf (&dev, "/dev/%s/%s", vg, lv) == -1) { + perror ("asprintf"); + exit (EXIT_FAILURE); + } + free_dev = 1; + + if (guestfs_lvresize_free (g, dev, 100) == -1) + exit (EXIT_FAILURE); + } + + if (filesystem) { + if (guestfs_mkfs_opts (g, filesystem, dev, -1) == -1) + exit (EXIT_FAILURE); + } + + if (free_dev) + free (dev); + } + + if (guestfs_sync (g) == -1) + exit (EXIT_FAILURE); + + ret = 0; + + out: + /* Free device list. */ + for (i = 0; devices[i] != NULL; ++i) + free (devices[i]); + free (devices); + + return ret; +} + +/* Rescan everything so the kernel knows that there are no partition + * tables, VGs etc. Returns 0 on success, 1 if we need to retry. + */ +static int +do_rescan (char **devices) +{ + size_t i; + int errors = 0; + guestfs_error_handler_cb old_error_cb; + void *old_error_data; + + old_error_cb = guestfs_get_error_handler (g, &old_error_data); + guestfs_set_error_handler (g, NULL, NULL); + + for (i = 0; devices[i] != NULL; ++i) { + if (guestfs_blockdev_rereadpt (g, devices[i]) == -1) + errors++; + } + + if (guestfs_vgscan (g) == -1) + errors++; + + guestfs_set_error_handler (g, old_error_cb, old_error_data); + + return errors ? 1 : 0; +} |