/*
 * tools/vgsplit.c
 *
 * Copyright (C) 1997 - 2000  Heinz Mauelshagen, Sistina Software
 *
 * June,September 1998
 * October 1999
 * February 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
 *
 *    27/06/1998 - changed to new vg_setup_for_split() calling convention
 *    06/09/1998 - corrected some messages
 *    06/10/1999 - implemented support for long options
 *    15/02/2000 - use lvm_error()
 *
 */

#include <lvm_user.h>

char *cmd = NULL;

#ifdef DEBUG
int opt_d = 0;
#endif

int main ( int argc, char **argv) {
   int c = 0;
   int l = 0;
   int lr = 0;
   int opt_A = 1;
   int opt_A_set = 0;
   int opt_l = 0;
   int opt_t = 0;
   int opt_v = 0;
   int p = 0;
   int p1 = 0;
   int np = 0;
   int pr = 0;
   int pv_for_new_count = 0;
   int ret = 0;
   int v = 0;
#ifdef DEBUG
   char *options = "A:dh?ltv";
#else
   char *options = "A:h?ltv";
#endif
   struct option long_options[] = {
      { "autobackup", required_argument, NULL, 'A'},
#ifdef DEBUG
      { "debug",      no_argument,       NULL, 'd'},
#endif
      { "help",       no_argument,       NULL, 'h'},
      { "list",       no_argument,       NULL, 'l'},
      { "test",       no_argument,       NULL, 't'},
      { "verbose",    no_argument,       NULL, 'v'},
      { NULL,         0,                 NULL, 0},
   }; 
   char *vg_name_exist = NULL;
   char *vg_name_new = NULL;
   char *error_pv_name = NULL;
   char **pv_for_new = NULL;
   char **lv_for_new = NULL;
   lv_t *lv = NULL;
   pv_t **pv = NULL;
   vg_t *vg_exist = NULL;
   vg_t *vg_exist_sav = NULL;
   vg_t *vg_new = NULL;
   vg_t vg_exist_this;
   vg_t vg_exist_sav_this;

   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 -- Volume Group Split\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/--list]\n"
                     "\t[-t/--test]\n"
                     "\t[-v/--verbose]\n"
                     "\tExistingVolumeGroupName NewVolumeGroupName\n"
                     "\tPhysicalVolumePath [PhysicalVolumePath...]\n\n",
                     lvm_version, LVM_LIB_IOP_VERSION,  cmd, cmd);
            return 0;
            break;

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

         case 't':
            if ( opt_t > 0) {
               fprintf ( stderr, "%s -- t option already given\n\n", cmd);
               return LVM_EINVALID_CMD_LINE;
            }
            opt_t++;
            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\n",
                      cmd, c);
            return LVM_EINVALID_CMD_LINE;
      }
   }
  
   CMD_MINUS_CHK;
   LVM_CHECK_IOP;
   LVM_LOCK ( 0);
   CMD_CHECK_OPT_A_SET;


   if ( optind > argc - 3) {
      fprintf ( stderr, "%s -- invalid command line\n\n", cmd);
      return LVM_EINVALID_CMD_LINE;
   }

   for ( v = optind; v < optind + 2; v++) {
      if ( opt_v > 0) printf ( "%s -- checking volume group name \"%s\"\n",
                               cmd, argv[v]);
      if ( vg_check_name ( argv[v]) < 0) {
         fprintf ( stderr, "%s -- invalid volume group name \"%s\"\n\n",
                           cmd, argv[v]);
         return LVM_EVGSPLIT_VG_CHECK_NAME;
      }
   }

   vg_name_exist = argv[optind++];
   vg_name_new = argv[optind++];

   for ( v = optind; v < argc; v++) {
      if ( opt_v > 0) printf ( "%s -- checking physical volume name \"%s\"\n",
                               cmd, argv[v]);
      if ( pv_check_name ( argv[v]) < 0) {
         fprintf ( stderr, "%s -- invalid physical volume name \"%s\"\n\n",
                           cmd, argv[v]);
         return LVM_EVGSPLIT_PV_CHECK_NAME;
      }
   }

   pv_for_new_count = argc - optind;
   if ( ( pv_for_new =
             malloc ( ( pv_for_new_count + 1) * sizeof ( char*))) == NULL) {
      fprintf ( stderr, "%s -- malloc error in file \"%s\" [line %d]\n\n",
                         cmd, __FILE__, __LINE__);
      return LVM_EVGSPLIT_MALLOC;
   }

   for ( p = 0; p < pv_for_new_count; p++) pv_for_new[p] = argv[optind + p];
   pv_for_new[p] = NULL;

   if ( opt_v > 0) printf ( "%s -- reading data of volume group \"%s\" "
                            "from \"%s\"\n",
                            cmd, vg_name_exist, LVMTAB);
   if ( ( ret = lvm_tab_vg_read_with_pv_and_lv ( vg_name_exist,
                                                 &vg_exist)) < 0) {
      fprintf ( stderr, "%s -- ERROR \"%s\" getting data of volume "
                        "group \"%s\"\n\n",
                        cmd, lvm_error ( ret), vg_name_exist);
      return LVM_EVGSPLIT_VG_READ_EXIST;
   }

   memcpy ( &vg_exist_this, vg_exist, sizeof ( vg_exist_this));
   vg_exist = &vg_exist_this;

   if ( opt_v > 0) printf ( "%s -- reading data of volume group \"%s\" "
                            "(saved)\n",
                            cmd, vg_name_exist);
   if ( ( ret = lvm_tab_vg_read_with_pv_and_lv ( vg_name_exist,
                                                 &vg_exist_sav)) < 0) {
      fprintf ( stderr, "%s -- ERROR \"%s\" getting data of volume "
                        "group \"%s\" (saved)\n\n",
                        cmd, lvm_error ( ret), vg_name_exist);
      return LVM_EVGSPLIT_VG_READ_EXIST;
   }

   memcpy ( &vg_exist_sav_this, vg_exist_sav, sizeof ( vg_exist_sav_this));
   vg_exist_sav = &vg_exist_sav_this;

   if ( opt_v > 0) printf ( "%s -- checking volume group \"%s\" existence\n",
                            cmd, vg_name_new);
   if ( lvm_tab_vg_check_exist ( vg_name_new, NULL) == TRUE) {
      fprintf ( stderr, "%s -- ERROR: volume group \"%s\" already exists\n\n",
                        cmd, vg_name_new);
      return LVM_EVGSPLIT_VG_CHECK_EXIST_NEW;
   }


   if ( opt_v > 0) printf ( "%s -- splitting VGDA of \"%s\" into \"%s\"\n",
                            cmd, vg_name_exist, vg_name_new);
   if ( ( ret = vg_setup_for_split ( vg_exist, vg_name_new, &vg_new,
                                     pv_for_new, &lv_for_new,
                                     &error_pv_name)) < 0) {
      if ( ret == -LVM_EVG_SETUP_FOR_SPLIT_PV_COUNT) {
         fprintf ( stderr, "%s -- ERROR: volume group \"%s\" only has "
                           "physical volume \"%s\"\n\n",
                           cmd, vg_name_exist, error_pv_name);
      } else if ( ret == -LVM_EVG_SETUP_FOR_SPLIT_PV) {
         fprintf ( stderr, "%s -- ERROR: physical volume \"%s\" doesn't belong "
                           "to volume group \"%s\"\n\n",
                           cmd, error_pv_name, vg_name_exist);
      } else if ( ret == -LVM_EVG_SETUP_FOR_SPLIT_LV_ON_PV) {
         fprintf ( stderr, "%s -- ERROR: can't split logical volumes; please "
                           "run pvmove\n\n", cmd);
      } else {
         fprintf ( stderr, "%s -- ERROR \"%s\" splitting volume group \"%s\" "
                           "into \"%s\"\n\n",
                           cmd, lvm_error ( ret), vg_name_exist, vg_name_new);
      }
      return LVM_EVGSPLIT_VG_SETUP;
   }


   if ( opt_l > 0) {
      vg_show_with_pv_and_lv ( vg_exist);
      putchar ( '\n');
      vg_show_with_pv_and_lv ( vg_new);
   }

   for ( l = 0; lv_for_new[l] != NULL; l++) {
      if ( lv_check_active ( vg_name_exist, lv_for_new[l]) == TRUE) {
         fprintf ( stderr, "%s -- logical volume \"%s\" must be inactive "
                           "to split volume group \"%s\"\n\n",
                           cmd, lv_for_new[l], vg_name_exist);
         return LVM_EVGSPLIT_LV_CHECK_EXIST;
      }
   }

   /* Release all split of LVs in structures */
   for ( l = 0; lv_for_new[l] != NULL; l++) {
      if ( opt_v > 0) printf ( "%s -- releasing logical volume \"%s\" in VGDA "
                               "of volume group \"%s\"\n",
                               cmd, lv_for_new[l], vg_name_exist);
      if ( ( ret = lv_release ( vg_exist_sav, lv_for_new[l])) != 0) {
         fprintf ( stderr, "%s -- ERROR \"%s\" releasing logical volume \"%s\" "
                           "in VGDA of volume group \"%s\"\n",
                           cmd, lvm_error ( ret), lv_for_new[l], vg_name_exist);
         continue;
      }
   }

   lvm_dont_interrupt ( 0);

   /* reduce vg_exist by splitted of logical volumes */
   for ( l = 0; lv_for_new[l] != NULL; l++) {
      if ( opt_v > 0) printf ( "%s -- getting status of logical volume "
                               "\"%s\" from VGDA in kernel\n",
                               cmd, lv_for_new[l]);
      if ( ( ret = lv_status_byname ( vg_name_exist, lv_for_new[l], &lv)) < 0) {
         if ( ret == -ENXIO) {
            fprintf ( stderr, "%s -- logical volume \"%s\" doesn't exist\n",
                              cmd, lv_for_new[l]);
         } else {
            fprintf ( stderr, "%s -- ERROR \"%s\" getting status"
                              " of logical volume \"%s\"\n",
                              cmd, lvm_error ( ret), lv_for_new[l]);
         }
         return LVM_EVGSPLIT_LV_STATUS_BYNAME;
      }

      if ( opt_v > 0) printf ( "%s -- removing logical volume \"%s\" from VGDA "
                               "in kernel\n",
                               cmd, lv_for_new[l]);
      if ( opt_t == 0 &&
           ( ret = lv_remove ( vg_exist_sav, lv, lv_for_new[l])) < 0) {
         fprintf ( stderr, "%s -- ERROR \"%s\" removing logical volume "
                           "\"%s\" in kernel\n",
                           cmd, lvm_error ( ret), vg_exist_sav->lv[lr]->lv_name);
         return LVM_EVGSPLIT_LV_REMOVE;
      }
   }

   if ( ( pv = malloc ( ( pv_for_new_count + 1) * sizeof ( pv_t*))) == NULL) {
      fprintf ( stderr, "%s -- malloc error in file \"%s\" [line %d]\n\n",
                         cmd, __FILE__, __LINE__);
      return LVM_EVGSPLIT_MALLOC;
   }


   /* Get and release pointers to PVs to reduce VG by */
   for ( p = 0; pv_for_new[p] != NULL; p++) {
      pr = pv_get_index_by_name ( vg_exist_sav, pv_for_new[p]);
      pv[p] = vg_exist_sav->pv[pr];
      vg_exist_sav->pv[pr] = NULL;
      vg_exist_sav->pe_allocated -= pv[p]->pe_allocated;
      vg_exist_sav->pe_total -= pv[p]->pe_total;
      vg_exist_sav->pv_act--;
      vg_exist_sav->pv_cur--;
   }
   pv[p] = NULL;
   
   /* make PV-pointer in VG-array contiguous */
   np = 0;
   for ( p1 = 0; p1 < vg_exist_sav->pv_max; p1++) {
      if ( vg_exist_sav->pv[p1] != NULL) {
         vg_exist_sav->pv[np] = vg_exist_sav->pv[p1];
         vg_exist_sav->pv[np]->pv_number = np + 1;
         np++;
      }
   }

   /* reduce vg_exist by splitted physical volumes */
   for ( p = 0; pv[p] != NULL; p++) {
      if ( opt_v > 0) printf ( "%s -- reducing volume group \"%s\" by "
                               "physical volume \"%s\" in kernel\n",
                               cmd, vg_name_exist,
                               pv[p]->pv_name);
      if ( opt_t == 0 &&
           ( ret = vg_reduce ( vg_name_exist, pv[p], vg_exist_sav)) < 0) {
         fprintf ( stderr, "%s -- ERROR \"%s\" reducing volume group \"%s\" "
                           "by physical volume \"%s\" in kernel\n",
                           cmd, lvm_error ( ret),
                           vg_name_exist, pv[p]->pv_name);
         return LVM_EVGSPLIT_VG_REDUCE;
      }
      free ( pv[p]);
   }
   free ( pv);

   /* store VGs on disk(s) */
   if ( opt_v > 0) printf ( "%s -- storing data of volume group "
                            "\"%s\" on disk(s)\n", cmd, vg_name_exist);
   if ( opt_t == 0 &&
        ( ret = vg_write_with_pv_and_lv ( vg_exist)) < 0) {
      fprintf ( stderr, "%s -- ERROR \"%s\" storing data of volume group "
                        "\"%s\" on disk(s)\n\n",
                        cmd, lvm_error ( ret), vg_name_exist);
      return LVM_EVGSPLIT_VG_WRITE_EXIST;
   }

   if ( opt_v > 0) printf ( "%s -- changing \"%s\" for volume group \"%s\"\n",
                            cmd, LVMTAB, vg_name_exist);
   if ( opt_t == 0 &&
        vg_cfgbackup ( vg_name_exist, LVMTAB_DIR, opt_v, vg_exist) == 0 &&
        opt_A > 0) {
      printf ( "%s -- doing automatic backup of volume group \"%s\"\n",
               cmd, vg_name_exist);
      vg_cfgbackup ( vg_name_exist, VG_BACKUP_DIR, opt_v, vg_exist);
   }

   if ( opt_v > 0) printf ( "%s -- storing data of volume group "
                            "\"%s\" on disk(s)\n", cmd, vg_name_new);
   if ( opt_t == 0 &&
        ( ret = vg_write_with_pv_and_lv ( vg_new)) < 0) {
      fprintf ( stderr, "%s -- ERROR \"%s\" storing data of volume group "
                        "\"%s\" on disk(s)\n\n",
                        cmd, lvm_error ( ret), vg_name_new);
      return LVM_EVGSPLIT_VG_WRITE_NEW;
   }

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


   if ( opt_v > 0) printf ( "%s -- removing special files for volume "
                            "group \"%s\"\n", cmd, vg_name_exist);
   if ( opt_t == 0) vg_remove_dir_and_group_and_nodes ( vg_name_exist);

   if ( opt_v > 0) printf ( "%s -- creating directory and nodes of volume "
                            "group \"%s\"\n",
                            cmd, vg_name_exist);
   if ( opt_t == 0 &&
        ( ret = vg_create_dir_and_group_and_nodes ( vg_exist,
                                                    opt_v)) < 0) {
      fprintf ( stderr, "%s -- ERROR \"%s\" creating volume group "
                        "nodes of \"%s\"\n\n",
                        cmd, lvm_error ( ret), vg_name_exist);
      return LVM_EVGSPLIT_VG_CREATE_DIR_AND_GROUP_NODES_EXIST;
   }

   if ( opt_v > 0) printf ( "%s -- removing special files for volume "
                            "group \"%s\"\n", cmd, vg_name_new);
   if ( opt_t == 0) vg_remove_dir_and_group_and_nodes ( vg_name_new);

   if ( opt_t == 0 &&
        ( ret = vg_create_dir_and_group_and_nodes ( vg_new, opt_v)) < 0) {
      fprintf ( stderr, "%s -- ERROR \"%s\" creating volume group "
                        "nodes of \"%s\"\n\n",
                        cmd, lvm_error ( ret), vg_name_new);
      return LVM_EVGSPLIT_VG_CREATE_DIR_AND_GROUP_NODES_NEW;
   }

   /* create VGDA in kernel */
   if ( opt_v > 0) printf ( "%s -- creating VGDA for volume group \"%s\" "
                            "in kernel\n",
                            cmd, vg_name_new);
   if ( opt_t == 0 &&
        ( ret = vg_create ( vg_name_new, vg_new)) < 0) {
      fprintf ( stderr, "%s -- ERROR \"%s\" creating VGDA for volume group "
                        "\"%s\" in kernel\n",
                        cmd, lvm_error ( ret), vg_name_new);
      if ( ret == -LVM_EVG_CREATE_REMOVE_OPEN)
         fprintf ( stderr, "%s -- LVM not in kernel?\n", cmd);
      fprintf ( stderr, "\n");
      return LVM_EVGSPLIT_VG_CREATE;
   }

   if ( opt_v > 0) printf ( "%s -- inserting volume group \"%s\" into lvmtab\n",
                            cmd, vg_name_new);
   if ( opt_t == 0 &&
        ( ret = lvm_tab_vg_insert ( vg_name_new)) < 0) {
      fprintf ( stderr, "%s -- ERROR \"%s\" inserting volume group \"%s\" "
                        "into \"%s\"\n\n",
                        cmd, lvm_error ( ret), vg_name_new, LVMTAB);
      return LVM_EVGSPLIT_VG_INSERT;
   }

   printf ( "%s -- volume group \"%s\" successfully split into "
            "\"%s\" and \"%s\"\n",
            cmd, vg_name_exist, vg_name_exist, vg_name_new);
   if ( opt_t > 0) printf ( "%s -- this only has been a test run\n", cmd);

   lvm_interrupt ();
   LVM_UNLOCK ( 0);

   printf ( "\n");

   return 0;
}
