/** ============================================================================
**   Copyright (c) 2005,2006 EnterpriseDB Corporation. All Rights Reserved.
** ============================================================================
**
** FILENAME
**
** 	pg_migrator.c
**	
**  DESCRIPTION
**
** The function of pg_migrator is to provide in-place (Binary) upgrade of
** a previous release to a newer one. The fact that it imitates
** a binary upgrade/downgrade, it will eliminate the need to dump/restore
** your databases in order to upgrade to newer versions. Consequently the
** upgrade overhead (in terms of time, space and server downtime) will be
** significantly improved. This document discusses the design and
** step-by-step process of pg_migrator utility.
** The pg_migrator can be used to upgrade from an older verion of PostgreSQL 
** or EnterpriseDB to a new version PostgreSQL or EnterpriseDB. It can also be used to 
** upgrade of an older  PostgreSQL version to newer EnterpriseDB version.
**
** USAGE
** 
** pg_migrator [OPTIONS]
** Options,
** -o, --old-datadir=OLDDATADIR old database directory
** -n, --new-datadir=NEWDATADIR new database directory
** -O, --old-bindir=OLDBINDIR old database executeable directory
** -N, --new-bindir=NEWBINDIR new database executeable directory
** -f, --logfile=LOGFILENAME  for log file
** -c|m, --mode=copy|move for copy|mv options
** -v, --verbose enable debugging
** -?, --help show this help, then exit
** -V, --version output version information, and then
**
** COPYRIGHT ACKNOWLEDGEMENTS
**
**   Copyright (c) 2005,2006 EnterpriseDB Corporation. All Rights Reserved.
**
** ORIGINAL AUTHORS
**
**   Ibrar Ahmed <ibrar@enterprisedb.com>
**   Sibtay <sibte@enterprisedb.com>
**
** REVIEWER
**
**   Ahsan Hadi <hadi@enterprisedb.com>
**
** CONTRIBUTING AUTHORS
**
**   Ibrar Ahmed <ibrar@enterprisedb.com>
**   Sibtay <sibte@enterprisedb.com>
**
** MAINTAINERS
**
**   Ibrar Ahmed <ibrar@enterprisedb.com>
**   Sibtay <sibte@enterprisedb.com>
**   Ahsan Hadi <hadi@enterprisedb.com>
**
** REFERENCE MATERIAL
**
**   http://www.postgresql.org/
**      - PostgreSQL Manual (8.x)
** ============================================================================*/

/* 
 * Standard operating system header files 
 */
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <locale.h>
#include <signal.h>
#include <grp.h>
#include <pwd.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/wait.h>
#ifdef HAVE_LANGINFO_H
#include <langinfo.h>
#endif

/*
 * Postgresql header files
 */
#include "postgres.h"
#include "libpq/pqsignal.h"
#include "libpq-fe.h"
#include "postgres_fe.h"
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "catalog/pg_control.h"
#include "catalog/pg_largeobject.h"

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif

#define POSTMASTER_UPTIME	10		
#define MAX_STRING		255
#define DUMP_FILE			"datamigdmp.sql"
#define DBG_FILE			"datamigdbg"

/* developer option, make it 1 to write debug messages in DBG_FILE */
#define DEBUG	0	

/* since we combine relnames with their schema names hence
 * we need to use twice the length normally used for relation names.
 * + 2 here is for the dot "." between schema name and relation name 
 * and one extra to always keep a '\0' in the end
 */
#define RELNAMEDATALEN (NAMEDATALEN + NAMEDATALEN + 2)

/*
 * since as convention we keep the last byte of the entity names
 * '\0' hence use this macro for entity names (other than relations
 * in which case RELNAMEDATALEN should be used).
 */
#define MYNAMEDATALEN	(NAMEDATALEN + 1)

#ifdef WIN32
	#define pg_copy_dir CopyDir
	#define pg_copy_file CopyFile
	#define pg_mv_file  MoveFile
	#define pg_link_file CreateLinkfile
#elif __linux__
	#define pg_copy_dir copy_dir
	#define pg_copy_file copy_file
	#define pg_mv_file  rename
	#define pg_link_file link
#endif /* __LINUX__ */

/*
 * Helper macro for snprintf (and similar) functions.
 * Note: use SIZEOF macro whereever you use the snprintf family functions,
 * and use the regular sizeof operator when you do memset.
 */
#define SIZEOF(arr)	(sizeof(arr) - 1)

/* 
 * Each relation is represented by a relinfo structure.
 */
typedef struct relinfo
{
	char		relname[RELNAMEDATALEN];	/* relation name 			 */
	Oid			reloid;						/* relation oid 			 */
	Oid			relfilenode;				/* relation relfile node 	 */
	Oid			toastrelid;					/* oid of the toast relation */
	char		tsp[MAXPGPATH];				/* relations tablespace path */
} RelInfo;

typedef struct relinfoarr
{
	RelInfo		*rels;
	int			 nrels;
} RelInfoArr;

/* 
 * The following structure represents a relation mapping.
 */
typedef struct filenodemap
{
	Oid   old;						  	/* Relfilenode of the old relation */
	Oid   new;						  	/* Relfilenode of the new relation */
	char  from[MAXPGPATH];
	char  to[MAXPGPATH];
	char  old_relname[RELNAMEDATALEN];	/* old name of the relation */
	char  new_relname[RELNAMEDATALEN];  /* new name of the relation */
} FileNodeMap;

/* 
 * Structure to store database information
 */
typedef struct dbinfo
{
	Oid			dboid;					/* oid of the database 				*/
	char		dbname[MYNAMEDATALEN];	/* database name 					*/
	RelInfoArr	rel_arr;				/* array of all user relinfos 		*/
	RelInfoArr	tsrel_arr;				/* array of all user toast relinfos */
} DbInfo;

typedef struct dbinfoarr
{
	DbInfo		*dbs;			/* array of db infos */
	int			 ndbs;			/* number of db infos */
} DbInfoArr;

/*
 * The following structure is used to hold pg_control information.
 * Rather than using the backend's control structure we use our own
 * structure to avoid pg_control version issues between releases.
 */
typedef struct controldata
{
	uint32 ctrl_ver;
	uint32 cat_ver;
	uint32 logid;
	uint32 nxtlogseg;
	uint32 chkpnt_tli;
	uint32 chkpnt_nxtxid;
	uint32 chkpnt_nxtoid;
} ControlData;

/*
 * Enumeration to denote link modes
 */
typedef enum
{
	LNK_MODE_CPY,				/* copy */
	LNK_MODE_MV					/* move(move means hard linking) */
} eLnkMode;

/* 
 * Enumeration to denote pg_log modes 
 */
typedef enum
{
	PG_INFO,
	PG_REPORT,
	PG_FATAL,
	PG_DEBUG
} eLogType;

/* 
 * Enumeration to denote directory options
 */
typedef enum
{
	OLDDATADIR,
	NEWDATADIR,
	OLDBINDIR,
	NEWBINDIR
} eDirOpt;

typedef long pgpid_t;

/*
 * Global variables
 */
static const char 		*g_progname 	 = NULL;		/* programe executeable name*/
static char 			*g_logfile 		 = NULL;		/* pg_log file name */
static bool 			 g_verbos 		 = 0;			/* use to show debug messages */
static eLnkMode 		 g_lnkmode;						/* mode of linking files (move/copy) */
static bool 			 g_check 		 = 0;			/* check option*/
static bool 			 g_caught_signal = 0;			/* signal captured or not */
static FILE 			*g_logfd 		 = 0;			/* log file name pointer */
static unsigned short 	 g_old_port 	 = 5432;		/* old server port number */
static unsigned short 	 g_new_port 	 = 5432;		/* new server port number */
static pgpid_t 			 g_pmpid 		 = 0;			/* pid of the current running postmaster. 0 if none running */
char   					 g_relnameext[MAX_STRING + 1];

#if DEBUG
static FILE 			*g_dbgfd 		 = NULL;		/* File descriptor for debug messages */
#endif

/*
 * private functions
 */
static void 			  usage(void);
static void 			  check_ok(void);
static void 			  trapsig(int signum);
static void 			  create_links(FileNodeMap *maps, int size);
static int 				  exec_prog(const char *cmd, bool throw_error);
static void 			  get_dbinfos(DbInfoArr *dbinfos,unsigned short port);
static void 			  define_dbs(DbInfoArr *db_arr, const char *binpath);
static void 			  create_fake_relfiles(DbInfoArr *db_arr, const char *datadir);
static void 			  remove_fake_relfiles(DbInfoArr *db_arr, const char *datadir);
static void 			  restore_toast_rels(DbInfoArr *olddb_arr, DbInfoArr *newdb_arr);
static void 			  get_ts_relinfos(const char *db, RelInfoArr *tsrel_arr);
static void 			  get_relinfos(const char *db, RelInfoArr *relarr, RelInfoArr *ts_relarr,unsigned short port);
static void 			  upgrade_all_dbs(DbInfoArr *olddb_arr, DbInfoArr *newdb_arr, char *old_pgdata, char *new_pgdata);
static void 			  exit_nicely(bool bexit);
static void 			  regen_db_infos(DbInfoArr *db_arr,unsigned short port);
static int 				  link_file(const char *fromfile, const char *tofile,const char *oldrelname,const char *newrelname);
static FileNodeMap 		 *gen_db_mappings(DbInfo *old_db, DbInfo *new_db, int *nmaps, const char *old_pgdata, const char *new_pgdata);
static void 			  gen_db_info(DbInfoArr *dbinfarr,unsigned short port);
static void 			  define_supfuncs( const char *mylibpath,const char *binpath, bool oldpm);
static bool 			  is_server_running(const char *datadir);
static bool 			  test_server_conn(int timeout,unsigned short port);
static DbInfo 			 *dbarr_lookupdb(DbInfoArr *db_arr, const char *dbname);
static RelInfo 			 *relarr_lookuprel(RelInfoArr *rel_arr, const char *relname);
static void 			  relarr_free(RelInfoArr *rel_arr);
static void 			  dbarr_free(DbInfoArr *db_arr);
static char 			**get_tblspc_paths(unsigned short port, int *size);
static void 			  ren_tblspcs(char **tblspcs, int n);
static void 			  start_postmaster(const char *bindir, const char *datadir, unsigned short port);
static void 			  stop_postmaster(const char *bindir, const char *datadir);
static void 			  map_rel(const RelInfo *oldrel, const RelInfo *newrel, const DbInfo *old_db, const DbInfo *new_db, const char *olddata, const char *newdata, FileNodeMap *map);
static void 			  map_relbyid(Oid	oldid, Oid newid, const char *oldrlname, const char *newrlname, const char *oldtsp, const DbInfo *old_db, const DbInfo *new_db, const char *olddata, const char *newdata, FileNodeMap *map);
static void 			  get_ctrl_data(const char *bindir, const char *datadir, ControlData *ctrl);

/* Utility Functions */
static void 	*pg_malloc(int size);
static void 	 pg_free(void *ptr);
static char 	*pg_strdup(const char *s);
static int 		 copy_file(const char *fromfile, const char *tofile, bool force);
static int 		 copy_dir(const char *from, const char *to, bool force);
static int 		 check_data_dir(const char *pg_data);
static int 		 validate_diropt(char *dirpath, eDirOpt type);
static void 	 check_ok(void);
static void 	 pg_log(eLogType type,char *fmt, ... );
static void 	 init_signals(void);
static void 	 do_verification(char *old_pgdata,char *old_bindir,char *new_pgdata, char *new_bindir);
static int		 validate_exec(const char *path);
static pgpid_t 	 get_pgpid(const char *datadir);
static uint32 	 get_pgver(const char *datadir);
static char 	*get_id(void);
       int 		 filter(const struct dirent *scan_ent);

#if DEBUG
static void 	relarr_print(RelInfoArr *arr);
static void 	maps_print(FileNodeMap *maps, int n);
static void 	dbarr_print(DbInfoArr *arr);
#endif

int main(int argc, char **argv)
{
    char 				cmd_line;       					/* For command line option char*/
    char 				old_pgdata[MAXPGPATH] = {0};		/* old PGDATA Path */
    char 				new_pgdata[MAXPGPATH] = {0};		/* new PGDATA Path */
    char 				old_bin[MAXPGPATH]    = {0};		/* old dbserver bin dir */
    char 				new_bin[MAXPGPATH]    = {0};		/* new dbserver bin dir */
    char 				home_dir[MAXPGPATH]   = {0};		/*TODO: Must remove User home dir ???*/
    char 				cmd[MAXPGPATH]        = {0};		/* for sql command build */
    char 				opts[MAX_STRING]      = {0};		/* command line option */
    char 				myexepath[MAXPGPATH]  = {0};		/* absolute path of this executeable */
    char 				mylibpath[MAXPGPATH]  = {0};		/* the absolute lib path */
    char 				cpy_from[MAXPGPATH]   = {0};		/* src path for copy command */
    char 				cpy_to[MAXPGPATH]     = {0};		/* destination path for copy command */
    char               *user                  = NULL;
	int		 			optindex 	          = 0;			/* used by getopt_long */
	int		 			ntblspcs 	          = 0;			
	int		  			i 		              = 0;
	uint32 				old_pgver 			  = 0;			/* version of old server */
	uint32				new_pgver 			  = 0;			/* version of new server */
	DbInfoArr			old_dbarr;							/* dbinfos array for old databases */
	DbInfoArr			new_dbarr;							/* dbinfos array for new databases */
	Oid					nxtoid 				  = InvalidOid;	/* next oid of the old system */
	char	  		  **tblspcs 			  = NULL;		/* array to store table space paths of old data */
	ControlData			oldctrl;							/* pg_control information of the old server */
	struct timeval 	  	stv;	
	struct timeval 	  	etv;
	struct timezone   	tz;

	static struct option long_options[] = {
		{"old-datadir", required_argument, NULL, 'o'},
		{"new-datadir", required_argument, NULL, 'n'},
		{"old-bindir", required_argument, NULL, 'O'},
		{"new-bindir", required_argument, NULL, 'N'},
		{"mode", required_argument, NULL, 'p'},
		{"user", required_argument, NULL, 'U'},
		{"logfile", required_argument, NULL, 'f'},
		{"libpath", required_argument, NULL, 'l'},
		{"verbose", no_argument, NULL, 'v'},
		{"check", no_argument, NULL, 'C'},
		{"help", no_argument, NULL, 'h'},
		{"version", no_argument, NULL, 'V'},
		{NULL, 0, NULL, 0}
		
	};

	/*
	 * Global variable initializations
	 */
	g_progname 		= NULL;
	g_logfile 		= NULL;
	g_verbos 		= false;
	g_lnkmode 		= LNK_MODE_CPY;
	g_check 		= false;
	g_caught_signal = false;
	g_logfd 		= NULL;
	g_pmpid 		= 0;

	if(argc > 1)
	{
		if(strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
		{
			usage();
			exit_nicely(true);
		}
		if(strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
		{
			pg_log(PG_REPORT,"pg_migrator (BETA 1) %s\n",PG_VERSION);
			exit_nicely(true);
		}
	}

	init_signals();

	/* process command-line options */
	g_progname = get_progname(argv[0]);
	get_home_path(home_dir);

#if DEBUG
	/* sorry for the compile time warning */
	char tmp[MAXPGPATH] = {0};
	
	snprintf(tmp, SIZEOF(tmp), "%s/%s", home_dir, DBG_FILE);
	g_dbgfd = fopen(tmp, "w");
#endif

	while((cmd_line = getopt_long(argc, argv, "o:n:vO:N:p:U:f:l:Cc|m", long_options, &optindex)) != -1)
	{
		switch (cmd_line)
		{
			case 'o':
				memset(old_pgdata, 0, sizeof(old_pgdata));
				snprintf(old_pgdata, SIZEOF(old_pgdata), "%s", optarg);
				break;
				
			case 'n':
				memset(new_pgdata, 0, sizeof(new_pgdata));
				snprintf(new_pgdata, SIZEOF(new_pgdata), "%s", optarg);
				break;
				
			case 'O':
				memset(old_bin, 0, sizeof(old_bin));
				snprintf(old_bin, SIZEOF(old_bin), "%s", optarg);	
				break;
				
			case 'N':
				memset(new_bin, 0, sizeof(new_bin));
				snprintf(new_bin, SIZEOF(new_bin), "%s", optarg);
				break;
				
			case 'v':
				pg_log(PG_REPORT,"Running in debug mode\n");
				g_verbos = true;	
				break;

			case 'f':
				g_logfile = pg_malloc(MAXPGPATH);
				memset(g_logfile, 0, MAXPGPATH);
				snprintf(g_logfile, MAXPGPATH - 1, "%s", optarg);
				break;

			case 'l':
				memset(mylibpath, 0, sizeof(mylibpath));
				snprintf(mylibpath, SIZEOF(mylibpath), "%s", optarg);
				break;

			case 'c':
				g_lnkmode = LNK_MODE_CPY;	
				break;

			case 'C':
				g_check = true;	
				break;

			case 'm':
				g_lnkmode = LNK_MODE_MV;	
				break;

			default:
				pg_log(PG_FATAL,"Try \"%s --help\" for more information.\n",g_progname);
		}
	}

	if(gettimeofday(&stv,&tz)  == -1)
		pg_log(PG_FATAL,"Unable to get time");

	/* disallow execution by the root user */
	user = get_id();
	pg_free(user);

	do_verification(old_pgdata, old_bin, new_pgdata, new_bin);
	
	/* get old and new server versions */
	old_pgver = get_pgver(old_pgdata);
	new_pgver = get_pgver(new_pgdata);

	/* start old postmaster */
	pg_log(PG_REPORT, "Starting old postmaster... ");
	start_postmaster(old_bin, old_pgdata, g_old_port);

	if(find_my_exec(argv[0], myexepath) != 0)
		pg_log(PG_FATAL, " Unable to find the my absolute path\n");
	
	/* find my lib path if not already specified */
	if(strlen(mylibpath) == 0)
		get_pkglib_path(argv[0], mylibpath); 

	pg_log(PG_REPORT, "Creating support functions... ");
	define_supfuncs(mylibpath, old_bin, true);
	check_ok();

	gen_db_info(&old_dbarr, g_old_port);	

#if DEBUG
	pg_log(PG_DEBUG, "Old dbinfos:\n");
	dbarr_print(&old_dbarr);
#endif

	pg_log(PG_REPORT, "Creating catalog dump... ");
	memset(cmd, 0, sizeof(cmd));
	snprintf(cmd, SIZEOF(cmd), "%s/pg_dumpall --schema-only > %s/%s", old_bin, home_dir, DUMP_FILE);
	exec_prog(cmd, true);
	check_ok();

	tblspcs = get_tblspc_paths(g_old_port, &ntblspcs);

	/* shutdown the old postmaster */
	pg_log(PG_REPORT, "Shutting down old postmaster... ");
	stop_postmaster(old_bin, old_pgdata);
	
	/* Rename all tablespace paths */
	ren_tblspcs(tblspcs, ntblspcs);

	for(i = 0; i < ntblspcs; i++)
		pg_free(tblspcs[i]);
	pg_free(tblspcs);

	/* get pg_control data of the old server */
	memset(&oldctrl, 0, sizeof(oldctrl));
	get_ctrl_data(old_bin, old_pgdata, &oldctrl);
	
	nxtoid = oldctrl.chkpnt_nxtoid;

	/* set the next transaction id of the new server */
	pg_log(PG_REPORT, "Setting next transaction id... ");
	memset(cmd, 0, sizeof(cmd));
	snprintf(cmd, SIZEOF(cmd), "%s/pg_resetxlog -f -x %u %s 1>/dev/null", new_bin, 
						oldctrl.chkpnt_nxtxid, new_pgdata);
	exec_prog(cmd, true);
	check_ok();

	/* now reset the wal archives */
	pg_log(PG_REPORT, "Resetting WAL archives... ");
	memset(cmd, 0, sizeof(cmd));
	snprintf(cmd, SIZEOF(cmd), "%s/pg_resetxlog -l %u,%u,%u %s >> %s 2>&1", new_bin,  
			    			oldctrl.chkpnt_tli, oldctrl.logid, oldctrl.nxtlogseg, 
						new_pgdata, g_logfile?g_logfile:"/dev/null");
 	exec_prog(cmd, true);
	check_ok();
	
	/* start new postmaster */
	pg_log(PG_REPORT, "Starting new postmaster... ");
	start_postmaster(new_bin, new_pgdata, g_new_port);

	pg_log(PG_REPORT, "Running vacuumdb on new server...");
	memset(cmd, 0, sizeof(cmd));
	snprintf(cmd, SIZEOF(cmd), "%s/vacuumdb --all --full --analyze >> %s 2>&1", 
			    new_bin,g_logfile?g_logfile:"/dev/null");
	exec_prog(cmd, true);
	check_ok();

	pg_log(PG_REPORT,"Shutting down new postmaster... ");
	stop_postmaster(new_bin, new_pgdata);
	
	/* copy old commit logs to new data dir */
	memset(cpy_from, 0, sizeof(cpy_from));
	memset(cpy_to, 0, sizeof(cpy_to));
	snprintf(cpy_from, SIZEOF(cpy_from), "%s/pg_clog", old_pgdata);
	snprintf(cpy_to, SIZEOF(cpy_to), "%s/pg_clog", new_pgdata);

	pg_log(PG_REPORT, "Deleting old commit clogs... ");
	memset(cmd, 0, sizeof(cmd));
	snprintf(cmd, SIZEOF(cmd), "rm -rf %s", cpy_to);
	exec_prog(cmd, true);
	check_ok();

	pg_log(PG_REPORT, "Copying commit clogs... ");
	memset(cmd, 0, sizeof(cmd));
	snprintf(cmd, SIZEOF(cmd), "cp -rf %s %s", cpy_from, cpy_to); 
	exec_prog(cmd, true);
	check_ok();

	if(g_check) 
	{
		char yesno = ' ';
		pg_log(PG_REPORT, "Do you want to commit changes? [Y/N]: ");
		scanf("%c",&yesno);

		if(yesno == 'Y' || yesno == 'y') 
		 	pg_log(PG_REPORT, "Commiting changes... \n");
		else
			exit_nicely(true);
	}	

	/* start new postmaster */
	pg_log(PG_REPORT, "Starting new postmaster... ");
	start_postmaster(new_bin, new_pgdata, g_new_port);
	
	pg_log(PG_REPORT, "Creating support functions... ");
	define_supfuncs(mylibpath, new_bin, false);
	check_ok();

	/* FIXME time should be reasonable ???*/
	sleep(1);

	/* although DUMP_FILE will create all the databases, but we need to
	 * perform this step before executing that file in order to handle
	 * toast tables.
	 */
	pg_log(PG_REPORT, "Creating databases... ");
	define_dbs(&old_dbarr, new_bin);
	check_ok();
	
	/* create fake relfilenodes for the toast relations */
	pg_log(PG_REPORT, "Create fake relfiles for toast relations... ");
	create_fake_relfiles(&old_dbarr, new_pgdata);
	check_ok();
	
	/* restore the previously taken dump */
	pg_log(PG_REPORT,"Restoring catalog dump... ");
	memset(cmd, 0, sizeof(cmd));
	memset(opts, 0 , sizeof(opts));
	
	snprintf(cmd, SIZEOF(cmd), "%s/psql template1 %s < %s/%s >> %s 2>&1", new_bin, opts, 
						home_dir, DUMP_FILE, g_logfile?g_logfile:"/dev/null");

	if(exec_prog(cmd, false) != 0)
	{
		snprintf(cmd, SIZEOF(cmd), "%s/edb-psql template1 %s < %s/%s >> %s 2>&1", 
							new_bin, opts, home_dir, DUMP_FILE, g_logfile?g_logfile:
							"/dev/null");
		exec_prog(cmd, true);
		
	}
	check_ok();

	/*
	 * The old schema is restored, so remove the fake relfiles created previously for 
	 * the toast relations.
	 */
	pg_log(PG_REPORT, "Removing fake relfiles of toast relations... ");
	remove_fake_relfiles(&old_dbarr, new_pgdata);	
	check_ok();

	gen_db_info(&new_dbarr, g_new_port);
	
	pg_log(PG_REPORT, "Restoring toast relations... ");
	restore_toast_rels(&old_dbarr, &new_dbarr);
	check_ok();

	/* restore_toast_rels would have created new entries in the new database.
	 * hence regenerate new_dbinfos to get in sync
	 */
	regen_db_infos(&new_dbarr, g_new_port);
#if DEBUG
	pg_log(PG_DEBUG, "New dbinfos:\n");
	dbarr_print(&new_dbarr);
#endif
	upgrade_all_dbs(&old_dbarr, &new_dbarr, old_pgdata, new_pgdata);
	
	pg_log(PG_REPORT,"Shutting down new postmaster... ");
	stop_postmaster(new_bin, new_pgdata);
	
	pg_log(PG_REPORT, "Setting next Oid... ");
	memset(cmd, 0, sizeof(cmd));
	snprintf(cmd, SIZEOF(cmd), "%s/pg_resetxlog -o %u %s 1>/dev/null", new_bin, 
						nxtoid, new_pgdata);
	exec_prog(cmd, true);
	check_ok();

	dbarr_free(&old_dbarr);
	dbarr_free(&new_dbarr);
	pg_free(g_logfile);

	if(g_logfd != NULL) 
	{ 
		fclose(g_logfd); 
		g_logfd = NULL; 
	}

#if DEBUG
	if(g_dbgfd) 
		fclose(g_dbgfd);
#endif

	if(gettimeofday(&etv,&tz)  == -1)
		pg_log(PG_FATAL,"Unable to get time");
	
	pg_log(PG_REPORT,"The data migration completed in %d seconds\n",etv.tv_sec - stv.tv_sec);
	return 0;
}

/* init_signals()
 * 
 * intalls trapsig function as the handler for SIGHUP, SIGINT, SIGQUIT, SIGQUIT and
 * SIGPIPE
 */
static void 
init_signals(void)
{
#ifdef SIGHUP
		pqsignal(SIGHUP, trapsig);
#endif
#ifdef SIGINT
		pqsignal(SIGINT, trapsig);
#endif
#ifdef SIGQUIT
		pqsignal(SIGQUIT, trapsig);
#endif
#ifdef SIGTERM
		pqsignal(SIGTERM, trapsig);
#endif
	
#ifdef SIGPIPE
		pqsignal(SIGPIPE, SIG_IGN);
#endif
}

/* do_verification()
 *
 * does all the hectick work of verifying directories and executables
 * of old and new server.
 *
 * NOTE: May update the values of all parameters
 */
static void
do_verification(char *old_pgdata, char *old_bindir, char *new_pgdata, char *new_bindir)
{
	char	cmd[MAXPGPATH] = {0};
	
	/* verify old data dir */
	validate_diropt(old_pgdata, OLDDATADIR);
	pg_log(PG_REPORT, "Checking old data directory %s... ", old_pgdata);

	if(check_data_dir(old_pgdata) != 0) 
		pg_log(PG_FATAL, "Failed\n");
	check_ok();

	/* verify new data dir */
	validate_diropt(new_pgdata, NEWDATADIR);
	pg_log(PG_REPORT, "Checking new data directory %s... ", new_pgdata);

	if(check_data_dir(new_pgdata) != 0) 
		pg_log(PG_FATAL, "Failed\n");
	check_ok();

	/* verify old bin dir */	
	validate_diropt(old_bindir, OLDBINDIR);
	pg_log(PG_REPORT, "Checking old executable directory %s... ", old_bindir);
	check_ok();

	/* verify new bin dir */	
	validate_diropt(new_bindir, NEWBINDIR);
	pg_log(PG_REPORT, "Checking new executable directory %s... ", new_bindir);
	check_ok();

	/* the old postmaster should not be running */
	if(is_server_running(old_pgdata))
	{
		pg_log(PG_FATAL, "\nlock file \"postmaster.pid\" for the old server exists\n"
			  "Is the old server running?  If not, delete the lock file and try"
			  "again.\n");
	}
		
	/* same goes for the new postmaster */
	if(is_server_running(new_pgdata))
	{
		pg_log(PG_FATAL, "\nlock file \"postmaster.pid\" for the new server exists\n"
			  "Is the new server running?  If not, delete the lock file and try"
			  "again.\n");
	}
	
	/* verify that old postmaster or edb-postmaster exits */
	memset(cmd, 0, sizeof(cmd));
	snprintf(cmd, SIZEOF(cmd), "%s/postmaster", old_bindir);
	pg_log(PG_REPORT,"Checking %s... ", cmd);

	if(validate_exec(cmd) != 0)
	{
		pg_log(PG_REPORT, "\n");
		snprintf(cmd, SIZEOF(cmd), "%s/edb-postmaster", old_bindir);
		pg_log(PG_REPORT, "Checking %s... ", cmd);
		if(validate_exec(cmd) != 0)
			pg_log(PG_FATAL, "Failed\n");

		g_old_port = 5444;	/* ??? */
	}

	check_ok();

	/* verify that old postgres or edb-postgres exits */
	snprintf(cmd, SIZEOF(cmd), "%s/postgres", old_bindir);
	pg_log(PG_REPORT,"Checking %s... ", cmd);

	if(validate_exec(cmd) != 0)
	{
		pg_log(PG_REPORT, "\n");
 		snprintf(cmd, SIZEOF(cmd), "%s/edb-postgres", old_bindir);
 	 	pg_log(PG_REPORT, "Checking %s... ", cmd);
		if(validate_exec(cmd) != 0)
			pg_log(PG_FATAL, "Failed\n");
	}

	check_ok();
	
	/* verify old pg_ctl */
	snprintf(cmd, SIZEOF(cmd), "%s/pg_ctl", old_bindir);
	pg_log(PG_REPORT,"Checking %s... ", cmd);
	if(validate_exec(cmd) != 0)
		pg_log(PG_FATAL, "Failed\n");
	check_ok();
	
	/* verify old pg_dumpall */

	snprintf(cmd, SIZEOF(cmd), "%s/pg_dumpall", old_bindir);
	pg_log(PG_REPORT,"Checking %s... ", cmd);
	if(validate_exec(cmd) != 0)
		pg_log(PG_FATAL,"Failed\n");
	check_ok();

	/* verify new postmaster or edb-postmaster */
	snprintf(cmd, SIZEOF(cmd), "%s/postmaster", new_bindir);
	pg_log(PG_REPORT,"Checking %s... ", cmd);
	if(validate_exec(cmd) != 0)
	{
		pg_log(PG_REPORT, "\n");
		snprintf(cmd, SIZEOF(cmd), "%s/edb-postmaster", new_bindir);
		pg_log(PG_REPORT,"Checking %s... ", cmd);
		if(validate_exec(cmd) != 0)
			pg_log(PG_FATAL,"Failed\n");
		g_new_port = 5444;	
	}
	check_ok();

	/* verify new postgres or edb-postgres */
	snprintf(cmd, SIZEOF(cmd), "%s/postgres", new_bindir);
	pg_log(PG_REPORT,"Checking %s... ", cmd);
	if(validate_exec(cmd) != 0)
	{
		pg_log(PG_REPORT, "\n");
		snprintf(cmd, SIZEOF(cmd), "%s/edb-postgres", new_bindir);
		pg_log(PG_REPORT,"Checking %s... ", cmd);
		if(validate_exec(cmd) != 0)
			pg_log(PG_FATAL, "Failed\n");
	}
	check_ok();

	/* verify new new pg_ctl */
	snprintf(cmd, SIZEOF(cmd), "%s/pg_ctl", new_bindir);
	pg_log(PG_REPORT,"Checking %s... ", cmd);
	if(validate_exec(cmd) != 0)
		 pg_log(PG_FATAL, "Failed\n");
	check_ok();
	
	/* verify new new pg_dumpall */
	snprintf(cmd, SIZEOF(cmd), "%s/pg_dumpall", new_bindir);
	pg_log(PG_REPORT,"Checking %s... ", cmd);
	if(validate_exec(cmd) != 0)
		pg_log(PG_FATAL, "Failed\n");
	check_ok();

	/* verify new psql or edb-psql */
	snprintf(cmd, SIZEOF(cmd), "%s/psql", new_bindir);
	pg_log(PG_REPORT,"Checking %s... ", cmd);
	if(validate_exec(cmd) != 0)
	{
		pg_log(PG_REPORT, "\n");
		snprintf(cmd, SIZEOF(cmd), "%s/edb-psql", new_bindir);
		pg_log(PG_REPORT,"Checking %s... ", cmd);
		if(validate_exec(cmd) != 0)
			pg_log(PG_FATAL, "Failed\n");
	}
	check_ok();
}

static void 
exit_nicely(bool bexit)
{
	pg_free(g_logfile);
	if(g_logfd) 
		fclose(g_logfd);
#if DEBUG
	if(g_dbgfd) 
		fclose(g_dbgfd);
#endif
	/* terminate any running instance of postmaster */
	if(g_pmpid != 0) 
		kill(g_pmpid, SIGTERM);

	if(!bexit)
	{
		/*
		 * FIXME must delete intermidiate files
		 */
	 	exit(1);
	}
	else
		exit(0);
}

/* is_server_running()
 *
 * checks whether postmaster/edb-postmaster on the given data directory is running 
 * or not. The check is performed by looking for the existence of postmaster.pid file.
 */
static bool
is_server_running(const char *datadir)
{
	char path[MAXPGPATH] = {0};
	int fd = 0;
	
	memset(path, 0, sizeof(path));
	snprintf(path, SIZEOF(path), "%s/postmaster.pid", datadir);

	if((fd = open(path, O_RDONLY)) < 0)
	{
		if(errno != ENOENT)
			pg_log(PG_FATAL,"\ncould not open file \"%s\" for reading\n", path);

		return false;
	}

	close(fd);
	return true;
}

/* get_pgpid()
 *
 * Returns the pid of the postmaster running on datadir. pid is retrieved
 * from the postmaster.pid file
 */
static pgpid_t
get_pgpid(const char *datadir)
{
	FILE		*pidf;
	long		pid;
	char		pid_file[MAXPGPATH] = {0};

	snprintf(pid_file, SIZEOF(pid_file), "%s/postmaster.pid", datadir);
	pidf = fopen(pid_file, "r");

	if(pidf == NULL) 
		return (pgpid_t) 0;

	if(fscanf(pidf, "%ld", &pid) != 1)
	{
		fclose(pidf);
		pg_log(PG_FATAL, "%s: invalid data in PID file \"%s\"\n", g_progname, pid_file);
	}

	fclose(pidf);

	return (pgpid_t) pid;
}

/* get_pgver()
 *
 * gets the version (in unsigned int form) for the given "datadir". Assumes
 * that datadir is an absolute path to a valid pgdata directory. The version
 * is retrieved by reading the PG_VERSION file.
 */
static uint32 
get_pgver(const char *datadir)
{
	FILE	*verf = NULL;
	char	ver_file[MAXPGPATH] = {0};
	int	vmaj = 0;
	int	vmin = 0;

	if(!datadir) 
		return 0;

	snprintf(ver_file, SIZEOF(ver_file), "%s/PG_VERSION", datadir);
	verf = fopen(ver_file, "r");

	if(verf == NULL) 
		return 0;

	if(fscanf(verf, "%d.%d", &vmaj, &vmin) != 2)
	{
		pg_log(PG_REPORT, "could'nt get version from %s\n", datadir);
		fclose(verf);
		return 0;
	}

	return (100 * vmaj + vmin) * 100;
}

/* start_postmaster()
 *
 */
static void
start_postmaster(const char *bindir, const char *datadir, unsigned short port)
{
	char	cmd[MAXPGPATH] = {0};
	
	snprintf(cmd, SIZEOF(cmd), "%s/pg_ctl -D %s start >> %s 2>&1", bindir, datadir, g_logfile?g_logfile:"/dev/null");
	exec_prog(cmd, true);
	check_ok();
	
	/* wait for the server to start properly */
	pg_log(PG_REPORT, "Waiting for postmaster to start...");

	if(test_server_conn(POSTMASTER_UPTIME, port) == false)
		pg_log(PG_FATAL, " Unable to start postmaster with the command: %s", cmd);
	check_ok();

	if((g_pmpid = get_pgpid(datadir)) == 0)
		pg_log(PG_FATAL, " Unable to get postmaster pid\n");
}

/* stop_postmaster()
 *
 */
static void
stop_postmaster(const char *bindir, const char *datadir)
{
	char	cmd[MAXPGPATH] = {0};

	snprintf(cmd, SIZEOF(cmd), "%s/pg_ctl -D %s stop >> %s 2>&1", bindir, datadir,
					g_logfile?g_logfile:"/dev/null");
	exec_prog(cmd, true);
	check_ok();

	g_pmpid = 0;
}

/* test_server_conn()
 *
 * tests whether postmaster is running or not by trying to connect
 * to it. If connection is unsuccessfull we do a sleep of 1 sec and then
 * try the connection again. This process continues "timeout" times.
 *
 * Returns true if the connection attempt was successfull, false otherwise.
 */
static bool
test_server_conn(int timeout, unsigned short port)
{                                                                                 
	PGconn		*conn				 = NULL;
	char		con_opts[MAX_STRING] = {0};
	int			i;

	snprintf(con_opts, SIZEOF(con_opts), "dbname = template1 port = %d ", port);

	for(i = 0; i < timeout; i++)
	{
		if((conn = PQconnectdb(con_opts)) != NULL && (PQstatus(conn) == CONNECTION_OK))
		{
			PQfinish(conn);
			return true;
		}

		pg_log(PG_REPORT, ".");
		sleep(1);
	}

	return false;
}

static void
trapsig(int signum)
{
	/* handle systems that reset the handler, like Windows (grr) */
	pqsignal(signum, trapsig);
	g_caught_signal = true;
}

static int
exec_prog(const char *cmd, bool throw_error)
{
	int	result;
	
	pg_log(PG_INFO, "%s\n", cmd);
	result = system(cmd);
	
	if(result != 0)
	{
		pg_log(throw_error?PG_FATAL:PG_INFO, "\nThere were problems executing %s\n", cmd);
		return 1;
	}	
	
	return 0;
}

static void 
usage(void)
{
	printf(_("\nUsage:\n\
pg_migrator [OPTIONS]...\n\n\
Options:\n\
 -o, --old-datadir=OLDDATADIR\t\told database directory\n\
 -n, --new-datadir=NEWDATADIR\t\tnew database directory\n\
 -O, --old-bindir=OLDBINDIR\t\told database executable directory\n\
 -N, --new-bindir=NEWBINDIR\t\tnew database executable directory\n\
 -f, --logfile=LOGFILENAME\t\tfor log file\n\
 -l, --libpath=lib directory\t\tlocation of pg_migrator lib files\n\
 c|m,--mode=copy|move\t\t\tfor copy|mv options\n\
 -C, --check\t\t\t\tfor checking\n\
 -v, --verbose\t\t\t\tenable debugging\n\
 -?, --help\t\t\t\tshow this help, then exit\n\
 -V, --version\t\t\t\toutput version information, then exit\n\n\
Report bugs to <support@enterprisedb.com>.\n"));
}

/* gen_db_info()
 *
 * higher level routine to generate dbinfos for the database running
 * on the given "port". Assumes that server is already running.
 */
static void 
gen_db_info(DbInfoArr *dbinfarr, unsigned short port)
{
	int	i = 0;
	
	if(!dbinfarr) 
		pg_log(PG_FATAL, "%d: an internal error occured\n", __LINE__);
	
	get_dbinfos(dbinfarr, port);

	for(i = 0; i < dbinfarr->ndbs; i++)
		get_relinfos(dbinfarr->dbs[i].dbname, &(dbinfarr->dbs[i].rel_arr), &(dbinfarr->dbs[i].tsrel_arr), port);
}

/* upgrade_all_dbs()
 *
 * Responsible for upgrading all database. invokes routines to generate mappings and then 
 * physically link the databases.
 */
static void
upgrade_all_dbs(DbInfoArr *olddb_arr, DbInfoArr *newdb_arr, char *old_pgdata, char *new_pgdata)
{
	int			 i 					= 0;
	int			 n_maps 			= 0;
	char		 from[MAXPGPATH]    = {0};
	char		 to[MAXPGPATH] 	    = {0};
	FileNodeMap	*mappings 			= NULL;
	DbInfo		*olddb 				= NULL;
	DbInfo		*newdb 				= NULL;
	
	if((olddb_arr == NULL) || (newdb_arr == NULL) || (old_pgdata == NULL) || (new_pgdata == NULL)) 
		pg_log(PG_FATAL, "%d: an internal error occured", __LINE__);

	for(i = 0; i < newdb_arr->ndbs; i++)
	{
		newdb = newdb_arr->dbs + i;
		olddb = dbarr_lookupdb(olddb_arr, newdb->dbname);

		if(!olddb) 
			continue;

		n_maps = 0;
		mappings = gen_db_mappings(olddb, newdb, &n_maps, old_pgdata, new_pgdata);
#if DEBUG
		pg_log(PG_DEBUG, "mappings for db %s:\n", newdb->dbname);
		maps_print(mappings, n_maps);
		pg_log(PG_DEBUG, "\n\n");
#endif
 		create_links(mappings, n_maps);
		pg_free(mappings);
				
		/* the pg_largeobject system catalog is treated as a user
	 	 * table. Since we already know its OID hence we simply 
	 	 * link it
	 	 */
		memset(from, 0, sizeof(from));
		memset(to, 0, sizeof(to));
	
		snprintf(from, SIZEOF(from), "%s/base/%u/%u", new_pgdata, newdb->dboid, LargeObjectRelationId);
		snprintf(to, SIZEOF(to), "%s/base/%u/%u", old_pgdata, olddb->dboid, LargeObjectRelationId);
				
		unlink(from);
		link_file(from, to, newdb->dbname, olddb->dbname);
	} 
}

/* get_tblspc_paths()
 *
 * Scans pg_tablespace and returns a malloc'ed array of all tablespace
 * paths. Its the caller's responsibility to free the array.
 */
static char **
get_tblspc_paths(unsigned short port, int *size)
{
	char		**paths 			   = NULL;
	PGconn		 *conn 				   = NULL;
   	PGresult	 *res 				   = NULL;
	int			  ntups 			   = 0;
	int			  i 				   = 0;
	char		  con_opts[MAX_STRING] = {0};
	int			  i_spclocation 	   = -1;

	snprintf(con_opts, SIZEOF(con_opts), "dbname = template1 port = %d", port);
	conn = PQconnectdb(con_opts);
	if(PQstatus(conn) != CONNECTION_OK)
	{
		pg_log(PG_REPORT, "Connection to database failed: %s",
		PQerrorMessage(conn));
		PQfinish(conn);
		exit_nicely(false);
	}

	res = PQexec(conn, "BEGIN");	
	if(PQresultStatus(res) != PGRES_COMMAND_OK)
	{
		pg_log(PG_REPORT, "BEGIN command failed: %s", PQerrorMessage(conn));
		PQclear(res);
		PQfinish(conn);
		exit_nicely(false);
	}
	PQclear(res);
	
	res = PQexec(conn, "DECLARE myportal CURSOR FOR select spclocation from pg_tablespace \
					WHERE spcname not like 'pg_default' AND spcname not like 'pg_global'");
	
	if(PQresultStatus(res) != PGRES_COMMAND_OK)
	{
        	pg_log(PG_REPORT, "DECLARE CURSOR failed: %s", PQerrorMessage(conn));
        	PQclear(res);
		PQfinish(conn);
        	exit_nicely(false);
	}
	PQclear(res);
	
	res = PQexec(conn, "FETCH ALL in myportal");

	if(PQresultStatus(res) != PGRES_TUPLES_OK)
	{
		pg_log(PG_REPORT, "FETCH ALL failed: %s", PQerrorMessage(conn));
		PQclear(res);
		PQfinish(conn);
		exit_nicely(false);
	}

	ntups = PQntuples(res);
	paths = (char **) pg_malloc(ntups * sizeof(char *));

	i_spclocation = PQfnumber(res, "spclocation");

	for(i = 0; i < ntups; i++)
		paths[i] = pg_strdup(PQgetvalue(res, i, i_spclocation));

	PQclear(res);
	
	res = PQexec(conn, "CLOSE myportal");
	PQclear(res);
	
	res = PQexec(conn, "END");
	PQclear(res);

	PQfinish(conn);

	*size = i;
	return paths;
}

/* ren_tblspcs()
 *
 * rename tablespace paths in "tblspcs" array to *_old
 */
static void
ren_tblspcs(char **tblspcs, int n)
{
	int	  i 				  = 0;
	char  old_path[MAXPGPATH] = {0};
	char  new_path[MAXPGPATH] = {0};

	if(!tblspcs) 
		return;

	for(i = 0; i < n; i++)
	{
		/* rename the old tablespace directory to *_old */
		memset(old_path, 0, sizeof(old_path));
		memset(new_path, 0, sizeof(new_path));
		snprintf(old_path, SIZEOF(old_path), "%s", tblspcs[i]);
		snprintf(new_path, SIZEOF(new_path), "%s_old", tblspcs[i]);

		pg_log(PG_INFO, "moving %s to %s\n", old_path, new_path);
		pg_mv_file(old_path, new_path);
		
		/* create an empty directory for the current tablespace */
		if(mkdir(old_path, 0x755) == -1)
			pg_log(PG_REPORT, "error while creating tablespace directory %s: %s\n", 
						old_path, strerror(errno));
	}
}

/* gen_db_mappings()
 *
 * generates database mappings for "old_db" and "new_db". Returns a malloc'ed
 * array of mappings. nmaps is a return parameter which refers to the number 
 * mappings.
 *
 * NOTE: Its the Caller's responsibility to free the returned array.
 */
static FileNodeMap *
gen_db_mappings(DbInfo *old_db, DbInfo *new_db, int *nmaps, const char *old_pgdata, const char *new_pgdata)
{
	FileNodeMap	*maps 	= NULL;
	int			 i 		= 0;
	int			 imaps 	= 0;
	RelInfo		*newrel = NULL;
	RelInfo		*oldrel = NULL;
	
	maps = (FileNodeMap *) pg_malloc(sizeof(FileNodeMap) * (new_db->rel_arr.nrels + new_db->tsrel_arr.nrels));
	
	for(i = 0; i < new_db->rel_arr.nrels; i++)
	{
		newrel = new_db->rel_arr.rels + i;
		oldrel = relarr_lookuprel(&(old_db->rel_arr), newrel->relname);

		if(!oldrel) 
			pg_log(PG_FATAL, "%d: an internal error occured\n", __LINE__);

		map_rel(oldrel, newrel, old_db, new_db, old_pgdata, new_pgdata, maps + imaps);
		imaps++;
				
		/* so much for the mapping of this relation. Now we need 
		 * a mapping for its corresponding toast relation if any.
		 */
		if(oldrel->toastrelid > 0)	
		{
			RelInfo		*newts;
			RelInfo		*oldts;
			char		newname[MAXPGPATH] = {0};
			char		oldname[MAXPGPATH] = {0};

			/* construct the new and old relnames for the toast relation */
			memset(oldname, 0, sizeof(oldname));
			memset(newname, 0, sizeof(newname));
			snprintf(oldname, SIZEOF(oldname), "pg_toast.pg_toast_%u", oldrel->reloid);
			snprintf(newname, SIZEOF(newname), "pg_toast.pg_toast_%u", newrel->reloid);

			/* look them up in their respective arrays */
			oldts = relarr_lookuprel(&old_db->tsrel_arr, oldname);
			if(oldts == NULL) 
				pg_log(PG_FATAL, "%d: an internal error occured\n", __LINE__);

			newts = relarr_lookuprel(&new_db->tsrel_arr, newname);
			if(newts == NULL) 
				pg_log(PG_FATAL, "%d: an internal error occured\n", __LINE__);

			/* finally create a mapping for them */
			map_rel(oldts, newts, old_db, new_db, old_pgdata, new_pgdata, maps + imaps);
			imaps++;	

			/* also need to provide a mapping for the index of this toast
			 * relation. The procedure is similar to what we did above for
			 * toast relation itself, the only difference being that the relnames 
			 * need to be appended with _index.
			 */

			/* construct the new and old relnames for the toast index relations */
			memset(oldname, 0, sizeof(oldname));
			memset(newname, 0, sizeof(newname));
			snprintf(oldname, SIZEOF(oldname), "pg_toast.pg_toast_%u_index", oldrel->reloid);
			snprintf(newname, SIZEOF(newname), "pg_toast.pg_toast_%u_index", newrel->reloid);
		
			/* look them up in their respective arrays */
			oldts = relarr_lookuprel(&old_db->tsrel_arr, oldname);
			if(oldts == NULL) 
				pg_log(PG_FATAL, "%d: an internal error occured\n", __LINE__);

			newts = relarr_lookuprel(&new_db->tsrel_arr, newname);
			if(newts == NULL) 
				pg_log(PG_FATAL, "%d: an internal error occured\n", __LINE__);
			
			/* finally create a mapping for them */
			map_rel(oldts, newts, old_db, new_db, old_pgdata, new_pgdata, maps + imaps);
			imaps++;
		}
	}
	
	*nmaps = imaps;
	return maps;
}

/* create_links()
 *
 * create links for mappings stored in "maps" array.
 */
static void
create_links(FileNodeMap *maps, int size)
{
	int				i 	   				 = 0;
	int 			numFiles 			= 0;
	char			new_file[MAXPGPATH] = {0};
	char			old_file[MAXPGPATH] = {0};
	struct dirent **namelist 			= NULL;
		
	for(i = 0; i < size; i++)
	{
		memset(new_file, 0, sizeof(new_file));
		memset(old_file, 0, sizeof(old_file));

		snprintf(new_file, SIZEOF(new_file), "%s/%u", maps[i].from, maps[i].new);
		snprintf(old_file, SIZEOF(old_file), "%s/%u", maps[i].to, maps[i].old);

		unlink(new_file);
		link_file(new_file, old_file,maps[i].old_relname,maps[i].new_relname);

		snprintf(g_relnameext,SIZEOF(g_relnameext),"%u.",maps[i].old);
		numFiles = scandir(maps[i].to, &namelist, filter, alphasort);
				
		while(numFiles--)
		{
			snprintf(new_file, SIZEOF(new_file), "%s/%u%s", maps[i].from, maps[i].new, strchr(namelist[numFiles]->d_name,'.'));
			snprintf(old_file, SIZEOF(old_file), "%s/%s", maps[i].to, namelist[numFiles]->d_name);
			unlink(new_file);
			link_file(new_file, old_file,maps[i].old_relname,maps[i].new_relname); pg_free(namelist[numFiles]);
		}

		pg_free(namelist);
	}
}

/* link_file()
 *
 * This routine is responsible for creating the physical links between relfiles. 
 * Based on the global g_lnkmode variable the link can be a mere copy or a
 * hardlink (very dangerous!).
 */
static int
link_file(const char *newfile, const char *oldfile, const char *oldrelname, const char *newrelname)
{
	int	retcode = 0;
	
	switch(g_lnkmode)
	{
		case LNK_MODE_CPY:
		{
			pg_log(PG_INFO, "copying %s to %s\n", oldfile, newfile);
			
			if((retcode = pg_copy_dir(oldfile, newfile, true)) == -1)
				pg_log(PG_REPORT, "error while copying %s(%s) to %s(%s): %s\n", newrelname,newfile, oldrelname,oldfile, strerror(errno));

			break;
		}

		case LNK_MODE_MV:
		{
			pg_log(PG_INFO, "linking %s to %s\n", newfile, oldfile);
			
			if((retcode = pg_link_file(oldfile, newfile)) == -1)
				pg_log(PG_REPORT, "error while creating hard link from %s(%s) to %s(%s): %s\n", newrelname,newfile, oldrelname,oldfile, strerror(errno));
							
			break;
		}

		default:
		{
			pg_log(PG_FATAL, "\nUnrecognized link mode %d", g_lnkmode);
			break;
		}
	}
	
	return retcode;
}

/* get_dbinfos()
 *
 * Scans pg_database system catalog and returns (in dbinfs_arr) all user
 * databases.
 */
static void
get_dbinfos(DbInfoArr *dbinfs_arr, unsigned short port)
{
	PGconn		*conn 				 = NULL;
   	PGresult	*res 				 = NULL;
	int			 ntups 				 = 0;
	DbInfo		*dbinfos 			 = NULL;
	int			 i 					 = 0;
	char		con_opts[MAX_STRING] = {0};
	int			 i_datname 			 = -1;
	int			 i_oid 				 = -1;
	
	if(!dbinfs_arr) 
		pg_log(PG_FATAL, "%d: an internal error occured\n", __LINE__);

	snprintf(con_opts, SIZEOF(con_opts), "dbname = template1 port = %d ", port);
	conn = PQconnectdb(con_opts);

	if(PQstatus(conn) != CONNECTION_OK)
	{
		pg_log(PG_REPORT, "Connection to database failed: %s",
		PQerrorMessage(conn));
		PQfinish(conn);
		exit_nicely(false);
	}

	res = PQexec(conn, "BEGIN");
	if(PQresultStatus(res) != PGRES_COMMAND_OK)
	{
		pg_log(PG_REPORT, "BEGIN command failed: %s", PQerrorMessage(conn));
		PQclear(res);
		PQfinish(conn);
		exit_nicely(false);
	}
	PQclear(res);
	
	res = PQexec(conn, "DECLARE myportal CURSOR FOR SELECT oid, datname FROM pg_database \
					WHERE lower(datname) <> 'template1' AND lower(datname) <> 'template0' \
					AND lower(datname) <> 'edb' AND lower(datname) <> 'postgres'");	
	
	if(PQresultStatus(res) != PGRES_COMMAND_OK)
	{
        	pg_log(PG_REPORT, "DECLARE CURSOR failed: %s", PQerrorMessage(conn));
        	PQclear(res);
		PQfinish(conn);
        	exit_nicely(false);
	}
	PQclear(res);
	
	res = PQexec(conn, "FETCH ALL in myportal");
	if(PQresultStatus(res) != PGRES_TUPLES_OK)
	{
		pg_log(PG_REPORT, "FETCH ALL failed: %s", PQerrorMessage(conn));
		PQclear(res);
		PQfinish(conn);
		exit_nicely(false);
	}

	i_datname = PQfnumber(res, "datname");
	i_oid = PQfnumber(res, "oid");

	ntups = PQntuples(res);
	dbinfos = (DbInfo *) pg_malloc(sizeof(DbInfo) * ntups);
	
	for(i = 0; i < ntups; i++)
	{
		dbinfos[i].dboid = atol(PQgetvalue(res, i, i_oid));

		memset(dbinfos[i].dbname, 0, sizeof(dbinfos[i].dbname));
		snprintf(dbinfos[i].dbname, SIZEOF(dbinfos[i].dbname), "%s", PQgetvalue(res, i, i_datname));
	}
	PQclear(res);
	
	res = PQexec(conn, "CLOSE myportal");
	PQclear(res);
	
	res = PQexec(conn, "END");
	PQclear(res);

	PQfinish(conn);
	
	dbinfs_arr->dbs = dbinfos;
	dbinfs_arr->ndbs = ntups;
}

/* get_relinfos()
 *
 * gets the relinfos for all the user tables of the database refered by "db". The 
 * user relations array is returned in "relarr" while the toast relations are seperately
 * returned in "ts_relarr". 
 *
 * NOTE: we assume that relations/entities with oids greater than 12000 belongs to the user
 */
static void
get_relinfos(const char *db, RelInfoArr *relarr, RelInfoArr *ts_relarr, unsigned short port)
{
	PGconn		*conn 						   = NULL;
	PGresult	*res 						   = NULL;
	RelInfo		*relinfos 					   = NULL;
	RelInfo		*toast_rinfos 				   = NULL;
	int			 ntups;
	int			 i 							   = 0;
	int			 itoast 					   = 0;
	int			 irel 						   = 0;
	char			con_opts[MAX_STRING] 	   = {0};
	RelInfo		*curr 						   = NULL;
	char 		*from 						   = NULL;
	char 		*relname 					   = NULL;
	char 		 relnamestr[RELNAMEDATALEN]    = {0};
	int			 i_spclocation 				   = -1;
	int			 i_relname 					   = -1;
	int			 i_oid 						   = -1;
	int			 i_relfilenode 				   = -1;
	int			 i_reltoastrelid 			   = -1;
	int			 i_nspname 					   = -1;
	
	if(!db || !relarr || !ts_relarr) 
		pg_log(PG_FATAL, "%d: an internal error occured\n", __LINE__);

	snprintf(con_opts, SIZEOF(con_opts), "dbname = %s port = %d", db, port);
	conn = PQconnectdb(con_opts);
		
	if(PQstatus(conn) != CONNECTION_OK)
	{
		pg_log(PG_REPORT, "Connection to database failed: %s",
		PQerrorMessage(conn));
		exit_nicely(false);
	}
	
	res = PQexec(conn, "BEGIN");
	if(PQresultStatus(res) != PGRES_COMMAND_OK)
	{
		pg_log(PG_REPORT, "BEGIN command failed: %s", PQerrorMessage(conn));
		PQclear(res);
		PQfinish(conn);
		exit_nicely(false);
	}
	PQclear(res);

	res = PQexec(conn,	" DECLARE myportal CURSOR FOR "
					" SELECT distinct c.oid, c.relname, c.relfilenode," 
                              "  c.reltoastrelid, t.spclocation,n.nspname " 
                              "  FROM pg_class c" 
					" left outer join pg_tablespace t on c.reltablespace = t.oid, " 
					" pg_namespace n " 
                              " WHERE relnamespace <> (select oid from pg_namespace where " 
                              " nspname='pg_catalog') " 
                              " AND relnamespace <> (select oid from pg_namespace where" 
                              " nspname='information_schema')" 
                              " AND c.oid >= 12000" 
                              " AND (relkind = 'r' OR relkind = 't' OR relkind = 'i')" 
                              " AND c.relnamespace = n.oid" 
                              " group by c.oid, c.relname, c.relfilenode," 
                              " c.reltoastrelid, t.spclocation,n.nspname " 
                              " ORDER BY c.relname;");
	if(PQresultStatus(res) != PGRES_COMMAND_OK)
	{
		pg_log(PG_REPORT, "DECLARE CURSOR failed: %s", PQerrorMessage(conn));
		PQclear(res);
		PQfinish(conn);
		exit_nicely(false);
	}		
	PQclear(res);
	
	res = PQexec(conn, "FETCH ALL in myportal");
	if(PQresultStatus(res) != PGRES_TUPLES_OK)
	{
		pg_log(PG_REPORT, "FETCH ALL failed: %s", PQerrorMessage(conn));
		PQclear(res);
		PQfinish(conn);
		exit_nicely(false);
	}
				
	ntups = PQntuples(res);

	relinfos = (RelInfo *) pg_malloc(sizeof(RelInfo) * ntups);
	
	/* FIXME: no need to allocate ntups memory for toast_rinfos */
	toast_rinfos = (RelInfo *) pg_malloc(sizeof(RelInfo) * ntups);
	
	i_spclocation 	= PQfnumber(res, "spclocation");
	i_relname 		= PQfnumber(res, "relname");
	i_oid 			= PQfnumber(res, "oid");
	i_relfilenode 	= PQfnumber(res, "relfilenode");
	i_reltoastrelid = PQfnumber(res, "reltoastrelid");
	i_nspname 		= PQfnumber(res, "nspname");

	for(i = 0; i < ntups; i++)
	{		
		relname = PQgetvalue(res, i, i_relname);

		if(strncmp(relname, "pg_toast_", 9) == 0)
			curr = toast_rinfos + itoast++;
		else
			curr = relinfos + irel++;
		
		from = PQgetvalue(res, i, i_spclocation);
		memset(curr->tsp, 0, sizeof(curr->tsp));
		snprintf(curr->tsp, SIZEOF(curr->tsp), "%s", from);

		from = PQgetvalue(res, i, i_nspname);
		memset(relnamestr, 0, sizeof(relnamestr));
		snprintf(relnamestr, SIZEOF(relnamestr), "%s.%s", from, relname);
				
		curr->reloid = atol(PQgetvalue(res, i, i_oid));
		
		memset(curr->relname, 0, sizeof(curr->relname));
		snprintf(curr->relname, SIZEOF(curr->relname), "%s", relnamestr);
		
		curr->relfilenode = atol(PQgetvalue(res, i, i_relfilenode));
		curr->toastrelid = atol(PQgetvalue(res, i, i_reltoastrelid));
	}
	PQclear(res);
	
	res = PQexec(conn, "CLOSE myportal");
	PQclear(res);
	
	res = PQexec(conn, "END");
	PQclear(res);

	PQfinish(conn);
	
	relarr->rels  = relinfos;
	relarr->nrels = irel;
	
	ts_relarr->rels  = toast_rinfos;
	ts_relarr->nrels = itoast;
}

/* get_ts_relinfos()
 *
 * like get_relinfos() but only retrieve toast and toast index information.
 */
static void
get_ts_relinfos(const char *db, RelInfoArr *tsrel_arr)
{
	PGconn		*conn 				  = NULL;
	PGresult	*res 				  = NULL;
	RelInfo		*ts_rinfos 			  = NULL;
	int			 ntups;
	int			 i 					  = 0;
	char		 con_opts[MAX_STRING] = {0};
	int			 i_relname 			  = 0;
	int			 i_tsp 				  = 0;
	int			 i_reloid 			  = 0;
	int			 i_relfilenode 		  = 0;
	int			 i_toastrelid 		  = 0;

	if(!db || !tsrel_arr) 
		pg_log(PG_FATAL, "%d: an internal error occured\n", __LINE__);

	snprintf(con_opts, SIZEOF(con_opts), "dbname = %s", db);
	conn = PQconnectdb(con_opts);
		
	if(PQstatus(conn) != CONNECTION_OK)
	{
		pg_log(PG_REPORT, "Connection to database failed: %s",
		PQerrorMessage(conn));
		exit_nicely(false);
	}
	
	res = PQexec(conn, "BEGIN");
	if(PQresultStatus(res) != PGRES_COMMAND_OK)
	{
		pg_log(PG_REPORT, "BEGIN command failed: %s", PQerrorMessage(conn));
		PQclear(res);
		PQfinish(conn);
		exit_nicely(false);
	}
	PQclear(res);
	
	res = PQexec(conn, "DECLARE myportal CURSOR FOR"
		" SELECT distinct c.oid, c.relname, c.relfilenode, c.reltoastrelid,t.spclocation"
		" FROM  pg_class c left outer join pg_tablespace t on c.reltablespace = t.oid"
		" WHERE relnamespace = (select oid from pg_namespace where nspname = 'pg_toast')"
		" AND c.oid >= 12000 "
		" GROUP BY c.oid, c.relname, c.relfilenode, c.reltoastrelid,t.spclocation"
		" ORDER BY c.relname;");

	if(PQresultStatus(res) != PGRES_COMMAND_OK)
	{
		pg_log(PG_REPORT, "DECLARE CURSOR failed: %s", PQerrorMessage(conn));
		PQclear(res);
		PQfinish(conn);
		exit_nicely(false);
	}		
	PQclear(res);
	
	res = PQexec(conn, "FETCH ALL in myportal");
	if(PQresultStatus(res) != PGRES_TUPLES_OK)
	{
		pg_log(PG_REPORT, "FETCH ALL failed: %s", PQerrorMessage(conn));
		PQclear(res);
		PQfinish(conn);
		exit_nicely(false);
	}
				
	i_relname 	  = PQfnumber(res, "relname");
	i_tsp 		  = PQfnumber(res, "spclocation");
	i_reloid 	  = PQfnumber(res, "oid");
	i_relfilenode = PQfnumber(res, "relfilenode");
	i_toastrelid  = PQfnumber(res, "reltoastrelid");

	ntups = PQntuples(res);
	ts_rinfos = (RelInfo *) pg_malloc(sizeof(RelInfo) * ntups);
			
	for(i = 0; i < ntups; i++)
	{		
		memset(ts_rinfos[i].tsp, 0, sizeof(ts_rinfos[i].tsp));
		snprintf(ts_rinfos[i].tsp, SIZEOF(ts_rinfos[i].tsp), "%s", PQgetvalue(res, i, i_tsp));
		
		ts_rinfos[i].reloid = atol(PQgetvalue(res, i, i_reloid));
		
		memset(ts_rinfos[i].relname, 0, sizeof(ts_rinfos[i].relname));
		snprintf(ts_rinfos[i].relname, SIZEOF(ts_rinfos[i].relname), "pg_toast.%s",
				    PQgetvalue(res, i, i_relname));
				
		ts_rinfos[i].relfilenode = atol(PQgetvalue(res, i, i_relfilenode));
		ts_rinfos[i].toastrelid = atol(PQgetvalue(res, i, i_toastrelid));	
	}
	PQclear(res);
	
	res = PQexec(conn, "CLOSE myportal");
	PQclear(res);
	
	res = PQexec(conn, "END");
	PQclear(res);

	PQfinish(conn);
	
	tsrel_arr->rels  = ts_rinfos;
	tsrel_arr->nrels = ntups;
}

/* get_ctrl_data()
 *
 * gets pg_control information in "ctrl". Assumes that bindir and datadir are 
 * valid absolute paths to enterprisedb or postgresql bin and pgdata directories
 * respectively *and* pg_resetxlog is version compatible with datadir. The main purpose 
 * of this function is to get pg_control data in a version independent manner.
 *
 * The approach taken here is to invoke pg_resetxlog with -n option and then
 * pipe its output. With little string parsing we get the pg_control data.
 */
static void
get_ctrl_data(const char *bindir, const char *datadir, ControlData *ctrl)
{
	char	 cmd[MAXPGPATH] 	= {0};
	char	 bufin[MAX_STRING] 	= {0};
	FILE	*output 	  		= NULL;
	char	*p 			  		= NULL;
	char	*op 		  		= NULL;
	bool	 gotxid 	  		= false;
	bool	 gotoid 	  		= false;
	bool	 gotlgid 	  		= false;
	bool	 gotlgseg 	  		= false;
	bool	 gottli 	 	 	= false;

	if(!ctrl) 
		pg_log(PG_FATAL, "%d: an internal error occured\n", __LINE__);

	snprintf(cmd, SIZEOF(cmd), "%s/pg_resetxlog -n %s", bindir, datadir);
	fflush(stdout);
	fflush(stderr);
	errno = 0;

	if((output = popen(cmd, "r")) == NULL)
		pg_log(PG_FATAL, "Could not get control data: %s\n",  strerror(errno));

	/* we have the result of cmd in "output". so parse it line by line now */
	while(fgets(bufin, sizeof(bufin), output))
	{
		if((p = strstr(bufin, "pg_control version number:")) != NULL)
		{
			p = strchr(p, ':');

			if(p ==  NULL || strlen(p) <= 1) 
				pg_log(PG_FATAL, "%d: pg_resetxlog  problem\n", __LINE__);

			p++; /*  removing ':' char */
			ctrl->ctrl_ver = (uint32) atol(p);
		}
		else if((p = strstr(bufin, "Catalog version number:")) != NULL)
		{
			p = strchr(p, ':');

			if(p ==  NULL || strlen(p) <= 1) 
				pg_log(PG_FATAL, "%d: pg_resetxlog  problem\n", __LINE__);

			p++; /*  removing ':' char */
			ctrl->cat_ver = (uint32) atol(p);
		}
		else if((p = strstr(bufin, "Current log file ID:")) != NULL)
		{
			p = strchr(p, ':');

			if(p ==  NULL || strlen(p) <= 1) 
				pg_log(PG_FATAL, "%d: pg_resetxlog  problem\n", __LINE__);

			p++; /*  removing ':' char */
			ctrl->logid = (uint32) atol(p);
			gotlgid = true;
		}
		else if((p = strstr(bufin, "Next log file segment:")) != NULL)
		{
			p = strchr(p, ':'); 

			if(p ==  NULL || strlen(p) <= 1) 
				pg_log(PG_FATAL, "%d: pg_resetxlog  problem\n", __LINE__);

			p++; /*  removing ':' char */
			ctrl->nxtlogseg = (uint32) atol(p);
			gotlgseg = true;
		}
		else if((p = strstr(bufin, "Latest checkpoint's TimeLineID:")) != NULL)
		{
			p = strchr(p, ':');

			if(p ==  NULL || strlen(p) <= 1) 
				pg_log(PG_FATAL, "%d: pg_resetxlog  problem\n", __LINE__);

			p++; /*  removing ':' char */
			ctrl->chkpnt_tli = (uint32) atol(p);
			gottli = true;
		}
		else if((p = strstr(bufin, "Latest checkpoint's NextXID:")) != NULL)
		{
			op = strchr(p, '/');

			if(op == NULL) 
				op = strchr(p, ':');

			if(op ==  NULL || strlen(op) <= 1) 
				pg_log(PG_FATAL, "%d: pg_resetxlog  problem\n", __LINE__);

			op++; /*  removing ':' char */
			ctrl->chkpnt_nxtxid = (uint32) atol(op);
			gotxid = true;
		}
		else if((p = strstr(bufin, "Latest checkpoint's NextOID:")) != NULL)
		{
			p = strchr(p, ':');

			if(p ==  NULL || strlen(p) <= 1) 
				pg_log(PG_FATAL, "%d: pg_resetxlog  problem\n", __LINE__);

			p++; /*  removing ':' char */
			ctrl->chkpnt_nxtoid = (uint32) atol(p);
			gotoid = true;
		}

		memset(bufin, 0, sizeof(bufin));
	}

	if(output) 
		pclose(output);

	/* verify that we got all the mandatory pg_control data */ 
	if(!gotxid || !gotoid || !gotlgid || !gotlgseg || !gottli)
		pg_log(PG_FATAL, "One or more of the required control information is missing\n");
}

/* define_dbs()
 *
 * create databases contained in db_arr via the "createdb" command
 */
static void
define_dbs(DbInfoArr *db_arr, const char *binpath)
{
	char cmd[MAXPGPATH] = {0};
	int	 i;
	
	for(i = 0; i < db_arr->ndbs; i++)
	{
		memset(cmd, 0, sizeof(cmd));
		snprintf(cmd, SIZEOF(cmd), "%s/createdb %s 1>/dev/null", binpath, db_arr->dbs[i].dbname);
		exec_prog(cmd, false);
	}

	/*FIXME temporary solution while upgrading postgres to enterprisedb*/
	if(g_old_port == 5432) 
	{
		memset(cmd, 0, sizeof(cmd));
		snprintf(cmd, SIZEOF(cmd), "%s/createdb postgres 1>/dev/null", binpath);
		exec_prog(cmd, false);
	}
}

/* create_fake_relfiles()
 *
 * Creates empty (rel)files for the respective toast tables of each database in 
 * their corresponding physical directories. This will prevent new refilenodes 
 * (to be created during restoration of the old schema) to conflict with the old 
 * toast table refilenodes and Oids.
 *
 * NOTE: One assumption here is that the server will not select an Oid for which
 * a relfilenode already exists in the database directory.
 *
 * NOTE: assumes that dbs are the old database's dbinfos and datadir is the 
 * path to the new data directory.
 */
static void
create_fake_relfiles(DbInfoArr *db_arr, const char *datadir)
{
	int			 i;
	int			 j;
	char		 con_opts[MAX_STRING] = {0};
	PGconn		*conn 				  = NULL;
	PGresult	*res 				  = NULL;
	int			 ntups 				  = 0;
	int			 i_oid 				  = 0;
	int			 i_dbname 			  = 0;
	char		*dbname 			  = NULL;
	Oid			 dboid 				  = InvalidOid;
	char		 cmd[MAXPGPATH] 	  = {0};
	DbInfo		*curr_db 			  = NULL;

	if(!db_arr || !datadir) 
		pg_log(PG_FATAL, "%d: an internal error occured\n", __LINE__);

	snprintf(con_opts, SIZEOF(con_opts), "dbname = template1");
	conn = PQconnectdb(con_opts);
	if(PQstatus(conn) != CONNECTION_OK)
	{
		pg_log(PG_REPORT, "Connection to database failed: %s",
		PQerrorMessage(conn));
		exit_nicely(false);
	}		

	res = PQexec(conn, "BEGIN");
	if(PQresultStatus(res) != PGRES_COMMAND_OK)
	{
		pg_log(PG_REPORT, "BEGIN command failed: %s", PQerrorMessage(conn));
		PQclear(res);
		PQfinish(conn);
		exit_nicely(false);
	}
	PQclear(res);
		
	res = PQexec(conn, "DECLARE myportal CURSOR FOR SELECT oid, datname from pg_database where oid > 12000");
	if(PQresultStatus(res) != PGRES_COMMAND_OK)
	{
		pg_log(PG_REPORT, "DECLARE CURSOR failed: %s", PQerrorMessage(conn));
		PQclear(res);
		PQfinish(conn);
		exit_nicely(false);
	}		
	PQclear(res);

	res = PQexec(conn, "FETCH ALL in myportal");
	if(PQresultStatus(res) != PGRES_TUPLES_OK)
	{
		pg_log(PG_REPORT, "FETCH ALL failed: %s", PQerrorMessage(conn));
		PQclear(res);
		PQfinish(conn);
		exit_nicely(false);
	}
	
	i_oid = PQfnumber(res, "oid");
	i_dbname = PQfnumber(res, "datname");
	
	ntups = PQntuples(res);
	for(i = 0; i < ntups; i++)
	{
		dbname 	= PQgetvalue(res, i, i_dbname);
		dboid 	= atol(PQgetvalue(res, i, i_oid));
		curr_db = dbarr_lookupdb(db_arr, dbname);
		
		if(!curr_db) 
		{
			/*FIXME temp changes*/
			if(strcasecmp(dbname,"postgres") == 0) 
				continue;
			pg_log(PG_FATAL, "%d: an internal error occured\n", __LINE__);
		}

		/* now create the fake rel files */
		for(j = 0; j < curr_db->tsrel_arr.nrels; j++)
		{
			/* skip toast indexes */
			if(strstr(curr_db->tsrel_arr.rels[j].relname, "index") != NULL) 
				continue;
			
			memset(cmd, 0, sizeof(cmd));
			snprintf(cmd, SIZEOF(cmd), "touch %s/base/%u/%u", datadir, dboid, curr_db->tsrel_arr.rels[j].reloid);
			exec_prog(cmd, false);			
		}
	}
	
	PQclear(res);
	
	res = PQexec(conn, "CLOSE myportal");
	PQclear(res);
	
	res = PQexec(conn, "END");
	PQclear(res);

	PQfinish(conn);
}

/* remove_fake_relfiles()
 *
 * removes the fake relfilenodes created by create_fake_relfiles() function. 
 * Same as create_fake_relfiles() with the difference that we execute "rm" instead
 * of "touch".
 */
static void
remove_fake_relfiles(DbInfoArr *db_arr, const char *datadir)
{
	int			i;
	int			j;
	char		con_opts[MAX_STRING] = {0};
	PGconn	   *conn 				 = NULL;
	PGresult   *res 				 = NULL;
	int			ntups 				 = 0;
	int			i_oid 				 = 0;
	int			i_dbname 			 = 0;
	char	   *dbname 				 = NULL;
	Oid			dboid 				 = InvalidOid;
	char	    cmd[MAXPGPATH]		 = {0};
	DbInfo	   *curr_db 			 = NULL;

	if(!db_arr || !datadir) 
		pg_log(PG_FATAL, "%d: an internal error occured\n", __LINE__);

	snprintf(con_opts, SIZEOF(con_opts), "dbname = template1");
	conn = PQconnectdb(con_opts);
	if(PQstatus(conn) != CONNECTION_OK)
	{
		pg_log(PG_REPORT, "Connection to database failed: %s",
		PQerrorMessage(conn));
		exit_nicely(false);
	}		

	res = PQexec(conn, "BEGIN");
	if(PQresultStatus(res) != PGRES_COMMAND_OK)
	{
		pg_log(PG_REPORT, "BEGIN command failed: %s", PQerrorMessage(conn));
		PQclear(res);
		PQfinish(conn);
		exit_nicely(false);
	}
	PQclear(res);
		
	res = PQexec(conn, "DECLARE myportal CURSOR FOR SELECT oid, datname from pg_database where oid > 12000");
	if(PQresultStatus(res) != PGRES_COMMAND_OK)
	{
		pg_log(PG_REPORT, "DECLARE CURSOR failed: %s", PQerrorMessage(conn));
		PQclear(res);
		PQfinish(conn);
		exit_nicely(false);
	}		
	PQclear(res);

	res = PQexec(conn, "FETCH ALL in myportal");
	if(PQresultStatus(res) != PGRES_TUPLES_OK)
	{
		pg_log(PG_REPORT, "FETCH ALL failed: %s", PQerrorMessage(conn));
		PQclear(res);
		PQfinish(conn);
		exit_nicely(false);
	}
	
	i_oid 	 = PQfnumber(res, "oid");
	i_dbname = PQfnumber(res, "datname");
	
	ntups = PQntuples(res);
	
	for(i = 0; i < ntups; i++)
	{
		dbname = PQgetvalue(res, i, i_dbname);
		dboid  = atol(PQgetvalue(res, i, i_oid));
		
		curr_db = dbarr_lookupdb(db_arr, dbname);

		if(!curr_db)
		{
			if(strcasecmp(dbname,"postgres") == 0) 
				continue;

			pg_log(PG_FATAL, "%d: an internal error occured\n", __LINE__);
		}
		/* now remove the fake rel files */
		for(j = 0; j < curr_db->tsrel_arr.nrels; j++)
		{
			/* skip toast indexes */
			if(strstr(curr_db->tsrel_arr.rels[j].relname, "index") != NULL) 
				continue;
			
			memset(cmd, 0, sizeof(cmd));
			snprintf(cmd, SIZEOF(cmd), "rm -f %s/base/%u/%u", datadir, dboid, curr_db->tsrel_arr.rels[j].reloid);
			exec_prog(cmd, false);
		}
	}
	
	PQclear(res);
	
	res = PQexec(conn, "CLOSE myportal");
	PQclear(res);
	
	res = PQexec(conn, "END");
	PQclear(res);

	PQfinish(conn);
}

/* regen_db_infos()
 *
 * regenerates db infos. For now it is only used to regenerate pg_toast tables
 * but in the future if the need arises we can use this function to regenerate
 * more information as well.
 */
static void
regen_db_infos(DbInfoArr *db_arr, unsigned short port)
{
	int			i;
	RelInfoArr	tsrel_arr;
	
	for(i = 0; i < db_arr->ndbs; i++)
	{
		get_ts_relinfos(db_arr->dbs[i].dbname, &tsrel_arr);
		
		pg_free(db_arr->dbs[i].tsrel_arr.rels);
		db_arr->dbs[i].tsrel_arr = tsrel_arr;
	}
}

/* restore_toast_rels()
 *
 * the core module which handles toast relations.
 */
static void
restore_toast_rels(DbInfoArr *olddb_arr, DbInfoArr *newdb_arr)
{
	int			i 						 = 0;
	int			j 						 = 0;
	char		con_opts[MAX_STRING] 	 = {0};
	char		qry[MAX_STRING] 	 	 = {0};
	PGconn		*conn 					 = NULL;
	PGresult	*res 					 = NULL;
	DbInfo		*newdb 					 = NULL;
	DbInfo		*olddb 					 = NULL;
	RelInfo		*newrel 				 = NULL;
	RelInfo		*oldrel 				 = NULL;
	
	if(!olddb_arr || !newdb_arr) 
		pg_log(PG_FATAL, "%d: an internal error occured\n", __LINE__);

	for(i = 0; i < newdb_arr->ndbs; i++)
	{
		memset(con_opts, 0, sizeof(con_opts));
		snprintf(con_opts, SIZEOF(con_opts), "dbname = %s", newdb_arr->dbs[i].dbname);
		conn = PQconnectdb(con_opts);

		if(PQstatus(conn) != CONNECTION_OK)
		{
			pg_log(PG_REPORT, "Connection to database failed: %s",
			PQerrorMessage(conn));
			exit_nicely(false);
		}			
		
		/* nullify reltoastrelid columns for all relations containing
		 * a toast relation
		 */
		res = PQexec(conn, "UPDATE pg_class SET reltoastrelid = 0 WHERE oid > 12000"
							"AND reltoastrelid > 0 AND relkind = 'r'");
		if(PQresultStatus(res) != PGRES_COMMAND_OK)
		{
			pg_log(PG_REPORT, "UPDATE failed: %s", PQerrorMessage(conn));
			PQclear(res);
			PQfinish(conn);
			exit_nicely(false);
		}		
		PQclear(res);
			
		newdb = newdb_arr->dbs + i;
		
		/* find the corresponding old db. Skip the iteration if not found */
		olddb = dbarr_lookupdb(olddb_arr, newdb->dbname);
		if(!olddb) 
			continue;
		
		for(j = 0; j < newdb->rel_arr.nrels; j++)
		{
			/* remove the pg_class entries */
			memset(qry, 0, sizeof(qry));
			snprintf(qry, SIZEOF(qry), 
					"delete from pg_class where relname like 'pg_toast_%u%%'",
					newdb->rel_arr.rels[j].reloid);
					
			res = PQexec(conn, qry);
			if(PQresultStatus(res) != PGRES_COMMAND_OK)
			{
				pg_log(PG_REPORT, "DELETE failed: %s", PQerrorMessage(conn));
				PQclear(res);
				PQfinish(conn);
				exit_nicely(false);
			}		
			PQclear(res);
			
			/* remove the pg_type entry */
			memset(qry, 0, sizeof(qry));
			snprintf(qry, SIZEOF(qry), 
					"delete from pg_type where typname = 'pg_toast_%u'",
					newdb->rel_arr.rels[j].reloid);
					
			res = PQexec(conn, qry);
			if(PQresultStatus(res) != PGRES_COMMAND_OK)
			{
				pg_log(PG_REPORT, "DELETE failed: %s", PQerrorMessage(conn));
				PQclear(res);
				PQfinish(conn);
				exit_nicely(false);
			}		
			PQclear(res);			
			
		}
		
		/* finally recreate the new toast tables */
		for(j = 0; j < newdb->rel_arr.nrels; j++)
		{
			newrel = newdb->rel_arr.rels + j;
			oldrel = relarr_lookuprel(&olddb->rel_arr, newrel->relname); 

			if(!oldrel) 
				continue;
			
			memset(qry, 0, sizeof(qry));
			snprintf(qry, SIZEOF(qry),
					"select toasttbl_recreate(%u, %u)",
					newrel->reloid, oldrel->toastrelid);
		
			res = PQexec(conn, qry);
		}
		
		PQfinish(conn);
	}
}

/* define_supfuncs()
 * 
 * pg_migrator requires some support functions, which should be dynamically linked
 * with the server. This routine creates those functions. 
 */
static void
define_supfuncs(const char *mylibpath, const char *mybinpath, bool oldpm)
{
	char	cmd[MAXPGPATH] = {0};
	bool	usepsql        = true;
	
	/* for now we dont need to add any functions if an
	 * old postmaster is running. so quickly return.
	 */
	if(oldpm) 
		return;

	/* decide whether to use psql or edb-psql */	
	memset(cmd, 0, sizeof(cmd));
	snprintf(cmd, SIZEOF(cmd), "%s/psql", mybinpath);
	if(validate_exec(cmd) != 0)
	{
		/* edb-psql executable was not found. Now see
		 * if we can find psql
		 */
		memset(cmd, 0, sizeof(cmd));
		snprintf(cmd, SIZEOF(cmd), "%s/edb-psql", mybinpath);
		if(validate_exec(cmd) != 0)
			pg_log(PG_FATAL, "Failed\n");

		usepsql = false;
	}
	
	/* add functions only required for the new postmaster */
	if(!oldpm)
	{					
		/* toasttbl_recreate */
		memset(cmd, 0, sizeof(cmd));
		snprintf(cmd, SIZEOF(cmd), "%s/%s template1 -c \""
					"CREATE OR REPLACE FUNCTION toasttbl_recreate(OID,OID) "
					"RETURNS VOID AS '%s/pg_migrator.so' "
					"LANGUAGE C; \" >> %s 2>&1",
					mybinpath, usepsql?"psql":"edb-psql", mylibpath, 
					g_logfile?g_logfile:"/dev/null");
		exec_prog(cmd, true);
	}
}

/* dbarr_lookupdb()
 *
 * NOTE: returns the *real* pointer to the DbInfo structure
 */
static DbInfo *
dbarr_lookupdb(DbInfoArr *db_arr, const char *dbname)
{
	int i;

	if(!db_arr || !dbname) 
		return NULL;

	for(i = 0; i < db_arr->ndbs; i++)
	{
		if(strcmp(db_arr->dbs[i].dbname, dbname) == 0)
			return db_arr->dbs + i;
	}

	return NULL;
}

/* relarr_lookuprel()
 *
 * searches "relname" in rel_arr. Returns the *real* pointer to the 
 * RelInfo structure
 */
static RelInfo *
relarr_lookuprel(RelInfoArr *rel_arr, const char *relname)
{
	int	i;
	  
	if(!rel_arr || !relname) 
		return NULL;

	for(i = 0; i < rel_arr->nrels; i++)
	{
		if(strcmp(rel_arr->rels[i].relname, relname) == 0)
			return rel_arr->rels + i;
	}
	  
	return NULL;
}

/* map_rel()
 *
 * a wrapper over map_relbyid().
 */
static void
map_rel(const RelInfo *oldrel, const RelInfo *newrel, const DbInfo *old_db, const DbInfo *new_db, const char *olddata, const char *newdata, FileNodeMap *map)
{
	if(!oldrel || !newrel || !old_db || !new_db || !olddata || !newdata || !map)
		pg_log(PG_FATAL, "%d: an internal error occured\n", __LINE__);

	map_relbyid(oldrel->relfilenode, newrel->relfilenode, oldrel->relname, newrel->relname, oldrel->tsp, old_db, new_db, olddata, newdata, map);
}

/* map_relbyid()
 *
 * fills a file node map structure and returns it in "map".
 */
static void
map_relbyid(Oid	oldid, Oid newid, const char *oldrlname, const char *newrlname, const char *oldtsp, const DbInfo *old_db, const DbInfo *new_db, const char *olddata, const char *newdata, FileNodeMap *map)
{
	if(!oldrlname || !newrlname || !old_db || !new_db || !olddata || !new_db || !map)
		pg_log(PG_FATAL, "%d: an internal error occured\n", __LINE__);

	map->new = newid;
	map->old = oldid;

	snprintf(map->new_relname, SIZEOF(map->new_relname), "%s", newrlname);
	snprintf(map->old_relname, SIZEOF(map->old_relname), "%s", oldrlname);
				
	if(strlen(oldtsp) == 0)
	{ 
		/* relation does not belong to the default tablespace, hence relfiles would
		 * exist in the data directories.
		 */
		snprintf(map->from,SIZEOF(map->from),"%s/base/%u", newdata, new_db->dboid);
		snprintf(map->to, SIZEOF(map->to), "%s/base/%u", olddata, old_db->dboid);
	}
	else
	{
		/* relation belongs to some tablespace, hence copy its physical location */
		snprintf(map->from, SIZEOF(map->from), "%s/%u", oldtsp, new_db->dboid);
		snprintf(map->to, SIZEOF(map->to), "%s_old/%u", oldtsp, old_db->dboid);
	}
}

/* relarr_free()
 *
 */
static void
relarr_free(RelInfoArr *rel_arr)
{
	pg_free(rel_arr->rels);
	rel_arr->nrels = 0;
}

/* dbarr_free()
 *
 */
static void 
dbarr_free(DbInfoArr *db_arr)
{
	int	i;

	for(i = 0; i < db_arr->ndbs; i++)
	{
	  	relarr_free(&db_arr->dbs[i].rel_arr);
		relarr_free(&db_arr->dbs[i].tsrel_arr);
	}	
	db_arr->ndbs = 0;
}

/* pg_log()
 *
 */
static void 
pg_log(eLogType type,char *fmt, ... )
{
	va_list 	args;
	char 		message[MAX_STRING];
		
	va_start(args, fmt);
	vsnprintf(message, SIZEOF(message), fmt, args);
	va_end(args);
	
	if(g_logfd != NULL) 
		g_logfd = fopen(g_logfile, "a");

	if(g_logfd != NULL)
	{
		fwrite(message, strlen(message), 1, g_logfd);
		fflush(g_logfd);
	}

	switch(type)
	{
		case PG_INFO:
			if(g_verbos) 
				printf ("%s",_(message));
			break;	

		case PG_REPORT:
			printf ("%s",_(message));
			break;

		case PG_FATAL:
			pg_log(PG_REPORT, _(message));
			exit_nicely(false);
			break;

		case PG_DEBUG:
#if DEBUG
			if(g_dbgfd) 
				fprintf(g_dbgfd, _(message));
#endif
			break;

		default:
			break;
	}
	fflush(stdout);	
}

/* Utility functions */

/* pg_malloc()
 *
 */
static void *
pg_malloc(int n)
{
	void *p = malloc(n);

	if(p == NULL)
		pg_log(PG_FATAL,"%s: out of memory", g_progname);

	return p;
}

/* pg_free()
 *
 */
static void
pg_free(void * p)
{
	if(p != NULL) 
		free(p);
}

/* pg_strdup()
 *
 */
static char *
pg_strdup(const char *s)
{
	char *result = strdup(s);

	if(result == NULL)
		pg_log(PG_FATAL,"%s: out of memory", g_progname);

	return result;	
}

/* validate_diropt()
 *
 * validates a directory option. "dirpath" is the directory path supplied by the user 
 * (can be an empty string if not supplied). In case the dirpath is empty, then the corresponding
 * environment variable (if any) is used. Also skips a trailing '/', if found in "dirpath"
 */
static int
validate_diropt(char *dirpath, eDirOpt type)
{
	char *pgdenv;

	if(strlen(dirpath) == 0)
	{
		switch(type)
		{	
			case OLDDATADIR:
				pgdenv = getenv("OLDDATADIR");
				if(pgdenv && strlen(pgdenv))
				{	
					memset(dirpath, 0, MAXPGPATH);
					snprintf(dirpath, MAXPGPATH - 1, "%s", pgdenv);
					break;
				}
				pg_log(PG_FATAL,"\n%s: no old data directory specified\n"
				"You must identify the directory where the data for previous\n" 
				"database system resides. Do this with either the invocation\n"
				"option -o or the environment variable OLDDATADIR.\n",g_progname);
				break;

			case NEWDATADIR:
				pgdenv = getenv("NEWDATADIR");
				if(pgdenv && strlen(pgdenv))
				{	
					memset(dirpath, 0, MAXPGPATH);
					snprintf(dirpath, MAXPGPATH - 1, "%s", pgdenv);
					break;
				}
				pg_log(PG_FATAL,"\n%s: no new data directory specified\n"
				"You must identify the directory where the data for new\n" 
				"database system resides. Do this with either the invocation\n"
				"option -n or the environment variable NEWDATADIR.\n",g_progname);
				break;

			case OLDBINDIR:
				pgdenv = getenv("OLDBINDIR");
				if(pgdenv && strlen(pgdenv))
				{	
					memset(dirpath, 0, MAXPGPATH);
					snprintf(dirpath, MAXPGPATH - 1, "%s", pgdenv);
					break;
				}
				pg_log(PG_FATAL,"\n%s: no old executeable/bin data directory specified\n"
				"You must identify the directory where the executeable/binary for previous\n" 
				"database system resides. Do this with either the invocation\n"
				"option -O or the environment variable OLDBINDIR.\n",g_progname);
				break;

			case NEWBINDIR:
				pgdenv = getenv("NEWBINDIR");
				if(pgdenv && strlen(pgdenv))
				{	
					memset(dirpath, 0, MAXPGPATH);
					snprintf(dirpath, MAXPGPATH - 1, "%s", pgdenv);
					break;
				}
				pg_log(PG_FATAL,"\n%s: no new executeable/bin data directory specified\n"
				"You must identify the directory where the executeable/binary for new\n" 
				"database system resides. Do this with either the invocation\n"
				"option -N or the environment variable NEWBINDIR.\n",g_progname);
				break;

			default:
				pg_log(PG_FATAL,"invalid options\n");
		}
	}

	if(strlen(dirpath) < 1) 
		return -1;

	if( dirpath[strlen(dirpath) - 1] == '/') 
		dirpath[strlen(dirpath) - 1] = 0;

	return 0;
}

/* check_ok()
 *
 */
static void
check_ok(void)
{
    if(g_caught_signal)
    {
		pg_log(PG_INFO,"all signal disabled \n");
		/*
		 * TODO
		 * Undo all changes made
		 * and exit
		 */
    }
    else
    {
        /* all seems well */
		pg_log(PG_REPORT,"ok\n");
		fflush(stdout);
    }
}

static int
copy_dir(const char *from, const char *to, bool force)
{
	DIR					*fromdir = NULL;
	struct  dirent		*de 	 = NULL;
	char				 fromfile[MAX_STRING];
	char				 tofile[MAX_STRING];
	struct stat			 fst;
		
	if((from == NULL) || (to == NULL ))  
		return -1;

	if((fromdir = opendir(from)) == NULL)
	{
		/* TODO: 
		 * Assuming that if "from" is not a directory then it must be a file
		 * worth reconsidering?
		 */
		if(errno == ENOTDIR)
			return copy_file(from, to, true);
		return -1;
	}
	
	if(mkdir(to, S_IRUSR | S_IWUSR | S_IXUSR) != 0)
	{
		/* 
		 * ignore directory already exist error 
		 */
		if(errno != EEXIST)
			return -1;
	}
	
	while((de = readdir(fromdir)) != NULL)
	{
		if(strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) 
			continue;
			
		memset(fromfile, 0, sizeof(fromfile));
		memset(tofile, 0, sizeof(tofile));
		
		snprintf(fromfile, sizeof(fromfile) - 1, "%s/%s", from, de->d_name);
		snprintf(tofile, sizeof(tofile) - 1, "%s/%s", to, de->d_name);
		
		if(stat(fromfile, &fst) < 0)
		{
			if(fromdir != NULL) 
			{ 
				closedir(fromdir); 
				fromdir = NULL;
			}

			return -1;
		}		

		if(fst.st_mode & S_IFDIR)
		{
			/* recurse to handle subdirectories */
			if(force) 
				copy_dir(fromfile, tofile, true);
		}
		else if(fst.st_mode & S_IFREG)
		{
			if((copy_file(fromfile, tofile, 1)) == -1)
			{
				if(fromdir != NULL) 
				{ 
					closedir(fromdir); 
					fromdir = NULL;
				}
				return -1;
			}
		}
	}

	if(fromdir != NULL) 
	{ 
		closedir(fromdir); 
		fromdir = NULL;
	}
	return 1;
}

static int 
copy_file(const char *fromfile, const char *tofile, bool force)
{
	int					 srcfd 	= 0;
	int					 destfd = 0;
	int					 nbytes = 0;
	char				*buffer = NULL;
	struct  stat		 fst;
	
	if((fromfile == NULL) || (tofile == NULL ))  
		return -1;

	if((srcfd = open(fromfile, O_RDONLY, 0)) < 0) 	
		return -1;
	
	if((destfd = open(tofile, O_RDWR | O_CREAT | (force ? 0 : O_EXCL), S_IRUSR | S_IWUSR)) < 0) 
	{
		if(srcfd != 0) 
			close(srcfd); 
		return -1;
	}
	
	if(fstat(srcfd, &fst) < 0)
	{
		if(srcfd != 0)  
			close(srcfd); 
		
		if(destfd != 0) 
			close(destfd); 

		return -1;
	}
	buffer = (char *) malloc((size_t)fst.st_size);

	if(buffer == NULL)
	{
		if(srcfd != 0)  
			close(srcfd); 

		if(destfd != 0)
			close(destfd); 

		return -1;
	}
	/* perform data copying i.e read from source, write to destination */
	for(;;)
	{
		memset(buffer, 0, (size_t)fst.st_size);
		nbytes = read(srcfd, buffer, (size_t)fst.st_size);

		if(nbytes < 0)
		{
			if(buffer != NULL)  
				free(buffer);

			if(srcfd != 0)
				close(srcfd);

			if(destfd != 0) 
				close(destfd);

			return -1;
		}
		
		if(nbytes == 0) 
			break;
		
		errno = 0;

		if((int) write(destfd, buffer, nbytes) != nbytes)
		{
			/* if write didn't set errno, assume problem is no disk space */
			if(errno == 0) 
				errno = ENOSPC;

			if(buffer != NULL) 
				free(buffer);

			if(srcfd != 0)
				close(srcfd); 

			if(destfd != 0)
				close(destfd);

			return -1;
		}
	}
	
	if(buffer != NULL)
		free(buffer);

	if(srcfd != 0)
		close(srcfd);

	if(destfd != 0)
		close(destfd);

	return 1;
}

/* check_data_dir()
 *
 */
static int
check_data_dir(const char *pg_data)
{
	DIR					*chkdir = NULL;
	struct dirent		*file 	= NULL;
	int					 result = 0;
	
	errno	= 0;	

	chkdir = opendir(pg_data);

	if(!chkdir)
		return (errno == ENOENT) ? 0 : -1;
	
	while((file = readdir(chkdir)) != NULL)
	{
		if((strcmp("base", file->d_name) == 0) ||
			(strcmp("global", file->d_name) == 0) ||
			(strcmp("pg_clog", file->d_name) == 0) ||
			(strcmp("pg_multixact", file->d_name) == 0) ||
			(strcmp("pg_subtrans", file->d_name) == 0) ||
			(strcmp("pg_tblspc", file->d_name) == 0) ||
			(strcmp("pg_twophase", file->d_name) == 0) ||
			(strcmp("pg_xlog", file->d_name) == 0))
		{
			result++;
			continue;
		}
	}
#ifdef WIN32
	/*
	* This fix is in mingw cvs (runtime/mingwex/dirent.c rev 1.4), but not in
	* released version
	*/
	if(GetLastError() == ERROR_NO_MORE_FILES)
		errno = 0;
#endif
	
	closedir(chkdir);
	
	if(errno != 0)
		result = -1;       /* some kind of I/O error? */
	
	if(result != 8) 
		return -1; /* Some directories are missing in the data dir */

	return 0;
}

int filter(const struct dirent *scan_ent)
{
     if(!strncmp(g_relnameext,scan_ent->d_name,strlen(g_relnameext))) 
		 return 1;

     return 0;
}

/*
 * validate_exec()
 *
 * validate "path" as an executable file
 * returns 0 if the file is found and no error is encountered.
 *		  -1 if the regular file "path" does not exist or cannot be executed.
 *		  -2 if the file is otherwise valid but cannot be read.
 */
static int
validate_exec(const char *path)
{
	struct stat 		 buf;

#ifndef WIN32
	uid_t				 euid;
	struct group 		*gp;
	struct passwd 		*pwp;
	int					 i;
	int					 in_grp = 0;
#else
	char				 path_exe[MAXPGPATH + sizeof(".exe") - 1];
#endif
	int					 is_r 	= 0;
	int					 is_x 	= 0;

#ifdef WIN32
	/* Win32 requires a .exe suffix for stat() */

	if(strlen(path) >= strlen(".exe") && pg_strcasecmp(path + strlen(path) - strlen(".exe"), ".exe") != 0)
	{
		strcpy(path_exe, path);
		strcat(path_exe, ".exe");
		path = path_exe;
	}
#endif

	/*
	 * Ensure that the file exists and is a regular file.
	 *
	 * XXX if you have a broken system where stat() looks at the symlink instead
	 * of the underlying file, you lose.
	 */
	if(stat(path, &buf) < 0)
		return -1;

	if((buf.st_mode & S_IFMT) != S_IFREG)
		return -1;

	/*
	 * Ensure that we are using an authorized executable.
	 */

	/*
	 * Ensure that the file is both executable and readable (required for
	 * dynamic loading).
	 */
#ifdef WIN32
	is_r = buf.st_mode & S_IRUSR;
	is_x = buf.st_mode & S_IXUSR;
	return is_x ? (is_r ? 0 : -2) : -1;
#else
	euid = geteuid();

	/* If owned by us, just check owner bits */
	if(euid == buf.st_uid)
	{
		is_r = buf.st_mode & S_IRUSR;
		is_x = buf.st_mode & S_IXUSR;
		return is_x ? (is_r ? 0 : -2) : -1;
	}

	/* OK, check group bits */
	pwp = getpwuid(euid);		/* not thread-safe */

	if(pwp)
	{
		if(pwp->pw_gid == buf.st_gid)	/* my primary group? */
			++in_grp;
		else if(pwp->pw_name && (gp = getgrgid(buf.st_gid)) != NULL && /* not thread-safe */ gp->gr_mem != NULL)
		{						
			/* try list of member groups */

			for(i = 0; gp->gr_mem[i]; ++i)
			{
				if(!strcmp(gp->gr_mem[i], pwp->pw_name))
				{
					++in_grp;
					break;
				}
			}
		}

		if(in_grp)
		{
			is_r = buf.st_mode & S_IRGRP;
			is_x = buf.st_mode & S_IXGRP;
			return is_x ? (is_r ? 0 : -2) : -1;
		}
	}

	/* Check "other" bits */
	is_r = buf.st_mode & S_IROTH;
	is_x = buf.st_mode & S_IXOTH;
	return is_x ? (is_r ? 0 : -2) : -1;
#endif
}

/* get_id()
 * (copied from initdb.c) find the current user
 *
 * on unix make sure it isn't really root. For now we only
 * use this function to get check that on *nix the user is'nt
 * root
 */
static char *
get_id(void)
{
#ifndef WIN32

	struct passwd *pw = getpwuid(geteuid());

#ifndef __BEOS__				/* no root check on BEOS */

	if(geteuid() == 0)			/* 0 is root's uid */
		pg_log(PG_FATAL, "%s: cannot be run as root\n", g_progname);
#endif
#else							/* the windows code */

	struct passwd_win32
	{
		int			pw_uid;
		char		pw_name[128];
	} pass_win32;
	struct passwd_win32 *pw 		 = &pass_win32;
	DWORD                pwname_size = sizeof(pass_win32.pw_name) - 1;

	pw->pw_uid = 1;
	GetUserName(pw->pw_name, &pwname_size);
#endif

	return pg_strdup(pw->pw_name);
}

#if DEBUG
static void
maps_print(FileNodeMap *maps, int n)
{
	int	i;

	for(i = 0; i < n; i++)
		pg_log(PG_DEBUG, "%s:%u ==> %s:%u\n", maps[i].old_relname, maps[i].old, maps[i]. new_relname, maps[i].new);
}

static void
relarr_print(RelInfoArr *arr)
{
	int	i;

	for(i = 0; i < arr->nrels; i++)
		pg_log(PG_DEBUG, "relname: %s: reloid: %u reltblspace: %s\n", arr->rels[i].relname, arr->rels[i].reloid,arr->rels[i].tsp);
}

static void
dbarr_print(DbInfoArr *arr)
{
	int i;

	for(i = 0; i < arr->ndbs; i++)
	{
		pg_log(PG_DEBUG, "Database: %s\n", arr->dbs[i].dbname);
		relarr_print(&arr->dbs[i].rel_arr);
		relarr_print(&arr->dbs[i].tsrel_arr);
		pg_log(PG_DEBUG, "\n\n");
	}
}

#endif

