diff options
author | Michael P | 2012-03-16 00:38:19 +0000 |
---|---|---|
committer | Michael P | 2012-03-16 00:38:19 +0000 |
commit | 6086e355914fdebcd2c002d7ee1d3132582a33f9 (patch) | |
tree | 7a33d952bebeff36232743c44c1c0b6987136d5a | |
parent | 648eede9a64745eba401a9b73a747b893cf781b3 (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.sgmlin | 8 | ||||
-rw-r--r-- | doc-xc/src/sgml/ref/allfiles.sgmlin | 3 | ||||
-rw-r--r-- | doc-xc/src/sgml/ref/initgtm.sgmlin | 205 | ||||
-rw-r--r-- | doc-xc/src/sgml/reference.sgmlin | 3 | ||||
-rw-r--r-- | src/bin/Makefile | 2 | ||||
-rw-r--r-- | src/bin/initgtm/Makefile | 45 | ||||
-rw-r--r-- | src/bin/initgtm/initgtm.c | 1117 |
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; +} |