/*
 * tools/lvextend.c
 *
 * Copyright (C) 1997 - 2000  Heinz Mauelshagen, Sistina Software
 *
 * April-May 1997
 * May,June,September 1998
 * February,August,October 1999
 * February,July,November 2000
 *
 * LVM 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, or (at your option)
 * any later version.
 * 
 * LVM 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 LVM; see the file COPYING.  If not, write to
 * the Free Software Foundation, 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA. 
 *
 */

/*
 * Changelog
 *
 *    16/10/1997 - change lv_get_index to lv_get_index_by_name
 *    09/11/1997 - added lvmtab handling
 *    16/05/1998 - added lvmtab checking
 *    12/06/1998 - enhanced checking numbers in option arguments
 *    27/06/1998 - changed lvm_tab_* calling convention
 *    29/06/1998 - corrected size error message
 *    04/09/1998 - corrected some messages
 *    06/02/1999 - fixed lvm_check_number() usage
 *    05/07/1999 - avoided vg_status()
 *    15/08/1999 - enhanced to avoid extension of snapshot logical volumes
 *    06/10/1999 - implemented support for long options
 *    15/02/2000 - use lvm_error()
 *    01/07/2000 - support for snapshot logical volumes
 *    07/11/2000 - used LVM_VG_NAME environment variable
 *    23/01/2001 - added call to lvm_init (JT)
 *
 */

/*
 * TODO
 *
 * allow striped volume extensiom on multiple stripe count volumes
 *
 */

#include <lvm_user.h>

char *cmd = NULL;

#ifdef DEBUG
int opt_d = 0;
#endif

int main ( int argc, char **argv) {
   int c = 0;
   int i = 0;
   int l = 0;
   int pa = 0;
   int opt_A = 1;
   int opt_A_set = 0;
   int opt_l = 0;
   int opt_L = 0;
   int opt_v = 0;
   int ret = 0;
   int sign = 0;
   int size_rest = 0;
   long new_size = 0;
   char buffer[NAME_LEN];
   char *dummy = 0;
#ifdef DEBUG
   char *options = "A:dh?l:L:v";
#else
   char *options = "A:h?l:L:v";
#endif
   struct option long_options[] = {
      { "autobackup", required_argument, NULL, 'A'},
#ifdef DEBUG
      { "debug",      no_argument,       NULL, 'd'},
#endif
      { "help",       no_argument,       NULL, 'h'},
      { "extents",    required_argument, NULL, 'l'},
      { "size",       required_argument, NULL, 'L'},
      { "verbose",    no_argument,       NULL, 'v'},
      { NULL, 0, NULL, 0}
   };
   char *lv_name = NULL;
   char *vg_name = NULL;
   char **pv_allowed = NULL;
   lv_t *lv = NULL;
   vg_t *vg = NULL;

   /* lvm_init(argc, argv); */
   cmd = basename ( argv[0]);

   SUSER_CHECK;
   LVMTAB_CHECK;

   while ( ( c = getopt_long ( argc, argv, options,
                               long_options, NULL)) != EOF) {
      switch ( c) {
         case 'A':
	    opt_A_set++;
            if ( opt_A > 1) {
               fprintf ( stderr, "%s -- A option already given\n\n", cmd);
               return LVM_EINVALID_CMD_LINE;
            }
            if ( strcmp ( optarg, "y") == 0);
            else if ( strcmp ( optarg, "n") == 0) opt_A = 0;
            else {
               fprintf ( stderr, "%s -- invalid option argument \"%s\"\n\n",
                                 cmd, optarg);
               return LVM_EINVALID_CMD_LINE;
            }
            break;

#ifdef DEBUG
         case 'd':
            if ( opt_d > 0) {
               fprintf ( stderr, "%s -- d option already given\n\n", cmd);
               return LVM_EINVALID_CMD_LINE;
            }
            opt_d++;
            break;
#endif

         case 'h':
         case '?':
            printf ( "\n%s (IOP %d)\n\n%s -- Logical Volume Extend\n\n"
                     "Synopsis:\n"
                     "---------\n\n"
                     "%s\n"
                     "\t[-A/--autobackup y/n]\n"
#ifdef DEBUG
                     "\t[-d/--debug]\n"
#endif
                     "\t[-h/-?/--help]\n"
                     "\t{-l/--extents [+]LogicalExtentsNumber |\n"
                     "\t -L/--size [+]LogicalVolumeSize[kKmMgGtT]}\n"
                     "\t[-v/--verbose]\n"
                     "\tLogicalVolume[Path] [ PhysicalVolumePath... ]\n\n",
                     lvm_version, LVM_LIB_IOP_VERSION,  cmd, cmd);
            return 0;
            break;

         case 'l':
            if ( opt_l > 0 || opt_L > 0) {
               fprintf ( stderr, "%s -- %s option already given\n\n",
                                 opt_l > 0 ? "l" : "L", cmd);
               return LVM_EINVALID_CMD_LINE;
            }

            sign = 0;
            if ( *optarg == '+') { sign = 1; optarg++;}
            if ( ( new_size = lvm_check_number ( optarg, FALSE)) > 0) {
               opt_l++;
            } else {
               fprintf ( stderr, "%s -- ERROR option l argument \"%s\"\n\n",
                                 cmd, optarg);
               return LVM_EINVALID_CMD_LINE;
            }
            break;

         case 'L':
            if ( opt_l > 0 || opt_L > 0) {
               fprintf ( stderr, "%s -- %s option already given\n\n",
                                 opt_l > 0 ? "l" : "L", cmd);
               return LVM_EINVALID_CMD_LINE;
            }

            sign = 0;
            if ( *optarg == '+') { sign = 1; optarg++;}
            if ( ( new_size = lvm_check_number ( optarg, TRUE)) > 0) {
               opt_L++;
            } else {
               fprintf ( stderr, "%s -- ERROR option L argument \"%s\"\n\n",
                                 cmd, optarg);
               return LVM_EINVALID_CMD_LINE;
            }
            break;

         case 'v':
            if ( opt_v > 0) {
               fprintf ( stderr, "%s -- v option already given\n\n", cmd);
               return LVM_EINVALID_CMD_LINE;
            }
            opt_v++;
            break;

         default:
            fprintf ( stderr, "%s -- invalid command line option \"%c\"\n",
                      cmd, c);
            return LVM_EINVALID_CMD_LINE;
      }
   }

   CMD_MINUS_CHK
   CMD_CHECK_OPT_A_SET;

   if ( optind == argc) {
      fprintf ( stderr, "%s -- please enter a logical volume path\n\n", cmd);
      return LVM_ELVEXTEND_LV_MISSING;
   }
   lv_name = argv[optind];
   LVM_CHECK_DEFAULT_VG_NAME ( lv_name, buffer, sizeof ( buffer));
   optind++;

   /* physical volumes in command line */
   if ( optind < argc) {
      if ( opt_v > 0) printf ( "%s -- checking for physical volume paths "
                               "on command line\n", cmd);
      for ( i = optind; i < argc; i++) {
         if ( pv_check_name ( argv[i]) < 0) {
            fprintf ( stderr, "%s -- \"%s\" is an invalid physical "
                              "volume name\n\n", cmd, argv[i]);
            return LVM_ELVEXTEND_PV_NAME;
         }
      }
      pv_allowed = &argv[optind];
   }

   if ( opt_v > 0) printf ( "%s -- checking for valid logical volume path\n",
                            cmd);
   if ( lv_check_name ( lv_name) < 0) {
      fprintf ( stderr, "%s -- no valid logical volume name \"%s\"\n\n",
                cmd, lv_name);
      return LVM_ELVEXTEND_LV_NAME;
   }

   LVM_CHECK_IOP;
   LVM_LOCK ( 0);

   if ( opt_v > 0) printf ( "%s -- checking existence of logical "
                            "volume \"%s\"\n",
                            cmd, lv_name);
   if ( lvm_tab_lv_check_exist ( lv_name) != TRUE) {
      fprintf ( stderr, "%s -- logical volume \"%s\" doesn't exist\n\n",
                cmd, lv_name);
      return LVM_ELVEXTEND_LV_CHECK_EXIST;
   }

   vg_name = vg_name_of_lv ( lv_name);

   if ( opt_v > 0) printf ( "%s -- checking for active logical volume \"%s\"\n",
                            cmd, lv_name);
   if ( lv_check_active ( vg_name, lv_name) != TRUE) {
      fprintf ( stderr, "%s -- logical volume \"%s\" isn't active\n\n",
                cmd, lv_name);
      return LVM_ELVEXTEND_LV_CHECK_ACTIVE;
   }

   if ( opt_v > 0) printf ( "%s -- reading volume group data of volume "
                            "group \"%s\" from disk(s)\n",
                            cmd, vg_name);
   if ( ( ret = lvm_tab_vg_read_with_pv_and_lv ( vg_name, &vg)) != 0) {
      fprintf ( stderr, "%s -- can't extend logical volume:"
                        " couldn't read data of volume group \"%s\"\n\n",
                        cmd, vg_name);
      return LVM_ELVEXTEND_VG_READ;
   }

   if ( pv_allowed != NULL) {
      for ( pa = 0; pv_allowed[pa] != NULL; pa++) {
         if ( opt_v > 0) printf ( "%s -- checking physical volume \"%s\" "
                                  "against volume group\n",
                                  pv_allowed[pa], cmd);
         if ( pv_check_in_vg ( vg, pv_allowed[pa]) == FALSE) {
            fprintf ( stderr, "%s -- physical volume \"%s\" doesn't belong "
                              "to volume group \"%s\"\n\n",
                              cmd, pv_allowed[pa], vg_name);
            return LVM_ELVEXTEND_PV_CHECK_IN_VG;
         }
      }
   }

   if ( opt_v > 0) printf ( "%s -- getting index of logical volume \"%s\" "
                            "in volume group \"%s\"\n",
                            cmd, lv_name, vg_name);
   if ( ( l = lv_get_index_by_name ( vg, lv_name)) < 0) {
      fprintf ( stderr, "%s -- can't extend logical volume without "
                        " the index of \"%s\"\n\n", cmd, lv_name);
      return LVM_ELVEXTEND_LV_GET_INDEX;
   }


   if ( opt_v > 0) printf ( "%s -- getting status of \"%s\" from VGDA in "
                            "kernel\n", cmd, lv_name);
   if ( ( ret = lv_status_byname ( vg_name, lv_name, &lv)) < 0) {
      fprintf ( stderr, "%s -- ERROR \"%s\" getting status of logical "
                        "volume \"%s\"\n\n",
                        cmd, lvm_error ( ret), lv_name);
      return LVM_ELVEXTEND_LV_STATUS_BYNAME;
   }

   if ( opt_L + opt_l > 0) {
      if ( opt_L > 0) new_size *= 2;
      else            new_size *= vg->pe_size;
      if ( new_size % vg->pe_size > 0) {
         new_size += vg->pe_size;
         printf ( "%s -- rounding size to physical extent boundary\n", cmd);
      }
      new_size = new_size / vg->pe_size * vg->pe_size;
      if ( sign != 0) new_size += vg->lv[l]->lv_size;

      size_rest = new_size % ( vg->lv[l]->lv_stripes * vg->pe_size);
      if ( size_rest != 0) {
         printf ( "%s -- rounding size %ld KB to stripe boundary size ",
                  cmd, new_size / 2);
         new_size = new_size - size_rest +
                    vg->lv[l]->lv_stripes * vg->pe_size;
         printf ( "%ld KB\n", new_size / 2);
      }


      if ( new_size <= vg->lv[l]->lv_size) {
         fprintf ( stderr, "%s -- new size %s is not bigger than old size\n\n",
                   cmd, ( dummy = lvm_show_size ( new_size / 2, SHORT)));
         free ( dummy);
         return LVM_ELVEXTEND_LV_SIZE;
      }
   } else {
      fprintf ( stderr, "%s -- please give an l or L option\n\n", cmd);
      return LVM_EINVALID_CMD_LINE;
   }

   if ( new_size - vg->lv[l]->lv_size >
        ( vg->pe_total - vg->pe_allocated) * vg->pe_size) {
      if ( vg->pe_total - vg->pe_allocated == 0) {
         fprintf ( stderr, "%s -- no free physical extents in volume "
                           "group \"%s\"\n\n",
                           cmd, vg_name);
      } else {
         fprintf ( stderr, "%s -- only %u free physical extent%s in volume "
                           "group \"%s\"\n\n",
                           cmd, vg->pe_total - vg->pe_allocated,
                           vg->pe_total - vg->pe_allocated > 1 ? "s": "",
                           vg_name);
      }
      return LVM_ELVEXTEND_LV_SIZE_FREE;
   }

   if ( new_size > LVM_LV_SIZE_MAX( vg)) {
      fprintf ( stderr, "%s -- new size %ld is to large for VGDA in kernel\n\n",
                cmd, new_size / 2);
      return LVM_ELVEXTEND_LV_SIZE_MAX;
   }

   if ( lv->lv_access & LV_SNAPSHOT) {
      ulong lv_org_size = vg->lv[lv_get_index_by_minor ( vg, vg->lv[l]->lv_snapshot_minor)]->lv_size;

      if ( new_size > lv_org_size * 1.1) {
            fprintf ( stderr, "%s -- ERROR: size of snapshot is too large\n",
                              cmd);
            return LVM_EINVALID_CMD_LINE;
      }
   }

   printf ( "%s -- extending logical volume \"%s\" to %s\n",
            cmd, lv_name, ( dummy = lvm_show_size ( new_size / 2, SHORT)));
   free ( dummy); dummy = NULL;

   if ( opt_v > 0) printf ( "%s -- setting up logical volume \"%s\" "
                            "for extend\n", cmd, lv_name);
   if ( ( ret = lv_setup_for_extend ( vg_name, vg, lv_name,
                                      new_size, pv_allowed)) < 0) {
      if ( ret == -LVM_ESIZE) {
         if ( ( vg->lv[l]->lv_allocation & LV_CONTIGUOUS) &&
              pv_allowed == NULL) {
            fprintf ( stderr, "%s -- please enter physical volumes to "
                              "extend contiguous logical volume\n",
                              cmd);
         } else {
            fprintf ( stderr, "%s -- not enough free/allocatable physical "
                              "extents to extend logical volume \"%s\"\n",
                              cmd, lv_name);
         }
      } else {
         fprintf ( stderr, "%s -- ERROR \"%s\" setting logical volume \"%s\" "
                           " up for extend\n",
                           cmd, lvm_error ( ret), lv_name);
      }
      fprintf ( stderr, "\n");
      return LVM_ELVEXTEND_LV_SETUP;
   }

   if ( lv->lv_access & LV_SNAPSHOT) {
      if ( ( ret = lv_setup_COW_table_for_create (
                      vg,
                      vg->lv[lv_get_index_by_minor ( vg, vg->lv[l]->lv_snapshot_minor)]->lv_name,
                      l,
                      lv->lv_chunk_size * SECTOR_SIZE / 1024)) < 0) {
         fprintf ( stderr, "%s -- ERROR \"%s\" setting up snapshot "
                           "copy on write exception table for \"%s\"\n\n",
                           cmd, lvm_error ( ret), lv_name);
         return LVM_ELVEXTEND_LV_SETUP_COW_TABLE_FOR_CREATE;
      }
   }

   lvm_dont_interrupt ( 0);

   /* create it in kernel */
   if ( opt_v > 0) printf ( "%s -- extending logical volume \"%s\" in "
                            "VGDA of kernel\n", cmd, lv_name);
   if ( ( ret = lv_extend ( vg, vg->lv[l], lv_name)) != 0) {
      fprintf ( stderr, "%s -- ERROR \"%s\" extending logical volume \"%s\" "
                        " in kernel's VGDA\n\n", 
                        cmd, lvm_error ( ret), lv_name);
      return LVM_ELVEXTEND_LV_EXTEND;
   }

   if ( opt_v > 0) printf ( "%s -- storing data of volume group \"%s\" "
                            "on disk(s)\n", cmd, vg_name);
   if ( ( ret = vg_write_with_pv_and_lv ( vg)) != 0) {
      fprintf ( stderr, "%s -- ERROR \"%s\" storing data of volume group "
                        "\"%s\" on disk(s)\n\n", 
                        cmd, lvm_error ( ret), vg_name);
      return LVM_ELVEXTEND_VG_WRITE;
   }

   if ( opt_v > 0) printf ( "%s -- changing lvmtab\n", cmd);
   if ( vg_cfgbackup ( vg_name, LVMTAB_DIR, opt_v, vg) == 0 &&
        opt_A > 0) {
      printf ( "%s -- doing automatic backup of volume group \"%s\"\n",
               cmd, vg_name);
      vg_cfgbackup ( vg_name, VG_BACKUP_DIR, opt_v, vg);
   } else {
      printf ( "%s -- WARNING: you don't have an automatic backup of \"%s\"\n",
               cmd, vg_name);
   }

   lvm_interrupt ();
   LVM_UNLOCK ( 0);

   printf ( "%s -- logical volume \"%s\" successfully extended\n\n",
             cmd, lv_name);

   return 0;
}
