From 7f6dc1c55eddf5fad8e3bb3033146ec0cf57a670 Mon Sep 17 00:00:00 2001 From: Petr Jelinek Date: Thu, 18 Jun 2015 18:24:01 +0200 Subject: [PATCH] Add our own function for finding Postgres binaries 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 | 3 +- bdr_init_copy.c | 29 ++++- bdr_init_replica.c | 37 ++++-- bdr_internal.h | 3 + bdr_pgutils.c | 312 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 366 insertions(+), 18 deletions(-) create mode 100644 bdr_pgutils.c diff --git a/Makefile.in b/Makefile.in index 735d83e47a..42f3a81b12 100644 --- a/Makefile.in +++ b/Makefile.in @@ -67,6 +67,7 @@ OBJS = \ bdr_label.o \ bdr_locks.o \ bdr_output.o \ + bdr_pgutils.o \ bdr_relcache.o \ bdr_remotecalls.o \ bdr_shmem.o \ @@ -112,7 +113,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 diff --git a/bdr_init_copy.c b/bdr_init_copy.c index b19b5d60bb..99bbcb2558 100644 --- a/bdr_init_copy.c +++ b/bdr_init_copy.c @@ -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; } diff --git a/bdr_init_replica.c b/bdr_init_replica.c index 8e887cfdc3..05d22ab187 100644 --- a/bdr_init_replica.c +++ b/bdr_init_replica.c @@ -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); } diff --git a/bdr_internal.h b/bdr_internal.h index af61ec0193..bc494c0498 100644 --- a/bdr_internal.h +++ b/bdr_internal.h @@ -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 index 0000000000..389569e189 --- /dev/null +++ b/bdr_pgutils.c @@ -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 +#include + +#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 + */ -- 2.39.5