/*
 * tools/lvchange.c
 *
 * Copyright (C) 1997 - 2000  Heinz Mauelshagen, Sistina Software
 *
 * May,October 1997
 * April-June,August 1998
 * February,July,August,October 1999
 * February,November 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
 *
 *    10/16/1907 - change lv_get_index to lv_get_index_by_name
 *    09/11/1997 - added lvmtab handling
 *    30/04/1998 - change to lv_status_byname()
 *    12/05/1998 - added some error messages
 *    16/05/1998 - added lvmtab checking
 *    27/06/1998 - changed lvm_tab_* calling convention
 *    04/09/1998 - fixed lv_write_all_pv() called every time
 *    09/02/1999 - implemented change of read ahead sectors
 *    04/08/1999 - support for snapshot logical volumes
 *    15/08/1999 - enhanced to avoid attribute changes on
 *                 snapshot logical volumes
 *    06/10/1999 - implemented support for long options
 *    15/02/2000 - use lvm_error()
 *    07/11/2000 - used LVM_VG_NAME environment variable
 *
 */


#include <lvm_user.h>

char *cmd = NULL;

#ifdef DEBUG
int opt_d = 0;
#endif

int main ( int argc, char **argv)
{
   int blkdev = -1;
   int c = 0;
   int doit = 0;
   int doit_sum = 0;
   int l = 0;
   int lv_access = 0; /* bitfield */
   int lv_allocation = 0; /* bitfield */
   int lv_stat = 0; /* bitfield */
   int opt_A = 1;
   int opt_A_set = 0;
   int opt_a = 0;
   int opt_C = 0;
   int opt_p = 0;
   int opt_r = 0;
   int opt_v = 0;
   long read_ahead = 0;
   int ret = 0;
   char *lv_name = NULL;
#ifdef DEBUG
   char *options = "A:a:C:dh?p:r:v";
#else
   char *options = "A:a:C:h?p:r:v";
#endif
   struct option long_options[] = {
      { "autobackup", required_argument, NULL, 'A'},
      { "available",  required_argument, NULL, 'a'},
      { "contiguous", required_argument, NULL, 'C'},
#ifdef DEBUG
      { "debug",      no_argument,       NULL, 'd'},
#endif
      { "help",       no_argument,       NULL, 'h'},
      { "permission", required_argument, NULL, 'p'},
      { "readahead",  required_argument, NULL, 'r'},
      { "verbose",    no_argument,       NULL, 'v'},
      { NULL, 0, NULL, 0}
   };
   char *vg_name = NULL;
   lv_t *lv = NULL;
   vg_t *vg = NULL;

   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;

         case 'a':
            if ( opt_a > 0) {
               fprintf ( stderr, "%s -- a option already given\n\n", cmd);
               return LVM_EINVALID_CMD_LINE;
            }
            opt_a++;
            if ( strcmp ( optarg, "y") == 0) { lv_stat |= LV_ACTIVE; break;}
            if ( strcmp ( optarg, "n") == 0) break;
            fprintf ( stderr, "%s -- a option argument \"%s\" invalid\n\n",
                      cmd, optarg);
            return LVM_EINVALID_CMD_LINE;
            break;

         case 'C':
            if ( opt_C > 0) {
               fprintf ( stderr, "%s -- C option already given\n\n", cmd);
               return LVM_EINVALID_CMD_LINE;
            }
            opt_C++;
            if ( strcmp ( optarg, "y") == 0) { lv_allocation |= LV_CONTIGUOUS;
                                               break;}
            if ( strcmp ( optarg, "n") == 0) break;
            fprintf ( stderr, "%s -- C option argument \"%s\" invalid\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 -- Logical Volume Change\n\n"
                     "Synopsis:\n"
                     "---------\n\n"
                     "%s\n"
                     "\t[-A/--autobackup y/n]\n"
                     "\t[-a/--available y/n]\n"
                     "\t[-C/--contiguous y/n]\n"
#ifdef DEBUG
                     "\t[-d/--debug]\n"
#endif
                     "\t[-h/-?/--help]\n"
                     "\t[-p/--permission r/rw]\n"
                     "\t[-r/--readahead ReadAheadSectors]\n"
                     "\t[-v/--verbose]\n"
                     "\tLogicalVolume[Path] [LogicalVolume[Path]...]\n\n",
                     lvm_version, LVM_LIB_IOP_VERSION,  cmd, cmd);
            return 0;
            break;

         case 'p':
            if ( opt_p > 0) {
               fprintf ( stderr, "%s -- p option already given\n\n", cmd);
               return LVM_EINVALID_CMD_LINE;
            }
            opt_p++;
            if ( strcmp ( optarg, "rw") == 0) {
               lv_access = ( LV_READ | LV_WRITE);
               break;
            }
            if ( strcmp ( optarg, "r") == 0) { lv_access = LV_READ; break;}
            fprintf ( stderr, "%s -- p option argument \"%s\" invalid\n\n",
                      cmd, optarg);
            return LVM_EINVALID_CMD_LINE;
            break;

         case 'r':
            if ( opt_r > 0) {
               fprintf ( stderr, "%s -- r option already given\n\n", cmd);
               return LVM_EINVALID_CMD_LINE;
            }
            if ( ( read_ahead = lvm_check_number ( optarg, FALSE)) < 0) {
               fprintf ( stderr, "%s -- ERROR option r argument \"%s\"\n",
                                 cmd, optarg);
               return LVM_EINVALID_CMD_LINE;
            }
            opt_r++;
            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;
   CMD_CHECK_OPT_A_SET;


   if ( opt_a == 0 && opt_C == 0 && opt_p == 0 && opt_r == 0) {
      fprintf ( stderr, "%s -- please give one or more options a, C, p "
                        "or r\n\n",
                        cmd);
      return LVM_EINVALID_CMD_LINE;
   }

   if ( optind == argc) {
      fprintf ( stderr, "%s -- please give a logical volume path\n\n", cmd);
      return LVM_ELVCHANGE_LV_PATH;
   }

   LVM_CHECK_IOP;
   LVM_LOCK ( 0);

   /* walk through logical volumes */
   for ( ; optind < argc; optind++) {
      char buffer[NAME_LEN];

      lv_name = argv[optind];

      LVM_CHECK_DEFAULT_VG_NAME ( lv_name, buffer, NAME_LEN);

      if ( opt_v > 0) printf ( "%s -- checking logical volume name\n", cmd);
      if ( lv_check_name ( lv_name) < 0) {
         fprintf ( stderr, "%s -- invalid logical volume name \"%s\"\n\n",
                   cmd, lv_name);
         continue;
      }
   
      /* does VG exist? */
      vg_name = vg_name_of_lv ( lv_name);
      if ( opt_v > 0) printf ( "%s -- checking volume group existence\n", cmd);
      if ( lvm_tab_vg_check_exist ( vg_name, NULL) != TRUE) {
         if ( vg_name != NULL) {
            fprintf ( stderr, "%s -- can't change logical volume: volume group "
                 "\"%s\" doesn't exist\n\n", cmd, vg_name);
         } else {
            fprintf ( stderr, "%s -- can't change logical volume: "
                              "need to specify volume group\n\n",
                              cmd);
         }
         return LVM_ELVCHANGE_VG_CHECK_EXIST;
      }

      if ( opt_v > 0) printf ( "%s -- checking for active volume "
                               "group \"%s\"\n",
                               cmd, vg_name);
      if ( vg_check_active ( vg_name) < 0) {
         fprintf ( stderr, "%s -- volume group \"%s\" is not active\n\n",
                   cmd, vg_name);
         continue;
      }
   
      if ( opt_v > 0) printf ( "%s -- reading volume group data of \"%s\" "
                               "from disk(s)\n", cmd, vg_name);
      if ( ( ret = lvm_tab_vg_read_with_pv_and_lv ( vg_name, &vg)) < 0) {
         if ( ret == -LVM_EPV_READ_PV_EXPORTED) {
            fprintf ( stderr, "%s -- volume group \"%s\" is exported\n\n",
                      cmd, vg_name);
         } else {
            fprintf ( stderr, "%s -- ERROR \"%s\" reading data of \"%s\"\n\n",
                      cmd, lvm_error ( ret), vg_name);
         }
         continue;
      }
   
      if ( opt_v > 0) printf ( "%s -- checking consistency of \"%s\"\n",
                               cmd, vg_name);
      if ( ( ret = vg_check_consistency_with_pv_and_lv ( vg)) < 0) {
         fprintf ( stderr, "%s -- ERROR \"%s\" checking consistency of "
                           "\"%s\"\n\n",
                           cmd, lvm_error ( ret), vg_name);
         continue;
      }
      
      if ( opt_v > 0) printf ( "%s -- checking logical volume \"%s\" "
                               "existence\n",
                               cmd, lv_name);
      if ( ( ret = lvm_tab_lv_check_exist ( lv_name)) != TRUE) {
         fprintf ( stderr, "%s -- logical volume \"%s\" doesn't exist\n\n",
                   cmd, lv_name);
         continue;
      }

      if ( opt_v > 0) printf ( "%s -- getting index of logical volume \"%s\" "
                               "in volume group \"%s\"\n",
                               cmd, lv_name, vg_name);
      if ( ( l = lv_get_index_by_name ( vg, lv_name)) < 0) {
         fprintf ( stderr, "%s -- couldn't get logical volume index in "
                           "volume group \"%s\"\n\n",
                   cmd, vg_name);
         continue;
      }
   
      if ( opt_v > 0) printf ( "%s -- getting status of \"%s\" from VGDA in "
                               "kernel\n", cmd, lv_name);
      if ( ( ret = lv_status_byname ( vg_name, lv_name, &lv)) < 0) {
         fprintf ( stderr, "%s -- ERROR \"%s\" getting status of logical "
                           "volume \"%s\"\n\n",
                           cmd, lvm_error ( ret), lv_name);
         continue;
      }
   
      if ( lv->lv_access & LV_SNAPSHOT) {
         fprintf ( stderr, "%s -- change on snapshot logical "
                           "volume \"%s\" not allowed\n", cmd, lv_name);
         continue;
      }

      if ( lv->lv_access & LV_SNAPSHOT_ORG) {
         fprintf ( stderr, "%s -- change on logical volume \"%s\" under "
                           "snapshot not allowed\n", cmd, lv_name);
         continue;
      }

      lvm_dont_interrupt ( 0);
   
      if ( opt_v > 0) printf ( "%s -- changing logical volume \"%s\" in VGDA "
                               "of kernel\n", cmd, lv_name);
      /* O_RDONLY to avoid error when LV is inactive!!! (see lvm_blk_open) */
      if ( ( blkdev = open ( lv_name, O_RDONLY)) < 0) {
         fprintf ( stderr, "%s -- ERROR \"%s\" couldn't open logical "
                           "volume \"%s\"\n\n",
                           cmd, lvm_error ( errno), lv_name);
         return LVM_ELVCHANGE_LV_OPEN;
      }
   
      /* access permission change */
      if ( opt_p > 0) {
         doit = 0;
         if ( lv_access & LV_WRITE) {
            if ( lv->lv_access & LV_WRITE)
               printf ( "%s -- logical volume \"%s\" is already writable\n",
                                                    cmd, lv_name);
            else doit++;
         } else {
            if ( ! ( lv->lv_access & LV_WRITE))
               printf ( "%s -- logical volume \"%s\" is already readonly\n",
                        cmd, lv_name);
            else doit++;
         }
   
         if ( doit > 0) {
            doit_sum++;
            lv->lv_access = lv_access;
            if ( opt_v > 0) printf ( "%s -- changing access permissions "
                                     "of logical volume \"%s\"\n",
                                     cmd, lv_name);
            if ( ( ret = ioctl ( blkdev, LV_SET_ACCESS,
                                 ( ulong) lv->lv_access)) < 0) {
               if ( ret == -EPERM) {
                  fprintf ( stderr, "%s -- operation not permitted "
                                    "for (open) logical volume \"%s\"\n\n",
                                    cmd, lv_name);
               } else {
                  fprintf ( stderr, "%s -- ERROR \"%s\" couldn't set access "
                                    "permissions for logical volume \"%s\"\n\n",
                                    cmd, lvm_error ( ret), lv_name);
               }
               return LVM_ELVCHANGE_LV_SET_ACCESS;
            }
         } else {
            printf ( "%s -- permissions of logical volume \"%s\" don't have "
                     "to be changed\n",
                     cmd, lv_name);
         }
      }
   
      /* availiability change */
      if ( opt_a > 0) {
         doit = 0;
         if ( lv_stat & LV_ACTIVE) {
            if ( lv->lv_status & LV_ACTIVE)
               printf ( "%s -- logical volume \"%s\" is already active\n",
                        cmd, lv_name);
            else doit++;
         } else {
            if ( ! ( lv->lv_status & LV_ACTIVE))
               printf ( "%s -- logical volume \"%s\" isn't active\n",
                        cmd, lv_name);
            else doit++;
         }
   
         if ( doit > 0) {
            doit_sum++;
            if ( lv_stat & LV_ACTIVE) lv->lv_status |= LV_ACTIVE;
            else                      lv->lv_status &= ~LV_ACTIVE;
            if ( opt_v > 0) printf ( "%s -- changing availiability of "
                                     "logical volume \"%s\"\n",
                                     cmd, lv_name);
            if ( ( ret = ioctl ( blkdev, LV_SET_STATUS,
                                 ( ulong) lv->lv_status)) < 0) {
               if ( ret == -EPERM) {
                  fprintf ( stderr, "%s -- operation not permitted "
                                    "for (open) logical volume \"%s\"\n\n",
                                    cmd, lv_name);
               } else {
                  fprintf ( stderr, "%s -- ERROR \"%s\" couldn't set state for "
                                    "logical volume \"%s\"\n\n",
                                    cmd, lvm_error ( ret), lv_name);
               }
               return LVM_ELVCHANGE_LV_SET_STATUS;
            }
         } else {
            printf ( "%s -- availiability of logical volume \"%s\" doesn't "
                     "have to be changed\n",
                      cmd, lv_name);
         }
      }
   
      /* allocation policy change */
      if ( opt_C > 0) {
         doit = 0;
         if ( lv_allocation & LV_CONTIGUOUS) {
            if ( lv->lv_allocation & LV_CONTIGUOUS)
               printf ( "%s -- allocation policy of logical volume \"%s\" "
                        "is already contiguous\n",
                         cmd, lv_name);
            else doit++;
         } else {
            if ( ! ( lv->lv_allocation & LV_CONTIGUOUS))
               printf ( "%s -- allocation policy of logical volume \"%s\" "
                        "is not contiguous\n",
                         cmd, lv_name);
            else doit++;
         }
   
         if ( doit > 0) {
            if ( lv_allocation & LV_CONTIGUOUS)
               lv->lv_allocation |= LV_CONTIGUOUS;
            else
               lv->lv_allocation &= ~LV_CONTIGUOUS;
            if ( ( lv_allocation & LV_CONTIGUOUS ) &&
                 ( ret = lv_check_contiguous ( vg, l + 1)) == FALSE) {
               fprintf ( stderr, "%s -- no contiguous logical volume \"%s\"\n",
                         cmd, lv_name);
            } else {
               if ( opt_v > 0) printf ( "%s -- changing allocation policy "
                                        "of logical volume \"%s\"\n",
                                        cmd, lv_name);
               if ( ( ret = ioctl ( blkdev, LV_SET_ALLOCATION,
                                    ( ulong) lv->lv_allocation)) < 0) {
                  if ( ret == -EPERM) {
                     fprintf ( stderr, "%s -- operation not permitted "
                                       "for (open) logical volume \"%s\"\n\n",
                                       cmd, lv_name);
                  } else {
                     fprintf ( stderr, "%s -- ERROR \"%s\" couldn't set "
                                       "allocation for logical volume "
                                       "\"%s\"\n\n",
                                       cmd, lvm_error ( ret), lv_name);
                  }
                  return LVM_ELVCHANGE_LV_SET_ALLOCATION;
               }
               doit_sum++;
            }
         } else {
            printf ( "%s -- allocation policy of logical volume \"%s\" "
                     "doesn't have to be changed\n", cmd, lv_name);
         }
      }

      /* read ahead sector change */
      if ( opt_r > 0) {
         if ( read_ahead < LVM_MIN_READ_AHEAD ||
              read_ahead > LVM_MAX_READ_AHEAD) {
            fprintf ( stderr, "%s -- read ahead sector argument is invalid\n\n",
                              cmd);
            return LVM_ELVCHANGE_READ_AHEAD;
         }
         if ( vg->lv[l]->lv_read_ahead == read_ahead) {
            fprintf ( stderr, "%s -- read ahead sector already is %ld\n\n",
                              cmd, read_ahead);
            return LVM_ELVCHANGE_READ_AHEAD;
         }
         if ( ( ret = lv_change_read_ahead ( lv_name, read_ahead)) < 0) {
            fprintf ( stderr, "%s -- ERROR \"%s\" read ahead sector change\n\n",
                              cmd, lvm_error ( ret));
            return LVM_ELVCHANGE_READ_AHEAD;
         }
         vg->lv[l]->lv_read_ahead = read_ahead;
         doit_sum++;
      }
   
      vg->lv[l]->lv_access = lv->lv_access;
      vg->lv[l]->lv_allocation = lv->lv_allocation;
      vg->lv[l]->lv_status = lv->lv_status;
   
      if ( doit_sum > 0) {
         if ( opt_v > 0) printf ( "%s -- storing logical volume on disk(s)\n",
                                  cmd);
         if ( ( ret = lv_write_all_pv ( vg, l)) < 0) {
            fprintf ( stderr, "%s -- ERROR \"%s\" couldn't store logical "
                              "volume information of \"%s\"\n",
                              cmd, lvm_error ( ret), lv_name);
            return LVM_ELVCHANGE_LV_WRITE_ALL_PV;
         }
   
         printf ( "%s -- logical volume \"%s\" changed\n",
                   cmd, lv_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);
         }
      } else  printf ( "%s -- logical volume \"%s\" didn't have "
                       "to be changed\n", cmd, lv_name);

      doit_sum = 0;
      close ( blkdev);
      blkdev = 0;
   }


   lvm_interrupt ();
   LVM_UNLOCK ( 0);

   printf ( "\n");
   return 0;
}
