/*
 * tools/vgscan.c
 *
 * Copyright (C) 1997 - 2001  Heinz Mauelshagen, Sistina Software
 *
 * Oktober  1997
 * January  1998
 * May-July,September 1998
 * May,July,October,November 1999
 * February,July 2000
 * January,March,October 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
 *
 *    11/01/1998 - added creation of volume group directory and special files
 *    03/05/1998 - enhanced error checking with lvm_tab_vg_insert()
 *                 and lvm_tab_vg_remove()
 *    27/06/1998 - changed lvm_tab_* calling convention
 *    10/07/1998 - implemented -A option
 *    02/09/1998 - corrected some messages
 *    19/05/1999 - major rewrite to correct multiple assigned volume
 *                 group numbers and block device specials
 *               - avoided option A
 *    04/08/1999 - support for snapshot logical volumes
 *    06/10/1999 - implemented support for long options
 *    30/11/1999 - delete special files and directory before
 *                 trying to create them
 *    12/02/2000 - use lvm_remove_recursive() instead of system ( "rm -fr ...
 *    15/02/2000 - use lvm_error()
 *    05/07/2000 - avoided resetting of snapshots
 *    05/01/2001 - reworded VGDA backup message (JT)
 *    23/01/2001 - added call to lvm_init (JT)
 *    12/03/2001 - ensured that empty lvmtab is created first (HM)
 *    05/10/2001 - tidy usage() (HM)
 *    24/10/2001 - support for persistent VG/LV numbers (HM)
 *
 */

#include <lvm_user.h>

#define EXPORT_IMPORT_MSG "please vgexport/vgimport that VG or use option -f\n"

/* internal function prototypes */
int _vgscan_do_read ( char *, vg_t **);
int _vgscan_do_insert ( char *);
int _vgscan_do_backup_and_create_group ( char *, vg_t *);

char *cmd = NULL;
int opt_v = 0;


#ifdef DEBUG
int opt_d = 0;
#endif


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

   if (ret == 0) {
      printf("%s (IOP %d)\n\n", lvm_version, LVM_LIB_IOP_VERSION);
      printf("%s -- scan for available volume groups for use by LVM\n", cmd);
      stream = stdout;
   }

   fprintf (stream, "\n%s "
           DEBUG_HELP_2
           "\n\t[-f/--force_numbers]\n\t"
           "[-h/--help]\n\t"
           "[-r/--remove_snapshots [VgNameToRemoveSnapshotsFrom]]\n\t"
           "[-v/--verbose]\n\n",
           cmd);

   exit ( ret);
}


int main ( int argc, char **argv)
{
   int blk_dev_free_count = 0;
   int c = 0;
   int change = 0;
   int i = 0;
   int l = 0;
   int ret = 0;
   int opt_f = 0;
   int opt_r = 0;
   char *options = "fA:h?r::v" DEBUG_SHORT_OPTION;
   struct option long_options[] = {
      { "autobackup",       required_argument, NULL, 'A'},
      DEBUG_LONG_OPTION
      { "force_numbers",    no_argument,       NULL, 'f'},
      { "help",             no_argument,       NULL, 'h'},
      { "remove_snapshots", optional_argument, NULL, 'r'},
      { "verbose",          no_argument,       NULL, 'v'},
      { NULL,               0,                 NULL, 0},
   };
   char *remove_vg_name = NULL;
   char *vg_name_ptr = NULL;
   char **vg_name_array = NULL;
   kdev_t *blk_dev_free = NULL;
   vg_t *vg = NULL;
   lv_t *lv = NULL;

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

   while ( ( c = getopt_long ( argc, argv, options,
                               long_options, NULL)) != EOF) {
      switch ( c) {
         DEBUG_HANDLE_CASE_D;

         case 'f':
            opt_f++;
            break;

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

         case 'r':
            opt_r++;
            remove_vg_name = optarg;
            break;

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

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

   CMD_MINUS_CHK;
   LVM_CHECK_IOP;
   LVM_LOCK ( 1);

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

   lvm_dont_interrupt ( 0);

   if ( opt_v > 0) printf ( "%s -- removing \"%s\" and \"%s\"\n",
                            cmd, LVMTAB, LVMTAB_DIR);
   unlink ( LVMTAB);
   lvm_remove_recursive ( LVMTAB_DIR);

   if ( opt_v > 0) printf ( "%s -- creating empty \"%s\" and \"%s\"\n",
                            cmd, LVMTAB, LVMTAB_DIR);
   if ( ( ret = lvm_tab_create ()) < 0) {
      fprintf ( stderr, "%s -- ERROR \"%s\" creating lvmtab\n",
                        cmd, lvm_error ( ret));
      ret = LVM_EVGSCAN_NO_VG;
      goto vgscan_end;
   }

   printf ( "%s -- reading all physical volumes (this may take a while...)\n",
            cmd);
   if ( ( vg_name_array = vg_check_exist_all_vg ()) != NULL) {
      /* all *active* volume groups first so that i don't
         have to change the minor numbers in the active kernel VGDAs */
      if ( opt_v > 0) printf ( "%s -- scanning for all active volume "
                               "group(s) first\n", cmd);
      for ( i = 0; vg_name_array[i] != NULL; i++) {
         vg_name_ptr = vg_name_array[i];
         if ( vg_check_active ( vg_name_ptr) != TRUE) continue;
         printf ( "%s -- found active volume group \"%s\"\n",
                  cmd, vg_name_ptr);
         if ( _vgscan_do_read ( vg_name_ptr, &vg) < 0) continue;
         if ( ( ret = _vgscan_do_insert ( vg_name_ptr)) != 0) return ret;
         if ( _vgscan_do_backup_and_create_group ( vg_name_ptr, vg) < 0)
            continue;
      }

      /* deal with all *inactive* volume groups now */
      for ( i = 0; vg_name_array[i] != NULL; i++) {
         char *status_str;
         change = 0;
         vg_name_ptr = vg_name_array[i];
         if ( vg_check_active ( vg_name_ptr) == TRUE) continue;
         if ( _vgscan_do_read ( vg_name_ptr, &vg) < 0) continue;
         if ( vg->vg_status & VG_EXPORTED) status_str = "exported";
         else status_str = "inactive";
         printf ( "%s -- found %s volume group \"%s\"\n",
                  cmd, status_str, vg_name_ptr);
         if ( vg->vg_status & VG_EXPORTED) continue;
         if ( lvm_tab_check_free_vg_number ( vg) == FALSE) {
            if ( opt_f == 0) {
               fprintf ( stderr, "%s -- ERROR: VG \"%s\" reuses an existing VG "
                                 "number; "
                                 EXPORT_IMPORT_MSG,
                                 cmd, vg->vg_name);
               continue;
            } else {
               printf ( "%s -- forcing change of number for volume "
                        "group \"%s\"\n",
                        cmd, vg->vg_name);
               ret = lvm_tab_get_free_vg_number();
               if ( ret < 0) {
                  fprintf ( stderr, "%s -- ERROR getting a free "
                                    "volume group number\n\n",
                                    cmd);
                  return LVM_EVGSCAN_NO_DEV;
               }
               vg->vg_number = ret;
               change++;
            }
         }

         if ( opt_v > 0)
            printf ( "%s -- getting block device numbers for "
                     "logical volumes\n\n",
                     cmd);
         if ( ( blk_dev_free_count =
                lvm_tab_get_free_blk_dev ( &blk_dev_free)) < 0) {
            fprintf ( stderr, "%s -- ERROR: no free block device "
                              "specials available\n\n",
                              cmd);
            return LVM_EVGSCAN_NO_DEV;
         }

         if ( blk_dev_free_count < vg->lv_cur) {
            fprintf ( stderr, "%s -- only %d free LVM block devices "
                              "available vs. %d needed\n\n",
                              cmd, blk_dev_free_count, vg->lv_cur);
            return LVM_EVGSCAN_NO_DEV;
         }

         /* remove snapshots */
         if ( opt_r > 0) {
            char lv_name[NAME_LEN];
            if ( remove_vg_name != NULL &&
                 strcmp ( remove_vg_name, vg->vg_name) != 0) break;
            for ( l = 0; l < vg->lv_max; l++) {
               lv = vg->lv[l];
               if ( lv == NULL) continue;
               if ( lv->lv_access & LV_SNAPSHOT) {
                  /* release it in structures */
                  printf ( "%s -- releasing snapshot logical volume \"%s\"\n",
                           cmd, lv->lv_name);
                  memset ( lv_name, 0, sizeof ( lv_name));
                  /* keep lv_name for potential error message after release */
                  strncpy ( lv_name, lv->lv_name, sizeof ( lv_name) - 1);
                  if ( ( ret = lv_release ( vg, lv->lv_name)) < 0) {
                     fprintf ( stderr, "%s -- ERROR \"%s\" releasing snapshot "
                                       "logical volume \"%s\"\n\n", 
                                       cmd, lvm_error ( ret), lv_name);
                     return LVM_EVGSCAN_LV_RELEASE;
                  }
                  change++;
               }
            }
         }

         /* now check for LV numbers in use */
         if ( opt_v > 0)
            printf ( "%s -- checking block device numbers of "
                     "logical volumes\n\n",
                     cmd);
         if ( lvm_tab_check_free_lv_numbers ( vg) == FALSE) {
            if ( opt_f == 0) {
               fprintf ( stderr, "%s -- volume group \"%s\" reuses an existing "
                                 "logical volume number; "
                                 EXPORT_IMPORT_MSG
                                 "\n", cmd, vg->vg_name);
               continue;
            } else {
               int blk_dev;

               for ( blk_dev = l = 0; l < vg->lv_max; l++) {
                  lv = vg->lv[l];
                  if ( lv == NULL) continue;
                  if ( lvm_tab_check_free_lv_number ( lv) == FALSE) {
                     printf ( "%s -- changing minor number on \"%s\"\n",
                              cmd, lv->lv_name);
                     if ( lv->lv_access & LV_SNAPSHOT_ORG) {
                        int ll;
                        for ( ll = 0; ll < vg->lv_max; ll++) {
                           lv_t *lv1 = vg->lv[ll];
                           if ( lv1 == NULL || lv1 == lv) continue;
                           if ( lv1->lv_access & LV_SNAPSHOT &&
                                lv1->lv_snapshot_minor == MINOR(lv->lv_dev)) {
                              printf ( "%s -- correcting snapshot "
                                       "relationship of \"%s\"\n",
                                       cmd, lv1->lv_name);
                              lv1->lv_snapshot_minor =
                                 MINOR ( blk_dev_free[blk_dev]);
                           }
                        }
                     }
                     lv->lv_dev = blk_dev_free[blk_dev++];
                     change++;
                     if ( blk_dev == blk_dev_free_count) {
                        fprintf ( stderr,
                                  "%s -- run out of free block devices "
                                  "(only %d were available)\n\n",
                                  cmd, blk_dev_free_count);
                        return LVM_EVGSCAN_NO_DEV;
                     }
                  }
               }
            }
         }

         if ( change > 0 && ( ret = vg_write_with_pv_and_lv ( vg)) < 0) {
               fprintf ( stderr, "%s -- ERROR \"%s\"storing volume group data "
                                 "of \"%s\" on disk(s)\n\n",
                                 cmd, lvm_error ( ret), vg->vg_name);
               return LVM_EVGSCAN_VG_WRITE;
            }
         if ( ( ret = _vgscan_do_insert ( vg_name_ptr)) != 0) return ret;
         if ( _vgscan_do_backup_and_create_group ( vg_name_ptr, vg) < 0)
            continue;
      }
   }
   vg_free(vg, FALSE);
   vg = NULL;

vgscan_end:
   lvm_interrupt ();
   LVM_UNLOCK ( 1);

   if ( ret == 0) {
      printf ( "%s -- \"%s\" and \"%s\" successfully created\n",
               cmd, LVMTAB, LVMTAB_DIR);
      printf ( "%s -- WARNING: This program does not do a VGDA backup "
	       "of your volume group%s\n\n", cmd, i > 1 ? "s" : "");
   } else if ( ret == LVM_EVGSCAN_NO_VG) {
      fprintf ( stderr, "%s -- no volume groups found\n\n", cmd);
   } else {
      fprintf ( stderr, "%s -- ERROR \"%s\" creating \"%s\" and \"%s\"\n\n",
                        cmd, lvm_error ( ret), LVMTAB, LVMTAB_DIR);
      ret = LVM_EVGSCAN_LVMTAB;
   }

   return ret;
}


/* internal functions */
int _vgscan_do_read ( char *vg_name_ptr, vg_t **vg) {
   int ret = 0;

   if ( opt_v > 0) printf ( "%s -- reading data of volume "
                            "group \"%s\" from physical "
                            "volume(s)\n",
                            cmd, vg_name_ptr);
   vg_free(*vg, FALSE);
   *vg = NULL;
   if ( ( ret = vg_read_with_pv_and_lv ( vg_name_ptr,
                                         vg)) < 0) {
      fprintf ( stderr, "%s -- ERROR \"%s\" can't get data of "
                        "volume group \"%s\" from physical "
                        "volume(s)\n",
                        cmd, lvm_error ( ret), vg_name_ptr);
      return -1;
   }
   return 0;
}

int _vgscan_do_insert ( char *vg_name_ptr) {
   int ret = 0;

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


int _vgscan_do_backup_and_create_group ( char *vg_name_ptr, vg_t *vg) {
   int ret = 0;

   if ( opt_v > 0) printf ( "%s -- backing up volume group "
                            "\"%s\"\n", \
                            cmd, vg_name_ptr);
   if ( ( ret = vg_cfgbackup ( vg_name_ptr, LVMTAB_DIR,
                               opt_v, vg)) < 0) {
      fprintf ( stderr, "%s -- ERROR: unable to do a backup "
                        "of volume group \"%s\"\n",
                        cmd, vg_name_ptr);
      if ( ( ret = lvm_tab_vg_remove ( vg_name_ptr)) < 0) {
         fprintf ( stderr, "%s -- ERROR \"%s\" removing volume group "
                           "\"%s\" from \"%s\"\n",
                           cmd, lvm_error ( ret), vg_name_ptr, LVMTAB);
      }
      return -1;
   }
   if ( opt_v > 0) printf ( "%s -- removing special files and directory "
                            "for volume group \"%s\"\n",
                            cmd, vg_name_ptr);
   if ( ( ret = vg_remove_dir_and_group_and_nodes ( vg->vg_name)) < 0) {
      fprintf ( stderr, "%s -- ERROR \"%s\" removing volume group "
                        "directory and special files\n",
                        cmd, lvm_error ( ret));
   }
   if ( ( ret = vg_create_dir_and_group_and_nodes ( vg,
                                                    opt_v)) < 0) {
      fprintf ( stderr, "%s -- ERROR \"%s\" creating volume group "
                        "directory and special files\n",
                        cmd, lvm_error ( ret));
      return -1;
   }
   return 0;
}
