
I now have a C implementation of initdb, which successfully runs with 
"make check" on my several linux machines, and compiles on Windows/MinGW 
too (can't run make check on Windows because we haven't got a native 
postgres yet - I'm going to create a small dummy Windows postgres that 
will let me check if this program works there).

There's a little work still to go (see below), but I'd appreciate some 
eyeballs on it to see if I have made any major booboos, or could have 
done things better. What's the best way to proceed? (All told it's about 
2500 lines of C.)

cheers

andrew

 From the heading comment:

/ *
 * initdb
 *
 * This is a C implementation of the previous shell script for setting up a
 * PostgreSQL cluster location, and should be highly compatible with it.
 *
 * TODO:
 *   - signal handling
 *   - more error checking, partiularly on the file i/o
 *   - check if we need workaround for timing error on win32 rmdir()?
 *   - clean up find_postgres code and return values
 *   - free up used memory? (probably not worth it - if we can't load this
 *     much data into memory how will we ever run postgres anyway?)
 */


Fri, 03 Oct 2003 14:32:56 -0400
Andrew Dunstan <andrew@dunslane.net>
initdb
Andrew Dunstan <andrew@dunslane.net> writes:
> I now have a C implementation of initdb, ...
> There's a little work still to go (see below), but I'd appreciate some 
> eyeballs on it to see if I have made any major booboos, or could have 
> done things better. What's the best way to proceed?

Put it on a web page and post the link, or if you don't have a website,
send it to pgsql-patches (clearly marked as "not ready to apply").

			regards, tom lane

Sat, 04 Oct 2003 15:58:35 -0400
Tom Lane <tgl@sss.pgh.pa.us>
Re: [HACKERS] initdb 

Excellent idea.

Here's the URL: http://www.dunslane.net/~andrew/Initdb_In_C.html

cheers

andrew

----- Original Message ----- 
From: "Tom Lane" <tgl@sss.pgh.pa.us>

> Andrew Dunstan <andrew@dunslane.net> writes:
> > I now have a C implementation of initdb, ...
> > There's a little work still to go (see below), but I'd appreciate some 
> > eyeballs on it to see if I have made any major booboos, or could have 
> > done things better. What's the best way to proceed?
> 
> Put it on a web page and post the link, or if you don't have a website,
> send it to pgsql-patches (clearly marked as "not ready to apply").
> 


Sat, 4 Oct 2003 19:15:15 -0400
"Andrew Dunstan" <andrew@dunslane.net>
Re: [HACKERS] initdb 

I have added this URL to the Win32 page, and removed the "initdb C" TODO
item.  Great job.  Do you wantt this in Win32 CVS or should we just wait
for 7.5 to start?

---------------------------------------------------------------------------

Andrew Dunstan wrote:
> 
> Excellent idea.
> 
> Here's the URL: http://www.dunslane.net/~andrew/Initdb_In_C.html
> 
> cheers
> 
> andrew
> 
> ----- Original Message ----- 
> From: "Tom Lane" <tgl@sss.pgh.pa.us>
> 
> > Andrew Dunstan <andrew@dunslane.net> writes:
> > > I now have a C implementation of initdb, ...
> > > There's a little work still to go (see below), but I'd appreciate some 
> > > eyeballs on it to see if I have made any major booboos, or could have 
> > > done things better. What's the best way to proceed?
> > 
> > Put it on a web page and post the link, or if you don't have a website,
> > send it to pgsql-patches (clearly marked as "not ready to apply").
> > 
> 
> 
> ---------------------------(end of broadcast)---------------------------
> TIP 6: Have you searched our list archives?
> 
>                http://archives.postgresql.org
> 

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Thu, 9 Oct 2003 21:39:13 -0400 (EDT)
Bruce Momjian <pgman@candle.pha.pa.us>
Re: [HACKERS] initdb
There will be a new version out there soon. When I am happy with it I will
let you know - right now I am dealing with 2 issues - setlocale(LC_ALL,"")
doesn't read from the environment on Windows, and the program is possibly
not picking up the buffers/connections properly.

Please don't put the version currently out there in CVS - the files won't
even be the same - initdb-scripts.h will disappear and there will be a new
file called "system_views.sql".

There is no urgency about this - until we have a working postgres executable
on Windows initdb is useless anyway. My aim has been to have initdb ready
when postgres is ready. Of course, I test against Unix all the time to make
sure nothing gets broken.

cheers

andrew


----- Original Message ----- 
From: "Bruce Momjian" <pgman@candle.pha.pa.us>
To: "Andrew Dunstan" <andrew@dunslane.net>
Cc: "Postgresql Hackers" <pgsql-hackers@postgresql.org>;
"pgsql-hackers-win32" <pgsql-hackers-win32@postgresql.org>
Sent: Thursday, October 09, 2003 9:39 PM
Subject: Re: [pgsql-hackers-win32] [HACKERS] initdb


>
> I have added this URL to the Win32 page, and removed the "initdb C" TODO
> item.  Great job.  Do you wantt this in Win32 CVS or should we just wait
> for 7.5 to start?
>
> --------------------------------------------------------------------------
-
>
> Andrew Dunstan wrote:
> >
> > Excellent idea.
> >
> > Here's the URL: http://www.dunslane.net/~andrew/Initdb_In_C.html
> >
> > cheers
> >
> > andrew
> >
> > ----- Original Message ----- 
> > From: "Tom Lane" <tgl@sss.pgh.pa.us>
> >
> > > Andrew Dunstan <andrew@dunslane.net> writes:
> > > > I now have a C implementation of initdb, ...
> > > > There's a little work still to go (see below), but I'd appreciate
some
> > > > eyeballs on it to see if I have made any major booboos, or could
have
> > > > done things better. What's the best way to proceed?
> > >
> > > Put it on a web page and post the link, or if you don't have a
website,
> > > send it to pgsql-patches (clearly marked as "not ready to apply").
> > >
> >
> >
> > ---------------------------(end of broadcast)---------------------------
> > TIP 6: Have you searched our list archives?
> >
> >                http://archives.postgresql.org
> >
>
> -- 
>   Bruce Momjian                        |  http://candle.pha.pa.us
>   pgman@candle.pha.pa.us               |  (610) 359-1001
>   +  If your life is a hard drive,     |  13 Roberts Road
>   +  Christ can be your backup.        |  Newtown Square, Pennsylvania
19073
>
> ---------------------------(end of broadcast)---------------------------
> TIP 7: don't forget to increase your free space map settings


Thu, 9 Oct 2003 22:03:47 -0400
"Andrew Dunstan" <andrew@dunslane.net>
Re: [HACKERS] initdb
Andrew Dunstan wrote:
> There will be a new version out there soon. When I am happy with it I will
> let you know - right now I am dealing with 2 issues - setlocale(LC_ALL,"")
> doesn't read from the environment on Windows, and the program is possibly
> not picking up the buffers/connections properly.
> 
> Please don't put the version currently out there in CVS - the files won't
> even be the same - initdb-scripts.h will disappear and there will be a new
> file called "system_views.sql".
> 
> There is no urgency about this - until we have a working postgres executable
> on Windows initdb is useless anyway. My aim has been to have initdb ready
> when postgres is ready. Of course, I test against Unix all the time to make
> sure nothing gets broken.

Fine.  We have the URL on the win32 web site, and that's all we need.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Thu, 9 Oct 2003 22:07:42 -0400 (EDT)
Bruce Momjian <pgman@candle.pha.pa.us>
Re: [HACKERS] initdb

There's a new version available at
http://www.dunslane.net/~andrew/Initdb_In_C.html - I think it takes care of
all of Peter's comments. I removed initdb-scripts.h - the smaller scripts
were moved into initdb.c with SQL comments turned into C comments, and the
large script to set up system views becomes its own file, which is now
passed to postgres with single line mode turned off.

I have just realised that I seem to have skipped the bki file version check.
I will fix that in the next version.

cheers

andrew


----- Original Message ----- 
From: "Bruce Momjian" <pgman@candle.pha.pa.us>
To: "Andrew Dunstan" <andrew@dunslane.net>
Cc: "Postgresql Hackers" <pgsql-hackers@postgresql.org>;
"pgsql-hackers-win32" <pgsql-hackers-win32@postgresql.org>
Sent: Thursday, October 09, 2003 10:07 PM
Subject: Re: [pgsql-hackers-win32] [HACKERS] initdb


> Andrew Dunstan wrote:
> > There will be a new version out there soon. When I am happy with it I
will
> > let you know - right now I am dealing with 2 issues -
setlocale(LC_ALL,"")
> > doesn't read from the environment on Windows, and the program is
possibly
> > not picking up the buffers/connections properly.
> >
> > Please don't put the version currently out there in CVS - the files
won't
> > even be the same - initdb-scripts.h will disappear and there will be a
new
> > file called "system_views.sql".
> >
> > There is no urgency about this - until we have a working postgres
executable
> > on Windows initdb is useless anyway. My aim has been to have initdb
ready
> > when postgres is ready. Of course, I test against Unix all the time to
make
> > sure nothing gets broken.
>
> Fine.  We have the URL on the win32 web site, and that's all we need.
>
> -- 
>   Bruce Momjian                        |  http://candle.pha.pa.us
>   pgman@candle.pha.pa.us               |  (610) 359-1001
>   +  If your life is a hard drive,     |  13 Roberts Road
>   +  Christ can be your backup.        |  Newtown Square, Pennsylvania
19073
>
> ---------------------------(end of broadcast)---------------------------
> TIP 7: don't forget to increase your free space map settings


Fri, 10 Oct 2003 07:50:06 -0400
"Andrew Dunstan" <andrew@dunslane.net>
Re: [HACKERS] initdb

Bruce,

I think this is now ready to import into the WIN32 branch. I have ironed out
the cygwin dependencies, and posted a note about what we'll need to do
inside postgres to handle the environment values for setlocale() on Windows.

If you want to rip out the unlink stuff (see previous email thread) we'll
need a version of dirmod.c that is compiled with -DFRONTEND - I guess libpq
is the obvious place for that.

Apart from initdb.c we'll need the file system_views.sql, which can be taken
without change (unless someone want to add comments to it), and Makefile
modifications (see my Makefile on the web for an example, but it probably
doesn't comply with our standard way of doing things).

Anything broken can then be fixed with patches - I have about 7 versions on
5 machines now and my head is starting to swim -).

pg_id and pg_encoding now become redundant in their entirety, as does
initdb.sh.

cheers

andrew


----- Original Message ----- 
From: "Bruce Momjian" <pgman@candle.pha.pa.us>
To: "Andrew Dunstan" <andrew@dunslane.net>
Cc: "Postgresql Hackers" <pgsql-hackers@postgresql.org>;
"pgsql-hackers-win32" <pgsql-hackers-win32@postgresql.org>
Sent: Thursday, October 09, 2003 10:07 PM
Subject: Re: [pgsql-hackers-win32] [HACKERS] initdb


> Andrew Dunstan wrote:
> > There will be a new version out there soon. When I am happy with it I
will
> > let you know - right now I am dealing with 2 issues -
setlocale(LC_ALL,"")
> > doesn't read from the environment on Windows, and the program is
possibly
> > not picking up the buffers/connections properly.
> >
> > Please don't put the version currently out there in CVS - the files
won't
> > even be the same - initdb-scripts.h will disappear and there will be a
new
> > file called "system_views.sql".
> >
> > There is no urgency about this - until we have a working postgres
executable
> > on Windows initdb is useless anyway. My aim has been to have initdb
ready
> > when postgres is ready. Of course, I test against Unix all the time to
make
> > sure nothing gets broken.
>
> Fine.  We have the URL on the win32 web site, and that's all we need.
>
> -- 
>   Bruce Momjian                        |  http://candle.pha.pa.us
>   pgman@candle.pha.pa.us               |  (610) 359-1001
>   +  If your life is a hard drive,     |  13 Roberts Road
>   +  Christ can be your backup.        |  Newtown Square, Pennsylvania
19073
>
> ---------------------------(end of broadcast)---------------------------
> TIP 3: if posting/reading through Usenet, please send an appropriate
>       subscribe-nomail command to majordomo@postgresql.org so that your
>       message can get through to the mailing list cleanly


Sun, 12 Oct 2003 13:13:41 -0400
"Andrew Dunstan" <andrew@dunslane.net>
Re: [HACKERS] initdb
Andrew Dunstan wrote:
> 
> Bruce,
> 
> I think this is now ready to import into the WIN32 branch. I have ironed out
> the cygwin dependencies, and posted a note about what we'll need to do
> inside postgres to handle the environment values for setlocale() on Windows.
> 
> If you want to rip out the unlink stuff (see previous email thread) we'll
> need a version of dirmod.c that is compiled with -DFRONTEND - I guess libpq
> is the obvious place for that.
> 
> Apart from initdb.c we'll need the file system_views.sql, which can be taken
> without change (unless someone want to add comments to it), and Makefile
> modifications (see my Makefile on the web for an example, but it probably
> doesn't comply with our standard way of doing things).
> 
> Anything broken can then be fixed with patches - I have about 7 versions on
> 5 machines now and my head is starting to swim -).
> 
> pg_id and pg_encoding now become redundant in their entirety, as does
> initdb.sh.

Here is a slightly modified version of Andrew's great work in making a C
version of initdb.  Other than minor cleanups, the only big change was
to remove rmdir handling because we using rm -r and rmdir /s in
commands/dbcommands.c, so we might as use the same thing for initdb.c
rather than having code that traverses the directory tree doing 'rm'.

The other change was to remove the unlink code and instead use
port/dirmod.c's version.

It passes all the regression tests.  I have also included a diff against
Andrew's version so you can see my changes.  It seems Andrew had a very
current version of initdb.  The only update he missed was the change to
test the number of connections before shared buffers --- I made that
change myself.

I would like to apply this in the next few days to HEAD before initdb.sh
drifts.  We are no longer using the WIN32_DEV branch because all our
work is now in 7.5 head.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

/*-------------------------------------------------------------------------
 *
 * initdb
 *
 * author: Andrew Dunstan	   mailto:andrew@dunslane.net
 *
 * Copyright (C) 2003 Andrew Dunstan
 * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * This code is released under the terms of the PostgreSQL License.
 *
 * This is a C implementation of the previous shell script for setting up a
 * PostgreSQL cluster location, and should be highly compatible with it.
 *
 * $Header$
 *
 * TODO:
 *	 - clean up find_postgres code and return values
 *
 * Note:
 *	 The program has some memory leakage - it isn't worth cleaning it up.
 *	 Even before the code was put in to free most of the dynamic memory
 *	 used it ran around 500Kb used + malloc overhead. It should now use
 *	 far less than that (around 240Kb - the size of the BKI file).
 *	 If we can't load this much data into memory how will we ever run
 *	 postgres anyway?
 *
 *-------------------------------------------------------------------------
 */

#include "postgres_fe.h"

#include "getopt_long.h"
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <locale.h>

#include <signal.h>

#include "libpq/pqsignal.h"
#include "mb/pg_wchar.h"


/* version string we expect back from postgres */
#define PG_VERSIONSTR "postgres (PostgreSQL) " PG_VERSION "\n"

/* values passed in by makefile define */

char	   *bindir = PGBINDIR;
char	   *datadir = PGDATADIR;

/* values to be obtained from arguments */
char	   *pg_data = "";
char	   *encoding = "";
char	   *locale = "";
char	   *lc_collate = "";
char	   *lc_ctype = "";
char	   *lc_monetary = "";
char	   *lc_numeric = "";
char	   *lc_time = "";
char	   *lc_messages = "";
char	   *username = "";
bool		pwprompt = false;
bool		debug = false;
bool		noclean = false;
bool		show_help = false;
bool		show_version = false;
bool		show_setting = false;


/* internal vars */
char	   *progname;
char	   *self_path;
char	   *postgres;
char	   *encodingid = "0";
char	   *bki_file;
char	   *desc_file;
char	   *hba_file;
char	   *ident_file;
char	   *conf_file;
char	   *conversion_file;
char	   *info_schema_file;
char	   *features_file;
char	   *system_views_file;
char	   *effective_user;
bool		testpath = true;
bool		made_new_pgdata = false;
char		infoversion[100];
bool		not_ok = false;

/* defaults */
int			n_connections = 10;
int			n_buffers = 50;


/* platform specific path stuff */
#if defined(__CYGWIN__) || defined(WIN32)
#define EXE ".exe"
#define DEVNULL "nul"
#else
#define EXE ""
#define DEVNULL "/dev/null"
#endif

#ifdef WIN32
#define PATHSEP ';'
#else
#define PATHSEP ':'
#endif

/* detected path to postgres and (we assume) friends */
char	   *pgpath;

/* forward declare all our functions */
static bool rmtree(char *, bool);
static void exit_nicely(void);
static void canonicalise_path(char *);

#ifdef WIN32
static char *expanded_path(char *);
#else
#define expanded_path(x) (x)
#endif

static char **readfile(char *);
static void writefile(char *, char **);
static char *get_id(void);
static char *get_encoding_id(char *);
static char *get_short_version(void);
static int	mkdir_p(char *, mode_t);
static bool check_data_dir(void);
static bool mkdatadir(char *);
static bool chklocale(const char *);
static void setlocales(void);
static void set_input(char **, char *);
static void check_input(char *path);
static int	find_postgres(char *);
static int	set_paths(void);
static char **replace_token(char **, char *, char *);
static void set_short_version(char *, char *);
static void set_null_conf(void);
static void test_buffers(void);
static void test_connections(void);
static void setup_config(void);
static void bootstrap_template1(char *);
static void setup_shadow(void);
static void get_set_pwd(void);
static void unlimit_systables(void);
static void setup_depend(void);
static void setup_sysviews(void);
static void setup_description(void);
static void setup_conversion(void);
static void setup_privileges(void);
static void set_info_version(void);
static void setup_schema(void);
static void vacuum_db(void);
static void make_template0(void);
static void usage(void);
static void trapsig(int);
static void check_ok(void);
static char *xstrdup(const char *);
static void *xmalloc(size_t);

/*
 * macros for running pipes to postgres
 */
#define PG_CMD_DECL		char cmd[MAXPGPATH]; char ** line ; FILE * pg
#define PG_CMD_DECL_NOLINE		   char cmd[MAXPGPATH]; FILE * pg

#define PG_CMD_OPEN \
do { \
		  pg = popen(cmd,PG_BINARY_W); \
		  if (pg == NULL)  \
			exit_nicely(); \
} while (0)

#define PG_CMD_CLOSE \
do { \
		 if(pclose(pg) >> 8 & 0xff) \
			exit_nicely(); \
} while (0)

#define PG_CMD_PUTLINE \
do { \
		 if (fputs(*line, pg) < 0) \
		   exit_nicely(); \
		 fflush(pg); \
} while (0)

#ifndef WIN32
#define QUOTE_PATH	""
#else
#define QUOTE_PATH	"\""
#endif

/*
 * routines to check mem allocations and fail noisily.
 * Note that we can't call exit_nicely() on a memory failure, as it calls
 * rmtree() which needs memory allocation. So we just exit with a bang.
 *
 */
static void *
xmalloc(size_t size)
{
	void	   *result;

	result = malloc(size);
	if (!result)
	{
		fputs("malloc failure - bailing out\n", stderr);
		exit(1);
	}
	return result;
}

static char *
xstrdup(const char *s)
{
	char	   *result;

	result = strdup(s);
	if (!result)
	{
		fputs("strdup failure - bailing out\n", stderr);
		exit(1);
	}
	return result;
}

/*
 * delete a directory tree recursively
 * assumes path points to a valid directory
 * deletes everything under path
 * if rmtopdir is true deletes the directory too
 *
 */
static bool
rmtree(char *path, bool rmtopdir)
{
	char		buf[MAXPGPATH + 64];
	int			ret = 1;

#ifndef WIN32
	/* doesn't handle .* files */
	snprintf(buf, sizeof(buf), "rm -rf '%s%s'", path,
			 (rmtopdir) ? "" : "/*");
#else
	snprintf(buf, sizeof(buf), "rmdir /s /q \"%s\"", path);
#endif

	if (system(buf) != 0)
		ret = 0;

#ifdef WIN32
	if (ret == 1 && !rmtopdir)
		ret = !mkdir(path);		/* recreate, no permission issues? */
#endif
	return ret;
}


/*
 * make all paths look like unix, with forward slashes
 * also strip any trailing slash
 * needed?
 */
static void
canonicalise_path(char *path)
{
	char	   *p;

	for (p = path; *p; p++)
	{
#ifdef WIN32
		if (*p == '\\')
			*p = '/';
#endif
	}
	if (p != path && *--p == '/')
		*p = '\0';
}

/*
 * make a copy of the array of lines, with token replaced by replacement
 * the first time it occurs on each line.
 * This does most of what sed was used for in the shell script, but
 * doesn't need any regexp stuff.
 */
static char **
replace_token(char **lines, char *token, char *replacement)
{
	int			numlines = 1;
	int			i;
	char	  **result;
	int			toklen,
				replen,
				diff;

	for (i = 0; lines[i]; i++)
		numlines++;

	result = (char **) xmalloc(numlines * sizeof(char *));

	toklen = strlen(token);
	replen = strlen(replacement);
	diff = replen - toklen;

	for (i = 0; i < numlines; i++)
	{
		char	   *where;
		char	   *newline;
		int			pre;

		/* just copy pointer if NULL or no change needed */

		if (lines[i] == NULL || (where = strstr(lines[i], token)) == NULL)
		{
			result[i] = lines[i];
			continue;
		}

		/* if we get here a change is needed - set up new line */

		newline = (char *) xmalloc(strlen(lines[i]) + diff + 1);

		pre = where - lines[i];

		strncpy(newline, lines[i], pre);

		strcpy(newline + pre, replacement);

		strcpy(newline + pre + replen, lines[i] + pre + toklen);

		result[i] = newline;

	}

	return result;

}

/*
 * get the lines from a text file
 *
 */
static char **
readfile(char *path)
{
	FILE	   *infile;
	int			maxlength = 0,
				linelen = 0;
	int			nlines = 0;
	char	  **result;
	char	   *buffer;
	int			c;

	if ((infile = fopen(path, "r")) == NULL)
	{
		fprintf(stderr, "could not read %s ... ", path);
		exit_nicely();
	}

	/* pass over the file twice - the first time to size the result */

	while ((c = fgetc(infile)) != EOF)
	{
		linelen++;
		if (c == '\n')
		{
			nlines++;
			if (linelen > maxlength)
				maxlength = linelen;
			linelen = 0;
		}
	}

	/* handle last line without a terminating newline (yuck) */

	if (linelen)
		nlines++;
	if (linelen > maxlength)
		maxlength = linelen;

	/* set up the result and the line buffer */

	result = (char **) xmalloc((nlines + 2) * sizeof(char *));
	buffer = (char *) xmalloc(maxlength + 2);

	/* now reprocess the file and store the lines */

	rewind(infile);
	nlines = 0;
	while (fgets(buffer, maxlength + 1, infile) != NULL)
	{
		result[nlines] = xstrdup(buffer);
		nlines++;
	}

	fclose(infile);
	result[nlines] = NULL;

	return result;
}

/*
 * write an array of lines to a file
 *
 */
static void
writefile(char *path, char **lines)
{
	FILE	   *out_file;
	char	  **line;

	;
	if ((out_file = fopen(path, PG_BINARY_W)) == NULL)
	{
		fprintf(stderr, "could not write %s ... ", path);
		exit_nicely();
	}
	for (line = lines; *line != NULL; line++)
	{
		if (fputs(*line, out_file) < 0)
			exit_nicely();
		free(*line);
	}
	if (fclose(out_file))
		exit_nicely();
}

/* source stolen from FreeBSD /src/bin/mkdir/mkdir.c and adapted */

/*
 * this tries to build all the elements of a path to a directory a la mkdir -p
 * we assume the path is in canonical form, i.e. uses / as the separator
 * we also assume it isn't null.
 *
 */
static int
mkdir_p(char *path, mode_t omode)
{
	struct stat sb;
	mode_t		numask,
				oumask;
	int			first,
				last,
				retval;
	char	   *p;

	p = path;
	oumask = 0;
	retval = 0;

#ifdef WIN32
	/* skip network and drive specifiers for win32 */
	if (strlen(p) >= 2)
	{
		if (p[0] == '/' && p[1] == '/')
		{
			/* network drive */
			p = strstr(p + 2, "/");
			if (p == NULL)
				return 1;
		}
		else if (p[1] == ':' &&
				 ((p[0] >= 'a' && p[0] <= 'z') ||
				  (p[0] >= 'A' && p[0] <= 'Z')))
		{
			/* local drive */
			p += 2;
		}
	}
#endif

	if (p[0] == '/')			/* Skip leading '/'. */
		++p;
	for (first = 1, last = 0; !last; ++p)
	{
		if (p[0] == '\0')
			last = 1;
		else if (p[0] != '/')
			continue;
		*p = '\0';
		if (p[1] == '\0')
			last = 1;
		if (first)
		{
			/*
			 * POSIX 1003.2: For each dir operand that does not name an
			 * existing directory, effects equivalent to those cased by
			 * the following command shall occcur:
			 *
			 * mkdir -p -m $(umask -S),u+wx $(dirname dir) && mkdir [-m mode]
			 * dir
			 *
			 * We change the user's umask and then restore it, instead of
			 * doing chmod's.
			 */
			oumask = umask(0);
			numask = oumask & ~(S_IWUSR | S_IXUSR);
			(void) umask(numask);
			first = 0;
		}
		if (last)
			(void) umask(oumask);
		if (mkdir(path, last ? omode : S_IRWXU | S_IRWXG | S_IRWXO) < 0)
		{
			if (errno == EEXIST || errno == EISDIR)
			{
				if (stat(path, &sb) < 0)
				{
					retval = 1;
					break;
				}
				else if (!S_ISDIR(sb.st_mode))
				{
					if (last)
						errno = EEXIST;
					else
						errno = ENOTDIR;
					retval = 1;
					break;
				}
			}
			else
			{
				retval = 1;
				break;
			}
		}
		if (!last)
			*p = '/';
	}
	if (!first && !last)
		(void) umask(oumask);
	return retval;
}

/*
 * clean up any files we created on failure
 * if we created the data directory remove it too
 *
 */
static void
exit_nicely(void)
{
	fprintf(stderr, "%s: failed\n", progname);

	if (!noclean)
	{
		if (made_new_pgdata)
		{
			fprintf(stderr, "%s: removing data directory \"%s\"\n",
					progname, pg_data);
			if (!rmtree(pg_data, true))
				fprintf(stderr, "%s: failed\n", progname);
		}
		else
		{
			fprintf(stderr,
					"%s: removing contents of data directory \"%s\"\n",
					progname, pg_data);
			if (!rmtree(pg_data, false))
				fprintf(stderr, "%s: failed\n", progname);
		}
	}
	exit(1);
}

/*
 * find the current user using code lifted from pg_id.c
 * on unix make sure it isn't really root
 *
 */
static char *
get_id(void)
{
#ifndef WIN32

	struct passwd *pw;

	pw = getpwuid(getuid());

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

	if (!geteuid())				/* 0 is root's uid */
	{
		fprintf(stderr,
				"%s: cannot be run as root\n"
			 "Please log in (using, e.g., \"su\") as the (unprivileged) "
				"user that will\n" "own the server process.\n",
				progname);
		exit(1);
	}
#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 xstrdup(pw->pw_name);
}

/*
 * get the encoding id for a given encoding name
 *
 */
static char *
get_encoding_id(char *encoding_name)
{
	int			enc;
	char		result[20];

	if (encoding_name && *encoding_name)
	{
		if ((enc = pg_char_to_encoding(encoding_name)) >= 0 &&
			pg_valid_server_encoding(encoding_name) >= 0)
		{
			sprintf(result, "%d", enc);
			return xstrdup(result);
		}
	}
	fprintf(stderr, "%s: \"%s\" is not a valid server encoding name\n",
			progname, encoding_name ? encoding_name : "(null)");
	exit(1);
}

/*
 * get short version of VERSION
 *
 */
static char *
get_short_version(void)
{
	bool		gotdot = false;
	int			end;
	char	   *vr;

	vr = xstrdup(PG_VERSION);

	for (end = 0; vr[end] != '\0'; end++)
	{
		if (vr[end] == '.')
		{
			if (end == 0)
				return NULL;
			else if (gotdot)
				break;
			else
				gotdot = true;
		}
		else if (vr[end] < '0' || vr[end] > '9')
		{
			/* gone past digits and dots */
			break;
		}
	}
	if (end == 0 || vr[end - 1] == '.' || !gotdot)
		return NULL;

	vr[end] = '\0';
	return vr;
}

/*
 * make sure the data directory either doesn't exist or is empty
 *
 */
static bool
check_data_dir(void)
{
	DIR		   *chkdir;
	struct dirent *file;
	bool		empty = true;

	chkdir = opendir(pg_data);

	if (!chkdir)
		return (errno == ENOENT);

	while ((file = readdir(chkdir)) != NULL)
	{
		if (strcmp(".", file->d_name) == 0 || strcmp("..", file->d_name) == 0)
		{
			/* skip this and parent directory */
			continue;
		}
		else
		{
			empty = false;
			break;
		}
	}

	closedir(chkdir);

	return empty;
}

/*
 * make the data directory (or one of its subdirectories if subdir is not NULL)
 *
 */
static bool
mkdatadir(char *subdir)
{
	char	   *path;
	int			res;

	path = xmalloc(strlen(pg_data) + 2 +
				   (subdir == NULL ? 0 : strlen(subdir)));

	if (subdir != NULL)
		sprintf(path, "%s/%s", pg_data, subdir);
	else
		strcpy(path, pg_data);

	res = mkdir(path, 0700);
	if (res == 0)
		return true;
	else if (subdir == NULL || errno != ENOENT)
		return false;
	else
		return !mkdir_p(path, 0700);
}


/*
 * set name of given input file variable under data directory
 *
 */
static void
set_input(char **dest, char *filename)
{
	*dest = xmalloc(strlen(datadir) + strlen(filename) + 2);
	sprintf(*dest, "%s/%s", datadir, filename);
}

/*
 * check that given input file exists
 *
 */
static void
check_input(char *path)
{
	struct stat statbuf;

	if (stat(path, &statbuf) != 0 || !S_ISREG(statbuf.st_mode))
	{
		fprintf(stderr,
				"%s: file \"%s\" not found\n"
		   "This means you have a corrupted installation or identified\n"
				"the wrong directory with the invocation option -L.\n",
				progname, path);
		exit(1);
	}

}

/*
 * TODO - clean this up and handle the errors properly
 * don't overkill
 *
 */
#define FIND_SUCCESS 0
#define FIND_NOT_FOUND 1
#define FIND_STAT_ERR 2
#define FIND_NOT_REGFILE 3
#define FIND_BAD_PERM 4
#define FIND_EXEC_ERR 5
#define FIND_WRONG_VERSION 6

/*
 * see if there is a postgres executable in the given path, and giving the
 * right version number
 *
 */
static int
find_postgres(char *path)
{
	char		fn[MAXPGPATH];
	char		cmd[MAXPGPATH];
	char		line[100];

#ifndef WIN32
	int			permmask = S_IROTH | S_IXOTH;
#endif

	struct stat statbuf;
	FILE	   *pgver;
	int			plen = strlen(path);

	if (path[plen - 1] != '/')
		snprintf(fn, MAXPGPATH, "%s/postgres%s", path, EXE);
	else
		snprintf(fn, MAXPGPATH, "%spostgres%s", path, EXE);

	if (stat(fn, &statbuf) != 0)
	{
		if (errno == ENOENT)
			return FIND_NOT_FOUND;
		else
			return FIND_STAT_ERR;
	}
	if (!S_ISREG(statbuf.st_mode))
		return FIND_NOT_REGFILE;

#ifndef WIN32
	/*
	 * Only unix requires this test, on WIN32 an .exe file should be
	 * executable
	 */
	if ((statbuf.st_mode & permmask) != permmask)
		return FIND_BAD_PERM;
#endif

	snprintf(cmd, MAXPGPATH, "\"%s/postgres\" -V 2>%s", path, DEVNULL);

	if ((pgver = popen(cmd, "r")) == NULL)
		return FIND_EXEC_ERR;

	if (fgets(line, sizeof(line), pgver) == NULL)
		perror("fgets failure");

	pclose(pgver);

	if (strcmp(line, PG_VERSIONSTR) != 0)
		return FIND_WRONG_VERSION;

	return FIND_SUCCESS;
}

/*
 * Windows doesn't like relative paths to executables (other things work fine)
 * so we call its builtin function to expand them. Elsewhere this is a NOOP
 *
 */
#ifdef WIN32
static char *
expanded_path(char *path)
{
	char		abspath[MAXPGPATH];

	if (_fullpath(abspath, path, MAXPGPATH) == NULL)
	{
		perror("expanded path");
		return path;
	}
	canonicalise_path(abspath);
	return xstrdup(abspath);
}
#endif 

/*
 * set the paths pointing to postgres
 * look for it in the same place we found this program, or in the environment
 * path, or in the configured bindir.
 *
 */
static int
set_paths(void)
{
	if (testpath && !self_path)
	{

		char	   *path,
				   *cursor;
		int			pathlen,
					i,
					pathsegs;
		char	  **pathbits;
		char		buf[MAXPGPATH];
		struct stat statbuf;

		path = xstrdup(getenv("PATH"));
		pathlen = strlen(path);

		for (i = 0, pathsegs = 1; i < pathlen; i++)
		{
			if (path[i] == PATHSEP)
				pathsegs++;
		}

		pathbits = (char **) xmalloc(pathsegs * sizeof(char *));
		for (i = 0, pathsegs = 0, cursor = path; i <= pathlen; i++)
		{
			if (path[i] == PATHSEP || path[i] == 0)
			{
				path[i] = 0;
				if (strlen(cursor) == 0)
				{
					/* empty path segment means current directory */
					pathbits[pathsegs] = xstrdup(".");
				}
				else
				{
					canonicalise_path(cursor);
					pathbits[pathsegs] = cursor;
				}
				pathsegs++;
				cursor = path + i + 1;
			}
		}

		for (i = 0; i < pathsegs; i++)
		{
			snprintf(buf, MAXPGPATH, "%s/%s%s", pathbits[i], progname, EXE);
			if (stat(buf, &statbuf) == 0 && S_ISREG(statbuf.st_mode))
			{
				self_path = pathbits[i];
				break;
			}
		}
	}

	if (testpath && self_path &&
		(find_postgres(expanded_path(self_path)) == 0))
	{
		/* we found postgres on out own path */
		pgpath = expanded_path(self_path);
	}
	else
	{
		/* look in the hardcoded bindir */
		int			res;
		char	   *cbindir;

		cbindir = xstrdup(bindir);
		canonicalise_path(cbindir);
		res = find_postgres(expanded_path(cbindir));
		if (res == 0)
			pgpath = expanded_path(cbindir);
		else
			return 1;
	}

	return 0;
}

/*
 * write out the PG_VERSION file in the data dir, or its subdirectory
 * if extrapath is not NULL
 *
 */
static void
set_short_version(char *short_version, char *extrapath)
{
	FILE	   *version_file;
	char	   *path;

	if (extrapath == NULL)
	{
		path = xmalloc(strlen(pg_data) + 12);
		sprintf(path, "%s/PG_VERSION", pg_data);
	}
	else
	{
		path = xmalloc(strlen(pg_data) + strlen(extrapath) + 13);
		sprintf(path, "%s/%s/PG_VERSION", pg_data, extrapath);
	}
	version_file = fopen(path, PG_BINARY_W);
	fprintf(version_file, "%s\n", short_version);
	fclose(version_file);
}

/*
 * set up an empty config file so we can check buffers and connections
 *
 */
static void
set_null_conf(void)
{
	FILE	   *conf_file;
	char	   *path;

	path = xmalloc(strlen(pg_data) + 17);
	sprintf(path, "%s/postgresql.conf", pg_data);
	conf_file = fopen(path, PG_BINARY_W);
	fclose(conf_file);
}

/*
 * check how many connections we can sustain
 *
 */
static void
test_connections(void)
{
	char	   *format =
	"\"%s/postgres\" -boot -x 0 -F "
	"-c shared_buffers=%d -c max_connections=%d template1 <%s >%s 2>&1";
	char		cmd[MAXPGPATH];
	int			conns[] = {100, 50, 40, 30, 20, 10};
	int			len = sizeof(conns) / sizeof(int);
	int			i,
				status;

	for (i = 0; i < len; i++)
	{
		snprintf(cmd, sizeof(cmd), format,
				 pgpath, conns[i] * 5, conns[i], DEVNULL, DEVNULL);
		status = system(cmd);
		if (status == 0)
			break;
	}
	if (i >= len)
		i = len - 1;
	n_connections = conns[i];
	printf("connections set to %d\n", n_connections);
}

/*
 * check how many buffers we can run with
 *
 */
static void
test_buffers(void)
{
	char	   *format =
	"\"%s/postgres\"  -boot -x 0 -F "
	"-c shared_buffers=%d -c max_connections=%d template1 <%s >%s 2>&1";
	char		cmd[MAXPGPATH];
	int			bufs[] = {1000, 900, 800, 700, 600, 500, 400, 300, 200, 100, 50};
	int			len = sizeof(bufs) / sizeof(int);
	int			i,
				status;

	for (i = 0; i < len; i++)
	{
		snprintf(cmd, sizeof(cmd), format, pgpath, bufs[i], n_connections,
			DEVNULL, DEVNULL);
		status = system(cmd);
		if (status == 0)
			break;
	}
	if (i >= len)
		i = len - 1;
	n_buffers = bufs[i];
	printf("buffers set to %d\n", n_buffers);
}

/*
 * set up all the config files
 *
 */
static void
setup_config(void)
{

	char	  **conflines;
	char		repltok[100];
	char		path[MAXPGPATH];

	fputs("creating configuration files ... ", stdout);

	/* postgresql.conf */

	conflines = readfile(conf_file);

	snprintf(repltok, sizeof(repltok), "max_connections = %d", n_connections);
	conflines = replace_token(conflines, "#max_connections = 100", repltok);

	snprintf(repltok, sizeof(repltok), "shared_buffers = %d", n_buffers);
	conflines = replace_token(conflines, "#shared_buffers = 1000", repltok);

	snprintf(repltok, sizeof(repltok), "lc_messages = '%s'", lc_messages);
	conflines = replace_token(conflines, "#lc_messages = 'C'", repltok);

	snprintf(repltok, sizeof(repltok), "lc_monetary = '%s'", lc_monetary);
	conflines = replace_token(conflines, "#lc_monetary = 'C'", repltok);

	snprintf(repltok, sizeof(repltok), "lc_numeric = '%s'", lc_numeric);

	conflines = replace_token(conflines, "#lc_numeric = 'C'", repltok);

	snprintf(repltok, sizeof(repltok), "lc_time = '%s'", lc_time);
	conflines = replace_token(conflines, "#lc_time = 'C'", repltok);

	snprintf(path, MAXPGPATH, "%s/postgresql.conf", pg_data);

	writefile(path, conflines);
	chmod(path, 0600);

	free(conflines);


	/* pg_hba.conf */

	conflines = readfile(hba_file);

#ifndef HAVE_IPV6
	conflines = replace_token(conflines,
							  "host    all         all         ::1",
							  "#host    all         all         ::1");
#endif

	snprintf(path, MAXPGPATH, "%s/pg_hba.conf", pg_data);

	writefile(path, conflines);
	chmod(path, 0600);

	free(conflines);

	/* pg_ident.conf */

	conflines = readfile(ident_file);

	snprintf(path, MAXPGPATH, "%s/pg_ident.conf", pg_data);

	writefile(path, conflines);
	chmod(path, 0600);

	free(conflines);

	check_ok();
}


/*
 * run the bootstrap code
 *
 */
static void
bootstrap_template1(char *short_version)
{
	char	   *talkargs = "";
	char	  **bki_lines;
	char		headerline[MAXPGPATH];

	PG_CMD_DECL;

	printf("creating template1 database in %s/base/1 ... ", pg_data);

	if (debug)
		talkargs = "-d 5";

	bki_lines = readfile(bki_file);

	snprintf(headerline, MAXPGPATH, "# PostgreSQL %s\n", short_version);

	if (strcmp(headerline, *bki_lines) != 0)
	{
		fprintf(stderr,
				"%s: input file \"%s\" does not belong to PostgreSQL %s\n"
				"Check your installation or specify the correct path "
				"using the option -L.\n",
				progname, bki_file, PG_VERSION);
		exit_nicely();

	}

	bki_lines = replace_token(bki_lines, "POSTGRES", effective_user);

	bki_lines = replace_token(bki_lines, "ENCODING", encodingid);

	/*
	 * we could save the old environment here, and restore it afterwards,
	 * but there doesn't seem to be any point, especially as we have
	 * already called setlocale().
	 *
	 */
	snprintf(cmd, MAXPGPATH, "LC_COLLATE=%s", lc_collate);
	putenv(xstrdup(cmd));

	snprintf(cmd, MAXPGPATH, "LC_CTYPE=%s", lc_ctype);
	putenv(xstrdup(cmd));

	putenv("LC_ALL");

	snprintf(cmd, MAXPGPATH,
		" \"%s/postgres\"  -boot -x1 -F %s template1", pgpath, talkargs);

	PG_CMD_OPEN;

	for (line = bki_lines; *line != NULL; line++)
	{
		PG_CMD_PUTLINE;
		free(*line);
	}

	PG_CMD_CLOSE;

	free(bki_lines);

	check_ok();
}

/*
 * set up the shadow password table
 *
 */
static void
setup_shadow(void)
{

	char	   *pg_shadow_setup[] = {
		/*
		 * Create a trigger so that direct updates to pg_shadow will be
		 * written to the flat password/group files pg_pwd and pg_group
		 */
		"CREATE TRIGGER pg_sync_pg_pwd "
		"  AFTER INSERT OR UPDATE OR DELETE ON pg_shadow "
		"  FOR EACH ROW EXECUTE PROCEDURE update_pg_pwd_and_pg_group();\n",
		"CREATE TRIGGER pg_sync_pg_group "
		"  AFTER INSERT OR UPDATE OR DELETE ON pg_group "
		"  FOR EACH ROW EXECUTE PROCEDURE update_pg_pwd_and_pg_group();\n",

		/*
		 * needs to be done before alter user, because alter user checks
		 * that pg_shadow is secure ...
		 */
		"REVOKE ALL on pg_shadow FROM public;\n",
		NULL
	};

	PG_CMD_DECL;

	fputs("initializing pg_shadow ... ", stdout);

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s",
			 pgpath, DEVNULL);

	PG_CMD_OPEN;

	for (line = pg_shadow_setup; *line != NULL; line++)
		PG_CMD_PUTLINE;

	PG_CMD_CLOSE;

	check_ok();
}

/*
 * get the superuser password if required, and call postgres to set it
 *
 */
static void
get_set_pwd(void)
{
	PG_CMD_DECL_NOLINE;

	char	   *pwd1,
			   *pwd2;
	char		pwdpath[MAXPGPATH];
	struct stat statbuf;

	pwd1 = simple_prompt("Enter new superuser password: ", 100, false);
	pwd2 = simple_prompt("Enter it again: ", 100, false);
	if (strcmp(pwd1, pwd2) != 0)
	{
		fprintf(stderr, "Passwords didn't match.\n");
		exit_nicely();
	}
	free(pwd2);

	printf("storing the password ... ");

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s", pgpath, DEVNULL);


	PG_CMD_OPEN;

	if (fprintf(
	   pg, "ALTER USER \"%s\" WITH PASSWORD '%s';\n", username, pwd1) < 0)
	{
		/* write failure */
		exit_nicely();
	}
	fflush(pg);

	PG_CMD_CLOSE;

	snprintf(pwdpath, MAXPGPATH, "%s/global/pg_pwd", pg_data);
	if (stat(pwdpath, &statbuf) != 0 || !S_ISREG(statbuf.st_mode))
	{
		fprintf(stderr,
				"%s: The password file was not generated - "
				"please report this problem\n",
				progname);
		exit_nicely();
	}

	check_ok();
}

/*
 * toast sys tables
 *
 */
static void
unlimit_systables(void)
{
	char	   *systables_setup[] = {
		"ALTER TABLE pg_attrdef CREATE TOAST TABLE;\n",
		"ALTER TABLE pg_constraint CREATE TOAST TABLE;\n",
		"ALTER TABLE pg_database CREATE TOAST TABLE;\n",
		"ALTER TABLE pg_description CREATE TOAST TABLE;\n",
		"ALTER TABLE pg_group CREATE TOAST TABLE;\n",
		"ALTER TABLE pg_proc CREATE TOAST TABLE;\n",
		"ALTER TABLE pg_rewrite CREATE TOAST TABLE;\n",
		"ALTER TABLE pg_shadow CREATE TOAST TABLE;\n",
		"ALTER TABLE pg_statistic CREATE TOAST TABLE;\n",
		NULL
	};

	PG_CMD_DECL;

	fputs("enabling unlimited row size for system tables ... ", stdout);

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s", pgpath, DEVNULL);

	PG_CMD_OPEN;

	for (line = systables_setup; *line != NULL; line++)
		PG_CMD_PUTLINE;

	PG_CMD_CLOSE;

	check_ok();
}

/*
 * set up pg_depend
 *
 */
static void
setup_depend(void)
{
	char	   *pg_depend_setup[] = {
		/*
		 * Make PIN entries in pg_depend for all objects made so far in
		 * the tables that the dependency code handles.  This is overkill
		 * (the system doesn't really depend on having every last weird
		 * datatype, for instance) but generating only the minimum
		 * required set of dependencies seems hard. Note that we
		 * deliberately do not pin the system views. First delete any
		 * already-made entries; PINs override all else, and must be the
		 * only entries for their objects.
		 */
		"DELETE FROM pg_depend;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_class;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_proc;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_type;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_cast;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_constraint;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_attrdef;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_language;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_operator;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_opclass;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_rewrite;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_trigger;\n",

		/*
		 * restriction here to avoid pinning the public namespace
		 */
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_namespace "
		"    WHERE nspname LIKE 'pg%';\n",
		NULL
	};

	PG_CMD_DECL;

	fputs("initializing pg_depend ... ", stdout);

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s",
			 pgpath, DEVNULL);

	PG_CMD_OPEN;

	for (line = pg_depend_setup; *line != NULL; line++)
		PG_CMD_PUTLINE;

	PG_CMD_CLOSE;

	check_ok();
}

/*
 * set up system views
 *
 */
static void
setup_sysviews(void)
{
	PG_CMD_DECL;

	char	  **sysviews_setup;

	fputs("creating system views ... ", stdout);

	sysviews_setup = readfile(system_views_file);

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -N -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s",
			 pgpath, DEVNULL);

	PG_CMD_OPEN;

	for (line = sysviews_setup; *line != NULL; line++)
	{
		PG_CMD_PUTLINE;
		free(*line);
	}

	PG_CMD_CLOSE;

	free(sysviews_setup);

	check_ok();
}

/*
 * load description data
 *
 */
static void
setup_description(void)
{
	char	   *pg_description_setup1[] = {
		"CREATE TEMP TABLE tmp_pg_description ( "
		"	objoid oid, "
		"	classname name, "
		"	objsubid int4, "
		"	description text) WITHOUT OIDS;\n",
		"COPY tmp_pg_description FROM STDIN;\n",
		NULL
	};

	char	   *pg_description_setup2[] = {
		"\\.\n",
		"INSERT INTO pg_description "
		" SELECT t.objoid, c.oid, t.objsubid, t.description "
		"  FROM tmp_pg_description t, pg_class c "
		"    WHERE c.relname = t.classname;\n",
		NULL
	};


	PG_CMD_DECL;

	char	  **desc_lines;

	fputs("loading pg_description ... ", stdout);

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s",
			 pgpath, DEVNULL);

	PG_CMD_OPEN;

	for (line = pg_description_setup1; *line != NULL; line++)
		PG_CMD_PUTLINE;

	desc_lines = readfile(desc_file);
	for (line = desc_lines; *line != NULL; line++)
	{
		PG_CMD_PUTLINE;
		free(*line);
	}

	free(desc_lines);


	for (line = pg_description_setup2; *line != NULL; line++)
		PG_CMD_PUTLINE;

	PG_CMD_CLOSE;

	check_ok();
}

/*
 * load conversion functions
 *
 */
static void
setup_conversion(void)
{
	PG_CMD_DECL;

	char	  **conv_lines;

	fputs("creating conversions ... ", stdout);

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s",
			 pgpath, DEVNULL);

	PG_CMD_OPEN;

	conv_lines = readfile(conversion_file);
	for (line = conv_lines; *line != NULL; line++)
	{
		if (strstr(*line, "DROP CONVERSION") != *line)
			PG_CMD_PUTLINE;

		free(*line);
	}

	free(conv_lines);

	PG_CMD_CLOSE;

	check_ok();
}

/*
 * run privileges script
 *
 */
static void
setup_privileges(void)
{
	char	   *privileges_setup[] = {
		"UPDATE pg_class "
		"  SET relacl = '{\"=r/\\\\\"$POSTGRES_SUPERUSERNAME\\\\\"\"}' "
		"  WHERE relkind IN ('r', 'v', 'S') AND relacl IS NULL;\n",
		"UPDATE pg_proc "
		"  SET proacl = '{\"=X/\\\\\"$POSTGRES_SUPERUSERNAME\\\\\"\"}' "
		"  WHERE proacl IS NULL;\n",
		"UPDATE pg_language "
		"  SET lanacl = '{\"=U/\\\\\"$POSTGRES_SUPERUSERNAME\\\\\"\"}' "
		"  WHERE lanpltrusted;\n",
		"GRANT USAGE ON SCHEMA pg_catalog TO PUBLIC;\n",
		"GRANT CREATE, USAGE ON SCHEMA public TO PUBLIC;\n",
		NULL
	};

	PG_CMD_DECL;

	char	  **priv_lines;

	fputs("setting privileges on builtin objects ... ", stdout);

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s",
			 pgpath, DEVNULL);

	PG_CMD_OPEN;

	priv_lines = replace_token(privileges_setup,
							   "$POSTGRES_SUPERUSERNAME", username);
	for (line = priv_lines; *line != NULL; line++)
		PG_CMD_PUTLINE;

	PG_CMD_CLOSE;

	check_ok();
}

/*
 * extract the strange version of version required for info schema
 *
 */
static void
set_info_version(void)
{
	char	   *letterversion;
	long		major = 0,
				minor = 0,
				micro = 0;
	char	   *endptr;
	char	   *vstr = xstrdup(PG_VERSION);
	char	   *ptr;

	ptr = vstr + (strlen(vstr) - 1);
	while (ptr != vstr && (*ptr < '0' || *ptr > '9'))
		ptr--;
	letterversion = ptr + 1;
	major = strtol(vstr, &endptr, 10);
	if (*endptr)
		minor = strtol(endptr + 1, &endptr, 10);
	if (*endptr)
		micro = strtol(endptr + 1, &endptr, 10);
	snprintf(infoversion, sizeof(infoversion), "%02ld.%02ld.%04ld%s",
			 major, minor, micro, letterversion);
}

/*
 * load info schema and populate from features file
 *
 */
static void
setup_schema(void)
{

	PG_CMD_DECL;

	char	  **lines;
	int			fres;

	fputs("creating information schema ... ", stdout);

	lines = readfile(info_schema_file);

	/*
	 * note that here we don't run in single line mode, unlike other
	 * places
	 */
	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true -N template1 >%s",
			 pgpath, DEVNULL);

	PG_CMD_OPEN;

	for (line = lines; *line != NULL; line++)
	{
		PG_CMD_PUTLINE;
		free(*line);
	}

	free(lines);

	PG_CMD_CLOSE;

	lines = readfile(features_file);

	/*
	 * strip CR before NL this is the only place we do this  (following
	 * the shell script) - we could do it universally in readfile() if
	 * necessary
	 *
	 */
	lines = replace_token(lines, "\r\n", "\n");

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s",
			 pgpath, DEVNULL);

	PG_CMD_OPEN;

	fres = fprintf(pg,
				   "UPDATE information_schema.sql_implementation_info "
				   "  SET character_value = '%s' "
				   "  WHERE implementation_info_name = 'DBMS VERSION';\n",
				   infoversion);

	if (fres < 0)
		exit_nicely();

	fres = fputs("COPY information_schema.sql_features "
				 "  (feature_id, feature_name, sub_feature_id, "
				 "  sub_feature_name, is_supported, comments) "
				 "FROM STDIN;\n",
				 pg);

	if (fres < 0)
		exit_nicely();

	for (line = lines; *line != NULL; line++)
	{
		PG_CMD_PUTLINE;
		free(*line);
	}

	free(lines);

	if (fputs("\\.\n", pg) < 0)
		exit_nicely();
	fflush(pg);

	PG_CMD_CLOSE;

	check_ok();
}

/*
 * clean everything up in template1
 *
 */
static void
vacuum_db(void)
{
	PG_CMD_DECL_NOLINE;

	fputs("vacuuming database template1 ... ", stdout);

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s",
			 pgpath, DEVNULL);

	PG_CMD_OPEN;

	if (fputs("ANALYSE;\nVACUUM FULL FREEZE;\n", pg) < 0)
		exit_nicely();
	fflush(pg);

	PG_CMD_CLOSE;

	check_ok();

}

/*
 * copy template1 to template0
 *
 */
static void
make_template0(void)
{
	char	   *template0_setup[] = {
		"CREATE DATABASE template0;\n",
		"UPDATE pg_database SET "
		"	datistemplate = 't', "
		"	datallowconn = 'f' "
		"    WHERE datname = 'template0';\n",

		/*
		 * We use the OID of template0 to determine lastsysoid
		 *
		 */
		"UPDATE pg_database SET datlastsysoid = "
		"    (SELECT oid::int4 - 1 FROM pg_database "
		"    WHERE datname = 'template0');\n",

		/*
		 * Explicitly revoke public create-schema and create-temp-table
		 * privileges in template1 and template0; else the latter would be
		 * on by default
		 */
		"REVOKE CREATE,TEMPORARY ON DATABASE template1 FROM public;\n",
		"REVOKE CREATE,TEMPORARY ON DATABASE template0 FROM public;\n",

		/*
		 * Finally vacuum to clean up dead rows in pg_database
		 */
		"VACUUM FULL pg_database;\n",
		NULL
	};

	PG_CMD_DECL;

	fputs("copying template1 to template0 ... ", stdout);

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s",
			 pgpath, DEVNULL);

	PG_CMD_OPEN;

	for (line = template0_setup; *line; line++)
		PG_CMD_PUTLINE;

	PG_CMD_CLOSE;

	check_ok();
}


/*
 * signal handler in case we are interrupted.
 *
 * The Windows runtime docs at
 * http://msdn.microsoft.com/library/en-us/vclib/html/_crt_signal.asp
 * specifically forbid a number of things being done from a signal handler,
 * including IO, memory allocation and system calls, and only allow jmpbuf
 * if you are handling SIGFPE.
 *
 * I avoided doing the forbidden things by setting a flag instead of calling
 * exit_nicely() directly.
 *
 * Also note the behaviour of Windows with SIGINT, which says this:
 *	 Note	SIGINT is not supported for any Win32 application, including
 *	 Windows 98/Me and Windows NT/2000/XP. When a CTRL+C interrupt occurs,
 *	 Win32 operating systems generate a new thread to specifically handle
 *	 that interrupt. This can cause a single-thread application such as UNIX,
 *	 to become multithreaded, resulting in unexpected behavior.
 *
 * I have no idea how to handle this. (Strange they call UNIX an application!)
 * So this will need some testing on Windows.
 *
 */
static void
trapsig(int signum)
{
	/* handle systems that reset the handler, like Windows (grr) */
	pqsignal(signum, trapsig);
	not_ok = true;
}

/*
 * call exit_nicely() if we got a signal, or else output "ok".
 *
 */
static void
check_ok()
{
	if (not_ok)
	{
		puts("Caught Signal.");
		exit_nicely();
	}
	else
	{
		/* no signal caught */
		puts("ok");
	}
}


/*
 * check if given string is a valid locle specifier
 * based on some code given to me by Peter Eisentraut
 * (but I take responsibility for it :-)
 */
static bool
chklocale(const char *locale)
{
	bool		ret;
	int			category = LC_CTYPE;
	char	   *save;

	save = setlocale(category, NULL);
	if (!save)
		return false;			/* should not happen; */

	save = xstrdup(save);

	ret = (setlocale(category, locale) != NULL);

	setlocale(category, save);
	free(save);

	/* should we exit here? */
	if (!ret)
		fprintf(stderr, "%s: invalid locale name \"%s\"\n", progname, locale);

	return ret;
}

/*
 * set up the locale variables
 * assumes we have called setlocale(LC_ALL,"")
 *
 */
static void
setlocales(void)
{
	/* set empty lc_* values to locale config if set */

	if (strlen(locale) > 0)
	{
		if (strlen(lc_ctype) == 0)
			lc_ctype = locale;
		if (strlen(lc_collate) == 0)
			lc_collate = locale;
		if (strlen(lc_numeric) == 0)
			lc_numeric = locale;
		if (strlen(lc_time) == 0)
			lc_time = locale;
		if (strlen(lc_monetary) == 0)
			lc_monetary = locale;
		if (strlen(lc_messages) == 0)
			lc_messages = locale;
	}

	/*
	 * override absent/invalid config settings from initdb's locale
	 * settings
	 */

	if (strlen(lc_ctype) == 0 || !chklocale(lc_ctype))
		lc_ctype = xstrdup(setlocale(LC_CTYPE, NULL));
	if (strlen(lc_collate) == 0 || !chklocale(lc_collate))
		lc_collate = xstrdup(setlocale(LC_COLLATE, NULL));
	if (strlen(lc_numeric) == 0 || !chklocale(lc_numeric))
		lc_numeric = xstrdup(setlocale(LC_NUMERIC, NULL));
	if (strlen(lc_time) == 0 || !chklocale(lc_time))
		lc_time = xstrdup(setlocale(LC_TIME, NULL));
	if (strlen(lc_monetary) == 0 || !chklocale(lc_monetary))
		lc_monetary = xstrdup(setlocale(LC_MONETARY, NULL));
	if (strlen(lc_messages) == 0 || !chklocale(lc_messages))
#ifdef LC_MESSAGES
	{
		/* when available get the current locale setting */
		lc_messages = xstrdup(setlocale(LC_MESSAGES, NULL));
	}
#else
	{
		/* when not available, get the CTYPE setting */
		lc_messages = xstrdup(setlocale(LC_CTYPE, NULL));
	}
#endif

}

/*
 * help text data
 *
 */
char	   *usage_text[] = {
	"$CMDNAME initializes a PostgreSQL database cluster.\n",
	"\n",
	"Usage:\n",
	"  $CMDNAME [OPTION]... [DATADIR]\n",
	"\n",
	"Options:\n",
	" [-D, --pgdata=]DATADIR     location for this database cluster\n",
	"  -E, --encoding=ENCODING   set default encoding for new databases\n",
	"  --locale=LOCALE           initialize database cluster with given locale\n",
	"  --lc-collate, --lc-ctype, --lc-messages=LOCALE\n",
	"  --lc-monetary, --lc-numeric, --lc-time=LOCALE\n",
	"                            initialize database cluster with given locale\n",
	"                            in the respective category (default taken from\n",
	"                            environment)\n",
	"  --no-locale               equivalent to --locale=C\n",
	"  -U, --username=NAME       database superuser name\n",
	"  -W, --pwprompt            prompt for a password for the new superuser\n",
	"  -?, --help                show this help, then exit\n",
	"  -V, --version             output version information, then exit\n",
	"\n",
	"Less commonly used options: \n",
	"  -d, --debug               generate lots of debugging output\n",
	"  -s, --show                show internal settings\n",
	"  -L DIRECTORY              where to find the input files\n",
	"  -n, --noclean             do not clean up after errors\n",
	"\n",
	"If the data directory is not specified, the environment variable PGDATA\n",
	"is used.\n",
	"\n",
	"Report bugs to <pgsql-bugs@postgresql.org>.\n",
	NULL
};



/*
 * print help text
 *
 */
static void
usage(void)
{

	int			i;
	char	  **newtext;

	newtext = replace_token(usage_text, "$CMDNAME", progname);

	for (i = 0; newtext[i]; i++)
		fputs(newtext[i], stdout);		/* faster than printf */
}

int
main(int argc, char *argv[])
{
	/*
	 * options with no short version return a low integer, the rest return
	 * their short version value
	 *
	 */
	static struct option long_options[] = {
		{"pgdata", required_argument, NULL, 'D'},
		{"encoding", required_argument, NULL, 'E'},
		{"locale", required_argument, NULL, 1},
		{"lc-collate", required_argument, NULL, 2},
		{"lc-ctype", required_argument, NULL, 3},
		{"lc-monetary", required_argument, NULL, 4},
		{"lc-numeric", required_argument, NULL, 5},
		{"lc-time", required_argument, NULL, 6},
		{"lc-messages", required_argument, NULL, 7},
		{"no-locale", no_argument, NULL, 8},
		{"pwprompt", no_argument, NULL, 'W'},
		{"username", required_argument, NULL, 'U'},
		{"help", no_argument, NULL, '?'},
		{"version", no_argument, NULL, 'V'},
		{"debug", no_argument, NULL, 'd'},
		{"show", no_argument, NULL, 's'},
		{"noclean", no_argument, NULL, 'n'},
		{0, 0, 0, 0}
	};

	int			c,
				i;
	int			option_index;
	char	   *short_version;
	char	   *pgdenv;			/* PGDATA value got from sent to
								 * environment */
	char	   *subdirs[] =
	{"global", "pg_xlog", "pg_clog", "base", "base/1"};

	char	   *lastsep;

	/* parse argv[0] - detect explicit path if there was one */

	char	   *carg0;

#if defined(__CYGWIN__) || defined(WIN32)
	char	   *exe;			/* location of exe suffix in progname */
#endif

	setlocale(LC_ALL, "");

	carg0 = xstrdup(argv[0]);
	canonicalise_path(carg0);

	lastsep = strrchr(carg0, '/');
	progname = lastsep ? xstrdup(lastsep + 1) : carg0;

#if defined(__CYGWIN__) || defined(WIN32)
	if (strlen(progname) > 4 &&
		(exe = progname + (strlen(progname) - 4)) &&
		stricmp(exe, EXE) == 0)
	{
		/* strip .exe suffix, regardless of case */
		*exe = '\0';
	}
#endif

	if (lastsep)
	{
		self_path = carg0;
		*lastsep = '\0';
	}
	else
	{
		/* no path known to ourselves from argv[0] */
		self_path = NULL;
	}

	/* process options */

	while (1)
	{
		/*
		 * a : as the first option char here lets us use ? as a short
		 * option
		 */
		c = getopt_long(argc, argv, ":D:E:WU:?sVdnL:",
						long_options, &option_index);

		if (c == -1)
			break;

		switch (c)
		{
			case 'D':
				pg_data = xstrdup(optarg);
				break;
			case 'E':
				encoding = xstrdup(optarg);
				break;
			case 'W':
				pwprompt = true;
				break;
			case 'U':
				username = xstrdup(optarg);
				break;
			case 'd':
				debug = true;
				break;
			case 'n':
				noclean = true;
				break;
			case 'L':
				datadir = xstrdup(optarg);
				break;
			case 1:
				locale = xstrdup(optarg);
				break;
			case 2:
				lc_collate = xstrdup(optarg);
				break;
			case 3:
				lc_ctype = xstrdup(optarg);
				break;
			case 4:
				lc_monetary = xstrdup(optarg);
				break;
			case 5:
				lc_numeric = xstrdup(optarg);
				break;
			case 6:
				lc_time = xstrdup(optarg);
				break;
			case 7:
				lc_messages = xstrdup(optarg);
				break;
			case 8:
				locale = "C";
				break;
			case '?':
				show_help = true;
				break;
			case 's':
				show_setting = true;
				break;
			case 'V':
				show_version = true;
				break;
			default:
				show_help = true;
				printf("Unrecognized option: %c\n", c);
		}

	}

	if (optind < argc)
	{
		pg_data = xstrdup(argv[optind]);
		optind++;
	}

	set_info_version();

	if (strlen(pg_data) == 0 && !(show_help || show_setting))
	{
		pgdenv = getenv("PGDATA");
		if (pgdenv && strlen(pgdenv))
		{
			/* PGDATA found */
			pg_data = xstrdup(pgdenv);
		}
		else
		{
			fprintf(stderr,
					"%s: no data directory specified\n"
			   "You must identify the directory where the data for this "
					"database system\n"
			   "will reside.  Do this with either the invocation option "
					"-D or the\n" "environment variable PGDATA.\n",
					progname);
		}
	}

	canonicalise_path(pg_data);

	/*
	 * we have to set PGDATA for postgres rather than pass it on the
	 * commnd line to avoid dumb quoting problems on Windows, and we would
	 * expecially need quotes otherwise on Windows because paths there are
	 * most likely to have embedded spaces.
	 *
	 */
	pgdenv = xmalloc(8 + strlen(pg_data));
	sprintf(pgdenv, "PGDATA=%s", pg_data);
	putenv(pgdenv);

	if (optind < argc)
		show_help = true;

	if (show_version)
	{
		/* hard coded name here, in case they rename executable */
		printf("initdb (PostgreSQL) %s\n", PG_VERSION);
		exit(0);
	}

	if (show_help)
	{
		usage();
		exit(0);
	}

	if (set_paths() != 0)
	{
		fprintf(stderr,
				"The program \"postgres\" is needed by %s "
				"but was not found in \n"
				"the directory \"%s\". Check your installation.\n",
				progname, bindir);
		exit(1);

	}


	if ((short_version = get_short_version()) == NULL)
	{
		fprintf(stderr, "%s: could not get valid short version\n", progname);
		exit(1);
	}

	effective_user = get_id();
	if (!strlen(username))
		username = effective_user;

	if (strlen(encoding))
		encodingid = get_encoding_id(encoding);

	set_input(&bki_file, "postgres.bki");
	set_input(&desc_file, "postgres.description");
	set_input(&hba_file, "pg_hba.conf.sample");
	set_input(&ident_file, "pg_ident.conf.sample");
	set_input(&conf_file, "postgresql.conf.sample");
	set_input(&conversion_file, "conversion_create.sql");
	set_input(&info_schema_file, "information_schema.sql");
	set_input(&features_file, "sql_features.txt");
	set_input(&system_views_file, "system_views.sql");

	if (show_setting || debug)
	{
		fprintf(stderr,
				"VERSION=%s\n"
				"PGDATA=%s\ndatadir=%s\nPGPATH=%s\n"
				"ENCODING=%s\nENCODINGID=%s\n"
				"POSTGRES_SUPERUSERNAME=%s\nPOSTGRES_BKI=%s\n"
				"POSTGRES_DESCR=%s\nPOSTGRESQL_CONF_SAMPLE=%s\n"
				"PG_HBA_SAMPLE=%s\nPG_IDENT_SAMPLE=%s\n",
				PG_VERSION,
				pg_data, datadir, pgpath,
				encoding, encodingid,
				username, bki_file,
				desc_file, conf_file,
				hba_file, ident_file);
	}

	if (show_setting)
		exit(0);

	check_input(bki_file);
	check_input(desc_file);
	check_input(hba_file);
	check_input(ident_file);
	check_input(conf_file);
	check_input(conversion_file);
	check_input(info_schema_file);
	check_input(features_file);
	check_input(system_views_file);

	setlocales();

	if (strcmp(lc_ctype, lc_collate) == 0 &&
		strcmp(lc_ctype, lc_time) == 0 &&
		strcmp(lc_ctype, lc_numeric) == 0 &&
		strcmp(lc_ctype, lc_monetary) == 0 &&
		strcmp(lc_ctype, lc_messages) == 0)
	{
		printf("The database cluster will be initialized with locale %s\n",
			   lc_ctype);
	}
	else
	{
		printf("The database cluster will be initialized with locales\n"
			   "  COLLATE:  %s\n"
			   "  CTYPE:    %s\n"
			   "  MESSAGES: %s\n"
			   "  MONETARY: %s\n"
			   "  NUMERIC:  %s\n"
			   "  TIME:     %s\n",
			   lc_collate,
			   lc_ctype,
			   lc_messages,
			   lc_monetary,
			   lc_numeric,
			   lc_time);
	}

	umask(077);

	/*
	 * now we are starting to do real work, trap signals so we can clean
	 * up
	 */

	/* some of these are not valid on Windows */
#ifdef SIGHUP
	pqsignal(SIGHUP, trapsig);
#endif
#ifdef SIGINT
	pqsignal(SIGINT, trapsig);
#endif
#ifdef SIGQUIT
	pqsignal(SIGQUIT, trapsig);
#endif
#ifdef SIGTERM
	pqsignal(SIGTERM, trapsig);
#endif

	/* clear this we'll use it in a few lines */
	errno = 0;

	if (!check_data_dir())
	{
		fprintf(stderr,
				"%s: directory \"%s\" exists but is not empty\n"
				"If you want to create a new database system, either "
				"remove or empty\n"
				"the directory \"$PGDATA\" or run $CMDNAME with an "
				"argument other than\n"
				"\"%s\".\n",
				progname, pg_data, pg_data);
		exit(1);
	}

	/*
	 * check_data_dir() called opendir - the errno should still be hanging
	 * around
	 */
	if (errno == ENOENT)
	{
		printf("creating directory \"%s\" ... ", pg_data);

		if (!mkdatadir(NULL))
			exit_nicely();
		else
			check_ok();

		made_new_pgdata = true;
	}

	for (i = 0; i < (sizeof(subdirs) / sizeof(char *)); i++)
	{
		printf("creating directory %s/%s ... ", pg_data, subdirs[i]);

		if (!mkdatadir(subdirs[i]))
			exit_nicely();
		else
			check_ok();
	}

	set_short_version(short_version, NULL);

	set_null_conf();

	/* test connections first because it has more constraints */
	test_connections();
	test_buffers();

	setup_config();

	bootstrap_template1(short_version);

	set_short_version(short_version, "base/1");

	setup_shadow();
	if (pwprompt)
		get_set_pwd();

	unlimit_systables();

	setup_depend();

	setup_sysviews();

	setup_description();

	setup_conversion();

	setup_privileges();

	setup_schema();

	vacuum_db();

	make_template0();

	printf("\n"
		   "Success. You can now start the database server using:\n\n"
		   "    %s/postmaster -D %s%s%s\n"
		   "or\n"
		   "    %s/pg_ctl -D %s%s%s -l logfile start\n\n",
			pgpath, QUOTE_PATH, pg_data, QUOTE_PATH,
			pgpath, QUOTE_PATH, pg_data, QUOTE_PATH);

	return 0;
}




CREATE VIEW pg_user AS 
    SELECT 
        usename, 
        usesysid, 
        usecreatedb, 
        usesuper, 
        usecatupd, 
        '********'::text as passwd, 
        valuntil, 
        useconfig 
    FROM pg_shadow;

CREATE VIEW pg_rules AS 
    SELECT 
        N.nspname AS schemaname, 
        C.relname AS tablename, 
        R.rulename AS rulename, 
        pg_get_ruledef(R.oid) AS definition 
    FROM (pg_rewrite R JOIN pg_class C ON (C.oid = R.ev_class)) 
        LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) 
    WHERE R.rulename != '_RETURN';

CREATE VIEW pg_views AS 
    SELECT 
        N.nspname AS schemaname, 
        C.relname AS viewname, 
        pg_get_userbyid(C.relowner) AS viewowner, 
        pg_get_viewdef(C.oid) AS definition 
    FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) 
    WHERE C.relkind = 'v';

CREATE VIEW pg_tables AS 
    SELECT 
        N.nspname AS schemaname, 
        C.relname AS tablename, 
        pg_get_userbyid(C.relowner) AS tableowner, 
        C.relhasindex AS hasindexes, 
        C.relhasrules AS hasrules, 
        (C.reltriggers > 0) AS hastriggers 
    FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) 
    WHERE C.relkind = 'r';

CREATE VIEW pg_indexes AS 
    SELECT 
        N.nspname AS schemaname, 
        C.relname AS tablename, 
        I.relname AS indexname, 
        pg_get_indexdef(I.oid) AS indexdef 
    FROM pg_index X JOIN pg_class C ON (C.oid = X.indrelid) 
         JOIN pg_class I ON (I.oid = X.indexrelid) 
         LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) 
    WHERE C.relkind = 'r' AND I.relkind = 'i';

CREATE VIEW pg_stats AS 
    SELECT 
        nspname AS schemaname, 
        relname AS tablename, 
        attname AS attname, 
        stanullfrac AS null_frac, 
        stawidth AS avg_width, 
        stadistinct AS n_distinct, 
        CASE 1 
            WHEN stakind1 THEN stavalues1 
            WHEN stakind2 THEN stavalues2 
            WHEN stakind3 THEN stavalues3 
            WHEN stakind4 THEN stavalues4 
        END AS most_common_vals, 
        CASE 1 
            WHEN stakind1 THEN stanumbers1 
            WHEN stakind2 THEN stanumbers2 
            WHEN stakind3 THEN stanumbers3 
            WHEN stakind4 THEN stanumbers4 
        END AS most_common_freqs, 
        CASE 2 
            WHEN stakind1 THEN stavalues1 
            WHEN stakind2 THEN stavalues2 
            WHEN stakind3 THEN stavalues3 
            WHEN stakind4 THEN stavalues4 
        END AS histogram_bounds, 
        CASE 3 
            WHEN stakind1 THEN stanumbers1[1] 
            WHEN stakind2 THEN stanumbers2[1] 
            WHEN stakind3 THEN stanumbers3[1] 
            WHEN stakind4 THEN stanumbers4[1] 
        END AS correlation 
    FROM pg_statistic s JOIN pg_class c ON (c.oid = s.starelid) 
         JOIN pg_attribute a ON (c.oid = attrelid AND attnum = s.staattnum) 
         LEFT JOIN pg_namespace n ON (n.oid = c.relnamespace) 
    WHERE has_table_privilege(c.oid, 'select');

REVOKE ALL on pg_statistic FROM public;

CREATE VIEW pg_stat_all_tables AS 
    SELECT 
            C.oid AS relid, 
            N.nspname AS schemaname, 
            C.relname AS relname, 
            pg_stat_get_numscans(C.oid) AS seq_scan, 
            pg_stat_get_tuples_returned(C.oid) AS seq_tup_read, 
            sum(pg_stat_get_numscans(I.indexrelid)) AS idx_scan, 
            sum(pg_stat_get_tuples_fetched(I.indexrelid)) AS idx_tup_fetch, 
            pg_stat_get_tuples_inserted(C.oid) AS n_tup_ins, 
            pg_stat_get_tuples_updated(C.oid) AS n_tup_upd, 
            pg_stat_get_tuples_deleted(C.oid) AS n_tup_del 
    FROM pg_class C LEFT JOIN 
         pg_index I ON C.oid = I.indrelid 
         LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) 
    WHERE C.relkind = 'r' 
    GROUP BY C.oid, N.nspname, C.relname;

CREATE VIEW pg_stat_sys_tables AS 
    SELECT * FROM pg_stat_all_tables 
    WHERE schemaname IN ('pg_catalog', 'pg_toast');

CREATE VIEW pg_stat_user_tables AS 
    SELECT * FROM pg_stat_all_tables 
    WHERE schemaname NOT IN ('pg_catalog', 'pg_toast');

CREATE VIEW pg_statio_all_tables AS 
    SELECT 
            C.oid AS relid, 
            N.nspname AS schemaname, 
            C.relname AS relname, 
            pg_stat_get_blocks_fetched(C.oid) - 
                    pg_stat_get_blocks_hit(C.oid) AS heap_blks_read, 
            pg_stat_get_blocks_hit(C.oid) AS heap_blks_hit, 
            sum(pg_stat_get_blocks_fetched(I.indexrelid) - 
                    pg_stat_get_blocks_hit(I.indexrelid)) AS idx_blks_read, 
            sum(pg_stat_get_blocks_hit(I.indexrelid)) AS idx_blks_hit, 
            pg_stat_get_blocks_fetched(T.oid) - 
                    pg_stat_get_blocks_hit(T.oid) AS toast_blks_read, 
            pg_stat_get_blocks_hit(T.oid) AS toast_blks_hit, 
            pg_stat_get_blocks_fetched(X.oid) - 
                    pg_stat_get_blocks_hit(X.oid) AS tidx_blks_read, 
            pg_stat_get_blocks_hit(X.oid) AS tidx_blks_hit 
    FROM pg_class C LEFT JOIN 
            pg_index I ON C.oid = I.indrelid LEFT JOIN 
            pg_class T ON C.reltoastrelid = T.oid LEFT JOIN 
            pg_class X ON T.reltoastidxid = X.oid 
            LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) 
    WHERE C.relkind = 'r' 
    GROUP BY C.oid, N.nspname, C.relname, T.oid, X.oid;

CREATE VIEW pg_statio_sys_tables AS 
    SELECT * FROM pg_statio_all_tables 
    WHERE schemaname IN ('pg_catalog', 'pg_toast');

CREATE VIEW pg_statio_user_tables AS 
    SELECT * FROM pg_statio_all_tables 
    WHERE schemaname NOT IN ('pg_catalog', 'pg_toast');

CREATE VIEW pg_stat_all_indexes AS 
    SELECT 
            C.oid AS relid, 
            I.oid AS indexrelid, 
            N.nspname AS schemaname, 
            C.relname AS relname, 
            I.relname AS indexrelname, 
            pg_stat_get_numscans(I.oid) AS idx_scan, 
            pg_stat_get_tuples_returned(I.oid) AS idx_tup_read, 
            pg_stat_get_tuples_fetched(I.oid) AS idx_tup_fetch 
    FROM pg_class C JOIN 
            pg_index X ON C.oid = X.indrelid JOIN 
            pg_class I ON I.oid = X.indexrelid 
            LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) 
    WHERE C.relkind = 'r';

CREATE VIEW pg_stat_sys_indexes AS 
    SELECT * FROM pg_stat_all_indexes 
    WHERE schemaname IN ('pg_catalog', 'pg_toast');

CREATE VIEW pg_stat_user_indexes AS 
    SELECT * FROM pg_stat_all_indexes 
    WHERE schemaname NOT IN ('pg_catalog', 'pg_toast');

CREATE VIEW pg_statio_all_indexes AS 
    SELECT 
            C.oid AS relid, 
            I.oid AS indexrelid, 
            N.nspname AS schemaname, 
            C.relname AS relname, 
            I.relname AS indexrelname, 
            pg_stat_get_blocks_fetched(I.oid) - 
                    pg_stat_get_blocks_hit(I.oid) AS idx_blks_read, 
            pg_stat_get_blocks_hit(I.oid) AS idx_blks_hit 
    FROM pg_class C JOIN 
            pg_index X ON C.oid = X.indrelid JOIN 
            pg_class I ON I.oid = X.indexrelid 
            LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) 
    WHERE C.relkind = 'r';

CREATE VIEW pg_statio_sys_indexes AS 
    SELECT * FROM pg_statio_all_indexes 
    WHERE schemaname IN ('pg_catalog', 'pg_toast');

CREATE VIEW pg_statio_user_indexes AS 
    SELECT * FROM pg_statio_all_indexes 
    WHERE schemaname NOT IN ('pg_catalog', 'pg_toast');

CREATE VIEW pg_statio_all_sequences AS 
    SELECT 
            C.oid AS relid, 
            N.nspname AS schemaname, 
            C.relname AS relname, 
            pg_stat_get_blocks_fetched(C.oid) - 
                    pg_stat_get_blocks_hit(C.oid) AS blks_read, 
            pg_stat_get_blocks_hit(C.oid) AS blks_hit 
    FROM pg_class C 
            LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) 
    WHERE C.relkind = 'S';

CREATE VIEW pg_statio_sys_sequences AS 
    SELECT * FROM pg_statio_all_sequences 
    WHERE schemaname IN ('pg_catalog', 'pg_toast');

CREATE VIEW pg_statio_user_sequences AS 
    SELECT * FROM pg_statio_all_sequences 
    WHERE schemaname NOT IN ('pg_catalog', 'pg_toast');

CREATE VIEW pg_stat_activity AS 
    SELECT 
            D.oid AS datid, 
            D.datname AS datname, 
            pg_stat_get_backend_pid(S.backendid) AS procpid, 
            pg_stat_get_backend_userid(S.backendid) AS usesysid, 
            U.usename AS usename, 
            pg_stat_get_backend_activity(S.backendid) AS current_query, 
            pg_stat_get_backend_activity_start(S.backendid) AS query_start 
    FROM pg_database D, 
            (SELECT pg_stat_get_backend_idset() AS backendid) AS S, 
            pg_shadow U 
    WHERE pg_stat_get_backend_dbid(S.backendid) = D.oid AND 
            pg_stat_get_backend_userid(S.backendid) = U.usesysid;

CREATE VIEW pg_stat_database AS 
    SELECT 
            D.oid AS datid, 
            D.datname AS datname, 
            pg_stat_get_db_numbackends(D.oid) AS numbackends, 
            pg_stat_get_db_xact_commit(D.oid) AS xact_commit, 
            pg_stat_get_db_xact_rollback(D.oid) AS xact_rollback, 
            pg_stat_get_db_blocks_fetched(D.oid) - 
                    pg_stat_get_db_blocks_hit(D.oid) AS blks_read, 
            pg_stat_get_db_blocks_hit(D.oid) AS blks_hit 
    FROM pg_database D;

CREATE VIEW pg_locks AS 
    SELECT * 
    FROM pg_lock_status() AS L(relation oid, database oid, 
	transaction xid, pid int4, mode text, granted boolean);

CREATE VIEW pg_settings AS 
    SELECT * 
    FROM pg_show_all_settings() AS A 
    (name text, setting text, context text, vartype text, 
     source text, min_val text, max_val text);

CREATE RULE pg_settings_u AS 
    ON UPDATE TO pg_settings 
    WHERE new.name = old.name DO 
    SELECT set_config(old.name, new.setting, 'f');

CREATE RULE pg_settings_n AS 
    ON UPDATE TO pg_settings 
    DO INSTEAD NOTHING;



Index: src/bin/initdb/Makefile
===================================================================
RCS file: /cvsroot/pgsql-server/src/bin/initdb/Makefile,v
retrieving revision 1.29
diff -c -c -r1.29 Makefile
*** src/bin/initdb/Makefile	7 Sep 2003 03:36:03 -0000	1.29
--- src/bin/initdb/Makefile	8 Nov 2003 04:57:44 -0000
***************
*** 13,37 ****
  top_builddir = ../../..
  include $(top_builddir)/src/Makefile.global
  
! all: initdb
  
! initdb: initdb.sh $(top_builddir)/src/Makefile.global
! 	sed -e 's/@VERSION@/$(VERSION)/g' \
! 	    -e 's,@SHELL@,$(SHELL),g' \
! 	    -e 's,@HAVE_IPV6@,$(HAVE_IPV6),g' \
! 	    -e 's,@bindir@,$(bindir),g' \
! 	    -e 's,@datadir@,$(datadir),g' \
! 	  $< >$@
! 	chmod a+x $@
  
  install: all installdirs
! 	$(INSTALL_SCRIPT) initdb $(DESTDIR)$(bindir)/initdb
  
  installdirs:
  	$(mkinstalldirs) $(DESTDIR)$(bindir)
  
  uninstall:
! 	rm -f $(DESTDIR)$(bindir)/initdb
  
! clean distclean maintainer-clean:
! 	rm -f initdb
--- 13,42 ----
  top_builddir = ../../..
  include $(top_builddir)/src/Makefile.global
  
! override CPPFLAGS := -DPGBINDIR=\"$(bindir)\" -DPGDATADIR=\"$(datadir)\" -DFRONTEND -I$(libpq_srcdir) $(CPPFLAGS)
  
! OBJS=	initdb.o sprompt.o
! ifeq ($(PORTNAME), win32)
! OBJS+=dirmod.o
! endif
! 
! all: submake-libpq submake-libpgport initdb
! 
! initdb: $(OBJS) $(libpq_builddir)/libpq.a
! 	$(CC) $(CFLAGS) $(OBJS) $(libpq) $(LDFLAGS) $(LIBS) -o $@
! 
! dirmod.c sprompt.c: % : $(top_srcdir)/src/port/%
! 	rm -f $@ && $(LN_S) $< .
  
  install: all installdirs
! 	$(INSTALL_PROGRAM) initdb$(X) $(DESTDIR)$(bindir)/initdb$(X)
! 	$(INSTALL_DATA) $(srcdir)/system_views.sql $(DESTDIR)$(datadir)/system_views.sql
  
  installdirs:
  	$(mkinstalldirs) $(DESTDIR)$(bindir)
  
  uninstall:
! 	rm -f $(DESTDIR)$(bindir)/initdb$(X) $(DESTDIR)$(datadir)/system_views.sql
  
! clean distclean:
! 	rm -f initdb$(X) $(OBJS)

*** /laptop/initdb.c	Sat Nov  8 00:10:58 2003
--- initdb.c	Fri Nov  7 23:55:07 2003
***************
*** 1,4 ****
- 
  /*-------------------------------------------------------------------------
   *
   * initdb
--- 1,3 ----
***************
*** 53,59 ****
  char	   *datadir = PGDATADIR;
  
  /* values to be obtained from arguments */
- 
  char	   *pg_data = "";
  char	   *encoding = "";
  char	   *locale = "";
--- 52,57 ----
***************
*** 93,100 ****
  bool		not_ok = false;
  
  /* defaults */
- int			n_buffers = 50;
  int			n_connections = 10;
  
  
  /* platform specific path stuff */
--- 91,98 ----
  bool		not_ok = false;
  
  /* defaults */
  int			n_connections = 10;
+ int			n_buffers = 50;
  
  
  /* platform specific path stuff */
***************
*** 104,116 ****
  #else
  #define EXE ""
  #define DEVNULL "/dev/null"
! #endif   /* defined(__CYGWIN__) || defined(WIN32) */
  
  #ifdef WIN32
  #define PATHSEP ';'
  #else
  #define PATHSEP ':'
! #endif   /* WIN32 */
  
  /* detected path to postgres and (we assume) friends */
  char	   *pgpath;
--- 102,114 ----
  #else
  #define EXE ""
  #define DEVNULL "/dev/null"
! #endif
  
  #ifdef WIN32
  #define PATHSEP ';'
  #else
  #define PATHSEP ':'
! #endif
  
  /* detected path to postgres and (we assume) friends */
  char	   *pgpath;
***************
*** 122,140 ****
  
  #ifdef WIN32
  static char *expanded_path(char *);
- static int	init_unlink(const char *);
- 
  #else
! #define expanded_path(x) ( x )
! #define init_unlink(x) unlink( (x) )
! #endif   /* WIN32 */
  
  static char **readfile(char *);
  static void writefile(char *, char **);
  static char *get_id(void);
  static char *get_encoding_id(char *);
  static char *get_short_version(void);
! static int	build_path(char *, mode_t);
  static bool check_data_dir(void);
  static bool mkdatadir(char *);
  static bool chklocale(const char *);
--- 120,135 ----
  
  #ifdef WIN32
  static char *expanded_path(char *);
  #else
! #define expanded_path(x) (x)
! #endif
  
  static char **readfile(char *);
  static void writefile(char *, char **);
  static char *get_id(void);
  static char *get_encoding_id(char *);
  static char *get_short_version(void);
! static int	mkdir_p(char *, mode_t);
  static bool check_data_dir(void);
  static bool mkdatadir(char *);
  static bool chklocale(const char *);
***************
*** 151,157 ****
  static void setup_config(void);
  static void bootstrap_template1(char *);
  static void setup_shadow(void);
! static void get_set_pw(void);
  static void unlimit_systables(void);
  static void setup_depend(void);
  static void setup_sysviews(void);
--- 146,152 ----
  static void setup_config(void);
  static void bootstrap_template1(char *);
  static void setup_shadow(void);
! static void get_set_pwd(void);
  static void unlimit_systables(void);
  static void setup_depend(void);
  static void setup_sysviews(void);
***************
*** 171,230 ****
  /*
   * macros for running pipes to postgres
   */
- 
  #define PG_CMD_DECL		char cmd[MAXPGPATH]; char ** line ; FILE * pg
  #define PG_CMD_DECL_NOLINE		   char cmd[MAXPGPATH]; FILE * pg
- #define PG_CMD_OPEN		\
- 	do {\
- 		  pg = popen(cmd,PG_BINARY_W);\
- 		  if (pg == NULL) \
- 			exit_nicely();\
- 	} while (0)
- #define PG_CMD_CLOSE	\
- 	do {\
- 		 if(pclose(pg)>>8 &0xff)\
- 			exit_nicely();\
- 	} while (0)
- #define PG_CMD_PUTLINE	\
- 	do {\
- 		 if (fputs(*line, pg) < 0) \
- 		   exit_nicely(); \
- 		 fflush(pg);\
- 	} while (0)
- 
- 
  
  
! #ifdef WIN32
! 
! /* workaround for win32 unlink bug, not using logging like in port/dirmod.c */
! 
! /* make sure we call the real unlink from MSVCRT */
  
! #ifdef unlink
! #undef unlink
  #endif
  
- static int
- init_unlink(const char *path)
- {
- 	while (unlink(path))
- 	{
- 		if (errno != EACCES)
- 			return -1;
- 		Sleep(100);				/* ms */
- 	}
- 	return 0;
- }
- #endif   /* WIN32 */
- 
  /*
!  * routines to check mem allocations and fail noisily
   * Note that we can't call exit_nicely() on a memory failure, as it calls
   * rmtree() which needs memory allocation. So we just exit with a bang.
   *
   */
- 
  static void *
  xmalloc(size_t size)
  {
--- 166,206 ----
  /*
   * macros for running pipes to postgres
   */
  #define PG_CMD_DECL		char cmd[MAXPGPATH]; char ** line ; FILE * pg
  #define PG_CMD_DECL_NOLINE		   char cmd[MAXPGPATH]; FILE * pg
  
+ #define PG_CMD_OPEN \
+ do { \
+ 		  pg = popen(cmd,PG_BINARY_W); \
+ 		  if (pg == NULL)  \
+ 			exit_nicely(); \
+ } while (0)
+ 
+ #define PG_CMD_CLOSE \
+ do { \
+ 		 if(pclose(pg) >> 8 & 0xff) \
+ 			exit_nicely(); \
+ } while (0)
  
! #define PG_CMD_PUTLINE \
! do { \
! 		 if (fputs(*line, pg) < 0) \
! 		   exit_nicely(); \
! 		 fflush(pg); \
! } while (0)
  
! #ifndef WIN32
! #define QUOTE_PATH	""
! #else
! #define QUOTE_PATH	"\""
  #endif
  
  /*
!  * routines to check mem allocations and fail noisily.
   * Note that we can't call exit_nicely() on a memory failure, as it calls
   * rmtree() which needs memory allocation. So we just exit with a bang.
   *
   */
  static void *
  xmalloc(size_t size)
  {
***************
*** 263,344 ****
  static bool
  rmtree(char *path, bool rmtopdir)
  {
! 	char		filepath[MAXPGPATH];
! 	DIR		   *dir;
! 	struct dirent *file;
! 	char	  **filenames;
! 	char	  **filename;
! 	int			numnames = 0;
! 	struct stat statbuf;
  
! 	/*
! 	 * we copy all the names out of the directory before we start
! 	 * modifying it.
! 	 *
! 	 */
! 
! 	dir = opendir(path);
! 	if (dir == NULL)
! 		return false;
! 
! 	while ((file = readdir(dir)) != NULL)
! 	{
! 		if (strcmp(file->d_name, ".") != 0 && strcmp(file->d_name, "..") != 0)
! 			numnames++;
! 	}
! 
! 	rewinddir(dir);
! 
! 	filenames = xmalloc((numnames + 2) * sizeof(char *));
! 	numnames = 0;
! 
! 	while ((file = readdir(dir)) != NULL)
! 	{
! 		if (strcmp(file->d_name, ".") != 0 && strcmp(file->d_name, "..") != 0)
! 			filenames[numnames++] = xstrdup(file->d_name);
! 	}
! 
! 	filenames[numnames] = NULL;
! 
! 	closedir(dir);
! 
! 	/* now we have the names we can start removing things */
! 
! 	for (filename = filenames; *filename; filename++)
! 	{
! 		snprintf(filepath, MAXPGPATH, "%s/%s", path, *filename);
! 
! 		if (stat(filepath, &statbuf) != 0)
! 			return false;
! 
! 		if (S_ISDIR(statbuf.st_mode))
! 		{
! 			/* call ourselves recursively for a directory */
! 			if (!rmtree(filepath, true))
! 				return false;
! 		}
! 		else
! 		{
! 			if (init_unlink(filepath) != 0)
! 				return false;
! 		}
! 	}
  
! 	if (rmtopdir)
! 	{
! 		if (rmdir(path) != 0)
! 			return false;
! 	}
  
! 	return true;
  }
  
  
  /*
   * make all paths look like unix, with forward slashes
   * also strip any trailing slash
   */
- 
  static void
  canonicalise_path(char *path)
  {
--- 239,271 ----
  static bool
  rmtree(char *path, bool rmtopdir)
  {
! 	char		buf[MAXPGPATH + 64];
! 	int			ret = 1;
  
! #ifndef WIN32
! 	/* doesn't handle .* files */
! 	snprintf(buf, sizeof(buf), "rm -rf '%s%s'", path,
! 			 (rmtopdir) ? "" : "/*");
! #else
! 	snprintf(buf, sizeof(buf), "rmdir /s /q \"%s\"", path);
! #endif
  
! 	if (system(buf) != 0)
! 		ret = 0;
  
! #ifdef WIN32
! 	if (ret == 1 && !rmtopdir)
! 		ret = !mkdir(path);		/* recreate, no permission issues? */
! #endif
! 	return ret;
  }
  
  
  /*
   * make all paths look like unix, with forward slashes
   * also strip any trailing slash
+  * needed?
   */
  static void
  canonicalise_path(char *path)
  {
***************
*** 349,355 ****
  #ifdef WIN32
  		if (*p == '\\')
  			*p = '/';
! #endif   /* WIN32 */
  	}
  	if (p != path && *--p == '/')
  		*p = '\0';
--- 276,282 ----
  #ifdef WIN32
  		if (*p == '\\')
  			*p = '/';
! #endif
  	}
  	if (p != path && *--p == '/')
  		*p = '\0';
***************
*** 361,367 ****
   * This does most of what sed was used for in the shell script, but
   * doesn't need any regexp stuff.
   */
- 
  static char **
  replace_token(char **lines, char *token, char *replacement)
  {
--- 288,293 ----
***************
*** 372,378 ****
  				replen,
  				diff;
  
- 
  	for (i = 0; lines[i]; i++)
  		numlines++;
  
--- 298,303 ----
***************
*** 431,439 ****
  	char	   *buffer;
  	int			c;
  
! 	infile = fopen(path, "r");
! 
! 	if (!infile)
  	{
  		fprintf(stderr, "could not read %s ... ", path);
  		exit_nicely();
--- 356,362 ----
  	char	   *buffer;
  	int			c;
  
! 	if ((infile = fopen(path, "r")) == NULL)
  	{
  		fprintf(stderr, "could not read %s ... ", path);
  		exit_nicely();
***************
*** 491,498 ****
  	FILE	   *out_file;
  	char	  **line;
  
! 	out_file = fopen(path, PG_BINARY_W);
! 	if (out_file == NULL)
  	{
  		fprintf(stderr, "could not write %s ... ", path);
  		exit_nicely();
--- 414,421 ----
  	FILE	   *out_file;
  	char	  **line;
  
! 	;
! 	if ((out_file = fopen(path, PG_BINARY_W)) == NULL)
  	{
  		fprintf(stderr, "could not write %s ... ", path);
  		exit_nicely();
***************
*** 515,523 ****
   * we also assume it isn't null.
   *
   */
- 
  static int
! build_path(char *path, mode_t omode)
  {
  	struct stat sb;
  	mode_t		numask,
--- 438,445 ----
   * we also assume it isn't null.
   *
   */
  static int
! mkdir_p(char *path, mode_t omode)
  {
  	struct stat sb;
  	mode_t		numask,
***************
*** 532,538 ****
  	retval = 0;
  
  #ifdef WIN32
- 
  	/* skip network and drive specifiers for win32 */
  	if (strlen(p) >= 2)
  	{
--- 454,459 ----
***************
*** 551,557 ****
  			p += 2;
  		}
  	}
! #endif   /* WIN32 */
  
  	if (p[0] == '/')			/* Skip leading '/'. */
  		++p;
--- 472,478 ----
  			p += 2;
  		}
  	}
! #endif
  
  	if (p[0] == '/')			/* Skip leading '/'. */
  		++p;
***************
*** 614,620 ****
  	}
  	if (!first && !last)
  		(void) umask(oumask);
! 	return (retval);
  }
  
  /*
--- 535,541 ----
  	}
  	if (!first && !last)
  		(void) umask(oumask);
! 	return retval;
  }
  
  /*
***************
*** 673,679 ****
  				progname);
  		exit(1);
  	}
! #endif   /* __BEOS__ */
  
  #else							/* the windows code */
  
--- 594,600 ----
  				progname);
  		exit(1);
  	}
! #endif
  
  #else							/* the windows code */
  
***************
*** 687,693 ****
  
  	pw->pw_uid = 1;
  	GetUserName(pw->pw_name, &pwname_size);
! #endif   /* ! WIN32 */
  
  	return xstrdup(pw->pw_name);
  }
--- 608,614 ----
  
  	pw->pw_uid = 1;
  	GetUserName(pw->pw_name, &pwname_size);
! #endif
  
  	return xstrdup(pw->pw_name);
  }
***************
*** 785,791 ****
  
  	closedir(chkdir);
  
! 	return (empty);
  }
  
  /*
--- 706,712 ----
  
  	closedir(chkdir);
  
! 	return empty;
  }
  
  /*
***************
*** 812,818 ****
  	else if (subdir == NULL || errno != ENOENT)
  		return false;
  	else
! 		return !build_path(path, 0700);
  }
  
  
--- 733,739 ----
  	else if (subdir == NULL || errno != ENOENT)
  		return false;
  	else
! 		return !mkdir_p(path, 0700);
  }
  
  
***************
*** 853,859 ****
   * don't overkill
   *
   */
- 
  #define FIND_SUCCESS 0
  #define FIND_NOT_FOUND 1
  #define FIND_STAT_ERR 2
--- 774,779 ----
***************
*** 875,885 ****
  	char		line[100];
  
  #ifndef WIN32
- 
  	int			permmask = S_IROTH | S_IXOTH;
  #endif
  
- 
  	struct stat statbuf;
  	FILE	   *pgver;
  	int			plen = strlen(path);
--- 795,803 ----
***************
*** 900,911 ****
  		return FIND_NOT_REGFILE;
  
  #ifndef WIN32
! 
! 	/* on windows a .exe file should be executable - this is the unix test */
! 
  	if ((statbuf.st_mode & permmask) != permmask)
  		return FIND_BAD_PERM;
! #endif   /* ! WIN32 */
  
  	snprintf(cmd, MAXPGPATH, "\"%s/postgres\" -V 2>%s", path, DEVNULL);
  
--- 818,830 ----
  		return FIND_NOT_REGFILE;
  
  #ifndef WIN32
! 	/*
! 	 * Only unix requires this test, on WIN32 an .exe file should be
! 	 * executable
! 	 */
  	if ((statbuf.st_mode & permmask) != permmask)
  		return FIND_BAD_PERM;
! #endif
  
  	snprintf(cmd, MAXPGPATH, "\"%s/postgres\" -V 2>%s", path, DEVNULL);
  
***************
*** 913,923 ****
  		return FIND_EXEC_ERR;
  
  	if (fgets(line, sizeof(line), pgver) == NULL)
- 	{
  		perror("fgets failure");
  
- 	}
- 
  	pclose(pgver);
  
  	if (strcmp(line, PG_VERSIONSTR) != 0)
--- 832,839 ----
***************
*** 926,939 ****
  	return FIND_SUCCESS;
  }
  
- #ifdef WIN32
- 
  /*
   * Windows doesn't like relative paths to executables (other things work fine)
   * so we call its builtin function to expand them. Elsewhere this is a NOOP
   *
   */
! 
  static char *
  expanded_path(char *path)
  {
--- 842,853 ----
  	return FIND_SUCCESS;
  }
  
  /*
   * Windows doesn't like relative paths to executables (other things work fine)
   * so we call its builtin function to expand them. Elsewhere this is a NOOP
   *
   */
! #ifdef WIN32
  static char *
  expanded_path(char *path)
  {
***************
*** 947,953 ****
  	canonicalise_path(abspath);
  	return xstrdup(abspath);
  }
! #endif   /* WIN32 */
  
  /*
   * set the paths pointing to postgres
--- 861,867 ----
  	canonicalise_path(abspath);
  	return xstrdup(abspath);
  }
! #endif 
  
  /*
   * set the paths pointing to postgres
***************
*** 1078,1140 ****
  }
  
  /*
!  * check how many buffers we can run with
   *
   */
  static void
! test_buffers(void)
  {
  	char	   *format =
! 	"\"%s/postgres\"  -boot -x 0 -F "
! 	"-c shared_buffers=%d -c max_connections=5 template1 <%s >%s 2>&1";
  	char		cmd[MAXPGPATH];
! 	int			bufs[] =
! 	{1000, 900, 800, 700, 600, 500, 400, 300, 200, 100, 50};
! 	int			len = sizeof(bufs) / sizeof(int);
  	int			i,
  				status;
  
  	for (i = 0; i < len; i++)
  	{
! 		snprintf(cmd, sizeof(cmd), format, pgpath, bufs[i], DEVNULL, DEVNULL);
  		status = system(cmd);
  		if (status == 0)
  			break;
  	}
  	if (i >= len)
  		i = len - 1;
! 	n_buffers = bufs[i];
! 	printf("buffers set to %d\n", n_buffers);
  }
  
  /*
!  * check how many connections we can sustain
   *
   */
  static void
! test_connections(void)
  {
  	char	   *format =
! 	"\"%s/postgres\" -boot -x 0 -F "
  	"-c shared_buffers=%d -c max_connections=%d template1 <%s >%s 2>&1";
  	char		cmd[MAXPGPATH];
! 	int			conns[] = {100, 50, 40, 30, 20, 10};
! 	int			len = sizeof(conns) / sizeof(int);
  	int			i,
  				status;
  
  	for (i = 0; i < len; i++)
  	{
! 		snprintf(cmd, sizeof(cmd), format,
! 				 pgpath, n_buffers, conns[i], DEVNULL, DEVNULL);
  		status = system(cmd);
  		if (status == 0)
  			break;
  	}
  	if (i >= len)
  		i = len - 1;
! 	n_connections = conns[i];
! 	printf("connections set to %d\n", n_connections);
  }
  
  /*
--- 992,1054 ----
  }
  
  /*
!  * check how many connections we can sustain
   *
   */
  static void
! test_connections(void)
  {
  	char	   *format =
! 	"\"%s/postgres\" -boot -x 0 -F "
! 	"-c shared_buffers=%d -c max_connections=%d template1 <%s >%s 2>&1";
  	char		cmd[MAXPGPATH];
! 	int			conns[] = {100, 50, 40, 30, 20, 10};
! 	int			len = sizeof(conns) / sizeof(int);
  	int			i,
  				status;
  
  	for (i = 0; i < len; i++)
  	{
! 		snprintf(cmd, sizeof(cmd), format,
! 				 pgpath, conns[i] * 5, conns[i], DEVNULL, DEVNULL);
  		status = system(cmd);
  		if (status == 0)
  			break;
  	}
  	if (i >= len)
  		i = len - 1;
! 	n_connections = conns[i];
! 	printf("connections set to %d\n", n_connections);
  }
  
  /*
!  * check how many buffers we can run with
   *
   */
  static void
! test_buffers(void)
  {
  	char	   *format =
! 	"\"%s/postgres\"  -boot -x 0 -F "
  	"-c shared_buffers=%d -c max_connections=%d template1 <%s >%s 2>&1";
  	char		cmd[MAXPGPATH];
! 	int			bufs[] = {1000, 900, 800, 700, 600, 500, 400, 300, 200, 100, 50};
! 	int			len = sizeof(bufs) / sizeof(int);
  	int			i,
  				status;
  
  	for (i = 0; i < len; i++)
  	{
! 		snprintf(cmd, sizeof(cmd), format, pgpath, bufs[i], n_connections,
! 			DEVNULL, DEVNULL);
  		status = system(cmd);
  		if (status == 0)
  			break;
  	}
  	if (i >= len)
  		i = len - 1;
! 	n_buffers = bufs[i];
! 	printf("buffers set to %d\n", n_buffers);
  }
  
  /*
***************
*** 1155,1166 ****
  
  	conflines = readfile(conf_file);
  
- 	snprintf(repltok, sizeof(repltok), "shared_buffers = %d", n_buffers);
- 	conflines = replace_token(conflines, "#shared_buffers = 1000", repltok);
- 
  	snprintf(repltok, sizeof(repltok), "max_connections = %d", n_connections);
  	conflines = replace_token(conflines, "#max_connections = 100", repltok);
  
  	snprintf(repltok, sizeof(repltok), "lc_messages = '%s'", lc_messages);
  	conflines = replace_token(conflines, "#lc_messages = 'C'", repltok);
  
--- 1069,1080 ----
  
  	conflines = readfile(conf_file);
  
  	snprintf(repltok, sizeof(repltok), "max_connections = %d", n_connections);
  	conflines = replace_token(conflines, "#max_connections = 100", repltok);
  
+ 	snprintf(repltok, sizeof(repltok), "shared_buffers = %d", n_buffers);
+ 	conflines = replace_token(conflines, "#shared_buffers = 1000", repltok);
+ 
  	snprintf(repltok, sizeof(repltok), "lc_messages = '%s'", lc_messages);
  	conflines = replace_token(conflines, "#lc_messages = 'C'", repltok);
  
***************
*** 1190,1196 ****
  	conflines = replace_token(conflines,
  							  "host    all         all         ::1",
  							  "#host    all         all         ::1");
! #endif   /* ! HAVE_IPV6 */
  
  	snprintf(path, MAXPGPATH, "%s/pg_hba.conf", pg_data);
  
--- 1104,1110 ----
  	conflines = replace_token(conflines,
  							  "host    all         all         ::1",
  							  "#host    all         all         ::1");
! #endif
  
  	snprintf(path, MAXPGPATH, "%s/pg_hba.conf", pg_data);
  
***************
*** 1211,1217 ****
  	free(conflines);
  
  	check_ok();
- 
  }
  
  
--- 1125,1130 ----
***************
*** 1258,1264 ****
  	 * already called setlocale().
  	 *
  	 */
- 
  	snprintf(cmd, MAXPGPATH, "LC_COLLATE=%s", lc_collate);
  	putenv(xstrdup(cmd));
  
--- 1171,1176 ----
***************
*** 1337,1359 ****
   *
   */
  static void
! get_set_pw(void)
  {
  	PG_CMD_DECL_NOLINE;
  
! 	char	   *pw1,
! 			   *pw2;
! 	char		pwpath[MAXPGPATH];
  	struct stat statbuf;
  
! 	pw1 = simple_prompt("Enter new superuser password: ", 100, false);
! 	pw2 = simple_prompt("Enter it again: ", 100, false);
! 	if (strcmp(pw1, pw2) != 0)
  	{
  		fprintf(stderr, "Passwords didn't match.\n");
  		exit_nicely();
  	}
! 	free(pw2);
  
  	printf("storing the password ... ");
  
--- 1249,1271 ----
   *
   */
  static void
! get_set_pwd(void)
  {
  	PG_CMD_DECL_NOLINE;
  
! 	char	   *pwd1,
! 			   *pwd2;
! 	char		pwdpath[MAXPGPATH];
  	struct stat statbuf;
  
! 	pwd1 = simple_prompt("Enter new superuser password: ", 100, false);
! 	pwd2 = simple_prompt("Enter it again: ", 100, false);
! 	if (strcmp(pwd1, pwd2) != 0)
  	{
  		fprintf(stderr, "Passwords didn't match.\n");
  		exit_nicely();
  	}
! 	free(pwd2);
  
  	printf("storing the password ... ");
  
***************
*** 1365,1371 ****
  	PG_CMD_OPEN;
  
  	if (fprintf(
! 	   pg, "ALTER USER \"%s\" WITH PASSWORD '%s';\n", username, pw1) < 0)
  	{
  		/* write failure */
  		exit_nicely();
--- 1277,1283 ----
  	PG_CMD_OPEN;
  
  	if (fprintf(
! 	   pg, "ALTER USER \"%s\" WITH PASSWORD '%s';\n", username, pwd1) < 0)
  	{
  		/* write failure */
  		exit_nicely();
***************
*** 1374,1381 ****
  
  	PG_CMD_CLOSE;
  
! 	snprintf(pwpath, MAXPGPATH, "%s/global/pg_pwd", pg_data);
! 	if (stat(pwpath, &statbuf) != 0 || !S_ISREG(statbuf.st_mode))
  	{
  		fprintf(stderr,
  				"%s: The password file was not generated - "
--- 1286,1293 ----
  
  	PG_CMD_CLOSE;
  
! 	snprintf(pwdpath, MAXPGPATH, "%s/global/pg_pwd", pg_data);
! 	if (stat(pwdpath, &statbuf) != 0 || !S_ISREG(statbuf.st_mode))
  	{
  		fprintf(stderr,
  				"%s: The password file was not generated - "
***************
*** 1898,1904 ****
   * So this will need some testing on Windows.
   *
   */
- 
  static void
  trapsig(int signum)
  {
--- 1810,1815 ----
***************
*** 2009,2015 ****
  		/* when not available, get the CTYPE setting */
  		lc_messages = xstrdup(setlocale(LC_CTYPE, NULL));
  	}
! #endif   /* LC_MESSAGES */
  
  }
  
--- 1920,1926 ----
  		/* when not available, get the CTYPE setting */
  		lc_messages = xstrdup(setlocale(LC_CTYPE, NULL));
  	}
! #endif
  
  }
  
***************
*** 2017,2023 ****
   * help text data
   *
   */
- 
  char	   *usage_text[] = {
  	"$CMDNAME initializes a PostgreSQL database cluster.\n",
  	"\n",
--- 1928,1933 ----
***************
*** 2117,2123 ****
  
  #if defined(__CYGWIN__) || defined(WIN32)
  	char	   *exe;			/* location of exe suffix in progname */
! #endif   /* defined(__CYGWIN__) || defined(WIN32) */
  
  	setlocale(LC_ALL, "");
  
--- 2027,2033 ----
  
  #if defined(__CYGWIN__) || defined(WIN32)
  	char	   *exe;			/* location of exe suffix in progname */
! #endif
  
  	setlocale(LC_ALL, "");
  
***************
*** 2135,2141 ****
  		/* strip .exe suffix, regardless of case */
  		*exe = '\0';
  	}
! #endif   /* defined(__CYGWIN__) || defined(WIN32) */
  
  	if (lastsep)
  	{
--- 2045,2051 ----
  		/* strip .exe suffix, regardless of case */
  		*exe = '\0';
  	}
! #endif
  
  	if (lastsep)
  	{
***************
*** 2317,2323 ****
  	set_input(&features_file, "sql_features.txt");
  	set_input(&system_views_file, "system_views.sql");
  
- 
  	if (show_setting || debug)
  	{
  		fprintf(stderr,
--- 2227,2232 ----
***************
*** 2335,2345 ****
  				hba_file, ident_file);
  	}
  
- 
  	if (show_setting)
  		exit(0);
  
- 
  	check_input(bki_file);
  	check_input(desc_file);
  	check_input(hba_file);
--- 2244,2252 ----
***************
*** 2388,2404 ****
  	/* some of these are not valid on Windows */
  #ifdef SIGHUP
  	pqsignal(SIGHUP, trapsig);
! #endif   /* SIGHUP */
  #ifdef SIGINT
  	pqsignal(SIGINT, trapsig);
! #endif   /* SIGINT */
  #ifdef SIGQUIT
  	pqsignal(SIGQUIT, trapsig);
! #endif   /* SIGQUIT */
  #ifdef SIGTERM
  	pqsignal(SIGTERM, trapsig);
! #endif   /* SIGTERM */
! 
  
  	/* clear this we'll use it in a few lines */
  	errno = 0;
--- 2295,2310 ----
  	/* some of these are not valid on Windows */
  #ifdef SIGHUP
  	pqsignal(SIGHUP, trapsig);
! #endif
  #ifdef SIGINT
  	pqsignal(SIGINT, trapsig);
! #endif
  #ifdef SIGQUIT
  	pqsignal(SIGQUIT, trapsig);
! #endif
  #ifdef SIGTERM
  	pqsignal(SIGTERM, trapsig);
! #endif
  
  	/* clear this we'll use it in a few lines */
  	errno = 0;
***************
*** 2420,2426 ****
  	 * check_data_dir() called opendir - the errno should still be hanging
  	 * around
  	 */
- 
  	if (errno == ENOENT)
  	{
  		printf("creating directory \"%s\" ... ", pg_data);
--- 2326,2331 ----
***************
*** 2447,2455 ****
  
  	set_null_conf();
  
! 	test_buffers();
! 
  	test_connections();
  
  	setup_config();
  
--- 2352,2360 ----
  
  	set_null_conf();
  
! 	/* test connections first because it has more constraints */
  	test_connections();
+ 	test_buffers();
  
  	setup_config();
  
***************
*** 2458,2466 ****
  	set_short_version(short_version, "base/1");
  
  	setup_shadow();
- 
  	if (pwprompt)
! 		get_set_pw();
  
  	unlimit_systables();
  
--- 2363,2370 ----
  	set_short_version(short_version, "base/1");
  
  	setup_shadow();
  	if (pwprompt)
! 		get_set_pwd();
  
  	unlimit_systables();
  
***************
*** 2481,2493 ****
  	make_template0();
  
  	printf("\n"
! 		   "Success. You can now start the database server using:\n"
! 		   "\n"
! 		   "    \"%s/postmaster\" -D \"%s\"\n"
  		   "or\n"
! 		   "    \"%s/pg_ctl\" -D \"%s\" -l logfile start\n"
! 		   "\n",
! 		   pgpath, pg_data, pgpath, pg_data);
  
  	return 0;
  }
--- 2385,2396 ----
  	make_template0();
  
  	printf("\n"
! 		   "Success. You can now start the database server using:\n\n"
! 		   "    %s/postmaster -D %s%s%s\n"
  		   "or\n"
! 		   "    %s/pg_ctl -D %s%s%s -l logfile start\n\n",
! 			pgpath, QUOTE_PATH, pg_data, QUOTE_PATH,
! 			pgpath, QUOTE_PATH, pg_data, QUOTE_PATH);
  
  	return 0;
  }
Sat, 8 Nov 2003 00:15:42 -0500 (EST)
Bruce Momjian <pgman@candle.pha.pa.us>
initdb in C
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> It passes all the regression tests.  I have also included a diff against
> Andrew's version so you can see my changes.  It seems Andrew had a very
> current version of initdb.  The only update he missed was the change to
> test the number of connections before shared buffers --- I made that
> change myself.

I had some concern about whether Andrew's rewrite had tracked all the
recent changes to the initdb shell-script sources.  Are you both
confident that it is up to date?

			regards, tom lane

Sat, 08 Nov 2003 00:42:36 -0500
Tom Lane <tgl@sss.pgh.pa.us>
Re: [PATCHES] initdb in C 


Tom Lane wrote:

>Bruce Momjian <pgman@candle.pha.pa.us> writes:
>  
>
>>It passes all the regression tests.  I have also included a diff against
>>Andrew's version so you can see my changes.  It seems Andrew had a very
>>current version of initdb.  The only update he missed was the change to
>>test the number of connections before shared buffers --- I made that
>>change myself.
>>    
>>
>
>I had some concern about whether Andrew's rewrite had tracked all the
>recent changes to the initdb shell-script sources.  Are you both
>confident that it is up to date?
>
>  
>

Yes. Bruce has picked up the one change I didn't track (revision 1.204).


cheers

andrew


Sat, 08 Nov 2003 03:37:53 -0500
Andrew Dunstan <andrew@dunslane.net>
Re: [PATCHES] initdb in C


Bruce Momjian wrote:

>It passes all the regression tests.  I have also included a diff against
>Andrew's version so you can see my changes.  It seems Andrew had a very
>current version of initdb.  The only update he missed was the change to
>test the number of connections before shared buffers --- I made that
>change myself.
>
>I would like to apply this in the next few days to HEAD before initdb.sh
>drifts.  We are no longer using the WIN32_DEV branch because all our
>work is now in 7.5 head.
>
>  
>

I will double check this over the weekend.

cheers

andrew


Sat, 08 Nov 2003 03:46:11 -0500
Andrew Dunstan <andrew@dunslane.net>
Re: initdb in C


Bruce Momjian wrote:

>Here is a slightly modified version of Andrew's great work in making a C
>version of initdb.  Other than minor cleanups, the only big change was
>to remove rmdir handling because we using rm -r and rmdir /s in
>commands/dbcommands.c, so we might as use the same thing for initdb.c
>rather than having code that traverses the directory tree doing 'rm'.
>
>The other change was to remove the unlink code and instead use
>port/dirmod.c's version.
>
>It passes all the regression tests.  I have also included a diff against
>Andrew's version so you can see my changes.  It seems Andrew had a very
>current version of initdb.  The only update he missed was the change to
>test the number of connections before shared buffers --- I made that
>change myself.
>
>I would like to apply this in the next few days to HEAD before initdb.sh
>drifts.  We are no longer using the WIN32_DEV branch because all our
>work is now in 7.5 head.
>  
>

My comments:

I have no problem with shelling out to rmdir - although my goal was to 
avoid shelling out to anything other than postgres itself. I think 
recreating the datadir if we didn't create it initially should be OK in 
that case, and it makes the code simpler. Not handling dot files in the 
Unix case should also be fine, as we know the directory was empty to 
start with and we don't create any.

Regarding the #endif comments you removed - Peter had asked me to put 
them in. See 
http://archives.postgresql.org/pgsql-hackers/2003-10/msg00352.php

You put a comment on canonicalise_path() asking if it is needed. The 
short answer is very much "yes". The Windows command processor will 
accept suitably quoted paths with forward slashes, but barfs badly with 
mixed forward and back slashes. (See recent discussion on 
hackers-win32).Removing the trailing slash on a path means we never get 
ugly double slashes. Feel free to put that info in as a comment if you 
think it is needed.

The changes you made for the final message are not going to work for 
Windows - if pgpath or pg_data contain spaces we need quotes (even on 
Unix, although there most people know better than to put spaces in 
names). Also see above about mixed slashes. Also, putting a \ before 
pg_data will be nasty if it starts with a drive or network specifier - 
or even without (you might turn a directory specifier into a network 
drive specifier). It's just a message, though, and we shouldn't hold up 
for that - we can patch it to get it right.

(Getting the path and slash thing working portably was by far the 
biggest headache in this project - the rest was just grunt work for the 
most part, although I learned a few interesting things.)

Otherwise I'm fine with this.

cheers

andrew




Sat, 08 Nov 2003 10:15:26 -0500
Andrew Dunstan <andrew@dunslane.net>
Re: initdb in C
Andrew Dunstan wrote:
> 
> 
> Tom Lane wrote:
> 
> >Bruce Momjian <pgman@candle.pha.pa.us> writes:
> >  
> >
> >>It passes all the regression tests.  I have also included a diff against
> >>Andrew's version so you can see my changes.  It seems Andrew had a very
> >>current version of initdb.  The only update he missed was the change to
> >>test the number of connections before shared buffers --- I made that
> >>change myself.
> >>    
> >>
> >
> >I had some concern about whether Andrew's rewrite had tracked all the
> >recent changes to the initdb shell-script sources.  Are you both
> >confident that it is up to date?
> >
> >  
> >
> 
> Yes. Bruce has picked up the one change I didn't track (revision 1.204).

Yes, I was concerned too that everything was in there.  I checked the
initdb.sh logs and found that the only thing not added was the checking
of the max number of connections before checking the max number of
buffers, which I added.  The other stuff was in there.  I also checked
pg_id's recent changes and those were in there too.

Andrew, I assume this was a new implementation of initdb, and not taken
from an older initdb C implementation made by a company.

This isn't really a patch but a C replacement of a critical shell
script so there is reason to double-check things.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Sat, 8 Nov 2003 10:39:38 -0500 (EST)
Bruce Momjian <pgman@candle.pha.pa.us>
Re: [PATCHES] initdb in C


Bruce Momjian wrote:

>Yes, I was concerned too that everything was in there.  I checked the
>initdb.sh logs and found that the only thing not added was the checking
>of the max number of connections before checking the max number of
>buffers, which I added.  The other stuff was in there.  I also checked
>pg_id's recent changes and those were in there too.
>
>Andrew, I assume this was a new implementation of initdb, and not taken
>from an older initdb C implementation made by a company.
>
>This isn't really a patch but a C replacement of a critical shell
>script so there is reason to double-check things.
>  
>


Yes, I worked from initdb.sh, not from any other source. It's "all my 
own work" :-) I think I started with 1.201 and later upgraded to 1.203.

I agree it needs careful checking - the more eyeballs the better.

cheers

andrew


Sat, 08 Nov 2003 10:55:52 -0500
Andrew Dunstan <andrew@dunslane.net>
Re: [PATCHES] initdb in C
Andrew Dunstan wrote:
> 
> 
> Bruce Momjian wrote:
> 
> >Here is a slightly modified version of Andrew's great work in making a C
> >version of initdb.  Other than minor cleanups, the only big change was
> >to remove rmdir handling because we using rm -r and rmdir /s in
> >commands/dbcommands.c, so we might as use the same thing for initdb.c
> >rather than having code that traverses the directory tree doing 'rm'.
> >
> >The other change was to remove the unlink code and instead use
> >port/dirmod.c's version.
> >
> >It passes all the regression tests.  I have also included a diff against
> >Andrew's version so you can see my changes.  It seems Andrew had a very
> >current version of initdb.  The only update he missed was the change to
> >test the number of connections before shared buffers --- I made that
> >change myself.
> >
> >I would like to apply this in the next few days to HEAD before initdb.sh
> >drifts.  We are no longer using the WIN32_DEV branch because all our
> >work is now in 7.5 head.
> >  
> >
> 
> My comments:
> 
> I have no problem with shelling out to rmdir - although my goal was to 
> avoid shelling out to anything other than postgres itself. I think 
> recreating the datadir if we didn't create it initially should be OK in 
> that case, and it makes the code simpler. Not handling dot files in the 
> Unix case should also be fine, as we know the directory was empty to 
> start with and we don't create any.

Good. I can do rmdir() in C in port/dirmod.c if we need it.  Right now
we are doing system(rm/rmdir) in dbcommands.c so we should consistent. 
Let's stay with system(rm/rmdir) and if it doesn't work as we expect, we
can add your rmdir() code and put it in port/dirmod.c.

> Regarding the #endif comments you removed - Peter had asked me to put 
> them in. See 
> http://archives.postgresql.org/pgsql-hackers/2003-10/msg00352.php

Peter, I thought we add:

	#endif /* port... */

only when the define covers many lines of code.  This case:
	
	#ifdef WIN32
	some code
	#endif

seems clearer than:

	#ifdef WIN32
	some code
	#endif /* WIN32 */

Of course, if the code block is very large, a closing comment can
sometimes help.  Personally, I don't like the comments anytime, but if
people like them on long blocks, I can understand that.
	
> You put a comment on canonicalise_path() asking if it is needed. The 
> short answer is very much "yes". The Windows command processor will 
> accept suitably quoted paths with forward slashes, but barfs badly with 
> mixed forward and back slashes. (See recent discussion on 
> hackers-win32).Removing the trailing slash on a path means we never get 
> ugly double slashes. Feel free to put that info in as a comment if you 
> think it is needed.

Done.

> The changes you made for the final message are not going to work for 
> Windows - if pgpath or pg_data contain spaces we need quotes (even on 
> Unix, although there most people know better than to put spaces in 
> names). Also see above about mixed slashes. Also, putting a \ before 
> pg_data will be nasty if it starts with a drive or network specifier - 
> or even without (you might turn a directory specifier into a network 
> drive specifier). It's just a message, though, and we shouldn't hold up 
> for that - we can patch it to get it right.

I am confused.  The only change I made was to have the quotes appear
only on WIN32.

	#ifndef WIN32
	#define QUOTE_PATH  ""
	#else
	#define QUOTE_PATH  "\""
	#endif
	
	...

        printf("\n"
                   "Success. You can now start the database server using:\n\n"
                   "    %s/postmaster -D %s%s%s\n"
                   "or\n"
                   "    %s/pg_ctl -D %s%s%s -l logfile start\n\n",
                        pgpath, QUOTE_PATH, pg_data, QUOTE_PATH,
                        pgpath, QUOTE_PATH, pg_data, QUOTE_PATH);

(I also merged multiple \n into a single line.)  My logic was that
spaces in directory names are much more common on WIN32, so we should
display the quotes only on WIN32.  Perhaps you read "\"" as "\\"?

> (Getting the path and slash thing working portably was by far the 
> biggest headache in this project - the rest was just grunt work for the 
> most part, although I learned a few interesting things.)
> 
> Otherwise I'm fine with this.

Thanks for reviewing my work. I am surprised how small the new initdb.c
is.  It is 50k vs 38k for initdb.sh.  Of course, system_views.sql is
another 10k, but still, it is smaller than I thought, and quite clear.

Thanks.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Sat, 8 Nov 2003 11:15:35 -0500 (EST)
Bruce Momjian <pgman@candle.pha.pa.us>
Re: initdb in C
Andrew Dunstan wrote:
> 
> 
> Bruce Momjian wrote:
> 
> >Yes, I was concerned too that everything was in there.  I checked the
> >initdb.sh logs and found that the only thing not added was the checking
> >of the max number of connections before checking the max number of
> >buffers, which I added.  The other stuff was in there.  I also checked
> >pg_id's recent changes and those were in there too.
> >
> >Andrew, I assume this was a new implementation of initdb, and not taken
> >from an older initdb C implementation made by a company.
> >
> >This isn't really a patch but a C replacement of a critical shell
> >script so there is reason to double-check things.
> >  
> >
> 
> 
> Yes, I worked from initdb.sh, not from any other source. It's "all my 
> own work" :-) I think I started with 1.201 and later upgraded to 1.203.
> 
> I agree it needs careful checking - the more eyeballs the better.

The great part is that it look so much like our code, unlike the
commerical port code I have seen for initdb in the past.  This certainly
moves us forward on Win32.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Sat, 8 Nov 2003 11:18:39 -0500 (EST)
Bruce Momjian <pgman@candle.pha.pa.us>
Re: [PATCHES] initdb in C
Andrew Dunstan writes:

> recreating the datadir if we didn't create it initially should be OK in
> that case, and it makes the code simpler.

That should be avoided, because you'll have trouble recreating the
original directory with all its properties such as ownership, permissions,
etc., at least not without making the code anything but simpler.  There
might even be a situation were you are allowed to delete the directory but
cannot create a new one.

-- 
Peter Eisentraut   peter_e@gmx.net


Sat, 8 Nov 2003 17:24:37 +0100 (CET)
Peter Eisentraut <peter_e@gmx.net>
Re: initdb in C
Peter Eisentraut <peter_e@gmx.net> writes:
> Andrew Dunstan writes:
>> recreating the datadir if we didn't create it initially should be OK in
>> that case, and it makes the code simpler.

> That should be avoided, because you'll have trouble recreating the
> original directory with all its properties such as ownership, permissions,
> etc., at least not without making the code anything but simpler.  There
> might even be a situation were you are allowed to delete the directory but
> cannot create a new one.

Consider also the strong likelihood that the data directory's parent
directory is owned by root, so that you do not have the ability to
delete and recreate the data directory because you don't have write
permission on its parent.  The main reason initdb is set up to be able
to start with an existing-but-empty data dir is exactly because creating
that directory may have required permissions that initdb itself hasn't
got.

			regards, tom lane

Sat, 08 Nov 2003 11:51:03 -0500
Tom Lane <tgl@sss.pgh.pa.us>
Re: initdb in C 


Bruce Momjian wrote:

>Andrew Dunstan wrote:
>  
>
>>    
>>
>>My comments:
>>
>>I have no problem with shelling out to rmdir - although my goal was to 
>>avoid shelling out to anything other than postgres itself. I think 
>>recreating the datadir if we didn't create it initially should be OK in 
>>that case, and it makes the code simpler. Not handling dot files in the 
>>Unix case should also be fine, as we know the directory was empty to 
>>start with and we don't create any.
>>    
>>
>
>Good. I can do rmdir() in C in port/dirmod.c if we need it.  Right now
>we are doing system(rm/rmdir) in dbcommands.c so we should consistent. 
>Let's stay with system(rm/rmdir) and if it doesn't work as we expect, we
>can add your rmdir() code and put it in port/dirmod.c.
>  
>

In view of Peter's email of a few minutes ago I think we need to do that.

>>The changes you made for the final message are not going to work for 
>>Windows - if pgpath or pg_data contain spaces we need quotes (even on 
>>Unix, although there most people know better than to put spaces in 
>>names). Also see above about mixed slashes. Also, putting a \ before 
>>pg_data will be nasty if it starts with a drive or network specifier - 
>>or even without (you might turn a directory specifier into a network 
>>drive specifier). It's just a message, though, and we shouldn't hold up 
>>for that - we can patch it to get it right.
>>    
>>
>
>I am confused.  The only change I made was to have the quotes appear
>only on WIN32.
>
>	#ifndef WIN32
>	#define QUOTE_PATH  ""
>	#else
>	#define QUOTE_PATH  "\""
>	#endif
>	
>	...
>
>        printf("\n"
>                   "Success. You can now start the database server using:\n\n"
>                   "    %s/postmaster -D %s%s%s\n"
>                   "or\n"
>                   "    %s/pg_ctl -D %s%s%s -l logfile start\n\n",
>                        pgpath, QUOTE_PATH, pg_data, QUOTE_PATH,
>                        pgpath, QUOTE_PATH, pg_data, QUOTE_PATH);
>
>(I also merged multiple \n into a single line.)  My logic was that
>spaces in directory names are much more common on WIN32, so we should
>display the quotes only on WIN32.  Perhaps you read "\"" as "\\"?
>  
>

Yes, I did. Sorry about that. But we also need to quote the path (the 
most obvious place to put it after all is "C:\Program 
Files\PostgreSQL"). In fact, that's more important than the data 
location. Unfortunately, the Windows command processor barfs on multiple 
quotes strings ;-(

cheers

andrew




Sat, 08 Nov 2003 11:51:13 -0500
Andrew Dunstan <andrew@dunslane.net>
Re: initdb in C
Peter Eisentraut wrote:
> Andrew Dunstan writes:
> 
> > recreating the datadir if we didn't create it initially should be OK in
> > that case, and it makes the code simpler.
> 
> That should be avoided, because you'll have trouble recreating the
> original directory with all its properties such as ownership, permissions,
> etc., at least not without making the code anything but simpler.  There
> might even be a situation were you are allowed to delete the directory but
> cannot create a new one.

Recreating the directory only happens on WIN32, where rmdir doesn't
allow you to only delete files and subdirectories and not the parent
directory.  Non-Win32 does rm -rf dir/*.

Is that OK?

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Sat, 8 Nov 2003 12:09:51 -0500 (EST)
Bruce Momjian <pgman@candle.pha.pa.us>
Re: initdb in C
Tom Lane wrote:
> Peter Eisentraut <peter_e@gmx.net> writes:
> > Andrew Dunstan writes:
> >> recreating the datadir if we didn't create it initially should be OK in
> >> that case, and it makes the code simpler.
> 
> > That should be avoided, because you'll have trouble recreating the
> > original directory with all its properties such as ownership, permissions,
> > etc., at least not without making the code anything but simpler.  There
> > might even be a situation were you are allowed to delete the directory but
> > cannot create a new one.
> 
> Consider also the strong likelihood that the data directory's parent
> directory is owned by root, so that you do not have the ability to
> delete and recreate the data directory because you don't have write
> permission on its parent.  The main reason initdb is set up to be able
> to start with an existing-but-empty data dir is exactly because creating
> that directory may have required permissions that initdb itself hasn't
> got.

Again, this directory recreate happens only on Win32, an I thought it
would be OK there.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Sat, 8 Nov 2003 12:10:23 -0500 (EST)
Bruce Momjian <pgman@candle.pha.pa.us>
Re: initdb in C
Bruce Momjian <pgman@candle.pha.pa.us> writes:
>> Consider also the strong likelihood that the data directory's parent
>> directory is owned by root,

> Again, this directory recreate happens only on Win32, an I thought it
> would be OK there.

Windows has no concept of directory permissions at all?  I thought the
more recent versions had at least rudimentary permissions.

			regards, tom lane

Sat, 08 Nov 2003 12:18:20 -0500
Tom Lane <tgl@sss.pgh.pa.us>
Re: initdb in C 
Bruce Momjian <pgman@candle.pha.pa.us> writes:
> Recreating the directory only happens on WIN32, where rmdir doesn't
> allow you to only delete files and subdirectories and not the parent
> directory.  Non-Win32 does rm -rf dir/*.

I think we should forget about invoking rm as a subprocess at all, and
just do the recursive directory walk and unlinks for ourselves.  We
already have code to do this for copy in copydir.c, and unlink would not
be any longer.  We will probably be forced into implementing database
removal for ourselves rather than by 'rm' hacks anyway as soon as
tablespaces come to pass; so why contort initdb's behavior for a very
transient implementation savings?

			regards, tom lane

Sat, 08 Nov 2003 12:27:57 -0500
Tom Lane <tgl@sss.pgh.pa.us>
Re: initdb in C 
Andrew Dunstan wrote:
> >Good. I can do rmdir() in C in port/dirmod.c if we need it.  Right now
> >we are doing system(rm/rmdir) in dbcommands.c so we should consistent. 
> >Let's stay with system(rm/rmdir) and if it doesn't work as we expect, we
> >can add your rmdir() code and put it in port/dirmod.c.
> >  
> >
> 
> In view of Peter's email of a few minutes ago I think we need to do that.

Again, recreate is only Win32.  I just figured out how to do this on
Win32.  We have to use 'del' rather than 'rmdir' to keep the directory:

New code is:

	/*
	 * delete a directory tree recursively
	 * assumes path points to a valid directory
	 * deletes everything under path
	 * if rmtopdir is true deletes the directory too
	 *
	 */
	static bool
	rmtree(char *path, bool rmtopdir)
	{
	    char        buf[MAXPGPATH + 64];
	
	#ifndef WIN32
	    /* doesn't handle .* files */
	    snprintf(buf, sizeof(buf), "rm -rf '%s%s'", path,
	            rmtopdir ? "" : "/*");
	#else
		snprintf(buf, sizeof(buf), "%s /s /q \"%s\"",
	    	    rmtopdir ? "rmdir" : "del", path);
	#endif
	
	    return !system(buf);
	}

> >        printf("\n"
> >                   "Success. You can now start the database server using:\n\n"
> >                   "    %s/postmaster -D %s%s%s\n"
> >                   "or\n"
> >                   "    %s/pg_ctl -D %s%s%s -l logfile start\n\n",
> >                        pgpath, QUOTE_PATH, pg_data, QUOTE_PATH,
> >                        pgpath, QUOTE_PATH, pg_data, QUOTE_PATH);
> >
> >(I also merged multiple \n into a single line.)  My logic was that
> >spaces in directory names are much more common on WIN32, so we should
> >display the quotes only on WIN32.  Perhaps you read "\"" as "\\"?
> >  
> >
> 
> Yes, I did. Sorry about that. But we also need to quote the path (the 
> most obvious place to put it after all is "C:\Program 
> Files\PostgreSQL"). In fact, that's more important than the data 
> location. Unfortunately, the Windows command processor barfs on multiple 
> quotes strings ;-(

I ran some tests using XP "CMD" and found:

	"C:\test"
and
	"\test"

works but:

	"test"

does not work.  Since I see that the output always has a leading path, I
have modified the code to do:

    printf("\nSuccess. You can now start the database server using:\n\n"
           "    %s%s%s/postmaster -D %s%s%s\n"
           "or\n"
           "    %s%s%s/pg_ctl -D %s%s%s -l logfile start\n\n",
            QUOTE_PATH, pgpath, QUOTE_PATH, QUOTE_PATH, pg_data, QUOTE_PATH,
            QUOTE_PATH, pgpath, QUOTE_PATH, QUOTE_PATH, pg_data, QUOTE_PATH);

I am attaching the updated initdb.c file.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

/*-------------------------------------------------------------------------
 *
 * initdb
 *
 * author: Andrew Dunstan	   mailto:andrew@dunslane.net
 *
 * Copyright (C) 2003 Andrew Dunstan
 * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * This code is released under the terms of the PostgreSQL License.
 *
 * This is a C implementation of the previous shell script for setting up a
 * PostgreSQL cluster location, and should be highly compatible with it.
 *
 * $Header$
 *
 * TODO:
 *	 - clean up find_postgres code and return values
 *
 * Note:
 *	 The program has some memory leakage - it isn't worth cleaning it up.
 *	 Even before the code was put in to free most of the dynamic memory
 *	 used it ran around 500Kb used + malloc overhead. It should now use
 *	 far less than that (around 240Kb - the size of the BKI file).
 *	 If we can't load this much data into memory how will we ever run
 *	 postgres anyway?
 *
 *-------------------------------------------------------------------------
 */

#include "postgres_fe.h"

#include "getopt_long.h"
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <locale.h>

#include <signal.h>

#include "libpq/pqsignal.h"
#include "mb/pg_wchar.h"


/* version string we expect back from postgres */
#define PG_VERSIONSTR "postgres (PostgreSQL) " PG_VERSION "\n"

/* values passed in by makefile define */

char	   *bindir = PGBINDIR;
char	   *datadir = PGDATADIR;

/* values to be obtained from arguments */
char	   *pg_data = "";
char	   *encoding = "";
char	   *locale = "";
char	   *lc_collate = "";
char	   *lc_ctype = "";
char	   *lc_monetary = "";
char	   *lc_numeric = "";
char	   *lc_time = "";
char	   *lc_messages = "";
char	   *username = "";
bool		pwprompt = false;
bool		debug = false;
bool		noclean = false;
bool		show_help = false;
bool		show_version = false;
bool		show_setting = false;


/* internal vars */
char	   *progname;
char	   *self_path;
char	   *postgres;
char	   *encodingid = "0";
char	   *bki_file;
char	   *desc_file;
char	   *hba_file;
char	   *ident_file;
char	   *conf_file;
char	   *conversion_file;
char	   *info_schema_file;
char	   *features_file;
char	   *system_views_file;
char	   *effective_user;
bool		testpath = true;
bool		made_new_pgdata = false;
char		infoversion[100];
bool		not_ok = false;

/* defaults */
int			n_connections = 10;
int			n_buffers = 50;


/* platform specific path stuff */
#if defined(__CYGWIN__) || defined(WIN32)
#define EXE ".exe"
#define DEVNULL "nul"
#else
#define EXE ""
#define DEVNULL "/dev/null"
#endif

#ifdef WIN32
#define PATHSEP ';'
#else
#define PATHSEP ':'
#endif

/* detected path to postgres and (we assume) friends */
char	   *pgpath;

/* forward declare all our functions */
static bool rmtree(char *, bool);
static void exit_nicely(void);
static void canonicalize_path(char *);

#ifdef WIN32
static char *expanded_path(char *);
#else
#define expanded_path(x) (x)
#endif

static char **readfile(char *);
static void writefile(char *, char **);
static char *get_id(void);
static char *get_encoding_id(char *);
static char *get_short_version(void);
static int	mkdir_p(char *, mode_t);
static bool check_data_dir(void);
static bool mkdatadir(char *);
static bool chklocale(const char *);
static void setlocales(void);
static void set_input(char **, char *);
static void check_input(char *path);
static int	find_postgres(char *);
static int	set_paths(void);
static char **replace_token(char **, char *, char *);
static void set_short_version(char *, char *);
static void set_null_conf(void);
static void test_buffers(void);
static void test_connections(void);
static void setup_config(void);
static void bootstrap_template1(char *);
static void setup_shadow(void);
static void get_set_pwd(void);
static void unlimit_systables(void);
static void setup_depend(void);
static void setup_sysviews(void);
static void setup_description(void);
static void setup_conversion(void);
static void setup_privileges(void);
static void set_info_version(void);
static void setup_schema(void);
static void vacuum_db(void);
static void make_template0(void);
static void usage(void);
static void trapsig(int);
static void check_ok(void);
static char *xstrdup(const char *);
static void *xmalloc(size_t);

/*
 * macros for running pipes to postgres
 */
#define PG_CMD_DECL		char cmd[MAXPGPATH]; char ** line ; FILE * pg
#define PG_CMD_DECL_NOLINE		   char cmd[MAXPGPATH]; FILE * pg

#define PG_CMD_OPEN \
do { \
		  pg = popen(cmd,PG_BINARY_W); \
		  if (pg == NULL)  \
			exit_nicely(); \
} while (0)

#define PG_CMD_CLOSE \
do { \
		 if(pclose(pg) >> 8 & 0xff) \
			exit_nicely(); \
} while (0)

#define PG_CMD_PUTLINE \
do { \
		 if (fputs(*line, pg) < 0) \
		   exit_nicely(); \
		 fflush(pg); \
} while (0)

#ifndef WIN32
#define QUOTE_PATH	""
#else
#define QUOTE_PATH	"\""
#endif

/*
 * routines to check mem allocations and fail noisily.
 * Note that we can't call exit_nicely() on a memory failure, as it calls
 * rmtree() which needs memory allocation. So we just exit with a bang.
 *
 */
static void *
xmalloc(size_t size)
{
	void	   *result;

	result = malloc(size);
	if (!result)
	{
		fputs("malloc failure - bailing out\n", stderr);
		exit(1);
	}
	return result;
}

static char *
xstrdup(const char *s)
{
	char	   *result;

	result = strdup(s);
	if (!result)
	{
		fputs("strdup failure - bailing out\n", stderr);
		exit(1);
	}
	return result;
}

/*
 * delete a directory tree recursively
 * assumes path points to a valid directory
 * deletes everything under path
 * if rmtopdir is true deletes the directory too
 *
 */
static bool
rmtree(char *path, bool rmtopdir)
{
	char		buf[MAXPGPATH + 64];

#ifndef WIN32
	/* doesn't handle .* files */
	snprintf(buf, sizeof(buf), "rm -rf '%s%s'", path,
		rmtopdir ? "" : "/*");
#else
	snprintf(buf, sizeof(buf), "%s /s /q \"%s\"",
		rmtopdir ? "rmdir" : "del", path);
#endif

	return !system(buf);
}


/*
 * make all paths look like unix, with forward slashes
 * also strip any trailing slash.
 * The Windows command processor will accept suitably quoted paths
 * with forward slashes, but barfs badly with mixed forward and back
 * slashes. Removing the trailing slash on a path means we never get
 * ugly double slashes.
 */
static void
canonicalize_path(char *path)
{
	char	   *p;

	for (p = path; *p; p++)
	{
#ifdef WIN32
		if (*p == '\\')
			*p = '/';
#endif
	}
	if (p != path && *--p == '/')
		*p = '\0';
}

/*
 * make a copy of the array of lines, with token replaced by replacement
 * the first time it occurs on each line.
 * This does most of what sed was used for in the shell script, but
 * doesn't need any regexp stuff.
 */
static char **
replace_token(char **lines, char *token, char *replacement)
{
	int			numlines = 1;
	int			i;
	char	  **result;
	int			toklen,
				replen,
				diff;

	for (i = 0; lines[i]; i++)
		numlines++;

	result = (char **) xmalloc(numlines * sizeof(char *));

	toklen = strlen(token);
	replen = strlen(replacement);
	diff = replen - toklen;

	for (i = 0; i < numlines; i++)
	{
		char	   *where;
		char	   *newline;
		int			pre;

		/* just copy pointer if NULL or no change needed */

		if (lines[i] == NULL || (where = strstr(lines[i], token)) == NULL)
		{
			result[i] = lines[i];
			continue;
		}

		/* if we get here a change is needed - set up new line */

		newline = (char *) xmalloc(strlen(lines[i]) + diff + 1);

		pre = where - lines[i];

		strncpy(newline, lines[i], pre);

		strcpy(newline + pre, replacement);

		strcpy(newline + pre + replen, lines[i] + pre + toklen);

		result[i] = newline;

	}

	return result;

}

/*
 * get the lines from a text file
 *
 */
static char **
readfile(char *path)
{
	FILE	   *infile;
	int			maxlength = 0,
				linelen = 0;
	int			nlines = 0;
	char	  **result;
	char	   *buffer;
	int			c;

	if ((infile = fopen(path, "r")) == NULL)
	{
		fprintf(stderr, "could not read %s ... ", path);
		exit_nicely();
	}

	/* pass over the file twice - the first time to size the result */

	while ((c = fgetc(infile)) != EOF)
	{
		linelen++;
		if (c == '\n')
		{
			nlines++;
			if (linelen > maxlength)
				maxlength = linelen;
			linelen = 0;
		}
	}

	/* handle last line without a terminating newline (yuck) */

	if (linelen)
		nlines++;
	if (linelen > maxlength)
		maxlength = linelen;

	/* set up the result and the line buffer */

	result = (char **) xmalloc((nlines + 2) * sizeof(char *));
	buffer = (char *) xmalloc(maxlength + 2);

	/* now reprocess the file and store the lines */

	rewind(infile);
	nlines = 0;
	while (fgets(buffer, maxlength + 1, infile) != NULL)
	{
		result[nlines] = xstrdup(buffer);
		nlines++;
	}

	fclose(infile);
	result[nlines] = NULL;

	return result;
}

/*
 * write an array of lines to a file
 *
 */
static void
writefile(char *path, char **lines)
{
	FILE	   *out_file;
	char	  **line;

	;
	if ((out_file = fopen(path, PG_BINARY_W)) == NULL)
	{
		fprintf(stderr, "could not write %s ... ", path);
		exit_nicely();
	}
	for (line = lines; *line != NULL; line++)
	{
		if (fputs(*line, out_file) < 0)
			exit_nicely();
		free(*line);
	}
	if (fclose(out_file))
		exit_nicely();
}

/* source stolen from FreeBSD /src/bin/mkdir/mkdir.c and adapted */

/*
 * this tries to build all the elements of a path to a directory a la mkdir -p
 * we assume the path is in canonical form, i.e. uses / as the separator
 * we also assume it isn't null.
 *
 */
static int
mkdir_p(char *path, mode_t omode)
{
	struct stat sb;
	mode_t		numask,
				oumask;
	int			first,
				last,
				retval;
	char	   *p;

	p = path;
	oumask = 0;
	retval = 0;

#ifdef WIN32
	/* skip network and drive specifiers for win32 */
	if (strlen(p) >= 2)
	{
		if (p[0] == '/' && p[1] == '/')
		{
			/* network drive */
			p = strstr(p + 2, "/");
			if (p == NULL)
				return 1;
		}
		else if (p[1] == ':' &&
				 ((p[0] >= 'a' && p[0] <= 'z') ||
				  (p[0] >= 'A' && p[0] <= 'Z')))
		{
			/* local drive */
			p += 2;
		}
	}
#endif

	if (p[0] == '/')			/* Skip leading '/'. */
		++p;
	for (first = 1, last = 0; !last; ++p)
	{
		if (p[0] == '\0')
			last = 1;
		else if (p[0] != '/')
			continue;
		*p = '\0';
		if (p[1] == '\0')
			last = 1;
		if (first)
		{
			/*
			 * POSIX 1003.2: For each dir operand that does not name an
			 * existing directory, effects equivalent to those cased by
			 * the following command shall occcur:
			 *
			 * mkdir -p -m $(umask -S),u+wx $(dirname dir) && mkdir [-m mode]
			 * dir
			 *
			 * We change the user's umask and then restore it, instead of
			 * doing chmod's.
			 */
			oumask = umask(0);
			numask = oumask & ~(S_IWUSR | S_IXUSR);
			(void) umask(numask);
			first = 0;
		}
		if (last)
			(void) umask(oumask);
		if (mkdir(path, last ? omode : S_IRWXU | S_IRWXG | S_IRWXO) < 0)
		{
			if (errno == EEXIST || errno == EISDIR)
			{
				if (stat(path, &sb) < 0)
				{
					retval = 1;
					break;
				}
				else if (!S_ISDIR(sb.st_mode))
				{
					if (last)
						errno = EEXIST;
					else
						errno = ENOTDIR;
					retval = 1;
					break;
				}
			}
			else
			{
				retval = 1;
				break;
			}
		}
		if (!last)
			*p = '/';
	}
	if (!first && !last)
		(void) umask(oumask);
	return retval;
}

/*
 * clean up any files we created on failure
 * if we created the data directory remove it too
 *
 */
static void
exit_nicely(void)
{
	fprintf(stderr, "%s: failed\n", progname);

	if (!noclean)
	{
		if (made_new_pgdata)
		{
			fprintf(stderr, "%s: removing data directory \"%s\"\n",
					progname, pg_data);
			if (!rmtree(pg_data, true))
				fprintf(stderr, "%s: failed\n", progname);
		}
		else
		{
			fprintf(stderr,
					"%s: removing contents of data directory \"%s\"\n",
					progname, pg_data);
			if (!rmtree(pg_data, false))
				fprintf(stderr, "%s: failed\n", progname);
		}
	}
	exit(1);
}

/*
 * find the current user using code lifted from pg_id.c
 * on unix make sure it isn't really root
 *
 */
static char *
get_id(void)
{
#ifndef WIN32

	struct passwd *pw;

	pw = getpwuid(getuid());

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

	if (!geteuid())				/* 0 is root's uid */
	{
		fprintf(stderr,
				"%s: cannot be run as root\n"
			 "Please log in (using, e.g., \"su\") as the (unprivileged) "
				"user that will\n" "own the server process.\n",
				progname);
		exit(1);
	}
#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 xstrdup(pw->pw_name);
}

/*
 * get the encoding id for a given encoding name
 *
 */
static char *
get_encoding_id(char *encoding_name)
{
	int			enc;
	char		result[20];

	if (encoding_name && *encoding_name)
	{
		if ((enc = pg_char_to_encoding(encoding_name)) >= 0 &&
			pg_valid_server_encoding(encoding_name) >= 0)
		{
			sprintf(result, "%d", enc);
			return xstrdup(result);
		}
	}
	fprintf(stderr, "%s: \"%s\" is not a valid server encoding name\n",
			progname, encoding_name ? encoding_name : "(null)");
	exit(1);
}

/*
 * get short version of VERSION
 *
 */
static char *
get_short_version(void)
{
	bool		gotdot = false;
	int			end;
	char	   *vr;

	vr = xstrdup(PG_VERSION);

	for (end = 0; vr[end] != '\0'; end++)
	{
		if (vr[end] == '.')
		{
			if (end == 0)
				return NULL;
			else if (gotdot)
				break;
			else
				gotdot = true;
		}
		else if (vr[end] < '0' || vr[end] > '9')
		{
			/* gone past digits and dots */
			break;
		}
	}
	if (end == 0 || vr[end - 1] == '.' || !gotdot)
		return NULL;

	vr[end] = '\0';
	return vr;
}

/*
 * make sure the data directory either doesn't exist or is empty
 *
 */
static bool
check_data_dir(void)
{
	DIR		   *chkdir;
	struct dirent *file;
	bool		empty = true;

	chkdir = opendir(pg_data);

	if (!chkdir)
		return (errno == ENOENT);

	while ((file = readdir(chkdir)) != NULL)
	{
		if (strcmp(".", file->d_name) == 0 || strcmp("..", file->d_name) == 0)
		{
			/* skip this and parent directory */
			continue;
		}
		else
		{
			empty = false;
			break;
		}
	}

	closedir(chkdir);

	return empty;
}

/*
 * make the data directory (or one of its subdirectories if subdir is not NULL)
 *
 */
static bool
mkdatadir(char *subdir)
{
	char	   *path;
	int			res;

	path = xmalloc(strlen(pg_data) + 2 +
				   (subdir == NULL ? 0 : strlen(subdir)));

	if (subdir != NULL)
		sprintf(path, "%s/%s", pg_data, subdir);
	else
		strcpy(path, pg_data);

	res = mkdir(path, 0700);
	if (res == 0)
		return true;
	else if (subdir == NULL || errno != ENOENT)
		return false;
	else
		return !mkdir_p(path, 0700);
}


/*
 * set name of given input file variable under data directory
 *
 */
static void
set_input(char **dest, char *filename)
{
	*dest = xmalloc(strlen(datadir) + strlen(filename) + 2);
	sprintf(*dest, "%s/%s", datadir, filename);
}

/*
 * check that given input file exists
 *
 */
static void
check_input(char *path)
{
	struct stat statbuf;

	if (stat(path, &statbuf) != 0 || !S_ISREG(statbuf.st_mode))
	{
		fprintf(stderr,
				"%s: file \"%s\" not found\n"
		   "This means you have a corrupted installation or identified\n"
				"the wrong directory with the invocation option -L.\n",
				progname, path);
		exit(1);
	}

}

/*
 * TODO - clean this up and handle the errors properly
 * don't overkill
 *
 */
#define FIND_SUCCESS 0
#define FIND_NOT_FOUND 1
#define FIND_STAT_ERR 2
#define FIND_NOT_REGFILE 3
#define FIND_BAD_PERM 4
#define FIND_EXEC_ERR 5
#define FIND_WRONG_VERSION 6

/*
 * see if there is a postgres executable in the given path, and giving the
 * right version number
 *
 */
static int
find_postgres(char *path)
{
	char		fn[MAXPGPATH];
	char		cmd[MAXPGPATH];
	char		line[100];

#ifndef WIN32
	int			permmask = S_IROTH | S_IXOTH;
#endif

	struct stat statbuf;
	FILE	   *pgver;
	int			plen = strlen(path);

	if (path[plen - 1] != '/')
		snprintf(fn, MAXPGPATH, "%s/postgres%s", path, EXE);
	else
		snprintf(fn, MAXPGPATH, "%spostgres%s", path, EXE);

	if (stat(fn, &statbuf) != 0)
	{
		if (errno == ENOENT)
			return FIND_NOT_FOUND;
		else
			return FIND_STAT_ERR;
	}
	if (!S_ISREG(statbuf.st_mode))
		return FIND_NOT_REGFILE;

#ifndef WIN32
	/*
	 * Only unix requires this test, on WIN32 an .exe file should be
	 * executable
	 */
	if ((statbuf.st_mode & permmask) != permmask)
		return FIND_BAD_PERM;
#endif

	snprintf(cmd, MAXPGPATH, "\"%s/postgres\" -V 2>%s", path, DEVNULL);

	if ((pgver = popen(cmd, "r")) == NULL)
		return FIND_EXEC_ERR;

	if (fgets(line, sizeof(line), pgver) == NULL)
		perror("fgets failure");

	pclose(pgver);

	if (strcmp(line, PG_VERSIONSTR) != 0)
		return FIND_WRONG_VERSION;

	return FIND_SUCCESS;
}

/*
 * Windows doesn't like relative paths to executables (other things work fine)
 * so we call its builtin function to expand them. Elsewhere this is a NOOP
 *
 */
#ifdef WIN32
static char *
expanded_path(char *path)
{
	char		abspath[MAXPGPATH];

	if (_fullpath(abspath, path, MAXPGPATH) == NULL)
	{
		perror("expanded path");
		return path;
	}
	canonicalize_path(abspath);
	return xstrdup(abspath);
}
#endif 

/*
 * set the paths pointing to postgres
 * look for it in the same place we found this program, or in the environment
 * path, or in the configured bindir.
 *
 */
static int
set_paths(void)
{
	if (testpath && !self_path)
	{

		char	   *path,
				   *cursor;
		int			pathlen,
					i,
					pathsegs;
		char	  **pathbits;
		char		buf[MAXPGPATH];
		struct stat statbuf;

		path = xstrdup(getenv("PATH"));
		pathlen = strlen(path);

		for (i = 0, pathsegs = 1; i < pathlen; i++)
		{
			if (path[i] == PATHSEP)
				pathsegs++;
		}

		pathbits = (char **) xmalloc(pathsegs * sizeof(char *));
		for (i = 0, pathsegs = 0, cursor = path; i <= pathlen; i++)
		{
			if (path[i] == PATHSEP || path[i] == 0)
			{
				path[i] = 0;
				if (strlen(cursor) == 0)
				{
					/* empty path segment means current directory */
					pathbits[pathsegs] = xstrdup(".");
				}
				else
				{
					canonicalize_path(cursor);
					pathbits[pathsegs] = cursor;
				}
				pathsegs++;
				cursor = path + i + 1;
			}
		}

		for (i = 0; i < pathsegs; i++)
		{
			snprintf(buf, MAXPGPATH, "%s/%s%s", pathbits[i], progname, EXE);
			if (stat(buf, &statbuf) == 0 && S_ISREG(statbuf.st_mode))
			{
				self_path = pathbits[i];
				break;
			}
		}
	}

	if (testpath && self_path &&
		(find_postgres(expanded_path(self_path)) == 0))
	{
		/* we found postgres on out own path */
		pgpath = expanded_path(self_path);
	}
	else
	{
		/* look in the hardcoded bindir */
		int			res;
		char	   *cbindir;

		cbindir = xstrdup(bindir);
		canonicalize_path(cbindir);
		res = find_postgres(expanded_path(cbindir));
		if (res == 0)
			pgpath = expanded_path(cbindir);
		else
			return 1;
	}

	return 0;
}

/*
 * write out the PG_VERSION file in the data dir, or its subdirectory
 * if extrapath is not NULL
 *
 */
static void
set_short_version(char *short_version, char *extrapath)
{
	FILE	   *version_file;
	char	   *path;

	if (extrapath == NULL)
	{
		path = xmalloc(strlen(pg_data) + 12);
		sprintf(path, "%s/PG_VERSION", pg_data);
	}
	else
	{
		path = xmalloc(strlen(pg_data) + strlen(extrapath) + 13);
		sprintf(path, "%s/%s/PG_VERSION", pg_data, extrapath);
	}
	version_file = fopen(path, PG_BINARY_W);
	fprintf(version_file, "%s\n", short_version);
	fclose(version_file);
}

/*
 * set up an empty config file so we can check buffers and connections
 *
 */
static void
set_null_conf(void)
{
	FILE	   *conf_file;
	char	   *path;

	path = xmalloc(strlen(pg_data) + 17);
	sprintf(path, "%s/postgresql.conf", pg_data);
	conf_file = fopen(path, PG_BINARY_W);
	fclose(conf_file);
}

/*
 * check how many connections we can sustain
 *
 */
static void
test_connections(void)
{
	char	   *format =
	"\"%s/postgres\" -boot -x 0 -F "
	"-c shared_buffers=%d -c max_connections=%d template1 <%s >%s 2>&1";
	char		cmd[MAXPGPATH];
	int			conns[] = {100, 50, 40, 30, 20, 10};
	int			len = sizeof(conns) / sizeof(int);
	int			i,
				status;

	for (i = 0; i < len; i++)
	{
		snprintf(cmd, sizeof(cmd), format,
				 pgpath, conns[i] * 5, conns[i], DEVNULL, DEVNULL);
		status = system(cmd);
		if (status == 0)
			break;
	}
	if (i >= len)
		i = len - 1;
	n_connections = conns[i];
	printf("connections set to %d\n", n_connections);
}

/*
 * check how many buffers we can run with
 *
 */
static void
test_buffers(void)
{
	char	   *format =
	"\"%s/postgres\"  -boot -x 0 -F "
	"-c shared_buffers=%d -c max_connections=%d template1 <%s >%s 2>&1";
	char		cmd[MAXPGPATH];
	int			bufs[] = {1000, 900, 800, 700, 600, 500, 400, 300, 200, 100, 50};
	int			len = sizeof(bufs) / sizeof(int);
	int			i,
				status;

	for (i = 0; i < len; i++)
	{
		snprintf(cmd, sizeof(cmd), format, pgpath, bufs[i], n_connections,
			DEVNULL, DEVNULL);
		status = system(cmd);
		if (status == 0)
			break;
	}
	if (i >= len)
		i = len - 1;
	n_buffers = bufs[i];
	printf("buffers set to %d\n", n_buffers);
}

/*
 * set up all the config files
 *
 */
static void
setup_config(void)
{

	char	  **conflines;
	char		repltok[100];
	char		path[MAXPGPATH];

	fputs("creating configuration files ... ", stdout);

	/* postgresql.conf */

	conflines = readfile(conf_file);

	snprintf(repltok, sizeof(repltok), "max_connections = %d", n_connections);
	conflines = replace_token(conflines, "#max_connections = 100", repltok);

	snprintf(repltok, sizeof(repltok), "shared_buffers = %d", n_buffers);
	conflines = replace_token(conflines, "#shared_buffers = 1000", repltok);

	snprintf(repltok, sizeof(repltok), "lc_messages = '%s'", lc_messages);
	conflines = replace_token(conflines, "#lc_messages = 'C'", repltok);

	snprintf(repltok, sizeof(repltok), "lc_monetary = '%s'", lc_monetary);
	conflines = replace_token(conflines, "#lc_monetary = 'C'", repltok);

	snprintf(repltok, sizeof(repltok), "lc_numeric = '%s'", lc_numeric);

	conflines = replace_token(conflines, "#lc_numeric = 'C'", repltok);

	snprintf(repltok, sizeof(repltok), "lc_time = '%s'", lc_time);
	conflines = replace_token(conflines, "#lc_time = 'C'", repltok);

	snprintf(path, MAXPGPATH, "%s/postgresql.conf", pg_data);

	writefile(path, conflines);
	chmod(path, 0600);

	free(conflines);


	/* pg_hba.conf */

	conflines = readfile(hba_file);

#ifndef HAVE_IPV6
	conflines = replace_token(conflines,
							  "host    all         all         ::1",
							  "#host    all         all         ::1");
#endif

	snprintf(path, MAXPGPATH, "%s/pg_hba.conf", pg_data);

	writefile(path, conflines);
	chmod(path, 0600);

	free(conflines);

	/* pg_ident.conf */

	conflines = readfile(ident_file);

	snprintf(path, MAXPGPATH, "%s/pg_ident.conf", pg_data);

	writefile(path, conflines);
	chmod(path, 0600);

	free(conflines);

	check_ok();
}


/*
 * run the bootstrap code
 *
 */
static void
bootstrap_template1(char *short_version)
{
	char	   *talkargs = "";
	char	  **bki_lines;
	char		headerline[MAXPGPATH];

	PG_CMD_DECL;

	printf("creating template1 database in %s/base/1 ... ", pg_data);

	if (debug)
		talkargs = "-d 5";

	bki_lines = readfile(bki_file);

	snprintf(headerline, MAXPGPATH, "# PostgreSQL %s\n", short_version);

	if (strcmp(headerline, *bki_lines) != 0)
	{
		fprintf(stderr,
				"%s: input file \"%s\" does not belong to PostgreSQL %s\n"
				"Check your installation or specify the correct path "
				"using the option -L.\n",
				progname, bki_file, PG_VERSION);
		exit_nicely();

	}

	bki_lines = replace_token(bki_lines, "POSTGRES", effective_user);

	bki_lines = replace_token(bki_lines, "ENCODING", encodingid);

	/*
	 * we could save the old environment here, and restore it afterwards,
	 * but there doesn't seem to be any point, especially as we have
	 * already called setlocale().
	 *
	 */
	snprintf(cmd, MAXPGPATH, "LC_COLLATE=%s", lc_collate);
	putenv(xstrdup(cmd));

	snprintf(cmd, MAXPGPATH, "LC_CTYPE=%s", lc_ctype);
	putenv(xstrdup(cmd));

	putenv("LC_ALL");

	snprintf(cmd, MAXPGPATH,
		" \"%s/postgres\"  -boot -x1 -F %s template1", pgpath, talkargs);

	PG_CMD_OPEN;

	for (line = bki_lines; *line != NULL; line++)
	{
		PG_CMD_PUTLINE;
		free(*line);
	}

	PG_CMD_CLOSE;

	free(bki_lines);

	check_ok();
}

/*
 * set up the shadow password table
 *
 */
static void
setup_shadow(void)
{

	char	   *pg_shadow_setup[] = {
		/*
		 * Create a trigger so that direct updates to pg_shadow will be
		 * written to the flat password/group files pg_pwd and pg_group
		 */
		"CREATE TRIGGER pg_sync_pg_pwd "
		"  AFTER INSERT OR UPDATE OR DELETE ON pg_shadow "
		"  FOR EACH ROW EXECUTE PROCEDURE update_pg_pwd_and_pg_group();\n",
		"CREATE TRIGGER pg_sync_pg_group "
		"  AFTER INSERT OR UPDATE OR DELETE ON pg_group "
		"  FOR EACH ROW EXECUTE PROCEDURE update_pg_pwd_and_pg_group();\n",

		/*
		 * needs to be done before alter user, because alter user checks
		 * that pg_shadow is secure ...
		 */
		"REVOKE ALL on pg_shadow FROM public;\n",
		NULL
	};

	PG_CMD_DECL;

	fputs("initializing pg_shadow ... ", stdout);

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s",
			 pgpath, DEVNULL);

	PG_CMD_OPEN;

	for (line = pg_shadow_setup; *line != NULL; line++)
		PG_CMD_PUTLINE;

	PG_CMD_CLOSE;

	check_ok();
}

/*
 * get the superuser password if required, and call postgres to set it
 *
 */
static void
get_set_pwd(void)
{
	PG_CMD_DECL_NOLINE;

	char	   *pwd1,
			   *pwd2;
	char		pwdpath[MAXPGPATH];
	struct stat statbuf;

	pwd1 = simple_prompt("Enter new superuser password: ", 100, false);
	pwd2 = simple_prompt("Enter it again: ", 100, false);
	if (strcmp(pwd1, pwd2) != 0)
	{
		fprintf(stderr, "Passwords didn't match.\n");
		exit_nicely();
	}
	free(pwd2);

	printf("storing the password ... ");

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s", pgpath, DEVNULL);


	PG_CMD_OPEN;

	if (fprintf(
	   pg, "ALTER USER \"%s\" WITH PASSWORD '%s';\n", username, pwd1) < 0)
	{
		/* write failure */
		exit_nicely();
	}
	fflush(pg);

	PG_CMD_CLOSE;

	snprintf(pwdpath, MAXPGPATH, "%s/global/pg_pwd", pg_data);
	if (stat(pwdpath, &statbuf) != 0 || !S_ISREG(statbuf.st_mode))
	{
		fprintf(stderr,
				"%s: The password file was not generated - "
				"please report this problem\n",
				progname);
		exit_nicely();
	}

	check_ok();
}

/*
 * toast sys tables
 *
 */
static void
unlimit_systables(void)
{
	char	   *systables_setup[] = {
		"ALTER TABLE pg_attrdef CREATE TOAST TABLE;\n",
		"ALTER TABLE pg_constraint CREATE TOAST TABLE;\n",
		"ALTER TABLE pg_database CREATE TOAST TABLE;\n",
		"ALTER TABLE pg_description CREATE TOAST TABLE;\n",
		"ALTER TABLE pg_group CREATE TOAST TABLE;\n",
		"ALTER TABLE pg_proc CREATE TOAST TABLE;\n",
		"ALTER TABLE pg_rewrite CREATE TOAST TABLE;\n",
		"ALTER TABLE pg_shadow CREATE TOAST TABLE;\n",
		"ALTER TABLE pg_statistic CREATE TOAST TABLE;\n",
		NULL
	};

	PG_CMD_DECL;

	fputs("enabling unlimited row size for system tables ... ", stdout);

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s", pgpath, DEVNULL);

	PG_CMD_OPEN;

	for (line = systables_setup; *line != NULL; line++)
		PG_CMD_PUTLINE;

	PG_CMD_CLOSE;

	check_ok();
}

/*
 * set up pg_depend
 *
 */
static void
setup_depend(void)
{
	char	   *pg_depend_setup[] = {
		/*
		 * Make PIN entries in pg_depend for all objects made so far in
		 * the tables that the dependency code handles.  This is overkill
		 * (the system doesn't really depend on having every last weird
		 * datatype, for instance) but generating only the minimum
		 * required set of dependencies seems hard. Note that we
		 * deliberately do not pin the system views. First delete any
		 * already-made entries; PINs override all else, and must be the
		 * only entries for their objects.
		 */
		"DELETE FROM pg_depend;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_class;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_proc;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_type;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_cast;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_constraint;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_attrdef;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_language;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_operator;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_opclass;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_rewrite;\n",
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_trigger;\n",

		/*
		 * restriction here to avoid pinning the public namespace
		 */
		"INSERT INTO pg_depend SELECT 0,0,0, tableoid,oid,0, 'p' "
		" FROM pg_namespace "
		"    WHERE nspname LIKE 'pg%';\n",
		NULL
	};

	PG_CMD_DECL;

	fputs("initializing pg_depend ... ", stdout);

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s",
			 pgpath, DEVNULL);

	PG_CMD_OPEN;

	for (line = pg_depend_setup; *line != NULL; line++)
		PG_CMD_PUTLINE;

	PG_CMD_CLOSE;

	check_ok();
}

/*
 * set up system views
 *
 */
static void
setup_sysviews(void)
{
	PG_CMD_DECL;

	char	  **sysviews_setup;

	fputs("creating system views ... ", stdout);

	sysviews_setup = readfile(system_views_file);

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -N -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s",
			 pgpath, DEVNULL);

	PG_CMD_OPEN;

	for (line = sysviews_setup; *line != NULL; line++)
	{
		PG_CMD_PUTLINE;
		free(*line);
	}

	PG_CMD_CLOSE;

	free(sysviews_setup);

	check_ok();
}

/*
 * load description data
 *
 */
static void
setup_description(void)
{
	char	   *pg_description_setup1[] = {
		"CREATE TEMP TABLE tmp_pg_description ( "
		"	objoid oid, "
		"	classname name, "
		"	objsubid int4, "
		"	description text) WITHOUT OIDS;\n",
		"COPY tmp_pg_description FROM STDIN;\n",
		NULL
	};

	char	   *pg_description_setup2[] = {
		"\\.\n",
		"INSERT INTO pg_description "
		" SELECT t.objoid, c.oid, t.objsubid, t.description "
		"  FROM tmp_pg_description t, pg_class c "
		"    WHERE c.relname = t.classname;\n",
		NULL
	};


	PG_CMD_DECL;

	char	  **desc_lines;

	fputs("loading pg_description ... ", stdout);

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s",
			 pgpath, DEVNULL);

	PG_CMD_OPEN;

	for (line = pg_description_setup1; *line != NULL; line++)
		PG_CMD_PUTLINE;

	desc_lines = readfile(desc_file);
	for (line = desc_lines; *line != NULL; line++)
	{
		PG_CMD_PUTLINE;
		free(*line);
	}

	free(desc_lines);


	for (line = pg_description_setup2; *line != NULL; line++)
		PG_CMD_PUTLINE;

	PG_CMD_CLOSE;

	check_ok();
}

/*
 * load conversion functions
 *
 */
static void
setup_conversion(void)
{
	PG_CMD_DECL;

	char	  **conv_lines;

	fputs("creating conversions ... ", stdout);

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s",
			 pgpath, DEVNULL);

	PG_CMD_OPEN;

	conv_lines = readfile(conversion_file);
	for (line = conv_lines; *line != NULL; line++)
	{
		if (strstr(*line, "DROP CONVERSION") != *line)
			PG_CMD_PUTLINE;

		free(*line);
	}

	free(conv_lines);

	PG_CMD_CLOSE;

	check_ok();
}

/*
 * run privileges script
 *
 */
static void
setup_privileges(void)
{
	char	   *privileges_setup[] = {
		"UPDATE pg_class "
		"  SET relacl = '{\"=r/\\\\\"$POSTGRES_SUPERUSERNAME\\\\\"\"}' "
		"  WHERE relkind IN ('r', 'v', 'S') AND relacl IS NULL;\n",
		"UPDATE pg_proc "
		"  SET proacl = '{\"=X/\\\\\"$POSTGRES_SUPERUSERNAME\\\\\"\"}' "
		"  WHERE proacl IS NULL;\n",
		"UPDATE pg_language "
		"  SET lanacl = '{\"=U/\\\\\"$POSTGRES_SUPERUSERNAME\\\\\"\"}' "
		"  WHERE lanpltrusted;\n",
		"GRANT USAGE ON SCHEMA pg_catalog TO PUBLIC;\n",
		"GRANT CREATE, USAGE ON SCHEMA public TO PUBLIC;\n",
		NULL
	};

	PG_CMD_DECL;

	char	  **priv_lines;

	fputs("setting privileges on builtin objects ... ", stdout);

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s",
			 pgpath, DEVNULL);

	PG_CMD_OPEN;

	priv_lines = replace_token(privileges_setup,
							   "$POSTGRES_SUPERUSERNAME", username);
	for (line = priv_lines; *line != NULL; line++)
		PG_CMD_PUTLINE;

	PG_CMD_CLOSE;

	check_ok();
}

/*
 * extract the strange version of version required for info schema
 *
 */
static void
set_info_version(void)
{
	char	   *letterversion;
	long		major = 0,
				minor = 0,
				micro = 0;
	char	   *endptr;
	char	   *vstr = xstrdup(PG_VERSION);
	char	   *ptr;

	ptr = vstr + (strlen(vstr) - 1);
	while (ptr != vstr && (*ptr < '0' || *ptr > '9'))
		ptr--;
	letterversion = ptr + 1;
	major = strtol(vstr, &endptr, 10);
	if (*endptr)
		minor = strtol(endptr + 1, &endptr, 10);
	if (*endptr)
		micro = strtol(endptr + 1, &endptr, 10);
	snprintf(infoversion, sizeof(infoversion), "%02ld.%02ld.%04ld%s",
			 major, minor, micro, letterversion);
}

/*
 * load info schema and populate from features file
 *
 */
static void
setup_schema(void)
{

	PG_CMD_DECL;

	char	  **lines;
	int			fres;

	fputs("creating information schema ... ", stdout);

	lines = readfile(info_schema_file);

	/*
	 * note that here we don't run in single line mode, unlike other
	 * places
	 */
	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true -N template1 >%s",
			 pgpath, DEVNULL);

	PG_CMD_OPEN;

	for (line = lines; *line != NULL; line++)
	{
		PG_CMD_PUTLINE;
		free(*line);
	}

	free(lines);

	PG_CMD_CLOSE;

	lines = readfile(features_file);

	/*
	 * strip CR before NL this is the only place we do this  (following
	 * the shell script) - we could do it universally in readfile() if
	 * necessary
	 *
	 */
	lines = replace_token(lines, "\r\n", "\n");

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s",
			 pgpath, DEVNULL);

	PG_CMD_OPEN;

	fres = fprintf(pg,
				   "UPDATE information_schema.sql_implementation_info "
				   "  SET character_value = '%s' "
				   "  WHERE implementation_info_name = 'DBMS VERSION';\n",
				   infoversion);

	if (fres < 0)
		exit_nicely();

	fres = fputs("COPY information_schema.sql_features "
				 "  (feature_id, feature_name, sub_feature_id, "
				 "  sub_feature_name, is_supported, comments) "
				 "FROM STDIN;\n",
				 pg);

	if (fres < 0)
		exit_nicely();

	for (line = lines; *line != NULL; line++)
	{
		PG_CMD_PUTLINE;
		free(*line);
	}

	free(lines);

	if (fputs("\\.\n", pg) < 0)
		exit_nicely();
	fflush(pg);

	PG_CMD_CLOSE;

	check_ok();
}

/*
 * clean everything up in template1
 *
 */
static void
vacuum_db(void)
{
	PG_CMD_DECL_NOLINE;

	fputs("vacuuming database template1 ... ", stdout);

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s",
			 pgpath, DEVNULL);

	PG_CMD_OPEN;

	if (fputs("ANALYSE;\nVACUUM FULL FREEZE;\n", pg) < 0)
		exit_nicely();
	fflush(pg);

	PG_CMD_CLOSE;

	check_ok();

}

/*
 * copy template1 to template0
 *
 */
static void
make_template0(void)
{
	char	   *template0_setup[] = {
		"CREATE DATABASE template0;\n",
		"UPDATE pg_database SET "
		"	datistemplate = 't', "
		"	datallowconn = 'f' "
		"    WHERE datname = 'template0';\n",

		/*
		 * We use the OID of template0 to determine lastsysoid
		 *
		 */
		"UPDATE pg_database SET datlastsysoid = "
		"    (SELECT oid::int4 - 1 FROM pg_database "
		"    WHERE datname = 'template0');\n",

		/*
		 * Explicitly revoke public create-schema and create-temp-table
		 * privileges in template1 and template0; else the latter would be
		 * on by default
		 */
		"REVOKE CREATE,TEMPORARY ON DATABASE template1 FROM public;\n",
		"REVOKE CREATE,TEMPORARY ON DATABASE template0 FROM public;\n",

		/*
		 * Finally vacuum to clean up dead rows in pg_database
		 */
		"VACUUM FULL pg_database;\n",
		NULL
	};

	PG_CMD_DECL;

	fputs("copying template1 to template0 ... ", stdout);

	snprintf(cmd, MAXPGPATH,
			 "\"%s/postgres\" -F -O -c search_path=pg_catalog "
			 "-c exit_on_error=true template1 >%s",
			 pgpath, DEVNULL);

	PG_CMD_OPEN;

	for (line = template0_setup; *line; line++)
		PG_CMD_PUTLINE;

	PG_CMD_CLOSE;

	check_ok();
}


/*
 * signal handler in case we are interrupted.
 *
 * The Windows runtime docs at
 * http://msdn.microsoft.com/library/en-us/vclib/html/_crt_signal.asp
 * specifically forbid a number of things being done from a signal handler,
 * including IO, memory allocation and system calls, and only allow jmpbuf
 * if you are handling SIGFPE.
 *
 * I avoided doing the forbidden things by setting a flag instead of calling
 * exit_nicely() directly.
 *
 * Also note the behaviour of Windows with SIGINT, which says this:
 *	 Note	SIGINT is not supported for any Win32 application, including
 *	 Windows 98/Me and Windows NT/2000/XP. When a CTRL+C interrupt occurs,
 *	 Win32 operating systems generate a new thread to specifically handle
 *	 that interrupt. This can cause a single-thread application such as UNIX,
 *	 to become multithreaded, resulting in unexpected behavior.
 *
 * I have no idea how to handle this. (Strange they call UNIX an application!)
 * So this will need some testing on Windows.
 *
 */
static void
trapsig(int signum)
{
	/* handle systems that reset the handler, like Windows (grr) */
	pqsignal(signum, trapsig);
	not_ok = true;
}

/*
 * call exit_nicely() if we got a signal, or else output "ok".
 *
 */
static void
check_ok()
{
	if (not_ok)
	{
		puts("Caught Signal.");
		exit_nicely();
	}
	else
	{
		/* no signal caught */
		puts("ok");
	}
}


/*
 * check if given string is a valid locle specifier
 * based on some code given to me by Peter Eisentraut
 * (but I take responsibility for it :-)
 */
static bool
chklocale(const char *locale)
{
	bool		ret;
	int			category = LC_CTYPE;
	char	   *save;

	save = setlocale(category, NULL);
	if (!save)
		return false;			/* should not happen; */

	save = xstrdup(save);

	ret = (setlocale(category, locale) != NULL);

	setlocale(category, save);
	free(save);

	/* should we exit here? */
	if (!ret)
		fprintf(stderr, "%s: invalid locale name \"%s\"\n", progname, locale);

	return ret;
}

/*
 * set up the locale variables
 * assumes we have called setlocale(LC_ALL,"")
 *
 */
static void
setlocales(void)
{
	/* set empty lc_* values to locale config if set */

	if (strlen(locale) > 0)
	{
		if (strlen(lc_ctype) == 0)
			lc_ctype = locale;
		if (strlen(lc_collate) == 0)
			lc_collate = locale;
		if (strlen(lc_numeric) == 0)
			lc_numeric = locale;
		if (strlen(lc_time) == 0)
			lc_time = locale;
		if (strlen(lc_monetary) == 0)
			lc_monetary = locale;
		if (strlen(lc_messages) == 0)
			lc_messages = locale;
	}

	/*
	 * override absent/invalid config settings from initdb's locale
	 * settings
	 */

	if (strlen(lc_ctype) == 0 || !chklocale(lc_ctype))
		lc_ctype = xstrdup(setlocale(LC_CTYPE, NULL));
	if (strlen(lc_collate) == 0 || !chklocale(lc_collate))
		lc_collate = xstrdup(setlocale(LC_COLLATE, NULL));
	if (strlen(lc_numeric) == 0 || !chklocale(lc_numeric))
		lc_numeric = xstrdup(setlocale(LC_NUMERIC, NULL));
	if (strlen(lc_time) == 0 || !chklocale(lc_time))
		lc_time = xstrdup(setlocale(LC_TIME, NULL));
	if (strlen(lc_monetary) == 0 || !chklocale(lc_monetary))
		lc_monetary = xstrdup(setlocale(LC_MONETARY, NULL));
	if (strlen(lc_messages) == 0 || !chklocale(lc_messages))
#ifdef LC_MESSAGES
	{
		/* when available get the current locale setting */
		lc_messages = xstrdup(setlocale(LC_MESSAGES, NULL));
	}
#else
	{
		/* when not available, get the CTYPE setting */
		lc_messages = xstrdup(setlocale(LC_CTYPE, NULL));
	}
#endif

}

/*
 * help text data
 *
 */
char	   *usage_text[] = {
	"$CMDNAME initializes a PostgreSQL database cluster.\n",
	"\n",
	"Usage:\n",
	"  $CMDNAME [OPTION]... [DATADIR]\n",
	"\n",
	"Options:\n",
	" [-D, --pgdata=]DATADIR     location for this database cluster\n",
	"  -E, --encoding=ENCODING   set default encoding for new databases\n",
	"  --locale=LOCALE           initialize database cluster with given locale\n",
	"  --lc-collate, --lc-ctype, --lc-messages=LOCALE\n",
	"  --lc-monetary, --lc-numeric, --lc-time=LOCALE\n",
	"                            initialize database cluster with given locale\n",
	"                            in the respective category (default taken from\n",
	"                            environment)\n",
	"  --no-locale               equivalent to --locale=C\n",
	"  -U, --username=NAME       database superuser name\n",
	"  -W, --pwprompt            prompt for a password for the new superuser\n",
	"  -?, --help                show this help, then exit\n",
	"  -V, --version             output version information, then exit\n",
	"\n",
	"Less commonly used options: \n",
	"  -d, --debug               generate lots of debugging output\n",
	"  -s, --show                show internal settings\n",
	"  -L DIRECTORY              where to find the input files\n",
	"  -n, --noclean             do not clean up after errors\n",
	"\n",
	"If the data directory is not specified, the environment variable PGDATA\n",
	"is used.\n",
	"\n",
	"Report bugs to <pgsql-bugs@postgresql.org>.\n",
	NULL
};



/*
 * print help text
 *
 */
static void
usage(void)
{

	int			i;
	char	  **newtext;

	newtext = replace_token(usage_text, "$CMDNAME", progname);

	for (i = 0; newtext[i]; i++)
		fputs(newtext[i], stdout);		/* faster than printf */
}

int
main(int argc, char *argv[])
{
	/*
	 * options with no short version return a low integer, the rest return
	 * their short version value
	 *
	 */
	static struct option long_options[] = {
		{"pgdata", required_argument, NULL, 'D'},
		{"encoding", required_argument, NULL, 'E'},
		{"locale", required_argument, NULL, 1},
		{"lc-collate", required_argument, NULL, 2},
		{"lc-ctype", required_argument, NULL, 3},
		{"lc-monetary", required_argument, NULL, 4},
		{"lc-numeric", required_argument, NULL, 5},
		{"lc-time", required_argument, NULL, 6},
		{"lc-messages", required_argument, NULL, 7},
		{"no-locale", no_argument, NULL, 8},
		{"pwprompt", no_argument, NULL, 'W'},
		{"username", required_argument, NULL, 'U'},
		{"help", no_argument, NULL, '?'},
		{"version", no_argument, NULL, 'V'},
		{"debug", no_argument, NULL, 'd'},
		{"show", no_argument, NULL, 's'},
		{"noclean", no_argument, NULL, 'n'},
		{0, 0, 0, 0}
	};

	int			c,
				i;
	int			option_index;
	char	   *short_version;
	char	   *pgdenv;			/* PGDATA value got from sent to
								 * environment */
	char	   *subdirs[] =
	{"global", "pg_xlog", "pg_clog", "base", "base/1"};

	char	   *lastsep;

	/* parse argv[0] - detect explicit path if there was one */

	char	   *carg0;

#if defined(__CYGWIN__) || defined(WIN32)
	char	   *exe;			/* location of exe suffix in progname */
#endif

	setlocale(LC_ALL, "");

	carg0 = xstrdup(argv[0]);
	canonicalize_path(carg0);

	lastsep = strrchr(carg0, '/');
	progname = lastsep ? xstrdup(lastsep + 1) : carg0;

#if defined(__CYGWIN__) || defined(WIN32)
	if (strlen(progname) > 4 &&
		(exe = progname + (strlen(progname) - 4)) &&
		stricmp(exe, EXE) == 0)
	{
		/* strip .exe suffix, regardless of case */
		*exe = '\0';
	}
#endif

	if (lastsep)
	{
		self_path = carg0;
		*lastsep = '\0';
	}
	else
	{
		/* no path known to ourselves from argv[0] */
		self_path = NULL;
	}

	/* process options */

	while (1)
	{
		/*
		 * a : as the first option char here lets us use ? as a short
		 * option
		 */
		c = getopt_long(argc, argv, ":D:E:WU:?sVdnL:",
						long_options, &option_index);

		if (c == -1)
			break;

		switch (c)
		{
			case 'D':
				pg_data = xstrdup(optarg);
				break;
			case 'E':
				encoding = xstrdup(optarg);
				break;
			case 'W':
				pwprompt = true;
				break;
			case 'U':
				username = xstrdup(optarg);
				break;
			case 'd':
				debug = true;
				break;
			case 'n':
				noclean = true;
				break;
			case 'L':
				datadir = xstrdup(optarg);
				break;
			case 1:
				locale = xstrdup(optarg);
				break;
			case 2:
				lc_collate = xstrdup(optarg);
				break;
			case 3:
				lc_ctype = xstrdup(optarg);
				break;
			case 4:
				lc_monetary = xstrdup(optarg);
				break;
			case 5:
				lc_numeric = xstrdup(optarg);
				break;
			case 6:
				lc_time = xstrdup(optarg);
				break;
			case 7:
				lc_messages = xstrdup(optarg);
				break;
			case 8:
				locale = "C";
				break;
			case '?':
				show_help = true;
				break;
			case 's':
				show_setting = true;
				break;
			case 'V':
				show_version = true;
				break;
			default:
				show_help = true;
				printf("Unrecognized option: %c\n", c);
		}

	}

	if (optind < argc)
	{
		pg_data = xstrdup(argv[optind]);
		optind++;
	}

	set_info_version();

	if (strlen(pg_data) == 0 && !(show_help || show_setting))
	{
		pgdenv = getenv("PGDATA");
		if (pgdenv && strlen(pgdenv))
		{
			/* PGDATA found */
			pg_data = xstrdup(pgdenv);
		}
		else
		{
			fprintf(stderr,
					"%s: no data directory specified\n"
			   "You must identify the directory where the data for this "
					"database system\n"
			   "will reside.  Do this with either the invocation option "
					"-D or the\n" "environment variable PGDATA.\n",
					progname);
		}
	}

	canonicalize_path(pg_data);

	/*
	 * we have to set PGDATA for postgres rather than pass it on the
	 * commnd line to avoid dumb quoting problems on Windows, and we would
	 * expecially need quotes otherwise on Windows because paths there are
	 * most likely to have embedded spaces.
	 *
	 */
	pgdenv = xmalloc(8 + strlen(pg_data));
	sprintf(pgdenv, "PGDATA=%s", pg_data);
	putenv(pgdenv);

	if (optind < argc)
		show_help = true;

	if (show_version)
	{
		/* hard coded name here, in case they rename executable */
		printf("initdb (PostgreSQL) %s\n", PG_VERSION);
		exit(0);
	}

	if (show_help)
	{
		usage();
		exit(0);
	}

	if (set_paths() != 0)
	{
		fprintf(stderr,
				"The program \"postgres\" is needed by %s "
				"but was not found in \n"
				"the directory \"%s\". Check your installation.\n",
				progname, bindir);
		exit(1);

	}


	if ((short_version = get_short_version()) == NULL)
	{
		fprintf(stderr, "%s: could not get valid short version\n", progname);
		exit(1);
	}

	effective_user = get_id();
	if (!strlen(username))
		username = effective_user;

	if (strlen(encoding))
		encodingid = get_encoding_id(encoding);

	set_input(&bki_file, "postgres.bki");
	set_input(&desc_file, "postgres.description");
	set_input(&hba_file, "pg_hba.conf.sample");
	set_input(&ident_file, "pg_ident.conf.sample");
	set_input(&conf_file, "postgresql.conf.sample");
	set_input(&conversion_file, "conversion_create.sql");
	set_input(&info_schema_file, "information_schema.sql");
	set_input(&features_file, "sql_features.txt");
	set_input(&system_views_file, "system_views.sql");

	if (show_setting || debug)
	{
		fprintf(stderr,
				"VERSION=%s\n"
				"PGDATA=%s\ndatadir=%s\nPGPATH=%s\n"
				"ENCODING=%s\nENCODINGID=%s\n"
				"POSTGRES_SUPERUSERNAME=%s\nPOSTGRES_BKI=%s\n"
				"POSTGRES_DESCR=%s\nPOSTGRESQL_CONF_SAMPLE=%s\n"
				"PG_HBA_SAMPLE=%s\nPG_IDENT_SAMPLE=%s\n",
				PG_VERSION,
				pg_data, datadir, pgpath,
				encoding, encodingid,
				username, bki_file,
				desc_file, conf_file,
				hba_file, ident_file);
	}

	if (show_setting)
		exit(0);

	check_input(bki_file);
	check_input(desc_file);
	check_input(hba_file);
	check_input(ident_file);
	check_input(conf_file);
	check_input(conversion_file);
	check_input(info_schema_file);
	check_input(features_file);
	check_input(system_views_file);

	setlocales();

	if (strcmp(lc_ctype, lc_collate) == 0 &&
		strcmp(lc_ctype, lc_time) == 0 &&
		strcmp(lc_ctype, lc_numeric) == 0 &&
		strcmp(lc_ctype, lc_monetary) == 0 &&
		strcmp(lc_ctype, lc_messages) == 0)
	{
		printf("The database cluster will be initialized with locale %s\n",
			   lc_ctype);
	}
	else
	{
		printf("The database cluster will be initialized with locales\n"
			   "  COLLATE:  %s\n"
			   "  CTYPE:    %s\n"
			   "  MESSAGES: %s\n"
			   "  MONETARY: %s\n"
			   "  NUMERIC:  %s\n"
			   "  TIME:     %s\n",
			   lc_collate,
			   lc_ctype,
			   lc_messages,
			   lc_monetary,
			   lc_numeric,
			   lc_time);
	}

	umask(077);

	/*
	 * now we are starting to do real work, trap signals so we can clean
	 * up
	 */

	/* some of these are not valid on Windows */
#ifdef SIGHUP
	pqsignal(SIGHUP, trapsig);
#endif
#ifdef SIGINT
	pqsignal(SIGINT, trapsig);
#endif
#ifdef SIGQUIT
	pqsignal(SIGQUIT, trapsig);
#endif
#ifdef SIGTERM
	pqsignal(SIGTERM, trapsig);
#endif

	/* clear this we'll use it in a few lines */
	errno = 0;

	if (!check_data_dir())
	{
		fprintf(stderr,
				"%s: directory \"%s\" exists but is not empty\n"
				"If you want to create a new database system, either "
				"remove or empty\n"
				"the directory \"$PGDATA\" or run $CMDNAME with an "
				"argument other than\n"
				"\"%s\".\n",
				progname, pg_data, pg_data);
		exit(1);
	}

	/*
	 * check_data_dir() called opendir - the errno should still be hanging
	 * around
	 */
	if (errno == ENOENT)
	{
		printf("creating directory \"%s\" ... ", pg_data);

		if (!mkdatadir(NULL))
			exit_nicely();
		else
			check_ok();

		made_new_pgdata = true;
	}

	for (i = 0; i < (sizeof(subdirs) / sizeof(char *)); i++)
	{
		printf("creating directory %s/%s ... ", pg_data, subdirs[i]);

		if (!mkdatadir(subdirs[i]))
			exit_nicely();
		else
			check_ok();
	}

	set_short_version(short_version, NULL);

	set_null_conf();

	/* test connections first because it has more constraints */
	test_connections();
	test_buffers();

	setup_config();

	bootstrap_template1(short_version);

	set_short_version(short_version, "base/1");

	setup_shadow();
	if (pwprompt)
		get_set_pwd();

	unlimit_systables();

	setup_depend();

	setup_sysviews();

	setup_description();

	setup_conversion();

	setup_privileges();

	setup_schema();

	vacuum_db();

	make_template0();

	printf("\nSuccess. You can now start the database server using:\n\n"
		   "    %s%s%s/postmaster -D %s%s%s\n"
		   "or\n"
		   "    %s%s%s/pg_ctl -D %s%s%s -l logfile start\n\n",
			QUOTE_PATH, pgpath, QUOTE_PATH, QUOTE_PATH, pg_data, QUOTE_PATH,
			QUOTE_PATH, pgpath, QUOTE_PATH, QUOTE_PATH, pg_data, QUOTE_PATH);

	return 0;
}
Sat, 8 Nov 2003 12:38:37 -0500 (EST)
Bruce Momjian <pgman@candle.pha.pa.us>
Re: [pgsql-hackers-win32] initdb in C
Tom Lane wrote:
> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> > Recreating the directory only happens on WIN32, where rmdir doesn't
> > allow you to only delete files and subdirectories and not the parent
> > directory.  Non-Win32 does rm -rf dir/*.
> 
> I think we should forget about invoking rm as a subprocess at all, and
> just do the recursive directory walk and unlinks for ourselves.  We
> already have code to do this for copy in copydir.c, and unlink would not
> be any longer.  We will probably be forced into implementing database
> removal for ourselves rather than by 'rm' hacks anyway as soon as
> tablespaces come to pass; so why contort initdb's behavior for a very
> transient implementation savings?

If we want to do that, fine, but I don't want to force the change just
for Win32.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Sat, 8 Nov 2003 12:39:41 -0500 (EST)
Bruce Momjian <pgman@candle.pha.pa.us>
Re: initdb in C
Tom Lane wrote:
> Bruce Momjian <pgman@candle.pha.pa.us> writes:
> >> Consider also the strong likelihood that the data directory's parent
> >> directory is owned by root,
> 
> > Again, this directory recreate happens only on Win32, an I thought it
> > would be OK there.
> 
> Windows has no concept of directory permissions at all?  I thought the
> more recent versions had at least rudimentary permissions.

I found "del" works for what we need.  I have posted the new code
already.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Sat, 8 Nov 2003 12:39:59 -0500 (EST)
Bruce Momjian <pgman@candle.pha.pa.us>
Re: initdb in C


Tom Lane wrote:

>Bruce Momjian <pgman@candle.pha.pa.us> writes:
>  
>
>>>Consider also the strong likelihood that the data directory's parent
>>>directory is owned by root,
>>>      
>>>
>
>  
>
>>Again, this directory recreate happens only on Win32, an I thought it
>>would be OK there.
>>    
>>
>
>Windows has no concept of directory permissions at all?  I thought the
>more recent versions had at least rudimentary permissions.
>


More than rudimentary on the server versions.

I found this info at http://www.cs.wayne.edu/labPages/modes.htm :


    Windows ACLs

Windows NT and Windows 2000 uses Access Control Lists or ACLs to 
determine what operations a user may perform on a file.  Windows ACLs 
allow one to set permissions with finer control that does the Unix file 
mode.  For example, one can all[ow] a user to append data to a file as 
opposed to overwiting the file.  ACLs also allow one to permit specific 
users to change the permissions on a file.  Perhaps the biggest 
difference is that ACLs allow us to accord permissions on a user-by-user 
basis, rather than the three categories of users permitted by Unix file 
systems.

This info applies to directories as well as plain files AFAIK

cheers

andrew







Sat, 08 Nov 2003 12:55:18 -0500
Andrew Dunstan <andrew@dunslane.net>
Re: initdb in C
Also, I see this at the top of the code:

	 * author: Andrew Dunstan	   mailto:andrew@dunslane.net
	 *
	 * Copyright (C) 2003 Andrew Dunstan
	 * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
	 * Portions Copyright (c) 1994, Regents of the University of California

Can I remove the first copyright?

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Sat, 8 Nov 2003 12:57:25 -0500 (EST)
Bruce Momjian <pgman@candle.pha.pa.us>
Re: [PATCHES] initdb in C


Bruce Momjian wrote:

>Also, I see this at the top of the code:
>
>	 * author: Andrew Dunstan	   mailto:andrew@dunslane.net
>	 *
>	 * Copyright (C) 2003 Andrew Dunstan
>	 * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
>	 * Portions Copyright (c) 1994, Regents of the University of California
>
>Can I remove the first copyright?
>  
>

Sure. I wasn't sure what our conventions were on that.

cheers

andrew


Sat, 08 Nov 2003 13:00:56 -0500
Andrew Dunstan <andrew@dunslane.net>
Re: [PATCHES] initdb in C


Bruce Momjian wrote:

>
>
>I ran some tests using XP "CMD" and found:
>
>	"C:\test"
>and
>	"\test"
>
>works but:
>
>	"test"
>
>does not work.  Since I see that the output always has a leading path,
>

On Windows, pgpath is guaranteed to be a full path (see call to 
expanded_path() ) exactly so it works inside quotes using the Windows 
command processor.

> I
>have modified the code to do:
>
>    printf("\nSuccess. You can now start the database server using:\n\n"
>           "    %s%s%s/postmaster -D %s%s%s\n"
>           "or\n"
>           "    %s%s%s/pg_ctl -D %s%s%s -l logfile start\n\n",
>            QUOTE_PATH, pgpath, QUOTE_PATH, QUOTE_PATH, pg_data, QUOTE_PATH,
>            QUOTE_PATH, pgpath, QUOTE_PATH, QUOTE_PATH, pg_data, QUOTE_PATH);
>
>I am attaching the updated initdb.c file.
>
>  
>

The problem with this is that you now have 2 quoted strings. This is 
exactly the problem that I solved inside initdb by passing pg_data via 
the environment rather than on the command line. "help cmd" on XP gives 
you this info:

If /C or /K is specified, then the remainder of the command line after
the switch is processed as a command line, where the following logic is
used to process quote (") characters:

    1.  If all of the following conditions are met, then quote characters
        on the command line are preserved:

        - no /S switch
        - exactly two quote characters
        - no special characters between the two quote characters,
          where special is one of: &<>()@^|
        - there are one or more whitespace characters between the
          the two quote characters
        - the string between the two quote characters is the name
          of an executable file.

    2.  Otherwise, old behavior is to see if the first character is
        a quote character and if so, strip the leading character and
        remove the last quote character on the command line, preserving
        any text after the last quote character.

It is amazingly brain dead and cost me hours and hours of grief trying 
to work out WTF was going on.

Offhand I can't think of a simple guaranteed to work command line for 
the Windows message.

In the last resort we might need to look at having initdb create a .bat 
file or two for us.

:-(((

cheers

andrew


Sat, 08 Nov 2003 14:00:29 -0500
Andrew Dunstan <andrew@dunslane.net>
Re: initdb in C


Andrew Dunstan wrote:

>
>
>
> The problem with this is that you now have 2 quoted strings. 


I take this back. You *can* have multiple quoted strings on the command 
line - it just doesn't work in popen() because it uses the /C flag.

Sorry for my confusion.

cheers

andrew


Sat, 08 Nov 2003 14:20:07 -0500
Andrew Dunstan <andrew@dunslane.net>
Re: initdb in C
Andrew Dunstan wrote:
> 
> 
> Andrew Dunstan wrote:
> 
> >
> >
> >
> > The problem with this is that you now have 2 quoted strings. 
> 
> 
> I take this back. You *can* have multiple quoted strings on the command 
> line - it just doesn't work in popen() because it uses the /C flag.

Oh, OK, that's good.

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Sat, 8 Nov 2003 14:23:27 -0500 (EST)
Bruce Momjian <pgman@candle.pha.pa.us>
Re: initdb in C

Patch applied.  We now have a C version of initdb!

---------------------------------------------------------------------------

Andrew Dunstan wrote:
> 
> 
> Bruce Momjian wrote:
> 
> >Here is a slightly modified version of Andrew's great work in making a C
> >version of initdb.  Other than minor cleanups, the only big change was
> >to remove rmdir handling because we using rm -r and rmdir /s in
> >commands/dbcommands.c, so we might as use the same thing for initdb.c
> >rather than having code that traverses the directory tree doing 'rm'.
> >
> >The other change was to remove the unlink code and instead use
> >port/dirmod.c's version.
> >
> >It passes all the regression tests.  I have also included a diff against
> >Andrew's version so you can see my changes.  It seems Andrew had a very
> >current version of initdb.  The only update he missed was the change to
> >test the number of connections before shared buffers --- I made that
> >change myself.
> >
> >I would like to apply this in the next few days to HEAD before initdb.sh
> >drifts.  We are no longer using the WIN32_DEV branch because all our
> >work is now in 7.5 head.
> >  
> >
> 
> My comments:
> 
> I have no problem with shelling out to rmdir - although my goal was to 
> avoid shelling out to anything other than postgres itself. I think 
> recreating the datadir if we didn't create it initially should be OK in 
> that case, and it makes the code simpler. Not handling dot files in the 
> Unix case should also be fine, as we know the directory was empty to 
> start with and we don't create any.
> 
> Regarding the #endif comments you removed - Peter had asked me to put 
> them in. See 
> http://archives.postgresql.org/pgsql-hackers/2003-10/msg00352.php
> 
> You put a comment on canonicalise_path() asking if it is needed. The 
> short answer is very much "yes". The Windows command processor will 
> accept suitably quoted paths with forward slashes, but barfs badly with 
> mixed forward and back slashes. (See recent discussion on 
> hackers-win32).Removing the trailing slash on a path means we never get 
> ugly double slashes. Feel free to put that info in as a comment if you 
> think it is needed.
> 
> The changes you made for the final message are not going to work for 
> Windows - if pgpath or pg_data contain spaces we need quotes (even on 
> Unix, although there most people know better than to put spaces in 
> names). Also see above about mixed slashes. Also, putting a \ before 
> pg_data will be nasty if it starts with a drive or network specifier - 
> or even without (you might turn a directory specifier into a network 
> drive specifier). It's just a message, though, and we shouldn't hold up 
> for that - we can patch it to get it right.
> 
> (Getting the path and slash thing working portably was by far the 
> biggest headache in this project - the rest was just grunt work for the 
> most part, although I learned a few interesting things.)
> 
> Otherwise I'm fine with this.
> 
> cheers
> 
> andrew
> 
> 
> 
> 
> ---------------------------(end of broadcast)---------------------------
> TIP 6: Have you searched our list archives?
> 
>                http://archives.postgresql.org
> 

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Mon, 10 Nov 2003 18:02:32 -0500 (EST)
Bruce Momjian <pgman@candle.pha.pa.us>
Re: [PATCHES] initdb in C
What's become of initdb's initial note about who you are?

$ initdb
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.
... etc etc...

The first two lines of printout don't seem to be in the "highly
compatible" C version.

			regards, tom lane

Wed, 12 Nov 2003 21:29:00 -0500
Tom Lane <tgl@sss.pgh.pa.us>
Re: [PATCHES] initdb in C 
Apologies. Here's the patch. There may be other very minor differences 
in wording, too. But if that's all I missed I won't think I did too 
badly :-)

cheers

andrew

Tom Lane wrote:

>What's become of initdb's initial note about who you are?
>
>$ initdb
>The files belonging to this database system will be owned by user "postgres".
>This user must also own the server process.
>... etc etc...
>
>The first two lines of printout don't seem to be in the "highly
>compatible" C version.
>
>  
>
Wed, 12 Nov 2003 23:17:56 -0500
Andrew Dunstan <andrew@dunslane.net>
Re: [PATCHES] initdb in C

ARRGGHH! It has a typo!. new patch shortly.

Andrew Dunstan wrote:

> Apologies. Here's the patch. There may be other very minor differences 
> in wording, too. But if that's all I missed I won't think I did too 
> badly :-)
>
> cheers
>
> andrew
>
> Tom Lane wrote:
>
>> What's become of initdb's initial note about who you are?
>>
>> $ initdb
>> The files belonging to this database system will be owned by user 
>> "postgres".
>> This user must also own the server process.
>> ... etc etc...
>>
>> The first two lines of printout don't seem to be in the "highly
>> compatible" C version.
>>
>>  
>>
>
>  
>


Wed, 12 Nov 2003 23:23:52 -0500
Andrew Dunstan <andrew@dunslane.net>
Re: [PATCHES] initdb in C
OK, this works.

cheers

andrew

Andrew Dunstan wrote:

>
> ARRGGHH! It has a typo!. new patch shortly.
>
> Andrew Dunstan wrote:
>
>> Apologies. Here's the patch. There may be other very minor 
>> differences in wording, too. But if that's all I missed I won't think 
>> I did too badly :-)
>>
>> cheers
>>
>> andrew
>>
>> Tom Lane wrote:
>>
>>> What's become of initdb's initial note about who you are?
>>>
>>> $ initdb
>>> The files belonging to this database system will be owned by user 
>>> "postgres".
>>> This user must also own the server process.
>>> ... etc etc...
>>>
>>> The first two lines of printout don't seem to be in the "highly
>>> compatible" C version.
>>>
Wed, 12 Nov 2003 23:51:04 -0500
Andrew Dunstan <andrew@dunslane.net>
Re: [PATCHES] initdb in C
Note to self: stop working when brain is clearly malfunctioning.

*This* patch works. Please disregard previous.

thanks

andrew

Andrew Dunstan wrote:

>
> OK, this works.
>
> cheers
>
> andrew
>
> Andrew Dunstan wrote:
>
>>
>> ARRGGHH! It has a typo!. new patch shortly.
>>
>> Andrew Dunstan wrote:
>>
>
Thu, 13 Nov 2003 08:14:27 -0500
Andrew Dunstan <andrew@dunslane.net>
Re: [PATCHES] initdb in C

Patch applied.  Thanks.

---------------------------------------------------------------------------


Andrew Dunstan wrote:
> Note to self: stop working when brain is clearly malfunctioning.
> 
> *This* patch works. Please disregard previous.
> 
> thanks
> 
> andrew
> 
> Andrew Dunstan wrote:
> 
> >
> > OK, this works.
> >
> > cheers
> >
> > andrew
> >
> > Andrew Dunstan wrote:
> >
> >>
> >> ARRGGHH! It has a typo!. new patch shortly.
> >>
> >> Andrew Dunstan wrote:
> >>
> >

> ? initdb
> Index: initdb.c
> ===================================================================
> RCS file: /projects/cvsroot/pgsql-server/src/bin/initdb/initdb.c,v
> retrieving revision 1.4
> diff -c -w -r1.4 initdb.c
> *** initdb.c	13 Nov 2003 01:36:00 -0000	1.4
> --- initdb.c	13 Nov 2003 12:52:21 -0000
> ***************
> *** 2275,2280 ****
> --- 2275,2285 ----
>   	check_input(features_file);
>   	check_input(system_views_file);
>   
> + 	printf("The files belonging to this database system will be owned "
> + 	       "by user \"%s\".\n"
> + 	       "This user must also own the server process.\n\n",
> + 	       effective_user);
> + 
>   	setlocales();
>   
>   	if (strcmp(lc_ctype, lc_collate) == 0 &&

> 
> ---------------------------(end of broadcast)---------------------------
> TIP 6: Have you searched our list archives?
> 
>                http://archives.postgresql.org

-- 
  Bruce Momjian                        |  http://candle.pha.pa.us
  pgman@candle.pha.pa.us               |  (610) 359-1001
  +  If your life is a hard drive,     |  13 Roberts Road
  +  Christ can be your backup.        |  Newtown Square, Pennsylvania 19073

Thu, 13 Nov 2003 09:52:50 -0500 (EST)
Bruce Momjian <pgman@candle.pha.pa.us>
Re: [PATCHES] initdb in C