summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael P2012-03-16 00:38:19 +0000
committerMichael P2012-03-16 00:38:19 +0000
commit6086e355914fdebcd2c002d7ee1d3132582a33f9 (patch)
tree7a33d952bebeff36232743c44c1c0b6987136d5a
parent648eede9a64745eba401a9b73a747b893cf781b3 (diff)
Addition of initgtm: module for GTm/Proxy initialization
inigtm uses the infrastructure of initdb to provide the means to initialize the data folder of a GTM or a GTM proxy. This is particularly useful in case Postgres-XC binaries are installed in a folder where a user cannot have access to it as, like initdb, it copies the sample configuration files and makes them available for GTM instances. initgtm has really a basic infrastructure now, but can be easily extended to provide support for a cluster ID or settings related to GTM that need to be decided at initialization. Documentation is included.
-rw-r--r--doc-xc/src/sgml/bookindex.sgmlin8
-rw-r--r--doc-xc/src/sgml/ref/allfiles.sgmlin3
-rw-r--r--doc-xc/src/sgml/ref/initgtm.sgmlin205
-rw-r--r--doc-xc/src/sgml/reference.sgmlin3
-rw-r--r--src/bin/Makefile2
-rw-r--r--src/bin/initgtm/Makefile45
-rw-r--r--src/bin/initgtm/initgtm.c1117
7 files changed, 1382 insertions, 1 deletions
diff --git a/doc-xc/src/sgml/bookindex.sgmlin b/doc-xc/src/sgml/bookindex.sgmlin
index d5f1c16dfe..69557d6889 100644
--- a/doc-xc/src/sgml/bookindex.sgmlin
+++ b/doc-xc/src/sgml/bookindex.sgmlin
@@ -3589,6 +3589,14 @@
</primaryie>
</indexentry>
+<!## XC>
+<indexentry>
+ <primaryie>initgtm,
+ <ulink url="app-initgtm.html" role="app-initgtm">initgtm</ulink>
+ </primaryie>
+</indexentry>
+<!## end>
+
<indexentry>
<primaryie>input function,
<ulink url="xtypes.html" role="AEN47475">User-Defined Types</ulink>
diff --git a/doc-xc/src/sgml/ref/allfiles.sgmlin b/doc-xc/src/sgml/ref/allfiles.sgmlin
index 3c52748fc7..62f8e8036b 100644
--- a/doc-xc/src/sgml/ref/allfiles.sgmlin
+++ b/doc-xc/src/sgml/ref/allfiles.sgmlin
@@ -192,6 +192,9 @@ Complete list of usable sgml source files in this directory.
<!ENTITY dropuser SYSTEM "dropuser.sgml">
<!ENTITY ecpgRef SYSTEM "ecpg-ref.sgml">
<!ENTITY initdb SYSTEM "initdb.sgml">
+<!## XC>
+<!ENTITY initgtm SYSTEM "initgtm.sgml">
+<!## end>
<!ENTITY pgBasebackup SYSTEM "pg_basebackup.sgml">
<!ENTITY pgConfig SYSTEM "pg_config-ref.sgml">
<!ENTITY pgControldata SYSTEM "pg_controldata.sgml">
diff --git a/doc-xc/src/sgml/ref/initgtm.sgmlin b/doc-xc/src/sgml/ref/initgtm.sgmlin
new file mode 100644
index 0000000000..0f74d0a50b
--- /dev/null
+++ b/doc-xc/src/sgml/ref/initgtm.sgmlin
@@ -0,0 +1,205 @@
+<!--
+doc/src/sgml/ref/initgtm.sgml
+Postgres-XC documentation
+-->
+
+<!## XC>
+<refentry id="APP-INITGTM">
+ <refmeta>
+ <refentrytitle>initgtm</refentrytitle>
+ <manvolnum>1</manvolnum>
+ <refmiscinfo>Application</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>initgtm</refname>
+ <refpurpose>create a new <productname>Postgres-XC</productname> GTM or GTM-Proxy for database cluster</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="app-initgtm">
+ <primary>initgtm</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>initgtm</command>
+ <arg rep="repeat"><replaceable>option</replaceable></arg>
+ <group choice="plain">
+ <arg>--pgdata</arg>
+ <arg>-D </arg>
+ <replaceable>directory</replaceable>
+ </group>
+ <arg choice="plain">-Z <replaceable>nodetype</replaceable></arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1 id="R1-APP-INITGTM-1">
+ <title>
+ Description
+ </title>
+&common;
+ <para>
+ <command>initgtm</command> creates a new GTM or GTM-Proxy node for a
+ <productname>Postgres-XC</productname> database cluster. A database
+ cluster has a unique GTM. A GTM-Proxy acts as an intermediate component
+ between GTM and Postgres-XC nodes to group request messages. Each Coordinator
+ and Datanode of the cluster need to register to GTM when starting up.
+ </para>
+
+ <para>
+ Creating a GTM for cluster consists of creating the directories and files in
+ which the GTM data will live.
+ </para>
+
+ <para>
+ Although <command>initgtm</command> will attempt to create the
+ specified data directory, it might not have permission if the parent
+ directory of the desired data directory is root-owned. To initialize
+ in such a setup, create an empty data directory as root, then use
+ <command>chown</command> to assign ownership of that directory to the
+ database user account, then <command>su</command> to become the
+ database user to run <command>initgtm</command>.
+ </para>
+
+ <para>
+ <command>initgtm</command> must be run as the user that will own the
+ server process, because the server needs to have access to the
+ files and directories that <command>initgtm</command> creates.
+ Since the server cannot be run as root, you must not run
+ <command>initgtm</command> as root either. (It will in fact refuse
+ to do so.)
+ </para>
+
+&xconly;
+ <para>
+ <command>initgtm</> will be performed locally.
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term><option>-D <replaceable class="parameter">directory</replaceable></option></term>
+ <term><option>--pgdata=<replaceable class="parameter">directory</replaceable></option></term>
+ <listitem>
+ <para>
+ This option specifies the directory where the GTM data
+ should be stored. Data folder and node type are the only information
+ required by <command>initgtm</command>. You can avoid writing it by
+ setting the <envar>PGDATA</envar> environment variable.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-Z <replaceable class="parameter">nodetype</replaceable></option></term>
+ <listitem>
+ <para>
+ This option specifies the node type which is initialized. It is possible to
+ specify gtm to set up a GTM node, or gtm_proxy to set up a GTM-Proxy.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </para>
+
+ <para>
+ Other, less commonly used, parameters are also available:
+
+ <variablelist>
+ <varlistentry>
+ <term><option>-d</option></term>
+ <term><option>--debug</option></term>
+ <listitem>
+ <para>
+ Print debugging output from the bootstrap backend and a few other
+ messages of lesser interest for the general public.
+ The bootstrap backend is the program <command>initgtm</command>
+ uses to create the catalog tables. This option generates a tremendous
+ amount of extremely boring output.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-n</option></term>
+ <term><option>--noclean</option></term>
+ <listitem>
+ <para>
+ By default, when <command>initgtm</command>
+ determines that an error prevented it from completely creating GTM data
+ it removes any files it might have created before discovering
+ that it cannot finish the job. This option inhibits tidying-up and is
+ thus useful for debugging.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-V</></term>
+ <term><option>--version</></term>
+ <listitem>
+ <para>
+ Print the <application>initgtm</application> version and exit.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>-?</></term>
+ <term><option>--help</></term>
+ <listitem>
+ <para>
+ Show help about <application>initgtm</application> command line
+ arguments, and exit.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </para>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Environment</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><envar>PGDATA</envar></term>
+
+ <listitem>
+ <para>
+ Specifies the directory where the GTM data is to be
+ stored; can be overridden using the <option>-D</option> option.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+
+ </refsect1>
+
+ <refsect1>
+ <title>Notes</title>
+
+&xconly;
+ <para>
+ <command>initgtm</> runs only locally.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+
+ <simplelist type="inline">
+ <member><xref linkend="app-gtm-ctl"></member>
+ </simplelist>
+ </refsect1>
+
+</refentry>
+<!## end>
diff --git a/doc-xc/src/sgml/reference.sgmlin b/doc-xc/src/sgml/reference.sgmlin
index 3e501e13c0..d33f5b0e2e 100644
--- a/doc-xc/src/sgml/reference.sgmlin
+++ b/doc-xc/src/sgml/reference.sgmlin
@@ -292,6 +292,9 @@
&pgxcDdl;
<!## end>
&initdb;
+<!## XC>
+ &initgtm;
+<!## end>
&pgControldata;
&pgCtl;
&pgResetxlog;
diff --git a/src/bin/Makefile b/src/bin/Makefile
index 3809412a2d..72844c81c1 100644
--- a/src/bin/Makefile
+++ b/src/bin/Makefile
@@ -13,7 +13,7 @@ subdir = src/bin
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
-SUBDIRS = initdb pg_ctl pg_dump \
+SUBDIRS = initdb initgtm pg_ctl pg_dump \
psql scripts pg_config pg_controldata pg_resetxlog pg_basebackup
ifeq ($(PORTNAME), win32)
SUBDIRS+=pgevent
diff --git a/src/bin/initgtm/Makefile b/src/bin/initgtm/Makefile
new file mode 100644
index 0000000000..e11544a807
--- /dev/null
+++ b/src/bin/initgtm/Makefile
@@ -0,0 +1,45 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/bin/initgtm
+#
+# Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/bin/initgtm/Makefile
+#
+#-------------------------------------------------------------------------
+
+PGFILEDESC = "initgtm - initialize a new GTM node in cluster"
+PGAPPICON=win32
+
+subdir = src/bin/initgtm
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+override CPPFLAGS := -DFRONTEND -I$(libpq_srcdir) $(CPPFLAGS)
+
+OBJS= initgtm.o pqsignal.o $(WIN32RES)
+
+all: initgtm
+
+initgtm: $(OBJS) | submake-libpgport
+ $(CC) $(CFLAGS) $(OBJS) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)
+
+pqsignal.c: % : $(top_srcdir)/src/interfaces/libpq/%
+ rm -f $@ && $(LN_S) $< .
+
+install: all installdirs
+ $(INSTALL_PROGRAM) initgtm$(X) '$(DESTDIR)$(bindir)/initgtm$(X)'
+
+installdirs:
+ $(MKDIR_P) '$(DESTDIR)$(bindir)'
+
+uninstall:
+ rm -f '$(DESTDIR)$(bindir)/initgtm$(X)'
+
+clean distclean maintainer-clean:
+ rm -f initgtm$(X) $(OBJS) pqsignal.c
+
+
+# ensure that changes in datadir propagate into object file
+initgtm.o: initgtm.c $(top_builddir)/src/Makefile.global
diff --git a/src/bin/initgtm/initgtm.c b/src/bin/initgtm/initgtm.c
new file mode 100644
index 0000000000..0c3b442aa4
--- /dev/null
+++ b/src/bin/initgtm/initgtm.c
@@ -0,0 +1,1117 @@
+/*-------------------------------------------------------------------------
+ *
+ * initgtm --- initialize a GTM (Global transaction manager) installation
+ *
+ * initgtm creates (initializes) a GTM/GTM-Proxy for Postgres-XC database
+ * cluster (site, instance, installation, whatever).
+ *
+ * Note:
+ * The program has some memory leakage - it isn't worth cleaning it up.
+ *
+ * This code is released under the terms of the PostgreSQL License.
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 2010-2012 Nippon Telegraph and Telephone Corporation
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/bin/initgtm/initgtm.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <locale.h>
+#include <signal.h>
+#include <time.h>
+
+#include "libpq/pqsignal.h"
+#include "mb/pg_wchar.h"
+#include "getaddrinfo.h"
+#include "getopt_long.h"
+#include "miscadmin.h"
+
+#include "postgres.h"
+
+/*
+ * these values are passed in by makefile defines
+ */
+static char *share_path = NULL;
+
+/* values to be obtained from arguments */
+static char *pg_data = "";
+static bool debug = false;
+static bool noclean = false;
+static bool show_setting = false;
+
+/* internal vars */
+static const char *progname;
+static char *conf_file; /* Used by GTM */
+static bool made_new_pgdata = false;
+static bool found_existing_pgdata = false;
+static bool caught_signal = false;
+static bool output_failed = false;
+static int output_errno = 0;
+
+/* about instance initialized */
+static bool is_gtm = true; /* GTM or proxy */
+
+/* defaults for all nodes */
+static int n_port = 6667;
+static char *n_name = "one";
+
+/* defaults for proxies */
+static int gtm_port = 6666;
+static char *gtm_host = "localhost";
+
+/* path to 'initgtm' binary directory */
+static char bin_path[MAXPGPATH];
+
+static void *pg_malloc(size_t size);
+static char *xstrdup(const char *s);
+static char **replace_token(char **lines,
+ const char *token, const char *replacement);
+
+#ifndef HAVE_UNIX_SOCKETS
+static char **filter_lines_with_token(char **lines, const char *token);
+#endif
+static char **readfile(const char *path);
+static void writefile(char *path, char **lines);
+static void exit_nicely(void);
+static char *get_id(void);
+static bool mkdatadir(const char *subdir);
+static void set_input(char **dest, char *filename);
+static void check_input(char *path);
+static void set_null_conf(void);
+static void setup_config(void);
+static void trapsig(int signum);
+static void check_ok(void);
+static void usage(const char *progname);
+
+#ifdef WIN32
+static int CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo);
+#endif
+
+#ifndef WIN32
+#define QUOTE_PATH ""
+#define DIR_SEP "/"
+#else
+#define QUOTE_PATH "\""
+#define DIR_SEP "\\"
+#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 *
+pg_malloc(size_t size)
+{
+ void *result;
+
+ result = malloc(size);
+ if (!result)
+ {
+ fprintf(stderr, _("%s: out of memory\n"), progname);
+ exit(1);
+ }
+ return result;
+}
+
+static char *
+xstrdup(const char *s)
+{
+ char *result;
+
+ result = strdup(s);
+ if (!result)
+ {
+ fprintf(stderr, _("%s: out of memory\n"), progname);
+ exit(1);
+ }
+ return result;
+}
+
+/*
+ * 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, const char *token, const char *replacement)
+{
+ int numlines = 1;
+ int i;
+ char **result;
+ int toklen,
+ replen,
+ diff;
+
+ for (i = 0; lines[i]; i++)
+ numlines++;
+
+ result = (char **) pg_malloc(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 *) pg_malloc(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;
+}
+
+/*
+ * make a copy of lines without any that contain the token
+ *
+ * a sort of poor man's grep -v
+ */
+#ifndef HAVE_UNIX_SOCKETS
+static char **
+filter_lines_with_token(char **lines, const char *token)
+{
+ int numlines = 1;
+ int i,
+ src,
+ dst;
+ char **result;
+
+ for (i = 0; lines[i]; i++)
+ numlines++;
+
+ result = (char **) pg_malloc(numlines * sizeof(char *));
+
+ for (src = 0, dst = 0; src < numlines; src++)
+ {
+ if (lines[src] == NULL || strstr(lines[src], token) == NULL)
+ result[dst++] = lines[src];
+ }
+
+ return result;
+}
+#endif
+
+/*
+ * get the lines from a text file
+ */
+static char **
+readfile(const char *path)
+{
+ FILE *infile;
+ int maxlength = 1,
+ linelen = 0;
+ int nlines = 0;
+ char **result;
+ char *buffer;
+ int c;
+
+ if ((infile = fopen(path, "r")) == NULL)
+ {
+ fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"),
+ progname, path, strerror(errno));
+ 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 **) pg_malloc((nlines + 1) * sizeof(char *));
+ buffer = (char *) pg_malloc(maxlength + 1);
+
+ /* now reprocess the file and store the lines */
+ rewind(infile);
+ nlines = 0;
+ while (fgets(buffer, maxlength + 1, infile) != NULL)
+ result[nlines++] = xstrdup(buffer);
+
+ fclose(infile);
+ free(buffer);
+ result[nlines] = NULL;
+
+ return result;
+}
+
+/*
+ * write an array of lines to a file
+ *
+ * This is only used to write text files. Use fopen "w" not PG_BINARY_W
+ * so that the resulting configuration files are nicely editable on Windows.
+ */
+static void
+writefile(char *path, char **lines)
+{
+ FILE *out_file;
+ char **line;
+
+ if ((out_file = fopen(path, "w")) == NULL)
+ {
+ fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
+ progname, path, strerror(errno));
+ exit_nicely();
+ }
+ for (line = lines; *line != NULL; line++)
+ {
+ if (fputs(*line, out_file) < 0)
+ {
+ fprintf(stderr, _("%s: could not write file \"%s\": %s\n"),
+ progname, path, strerror(errno));
+ exit_nicely();
+ }
+ free(*line);
+ }
+ if (fclose(out_file))
+ {
+ fprintf(stderr, _("%s: could not write file \"%s\": %s\n"),
+ progname, path, strerror(errno));
+ exit_nicely();
+ }
+}
+
+
+/*
+ * clean up any files we created on failure
+ * if we created the data directory remove it too
+ */
+static void
+exit_nicely(void)
+{
+ 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 to remove data directory\n"),
+ progname);
+ }
+ else if (found_existing_pgdata)
+ {
+ fprintf(stderr,
+ _("%s: removing contents of data directory \"%s\"\n"),
+ progname, pg_data);
+ if (!rmtree(pg_data, false))
+ fprintf(stderr, _("%s: failed to remove contents of data directory\n"),
+ progname);
+ }
+ /* otherwise died during startup, do nothing! */
+ }
+ else
+ {
+ if (made_new_pgdata || found_existing_pgdata)
+ fprintf(stderr,
+ _("%s: data directory \"%s\" not removed at user's request\n"),
+ progname, pg_data);
+ }
+
+ exit(1);
+}
+
+/*
+ * find the current user
+ *
+ * on unix make sure it isn't really root
+ */
+static char *
+get_id(void)
+{
+#ifndef WIN32
+
+ struct passwd *pw;
+
+ if (geteuid() == 0) /* 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);
+ }
+
+ pw = getpwuid(geteuid());
+ if (!pw)
+ {
+ fprintf(stderr,
+ _("%s: could not obtain information about current user: %s\n"),
+ progname, strerror(errno));
+ exit(1);
+ }
+#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;
+ if (!GetUserName(pw->pw_name, &pwname_size))
+ {
+ fprintf(stderr, _("%s: could not get current user name: %s\n"),
+ progname, strerror(errno));
+ exit(1);
+ }
+#endif
+
+ return xstrdup(pw->pw_name);
+}
+
+
+/*
+ * make the data directory (or one of its subdirectories if subdir is not NULL)
+ */
+static bool
+mkdatadir(const char *subdir)
+{
+ char *path;
+
+ path = pg_malloc(strlen(pg_data) + 2 +
+ (subdir == NULL ? 0 : strlen(subdir)));
+
+ if (subdir != NULL)
+ sprintf(path, "%s/%s", pg_data, subdir);
+ else
+ strcpy(path, pg_data);
+
+ if (pg_mkdir_p(path, S_IRWXU) == 0)
+ return true;
+
+ fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
+ progname, path, strerror(errno));
+
+ return false;
+}
+
+
+/*
+ * set name of given input file variable under data directory
+ */
+static void
+set_input(char **dest, char *filename)
+{
+ *dest = pg_malloc(strlen(share_path) + strlen(filename) + 2);
+ sprintf(*dest, "%s/%s", share_path, filename);
+}
+
+/*
+ * check that given input file exists
+ */
+static void
+check_input(char *path)
+{
+ struct stat statbuf;
+
+ if (stat(path, &statbuf) != 0)
+ {
+ if (errno == ENOENT)
+ {
+ fprintf(stderr,
+ _("%s: file \"%s\" does not exist\n"), progname, path);
+ fprintf(stderr,
+ _("This might mean you have a corrupted installation or identified\n"
+ "the wrong directory with the invocation option -L.\n"));
+ }
+ else
+ {
+ fprintf(stderr,
+ _("%s: could not access file \"%s\": %s\n"), progname, path,
+ strerror(errno));
+ fprintf(stderr,
+ _("This might mean you have a corrupted installation or identified\n"
+ "the wrong directory with the invocation option -L.\n"));
+ }
+ exit(1);
+ }
+ if (!S_ISREG(statbuf.st_mode))
+ {
+ fprintf(stderr,
+ _("%s: file \"%s\" is not a regular file\n"), progname, path);
+ fprintf(stderr,
+ _("This might mean you have a corrupted installation or identified\n"
+ "the wrong directory with the invocation option -L.\n"));
+ exit(1);
+ }
+}
+
+
+/*
+ * set up an empty config file so we can check config settings by launching
+ * a test backend
+ */
+static void
+set_null_conf(void)
+{
+ FILE *conf_file;
+ char *path;
+
+ path = pg_malloc(strlen(pg_data) + 17);
+ if (is_gtm)
+ sprintf(path, "%s/gtm.conf", pg_data);
+ else
+ sprintf(path, "%s/gtm_proxy.conf", pg_data);
+ conf_file = fopen(path, PG_BINARY_W);
+ if (conf_file == NULL)
+ {
+ fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
+ progname, path, strerror(errno));
+ exit_nicely();
+ }
+ if (fclose(conf_file))
+ {
+ fprintf(stderr, _("%s: could not write file \"%s\": %s\n"),
+ progname, path, strerror(errno));
+ exit_nicely();
+ }
+ free(path);
+}
+
+
+/*
+ * set up all the config files
+ */
+static void
+setup_config(void)
+{
+ char **conflines;
+ char repltok[100];
+ char path[MAXPGPATH];
+
+ fputs(_("creating configuration files ... "), stdout);
+ fflush(stdout);
+
+ /* gtm.conf/gtm_proxy.conf */
+
+ conflines = readfile(conf_file);
+
+ /* Set options dedicated to both nodes */
+ snprintf(repltok, sizeof(repltok), "nodename = '%s'", n_name);
+ conflines = replace_token(conflines, "#nodename = ''", repltok);
+
+ snprintf(repltok, sizeof(repltok), "port = %d", n_port);
+ conflines = replace_token(conflines, "#port = 6666", repltok);
+
+ if (is_gtm)
+ snprintf(path, sizeof(path), "%s/gtm.conf", pg_data);
+ else
+ {
+ /* Set options dedicated to Proxy */
+ snprintf(repltok, sizeof(repltok), "gtm_host = '%s'", gtm_host);
+ conflines = replace_token(conflines, "#gtm_host = ''", repltok);
+
+ snprintf(repltok, sizeof(repltok), "gtm_port = %d", gtm_port);
+ conflines = replace_token(conflines, "#gtm_port =", repltok);
+
+ snprintf(path, sizeof(path), "%s/gtm_proxy.conf", pg_data);
+ }
+
+ writefile(path, conflines);
+ chmod(path, S_IRUSR | S_IWUSR);
+
+ free(conflines);
+
+ 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);
+ caught_signal = true;
+}
+
+/*
+ * call exit_nicely() if we got a signal, or else output "ok".
+ */
+static void
+check_ok(void)
+{
+ if (caught_signal)
+ {
+ printf(_("caught signal\n"));
+ fflush(stdout);
+ exit_nicely();
+ }
+ else if (output_failed)
+ {
+ printf(_("could not write to child process: %s\n"),
+ strerror(output_errno));
+ fflush(stdout);
+ exit_nicely();
+ }
+ else
+ {
+ /* all seems well */
+ printf(_("ok\n"));
+ fflush(stdout);
+ }
+}
+
+/* Hack to suppress a warning about %x from some versions of gcc */
+static inline size_t
+my_strftime(char *s, size_t max, const char *fmt, const struct tm * tm)
+{
+ return strftime(s, max, fmt, tm);
+}
+
+#ifdef WIN32
+
+/*
+ * Replace 'needle' with 'replacement' in 'str' . Note that the replacement
+ * is done in-place, so 'replacement' must be shorter than 'needle'.
+ */
+static void
+strreplace(char *str, char *needle, char *replacement)
+{
+ char *s;
+
+ s = strstr(str, needle);
+ if (s != NULL)
+ {
+ int replacementlen = strlen(replacement);
+ char *rest = s + strlen(needle);
+
+ memcpy(s, replacement, replacementlen);
+ memmove(s + replacementlen, rest, strlen(rest) + 1);
+ }
+}
+#endif /* WIN32 */
+
+
+#ifdef WIN32
+typedef BOOL (WINAPI * __CreateRestrictedToken) (HANDLE, DWORD, DWORD, PSID_AND_ATTRIBUTES, DWORD, PLUID_AND_ATTRIBUTES, DWORD, PSID_AND_ATTRIBUTES, PHANDLE);
+
+/* Windows API define missing from some versions of MingW headers */
+#ifndef DISABLE_MAX_PRIVILEGE
+#define DISABLE_MAX_PRIVILEGE 0x1
+#endif
+
+/*
+ * Create a restricted token and execute the specified process with it.
+ *
+ * Returns 0 on failure, non-zero on success, same as CreateProcess().
+ *
+ * On NT4, or any other system not containing the required functions, will
+ * NOT execute anything.
+ */
+static int
+CreateRestrictedProcess(char *cmd, PROCESS_INFORMATION *processInfo)
+{
+ BOOL b;
+ STARTUPINFO si;
+ HANDLE origToken;
+ HANDLE restrictedToken;
+ SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY};
+ SID_AND_ATTRIBUTES dropSids[2];
+ __CreateRestrictedToken _CreateRestrictedToken = NULL;
+ HANDLE Advapi32Handle;
+
+ ZeroMemory(&si, sizeof(si));
+ si.cb = sizeof(si);
+
+ Advapi32Handle = LoadLibrary("ADVAPI32.DLL");
+ if (Advapi32Handle != NULL)
+ {
+ _CreateRestrictedToken = (__CreateRestrictedToken) GetProcAddress(Advapi32Handle, "CreateRestrictedToken");
+ }
+
+ if (_CreateRestrictedToken == NULL)
+ {
+ fprintf(stderr, "WARNING: cannot create restricted tokens on this platform\n");
+ if (Advapi32Handle != NULL)
+ FreeLibrary(Advapi32Handle);
+ return 0;
+ }
+
+ /* Open the current token to use as a base for the restricted one */
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &origToken))
+ {
+ fprintf(stderr, "Failed to open process token: %lu\n", GetLastError());
+ return 0;
+ }
+
+ /* Allocate list of SIDs to remove */
+ ZeroMemory(&dropSids, sizeof(dropSids));
+ if (!AllocateAndInitializeSid(&NtAuthority, 2,
+ SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0,
+ 0, &dropSids[0].Sid) ||
+ !AllocateAndInitializeSid(&NtAuthority, 2,
+ SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_POWER_USERS, 0, 0, 0, 0, 0,
+ 0, &dropSids[1].Sid))
+ {
+ fprintf(stderr, "Failed to allocate SIDs: %lu\n", GetLastError());
+ return 0;
+ }
+
+ b = _CreateRestrictedToken(origToken,
+ DISABLE_MAX_PRIVILEGE,
+ sizeof(dropSids) / sizeof(dropSids[0]),
+ dropSids,
+ 0, NULL,
+ 0, NULL,
+ &restrictedToken);
+
+ FreeSid(dropSids[1].Sid);
+ FreeSid(dropSids[0].Sid);
+ CloseHandle(origToken);
+ FreeLibrary(Advapi32Handle);
+
+ if (!b)
+ {
+ fprintf(stderr, "Failed to create restricted token: %lu\n", GetLastError());
+ return 0;
+ }
+
+#ifndef __CYGWIN__
+ AddUserToTokenDacl(restrictedToken);
+#endif
+
+ if (!CreateProcessAsUser(restrictedToken,
+ NULL,
+ cmd,
+ NULL,
+ NULL,
+ TRUE,
+ CREATE_SUSPENDED,
+ NULL,
+ NULL,
+ &si,
+ processInfo))
+
+ {
+ fprintf(stderr, "CreateProcessAsUser failed: %lu\n", GetLastError());
+ return 0;
+ }
+
+ return ResumeThread(processInfo->hThread);
+}
+#endif
+
+/*
+ * print help text
+ */
+static void
+usage(const char *progname)
+{
+ printf(_("%s initializes a GTM for Postgres-XC database cluster.\n\n"), progname);
+ printf(_("Usage:\n"));
+ printf(_(" %s [NODE-TYPE] [OPTION]... [DATADIR]\n"), progname);
+ printf(_("\nOptions:\n"));
+ printf(_(" [-D, --pgdata=]DATADIR location for this GTM node\n"));
+ printf(_(" [-Z]NODE-TYPE can be \"gtm\" or \"gtm_proxy\""));
+ printf(_("\nLess commonly used options:\n"));
+ printf(_(" -d, --debug generate lots of debugging output\n"));
+ printf(_(" -n, --noclean do not clean up after errors\n"));
+ printf(_(" -s, --show show internal settings\n"));
+ printf(_("\nOther options:\n"));
+ printf(_(" -?, --help show this help, then exit\n"));
+ printf(_(" -V, --version output version information, then exit\n"));
+}
+
+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'},
+ {"help", no_argument, NULL, '?'},
+ {"version", no_argument, NULL, 'V'},
+ {"debug", no_argument, NULL, 'd'},
+ {"show", no_argument, NULL, 's'},
+ {"noclean", no_argument, NULL, 'n'},
+ {NULL, 0, NULL, 0}
+ };
+
+ int c;
+ int option_index;
+ char *effective_user;
+ char bin_dir[MAXPGPATH];
+ char full_path[MAXPGPATH];
+ char *pg_data_native;
+ bool node_type_specified = false;
+
+ progname = get_progname(argv[0]);
+ set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("initgtm"));
+
+ if (argc > 1)
+ {
+ if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
+ {
+ usage(progname);
+ exit(0);
+ }
+ if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
+ {
+ puts("initgtm (Postgres-XC) " PGXC_VERSION);
+ exit(0);
+ }
+ }
+
+ /* process command-line options */
+
+ while ((c = getopt_long(argc, argv, "dD:nsZ:", long_options, &option_index)) != -1)
+ {
+ switch (c)
+ {
+ case 'D':
+ pg_data = xstrdup(optarg);
+ break;
+ case 'd':
+ debug = true;
+ printf(_("Running in debug mode.\n"));
+ break;
+ case 'n':
+ noclean = true;
+ printf(_("Running in noclean mode. Mistakes will not be cleaned up.\n"));
+ break;
+ case 's':
+ show_setting = true;
+ break;
+ case 'Z':
+ if (strcmp(xstrdup(optarg), "gtm") == 0)
+ is_gtm = true;
+ else if (strcmp(xstrdup(optarg), "gtm_proxy") == 0)
+ is_gtm = false;
+ else
+ {
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+ progname);
+ exit(1);
+ }
+ node_type_specified = true;
+ break;
+ default:
+ /* getopt_long already emitted a complaint */
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+ progname);
+ exit(1);
+ }
+ }
+
+ /* Non-option argument specifies data directory */
+ if (optind < argc)
+ {
+ pg_data = xstrdup(argv[optind]);
+ optind++;
+ }
+
+ if (optind < argc)
+ {
+ fprintf(stderr, _("%s: too many command-line arguments (first is \"%s\")\n"),
+ progname, argv[optind + 1]);
+ fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
+ progname);
+ exit(1);
+ }
+
+ /* Check on definition of GTM data folder */
+ if (strlen(pg_data) == 0)
+ {
+ fprintf(stderr,
+ _("%s: no data directory specified\n"
+ "You must identify the directory where the data for this GTM system\n"
+ "will reside. Do this with either the invocation option -D or the\n"
+ "environment variable PGDATA.\n"),
+ progname);
+ exit(1);
+ }
+
+ if (!node_type_specified)
+ {
+ fprintf(stderr,
+ _("%s: no node type specified\n"
+ "You must identify the node type chosen for initialization.\n"
+ "Do this with the invocation option -Z by choosing \"gtm\" or"
+ "\"gtm_proxy\"\n"),
+ progname);
+ exit(1);
+ }
+
+ pg_data_native = pg_data;
+ canonicalize_path(pg_data);
+
+#ifdef WIN32
+
+ /*
+ * Before we execute another program, make sure that we are running with a
+ * restricted token. If not, re-execute ourselves with one.
+ */
+
+ if ((restrict_env = getenv("PG_RESTRICT_EXEC")) == NULL
+ || strcmp(restrict_env, "1") != 0)
+ {
+ PROCESS_INFORMATION pi;
+ char *cmdline;
+
+ ZeroMemory(&pi, sizeof(pi));
+
+ cmdline = xstrdup(GetCommandLine());
+
+ putenv("PG_RESTRICT_EXEC=1");
+
+ if (!CreateRestrictedProcess(cmdline, &pi))
+ {
+ fprintf(stderr, "Failed to re-exec with restricted token: %lu.\n", GetLastError());
+ }
+ else
+ {
+ /*
+ * Successfully re-execed. Now wait for child process to capture
+ * exitcode.
+ */
+ DWORD x;
+
+ CloseHandle(pi.hThread);
+ WaitForSingleObject(pi.hProcess, INFINITE);
+
+ if (!GetExitCodeProcess(pi.hProcess, &x))
+ {
+ fprintf(stderr, "Failed to get exit code from subprocess: %lu\n", GetLastError());
+ exit(1);
+ }
+ exit(x);
+ }
+ }
+#endif
+
+ /* Find full path name */
+ if (find_my_exec(argv[0], full_path) < 0)
+ strlcpy(full_path, progname, sizeof(full_path));
+
+ /* store binary directory */
+ strcpy(bin_path, full_path);
+ *last_dir_separator(bin_path) = '\0';
+ canonicalize_path(bin_path);
+
+ if (!share_path)
+ {
+ share_path = pg_malloc(MAXPGPATH);
+ get_share_path(bin_path, share_path);
+ }
+ else if (!is_absolute_path(share_path))
+ {
+ fprintf(stderr, _("%s: input file location must be an absolute path\n"), progname);
+ exit(1);
+ }
+
+ canonicalize_path(share_path);
+
+ effective_user = get_id();
+ /* TODO: separate the case of GTM and GTM-Proxy depending on options specified */
+ if (is_gtm)
+ set_input(&conf_file, "gtm.conf.sample");
+ else
+ set_input(&conf_file, "gtm_proxy.conf.sample");
+
+ if (show_setting || debug)
+ {
+ fprintf(stderr,
+ "VERSION=%s\n"
+ "GTMDATA=%s\nshare_path=%s\nGTMPATH=%s\n"
+ "GTM_CONF_SAMPLE=%s\n",
+ PGXC_VERSION,
+ pg_data, share_path, bin_path,
+ conf_file);
+ if (show_setting)
+ exit(0);
+ }
+
+ check_input(conf_file);
+
+ printf(_("The files belonging to this GTM system will be owned "
+ "by user \"%s\".\n"
+ "This user must also own the server process.\n\n"),
+ effective_user);
+
+ printf("\n");
+
+ umask(S_IRWXG | S_IRWXO);
+
+ /*
+ * 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
+
+ /* Ignore SIGPIPE when writing to backend, so we can clean up */
+#ifdef SIGPIPE
+ pqsignal(SIGPIPE, SIG_IGN);
+#endif
+
+ switch (pg_check_dir(pg_data))
+ {
+ case 0:
+ /* PGDATA not there, must create it */
+ printf(_("creating directory %s ... "),
+ pg_data);
+ fflush(stdout);
+
+ if (!mkdatadir(NULL))
+ exit_nicely();
+ else
+ check_ok();
+
+ made_new_pgdata = true;
+ break;
+
+ case 1:
+ /* Present but empty, fix permissions and use it */
+ printf(_("fixing permissions on existing directory %s ... "),
+ pg_data);
+ fflush(stdout);
+
+ if (chmod(pg_data, S_IRWXU) != 0)
+ {
+ fprintf(stderr, _("%s: could not change permissions of directory \"%s\": %s\n"),
+ progname, pg_data, strerror(errno));
+ exit_nicely();
+ }
+ else
+ check_ok();
+
+ found_existing_pgdata = true;
+ break;
+
+ case 2:
+ /* Present and not empty */
+ fprintf(stderr,
+ _("%s: directory \"%s\" exists but is not empty\n"),
+ progname, pg_data);
+ fprintf(stderr,
+ _("If you want to create a new GTM system, either remove or empty\n"
+ "the directory \"%s\" or run %s\n"
+ "with an argument other than \"%s\".\n"),
+ pg_data, progname, pg_data);
+ exit(1); /* no further message needed */
+
+ default:
+ /* Trouble accessing directory */
+ fprintf(stderr, _("%s: could not access directory \"%s\": %s\n"),
+ progname, pg_data, strerror(errno));
+ exit_nicely();
+ }
+
+ /* Select suitable configuration settings */
+ set_null_conf();
+
+ /* Now create all the text config files */
+ setup_config();
+
+ /* Get directory specification used to start this executable */
+ strcpy(bin_dir, argv[0]);
+ get_parent_directory(bin_dir);
+
+ if (is_gtm)
+ printf(_("\nSuccess. You can now start the GTM server using:\n\n"
+ " %s%s%sgtm%s -D %s%s%s\n"
+ "or\n"
+ " %s%s%sgtm_ctl%s -Z gtm -D %s%s%s -l logfile start\n\n"),
+ QUOTE_PATH, bin_dir, (strlen(bin_dir) > 0) ? DIR_SEP : "", QUOTE_PATH,
+ QUOTE_PATH, pg_data_native, QUOTE_PATH,
+ QUOTE_PATH, bin_dir, (strlen(bin_dir) > 0) ? DIR_SEP : "", QUOTE_PATH,
+ QUOTE_PATH, pg_data_native, QUOTE_PATH);
+ else
+ printf(_("\nSuccess. You can now start the GTM proxy server using:\n\n"
+ " %s%s%sgtm_proxy%s -D %s%s%s\n"
+ "or\n"
+ " %s%s%sgtm_ctl%s -Z gtm_proxy -D %s%s%s -l logfile start\n\n"),
+ QUOTE_PATH, bin_dir, (strlen(bin_dir) > 0) ? DIR_SEP : "", QUOTE_PATH,
+ QUOTE_PATH, pg_data_native, QUOTE_PATH,
+ QUOTE_PATH, bin_dir, (strlen(bin_dir) > 0) ? DIR_SEP : "", QUOTE_PATH,
+ QUOTE_PATH, pg_data_native, QUOTE_PATH);
+
+ return 0;
+}