/*
 * tools/e2fsadm.c
 *
 * Copyright (C) 1997 - 2002  Heinz Mauelshagen, Sistina Software
 *
 * June,September 1998
 * February,July,October 1999
 * January,February 2000
 * January,February 2001
 * 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
 *
 *    04/09/1998 - corrected some messages
 *    06/02/1999 - fixed size bug
 *               - fixed lvm_check_number() usage
 *    23/07/1999 - fixed modulo size bug
 *    25/07/1999 - supported ext2fs with blocksize != 1k
 *    21/09/1999 - added environment variable support for resizer command
 *                 and options (idea by Torsten Neumann
 *                 <londo!torsten@odb.rhein-main.de>)
 *    06/10/1999 - implemented support for long options
 *    01/01/2000 - take care of rounding if striped resize
 *                 (idea by Pete Allen <petea3@attglobal.net>)
 *    15/02/2000 - use lvm_error()
 *    23/01/2001 - added call to lvm_init (JT)
 *    19/02/2001 - incorporated support for ext2online <adilger@turbolinux.com>
 *               - use getmntent on /etc/mtab, then /proc/mounts for mounted LV
 *               - also check mountpoint in addition to device name.
 *               - check /proc/mounts first, don't run e2fsck for ext2resize
 *               - swap bytes on ext2 superblock values for big endian systems
 *    06/04/2001 - Tidy usage message and command-line options.
 *    14/02/2002 - use lvm_check_number_ll() for (long long) numbers
 *
 */

#include <lvm_user.h>
#include <mntent.h>

#define	CMD_LEN	256

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 -- resize logical volume and ext2 filesystem\n", cmd);
   }

   fprintf ( stream, "\n%s "
             DEBUG_HELP_2
             "[-h|--help] "
             "[-n|--nofsck]\n\t"
             "{[-l|--extents] [+|-]LogicalExtentsNumber |\n\t"
             " [-L|--size] [+|-]LogicalVolumeSize[kKmMgGtT]}\n\t"
             "[-t|--test] "
             "[-v|--verbose] "
             "[--version] "
             "LogicalVolumePath\n\n",
             cmd);
   exit ( ret);
}

typedef struct {
   uint32_t	dummy1;
   uint32_t	s_blocks_count;
   uint32_t	dummy2[4];
   uint32_t	s_log_block_size;
   uint32_t	dummy3[7];
   uint16_t	s_magic;
   uint16_t	dummy4;
   uint32_t	dummy5[241];
} ext2_super_t;

static char *bin_dirs[] = { "/sbin", "/usr/sbin", "/usr/local/sbin", "/bin", "/usr/bin", NULL};
static char *mnt_files[] = { "/proc/mounts", MOUNTED, MNTTAB , NULL};

/* internal function prototypes */
char *lvm_find_command ( char *);
void lvm_path_error ( char *, char *);
ext2_super_t *lvm_is_ext2_fs ( char *);

int main ( int argc, char **argv) {
   int c = 0;
   int grow = UNDEF;
   int l = 0;
   int opt_l = 0;
   int opt_L = 0;
   int opt_n = 0;
   int opt_t = 0;
   int opt_v = 0;
   int ret = 0;
   int sign = 0;
   int size_rest = 0;
   int size_equal = FALSE;
   int size_invalid = FALSE;
   long stripepesize = 0;
   long long size = 0;
   char *cmd_fsck = "e2fsck";
   /* FIXME check "resize.<fstype>" and "oresize.<fstype>" instead */
   char *cmd_resize = NULL;
   char *cmd_resize_opts = "";
   char *cmd_resize_default = "resize2fs";
   char *cmd_resize_opts_default = "-p";
   char *cmd_resize_default2 = "ext2resize";
   char *cmd_resize_opts_default2 = "-v";
   char *cmd_online = NULL;
   char *cmd_online_default = "ext2online";
   char *cmd_online_opts = NULL;
   char *cmd_online_opts_default = "-v";
   char *cmd_extend = "lvextend";
   char *cmd_ptr = NULL;
   char *cmd_reduce = "lvreduce";
   char *dummy = NULL;
   char *lv_name = NULL;
   char *vg_name = NULL;
   char **mntfile = NULL;
   char *size_ptr = NULL;
   char command[CMD_LEN];
   char *options = "h?l:L:ntv" DEBUG_SHORT_OPTION;
   struct option long_options[] = {
      DEBUG_LONG_OPTION
      { "help",    no_argument,       NULL, 'h'},
      { "extents", required_argument, NULL, 'l'},
      { "size",    required_argument, NULL, 'L'},
      { "nofsck",  no_argument,       NULL, 'n'},
      { "test",    no_argument,       NULL, 't'},
      { "verbose", no_argument,       NULL, 'v'},
      { "version", no_argument,       NULL, 22 },
      { NULL,      0,                 NULL, 0}
   };
   lv_t *lv = NULL;
   vg_t *vg = NULL;
   ext2_super_t *super = NULL;

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

   LVMTAB_CHECK;

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

         case 'h':
            usage ( 0);

         case 'l':
            if ( opt_L) {
               fprintf ( stderr, "%s -- l and L options incompatible\n", cmd);
               usage ( LVM_EINVALID_CMD_LINE);
            }
            size_ptr = optarg;
            opt_l++;
            break;

         case 'L':
            if ( opt_l) {
               fprintf ( stderr, "%s -- l and L options incompatible\n", cmd);
               usage ( LVM_EINVALID_CMD_LINE);
            }
            size_ptr = optarg;
            opt_L++;
            break;

         case 'n':
            opt_n++;
            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",
                      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 argument '%s'\n", cmd, argv[optind]);
      usage ( LVM_EINVALID_CMD_LINE);
   }

   if ( ( cmd_resize = getenv ( "E2FSADM_RESIZE_CMD" )) == NULL) {
      if ( lvm_find_command ( cmd_resize_default)) {
         cmd_resize = cmd_resize_default;
         cmd_resize_opts = cmd_resize_opts_default;
      } else if ( lvm_find_command ( cmd_resize_default2)) {
         cmd_resize = cmd_resize_default2;
         cmd_resize_opts = cmd_resize_opts_default2;
      }
   }


   if ( ( dummy = getenv ( "E2FSADM_RESIZE_OPTS" )))
      cmd_resize_opts = dummy;

   if ( ( cmd_online = getenv ( "E2FSADM_ONLINE_CMD" )) == NULL) {
      cmd_online = cmd_online_default;
      cmd_online_opts = cmd_online_opts_default;
   }

   if ( ( dummy = getenv ( "E2FSADM_ONLINE_OPTS" )))
      cmd_online_opts = dummy;

   if ( opt_l == 0 && opt_L == 0) {
      fprintf ( stderr, "%s -- please give one of l or L options\n", cmd);
      usage ( LVM_EINVALID_CMD_LINE);
   }

   if ( optind != argc - 1) {
      fprintf ( stderr, "%s -- please give a logical volume name\n\n", cmd);
      usage ( LVM_EE2FSADM_LV_MISSING);
   }

   if ( opt_t > 0)
      printf ( "%s -- this is a test run and not a real resize\n", cmd);

   lv_name = argv[optind];

   /* check to see if the LV or mountpoint appears in one of the mtab files */
   for ( mntfile = mnt_files; *mntfile != NULL; mntfile++) {
      struct mntent *mntent;
      FILE *mounts;

      if ( ( mounts = setmntent ( *mntfile, "r")) == NULL)
         continue;

      while ( ( mntent = getmntent ( mounts)) != NULL) {
         /* Also check the mount point, and update lv_name if it matches */
         if ( strcmp( lv_name, mntent->mnt_dir) == 0)
            lv_name = mntent->mnt_fsname;

         if ( strcmp ( lv_name, mntent->mnt_fsname) == 0) {
            /* If we aren't looking at /etc/fstab, the filesystem is mounted */
            if ( strcmp ( *mntfile, MNTTAB)) {
               if ( lvm_find_command ( cmd_online) == NULL) {
                  lvm_path_error ( cmd_online, "online resize");
                  fprintf ( stderr, "%s -- ERROR: %s says %s is mounted on "
                                    "%s.\nPlease unmount it to resize.\n\n",
                                    cmd, *mntfile, lv_name, mntent->mnt_dir);
                  endmntent ( mounts);
                  return LVM_EE2FSADM_RESIZE_PATH;
               }
               cmd_resize = cmd_online;
               cmd_resize_opts = cmd_online_opts;
            }
            /* don't close mounts, in case we point to mnt_fsname */
            goto have_mntent;
         }
      }
      endmntent ( mounts);
   }
have_mntent:

   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);
      usage ( LVM_EE2FSADM_LVNAME);
   }

   if ( opt_v > 0)
      printf ( "%s -- checking existance of logical volume %s\n", cmd, lv_name);
   if ( lvm_tab_lv_check_exist ( lv_name) != TRUE) {
      fprintf ( stderr, "%s -- logical volume \"%s\" doesn't exist\n\n",
                        cmd, lv_name);
      usage ( LVM_EE2FSADM_LV_EXIST);
   }

   if ( opt_v > 0)
      printf ( "%s -- checking for relative/absolute size change\n", cmd);

   if ( *size_ptr == '+') {
      sign = +1;
      size_ptr++;
   } else if ( *size_ptr == '-') {
      sign = -1;
      size_ptr++;
   }

   if ( ( size = lvm_check_number_ll ( size_ptr,
                                       opt_L > 0 ? TRUE : FALSE)) < 0) {
      fprintf ( stderr, "%s -- invalid filesystem size %c%s\n", cmd,
                sign > 0 ? '+' : '-', size_ptr);
      usage ( LVM_EE2FSADM_FSSIZE);
   }

   vg_name = vg_name_of_lv ( lv_name);

   if ( opt_v > 0) printf ( "%s -- reading VGDA 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 reading VGDA of \"%s\" from lvmtab\n\n",
                        cmd, vg_name);
      return LVM_EE2FSADM_VG_READ;
   }
   l = lv_get_index_by_name ( vg, lv_name);

   /* make size absolute */
   if ( opt_v > 0) printf ( "%s -- calculating absolute logical volume size\n",
                            cmd);
   if ( opt_L) size *= 2;
   else        size *= vg->pe_size;

   /* calculate and check absolute or relative size */
   size_invalid = size_equal = FALSE;
   if ( sign == 0) {
      if ( size < vg->lv[l]->lv_size) grow = FALSE;
      else if ( size > vg->lv[l]->lv_size) grow = TRUE;
      else { size_invalid = TRUE; size_equal = TRUE;}
      if ( size < vg->pe_size) size_invalid = TRUE;
   /* + size */
   } else if ( sign == 1) {
      if ( size > ( vg->pe_total - vg->pe_allocated) * vg->pe_size) {
         size_invalid = TRUE;
         fprintf ( stderr, "%s -- new size too large for \"%s\"\n",
                           cmd, vg_name);
      } else {
         if ( size == 0) { size_invalid = TRUE; size_equal = TRUE;}
         size += vg->lv[l]->lv_size;
         grow = TRUE;
      }
   /* - size */
   } else {
      if ( size == 0) { size_invalid = TRUE; size_equal = TRUE;}
      size = vg->lv[l]->lv_size - size;
      grow = FALSE;
      if ( size < 0 || size < vg->pe_size) size_invalid = TRUE;
   }

   if ( size_invalid == TRUE) {
      fprintf ( stderr, "%s -- size of \"%s\" would %s\n\n", cmd, lv_name,
                size_equal == TRUE ? "not change" : " be invalid");
      return LVM_EE2FSADM_FSSIZE_CHANGE;
   }

   if ( opt_v > 0) printf ( "%s -- checking size modulo PE size\n", cmd);
   size_rest = size % vg->pe_size;
   if ( size_rest > 0) {
      printf ( "%s -- rounding size up to physical extent boundary\n", cmd);
      size += vg->pe_size - size_rest;
      if ( size > 0 && sign == -1) size -= vg->pe_size;
   }

   /* So that -L2t can be given */
   if ( size > LVM_LV_SIZE_2TB ( vg)) size -= vg->pe_size;

   stripepesize = vg->lv[l]->lv_stripes * vg->pe_size;
   size_rest = size % stripepesize;
   if ( size_rest != 0) {
      printf ( "%s -- rounding up size to stripe boundary size\n", cmd);
      size = size - size_rest + stripepesize;
      if ( size > LVM_LV_SIZE_2TB ( vg)) size -= stripepesize;
      if ( size == 0) size = stripepesize;
   }

   if ( opt_v > 0)
      printf ( "%s -- checking logical volume maximum size\n", cmd);
   if ( size > LVM_LV_SIZE_2TB ( vg)) {
      fprintf ( stderr, "%s -- size %Ld KB is larger than maximum VGDA "
                        "kernel size of %Ld KB\n",
                        cmd, size / 2, LVM_LV_SIZE_2TB ( vg) / 2);
      usage ( LVM_ELVCREATE_LV_SIZE);
   }

   if ( ( super = lvm_is_ext2_fs ( lv_name)) == NULL)
      return LVM_EE2FSADM_NO_EXT2;

   if ( ( super->s_blocks_count << ( super->s_log_block_size+1))
        + sign * size < 0) {
      fprintf ( stderr,"%s -- logical volume size for \"%s\" invalid\n\n",
                       cmd, lv_name);
      return LVM_EE2FSADM_LV_SIZE;
   }

   if ( opt_n == 0 && cmd_resize != cmd_online &&
        cmd_resize != cmd_resize_default2) {
      if ( opt_v > 0)
         printf ( "%s -- checking filesystem (this can take a while)\n", cmd);
      if ( ( cmd_ptr = lvm_find_command ( cmd_fsck)) == NULL) {
         lvm_path_error ( cmd_fsck, "do filesystem check");
         return LVM_EE2FSADM_FSCK_PATH;
      }
      if ( opt_t == 0) {
         memset ( command, 0, sizeof ( command));
         snprintf ( command, sizeof ( command) - 1,
                             "%s -f %s", cmd_ptr, lv_name);
         if ( system ( command) != 0) return  LVM_EE2FSADM_FSCK_RUN;
      }
   }

   /* GROW */
   if ( grow == TRUE) {
      if ( opt_v > 0) printf ( "%s -- extending logical volume size\n", cmd);
      if ( ( cmd_ptr = lvm_find_command ( cmd_extend)) == NULL) {
         lvm_path_error ( cmd_extend, "extend logical volume");
         return LVM_EE2FSADM_LV_EXTEND_PATH;
      }
      memset ( command, 0, sizeof ( command));
      snprintf ( command, sizeof ( command) - 1,
                          "%s -l %Ld %s",
                          cmd_ptr, size / vg->pe_size, lv_name);
      if ( opt_t == 0) {
         if ( system ( command) != 0) return LVM_EE2FSADM_LV_EXTEND_RUN;
         if ( ( ret = lvm_tab_lv_read_by_name ( vg_name, lv_name, &lv)) < 0) {
            fprintf ( stderr, "%s -- ERROR reading VGDA of %s from lvmtab\n\n",
                      cmd, lv_name);
            return LVM_EE2FSADM_LV_READ;
         }
         if ( size != lv->lv_size) {
            fprintf ( stderr, "%s -- new logical volume size of %s invalid\n\n",
                      cmd, lv_name);
            return LVM_EE2FSADM_LV_SIZE;
         }
      }
      if ( opt_v > 0)
         printf ( "%s -- resizing filesystem (this can take a while)\n", cmd);
      if ( cmd_resize == NULL ||
           ( cmd_ptr = lvm_find_command ( cmd_resize)) == NULL) {
        lvm_path_error ( cmd_resize ?
                         cmd_resize : cmd_resize_default, "resize");
        return LVM_EE2FSADM_RESIZE_PATH;
      }
      memset ( command, 0, sizeof ( command));
      snprintf ( command, sizeof ( command) - 1,
                          "%s %s %s %Ld",
                          cmd_ptr, cmd_resize_opts,
                          lv_name, size >> ( super->s_log_block_size + 1));
      if ( opt_t == 0) {
         if ( system ( command) != 0) return LVM_EE2FSADM_RESIZE_RUN;
         if ( ( super = lvm_is_ext2_fs ( lv_name)) == NULL)
            return LVM_EE2FSADM_NO_EXT2;
         if ( size >> ( super->s_log_block_size + 1) != super->s_blocks_count) {
            fprintf ( stderr, "%s -- failure of of \"%s\": \"%s\" "
                              "was not successfully extended\n",
                              cmd, cmd_ptr, lv_name);
            return LVM_EE2FSADM_LV_SIZE;
         }
      }
   /* SHRINK */
   } else { /* grow == FALSE */
      if ( opt_v > 0)
         printf ( "%s -- resizing filesystem (this can take a while)\n", cmd);
      if ( cmd_resize == NULL ||
           ( cmd_ptr = lvm_find_command ( cmd_resize)) == NULL) {
         lvm_path_error ( cmd_resize ?
                          cmd_resize : cmd_resize_default, "resize");
         return LVM_EE2FSADM_RESIZE_PATH;
      }
      memset ( command, 0, sizeof ( command));
      snprintf ( command, sizeof ( command) - 1,
                          "%s %s %s %Ld",
                          cmd_ptr, cmd_resize_opts,
                          lv_name, size >> ( super->s_log_block_size + 1));
      if ( opt_t == 0) {
         if ( system ( command) != 0) return LVM_EE2FSADM_RESIZE_RUN;
         if ( ( super = lvm_is_ext2_fs ( lv_name)) == NULL)
            return LVM_EE2FSADM_NO_EXT2;
         if ( size >> ( super->s_log_block_size + 1) != super->s_blocks_count) {
            fprintf ( stderr, "%s -- failure of of \"%s\": \"%s\" "
                              "was not successfully reduced\n",
                              cmd, cmd_ptr, lv_name);
            return LVM_EE2FSADM_LV_SIZE;
         }
      }
      if ( opt_v > 0) printf ( "%s -- reducing logical volume size\n", cmd);
      if ( ( cmd_ptr = lvm_find_command ( cmd_reduce)) == NULL) {
         lvm_path_error ( cmd_reduce, "reduce logical volume");
         return LVM_EE2FSADM_LV_REDUCE_PATH;
      }
      memset ( command, 0, sizeof ( command));
      snprintf ( command, sizeof ( command) - 1,
                          "%s -f -l %Ld %s",
                          cmd_ptr, size / vg->pe_size, lv_name);
      if ( opt_t == 0) {
         if ( system ( command) != 0) return LVM_EE2FSADM_LV_REDUCE_RUN;
      }
   }

   printf ( "%s -- ext2fs in logical volume %s successfully %s to %s\n\n",
            cmd, lv_name, grow == TRUE ? "extended" : "reduced",
            lvm_show_size(size / 2, SHORT));

   return 0;
}


char *lvm_find_command ( char *cmd_to_find) {
   int i = 0;
   char *ret = NULL;
   static char command[CMD_LEN];
   struct stat stat_b;

   if ( cmd_to_find == NULL || strlen ( cmd_to_find) == 0) return ret;

   while ( bin_dirs[i] != NULL) {
      memset ( command, 0, sizeof ( command));
      snprintf ( command, sizeof ( command) - 1,
                          "%s/%s", bin_dirs[i], cmd_to_find);
      if ( stat ( command, &stat_b) == 0) {
         ret = command;
         break;
      }
      i++;
   }

   return ret;
}


void lvm_path_error ( char *cmd_to_find, char *comment) {
   int i = 0;

   if ( cmd_to_find == NULL || strlen ( cmd_to_find) == 0 ||
        comment     == NULL || strlen (     comment) == 0) return;

   fprintf(stderr, "%s -- Sorry: '%s' not found in any of", cmd, cmd_to_find);
   while ( bin_dirs[i] != NULL) fprintf ( stderr, " %s", bin_dirs[i++]);
   fprintf ( stderr, "\n%s -- ERROR: can't %s\n\n", cmd, comment);

   return;
}


ext2_super_t *lvm_is_ext2_fs ( char *device) {
   int fs = -1;
   static ext2_super_t super;
   ext2_super_t *ret = &super;

   if ( ( fs = open ( device, O_RDONLY)) == -1) {
      fprintf ( stderr, "%s -- ERROR \"%s\" opening logical volume \"%s\"\n\n",
                        cmd, lvm_error ( errno), device);
      ret = NULL;
      goto lvm_is_ext2_fs_end;
   }

   if ( lseek ( fs, 1024, SEEK_SET) == -1) {
      fprintf(stderr, "%s -- ERROR \"%s\" seeking for superblock on \"%s\"\n\n",
              cmd, lvm_error ( errno), device);
      ret = NULL;
      goto lvm_is_ext2_fs_end;
   }

   if ( read ( fs, &super, sizeof ( super)) != sizeof ( super)) {
      fprintf ( stderr, "%s -- ERROR \"%s\" reading superblock from \"%s\"\n\n",
                        cmd, lvm_error ( errno), device);
      ret = NULL;
      goto lvm_is_ext2_fs_end;
   }

   if ( super.s_magic != LVM_TO_DISK16 ( EXT2_SUPER_MAGIC)) {
      fprintf ( stderr, "%s -- ERROR: \"%s\" doesn't contain an ext2 "
                        "filesystem\n\n",
                        cmd, device);
      ret = NULL;
      goto lvm_is_ext2_fs_end;
   }

   /* byteswap needed superblock fields */
   super.s_blocks_count = LVM_TO_CORE32 ( super.s_blocks_count);
   super.s_log_block_size = LVM_TO_CORE32 ( super.s_log_block_size);

lvm_is_ext2_fs_end:
   if ( fs != -1) close ( fs);

   return ret;
}
