/*
 * tools/pvmove.c
 *
 * Copyright (C) 1997 - 2001  Heinz Mauelshagen, Sistina Software
 *
 * June,October 1997
 * April,May,July,September 1998
 * January,October 1999
 * February 2000
 * January,February 2001
 *
 * 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
 *
 *    09/11/1997 - added lvmtab handling
 *    07/05/1998 - implemented -n for LV selection by name
 *    16/05/1998 - added lvmtab checking
 *    27/06/1998 - changed lvm_tab_* calling convention
 *    05/07/1998 - support LE/PV move (on same PV too)
 *    05/09/1998 - corrected some messages
 *    26/01/1999 - made volume group directory prefix a preprocessor option
 *    06/10/1999 - implemented support for long options
 *    15/02/2000 - use lvm_error()
 *    23/01/2001 - added call to lvm_init (JT)
 *    14/02/2001 - Tidied usage message and options
 *    19/02/2001 - changed messages to give better understanding of pvmove's
 *                 behaviour
 *    20/02/2001 - fixed bug preventing move from/to MD devices by checking
 *                 LVM_EPV_READ_MD_DEVICE pv_read() return code (Andreas Dilger)
 *    19/03/2001 - Fix some typos in messages
 *
 */

#include <lvm_user.h>

char *cmd = NULL;

#ifdef DEBUG
int opt_d = 0;
#endif

void usage(int ret)
{
   FILE *stream = stderr;

   if (ret == 0) {
      stream = stdout;
      printf("%s  (IOP %d)\n\n", lvm_version, LVM_LIB_IOP_VERSION);
      printf("%s - move extents from physical volume to another\n", cmd);
   }

   fprintf(stream, "\n%s "
	   "[-A|--autobackup {y|n}] "
#ifdef DEBUG
	   "[-d|--debug] "
#endif
	   "[-f|--force]"
	   "[-h|--help]\n\t"
	   "[-t|--test] "
	   "[-v[v]|--verbose [--verbose]] "
	   "[-V|--version]\n\t"
	   "[{-n|--name} LogicalVolume[:LogicalExtent[-LogicalExtent]...]]\n\t"
	   "SourcePhysicalVolume[:PhysicalExtent[-PhysicalExtent]...]}\n\t"
	   "[DestinationPhysicalVolume[:PhysicalExtent[-PhysicalExtent]...]...]\n\n",
	   cmd);
   exit(ret);
}

int main ( int argc, char **argv) {
   int buffer_size = 64*1024;
   int c = 0;
   int c1 = 0;
   int ext = 0;
   int i = 0;
   int p = 0;
   int lv_index = -1;
   int lv_num = 0;
   int np = 0;
   int opt_A = 1;
   int opt_A_set = 0;
   int opt_f = 0;
   int opt_n = 0;
   int opt_t = 0;
   int opt_v = 0;
   int ret = 0;
   int dst_pv_index = 0;
   int src_pv_index = 0;
   int vg_active = FALSE;
   long *les = NULL;
   long *source_pes = NULL;
   long *dest_pes = NULL;
   ulong pe_moved = 0;
   char lv_name_buf[NAME_LEN] = { 0, };
   char *buffer = NULL;
   char *lv_name = NULL;
#ifdef DEBUG
   char *options = "A:dfhn:tvV";
#else
   char *options = "A:fhn:tvV";
#endif
   struct option long_options[] = {
      { "autobackup", required_argument, NULL, 'A'},
#ifdef DEBUG
      { "debug",      no_argument,       NULL, 'd'},
#endif
      { "force",      no_argument,       NULL, 'f'},
      { "help",       no_argument,       NULL, 'h'},
      { "name",       required_argument, NULL, 'n'},
      { "test",       no_argument,       NULL, 't'},
      { "verbose",    no_argument,       NULL, 'v'},
      { "version",    no_argument,       NULL, 'V'},
      { NULL, 0, NULL, 0}
   };
   char **pv_allowed = NULL;
   char *src_pv_name = NULL;
   char *vg_name = NULL;
   pv_t pv_this;
   pv_t *pv = 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 ( strcmp ( optarg, "y") == 0);
            else if ( strcmp ( optarg, "n") == 0) opt_A = 0;
            else {
               fprintf ( stderr, "%s -- invalid autobackup argument '%s'\n",
                                 cmd, optarg);
               usage(LVM_EINVALID_CMD_LINE);
            }
            break;

#ifdef DEBUG
         case 'd':
            opt_d++;
            break;
#endif

         case 'f':
            opt_f++;
            break;

         case 'h':
            usage(0);
            break;

         case 'n':
            lv_name = optarg;
            if ( lvm_get_col_numbers ( optarg, &les) < 0)
               usage(LVM_EPVMOVE_LVM_GET_COL_NUMBERS);
            opt_n++;
            break;

         case 't':
            opt_t++;
            break;

         case 'v':
            opt_v++;
            break;

         case 'V':
            printf("%s  (IOP %d)\n\n", lvm_version, LVM_LIB_IOP_VERSION);
            return 0;
            break;

         default:
            fprintf(stderr, "%s -- invalid option '%c'\n", cmd, c);
         case '?':
            usage(LVM_EINVALID_CMD_LINE);
      }
   }

   /* Not until all commands are converted.
   CMD_MINUS_CHK;
   */
   if ( optind < argc && *argv[optind] == '-') {
      fprintf(stderr, "%s -- invalid option '%s'\n", argv[optind], cmd);
      usage(LVM_EINVALID_CMD_LINE);
   }
   CMD_CHECK_OPT_A_SET;

   if ( opt_n == 0 && optind == argc) {
      fprintf(stderr, "%s -- no source physical volume path given\n\n", cmd);
      usage(LVM_EPVMOVE_PV_SOURCE);
   }

   src_pv_name = argv[optind++];

   if ( lvm_get_col_numbers ( src_pv_name, &source_pes) < 0) {
      fprintf ( stderr, "%s -- ERROR: invalid physical extent numbers "
                        "on command line\n",
                        cmd);
      usage(LVM_EPVMOVE_LVM_GET_COL_NUMBERS);
   }

   if ( opt_n > 0 && source_pes != NULL && source_pes[0] != -1) {
      fprintf ( stderr, "%s -- ERROR: you can't select a logical volume AND "
                        "source physical extents\n\n",
                        cmd);
      usage(LVM_EINVALID_CMD_LINE);
   }

   if ( opt_v > 0)
      printf("%s -- checking name of source physical volume \"%s\"\n",
             cmd, src_pv_name);
   if ( pv_check_name ( src_pv_name) < 0) {
      fprintf ( stderr, "%s -- \"%s\" is an invalid physical volume name\n\n",
                        cmd, src_pv_name);
      usage(LVM_EPVMOVE_PV_CHECK_NAME_SOURCE);
   }

   LVM_CHECK_IOP;
   LVM_LOCK ( 0);
   lvm_dont_interrupt ( 0);

   if ( opt_v > 0) printf ( "%s -- reading data of source physical volume "
                            "from \"%s\"\n", cmd, src_pv_name);
   if ( ( ret = pv_read ( src_pv_name, &pv, NULL)) < 0 &&
        ret != -LVM_EPV_READ_MD_DEVICE) {
      if ( ret == -LVM_EPV_READ_OPEN) {
         fprintf ( stderr, "%s -- source physical volume \"%s\" "
                           "doesn't exist\n\n",
                           cmd, src_pv_name);
      } else {
         fprintf ( stderr, "%s -- ERROR \"%s\" reading source physical "
                           "volume \"%s\"\n\n",
                           cmd, lvm_error ( ret), src_pv_name);
      }
      return LVM_EPVMOVE_PV_READ;
   }
   memcpy ( &pv_this, pv, sizeof ( pv_this));
   vg_name = pv_this.vg_name;

   if ( ( ret = pv_check_consistency ( &pv_this)) < 0) {
      fprintf ( stderr, "%s -- ERROR \"%s\" physical volume \"%s\" "
                        "is inconsistent\n\n",
                        cmd, lvm_error ( ret), src_pv_name);
      return LVM_EPVMOVE_PV_CHECK_CONSISTENCY;
   }

   /* does VG exist? */
   if ( opt_v > 0) printf ( "%s -- checking volume group existence\n", cmd);
   if ( lvm_tab_vg_check_exist ( vg_name, NULL) != TRUE) {
      fprintf ( stderr, "%s -- ERROR: can't move physical extents: "
                        "volume group %s doesn't exist\n\n",
                        cmd, vg_name);
      return LVM_EPVMOVE_VG_CHECK_EXIST;
   }

   if ( opt_v > 0) printf ( "%s -- reading data of volume group \"%s\" "
                            "from lvmtab\n",
                            cmd, vg_name);
   if ( ( ret = lvm_tab_vg_read_with_pv_and_lv ( vg_name, &vg)) != 0) {
      fprintf ( stderr, "%s -- ERROR \"%s\" couldn't get data of volume "
                        "group \"%s\"\n\n",
                        cmd, lvm_error ( ret), vg_name);
      return LVM_EPVMOVE_VG_READ;
   }

   if ( opt_v > 0) printf ( "%s -- checking volume group consistency "
                            "of \"%s\"\n",
                            cmd, vg_name);
   if ( ( ret = vg_check_consistency_with_pv_and_lv ( vg)) != 0) {
      fprintf ( stderr, "%s -- ERROR \"%s\" volume group \"%s\" is "
                        "inconsistent\n\n",
                        cmd, lvm_error ( ret), vg_name);
      return LVM_EPVMOVE_VG_CHECK_CONSISTENCY;
   }

   /* get index of source physical volume */
   if ( opt_v > 0) printf ( "%s -- searching for source physical volume \"%s\" "
                            "in volume group \"%s\"\n",
                            cmd, src_pv_name, vg_name);
   if ( ( src_pv_index = pv_get_index_by_name ( vg, src_pv_name)) < 0) {
      fprintf ( stderr, "%s -- couldn't find source physical volume \"%s\" "
                        "in volume group \"%s\"\n\n",
                        cmd, src_pv_name, vg_name);
      return LVM_EPVMOVE_PV_GET_INDEX;
   }

   if ( vg->pv[src_pv_index]->pe_allocated == 0) {
      fprintf ( stderr, "%s -- no logical volumes on empty source physical "
                        "volume \"%s\"\n\n",
                        cmd, src_pv_name);
      return LVM_EPVMOVE_PE_ALLOCATED;
   }

   if ( opt_n > 0) {
      if ( opt_v > 0) printf ( "%s -- checking logical volume \"%s\"\n",
                               cmd, lv_name);
      if ( strchr ( lv_name, '/') == NULL) {
         sprintf ( lv_name_buf, LVM_DIR_PREFIX "%s/%s", vg_name, lv_name);
         lv_name = lv_name_buf;
      }
      if ( lv_check_name ( lv_name) < 0) {
         fprintf ( stderr, "%s -- \"%s\" is an invalid logical volume name\n\n",
                           cmd, lv_name);
         return LVM_EPVMOVE_LV_CHECK_NAME;
      }
      if ( ( lv_num = lv_number_from_name_in_vg ( lv_name, vg)) < 0) {
         fprintf ( stderr, "%s -- logical volume \"%s\" is not in volume "
                           "group \"%s\"\n\n",
                           cmd, lv_name, vg_name);
         return LVM_EPVMOVE_LV_NUMBER;
      }
      lv_num++;
      if ( lv_check_on_pv ( vg->pv[src_pv_index], lv_num) == FALSE) {
         fprintf ( stderr, "%s -- logical volume \"%s\" isn't on physical "
                           "volume \"%s\"\n\n",
                           cmd, lv_name, src_pv_name);
         return LVM_EPVMOVE_LV_CHECK_ON_PV;
      }
   }

   if ( opt_v > 0) printf ( "%s -- building list of possible destination "
                            "physical volumes\n", cmd);
   if ( ( pv_allowed = malloc ( vg->pv_cur * sizeof ( char*))) == NULL) {
      fprintf ( stderr, "%s -- malloc error in file \"%s\" [line: %d]\n\n",
                        cmd, __FILE__, __LINE__);
      return LVM_EPVMOVE_MALLOC;
   }

   /* destination physical volumes in command line ? */
   np = 0;
   if ( optind < argc) {
      if ( opt_v > 0) printf ( "%s -- checking destination physical "
                               "volume names in command line\n", cmd);
      for ( i = optind; i < argc; i++) {
         if ( lvm_get_col_numbers ( argv[i], &dest_pes) < 0) {
            fprintf ( stderr, "%s -- ERROR: invalid physical destination "
                              "extent numbers on command line\n",
                              cmd);
            return LVM_EPVMOVE_LVM_GET_COL_NUMBERS;
         }
         if ( pv_check_name ( argv[i]) < 0) {
            fprintf ( stderr, "%s -- invalid physical volume name \"%s\"\n\n",
                              cmd, argv[i]);
            return LVM_EPVMOVE_PV_CHECK_NAME;
         }
         if ( ( ret = pv_read ( argv[i], &pv, NULL)) < 0 &&
              ret != -LVM_EPV_READ_MD_DEVICE) {
            if ( ret == -LVM_EPV_READ_OPEN) {
               fprintf ( stderr, "%s -- ERROR: destination physical volume "
                                 "%s doesn't exist\n\n",
                                 cmd, argv[i]);
            } else {
               fprintf ( stderr, "%s -- ERROR \"%s\" reading destination "
                                 "physical volume \"%s\"\n\n",
                                 cmd, lvm_error ( ret), argv[i]);
            }
            return LVM_EPVMOVE_PV_READ;
         }
         if ( ( ret = pv_check_consistency ( pv)) < 0) {
            fprintf ( stderr, "%s -- ERROR \"%s\" physical volume \"%s\" "
                              "is inconsistent\n\n",
                              cmd, lvm_error ( ret), argv[i]);
            return LVM_EPVMOVE_PV_CHECK_CONSISTENCY;
         }
         if ( pv_check_in_vg ( vg, pv->pv_name) == FALSE) {
            fprintf ( stderr, "%s -- ERROR: physical volume \"%s\" doesn't "
                              "belong to volume group\"%s\"\n\n",
                              cmd, argv[i], vg_name);
            return LVM_EPVMOVE_PV_CHECK_IN_VG;
         }
         pv_allowed[np] = argv[i];
         if ( dest_pes != NULL && ( optind < argc - 1 || np > 1)) {
            fprintf ( stderr, "%s -- only one destination physical volume "
                              "with physical extents allowed\n", cmd);
            return LVM_EPVMOVE_DEST_PES;
         }
         np++;
      }
   } else {
      for ( p = 0; p < vg->pv_max && vg->pv[p] != NULL; p++) {
         if ( p != src_pv_index) pv_allowed[np++] = vg->pv[p]->pv_name;
      }
   }
   pv_allowed[np] = NULL;


   if ( opt_n > 0) {
      if ( ( lv_index = lv_get_index_by_name ( vg, lv_name)) < 0) {
         fprintf ( stderr, "%s -- ERROR: couldn't find logical volume \"%s\" "
                           "in volume group \"%s\"\n\n",
                           cmd, lv_name, vg_name);
         return LVM_EPVMOVE_LV_GET_INDEX;
      }
      if ( les != NULL) {
         for ( ext = 0; les[ext] != -1; ext++) {
            if ( les[ext] < 0 ||
                 les[ext] >= vg->lv[lv_index]->lv_current_le) {
               fprintf ( stderr, "%s -- ERROR: logical extent %ld invalid for "
                                 "logical volume \"%s\"\n\n",
                                 cmd, les[ext], lv_name);
               return LVM_EPVMOVE_LE_INVALID;
            }
         }
      }
   }

   if ( source_pes != NULL) {
      if ( les != NULL) {
         fprintf ( stderr, "%s -- logical volume with logical extents and "
                           "source physical volume "
                           "%s -- with physical extents are not allowed\n\n",
                           cmd, cmd);
         return LVM_EINVALID_CMD_LINE;
      }
      for ( ext = 0; source_pes[ext] != -1; ext++) {
         if ( source_pes[ext] < 0 ||
              source_pes[ext] >= vg->pv[src_pv_index]->pe_total ||
              vg->pv[src_pv_index]->pe[source_pes[ext]].lv_num == 0) {
            fprintf ( stderr, "%s -- ERROR: source physical extent %ld invalid "
                              "for physical volume \"%s\"\n\n",
                              cmd, source_pes[ext],
                              vg->pv[src_pv_index]->pv_name);
            return LVM_EPVMOVE_PE_INVALID;
         }
      }
   }

   if ( dest_pes != NULL) {
      /* get index of destination physical volume */
      if ( ( dst_pv_index = pv_get_index_by_name ( vg, pv_allowed[0])) < 0) {
         fprintf ( stderr, "%s -- ERROR: couldn't find destination physical "
                           "volume \"%s\" in volume group \"%s\"\n\n",
                           cmd, pv_allowed[0], vg_name);
         return LVM_EPVMOVE_PV_GET_INDEX;
      }

      for ( ext = 0; dest_pes[ext] != -1; ext++) {
         if ( dest_pes[ext] < 0 ||
              dest_pes[ext] >= vg->pv[dst_pv_index]->pe_total ||
              vg->pv[dst_pv_index]->pe[dest_pes[ext]].lv_num != 0) {
            fprintf ( stderr, "%s -- ERROR: destination physical extent %ld "
                              "already in use on physical volume \"%s\"\n\n",
                              cmd, dest_pes[ext],
                              vg->pv[dst_pv_index]->pv_name);
            return LVM_EPVMOVE_PE_IN_USE;
         }
      }
   }

   /* allocate transfer buffer memory */
   if ( ( buffer = malloc ( buffer_size)) == NULL) {
      fprintf ( stderr, "%s -- malloc error in file \"%s\" [line: %d]\n\n",
                        cmd, __FILE__, __LINE__);
      return LVM_EPVMOVE_MALLOC;
   }
   
   if ( opt_v > 0) printf ( "%s -- checking volume group activity\n", cmd);
   printf ( "%s -- moving physical extents in ", cmd);
   if ( vg_check_active ( vg_name) == TRUE) {
      vg_active = TRUE;
   } else {
      vg_active = FALSE;
      printf ( "in");
   }
   printf ( "active volume group \"%s\"\n", vg_name);

   if ( opt_f == 0) {
      printf ( "%s -- WARNING: if you lose power during the move you may need to restore your LVM metadata from backup!\n", cmd);
      c = 0;
      while ( c != 'y' && c != 'n') {
         if ( c == '\n' || c == 0)
            printf ( "%s -- do you want to continue? [y/n] ", cmd);
         c = tolower ( getchar ());
      }
      c1 = c;
      while ( c != '\n') c = tolower ( getchar ());
      if ( c1 == 'n') goto pvmove_end;
   }

   /* let's rock and role */
   if ( opt_v > 0) {
      printf ( "%s -- starting to move ", cmd);
      if ( lv_index > -1) {
         if ( vg->lv[lv_index]->lv_stripes > 1) printf ( "striped");
         else                                   printf ( "linear");
         printf ( " logical volume \"%s\"\n", vg->lv[lv_index]->lv_name);
      } else {
         printf ( "extents away from physical volume \"%s\"\n",
                  src_pv_name);
      }
   }
  
   pe_moved = 0;
   ret = 1;
   while ( ret > 0) {
      if ( ( ret = pv_move_pes ( vg, buffer, buffer_size, pv_allowed,
                                 src_pv_index, lv_index,
                                 les, source_pes, dest_pes,
                                 opt_v, opt_t)) < 0) {
         if ( ret == -LVM_EPV_MOVE_PES_NO_SPACE) {
            fprintf ( stderr, "%s -- not enough free/allocatable "
                              "physical extents\n\n", cmd);
         } else if ( ret == -LVM_EPARAM) {
            fprintf ( stderr, "%s -- invalid parameters to move\n\n", cmd);
         } else if ( ret == -LVM_EPV_MOVE_PES_ALLOC_STRIPES) {
            fprintf ( stderr, "%s -- ERROR: physical extents given for a "
                              "striped/contiguous logical volume\n\n",
                              cmd);
         } else if ( ret == -LVM_EPV_MOVE_PES_NO_PES) {
            printf ( "%s -- no physical extents to move\n\n", cmd);
            ret = 0;
         } else {
            fprintf ( stderr, "%s -- ERROR \"%s\" moving physical extents\n\n",
                              cmd, lvm_error ( ret));
         }
         return LVM_EPVMOVE_PV_MOVE_PES;
      }
      pe_moved += ret;
      if ( lv_index > -1 || vg->pv[src_pv_index]->pe_allocated == 0) break;
      if ( source_pes != NULL || dest_pes != NULL ) ret = 0;
   }

   free ( buffer);
   if ( les        != NULL) free ( les);
   if ( source_pes != NULL) free ( source_pes);
   if ( dest_pes   != NULL) free ( dest_pes);

   if ( opt_v > 0) printf ( "%s -- changing lvmtab\n", cmd);
   if ( opt_t == 0) {
      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 "
                  "metadata backup of \"%s\"\n",
                  cmd, vg_name);
      }
   }

   lvm_interrupt ();
   LVM_UNLOCK ( 0);

   printf ( "%s -- %lu extent%s of physical volume \"%s\" successfully moved\n",
            cmd, pe_moved, pe_moved > 1 ? "s" : "", src_pv_name);
   if ( opt_t > 0) printf ( "%s -- this has been a test run WITHOUT "
                            "any real change\n", cmd);

pvmove_end:
   printf ( "\n");

   return 0;
}
