/*      $NetBSD: lvcreate.c,v 1.1.1.2 2009/12/02 00:25:50 haad Exp $    */

/*
* Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.
* Copyright (C) 2004-2007 Red Hat, Inc. All rights reserved.
*
* This file is part of LVM2.
*
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License v.2.1.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "tools.h"
#include "lv_alloc.h"

#include <fcntl.h>

struct lvcreate_cmdline_params {
       percent_t percent;
       uint64_t size;
       char **pvs;
       int pv_count;
};

static int _lvcreate_name_params(struct lvcreate_params *lp,
                                struct cmd_context *cmd,
                                int *pargc, char ***pargv)
{
       int argc = *pargc;
       char **argv = *pargv, *ptr;
       char *vg_name;

       lp->lv_name = arg_str_value(cmd, name_ARG, NULL);

       if (lp->snapshot && !arg_count(cmd, virtualsize_ARG)) {
               if (!argc) {
                       log_error("Please specify a logical volume to act as "
                                 "the snapshot origin.");
                       return 0;
               }

               lp->origin = argv[0];
               (*pargv)++, (*pargc)--;
               if (!(lp->vg_name = extract_vgname(cmd, lp->origin))) {
                       log_error("The origin name should include the "
                                 "volume group.");
                       return 0;
               }

               /* Strip the volume group from the origin */
               if ((ptr = strrchr(lp->origin, (int) '/')))
                       lp->origin = ptr + 1;

       } else {
               /*
                * If VG not on command line, try -n arg and then
                * environment.
                */
               if (!argc) {
                       if (!(lp->vg_name = extract_vgname(cmd, lp->lv_name))) {
                               log_error("Please provide a volume group name");
                               return 0;
                       }

               } else {
                       vg_name = skip_dev_dir(cmd, argv[0], NULL);
                       if (strrchr(vg_name, '/')) {
                               log_error("Volume group name expected "
                                         "(no slash)");
                               return 0;
                       }

                       /*
                        * Ensure lv_name doesn't contain a
                        * different VG.
                        */
                       if (lp->lv_name && strchr(lp->lv_name, '/')) {
                               if (!(lp->vg_name =
                                     extract_vgname(cmd, lp->lv_name)))
                                       return 0;

                               if (strcmp(lp->vg_name, vg_name)) {
                                       log_error("Inconsistent volume group "
                                                 "names "
                                                 "given: \"%s\" and \"%s\"",
                                                 lp->vg_name, vg_name);
                                       return 0;
                               }
                       }

                       lp->vg_name = vg_name;
                       (*pargv)++, (*pargc)--;
               }
       }

       if (!validate_name(lp->vg_name)) {
               log_error("Volume group name %s has invalid characters",
                         lp->vg_name);
               return 0;
       }

       if (lp->lv_name) {
               if ((ptr = strrchr(lp->lv_name, '/')))
                       lp->lv_name = ptr + 1;

               if (!apply_lvname_restrictions(lp->lv_name))
                       return_0;

               if (!validate_name(lp->lv_name)) {
                       log_error("Logical volume name \"%s\" is invalid",
                                 lp->lv_name);
                       return 0;
               }
       }

       return 1;
}

/*
* Update extents parameters based on other parameters which affect the size
* calcuation.
* NOTE: We must do this here because of the percent_t typedef and because we
* need the vg.
*/
static int _update_extents_params(struct volume_group *vg,
                                 struct lvcreate_params *lp,
                                 struct lvcreate_cmdline_params *lcp)
{
       uint32_t pv_extent_count;

       if (lcp->size &&
           !(lp->extents = extents_from_size(vg->cmd, lcp->size,
                                              vg->extent_size)))
               return_0;

       if (lp->voriginsize &&
           !(lp->voriginextents = extents_from_size(vg->cmd, lp->voriginsize,
                                                     vg->extent_size)))
               return_0;

       /*
        * Create the pv list before we parse lcp->percent - might be
        * PERCENT_PVSs
        */
       if (lcp->pv_count) {
               if (!(lp->pvh = create_pv_list(vg->cmd->mem, vg,
                                          lcp->pv_count, lcp->pvs, 1)))
                       return_0;
       } else
               lp->pvh = &vg->pvs;

       switch(lcp->percent) {
               case PERCENT_VG:
                       lp->extents = lp->extents * vg->extent_count / 100;
                       break;
               case PERCENT_FREE:
                       lp->extents = lp->extents * vg->free_count / 100;
                       break;
               case PERCENT_PVS:
                       if (!lcp->pv_count)
                               lp->extents = lp->extents * vg->extent_count / 100;
                       else {
                               pv_extent_count = pv_list_extents_free(lp->pvh);
                               lp->extents = lp->extents * pv_extent_count / 100;
                       }
                       break;
               case PERCENT_LV:
                       log_error("Please express size as %%VG, %%PVS, or "
                                 "%%FREE.");
                       return 0;
               case PERCENT_NONE:
                       break;
       }
       return 1;
}

static int _read_size_params(struct lvcreate_params *lp,
                            struct lvcreate_cmdline_params *lcp,
                            struct cmd_context *cmd)
{
       if (arg_count(cmd, extents_ARG) + arg_count(cmd, size_ARG) != 1) {
               log_error("Please specify either size or extents (not both)");
               return 0;
       }

       if (arg_count(cmd, extents_ARG)) {
               if (arg_sign_value(cmd, extents_ARG, 0) == SIGN_MINUS) {
                       log_error("Negative number of extents is invalid");
                       return 0;
               }
               lp->extents = arg_uint_value(cmd, extents_ARG, 0);
               lcp->percent = arg_percent_value(cmd, extents_ARG, PERCENT_NONE);
       }

       /* Size returned in kilobyte units; held in sectors */
       if (arg_count(cmd, size_ARG)) {
               if (arg_sign_value(cmd, size_ARG, 0) == SIGN_MINUS) {
                       log_error("Negative size is invalid");
                       return 0;
               }
               lcp->size = arg_uint64_value(cmd, size_ARG, UINT64_C(0));
               lcp->percent = PERCENT_NONE;
       }

       /* Size returned in kilobyte units; held in sectors */
       if (arg_count(cmd, virtualsize_ARG)) {
               if (arg_sign_value(cmd, virtualsize_ARG, 0) == SIGN_MINUS) {
                       log_error("Negative virtual origin size is invalid");
                       return 0;
               }
               lp->voriginsize = arg_uint64_value(cmd, virtualsize_ARG,
                                                  UINT64_C(0));
               if (!lp->voriginsize) {
                       log_error("Virtual origin size may not be zero");
                       return 0;
               }
       }

       return 1;
}

/*
* Generic stripe parameter checks.
* FIXME: Should eventually be moved into lvm library.
*/
static int _validate_stripe_params(struct cmd_context *cmd,
                                  struct lvcreate_params *lp)
{
       if (lp->stripes == 1 && lp->stripe_size) {
               log_print("Ignoring stripesize argument with single stripe");
               lp->stripe_size = 0;
       }

       if (lp->stripes > 1 && !lp->stripe_size) {
               lp->stripe_size = find_config_tree_int(cmd,
                                                 "metadata/stripesize",
                                                 DEFAULT_STRIPESIZE) * 2;
               log_print("Using default stripesize %s",
                         display_size(cmd, (uint64_t) lp->stripe_size));
       }

       if (lp->stripes < 1 || lp->stripes > MAX_STRIPES) {
               log_error("Number of stripes (%d) must be between %d and %d",
                         lp->stripes, 1, MAX_STRIPES);
               return 0;
       }

       /* MAX size check is in _lvcreate */
       if (lp->stripes > 1 && (lp->stripe_size < STRIPE_SIZE_MIN ||
                               lp->stripe_size & (lp->stripe_size - 1))) {
               log_error("Invalid stripe size %s",
                         display_size(cmd, (uint64_t) lp->stripe_size));
               return 0;
       }

       return 1;
}

/* The stripe size is limited by the size of a uint32_t, but since the
* value given by the user is doubled, and the final result must be a
* power of 2, we must divide UINT_MAX by four and add 1 (to round it
* up to the power of 2) */
static int _read_stripe_params(struct lvcreate_params *lp,
                              struct cmd_context *cmd)
{
       if (arg_count(cmd, stripesize_ARG)) {
               if (arg_sign_value(cmd, stripesize_ARG, 0) == SIGN_MINUS) {
                       log_error("Negative stripesize is invalid");
                       return 0;
               }
               /* Check to make sure we won't overflow lp->stripe_size */
               if(arg_uint_value(cmd, stripesize_ARG, 0) > STRIPE_SIZE_LIMIT * 2) {
                       log_error("Stripe size cannot be larger than %s",
                                 display_size(cmd, (uint64_t) STRIPE_SIZE_LIMIT));
                       return 0;
               }
               lp->stripe_size = arg_uint_value(cmd, stripesize_ARG, 0);
       }


       if (!_validate_stripe_params(cmd, lp))
               return 0;

       return 1;
}

/*
* Generic mirror parameter checks.
* FIXME: Should eventually be moved into lvm library.
*/
static int _validate_mirror_params(const struct cmd_context *cmd __attribute((unused)),
                                  const struct lvcreate_params *lp)
{
       int pagesize = lvm_getpagesize();

       if (lp->region_size & (lp->region_size - 1)) {
               log_error("Region size (%" PRIu32 ") must be a power of 2",
                         lp->region_size);
               return 0;
       }

       if (lp->region_size % (pagesize >> SECTOR_SHIFT)) {
               log_error("Region size (%" PRIu32 ") must be a multiple of "
                         "machine memory page size (%d)",
                         lp->region_size, pagesize >> SECTOR_SHIFT);
               return 0;
       }

       if (!lp->region_size) {
               log_error("Non-zero region size must be supplied.");
               return 0;
       }

       return 1;
}

static int _read_mirror_params(struct lvcreate_params *lp,
                              struct cmd_context *cmd)
{
       int region_size;
       const char *mirrorlog;

       if (arg_count(cmd, corelog_ARG))
               lp->corelog = 1;

       mirrorlog = arg_str_value(cmd, mirrorlog_ARG,
                                 lp->corelog ? "core" : DEFAULT_MIRRORLOG);

       if (!strcmp("disk", mirrorlog)) {
               if (lp->corelog) {
                       log_error("--mirrorlog disk and --corelog "
                                 "are incompatible");
                       return 0;
               }
               lp->corelog = 0;
       } else if (!strcmp("core", mirrorlog))
               lp->corelog = 1;
       else {
               log_error("Unknown mirrorlog type: %s", mirrorlog);
               return 0;
       }

       log_verbose("Setting logging type to %s", mirrorlog);

       lp->nosync = arg_is_set(cmd, nosync_ARG);

       if (arg_count(cmd, regionsize_ARG)) {
               if (arg_sign_value(cmd, regionsize_ARG, 0) == SIGN_MINUS) {
                       log_error("Negative regionsize is invalid");
                       return 0;
               }
               lp->region_size = arg_uint_value(cmd, regionsize_ARG, 0);
       } else {
               region_size = 2 * find_config_tree_int(cmd,
                                       "activation/mirror_region_size",
                                       DEFAULT_MIRROR_REGION_SIZE);
               if (region_size < 0) {
                       log_error("Negative regionsize in configuration file "
                                 "is invalid");
                       return 0;
               }
               lp->region_size = region_size;
       }

       if (!_validate_mirror_params(cmd, lp))
               return 0;

       return 1;
}

static int _lvcreate_params(struct lvcreate_params *lp,
                           struct lvcreate_cmdline_params *lcp,
                           struct cmd_context *cmd,
                           int argc, char **argv)
{
       int contiguous;
       unsigned pagesize;

       memset(lp, 0, sizeof(*lp));
       memset(lcp, 0, sizeof(*lcp));

       /*
        * Check selected options are compatible and determine segtype
        */
       lp->segtype = (const struct segment_type *)
           arg_ptr_value(cmd, type_ARG,
                         get_segtype_from_string(cmd, "striped"));

       lp->stripes = arg_uint_value(cmd, stripes_ARG, 1);
       if (arg_count(cmd, stripes_ARG) && lp->stripes == 1)
               log_print("Redundant stripes argument: default is 1");

       if (arg_count(cmd, snapshot_ARG) || seg_is_snapshot(lp) ||
           arg_count(cmd, virtualsize_ARG))
               lp->snapshot = 1;

       lp->mirrors = 1;

       /* Default to 2 mirrored areas if --type mirror */
       if (seg_is_mirrored(lp))
               lp->mirrors = 2;

       if (arg_count(cmd, mirrors_ARG)) {
               lp->mirrors = arg_uint_value(cmd, mirrors_ARG, 0) + 1;
               if (lp->mirrors == 1)
                       log_print("Redundant mirrors argument: default is 0");
               if (arg_sign_value(cmd, mirrors_ARG, 0) == SIGN_MINUS) {
                       log_error("Mirrors argument may not be negative");
                       return 0;
               }
       }

       if (lp->snapshot) {
               if (arg_count(cmd, zero_ARG)) {
                       log_error("-Z is incompatible with snapshots");
                       return 0;
               }
               if (arg_sign_value(cmd, chunksize_ARG, 0) == SIGN_MINUS) {
                       log_error("Negative chunk size is invalid");
                       return 0;
               }
               lp->chunk_size = arg_uint_value(cmd, chunksize_ARG, 8);
               if (lp->chunk_size < 8 || lp->chunk_size > 1024 ||
                   (lp->chunk_size & (lp->chunk_size - 1))) {
                       log_error("Chunk size must be a power of 2 in the "
                                 "range 4K to 512K");
                       return 0;
               }
               log_verbose("Setting chunksize to %d sectors.", lp->chunk_size);

               if (!(lp->segtype = get_segtype_from_string(cmd, "snapshot")))
                       return_0;
       } else {
               if (arg_count(cmd, chunksize_ARG)) {
                       log_error("-c is only available with snapshots");
                       return 0;
               }
       }

       if (lp->mirrors > 1) {
               if (lp->snapshot) {
                       log_error("mirrors and snapshots are currently "
                                 "incompatible");
                       return 0;
               }

               if (lp->stripes > 1) {
                       log_error("mirrors and stripes are currently "
                                 "incompatible");
                       return 0;
               }

               if (!(lp->segtype = get_segtype_from_string(cmd, "striped")))
                       return_0;
       } else {
               if (arg_count(cmd, corelog_ARG)) {
                       log_error("--corelog is only available with mirrors");
                       return 0;
               }

               if (arg_count(cmd, nosync_ARG)) {
                       log_error("--nosync is only available with mirrors");
                       return 0;
               }
       }

       if (activation() && lp->segtype->ops->target_present &&
           !lp->segtype->ops->target_present(cmd, NULL, NULL)) {
               log_error("%s: Required device-mapper target(s) not "
                         "detected in your kernel", lp->segtype->name);
               return 0;
       }

       if (!_lvcreate_name_params(lp, cmd, &argc, &argv) ||
           !_read_size_params(lp, lcp, cmd) ||
           !_read_stripe_params(lp, cmd) ||
           !_read_mirror_params(lp, cmd))
               return_0;

       /*
        * Should we zero the lv.
        */
       lp->zero = strcmp(arg_str_value(cmd, zero_ARG,
               (lp->segtype->flags & SEG_CANNOT_BE_ZEROED) ? "n" : "y"), "n");

       /*
        * Alloc policy
        */
       contiguous = strcmp(arg_str_value(cmd, contiguous_ARG, "n"), "n");

       lp->alloc = contiguous ? ALLOC_CONTIGUOUS : ALLOC_INHERIT;

       lp->alloc = arg_uint_value(cmd, alloc_ARG, lp->alloc);

       if (contiguous && (lp->alloc != ALLOC_CONTIGUOUS)) {
               log_error("Conflicting contiguous and alloc arguments");
               return 0;
       }

       /*
        * Read ahead.
        */
       lp->read_ahead = arg_uint_value(cmd, readahead_ARG, DM_READ_AHEAD_NONE);
       pagesize = lvm_getpagesize() >> SECTOR_SHIFT;
       if (lp->read_ahead != DM_READ_AHEAD_AUTO &&
           lp->read_ahead != DM_READ_AHEAD_NONE &&
           lp->read_ahead % pagesize) {
               if (lp->read_ahead < pagesize)
                       lp->read_ahead = pagesize;
               else
                       lp->read_ahead = (lp->read_ahead / pagesize) * pagesize;
               log_warn("WARNING: Overriding readahead to %u sectors, a multiple "
                           "of %uK page size.", lp->read_ahead, pagesize >> 1);
       }

       /*
        * Permissions.
        */
       lp->permission = arg_uint_value(cmd, permission_ARG,
                                       LVM_READ | LVM_WRITE);

       /* Must not zero read only volume */
       if (!(lp->permission & LVM_WRITE))
               lp->zero = 0;

       lp->minor = arg_int_value(cmd, minor_ARG, -1);
       lp->major = arg_int_value(cmd, major_ARG, -1);

       /* Persistent minor */
       if (arg_count(cmd, persistent_ARG)) {
               if (!strcmp(arg_str_value(cmd, persistent_ARG, "n"), "y")) {
                       if (lp->minor == -1) {
                               log_error("Please specify minor number with "
                                         "--minor when using -My");
                               return 0;
                       }
                       if (lp->major == -1) {
                               log_error("Please specify major number with "
                                         "--major when using -My");
                               return 0;
                       }
               } else {
                       if ((lp->minor != -1) || (lp->major != -1)) {
                               log_error("--major and --minor incompatible "
                                         "with -Mn");
                               return 0;
                       }
               }
       } else if (arg_count(cmd, minor_ARG) || arg_count(cmd, major_ARG)) {
               log_error("--major and --minor require -My");
               return 0;
       }

       lp->tag = arg_str_value(cmd, addtag_ARG, NULL);

       lcp->pv_count = argc;
       lcp->pvs = argv;

       return 1;
}

int lvcreate(struct cmd_context *cmd, int argc, char **argv)
{
       int r = ECMD_PROCESSED;
       struct lvcreate_params lp;
       struct lvcreate_cmdline_params lcp;
       struct volume_group *vg;

       memset(&lp, 0, sizeof(lp));

       if (!_lvcreate_params(&lp, &lcp, cmd, argc, argv))
               return EINVALID_CMD_LINE;

       log_verbose("Finding volume group \"%s\"", lp.vg_name);
       vg = vg_read_for_update(cmd, lp.vg_name, NULL, 0);
       if (vg_read_error(vg)) {
               vg_release(vg);
               stack;
               return ECMD_FAILED;
       }

       if (!_update_extents_params(vg, &lp, &lcp)) {
               r = ECMD_FAILED;
               goto_out;
       }

       if (!lv_create_single(vg, &lp)) {
               stack;
               r = ECMD_FAILED;
       }
out:
       unlock_and_release_vg(cmd, vg, lp.vg_name);
       return r;
}