Add our own function for finding Postgres binaries
authorPetr Jelinek <petr@2ndquadrant.com>
Thu, 18 Jun 2015 16:24:01 +0000 (18:24 +0200)
committerPetr Jelinek <petr@2ndquadrant.com>
Thu, 18 Jun 2015 16:24:01 +0000 (18:24 +0200)
We need our own function because the built-in function compares the
binary version including the minor version and since BDR is an extension
it's not realistic to support every possible version combination. The
BDR specific function for finding and validation of Postgres binaries
doesn't do version validation but instead returns major version in the
PG_VERSION_NUM format so it can be easily compared by the caller as
needed.

The pg_dump and pg_restore in bdr_init_replica and pg_ctl and
pg_basebackup check in bdr_init_copy are using the function now.

Makefile.in
bdr_init_copy.c
bdr_init_replica.c
bdr_internal.h
bdr_pgutils.c [new file with mode: 0644]

index 1c9b6304e87a3bf4991518dcbcb1018a9809ce81..68c3b4a55d35e1db7f6b4d70ea9fcdc01c393e3d 100644 (file)
@@ -80,6 +80,7 @@ OBJS = \
    bdr_locks.o \
    bdr_nodecache.o \
    bdr_output.o \
+   bdr_pgutils.o \
    bdr_relcache.o \
    bdr_remotecalls.o \
    bdr_shmem.o \
@@ -125,7 +126,7 @@ bdr_version.h: bdr_version.h.in
 
 bdr.o: bdr_version.h
 
-bdr_init_copy: bdr_init_copy.o bdr_common.o
+bdr_init_copy: bdr_init_copy.o bdr_common.o bdr_pgutils.o
    $(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(libpq_pgport) $(LIBS) -o $@$(X)
 
 scripts/bdr_initial_load: scripts/bdr_initial_load.in
index b19b5d60bba5125870122863599a9a359bff11c7..99bbcb2558522681473573609c771f294caf9824 100644 (file)
@@ -570,7 +570,7 @@ run_pg_ctl(const char *arg)
 {
    int          ret;
    PQExpBuffer  cmd = createPQExpBuffer();
-   char        *exec_path = find_other_exec_or_die(argv0, "pg_ctl", "pg_ctl (PostgreSQL) " PG_VERSION "\n");
+   char        *exec_path = find_other_exec_or_die(argv0, "pg_ctl", NULL);
 
    appendPQExpBuffer(cmd, "%s %s -D \"%s\" -s", exec_path, arg, data_dir);
 
@@ -595,7 +595,7 @@ run_basebackup(const char *remote_connstr, const char *data_dir)
 {
    int          ret;
    PQExpBuffer  cmd = createPQExpBuffer();
-   char        *exec_path = find_other_exec_or_die(argv0, "pg_basebackup", "pg_basebackup (PostgreSQL) " PG_VERSION "\n");
+   char        *exec_path = find_other_exec_or_die(argv0, "pg_basebackup", NULL);
 
    appendPQExpBuffer(cmd, "%s -D \"%s\" -d \"%s\" -X s -P", exec_path, data_dir, remote_connstr);
 
@@ -1521,19 +1521,22 @@ copy_file(char *fromfile, char *tofile)
    free(buffer);
 }
 
-/*
- * Utility functions taken from pg_ctl
- */
 
 static char *
 find_other_exec_or_die(const char *argv0, const char *target, const char *versionstr)
 {
    int         ret;
    char       *found_path;
+   uint32      bin_version;
 
    found_path = pg_malloc(MAXPGPATH);
 
-   if ((ret = find_other_exec(argv0, target, versionstr, found_path)) < 0)
+   if (versionstr)
+       ret = find_other_exec(argv0, target, versionstr, found_path);
+   else
+       ret = bdr_find_other_exec(argv0, target, &bin_version, found_path);
+
+   if (ret < 0)
    {
        char        full_path[MAXPGPATH];
 
@@ -1552,6 +1555,20 @@ find_other_exec_or_die(const char *argv0, const char *target, const char *versio
                           "Check your installation.\n"),
                         target, full_path, progname);
    }
+   else if (!versionstr)
+   {
+       char        full_path[MAXPGPATH];
+
+       if (find_my_exec(argv0, full_path) < 0)
+           strlcpy(full_path, progname, sizeof(full_path));
+
+       if (bin_version / 100 != PG_VERSION_NUM / 100)
+           die(_("The program \"%s\" was found by \"%s\"\n"
+                          "but was not the same version as %s.\n"
+                          "Check your installation.\n"),
+                        target, full_path, progname);
+
+   }
 
    return found_path;
 }
index 73dcfe9ac49042dab74fa4aaf4c87ca1f6b460b5..5ef0c686af20b0217cb904d07b985457dfa496df 100644 (file)
@@ -210,6 +210,7 @@ bdr_init_exec_dump_restore(BDRNodeInfo *node,
    StringInfoData origin_dsn;
    StringInfoData local_dsn;
    int   saved_errno;
+   uint32  bin_version;
 
    initStringInfo(&path);
    initStringInfo(&origin_dsn);
@@ -222,27 +223,41 @@ bdr_init_exec_dump_restore(BDRNodeInfo *node,
                        BDR_INIT_REPLICA_CMD " (PostgreSQL " PG_VERSION ", BDR " BDR_VERSION ")\n",
                        &bdr_init_replica_script_path[0]) < 0)
    {
-       elog(ERROR, "bdr: failed to find " BDR_INIT_REPLICA_CMD
+       elog(ERROR, "bdr node init failed to find " BDR_INIT_REPLICA_CMD
             " relative to binary %s or wrong version. Expected (PostgreSQL %s, BDR %s)",
             my_exec_path, PG_VERSION, BDR_VERSION);
    }
 
-   if (find_other_exec(my_exec_path, BDR_DUMP_CMD,
-                       "pg_dump (PostgreSQL) " PG_VERSION "\n",
+   if (bdr_find_other_exec(my_exec_path, BDR_DUMP_CMD,
+                       &bin_version,
                        &bdr_dump_path[0]) < 0)
    {
-       elog(ERROR, "bdr: failed to find " BDR_DUMP_CMD
-            " relative to binary %s or wrong version (expected %s)",
-            my_exec_path, PG_VERSION);
+       elog(ERROR, "bdr node init failed to find " BDR_DUMP_CMD
+            " relative to binary %s",
+            my_exec_path);
+   }
+   if (bin_version / 100 != PG_VERSION_NUM / 100)
+   {
+       elog(ERROR, "bdr node init found " BDR_DUMP_CMD
+            " with wrong major version %d.%d, expected %d.%d",
+            bin_version / 100 / 100, bin_version / 100 % 100,
+            PG_VERSION_NUM / 100 / 100, PG_VERSION_NUM / 100 % 100);
    }
 
-   if (find_other_exec(my_exec_path, BDR_RESTORE_CMD,
-                       BDR_RESTORE_CMD " (PostgreSQL) " PG_VERSION "\n",
+   if (bdr_find_other_exec(my_exec_path, BDR_RESTORE_CMD,
+                       &bin_version,
                        &bdr_restore_path[0]) < 0)
    {
-       elog(ERROR, "bdr: failed to find " BDR_RESTORE_CMD
-            " relative to binary %s or wrong version (expected %s)",
-            my_exec_path, PG_VERSION);
+       elog(ERROR, "bdr node init failed to find " BDR_RESTORE_CMD
+            " relative to binary %s",
+            my_exec_path);
+   }
+   if (bin_version / 100 != PG_VERSION_NUM / 100)
+   {
+       elog(ERROR, "bdr node init found " BDR_RESTORE_CMD
+            " with wrong major version %d.%d, expected %d.%d",
+            bin_version / 100 / 100, bin_version / 100 % 100,
+            PG_VERSION_NUM / 100 / 100, PG_VERSION_NUM / 100 % 100);
    }
 
 
index af61ec01932a5cbb77391ea4331d13984b0040b9..bc494c04984ead675817634731427302480f9945 100644 (file)
@@ -73,4 +73,7 @@ extern void bdr_parse_slot_name(const char *name, uint64 *remote_sysid,
                                Oid *remote_dboid, TimeLineID *remote_tli,
                                Oid *local_dboid);
 
+extern int bdr_find_other_exec(const char *argv0, const char *target,
+                              uint32 *version, char *retpath);
+
 #endif   /* BDR_INTERNAL_H */
diff --git a/bdr_pgutils.c b/bdr_pgutils.c
new file mode 100644 (file)
index 0000000..389569e
--- /dev/null
@@ -0,0 +1,312 @@
+/*-------------------------------------------------------------------------
+ *
+ * bdr_pgutils.c
+ *     This files is used as common place for function that we took
+ *     verbatim from PostgreSQL because they are declared as static and
+ *     we can't use them as API.
+ *     Also the internal API function that use those static functions
+ *     be defined here and should start with bdr_ prefix.
+ *
+ * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *   bdr_pgutils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef FRONTEND
+#include "postgres.h"
+#else
+#include "postgres_fe.h"
+#endif
+
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "access/xlogdefs.h"
+#include "nodes/pg_list.h"
+
+#include "bdr_internal.h"
+
+#ifndef FRONTEND
+#define log_error4(str, param, arg1)   elog(LOG, str, param, arg1)
+#else
+#define log_error4(str, param, arg1)   (fprintf(stderr, str, param, arg1), fputc('\n', stderr))
+#endif
+
+/*
+ * Start function taken from src/common/exec.c
+ */
+
+static int validate_exec(const char *path);
+static char *pipe_read_line(char *cmd, char *line, int maxsize);
+
+/*
+ * validate_exec -- validate "path" as an executable file
+ *
+ * returns 0 if the file is found and no error is encountered.
+ *       -1 if the regular file "path" does not exist or cannot be executed.
+ *       -2 if the file is otherwise valid but cannot be read.
+ */
+static int
+validate_exec(const char *path)
+{
+   struct stat buf;
+   int         is_r;
+   int         is_x;
+
+#ifdef WIN32
+   char        path_exe[MAXPGPATH + sizeof(".exe") - 1];
+
+   /* Win32 requires a .exe suffix for stat() */
+   if (strlen(path) >= strlen(".exe") &&
+       pg_strcasecmp(path + strlen(path) - strlen(".exe"), ".exe") != 0)
+   {
+       strlcpy(path_exe, path, sizeof(path_exe) - 4);
+       strcat(path_exe, ".exe");
+       path = path_exe;
+   }
+#endif
+
+   /*
+    * Ensure that the file exists and is a regular file.
+    *
+    * XXX if you have a broken system where stat() looks at the symlink
+    * instead of the underlying file, you lose.
+    */
+   if (stat(path, &buf) < 0)
+       return -1;
+
+   if (!S_ISREG(buf.st_mode))
+       return -1;
+
+   /*
+    * Ensure that the file is both executable and readable (required for
+    * dynamic loading).
+    */
+#ifndef WIN32
+   is_r = (access(path, R_OK) == 0);
+   is_x = (access(path, X_OK) == 0);
+#else
+   is_r = buf.st_mode & S_IRUSR;
+   is_x = buf.st_mode & S_IXUSR;
+#endif
+   return is_x ? (is_r ? 0 : -2) : -1;
+}
+
+
+/*
+ * Find another program in our binary's directory,
+ * then make sure it is the proper version.
+ *
+ * BDR modified version - returns computed major version number
+ */
+int
+bdr_find_other_exec(const char *argv0, const char *target,
+                   uint32 *version, char *retpath)
+{
+   char        cmd[MAXPGPATH];
+   char        line[100];
+   int         pre_dot,
+               post_dot;
+
+   if (find_my_exec(argv0, retpath) < 0)
+       return -1;
+
+   /* Trim off program name and keep just directory */
+   *last_dir_separator(retpath) = '\0';
+   canonicalize_path(retpath);
+
+   /* Now append the other program's name */
+   snprintf(retpath + strlen(retpath), MAXPGPATH - strlen(retpath),
+            "/%s%s", target, EXE);
+
+   if (validate_exec(retpath) != 0)
+       return -1;
+
+   snprintf(cmd, sizeof(cmd), "\"%s\" -V", retpath);
+
+   if (!pipe_read_line(cmd, line, sizeof(line)))
+       return -1;
+
+   if (sscanf(line, "%*s %*s %d.%d", &pre_dot, &post_dot) != 2)
+       return -2;
+
+   *version = (pre_dot * 100 + post_dot) * 100;
+
+   return 0;
+}
+
+
+/*
+ * The runtime library's popen() on win32 does not work when being
+ * called from a service when running on windows <= 2000, because
+ * there is no stdin/stdout/stderr.
+ *
+ * Executing a command in a pipe and reading the first line from it
+ * is all we need.
+ */
+static char *
+pipe_read_line(char *cmd, char *line, int maxsize)
+{
+#ifndef WIN32
+   FILE       *pgver;
+
+   /* flush output buffers in case popen does not... */
+   fflush(stdout);
+   fflush(stderr);
+
+   errno = 0;
+   if ((pgver = popen(cmd, "r")) == NULL)
+   {
+       perror("popen failure");
+       return NULL;
+   }
+
+   errno = 0;
+   if (fgets(line, maxsize, pgver) == NULL)
+   {
+       if (feof(pgver))
+           fprintf(stderr, "no data was returned by command \"%s\"\n", cmd);
+       else
+           perror("fgets failure");
+       pclose(pgver);          /* no error checking */
+       return NULL;
+   }
+
+   if (pclose_check(pgver))
+       return NULL;
+
+   return line;
+#else                          /* WIN32 */
+
+   SECURITY_ATTRIBUTES sattr;
+   HANDLE      childstdoutrd,
+               childstdoutwr,
+               childstdoutrddup;
+   PROCESS_INFORMATION pi;
+   STARTUPINFO si;
+   char       *retval = NULL;
+
+   sattr.nLength = sizeof(SECURITY_ATTRIBUTES);
+   sattr.bInheritHandle = TRUE;
+   sattr.lpSecurityDescriptor = NULL;
+
+   if (!CreatePipe(&childstdoutrd, &childstdoutwr, &sattr, 0))
+       return NULL;
+
+   if (!DuplicateHandle(GetCurrentProcess(),
+                        childstdoutrd,
+                        GetCurrentProcess(),
+                        &childstdoutrddup,
+                        0,
+                        FALSE,
+                        DUPLICATE_SAME_ACCESS))
+   {
+       CloseHandle(childstdoutrd);
+       CloseHandle(childstdoutwr);
+       return NULL;
+   }
+
+   CloseHandle(childstdoutrd);
+
+   ZeroMemory(&pi, sizeof(pi));
+   ZeroMemory(&si, sizeof(si));
+   si.cb = sizeof(si);
+   si.dwFlags = STARTF_USESTDHANDLES;
+   si.hStdError = childstdoutwr;
+   si.hStdOutput = childstdoutwr;
+   si.hStdInput = INVALID_HANDLE_VALUE;
+
+   if (CreateProcess(NULL,
+                     cmd,
+                     NULL,
+                     NULL,
+                     TRUE,
+                     0,
+                     NULL,
+                     NULL,
+                     &si,
+                     &pi))
+   {
+       /* Successfully started the process */
+       char       *lineptr;
+
+       ZeroMemory(line, maxsize);
+
+       /* Try to read at least one line from the pipe */
+       /* This may require more than one wait/read attempt */
+       for (lineptr = line; lineptr < line + maxsize - 1;)
+       {
+           DWORD       bytesread = 0;
+
+           /* Let's see if we can read */
+           if (WaitForSingleObject(childstdoutrddup, 10000) != WAIT_OBJECT_0)
+               break;          /* Timeout, but perhaps we got a line already */
+
+           if (!ReadFile(childstdoutrddup, lineptr, maxsize - (lineptr - line),
+                         &bytesread, NULL))
+               break;          /* Error, but perhaps we got a line already */
+
+           lineptr += strlen(lineptr);
+
+           if (!bytesread)
+               break;          /* EOF */
+
+           if (strchr(line, '\n'))
+               break;          /* One or more lines read */
+       }
+
+       if (lineptr != line)
+       {
+           /* OK, we read some data */
+           int         len;
+
+           /* If we got more than one line, cut off after the first \n */
+           lineptr = strchr(line, '\n');
+           if (lineptr)
+               *(lineptr + 1) = '\0';
+
+           len = strlen(line);
+
+           /*
+            * If EOL is \r\n, convert to just \n. Because stdout is a
+            * text-mode stream, the \n output by the child process is
+            * received as \r\n, so we convert it to \n.  The server main.c
+            * sets setvbuf(stdout, NULL, _IONBF, 0) which has the effect of
+            * disabling \n to \r\n expansion for stdout.
+            */
+           if (len >= 2 && line[len - 2] == '\r' && line[len - 1] == '\n')
+           {
+               line[len - 2] = '\n';
+               line[len - 1] = '\0';
+               len--;
+           }
+
+           /*
+            * We emulate fgets() behaviour. So if there is no newline at the
+            * end, we add one...
+            */
+           if (len == 0 || line[len - 1] != '\n')
+               strcat(line, "\n");
+
+           retval = line;
+       }
+
+       CloseHandle(pi.hProcess);
+       CloseHandle(pi.hThread);
+   }
+
+   CloseHandle(childstdoutwr);
+   CloseHandle(childstdoutrddup);
+
+   return retval;
+#endif   /* WIN32 */
+}
+
+/*
+ * End function taken from src/common/exec.c
+ */