blob: 589e9883d0a187bcecca7072391afd91ecee56ad [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*-------------------------------------------------------------------------
*
* cdb_dump.c
* cdb_dump is a utility for dumping out a cdb cluster of postgres databases
*
* Portions Copyright (c) 2005-2010, Greenplum inc
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
*-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
#include <unistd.h>
#include <signal.h>
#include <regex.h>
#include <ctype.h>
#include <pthread.h>
#include "getopt_long.h"
#include "dumputils.h"
#include "fe-auth.h"
#include "cdb_table.h"
#include "cdb_dump_util.h"
#include "cdb_backup_state.h"
#include "cdb_dump.h"
#include "cdb_dump_include.h"
/* This is necessary on platforms where optreset variable is not available.
* Look at "man getopt" documentation for details.
*/
#ifndef HAVE_OPTRESET
int optreset;
#endif
/*
* static helper functions. See each function body for a description.
*/
static char *addPassThroughParm(char Parm, const char *pszValue, char *pszPassThroughParmString);
static char *addPassThroughLongParm(const char *Parm, const char *pszValue, char *pszPassThroughParmString);
static char *shellEscape(const char *shellArg, PQExpBuffer escapeBuf);
static bool createThreadParmArray(int nCount, ThreadParmArray * pParmAr);
static void decrementFinishedLaunchCount(void);
static void decrementFinishedLockingCount(void);
static bool startTransactionAndLock(PGconn *pConn);
static bool execCommit(PGconn *pConn);
static char* getRelativeFileName(char* longFileName);
static void addFileNameParam(const char* flag, char* file_name, InputOptions* pInputOpts);
static bool fillInputOptions(int argc, char **argv, InputOptions * pInputOpts);
static void freeThreadParmArray(ThreadParmArray * pParmAr);
static void help(const char *progname);
static void installSignalHandlers(void);
static void myHandler(SIGNAL_ARGS);
static bool parmValNeedsQuotes(const char *Value);
static int reportBackupResults(InputOptions inputopts, ThreadParmArray * pParmAr);
static void spinOffThreads(PGconn *pConn, InputOptions * pInputOpts, const SegmentDatabaseArray *psegDBAr,
ThreadParmArray * pParmAr);
static void *threadProc(void *arg);
static bool testPartitioningSupport(void);
static bool transformPassThroughParms(InputOptions * pInputOpts);
static bool copyFilesToSegments(InputOptions * pInputOpts, SegmentDatabaseArray *segDBAr);
static int getRemoteVersion(void);
/*
* static and extern global variables left over from pg_dump
*/
extern char *optarg;
extern int optind,
opterr;
static int dump_inserts; /* dump data using proper insert strings */
static int column_inserts; /* put attr names into insert strings */
static char *dumpencoding = NULL;
static bool schemaOnly;
static bool dataOnly;
static bool aclsSkip;
static char *selectTableName = NULL; /* name of a single table to dump */
static char *selectSchemaName = NULL; /* name of a single schema to dump */
static char *tableFileName = NULL; /* file name with tables to dump (--table-file) */
static char *excludeTableFileName = NULL; /* file name with tables to exclude (--exclude-table-file) */
char g_comment_start[10];
char g_comment_end[10];
static bool bForcePassword = false;
static bool bIgnoreVersion = false;
static int rsyncable = false;
static const char *logInfo = "INFO";
static const char *logWarn = "WARN";
static const char *logError = "ERROR";
static const char *logFatal = "FATAL";
/*
* Global variables needed by cdb_dump_include routines.
*/
PGconn *g_conn; /* the database connection */
const char *progname; /* the program name */
bool g_verbose; /* User wants verbose narration of our
* activities. */
/* default, if no "inclusion" switches appear, is to dump everything */
static bool include_everything = true;
/*
* Thread synchronization variables
*/
static int nThreadsFinishedLaunch;
static int nThreadsFinishedLocking;
static pthread_cond_t MyCondVar = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t MyMutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t MyCondVar2 = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t MyMutex2 = PTHREAD_MUTEX_INITIALIZER;
static bool g_b_SendCancelMessage = false;
static const char *pszAgent = "gp_dump_agent";
int
main(int argc, char **argv)
{
int failCount = -1;
int rc = 0; /* return code */
int remote_version;
InputOptions inputOpts; /* command line parameters */
SegmentDatabaseArray segDBAr; /* array of the segdbs from the master
* mpp tables */
ThreadParmArray parmAr; /* array of the thread parameters */
PGconn *master_db_conn = NULL;
memset(&inputOpts, 0, sizeof(inputOpts));
memset(&segDBAr, 0, sizeof(segDBAr));
memset(&parmAr, 0, sizeof(parmAr));
/* Parse command line for options */
if (!fillInputOptions(argc, argv, &inputOpts))
goto cleanup;
mpp_msg(logInfo, progname, "Command line options analyzed.\n");
/* Connect to database on the master */
mpp_msg(logInfo, progname, "Connecting to master database on host %s port %s database %s.\n",
StringNotNull(inputOpts.pszPGHost, "localhost"),
StringNotNull(inputOpts.pszPGPort, "5432"),
StringNotNull(inputOpts.pszDBName, "?"));
master_db_conn = g_conn = GetMasterConnection(progname, inputOpts.pszDBName, inputOpts.pszPGHost,
inputOpts.pszPGPort, inputOpts.pszUserName,
bForcePassword, bIgnoreVersion, false);
if (master_db_conn == NULL)
goto cleanup;
remote_version = getRemoteVersion();
if (!remote_version)
goto cleanup;
mpp_msg(logInfo, progname, "Reading Greenplum Database configuration info from master database.\n");
if (!GetDumpSegmentDatabaseArray(master_db_conn, remote_version, &segDBAr, inputOpts.actors, inputOpts.pszRawDumpSet, dataOnly, schemaOnly))
goto cleanup;
/*
* Process the schema and table include/exclude lists and determine the
* OIDs of the tables to be dumped.
*/
convert_patterns_to_oids();
/*
* make changes and verifications (if any) needed to be made before
* shipping options to the agents
*/
if (!transformPassThroughParms(&inputOpts))
goto cleanup;
/*
* Copy necessary files to segments (from --table-file / -- exclude-table-file options)
* Files will be copied to the same location on all segments: /tmp/filename
* If a file with the same name exists on a segment, it will be overridden.
*
*/
if (!copyFilesToSegments(&inputOpts, &segDBAr))
goto cleanup;
/* Start a transaction (to be released in spinOffThreads) */
if (!startTransactionAndLock(master_db_conn))
goto cleanup;
/* Install the SIGINT and SIGTERM handlers */
installSignalHandlers();
/*
* Create the threads to spawn local segment dumps using gp_dump_agent.
* Inside this function we release the lock taken in
* startTransactionAndLock. When this function returns, all the agents
* have completed operation.
*/
spinOffThreads(master_db_conn, &inputOpts, &segDBAr, &parmAr);
/* Clean-up memory allocated */
cleanup:
/* Produce results report */
failCount = reportBackupResults(inputOpts, &parmAr);
freeThreadParmArray(&parmAr);
FreeSegmentDatabaseArray(&segDBAr);
FreeInputOptions(&inputOpts);
/* Clean-up synchronization variables */
pthread_mutex_destroy(&MyMutex);
pthread_cond_destroy(&MyCondVar);
pthread_mutex_destroy(&MyMutex2);
pthread_cond_destroy(&MyCondVar2);
if (master_db_conn != NULL)
PQfinish(master_db_conn);
rc = (failCount == 0 ? 0 : 1);
exit(rc);
}
/*
* addPassThroughParm: this function adds to the string of pass-through parameters.
* These get sent to each backend and get passed to the gp_dump_agent program.
*/
static char *
addPassThroughParm(char Parm, const char *pszValue, char *pszPassThroughParmString)
{
char *pszRtn;
bool bFirstTime = (pszPassThroughParmString == NULL);
if (pszValue != NULL)
{
if (parmValNeedsQuotes(pszValue))
{
PQExpBuffer valueBuf = createPQExpBuffer();
if (bFirstTime)
pszRtn = MakeString("-%c \"%s\"", Parm, shellEscape(pszValue, valueBuf));
else
pszRtn = MakeString("%s -%c \"%s\"", pszPassThroughParmString, Parm, shellEscape(pszValue, valueBuf));
destroyPQExpBuffer(valueBuf);
}
else
{
if (bFirstTime)
pszRtn = MakeString("-%c %s", Parm, pszValue);
else
pszRtn = MakeString("%s -%c %s", pszPassThroughParmString, Parm, pszValue);
}
}
else
{
if (bFirstTime)
pszRtn = MakeString("-%c", Parm);
else
pszRtn = MakeString("%s -%c", pszPassThroughParmString, Parm);
}
return pszRtn;
}
/*
* addPassThroughLongParm: this function adds a long option to the string of pass-through parameters.
* These get sent to each backend and get passed to the gp_dump_agent program.
*/
static char *
addPassThroughLongParm(const char *Parm, const char *pszValue, char *pszPassThroughParmString)
{
char *pszRtn;
bool bFirstTime = (pszPassThroughParmString == NULL);
if (pszValue != NULL)
{
if (parmValNeedsQuotes(pszValue))
{
PQExpBuffer valueBuf = createPQExpBuffer();
if (bFirstTime)
pszRtn = MakeString("--%s \"%s\"", Parm, shellEscape(pszValue, valueBuf));
else
pszRtn = MakeString("%s --%s \"%s\"", pszPassThroughParmString, Parm, shellEscape(pszValue, valueBuf));
destroyPQExpBuffer(valueBuf);
}
else
{
if (bFirstTime)
pszRtn = MakeString("--%s %s", Parm, pszValue);
else
pszRtn = MakeString("%s --%s %s", pszPassThroughParmString, Parm, pszValue);
}
}
else
{
if (bFirstTime)
pszRtn = MakeString("--%s", Parm);
else
pszRtn = MakeString("%s --%s", pszPassThroughParmString, Parm);
}
return pszRtn;
}
/*
* Error if any child tables were specified in an input option (-t or -T)
*/
//static bool isChildSelected(char opt, SimpleOidList list)
//{
// PQExpBuffer query = createPQExpBuffer();
// PGresult *res;
// SimpleOidListCell *cell;
//
// if (list.head != NULL)
// {
// for (cell = list.head; cell; cell = cell->next)
// {
// int numtups = 0;
//
// appendPQExpBuffer(query, "select 1 from pg_partition_rule "
// "where parchildrelid ='%u'::pg_catalog.oid; ",
// cell->val);
//
// res = PQexec(g_conn, query->data);
// check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK);
// numtups = PQntuples(res);
// PQclear(res);
// resetPQExpBuffer(query);
//
// if (numtups == 1)
// {
// mpp_err_msg_cache(logError, progname, "specifying child tables in -%c option is not allowed\n", opt);
// return false;
// }
//
// }
// }
//
// destroyPQExpBuffer(query);
//
// return true;
//}
/*
* Given an input option -t (include table) or -T (exclude table) check if this is
* a parent table of a partitioned table and if it is, add its children to the list
* as well (see MPP-7540).
*/
static void
addChildrenToPassThrough(InputOptions *pInputOpts, char opt, SimpleOidList list)
{
PQExpBuffer query = createPQExpBuffer();
PGresult *res;
SimpleOidListCell *cell;
int i;
/* if table oids were found, add children for all parent tables */
if (list.head != NULL)
{
for (cell = list.head; cell; cell = cell->next)
{
appendPQExpBuffer(query, "SELECT p.partitiontablename "
"FROM pg_partitions p, pg_class c, pg_namespace n "
"WHERE p.partitionschemaname = n.nspname "
"AND n.oid = c.relnamespace "
"AND c.relname = p.tablename "
"AND c.oid ='%u'::pg_catalog.oid;", cell->val);
res = PQexec(g_conn, query->data);
check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK);
for (i = 0; i < PQntuples(res); i++)
{
char *childname = PQgetvalue(res, i, 0);
pInputOpts->pszPassThroughParms = addPassThroughParm(opt, childname, pInputOpts->pszPassThroughParms);
}
PQclear(res);
resetPQExpBuffer(query);
}
}
destroyPQExpBuffer(query);
}
/*
* Any transformation of the input options that is passed down to the segdb dump
* agents should be done in here.
*
* For now, there's only a partitioning related transformation, see addChildrenToPassThrough
* for more information.
*
* returns 'false' if error was reported. 'true' otherwise.
*/
static bool
transformPassThroughParms(InputOptions *pInputOpts)
{
g_gp_supportsPartitioning = testPartitioningSupport();
if (g_gp_supportsPartitioning)
{
/* if(!isChildSelected('t', table_include_oids) || */
/* !isChildSelected('T', table_exclude_oids)) */
/* return false; */
addChildrenToPassThrough(pInputOpts, 't', table_include_oids);
addChildrenToPassThrough(pInputOpts, 'T', table_exclude_oids);
}
return true;
}
/*
* Copy --table-file and --exclude-table-file files
* to all segments.
*
* returns 'false' if error was reported. 'true' otherwise.
*/
static bool
copyFilesToSegments(InputOptions *pInputOpts, SegmentDatabaseArray *segDBAr)
{
if ((tableFileName == NULL) && (excludeTableFileName == NULL))
return true; /* nothing to do */
/* get destination list */
const char *hostfile_name = "/tmp/gp_dump_hostfilelist.txt";
FILE* hostfile = fopen(hostfile_name, "w");
if (hostfile == NULL)
{
mpp_err_msg_cache(logError, progname, "Can't open hostfile file %s",
hostfile_name);
return false;
}
for (int i = 0; i < segDBAr->count; ++i)
{
mpp_msg(logInfo, progname, "Writing to hostfile: line #%d, segment %s@%s:%d\n",
i,
segDBAr->pData[i].pszDBUser,
segDBAr->pData[i].pszHost,
segDBAr->pData[i].port);
fprintf(hostfile, "%s\n", segDBAr->pData[i].pszHost);
}
fclose(hostfile);
/* run gpscp to copy file(s) to all segments */
char *cmdLine = MakeString("gpscp -f %s %s %s =:/tmp/.",
hostfile_name,
StringNotNull(tableFileName, ""),
StringNotNull(excludeTableFileName, ""));
mpp_msg(logInfo, progname, "Copying table lists files with gpscp cmd: %s\n", cmdLine);
/* This execs a shell that runs the gpscp program */
int result = system(cmdLine);
mpp_msg(logInfo, progname, "Finished running gpscp cmd");
/* delete hostfile name */
unlink(hostfile_name);
free(cmdLine);
if (result == -1)
{
mpp_err_msg_cache(logError, progname, "failed to send table files to segments (%d, %d)",
result, errno);
return false;
}
return true;
}
/*
* shellEscape: Returns a string in which the shell-significant quoted-string characters are
* escaped. The resulting string, if used as a SQL statement component, should be quoted
* using the PG $$ delimiter (or as an E-string with the '\' characters escaped again).
*
* This function escapes the following characters: '"', '$', '`', '\', '!'.
*
* The PQExpBuffer escapeBuf is used for assembling the escaped string and is reset at the
* start of this function.
*
* The return value of this function is the data area from excapeBuf.
*/
static char *
shellEscape(const char *shellArg, PQExpBuffer escapeBuf)
{
const char *s = shellArg;
const char escape = '\\';
resetPQExpBuffer(escapeBuf);
/*
* Copy the shellArg into the escapeBuf prepending any characters
* requiring an escape with the escape character.
*/
while (*s != '\0')
{
switch (*s)
{
case '"':
case '$':
case '\\':
case '`':
case '!':
appendPQExpBufferChar(escapeBuf, escape);
}
appendPQExpBufferChar(escapeBuf, *s);
s++;
}
return escapeBuf->data;
}
/*
* createThreadParmArray: This function initializes the count and pData elements of the ThreadParmArray.
*/
bool
createThreadParmArray(int nCount, ThreadParmArray * pParmAr)
{
int i;
pParmAr->count = nCount;
pParmAr->pData = (ThreadParm *) calloc(nCount, sizeof(ThreadParm));
if (pParmAr->pData == NULL)
return false;
for (i = 0; i < nCount; i++)
{
ThreadParm *p = &pParmAr->pData[i];
p->thread = 0;
p->pSourceSegDBData = NULL; /* not used for dump */
p->pTargetSegDBData = NULL;
p->pOptionsData = NULL;
p->bSuccess = true;
p->pszErrorMsg = NULL;
p->pszRemoteBackupPath = NULL;
}
return true;
}
/*
* decrementFinishedLaunchCount: This function handles the decrementing
* of the finished launch count. It uses a mutex to protect the int variable.
* If this is the last thread to get here, the variable will be 0, and the
* condition variable is signaled, so the main thread wakes up.
*/
void
decrementFinishedLaunchCount(void)
{
pthread_mutex_lock(&MyMutex);
nThreadsFinishedLaunch--;
if (nThreadsFinishedLaunch == 0)
pthread_cond_signal(&MyCondVar);
pthread_mutex_unlock(&MyMutex);
}
void
decrementFinishedLockingCount(void)
{
pthread_mutex_lock(&MyMutex2);
nThreadsFinishedLocking--;
if (nThreadsFinishedLocking == 0)
pthread_cond_signal(&MyCondVar2);
pthread_mutex_unlock(&MyMutex2);
}
/*
* startTransactionAndLock: This function uses the PGconn connection to begin
* a transaction. After opening the transaction, we grab a lock on pg_class
*
* The locks obtained by this routine are released through COMMIT by
* spinOffThreads().
*/
bool
startTransactionAndLock(PGconn *pMasterConn)
{
bool bRtn = false;
PQExpBuffer pQry = NULL;
PGresult *pRes = NULL;
mpp_msg(logInfo, progname, "Starting a transaction on master database %s.\n", pMasterConn->dbName);
pQry = createPQExpBuffer();
appendPQExpBuffer(pQry, "BEGIN;");
pRes = PQexec(pMasterConn, pQry->data);
if (pRes == NULL)
{
mpp_err_msg_cache(logError, progname, "no result from server: %s", PQerrorMessage(pMasterConn));
goto cleanup;
}
if (PQresultStatus(pRes) != PGRES_COMMAND_OK)
{
mpp_err_msg_cache(logError, progname, "bad result from server: %s", PQerrorMessage(pMasterConn));
goto cleanup;
}
PQclear(pRes);
pRes = NULL;
resetPQExpBuffer(pQry);
/*
* Now grab a lock on pg_class on the master to avoid colliding with
* concurrent transaction DROP/CREATE. This lock will later be release
* when COMMIT is run.
*/
mpp_msg(logInfo, progname, "Getting a lock on pg_class in database %s.\n", pMasterConn->dbName);
pQry = createPQExpBuffer();
appendPQExpBuffer(pQry, "LOCK TABLE pg_catalog.pg_class IN EXCLUSIVE MODE;");
pRes = PQexec(pMasterConn, pQry->data);
if (pRes == NULL)
{
mpp_err_msg_cache(logError, progname, "no result from server: %s", PQerrorMessage(pMasterConn));
goto cleanup;
}
if (PQresultStatus(pRes) != PGRES_COMMAND_OK)
{
mpp_err_msg_cache(logError, progname, "bad result from server: %s", PQerrorMessage(pMasterConn));
goto cleanup;
}
bRtn = true;
cleanup:
if (pRes != NULL)
PQclear(pRes);
if (pQry != NULL)
destroyPQExpBuffer(pQry);
return bRtn;
}
/*
* testPartitioningSupport - tests whether or not the current GP
* database includes support for partitioning.
*/
static bool
testPartitioningSupport(void)
{
PQExpBuffer query;
PGresult *res;
bool isSupported;
query = createPQExpBuffer();
appendPQExpBuffer(query, "SELECT 1 FROM pg_class WHERE relname = 'pg_partition' and relnamespace = 11;");
res = PQexec(g_conn, query->data);
check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK);
isSupported = (PQntuples(res) == 1);
PQclear(res);
destroyPQExpBuffer(query);
return isSupported;
}
static int
getRemoteVersion(void)
{
const char *remoteversion_str;
int remoteversion;
remoteversion_str = PQparameterStatus(g_conn, "server_version");
if (!remoteversion_str)
return 0; /* error */
remoteversion = parse_version(remoteversion_str);
return remoteversion;
}
/*
* execCommit: This function simply issues a commit command using the pConn PGconn* parameter.
*/
bool
execCommit(PGconn *pConn)
{
bool bRtn = false;
PQExpBuffer pQry = NULL;
PGresult *pRes = NULL;
pQry = createPQExpBuffer();
appendPQExpBuffer(pQry, " COMMIT");
pRes = PQexec(pConn, pQry->data);
if (pRes == NULL)
{
mpp_err_msg_cache(logError, progname, "no result from server: %s\n", PQerrorMessage(pConn));
goto cleanup;
}
if (PQresultStatus(pRes) != PGRES_COMMAND_OK)
{
mpp_err_msg_cache(logError, progname, "bad result from server: %s\n", PQerrorMessage(pConn));
goto cleanup;
}
bRtn = true;
cleanup:
if (pRes != NULL)
PQclear(pRes);
if (pQry != NULL)
destroyPQExpBuffer(pQry);
return bRtn;
}
char* getRelativeFileName(char* longFileName)
{
char *slash = strrchr(longFileName, '/');
if (slash == NULL)
return longFileName;
/* return file name from after last '/' */
return slash + 1;
}
/*
* extract file name without path from original file_name,
* append to a directory_name,
* and add to to passed params with given flag option.
*/
void addFileNameParam(const char* flag, char* file_name, InputOptions* pInputOpts)
{
static char* short_file_name = NULL;
static char* tmp_name = NULL;
static char* directory_name = "/tmp/";
short_file_name = getRelativeFileName(file_name);
tmp_name = MakeString("%s%s", directory_name, short_file_name);
pInputOpts->pszPassThroughParms =
addPassThroughLongParm(flag, tmp_name, pInputOpts->pszPassThroughParms);
tableFileName = Safe_strdup(file_name);
free(tmp_name);
}
/*
* fillInputOptions: This function gets the command line options, and
* stores the values in the InputOptions structure. Much of this code
* was copied from pg_dump.
*/
bool
fillInputOptions(int argc, char **argv, InputOptions * pInputOpts)
{
bool bRtn = false;
int c;
char *pszFormat = "p";
bool bOutputBlobs = false;
bool bOids = false;
/* int compressLevel = -1; */
int outputClean = 0;
/* int outputCreate = 0; */
int outputNoOwner = 0;
static int disable_triggers = 0;
static int auth = 0;
static int skipcat = 0;
bool bSawCDB_S_Option;
int lenCmdLineParms;
int i;
static struct option long_options[] =
{
{"data-only", no_argument, NULL, 'a'},
/* {"blobs", no_argument, NULL, 'b'}, */
{"clean", no_argument, NULL, 'c'},
/* {"create", no_argument, NULL, 'C'}, */
/*
* {"file", required_argument, NULL, 'f'},
* {"format", required_argument, NULL, 'F'},
*/
{"encoding", required_argument, NULL, 'E'},
{"host", required_argument, NULL, 'h'},
{"ignore-version", no_argument, NULL, 'i'},
{"no-reconnect", no_argument, NULL, 'R'},
{"oids", no_argument, NULL, 'o'},
{"no-owner", no_argument, NULL, 'O'},
{"port", required_argument, NULL, 'p'},
{"schema", required_argument, NULL, 'n'},
{"exclude-schema", required_argument, NULL, 'N'},
{"schema-only", no_argument, NULL, 's'},
{"superuser", required_argument, NULL, 'S'},
{"table", required_argument, NULL, 't'},
{"exclude-table", required_argument, NULL, 'T'},
{"password", no_argument, NULL, 'W'},
{"username", required_argument, NULL, 'U'},
{"verbose", no_argument, NULL, 'v'},
{"no-privileges", no_argument, NULL, 'x'},
{"no-acl", no_argument, NULL, 'x'},
/* {"compress", required_argument, NULL, 'Z'}, */
{"help", no_argument, NULL, '?'},
{"version", no_argument, NULL, 'V'},
/*
* the following options don't have an equivalent short option letter
*/
{"use-set-session-authorization", no_argument, &auth, 1},
{"attribute-inserts", no_argument, &column_inserts, 1},
{"column-inserts", no_argument, &column_inserts, 1},
{"disable-triggers", no_argument, &disable_triggers, 1},
{"inserts", no_argument, &dump_inserts, 1},
{"gp-catalog-skip", no_argument, &skipcat, 1},
/*
* the following are Greenplum Database specific, and don't have an
* equivalent short option
*/
{"gp-c", no_argument, NULL, 1},
/* {"gp-cf", required_argument, NULL, 2}, */
{"gp-d", required_argument, NULL, 3},
{"gp-r", required_argument, NULL, 4},
{"gp-s", required_argument, NULL, 5},
{"rsyncable", no_argument, &rsyncable, 1},
{"table-file", required_argument, NULL, 7},
{"exclude-table-file", required_argument, NULL, 8},
{NULL, 0, NULL, 0}
};
int optindex;
/* Initialize option fields */
memset(pInputOpts, 0, sizeof(InputOptions));
pInputOpts->actors = SET_NO_MIRRORS; /* backup primaries only by
* default */
g_verbose = false;
strcpy(g_comment_start, "-- ");
g_comment_end[0] = '\0';
dataOnly = schemaOnly = dump_inserts = column_inserts = false;
progname = (char *) get_progname(argv[0]);
bSawCDB_S_Option = false;
/*if (argc == 1)
{
help(progname);
goto cleanup;
}*/
if (argc > 1)
{
if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
{
help(progname);
exit(0);
}
if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
{
puts("gp_dump (Greenplum Database) " GP_VERSION);
exit(0);
}
}
/*
* Record the command line parms as a string for documentation in the
* ouput report
*/
lenCmdLineParms = 0;
for (i = 1; i < argc; i++)
{
if (i > 1)
lenCmdLineParms += 1;
lenCmdLineParms += strlen(argv[i]);
if (parmValNeedsQuotes(argv[i]))
lenCmdLineParms += 2;
}
pInputOpts->pszCmdLineParms = (char *) malloc(lenCmdLineParms + 1);
if (pInputOpts->pszCmdLineParms == NULL)
{
mpp_err_msg_cache(logError, progname, "error allocating memory for pInputOpts->pszCmdLineParms\n");
goto cleanup;
}
memset(pInputOpts->pszCmdLineParms, 0, lenCmdLineParms + 1);
for (i = 1; i < argc; i++)
{
if (i > 1)
strcat(pInputOpts->pszCmdLineParms, " ");
if (parmValNeedsQuotes(argv[i]))
{
strcat(pInputOpts->pszCmdLineParms, "\"");
strcat(pInputOpts->pszCmdLineParms, argv[i]);
strcat(pInputOpts->pszCmdLineParms, "\"");
}
else
strcat(pInputOpts->pszCmdLineParms, argv[i]);
}
while ((c = getopt_long(argc, argv, "acdDE:h:in:N:oOp:RsS:t:T:U:vWxX:",
long_options, &optindex)) != -1)
{
switch (c)
{
case 'a': /* Dump data only */
dataOnly = true;
pInputOpts->pszPassThroughParms = addPassThroughParm(c, NULL, pInputOpts->pszPassThroughParms);
break;
/* case 'b': // Dump blobs
bOutputBlobs = true;
pInputOpts->pszPassThroughParms = addPassThroughParm( c, NULL, pInputOpts->pszPassThroughParms );
break;
*/
case 'c': /* clean (i.e., drop) schema prior to create */
outputClean = 1;
pInputOpts->pszPassThroughParms = addPassThroughParm(c, NULL, pInputOpts->pszPassThroughParms);
break;
/*
case 'C':
outputCreateDB = 1;
break;
*/
case 'd': /* dump data as proper insert strings */
dump_inserts = true;
pInputOpts->pszPassThroughParms = addPassThroughParm(c, NULL, pInputOpts->pszPassThroughParms);
fprintf(stderr," --inserts is preferred over -d. -d is deprecated.\n");
break;
case 'D': /* dump data as proper insert strings with
* attr names */
dump_inserts = true;
column_inserts = true;
pInputOpts->pszPassThroughParms = addPassThroughParm(c, NULL, pInputOpts->pszPassThroughParms);
fprintf(stderr," --column-inserts is preferred over -D. -D is deprecated.\n");
break;
case 'E': /* Dump encoding */
dumpencoding = Safe_strdup(optarg);
pInputOpts->pszPassThroughParms = addPassThroughParm(c, optarg, pInputOpts->pszPassThroughParms);
break;
/* Invalid for gp_dump.
case 'f':
iopt->filename = optarg;
break;
case 'F':
pszFormat = optarg;
pInputOpts->pszPassThroughParms = addPassThroughParm( c, optarg, pInputOpts->pszPassThroughParms );
break;
*/
case 'h': /* server host */
pInputOpts->pszPGHost = Safe_strdup(optarg);
break;
case 'i': /* ignore database version mismatch */
bIgnoreVersion = true;
pInputOpts->pszPassThroughParms = addPassThroughParm(c, NULL, pInputOpts->pszPassThroughParms);
break;
case 'n': /* Dump data for this schema only */
selectSchemaName = Safe_strdup(optarg);
pInputOpts->pszPassThroughParms = addPassThroughParm(c, optarg, pInputOpts->pszPassThroughParms);
simple_string_list_append(&schema_include_patterns, optarg);
include_everything = false;
break;
case 'N': /* exclude schema(s) */
pInputOpts->pszPassThroughParms = addPassThroughParm(c, optarg, pInputOpts->pszPassThroughParms);
simple_string_list_append(&schema_exclude_patterns, optarg);
break;
case 'o': /* Dump oids */
bOids = true;
pInputOpts->pszPassThroughParms = addPassThroughParm(c, NULL, pInputOpts->pszPassThroughParms);
break;
case 'O': /* Don't reconnect to match owner */
outputNoOwner = 1;
pInputOpts->pszPassThroughParms = addPassThroughParm(c, NULL, pInputOpts->pszPassThroughParms);
break;
case 'p': /* server port */
pInputOpts->pszPGPort = Safe_strdup(optarg);
break;
case 'R':
/* no-op, still accepted for backwards compatibility */
break;
case 's': /* dump schema only */
schemaOnly = true;
pInputOpts->pszPassThroughParms = addPassThroughParm(c, NULL, pInputOpts->pszPassThroughParms);
break;
case 'S': /* Superuser username */
if (strlen(optarg) != 0)
pInputOpts->pszPassThroughParms = addPassThroughParm(c, optarg, pInputOpts->pszPassThroughParms);
break;
case 't': /* Dump data for this table only */
selectTableName = Safe_strdup(optarg);
pInputOpts->pszPassThroughParms = addPassThroughParm(c, optarg, pInputOpts->pszPassThroughParms);
simple_string_list_append(&table_include_patterns, optarg);
include_everything = false;
break;
case 'T': /* exclude table(s) */
pInputOpts->pszPassThroughParms = addPassThroughParm(c, optarg, pInputOpts->pszPassThroughParms);
simple_string_list_append(&table_exclude_patterns, optarg);
break;
case 'u':
bForcePassword = true;
pInputOpts->pszUserName = simple_prompt("User name: ", 100, true);
break;
case 'U':
pInputOpts->pszUserName = Safe_strdup(optarg);
break;
case 'v': /* verbose */
g_verbose = true;
pInputOpts->pszPassThroughParms = addPassThroughParm(c, NULL, pInputOpts->pszPassThroughParms);
break;
case 'W':
bForcePassword = true;
break;
case 'x': /* skip ACL dump */
aclsSkip = true;
pInputOpts->pszPassThroughParms = addPassThroughParm(c, NULL, pInputOpts->pszPassThroughParms);
break;
/*
* Option letters were getting scarce, so I invented this new
* scheme: '-X feature' turns on some feature. Compare to the
* -f option in GCC. You should also add an equivalent
* GNU-style option --feature. Features that require
* arguments should use '-X feature=foo'.
*/
case 'X':
if (strcmp(optarg, "use-set-session-authorization") == 0)
auth = 1;
else if (strcmp(optarg, "disable-triggers") == 0)
disable_triggers = 1;
else if (strcmp(optarg, "gp-catalog-skip") == 0)
skipcat = 1;
else
{
fprintf(stderr,
("%s: invalid -X option -- %s\n"),
progname, optarg);
fprintf(stderr, ("Try \"%s --help\" for more information.\n"), progname);
goto cleanup;
}
break;
/* case 'Z': // Compression Level
compressLevel = atoi(optarg);
pInputOpts->pszPassThroughParms = addPassThroughParm( c, optarg, pInputOpts->pszPassThroughParms );
break;
*/ /* This covers the long options equivalent to -X xxx. */
case 0:
/* This covers the long options equivalent to -X xxx. */
break;
case 1:
/* gp-c remote compression program */
pInputOpts->pszCompressionProgram = "gzip"; /* Safe_strdup(optarg); */
break;
case 2:
/* gp-cf control file */
/* temporary disabled
if ( bSawCDB_S_Option )
{
// Ignore gp_s option
mpp_msg(logInfo, progname, "ignoring the gp-s option, since the gp-cf option is set.\n");
}
if (!FillCDBSet(&pInputOpts->set, optarg))
goto cleanup;
// Validate that none of the role's are hosts
if (0 < CountInstanceHosts(&pInputOpts->set))
{
mpp_msg(logInfo, progname, "The control file %s contains Greenplum Database instances specifies as host name or ip address. This is not allowed.\n",
optarg);
goto cleanup;
}
*/
break;
case 3:
/* gp-d backup remote directory */
pInputOpts->pszBackupDirectory = Safe_strdup(optarg);
break;
case 4:
/* gp-r report directory */
pInputOpts->pszReportDirectory = Safe_strdup(optarg);
break;
case 5:
/* gp-s backup set specification */
if (strcasecmp(optarg, "p") == 0)
{
pInputOpts->actors = SET_NO_MIRRORS;
pInputOpts->pszRawDumpSet = NULL;
}
else if (strncasecmp(optarg, "i", 1) == 0)
{
pInputOpts->actors = SET_INDIVIDUAL;
pInputOpts->pszRawDumpSet = Safe_strdup(optarg);
}
else
{
mpp_err_msg_cache(logError, progname, "invalid gp-s option %s. Must be p for \"all primary segments\", or i[dbid list] for \"individual\".\n", optarg);
goto cleanup;
}
break;
case 7:
/* table-file option */
if (tableFileName != NULL)
{
mpp_err_msg_cache(logError, progname, "Can't use --table-file option more than once\n");
goto cleanup;
}
if (!open_file_and_append_to_list(optarg, &table_include_patterns, "include tables list"))
{
mpp_err_msg_cache(logError, progname, "can't open file %s. Invalid --table-file parameter\n",
optarg);
goto cleanup;
}
/* extract file name without path, and add it to passed params */
addFileNameParam("table-file", optarg, pInputOpts);
include_everything = false;
break;
case 8:
/* exclude-table-file option */
if (excludeTableFileName != NULL)
{
mpp_err_msg_cache(logError, progname, "Can't use --exclude-table-file option more than once\n");
goto cleanup;
}
if (!open_file_and_append_to_list(optarg, &table_exclude_patterns, "exclude tables list"))
{
mpp_err_msg_cache(logError, progname, "can't open file %s. Invalid --exclude-table-file parameter\n",
optarg);
goto cleanup;
}
/* extract file name without path, and add it to passed params */
addFileNameParam("exclude-table-file", optarg, pInputOpts);
break;
default:
mpp_err_msg_cache(logError, progname, "Try \"%s --help\" for more information.\n", progname);
goto cleanup;
}
}
if (optind < argc - 1)
{
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);
goto cleanup;
}
/* Add interesting long options to the passthrough arguments */
if (auth)
pInputOpts->pszPassThroughParms = addPassThroughLongParm("use-set-session-authorization", NULL, pInputOpts->pszPassThroughParms);
if (disable_triggers)
pInputOpts->pszPassThroughParms = addPassThroughLongParm("disable-triggers", NULL, pInputOpts->pszPassThroughParms);
if (skipcat)
pInputOpts->pszPassThroughParms = addPassThroughLongParm("gp-catalog-skip", NULL, pInputOpts->pszPassThroughParms);
if (rsyncable)
pInputOpts->pszPassThroughParms = addPassThroughLongParm("rsyncable", NULL, pInputOpts->pszPassThroughParms);
/* Get database name from command line */
if (optind < argc)
pInputOpts->pszDBName = Safe_strdup(argv[optind]);
/*
* get PG env variables, override only of no cmd-line value specified
*/
if (pInputOpts->pszDBName == NULL)
{
if (getenv("PGDATABASE") != NULL)
pInputOpts->pszDBName = Safe_strdup(getenv("PGDATABASE"));
}
if (pInputOpts->pszPGPort == NULL)
{
if (getenv("PGPORT") != NULL)
pInputOpts->pszPGPort = Safe_strdup(getenv("PGPORT"));
}
if (pInputOpts->pszPGHost == NULL)
{
if (getenv("PGHOST") != NULL)
pInputOpts->pszPGHost = Safe_strdup(getenv("PGHOST"));
}
/*
* Command line options error checking
*/
if (pInputOpts->pszDBName == NULL)
{
mpp_err_msg_cache(logError, progname, "No database name specified for dump, and PGDATABASE is not set either.\n");
goto cleanup;
}
if (dataOnly && schemaOnly)
{
mpp_err_msg_cache(logError, progname, "options \"schema only\" (-s) and \"data only\" (-a) cannot be used together\n");
goto cleanup;
}
if ((dataOnly || schemaOnly) && pInputOpts->actors == SET_INDIVIDUAL)
{
mpp_err_msg_cache(logError, progname, "options \"schema only\" (-s) or \"data only\" (-a) cannot be used together with --gp-s=i\n"
"If you want to use --gp-i and dump only data, omit dbid number 1 (master) from the individual dbid list.");
goto cleanup;
}
if (dataOnly && outputClean)
{
mpp_err_msg_cache(logError, progname, "options \"clean\" (-c) and \"data only\" (-a) cannot be used together\n");
goto cleanup;
}
if (bOutputBlobs && selectTableName != NULL)
{
mpp_err_msg_cache(logError, progname, "large-object output not supported for a single table\n");
mpp_err_msg_cache(logError, progname, "use a full dump instead\n");
goto cleanup;
}
if (bOutputBlobs && selectSchemaName != NULL)
{
mpp_err_msg_cache(logError, progname, "large-object output not supported for a single schema\n");
mpp_err_msg_cache(logError, progname, "use a full dump instead\n");
goto cleanup;
}
if (dump_inserts && bOids)
{
mpp_err_msg_cache(logError, progname, "INSERT (-d, -D) and OID (-o) options cannot be used together\n");
mpp_err_msg_cache(logError, progname, "(The INSERT command cannot set OIDs.)\n");
goto cleanup;
}
if (bOutputBlobs && (pszFormat[0] == 'p' || pszFormat[0] == 'P'))
{
mpp_err_msg_cache(logError, progname, "large-object output is not supported for plain-text dump files\n");
mpp_err_msg_cache(logError, progname, "(Use a different output format.)\n");
goto cleanup;
}
bRtn = true;
if (pInputOpts->pszPassThroughParms != NULL)
mpp_msg(logInfo, progname, "Read params: %s\n", pInputOpts->pszPassThroughParms);
else
mpp_msg(logInfo, progname, "Read params: <empty>\n");
cleanup:
return bRtn;
}
/*
* freeThreadParmArray: This function frees the memory allocated for the pData element of the
* ThreadParmArray, as well as the strings allocated within each element
* of the array. It does not free the pParmAr itself.
*/
void
freeThreadParmArray(ThreadParmArray *pParmAr)
{
int i;
for (i = 0; i < pParmAr->count; i++)
{
ThreadParm *p = &pParmAr->pData[i];
if (p->pszRemoteBackupPath != NULL)
free(p->pszRemoteBackupPath);
if (p->pszErrorMsg != NULL)
free(p->pszErrorMsg);
}
if (pParmAr->pData != NULL)
free(pParmAr->pData);
pParmAr->count = 0;
pParmAr->pData = NULL;
}
/*
* help: This function prints the usage info to stdout.
*/
void
help(const char *progname)
{
printf(("%s dumps a database as a text file or to other formats.\n\n"), progname);
printf(("Usage:\n"));
printf((" %s [OPTION]... [DBNAME]\n"), progname);
printf(("\nGeneral options:\n"));
//printf((" -f, --file=FILENAME output file name\n"));
//printf((" -F, --format=c|t|p output file format (custom, tar, plain text)\n"));
printf((" -i, --ignore-version proceed even when server version mismatches\n"
" gp_dump version\n"));
printf((" -v, --verbose verbose mode. adds verbose information to the\n"
" per segment status files\n"));
//printf((" -Z, --compress=0-9 compression level for compressed formats\n"));
printf((" --help show this help, then exit\n"));
printf((" --version output version information, then exit\n"));
printf(("\nOptions controlling the output content:\n"));
printf((" -a, --data-only dump only the data, not the schema\n"));
/* printf((" -b, --blobs include large objects in dump\n")); */
printf((" -c, --clean clean (drop) schema prior to create\n"));
//printf((" -C, --create include commands to create database in dump\n"));
printf((" -d, --inserts dump data as INSERT, rather than COPY, commands\n"));
printf((" -D, --column-inserts dump data as INSERT commands with column names\n"));
printf(_(" -E, --encoding=ENCODING dump the data in encoding ENCODING\n"));
printf((" -n, --schema=SCHEMA dump the named schema only\n"));
printf(_(" -N, --exclude-schema=SCHEMA do NOT dump the named schema(s)\n"));
printf((" -o, --oids include OIDs in dump\n"));
printf((" -O, --no-owner do not output commands to set object ownership\n"
" in plain text format\n"));
printf((" -s, --schema-only dump only the schema, no data\n"));
printf((" -S, --superuser=NAME specify the superuser user name to use in\n"
" plain text format\n"));
printf((" -t, --table=TABLE dump only matching table(s) (or views or sequences)\n"));
printf(_(" -T, --exclude-table=TABLE do NOT dump matching table(s) (or views or sequences)\n"));
printf((" -x, --no-privileges do not dump privileges (grant/revoke)\n"));
printf(_(" --disable-triggers disable triggers during data-only restore\n"));
printf(_(" --use-set-session-authorization\n"
" use SESSION AUTHORIZATION commands instead of\n"
" ALTER OWNER commands to set ownership\n"));
printf(("\nConnection options:\n"));
printf((" -h, --host=HOSTNAME database server host or socket directory\n"));
printf((" -p, --port=PORT database server port number\n"));
printf((" -U, --username=NAME connect as specified database user\n"));
printf((" -W, --password force password prompt (should happen automatically)\n"));
printf(("\nGreenplum Database specific options:\n"));
printf((" --gp-c use gzip for in-line compression\n"));
printf((" --gp-d=BACKUPFILEDIR directory where backup files are placed\n"));
printf((" --gp-r=REPORTFILEDIR directory where report file is placed\n"));
printf((" --gp-s=BACKUPSET backup set indicator - (p)rimaries only (default)\n"));
printf((" or (i)ndividual segdb (must be followed with a list of dbids\n"));
printf((" of primary segments to dump. For example: --gp-s=i[10,12,14]\n"));
printf((" --rsyncable pass --rsyncable option to gzip"));
printf(("\nIf no database name is supplied, then the PGDATABASE environment\n"
"variable value is used.\n\n"));
/* printf(("Report bugs to <pgsql-bugs@postgresql.org>.\n")); */
}
/*
* exit_nicely: This function was copied from pg_dump.
*
* This routine is called by functions in cdb_dump_include to exit. When exiting
* through this routine, a normal backup report is not generated.
*/
void
exit_nicely(void)
{
char *lastMsg;
char *pszErrorMsg;
lastMsg = PQerrorMessage(g_conn);
pszErrorMsg = MakeString("*** aborted because of error: %s\n", lastMsg);
mpp_err_msg(logError, progname, pszErrorMsg);
/* Clean-up synchronization variables */
pthread_mutex_destroy(&MyMutex);
pthread_cond_destroy(&MyCondVar);
if (pszErrorMsg)
free(pszErrorMsg);
PQfinish(g_conn);
g_conn = NULL;
exit(1);
}
/*
* installSignalHandlers: This function sets both the SIGINT and SIGTERM signal handlers
* to the routine myHandler.
*/
void
installSignalHandlers(void)
{
struct sigaction act,
oact;
act.sa_handler = myHandler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
act.sa_flags |= SA_RESTART;
/* Install SIGINT interrupt handler */
if (sigaction(SIGINT, &act, &oact) < 0)
{
mpp_err_msg_cache(logError, progname, "Error trying to set SIGINT interrupt handler\n");
}
act.sa_handler = myHandler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
act.sa_flags |= SA_RESTART;
/* Install SIGTERM interrupt handler */
if (sigaction(SIGTERM, &act, &oact) < 0)
{
mpp_err_msg_cache(logError, progname, "Error trying to set SIGTERM interrupt handler\n");
}
}
/*
* myHandler: This function is the signal handler for both SIGINT and SIGTERM signals.
* It simply sets a global variable, which is checked by each thread to see whether it should cancel
* the backup running on the remote database.
*/
void
myHandler(SIGNAL_ARGS)
{
static bool bAlreadyHere = false;
if (bAlreadyHere)
{
return;
}
bAlreadyHere = true;
g_b_SendCancelMessage = true;
}
/*
* parmValNeedsQuotes: This function checks to see whether there is any whitespace in the parameter value.
* This is used for pass thru parameters, top know whether to enclose them in quotes or not.
*
* This function considers a string containing an embedded quotation mark or a dollar sign as requiring
* quoting. (This presumes that values checked by this function are being prepared as arguments to a
* shell command. Another function is required to escape these characters [and others].)
*/
static bool
parmValNeedsQuotes(const char *Value)
{
static regex_t rFinder;
static bool bCompiled = false;
static bool bAlwaysPutQuotes = false;
bool bPutQuotes = false;
if (!bCompiled)
{
if (0 != regcomp(&rFinder, "[[:space:]\"$]", REG_EXTENDED | REG_NOSUB))
bAlwaysPutQuotes = true;
bCompiled = true;
}
if (!bAlwaysPutQuotes)
{
if (regexec(&rFinder, Value, 0, NULL, 0) == 0)
bPutQuotes = true;
}
else
bPutQuotes = true;
return bPutQuotes;
}
/*
* reportBackupResults: This function outputs a summary of the backup results to a file and stdout
*/
int
reportBackupResults(InputOptions inputopts, ThreadParmArray *pParmAr)
{
FILE *fRptFile = NULL;
PQExpBuffer reportBuf = NULL;
char *pszReportPathName = NULL;
char *pszFormat;
ThreadParm *pParm0;
int i;
int failCount;
char *pszStatus;
char *pszMsg;
char *pszReportDirectory = inputopts.pszReportDirectory;
SegmentDatabase *pSegDB;
ThreadParm *p;
if (pParmAr == NULL || pParmAr->count == 0)
{
pParmAr->count = 1; /* just use master in this case (early error) */
pParmAr->pData = (ThreadParm *) calloc(1, sizeof(ThreadParm));
if (pParmAr->pData == NULL)
{
mpp_err_msg(logError, progname, "Cannot allocate memory for final report\n");
return 0;
}
p = &pParmAr->pData[0];
p->thread = 0;
p->pOptionsData = &inputopts;
p->pOptionsData->pszKey = GenerateTimestampKey();
p->bSuccess = false;
p->pszErrorMsg = NULL;
p->pszRemoteBackupPath = NULL;
pSegDB = (SegmentDatabase *) calloc(1, sizeof(SegmentDatabase));
pSegDB->dbid = 1;
pSegDB->content = -1;
pSegDB->role = ROLE_MASTER;
pSegDB->port = (inputopts.pszPGPort ? atoi(inputopts.pszPGPort) : 0);
pSegDB->pszDBName = (inputopts.pszDBName ? inputopts.pszDBName : "unknown");
pSegDB->pszHost = inputopts.pszPGHost;
pSegDB->pszDBUser = NULL;
pSegDB->pszDBPswd = NULL;
p->pTargetSegDBData = pSegDB;
}
/*
* Set the report directory if not set by user (with --gp-r)
*/
if (pszReportDirectory == NULL || *pszReportDirectory == '\0')
{
if (getenv("MASTER_DATA_DIRECTORY") != NULL)
{
/*
* report directory not set by user - default to
* $MASTER_DATA_DIRECTORY
*/
pszReportDirectory = Safe_strdup(getenv("MASTER_DATA_DIRECTORY"));
}
else
{
/* $MASTER_DATA_DIRECTORY not set. Default to current directory */
pszReportDirectory = "./";
}
}
/* See whether we can create the file in the report directory */
if (pszReportDirectory[strlen(pszReportDirectory) - 1] != '/')
pszFormat = "%s/gp_dump_%s.rpt";
else
pszFormat = "%sgp_dump_%s.rpt";
pszReportPathName = MakeString(pszFormat, pszReportDirectory, pParmAr->pData[0].pOptionsData->pszKey);
fRptFile = fopen(pszReportPathName, "w");
if (fRptFile == NULL)
mpp_err_msg(logWarn, progname, "Cannot open report file %s for writing. Will use stdout instead.\n",
pszReportPathName);
/* buffer to store the complete dump report */
reportBuf = createPQExpBuffer();
/*
* Write to buffer a report showing the timestamp key, what the command
* line options were, which segment databases were backed up, to where,
* and any errors that occurred.
*/
pParm0 = &pParmAr->pData[0];
appendPQExpBuffer(reportBuf, "\nGreenplum Database Backup Report\n");
appendPQExpBuffer(reportBuf, "Timestamp Key: %s\n", pParm0->pOptionsData->pszKey);
appendPQExpBuffer(reportBuf, "gp_dump Command Line: %s\n",
StringNotNull(pParm0->pOptionsData->pszCmdLineParms, "None"));
appendPQExpBuffer(reportBuf, "Pass through Command Line Options: %s\n",
StringNotNull(pParm0->pOptionsData->pszPassThroughParms, "None"));
appendPQExpBuffer(reportBuf, "Compression Program: %s\n",
StringNotNull(pParm0->pOptionsData->pszCompressionProgram, "None"));
appendPQExpBuffer(reportBuf, "\n");
appendPQExpBuffer(reportBuf, "Individual Results\n");
failCount = 0;
for (i = 0; i < pParmAr->count; i++)
{
ThreadParm *pParm = &pParmAr->pData[i];
if (!pParm->bSuccess)
failCount++;
pszStatus = pParm->bSuccess ? "Succeeded" : "Failed with error: \n{";
pszMsg = pParm->pszErrorMsg;
if (pszMsg == NULL)
{
/* if we failed pre maturely try to get the predump error */
pszMsg = get_early_error();
if (pszMsg == NULL)
pszMsg = "";
}
if (pParm->pTargetSegDBData->role == ROLE_MASTER)
appendPQExpBuffer(reportBuf, "\tMaster ");
else
appendPQExpBuffer(reportBuf, "\tsegment %d ", pParm->pTargetSegDBData->content);
appendPQExpBuffer(reportBuf, "(dbid %d) Host %s Port %d Database %s BackupFile %s: %s %s%s\n",
pParm->pTargetSegDBData->dbid,
StringNotNull(pParm->pTargetSegDBData->pszHost, "localhost"),
pParm->pTargetSegDBData->port,
pParm->pTargetSegDBData->pszDBName,
StringNotNull(pParm->pszRemoteBackupPath, ""),
pszStatus,
pszMsg,
(strcmp(pszMsg, "") == 0) ? "" : "}"
);
/*
* Ugly kludge to 'fix' MPP-12697. The issue is, the dump agent
* doesn't return any status about the _post_data file because it's
* assumed that it will dump it anyway. If we change that behaviour, we
* must remember to change this code too. See the (terribly named)
* gp_backup_launch__().
*/
if (pParm->pTargetSegDBData->role == ROLE_MASTER)
{
appendPQExpBuffer(reportBuf, "\tMaster (dbid %d) Host %s Port %d Database %s BackupFile %s_post_data: %s %s%s\n",
pParm->pTargetSegDBData->dbid,
StringNotNull(pParm->pTargetSegDBData->pszHost, "localhost"),
pParm->pTargetSegDBData->port,
pParm->pTargetSegDBData->pszDBName,
StringNotNull(pParm->pszRemoteBackupPath, ""),
pszStatus,
pszMsg,
(strcmp(pszMsg, "") == 0) ? "" : "}"
);
}
}
if (failCount == 0)
appendPQExpBuffer(reportBuf, "\n%s utility finished successfully.\n", progname);
else
appendPQExpBuffer(reportBuf, "\n%s utility finished unsuccessfully with %d failures.\n", progname, failCount);
/* write report to report file */
if (fRptFile != NULL)
{
mpp_msg(logInfo, progname, "Report results also written to %s.\n", pszReportPathName);
fprintf(fRptFile, "%s", reportBuf->data);
fclose(fRptFile);
}
/* write report to stdout */
fprintf(stdout, "%s", reportBuf->data);
if (pszReportPathName != NULL)
free(pszReportPathName);
destroyPQExpBuffer(reportBuf);
return failCount;
}
/*
* spinOffThreads: This function deals with all the threads that drive the
* backend backups. First, it creates the timestamp key, initializes the
* ThreadParmArray, and the thread synchronization objects. Then it loops
* and creates each of the threads. It waits on a condition variable. This
* will release when all threads have attained the SET_SERIALIZABLE state
* (or have failed to do so). Once SET_SERIALIZABLE is reached we know that
* all the agents are in healthy shape to start operation. We then wait for
* them to grab locks on their objects. again, we wait on a condition variable
* until they all done so.
*
* in here we release the LOCKs we have on the pg_class by committing
* the transaction, once all the threads reached STATE_GETLOCKS
*
* This function waits for all threads to finish, and only then returns.
*/
void
spinOffThreads(PGconn *pConn,
InputOptions * pInputOpts,
const SegmentDatabaseArray *psegDBAr,
ThreadParmArray * pParmAr)
{
int i;
ThreadParm *pParm;
pthread_cond_init(&MyCondVar, NULL);
pthread_mutex_init(&MyMutex, NULL);
pthread_cond_init(&MyCondVar2, NULL);
pthread_mutex_init(&MyMutex2, NULL);
pInputOpts->pszKey = GenerateTimestampKey();
/*
* Create a thread and have it work on executing the dump on the
* appropriate segment database.
*/
if (!createThreadParmArray(psegDBAr->count, pParmAr))
{
mpp_err_msg(logError, progname, "Cannot allocate memory for thread parameters\n");
exit(1);
}
nThreadsFinishedLaunch = nThreadsFinishedLocking = pParmAr->count;
mpp_msg(logInfo, progname, "About to spin off %d threads with timestamp key %s\n",
nThreadsFinishedLaunch, pInputOpts->pszKey);
for (i = 0; i < pParmAr->count; i++)
{
pParm = &pParmAr->pData[i];
pParm->pTargetSegDBData = &psegDBAr->pData[i];
pParm->pOptionsData = pInputOpts;
pParm->bSuccess = false;
mpp_msg(logInfo, progname, "Creating thread to backup dbid %d: host %s port %d database %s\n",
pParm->pTargetSegDBData->dbid,
StringNotNull(pParm->pTargetSegDBData->pszHost, "localhost"),
pParm->pTargetSegDBData->port,
pParm->pTargetSegDBData->pszDBName
);
pthread_create(&pParm->thread,
NULL,
threadProc,
pParm);
}
/*
* wait for all threads to complete launch and attain the SET_SERIALIZABLE
* state.
*/
mpp_msg(logInfo, progname, "Waiting for remote %s processes to start "
"transactions in serializable isolation level\n",
pszAgent);
pthread_mutex_lock(&MyMutex);
if (nThreadsFinishedLaunch > 0)
{
pthread_cond_wait(&MyCondVar, &MyMutex);
}
pthread_mutex_unlock(&MyMutex);
mpp_msg(logInfo, progname, "All remote %s processes have began "
"transactions in serializable isolation level\n",
pszAgent);
/*
* wait for all threads to lock all dumpable objects and attain the
* SET_GOTLOCKS state.
*/
mpp_msg(logInfo, progname, "Waiting for remote %s processes to obtain "
"local locks on dumpable objects\n",
pszAgent);
pthread_mutex_lock(&MyMutex2);
if (nThreadsFinishedLocking > 0)
{
pthread_cond_wait(&MyCondVar2, &MyMutex2);
}
pthread_mutex_unlock(&MyMutex2);
mpp_msg(logInfo, progname, "All remote %s processes have obtains the necessary locks\n",
pszAgent);
/* End the transaction and therefore unlock the lock we took on pg_class */
mpp_msg(logInfo, progname, "Committing transaction on the master database, thereby releasing locks.\n");
execCommit(pConn);
mpp_msg(logInfo, progname, "Waiting for all remote %s programs to finish.\n", pszAgent);
/* Wait for all threads to complete */
for (i = 0; i < pParmAr->count; i++)
{
ThreadParm *pParm = &pParmAr->pData[i];
if (pParm->thread == (pthread_t) 0)
continue;
pthread_join(pParm->thread, NULL);
pParm->thread = (pthread_t) 0;
}
mpp_msg(logInfo, progname, "All remote %s programs are finished.\n", pszAgent);
}
/* threadProc: This function is used to connect to one database that needs to be backed up,
* make a gp_backup_launch call to cause the backup process to begin on the instance hosting the
* database. Then it waits for notifications from the process. It receives a notification
* whenever the gp_dump_agent process writes a row into the gp_backup_status table. This happens
* when the process starts, when it attains the SET_SERIALIZABLE state, and when it finishes.
*/
void *
threadProc(void *arg)
{
bool decrementedLunchCount = false;
bool decrementedLockCount = false;
/*
* The argument is a pointer to a ThreadParm structure that stays around
* for the entire time the program is running. so we need not worry about
* making a copy of it.
*/
ThreadParm *pParm = (ThreadParm *) arg;
const SegmentDatabase *pSegDB = pParm->pTargetSegDBData;
const InputOptions *pInputOpts = pParm->pOptionsData;
const char *pszKey = pInputOpts->pszKey;
PGconn *pConn;
char *pszPassThroughCredentials;
PQExpBuffer Qry;
PGresult *pRes;
int sock;
fd_set input_mask;
bool bSentCancelMessage;
BackupStateMachine *pState;
struct timeval tv;
PGnotify *pNotify;
/*
* Block SIGINT and SIGKILL signals (we can handle them in the main
* thread)
*/
sigset_t newset;
sigemptyset(&newset);
sigaddset(&newset, SIGINT);
sigaddset(&newset, SIGKILL);
pthread_sigmask(SIG_SETMASK, &newset, NULL);
/* Create a connection for this thread */
pConn = MakeDBConnection(pSegDB, false);
if (PQstatus(pConn) == CONNECTION_BAD)
{
g_b_SendCancelMessage = true;
pParm->pszErrorMsg = MakeString("Connection to dbid %d on host %s failed: %s",
pSegDB->dbid, StringNotNull(pSegDB->pszHost, "localhost"), PQerrorMessage(pConn));
mpp_err_msg_cache(logError, progname, pParm->pszErrorMsg);
PQfinish(pConn);
decrementFinishedLaunchCount();
return NULL;
}
/* issue a LISTEN command for 5 different names */
DoCancelNotifyListen(pConn, true, pszKey, pSegDB->role, pSegDB->dbid, -1, SUFFIX_START);
DoCancelNotifyListen(pConn, true, pszKey, pSegDB->role, pSegDB->dbid, -1, SUFFIX_SET_SERIALIZABLE);
DoCancelNotifyListen(pConn, true, pszKey, pSegDB->role, pSegDB->dbid, -1, SUFFIX_GOTLOCKS);
DoCancelNotifyListen(pConn, true, pszKey, pSegDB->role, pSegDB->dbid, -1, SUFFIX_SUCCEED);
DoCancelNotifyListen(pConn, true, pszKey, pSegDB->role, pSegDB->dbid, -1, SUFFIX_FAIL);
mpp_msg(logInfo, progname, "Listening for messages from server on dbid %d connection\n", pSegDB->dbid);
/*
* If there is a password associated with this login, we pass it as a
* base64 encoded string in the parameter pszPassThroughCredentials
*/
pszPassThroughCredentials = NULL;
if (pSegDB->pszDBPswd != NULL && *pSegDB->pszDBPswd != '\0')
pszPassThroughCredentials = DataToBase64(pSegDB->pszDBPswd, strlen(pSegDB->pszDBPswd));
/* form an gp_backup_launch statement */
/*
* Argument 4 of the gp_launch_backup function may contain escape
* sequences and must either be quoted using the PG $$ quote mechanism or
* using an E-type constant with embedded '\' characters doubled.
*/
Qry = createPQExpBuffer();
appendPQExpBuffer(Qry, "SELECT * FROM gp_backup_launch('%s', '%s', '%s', $$%s$$, '%s')",
StringNotNull(pInputOpts->pszBackupDirectory, ""),
pszKey,
StringNotNull(pInputOpts->pszCompressionProgram, ""),
StringNotNull(pInputOpts->pszPassThroughParms, ""),
StringNotNull(pszPassThroughCredentials, "")
);
if (pszPassThroughCredentials != NULL)
free(pszPassThroughCredentials);
/* Execute gp_backup_launch. This will start an gp_dump agent */
pRes = PQexec(pConn, Qry->data);
if (!pRes || PQresultStatus(pRes) != PGRES_TUPLES_OK || PQntuples(pRes) == 0)
{
g_b_SendCancelMessage = true;
pParm->pszErrorMsg = MakeString("could not start Greenplum Database backup: %s", PQerrorMessage(pConn));
mpp_err_msg_cache(logFatal, progname, pParm->pszErrorMsg);
PQfinish(pConn);
decrementFinishedLaunchCount();
decrementedLunchCount = true;
return NULL;
}
mpp_msg(logInfo, progname, "Successfully launched Greenplum Database backup on dbid %d server\n", pSegDB->dbid);
pParm->pszRemoteBackupPath = strdup(PQgetvalue(pRes, 0, 0));
PQclear(pRes);
destroyPQExpBuffer(Qry);
/* Now wait for notifications from the back end */
sock = PQsocket(pConn);
bSentCancelMessage = false;
pState = CreateBackupStateMachine(pszKey, pSegDB->role, pSegDB->dbid);
if (pState == NULL)
{
g_b_SendCancelMessage = true;
pParm->pszErrorMsg = "error allocating memory for Greenplum Database backup";
mpp_err_msg_cache(logError, progname, pParm->pszErrorMsg);
PQfinish(pConn);
decrementFinishedLaunchCount();
decrementedLunchCount = true;
return NULL;
}
/*
* This is a bit involved. We are waiting for 4 messages: one that the
* backend process has started, one that the SET_SERIALIZABLE has
* happened, one that the SET_GOTLOCKS has happened, and the third that
* FINISH has happened. To avoid polling, we use the NOTIFY/LISTEM
* approach. But we only select for 2 secs at a time, so we can see
* whether or not we need to NOTIFY our backend to cancel, based on
* another thread failing. A BackupStateMachine object is used to manage
* receiving these notifications
*/
while (!IsFinalState(pState))
{
/*
* Check to see whether another thread has failed and therefore we
* should cancel
*/
if (g_b_SendCancelMessage && !bSentCancelMessage && HasStarted(pState))
{
mpp_msg(logInfo, progname, "noticed that a cancel order is in effect. Informing dbid %d on host %s by notifying on connection\n",
pSegDB->dbid, StringNotNull(pSegDB->pszHost, "localhost"));
/*
* Either one of the other threads have failed, or a Ctrl C was
* received. So post a cancel message
*/
DoCancelNotifyListen(pConn, false, pszKey, pSegDB->role, pSegDB->dbid, -1, NULL);
bSentCancelMessage = true;
}
tv.tv_sec = 2;
tv.tv_usec = 0;
FD_ZERO(&input_mask);
FD_SET(sock, &input_mask);
if (select(sock + 1, &input_mask, NULL, NULL, &tv) < 0)
{
g_b_SendCancelMessage = true;
pParm->pszErrorMsg = MakeString("select failed for backup key %s, role %d, dbid %d failed\n",
pszKey, pSegDB->role, pSegDB->dbid);
mpp_err_msg(logFatal, progname, pParm->pszErrorMsg);
PQfinish(pConn);
DestroyBackupStateMachine(pState);
if (!decrementedLunchCount)
{
decrementFinishedLaunchCount();
decrementedLunchCount = true;
}
return NULL;
}
/* See whether the connection went down */
if (PQstatus(pConn) == CONNECTION_BAD)
{
g_b_SendCancelMessage = true;
pParm->pszErrorMsg = MakeString("connection went down for backup key %s, role %d, dbid %d\n",
pszKey, pSegDB->role, pSegDB->dbid);
mpp_err_msg(logError, progname, pParm->pszErrorMsg);
PQfinish(pConn);
DestroyBackupStateMachine(pState);
if (!decrementedLunchCount)
{
decrementFinishedLaunchCount();
decrementedLunchCount = true;
}
return NULL;
}
/* try to get any notification from the server */
PQconsumeInput(pConn);
CleanupNotifications(pState);
/* get the next notification from the server */
while (NULL != (pNotify = PQnotifies(pConn)))
{
/*
* compare the LISTEN/NOTIFY (without suffix) name with the one we
* expect
*/
if (strncasecmp(pState->pszNotifyRelName, pNotify->relname,
strlen(pState->pszNotifyRelName)) == 0)
{
/* add this notification to our state notification array */
if (!AddNotificationtoBackupStateMachine(pState, pNotify))
{
g_b_SendCancelMessage = true;
pParm->pszErrorMsg = MakeString("error allocating memory for Greenplum Database backup\n");
mpp_err_msg(logError, progname, pParm->pszErrorMsg);
PQfinish(pConn);
DestroyBackupStateMachine(pState);
if (!decrementedLunchCount)
{
decrementFinishedLaunchCount();
decrementedLunchCount = true;
}
return NULL;
}
}
}
ProcessInput(pState);
if ( /* !decrementedLunchCount && */ HasReceivedSetSerializable(pState))
{
decrementFinishedLaunchCount();
decrementedLunchCount = true;
}
if ( /* !decrementedLunchCount && */ HasReceivedGotLocks(pState))
{
decrementFinishedLockingCount();
decrementedLockCount = true;
}
}
/*
* make sure to decrement if we haven't already, to release mutex if in
* error state
*/
if (!decrementedLunchCount)
{
decrementFinishedLaunchCount();
decrementedLunchCount = true;
}
if (!decrementedLockCount)
{
decrementFinishedLockingCount();
decrementedLockCount = true;
}
/*
* We don't get here unless the BackupStateMachine reached a final state.
* If the status indicates an error, we process this error in the switch
* statement below.
*/
if (!pState->bStatus)
{
g_b_SendCancelMessage = true;
pParm->bSuccess = false;
mpp_err_msg(logError, progname, "backup failed for dbid %d on host %s\n",
pSegDB->dbid, StringNotNull(pSegDB->pszHost, "localhost"));
switch (pState->currentState)
{
case STATE_TIMEOUT:
DoCancelNotifyListen(pConn, false, pszKey, pSegDB->role, pSegDB->dbid, -1, NULL);
bSentCancelMessage = true;
/*
* this historically reported "failed to set transaction level
* serializable"
*/
pParm->pszErrorMsg = MakeString("backup failed before being able to begin the backup transaction. "
"there are various reasons that may cause this to happen. Please "
"inspect the server log of dbid %d on host %s\n "
"Detail from remote status file: %s\n",
pSegDB->dbid, StringNotNull(pSegDB->pszHost, "localhost"),
ReadBackendBackupFile(pConn, pInputOpts->pszBackupDirectory,
pszKey, BFT_BACKUP_STATUS, progname));
break;
case STATE_BACKUP_ERROR:
/* Make call to get error message from file on server */
pParm->pszErrorMsg = ReadBackendBackupFile(pConn, pInputOpts->pszBackupDirectory, pszKey, BFT_BACKUP_STATUS, progname);
break;
case STATE_UNEXPECTED_INPUT:
DoCancelNotifyListen(pConn, false, pszKey, pSegDB->role, pSegDB->dbid, -1, NULL);
bSentCancelMessage = true;
pParm->pszErrorMsg = MakeString("Unexpected task id encountered while performing backup for for segment %d on host %s",
pSegDB->dbid, StringNotNull(pSegDB->pszHost, "localhost"));
break;
default:
break;
}
mpp_err_msg(logError, progname, pParm->pszErrorMsg);
}
else
{
pParm->bSuccess = true;
mpp_msg(logInfo, progname, "backup succeeded for dbid %d on host %s\n",
pSegDB->dbid, StringNotNull(pSegDB->pszHost, "localhost"));
}
PQfinish(pConn);
DestroyBackupStateMachine(pState);
return (NULL);
}