/*
 * tools/vgcfgrestore.c
 *
 * Copyright (C) 1997 - 2002  Heinz Mauelshagen, Sistina Software
 *
 * June,October 1997
 * May 1998
 * October 1999
 * February 2000
 * February 2002
 *
 * 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
 *
 *    12/10/1997  - removed vg_check_exist
 *                - added pv_read() to check againts overwrite of
 *                  physical volume belonging to a different volume group
 *                - added check for physical volume size
 *    08/11/1997  - added message for malloc errors
 *                - moved restore code to lib/vg_cfgrestore.c to use
 *                  it with lvm_tab code
 *                - added -n option to support test runs
 *    02/05/1998 - enhanced error checking
 *    02/05/1998 - used pv_check_new() instead of str-function
 *    16/05/1998 - added lvmtab checking
 *    21/05/1998 - implemented option -o
 *    06/10/1999 - implemented support for long options
 *    12/02/2000 - avoided catch 22 situation by avoiding lvmtab check 
 *    15/02/2000 - use lvm_error()
 *    23/01/2001 - added call to lvm_init (JT)
 *    06/04/2001 - Tidy usage message and command-line options.
 *    09/04/2001 - Fix spelling of argument in error message
 *    06/02/2002 - Fix -o bug [HM]
 *    07/02/2002 - fixes for > 1TB support
 *               - avoid creating group and nodes file; let vgscan do that
 *    13/02/2002 - implemented option -b
 *    17/02/2002 - avoid trying a restore to physical volumes smaller
 *                 than the VGDA size
 *               - use pv_get_size_ll() for library compatibility
 *
 */

#include <lvm_user.h>

char *cmd = NULL;

#ifdef DEBUG
int opt_d = 0;
#endif

int vg_backup_number = 0;
char vg_backup_path[PATH_MAX];
char *vg_conf_path = NULL;

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 -- restore volume group data to a disk\n", cmd);
   }

   fprintf ( stream, "%s "
             "[-b|--backup_number BackupNumber] "
             DEBUG_HELP_2
             "\n\t[-f|--file VGConfPath] "
             "[-i|--ignoresize]\n\t"
             "[-l[l]|--list [--list]]"
             "[-n|--name VolumeGroupName]\n\t"
             "[-h|--help]"
             "[-o|--oldpath OldPhysicalVolumePath] "
             "[-t|--test]\n\t"
             "[-v|--verbose]"
             "[--version] "
             "[PhysicalVolumePath]\n",
             cmd);
   exit ( ret);
}

inline void _setup_vg_backup_path ( int opt_b, int opt_f, char *vg_name) {
   if ( opt_f == 0) {
      memset ( vg_backup_path, 0, sizeof ( vg_backup_path));
      if ( opt_b == 0) {
         snprintf ( vg_backup_path, sizeof ( vg_backup_path) - 1,
                                    "%s/%s.conf", VG_BACKUP_DIR, vg_name);
      } else {
         snprintf ( vg_backup_path, sizeof ( vg_backup_path) - 1,
                                    "%s/%s.conf.%d.old",
                                    VG_BACKUP_DIR, vg_name, vg_backup_number);
      }
      printf ( "%s -- INFO: using backup file \"%s\"\n", cmd, vg_backup_path);
   } else {
      int sz = 0;
      if ( strchr ( vg_conf_path, '/') == NULL)
         sz = sprintf ( vg_backup_path, "./");
      snprintf ( &vg_backup_path[sz], sizeof ( vg_backup_path) - sz - 1,
                                      "%s", vg_conf_path);
   }
}

inline char *_from_vg_backup_path ( char *path) {
   int i = 0;
   static char vg_name[NAME_LEN];
   char *p = strrchr ( path, '/');

   if ( p == NULL) return NULL;
   memset ( vg_name, 0, sizeof ( vg_name));
   for ( p++; *p != '\0' && *p != '.'; p++) vg_name[i++] = *p;
   return vg_name;
}


int main ( int argc, char **argv)
{
   int c = 0;
   int p = 0;
   int opt_b = 0;
   int opt_f = 0;
   int opt_i = 0;
   int opt_l = 0;
   int opt_n = 0;
   int opt_o = 0;
   int opt_t = 0;
   int opt_v = 0;
   int index = -1;
   int pv_index = -1;
   int pv_index_old = -1;
   int ret = 0;
   long long pv_size;
   char *options = "b:f:h?iln:o:tv" DEBUG_SHORT_OPTION;
   struct option long_options[] = {
      { "backup_number", required_argument, NULL, 'b'},
      DEBUG_LONG_OPTION
      { "file",          required_argument, NULL, 'f'},
      { "help",          no_argument,       NULL, 'h'},
      { "ignoresize",    no_argument,       NULL, 'i'},
      { "list",          no_argument,       NULL, 'l'},
      { "name",          required_argument, NULL, 'n'},
      { "oldpath",       required_argument, NULL, 'o'},
      { "rename",        required_argument, NULL, 'r'},
      { "test",          no_argument,       NULL, 't'},
      { "verbose",       no_argument,       NULL, 'v'},
      { "version",       no_argument,       NULL, 22},
      { NULL, 0, NULL, 0}
   };
   char *pv_name = NULL;
   char *pv_name_old = NULL;
   char *vg_name = NULL;
   pv_t *pv = NULL;
   vg_t vg;

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

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

   while ( ( c = getopt_long ( argc, argv, options,
                               long_options, NULL)) != EOF) {
      switch ( c) {
         case 'b':
            vg_backup_number = lvm_check_number ( optarg, FALSE);
            opt_b++;
            break;

         DEBUG_HANDLE_CASE_D;

         case 'f':
            vg_conf_path = optarg;
            opt_f++;
            break;

         case 'h':
            usage(0);

         case 'i':
            opt_i++;
            break;

         case 'l':
            opt_l++;
            break;

         case 'n':
            vg_name = optarg;
            if ( vg_check_name ( vg_name) < 0) {
               fprintf ( stderr, "%s -- invalid volume group name \"%s\"\n",
                                 cmd, vg_name);
               usage ( LVM_EINVALID_CMD_LINE);
            }
            opt_n++;
            break;

         case 'o':
            pv_name_old = optarg;
            if ( pv_check_name ( pv_name_old) < 0) {
               fprintf ( stderr, "%s -- invalid physical volume path \"%s\"\n",
                                 cmd, pv_name_old);
               usage ( LVM_EINVALID_CMD_LINE);
            }
            opt_o++;
            break;

         case 't':
            opt_t++;
            break;

         case 'v':
            opt_v++;
            break;

         case 22:
            printf ("%s: %s (IOP %d)\n", cmd, lvm_version, LVM_LIB_IOP_VERSION);
            return 0;

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

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

   if ( opt_f == 0 && opt_n == 0) {
      fprintf ( stderr, "%s -- please give either option f or n or both\n",
                        cmd);
      usage ( LVM_EINVALID_CMD_LINE);
   }
   if ( opt_f != 0 && opt_b != 0) {
      fprintf ( stderr, "%s -- option b is not possible with option f\n", cmd);
      usage ( LVM_EINVALID_CMD_LINE);
   }

   _setup_vg_backup_path ( opt_b, opt_f, vg_name);

   if ( opt_l == 0) {
      if ( optind != argc - 1) {
         printf ( "%s -- please enter a physical volume name\n", cmd);
         usage ( LVM_EVGCFGRESTORE_PV_MISSING);
      }
      pv_name = argv[optind];
      if ( pv_check_name ( pv_name) < 0) {
         fprintf ( stderr, "%s -- invalid physical volume \"%s\"\n",
                           cmd, pv_name);
         usage ( LVM_EVGCFGRESTORE_PV_CHECK_NAME);
      }
      if ( opt_f == 0 && opt_n == 0) {
         fprintf ( stderr, "%s -- please enter a volume group name or a "
                           "volume group backup\n", cmd);
         usage ( LVM_EINVALID_CMD_LINE);
      }
   }

   if ( opt_n == 0) vg_name = _from_vg_backup_path ( vg_backup_path);
   if ( opt_t == 0 && opt_l == 0 && vg_check_active ( vg_name) == TRUE) {
      fprintf ( stderr,
                "%s -- can't restore part of active volume group \"%s\"\n",
                        cmd, vg_name);
      usage ( LVM_EVGCFGRESTORE_VG_CHECK_ACTIVE);
   }


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

   if ( opt_v > 0) printf ( "%s -- restoring volume group \"%s\" from \"%s\"\n",
                            cmd, vg_name, vg_backup_path);
   if ( ( ret = vg_cfgrestore ( vg_name, vg_backup_path, opt_v, &vg)) < 0) {
      if ( ret == -LVM_EVG_CFGRESTORE_OPEN) {
         fprintf ( stderr,
                   "%s -- a backup for volume group %s is not available\n\n",
                   cmd, vg_name);
      } else {
         fprintf ( stderr,
                   "%s -- ERROR \"%s\" restoring volume group \"%s\"\n\n",
                   cmd, lvm_error ( ret), vg_name);
      }
      return LVM_EVGCFGRESTORE_VG_CFGRESTORE;
   }

   if ( opt_n == 0) {
      vg_name = vg.vg_name;
      printf ( "%s -- this is a backup of volume group \"%s\"\n", cmd, vg_name);
   } else if ( strcmp ( vg_name, vg.vg_name) != 0) {
      /* user wants to change volume group name during restore ->
         change it in VG struct, all PV structs and in all LV structs */
      int l;
      printf ( "%s -- renaming volume group \"%s\" in backup to "
               " volume group \"%s\"\n\n",
               cmd, vg.vg_name, vg_name);
      memset  ( vg.vg_name, 0, sizeof ( vg.vg_name));
      strncpy ( vg.vg_name, vg_name, sizeof ( vg.vg_name) - 1);
      for ( p = 0; p < vg.pv_cur; p++) {
         if ( vg.pv[p] == NULL) continue;
         memset  ( vg.pv[p]->vg_name, 0, sizeof ( vg.pv[p]->vg_name));
         strncpy ( vg.pv[p]->vg_name, vg_name, sizeof ( vg.pv[p]->vg_name) - 1);
      }
      for ( l = 0; l < vg.lv_max; l++) {
         if ( vg.lv[l] == NULL) continue;
         strcpy ( vg.lv[l]->lv_name, lv_change_vgname ( vg_name, vg.lv[l]->lv_name));
      }
   }

   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_EVGCFGRESTORE_VG_CHECK_CONSISTENCY;
   } else if ( opt_t > 0) printf ( "%s -- backup of volume group \"%s\" "
                                   " is consistent\n",
                                   cmd, vg_name);

   pv_index = pv_index_old = -1;
   if ( opt_o > 0) {
      if ( pv_check_in_vg ( &vg, pv_name_old) == FALSE) {
         fprintf ( stderr, "%s -- physical volume \"%s\" doesn't belong "
                           "to volume group \"%s\" backup\n\n",
                           cmd, pv_name_old, vg_name);
         return LVM_EVGCFGRESTORE_PV_CHECK_IN_VG;
      } else pv_index_old = pv_get_index_by_name ( &vg, pv_name_old);
      if ( pv_check_in_vg ( &vg, pv_name) == TRUE) {
         fprintf ( stderr, "%s -- \"%s\" may not be an actual physical volume "
                           "of volume group \"%s\"\n\n", cmd, pv_name, vg_name);
         return LVM_EVGCFGRESTORE_PV_CHECK_IN_VG;
      } else pv_index = pv_get_index_by_name ( &vg, pv_name);
   } else {
      if ( pv_check_in_vg ( &vg, pv_name) == FALSE) {
         fprintf ( stderr, "%s -- physical volume \"%s\" doesn't belong "
                           "to volume group \"%s\"\n\n",
                           cmd, pv_name, vg_name);
         return LVM_EVGCFGRESTORE_PV_CHECK_IN_VG;
      } else pv_index = pv_get_index_by_name ( &vg, pv_name);
   }

   if ( opt_l > 0) {
      if ( opt_l == 1) vg_show ( &vg);
      else             vg_show_with_pv_and_lv ( &vg);
      printf ( "\n");
   } else {
      /* no test run */
      if ( opt_t == 0) {
         /* check given physical volume */
         if ( opt_v > 0) printf ( "%s -- reading physical volume \"%s\"\n",
                                  cmd, pv_name);
         if ( ( ret = pv_read ( pv_name, &pv, NULL)) < 0 &&
              ret != -LVM_EPV_READ_MD_DEVICE) {
            fprintf ( stderr, "%s -- ERROR \"%s\" reading physical "
                              "volume \"%s\"\n\n",
                              cmd, lvm_error ( ret), pv_name);
            return LVM_EVGCFGRESTORE_PV_READ;
         }

         if ( opt_v > 0) printf ( "%s -- checking for new physical "
                                  "volume \"%s\"\n",
                                  cmd, pv_name);
         if ( pv_check_new ( pv) == FALSE) {
            if ( vg_check_name ( pv->vg_name) == 0 &&
                 strcmp ( pv->vg_name, vg_name) != 0) {
               fprintf ( stderr, "%s -- physical volume \"%s\" belongs "
                                 "to volume group \"%s\"\n\n",
                                 cmd, pv->pv_name, pv->vg_name);
               return LVM_EVGCFGRESTORE_PV_CHECK_IN_VG;
            }
         }

         if ( pv_index_old > -1) index = pv_index_old;
         else                    index = pv_index;

         pv_size = pv_get_size_ll ( pv_name, NULL);
         if ( opt_i == 0) {
            if ( pv_size < 0LL || ( uint) pv_size != vg.pv[index]->pv_size) {
               fprintf ( stderr,
                         "%s -- size of physical volume %s differs "
                         "from backup\n\n",
                         cmd, pv_name);
               return LVM_EVGCFGRESTORE_PV_GET_SIZE;
            }
         } else {
            printf ( "%s -- forcing write of VGDA of \"%s\" to physical "
                     "volume \"%s\"\n%s -- ignoring size mismatches\n",
                     cmd, vg_name, pv_name, cmd);
            if ( pv_size < LVM_VGDA_SIZE(vg.pv[index]) / SECTOR_SIZE) {
               fprintf ( stderr,
                         "%s -- size of physical volume %s is too small "
                         "to store VGDA\n\n",
                         cmd, pv_name);
               return LVM_EVGCFGRESTORE_PV_GET_SIZE;
            }
         }

         vg.vg_number = lvm_tab_get_free_vg_number();
         for ( p = 0; p < vg.pv_cur; p++) {
            /* zero all pv_names for vg_write_with_pv_and_lv() which is
               necessary for all PV UUIDs to be written */
            memset ( vg.pv[p]->pv_name, 0, sizeof (vg.pv[p]->pv_name));
         }
         /* insert the new PV name so that vg_write_with_pv_and_lv()
            does write the complete VGDA to that one */
         strncpy( vg.pv[index]->pv_name, pv_name,
                  sizeof(vg.pv[index]->pv_name) - 1);

         /* store VGDA from backup on physical volume */
         if ( opt_v)
            printf ( "%s -- writing VGDA of \"%s\" to physical volume \"%s\"\n",
                     cmd, vg_name, vg.pv[index]->pv_name);
         if ( ( ret = vg_write_with_pv_and_lv ( &vg)) < 0) {
            fprintf ( stderr,
                      "%s -- ERROR \"%s\" storing VGDA of \"%s\" on disks\n\n",
                      cmd, lvm_error ( ret), vg_name);
            return LVM_EVGCFGRESTORE_VG_WRITE;
         }
   
         if ( opt_v)
            printf ( "%s -- removing any special files for \"%s\"\n",
                     cmd, vg_name);
         vg_remove_dir_and_group_and_nodes ( vg_name);
   
         printf ( "%s -- VGDA for \"%s\" successfully restored "
                  "to physical volume \"%s\"\n",
                  cmd, vg_name, pv_name);
         if ( opt_f > 0)
            printf ( "%s -- you may not have an actual backup of "
                     "restored volume group \"%s\"\n", cmd, vg_name);
      } else printf ( "%s -- test run for volume group \"%s\" end\n",
                      cmd, vg_name);
   }

   printf ( "\n");

   lvm_interrupt ();
   LVM_UNLOCK ( 0);

   return 0;
}
