/*
 * tools/lib/vg_cfgbackup.c
 *
 * Copyright (C) 1997 - 2000  Heinz Mauelshagen, Sistina Software
 *
 * September 1997
 * May,July-August 1998
 * January,February 2000
 *
 *
 * This LVM library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This LVM library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this LVM library; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA
 *
 */

/*
 * Changelog
 *
 *    16/05/1998 - added errror return code for ordinary file existing
 *                 instead of directory
 *               - enhanced parameter checking
 *               - avoided creating backup if new one is the same
 *    05/07/1998 - implemented deeper VGDA backup history
 *    07/07/1998 - zero I/Os and open data to avoid backups
 *    10/07/1998 - enhanced ERROR output with < pv_cur PVs
 *    30/08/1998 - seperated disk and core structures by using
 *                 lv_copy_to_disk(), pv_copy_to_disk() and vg_copy_to_disk()
 *    08/02/2000 - use debug_enter()/debug_leave()
 *               - implemented support for envirinment variable
 *                 LVM_VG_MAX_BACKUPS to choose backup history depth
 *    15/02/2000 - use lvm_error()
 *
 */

#include <liblvm.h>

/* default maximum backup history depth */
#define VG_DEF_BACKUPS	9


#define	VGCFG_WRITE( handle, what, size, file_name) ( { \
      unsigned int this_size = size; \
      if ( write ( handle, &this_size, sizeof ( this_size)) != \
           sizeof ( this_size)) { \
         fprintf ( stderr, "%s -- ERROR %d writing structure size to volume " \
                           "group backup file %s in %s [line %d]\n", \
                           cmd, errno, file_name, __FILE__, __LINE__); \
         close ( handle); \
         unlink ( file_name); \
         ret = -LVM_EVG_CFGBACKUP_WRITE; \
         goto vg_cfgbackup_end; \
      }; \
      if ( write ( handle, what, size) != size) { \
         fprintf ( stderr, "%s -- ERROR %d writing volume group backup " \
                           "file %s in %s [line %d]\n", \
                           cmd, errno, file_name, __FILE__, __LINE__); \
         close ( handle); \
         unlink ( file_name); \
         ret = -LVM_EVG_CFGBACKUP_WRITE; \
         goto vg_cfgbackup_end; \
      }; \
} )


int vg_cfgbackup ( char* vg_name, char* directory,
                   int opt_v, vg_t *vg_in) {
   int file_differ = FALSE;
   int i = 0;
   int l = 0;
   int le = 0;
   int p = 0;
   int ret = 0;
   int sz = 0;
   int vg_max_backups = VG_DEF_BACKUPS;
   int vg_backup = -1;
   int vg_backup_tmp = -1;
   char *envptr = NULL;
   char *vg_backup_buffer = NULL;
   char *vg_backup_tmp_buffer = NULL;
   char vg_backup_path[NAME_LEN] = { 0, };
   char vg_backup_path_tmp[NAME_LEN] = { 0, };
   char vg_backup_path_old1[NAME_LEN] = { 0, };
   char vg_backup_path_old2[NAME_LEN] = { 0, };
   struct stat stat_b1;
   struct stat stat_b2;
   lv_t lv_zero;
   vg_t *vg = NULL;

   debug_enter ( "vg_cfgbackup -- CALLED\n");

   if ( vg_name == NULL || directory == NULL ||
        opt_v < 0 || vg_check_name ( vg_name) < 0) {
      ret = -LVM_EPARAM;
      goto vg_cfgbackup_end;
   }

   memset ( &lv_zero, 0, sizeof ( lv_zero));
   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 -- invalid volume group name \"%s\"\n",
                cmd, vg_name);
      ret = -LVM_EPARAM;
      goto vg_cfgbackup_end;
   }

   if ( stat ( directory, &stat_b1) == 0) {
      if ( ! S_ISDIR ( stat_b1.st_mode)) {
         fprintf ( stderr, "%s -- ERROR: file \"%s\" exists; "
                           "must be directory\n",
                           cmd, directory);
         ret = -LVM_EVG_CFGBACKUP_FILE_EXISTS;
         goto vg_cfgbackup_end;
      }
   }

   if ( ( envptr = getenv ( "LVM_VG_MAX_BACKUPS")) != NULL) {
      if ( ( vg_max_backups = atoi ( envptr)) < 0 ||
           vg_max_backups > 999) {
         vg_max_backups = VG_DEF_BACKUPS;
         printf ( "%s -- WARNING: using default backup history "
                  "depth of %d\n"
                  "%s -- please check LVM_VG_MAX_BACKUPS "
                  "environment variable\n", cmd, vg_max_backups, cmd);
      }
   }

   if ( vg_in == NULL) {
      if ( opt_v > 0) printf ( "%s -- checking existence of volume "
                               "group \"%s\"\n",
                               cmd, vg_name);
      if ( ( ret = vg_check_exist ( vg_name)) < 0) {
         if ( ret == -LVM_EPV_READ_PV_EXPORTED)
            fprintf ( stderr, "%s -- INFO: can't access exported "
                              "volume group \"%s\"\n",
                              cmd, vg_name);
         else if ( ret == -LVM_EPV_READ_ALL_PV_OF_VG_NP)
            fprintf ( stderr, "%s -- volume group \"%s\" doesn't exist\n",
                              cmd, vg_name);
         else if ( ret == -LVM_EVG_READ_LVM_STRUCT_VERSION)
            fprintf ( stderr, "%s -- ERROR: volume group \"%s\" has physical "
                              "volumes with invalid version\n",
                              cmd, vg_name);
         else if ( ret == -LVM_EPV_READ_ALL_PV_OF_VG_PV_NUMBER ||
                   ret == -LVM_EVG_CHECK_EXIST_PV_COUNT)
            fprintf ( stderr, "%s -- ERROR: volume group \"%s\" has an "
                              "invalid number of physical volumes\n",
                              cmd, vg_name);
         else
            fprintf ( stderr, "%s -- ERROR %d checking existence of "
                              "volume group \"%s\"\n",
                              cmd, ret, vg_name);
         ret = -LVM_EVG_CFGBACKUP_VG_CHECK_EXIST;
         goto vg_cfgbackup_end;
      }
   
      if ( opt_v > 0) printf ( "%s -- reading volume group data for \"%s\" "
                               "from disk(s)\n", cmd, vg_name);
      if ( ( ret = vg_read_with_pv_and_lv ( vg_name, &vg)) < 0) {
         if ( ret == -LVM_EVG_READ_LVM_STRUCT_VERSION) {
            fprintf ( stderr, "%s -- volume group \"%s\" can't be "
                              "used because:\n",
                              cmd, vg_name);
            for ( p = 0; p < vg->pv_cur; p++) {
               if (!lvm_pv_check_version(vg->pv[p]))
                  fprintf ( stderr, "   physical volume \"%s\" has invalid "
                                    "version %d\n",
                                    vg->pv[p]->pv_name, vg->pv[p]->version);
            }
         } else {
            fprintf ( stderr, "%s -- ERROR \"%s\" reading VGDA of \"%s\"\n",
                      cmd, lvm_error ( ret), vg_name);
         }
         ret = -LVM_EVG_CFGBACKUP_VG_READ_WITH_PV_AND_LV;
         goto vg_cfgbackup_end;
      }
   } else vg = vg_in;

   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",
                        cmd, lvm_error ( ret), vg_name);
      ret = -LVM_EVG_CHECK_CONSISTENCY;
      goto vg_cfgbackup_end;
   }

   /* zero I/Os and open data to avoid difference in compare */
   for ( l = 0; l < vg->lv_max; l++) {
      if ( vg->lv[l] != NULL) {
         vg->lv[l]->lv_open = 0;
         for ( le = 0; le < vg->lv[l]->lv_current_le; le++) {
            vg->lv[l]->lv_current_pe[le].reads = 0;
            vg->lv[l]->lv_current_pe[le].writes = 0;
         }
      }
   }

   memset ( vg_backup_path, 0, sizeof ( vg_backup_path));
   sz = snprintf ( vg_backup_path, sizeof ( vg_backup_path) - 1,
                                  "%s/%s", directory, vg_name);
   if ( strcmp ( directory, VG_BACKUP_DIR) == 0)
      sz += snprintf ( &vg_backup_path[sz], sizeof ( vg_backup_path) - sz - 1,
                                            ".conf");
   snprintf ( vg_backup_path_tmp, sizeof ( vg_backup_path_tmp) - 1,
                                  "%s.tmp", vg_backup_path);

   if ( opt_v > 0) printf ( "%s -- checking existence of \"%s\"\n",
                            cmd, directory);
   if ( stat ( directory, &stat_b1) == -1) {
      if ( opt_v > 0) printf ( "%s -- making directory \"%s\"\n",
                               cmd, directory);
      if ( mkdir ( directory, 0755) == -1)
         fprintf ( stderr, "%s -- ERROR \"%s\" making directory \"%s\"\n",
                           cmd, lvm_error ( errno), directory);
   }

   if ( ( vg_backup = open ( vg_backup_path_tmp,
                             O_CREAT | O_WRONLY | O_TRUNC, 0640)) == -1) {
      if ( errno == EROFS)
         fprintf ( stderr, "%s -- read only filesystem opening volume group "
                           "backup file \"%s\" for writing\n",
                           cmd, vg_backup_path_tmp);
      else
         fprintf ( stderr, "%s -- ERROR \"%s\" opening volume group "
                           "backup file \"%s\" for writing\n",
                           cmd, lvm_error ( errno), vg_backup_path_tmp);
      ret = -LVM_EVG_CFGBACKUP_OPEN;
      goto vg_cfgbackup_end;
   }

   fchmod ( vg_backup, 0640);

   if ( opt_v > 0) printf ( "%s -- storing volume group data of \"%s\" "
                            "in \"%s\"\n",
                            cmd, vg_name, vg_backup_path_tmp);
   VGCFG_WRITE ( vg_backup, vg, sizeof ( vg_t), vg_backup_path_tmp);

   if ( opt_v > 0) printf ( "%s -- storing physical volume data of \"%s\" "
                            "in \"%s\"\n",
                            cmd, vg_name, vg_backup_path_tmp);
   for ( p = 0; p < vg->pv_cur; p++) {
      VGCFG_WRITE ( vg_backup, vg->pv[p], sizeof ( pv_t), vg_backup_path_tmp);
      VGCFG_WRITE ( vg_backup, vg->pv[p]->pe,
                    vg->pv[p]->pe_total * sizeof ( pe_disk_t),
                    vg_backup_path_tmp);
   }

   if ( opt_v > 0) printf ( "%s -- storing logical volume data of "
                            "volume group \"%s\" in \"%s\"\n",
                            cmd, vg_name, vg_backup_path_tmp);
   for ( l = 0; l < vg->lv_max; l++) {
      if ( vg->lv[l] != NULL) {
         VGCFG_WRITE ( vg_backup, vg->lv[l], sizeof ( lv_t),
                       vg_backup_path_tmp);
         VGCFG_WRITE ( vg_backup, vg->lv[l]->lv_current_pe,
                       vg->lv[l]->lv_allocated_le * sizeof ( pe_t),
                       vg_backup_path_tmp);
      } else VGCFG_WRITE ( vg_backup, &lv_zero, sizeof ( lv_zero),
                           vg_backup_path_tmp);
   }
   if ( vg_backup != -1) {
      close ( vg_backup);
      vg_backup = -1;
   }


   /* volume group backup */
   if ( strcmp ( directory, VG_BACKUP_DIR) == 0) {
      if ( opt_v > 0) printf ( "%s -- checking existence of \"%s\"\n",
                               cmd, vg_backup_path_tmp);
      if ( stat ( vg_backup_path_tmp, &stat_b1) < 0) {
         ret = -LVM_EVG_CFGBACKUP_TMP_FILE;
         goto vg_cfgbackup_end;
      }

      if ( opt_v > 0) printf ( "%s -- checking existence of \"%s\"\n",
                               cmd, vg_backup_path);
      if ( stat ( vg_backup_path, &stat_b2) == -1) {
         if ( opt_v > 0) printf ( "%s -- renaming \"%s\" to \"%s\"\n",
                                  cmd, vg_backup_path_tmp, vg_backup_path);
         if ( rename ( vg_backup_path_tmp, vg_backup_path) == -1) {
            fprintf ( stderr, "%s -- ERROR \"%s\" renaming \"%s\"\n",
                              cmd, lvm_error ( errno), vg_backup_path);
            ret = -LVM_EVG_CFGBACKUP_RENAME;
            goto vg_cfgbackup_end;
         }
         ret = 0;
         goto vg_cfgbackup_end;
      }

      /* compare both files if they are of equal size */
      if ( stat_b1.st_size == stat_b2.st_size) {
         if ( ( vg_backup_tmp = open ( vg_backup_path_tmp, O_RDONLY)) == -1) {
            fprintf ( stderr, "%s -- ERROR opening temporary file \"%s\"\n",
                              cmd, vg_backup_path_tmp);
            ret = -LVM_EVG_CFGBACKUP_OPEN;
            goto vg_cfgbackup_end;
         }
         if ( ( vg_backup = open ( vg_backup_path, O_RDONLY)) == -1) {
            fprintf ( stderr, "%s -- ERROR opening file \"%s\"\n",
                              cmd, vg_backup_path);
            ret = -LVM_EVG_CFGBACKUP_OPEN;
            goto vg_cfgbackup_end;
         }
         if ( ( vg_backup_tmp_buffer = malloc ( stat_b1.st_size)) == NULL) {
            fprintf ( stderr, "%s -- malloc error in \"%s\" [line %d]\n\n",
                              cmd, __FILE__, __LINE__);
            ret = -LVM_EVG_CFGBACKUP_MALLOC;
            goto vg_cfgbackup_end;
         }
         if ( ( vg_backup_buffer = malloc ( stat_b2.st_size)) == NULL) {
            fprintf ( stderr, "%s -- malloc error in \"%s\" [line %d]\n\n",
                              cmd, __FILE__, __LINE__);
            ret = -LVM_EVG_CFGBACKUP_MALLOC;
            goto vg_cfgbackup_end;
         }
         if ( read ( vg_backup_tmp, vg_backup_tmp_buffer, stat_b1.st_size) != 
              stat_b1.st_size) {
            fprintf ( stderr, "%s -- ERROR reading \"%s\"\n",
                              cmd, vg_backup_path_tmp);
            ret = -LVM_EVG_CFGBACKUP_READ;
            goto vg_cfgbackup_end;
         }
         if ( read ( vg_backup, vg_backup_buffer, stat_b2.st_size) != 
              stat_b2.st_size) {
            fprintf ( stderr, "%s -- ERROR reading \"%s\"\n",
                              cmd, vg_backup_path);
            ret = -LVM_EVG_CFGBACKUP_READ;
            goto vg_cfgbackup_end;
         }
        
         file_differ = FALSE;
         for ( i = 0; i < stat_b1.st_size; i++) {
            if ( vg_backup_buffer[i] != vg_backup_tmp_buffer[i]) {
               file_differ = TRUE;
               break;
            }
         }

         close ( vg_backup); vg_backup = -1;

         if ( file_differ == FALSE) {
            printf ( "%s -- no difference to old backup in \"%s\"\n",
                     cmd, vg_backup_path);
            ret = LVM_VG_CFGBACKUP_NO_DIFF;
            if ( unlink ( vg_backup_path_tmp) == -1) {
               fprintf ( stderr, "%s -- ERROR \"%s\" unlinking \"%s\"\n",
                                 cmd, lvm_error ( errno), vg_backup_path_tmp);
               ret = -LVM_EVG_CFGBACKUP_UNLINK;
            }
            goto vg_cfgbackup_end;
         }
      }

      /* VGDA backup history */
      for ( i = vg_max_backups; i >= 1; i--) {
         memset ( vg_backup_path_old2, 0, sizeof ( vg_backup_path_old2));
         snprintf ( vg_backup_path_old2, sizeof ( vg_backup_path_old2) - 1,
                                         "%s/%s.conf.%d.old%c",
                                         directory, vg_name, i, 0);
         if ( opt_v > 0) printf ( "%s -- checking for \"%s\"\n",
                                  cmd, vg_backup_path_old2);
         if ( i == vg_max_backups &&
              stat ( vg_backup_path_old2, &stat_b1) == 0) {
            if ( unlink ( vg_backup_path_old2) == -1) {
               fprintf ( stderr, "%s -- ERROR \"%s\" unlinking \"%s\"\n",
                                 cmd, lvm_error ( errno), vg_backup_path_old2);
               ret = -LVM_EVG_CFGBACKUP_UNLINK;
               goto vg_cfgbackup_end;
            }
         }

         memset ( vg_backup_path_old1, 0, sizeof ( vg_backup_path_old1));
         if ( i == 1) snprintf ( vg_backup_path_old1,
                                 sizeof ( vg_backup_path_old1) - 1,
                                 "%s/%s.conf%c",
                                 directory, vg_name, 0);
         else snprintf ( vg_backup_path_old1,
                         sizeof ( vg_backup_path_old1) - 1,
                         "%s/%s.conf.%d.old%c",
                         directory, vg_name, i - 1, 0);
         if ( opt_v > 0) printf ( "%s -- checking for \"%s\"\n",
                                  cmd, vg_backup_path_old1);
         if ( stat ( vg_backup_path_old1, &stat_b1) == -1) continue;


         if ( opt_v > 0) printf ( "%s -- renaming \"%s\" to \"%s\"\n",
                                  cmd,
                                  vg_backup_path_old1,
                                  vg_backup_path_old2);
         if ( rename ( vg_backup_path_old1, vg_backup_path_old2) == -1) {
            fprintf ( stderr, "%s -- ERROR \"%s\" renaming \"%s\" to \"%s\"\n",
                              cmd, lvm_error ( errno),
                              vg_backup_path_old1,
                              vg_backup_path_old2);
            ret = -LVM_EVG_CFGBACKUP_RENAME;
            goto vg_cfgbackup_end;
         }
      }

      unlink ( vg_backup_path);
      if ( opt_v > 0) printf ( "%s -- renaming \"%s\" to \"%s\"\n",
                               cmd, vg_backup_path_tmp, vg_backup_path);
      if ( rename ( vg_backup_path_tmp, vg_backup_path) == -1) {
         fprintf ( stderr, "%s -- ERROR \"%s\" renaming \"%s\"\n",
                           cmd, lvm_error ( errno), vg_backup_path_tmp);
         ret = -LVM_EVG_CFGBACKUP_RENAME;
         goto vg_cfgbackup_end;
      }
   /* lvmtab backup */
   } else {
      unlink ( vg_backup_path);
      if ( opt_v > 0) printf ( "%s -- renaming \"%s\" to \"%s\"\n",
                               cmd, vg_backup_path_tmp, vg_backup_path);
      if ( rename ( vg_backup_path_tmp, vg_backup_path) == -1) {
         fprintf ( stderr, "%s -- ERROR \"%s\" renaming \"%s\"\n",
                           cmd, lvm_error ( errno), vg_backup_path);
         ret = -LVM_EVG_CFGBACKUP_RENAME;
      }
   }

vg_cfgbackup_end:
   if ( vg_backup_buffer != NULL) free ( vg_backup_buffer);
   if ( vg_backup_tmp_buffer != NULL) free ( vg_backup_tmp_buffer);
   if ( vg_backup_tmp != -1) close ( vg_backup_tmp);
   if ( vg_backup != -1) close ( vg_backup);

   debug_leave ( "vg_cfgbackup -- LEAVING with ret: %d\n", ret);
   return ret;
} /* vg_cfgbackup() */
