/*
 * tools/vgreduce.c
 *
 * Copyright (C) 1997 - 2000  Heinz Mauelshagen, Sistina Software
 *
 * April,October 1997
 * May,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
 *
 *    08/10/1997 - added option -a
 *    09/11/1997 - added lvmtab handling
 *    09/05/1998 - check for volume group beeing reducable
 *    16/05/1998 - added lvmtab checking
 *    27/06/1998 - changed lvm_tab_* calling convention
 *    05/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 error = 0;
   int p = 0;
   int np = 0;
   int opt_a = 0;
   int opt_A = 1;
   int opt_A_set = 0;
   int opt_v = 0;
   int ret = 0;
   int c = 0;
   char *error_pv_name = NULL;
   char **pv_names = NULL;
   char *vg_name = NULL;
   vg_t *vg = NULL;
#ifdef DEBUG
   char *options = "aA:dh?v";
#else
   char *options = "aA:h?v";
#endif
   struct option long_options[] = {
      { "all",        no_argument,       NULL, 'a'},
      { "autobackup", required_argument, NULL, 'A'},
#ifdef DEBUG
      { "debug",      no_argument,       NULL, 'd'},
#endif
      { "help",       no_argument,       NULL, 'h'},
      { "verbose",    no_argument,       NULL, 'v'},
      { NULL,         0,                 NULL, 0},
   };
   pv_t **pv_this = NULL;

   cmd = basename ( argv[0]);

   SUSER_CHECK;
   LVMTAB_CHECK;

   while ( ( c = getopt_long ( argc, argv, options,
                               long_options, NULL)) != EOF) {
      switch ( c) {
         case 'a':
            if ( opt_a > 0) {
               fprintf ( stderr, "%s -- a option already given\n\n", cmd);
               return LVM_EINVALID_CMD_LINE;
            }
            opt_a++;
            break;

         case 'A':
            opt_A_set = 1;
            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 Reduce\n\n"
                     "Synopsis:\n"
                     "---------\n\n"
                     "%s\n"
                     "\t[-a/--all]\n"
                     "\t[-A/--autobackup y/n]\n"
#ifdef DEBUG
                     "\t[-d/--debug]\n"
#endif
                     "\t[-h/-?/--help]\n"
                     "\t[-v/--verbose]\n"
                     "\tVolumeGroupName\n"
                     "\t[PhysicalVolumePath...]\n\n",
                     lvm_version, LVM_LIB_IOP_VERSION,  cmd, cmd);
            return 0;
            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;
   LVM_CHECK_IOP;
   LVM_LOCK ( 0);
   CMD_CHECK_OPT_A_SET;


   if ( optind == argc) {
      fprintf ( stderr, "%s -- please enter a volume group name"
                         " and physical volume paths\n\n", cmd);
      return LVM_EVGREDUCE_VG_PV_NAMES;
   }

   vg_name = argv[optind++];

   /* valid VG name? */
   if ( opt_v > 0) printf ( "%s -- checking volume group name \"%s\"\n",
                            cmd, vg_name);
   if ( vg_check_name ( vg_name) < 0) {
      fprintf ( stderr, "%s -- ERROR: invalid volume group name \"%s\"\n\n",
                        cmd, vg_name);
      return LVM_EVGREDUCE_VG_CHECK_NAME;
   } 

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

   /* is VG active? */
   if ( opt_v > 0) printf ( "%s -- checking volume group \"%s\" activity\n",
                            cmd, vg_name);
   if ( vg_check_active ( vg_name) != TRUE) {
      fprintf ( stderr, "%s -- ERROR: volume group \"%s\" is not active\n\n",
                        cmd, vg_name);
      return LVM_EVGREDUCE_VG_CHECK_ACTIVE;
   } 

   /* read complete VGDA */
   if ( opt_v > 0) printf ( "%s -- reading 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 -- ERROR \"%s\" couldn't get data of volume "
                        "group \"%s\"\n\n",
                        cmd, lvm_error ( ret), vg_name);
      return LVM_EVGREDUCE_VG_READ;
   }

   if ( ! ( vg->vg_status & VG_EXTENDABLE)) {
      fprintf ( stderr, "%s -- ERROR: volume group \"%s\" is not reducable\n\n",
                        cmd, vg_name);
      return LVM_EVGREDUCE_VG_NOT_REDUCABLE;
   }

   if ( optind == argc) {
      if ( opt_a == 0) {
         fprintf ( stderr, "%s -- please enter physical volume paths "
                           "or option -a\n\n", cmd);
         return LVM_EVGREDUCE_PV_NAME;
      } else {
         np = 0;
         pv_names = NULL;
         for ( p = 0; p < vg->pv_max; p++) {
            if ( vg->pv[p] != NULL && vg->pv[p]->pe_allocated == 0) {
               if ( ( pv_names = realloc ( pv_names,
                                           sizeof ( char*) *
                                           ( np + 2))) == NULL) {
                  fprintf ( stderr, "%s -- realloc error in file \"%s\" "
                                    "[line %d]\n\n", cmd, __FILE__, __LINE__);
                  return LVM_EVGREDUCE_REALLOC;
               }
               if ( ( pv_names[np] = malloc ( NAME_LEN)) == NULL) {
                  fprintf ( stderr, "%s -- malloc error in file \"%s\" "
                                    "[line %d]\n\n", cmd, __FILE__, __LINE__);
                  return LVM_EVGREDUCE_MALLOC;
               }
               strcpy ( pv_names[np], vg->pv[p]->pv_name);
               np++;
               pv_names[np] = NULL;
            }
         }
         if ( np == 0) {
            fprintf ( stderr, "%s -- ERROR: no empty physical volumes found "
                              "in volume group \"%s\"\n\n",
                              cmd, vg_name);
            return LVM_EVGREDUCE_NO_EMPTY_PV;
         }
         if ( np == vg->pv_cur) {
            np--;
            free ( pv_names[np]);
            pv_names[np] = NULL;
         }
         if ( opt_v > 0) {
            for ( p = 0; p < np; p++) {
               printf ( "%s -- reducing volume group \"%s\" by physical "
                        "volume \"%s\"\n",
                        cmd, vg_name, pv_names[p]);
            }
         }
         optind = 0; argc = np;
      }
   } else if ( opt_a != 0) {
      fprintf ( stderr, "%s -- you can't give option -a AND enter "
                        "physical volume paths\n\n", cmd);
      return LVM_EINVALID_CMD_LINE;
   } else {
      pv_names = argv + optind;
      argc -= optind;
   }

   np = vg->pv_cur;

   /* check, if PVs are all defined and in this volume group
      and reduce them in VG structures */
   if ( opt_v > 0) printf ( "%s -- reducing VGDA structures of volume "
                            "group \"%s\" \n",
                            cmd, vg_name);
   if ( ( ret = vg_setup_for_reduce ( pv_names, argc,
                                      vg, &pv_this, &error_pv_name)) < 0) {
      if ( ret == -LVM_EVG_SETUP_FOR_REDUCE_PV_INVALID) {
         fprintf ( stderr, "%s -- ERROR: invalid physical volume "
                           "name \"%s\"\n\n",
                           cmd, error_pv_name);
      } else if ( ret == -LVM_EVG_SETUP_FOR_REDUCE_LV) {
         fprintf ( stderr, "%s -- ERROR: can't reduce volume group \"%s\" by "
                           "used physical volume \"%s\"\n\n",
                           cmd, vg_name, error_pv_name);
      } else if ( ret == -LVM_EVG_SETUP_FOR_REDUCE_LAST_PV) {
         fprintf ( stderr, "%s -- can't reduce volume group \"%s\" "
                           "to zero physical volumes\n\n", cmd, vg_name);
      } else if ( ret == -LVM_EVG_SETUP_FOR_REDUCE_LAST_PV_NOT_IN_VG) {
         fprintf ( stderr, "%s -- physical volume \"%s\" doesn't belong "
                           "to volume group \"%s\"\n",
                           cmd, error_pv_name, vg_name);
      } else if ( ret == -LVM_EVG_SETUP_FOR_REDUCE_NO_PV_TO_REDUCE) {
         fprintf ( stderr, "%s -- no physical volumes found to "
                           "reduce \"%s\"\n\n",
                           cmd, vg_name);
      } else {
         fprintf ( stderr, "%s -- ERROR \"%s\" reducing VGDA structures of "
                           "volume group \"%s\"\n",
                           cmd, lvm_error ( ret), vg_name);
      }
      return LVM_EVGREDUCE_VG_SETUP;
   }

   np -= ret;
   if ( opt_v > 0) {
      printf ( "%s -- volume group \"%s\" will be reduced by "
               "%d physical volume%s\n",
               cmd, vg_name, np, np > 1 ? "s" : "");
      printf ( "\n");
   }

   lvm_dont_interrupt ( 0);

   /* reduce vg */
   if ( opt_v > 0) printf ( "%s -- reducing physical volumes in VGDA "
                            "in kernel\n", cmd);
   for ( p = 0; pv_this[p] != NULL; p++) {
      if ( ( ret = vg_reduce ( vg_name, pv_this[p], vg)) < 0) {
         fprintf ( stderr, "%s -- ERROR \"%s\" reducing volume group \"%s\" "
                           "by physical volume \"%s\" in kernel\n",
                           cmd, lvm_error ( ret), vg_name, pv_this[p]->pv_name);
         return LVM_EVGREDUCE_VG_REDUCE;
      }
   }

   /* store vg on disk(s) */
   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_EVGREDUCE_VG_WRITE;
   }

   /* setup freed PVs and store them */
   error = 0;
   for ( p = 0; pv_this[p] != NULL; p++) {
      if ( opt_v > 0) printf ( "%s -- initializing reduced physical "
                               "volume \"%s\"\n",
                               cmd, pv_this[p]->pv_name);
      pv_setup_for_create ( pv_this[p]->pv_name, pv_this[p],
                            pv_this[p]->pv_size);
      if ( ( ret = pv_write ( pv_this[p]->pv_name, pv_this[p])) < 0) {
         fprintf ( stderr, "%s -- ERROR \"%s\" storing physical "
                           "volume \"%s\"\n",
                           cmd, lvm_error ( ret), pv_this[p]->pv_name);
         error++;
      }
   }

   if ( error > 0) fprintf ( stderr, "%s -- %d errors initializing reduced "
                                     "physical volume data of \"%s\"\n",
                                     cmd, error, vg_name);

   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);

   for ( p = 0; pv_this[p] != NULL; p++) {}
   printf ( "%s -- volume group \"%s\" %ssuccessfully reduced "
            "by physical volume%s:\n",
            cmd, vg_name, error > 0 ? "NOT " : "", p > 1 ? "s": "");
   for ( p = 0; pv_this[p] != NULL; p++)
      printf ( "%s -- %s\n", cmd , pv_this[p]->pv_name);
   printf ( "\n");

   free ( pv_this);
   pv_this = NULL;

   return 0;
}
