blob: 9c410dee77d1711813d89c9f79ece33c976cbd68 [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.
*/
/*-------------------------------------------------------------------------
*
* cdbbackup.c
*
*
*
*/
#include "postgres.h"
#include <unistd.h>
#include <sys/stat.h>
#include <assert.h>
#include <dirent.h>
#include <sys/wait.h>
#include "regex/regex.h"
#include "gp-libpq-fe.h"
#include "libpq/libpq-be.h"
#include "fmgr.h"
#include "funcapi.h"
#include "utils/builtins.h"
#include "cdb/cdbvars.h"
#include "cdb/cdbutil.h"
#include "cdb/cdbbackup.h"
#include "cdb/cdbtimer.h"
#include "miscadmin.h"
#include "postmaster/postmaster.h"
#define EMPTY_STR '\0'
/* general utility copied from cdblink.c */
#define GET_STR(textp) DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(textp)))
/* static helper functions */
#ifdef GPDUMP_SUPPORT_IN_HAWQ
static bool createBackupDirectory(char *pszPathName);
static char *findAcceptableBackupFilePathName(char *pszBackupDirectory, char *pszBackupKey, int segid);
static char *formBackupFilePathName(char *pszBackupDirectory, char *pszBackupKey, bool is_compress, bool isPostData);
static char *formStatusFilePathName(char *pszBackupDirectory, char *pszBackupKey, bool bIsBackup);
static char *formThrottleCmd(char *pszBackupFileName, int directIO_read_chunk_mb, bool bIsThrottlingEnabled);
static char *relativeToAbsolute(char *pszRelativeDirectory);
static void testCmdsExist(char *pszCompressionProgram, char *pszBackupProgram);
static char *testProgramExists(char *pszProgramName);
static void validateBackupDirectory(char *pszBackupDirectory);
static char *positionToError(char *source);
static char *compPg = NULL,
*bkPg = NULL;
#endif
typedef enum backup_file_type
{
BFT_BACKUP = 0,
BFT_BACKUP_STATUS = 1,
BFT_RESTORE_STATUS = 2
} BackupFileType;
/*
* gp_backup_launch__( TEXT, TEXT, TEXT, TEXT, TEXT ) returns TEXT
*
* Called by gp_dump.c to launch gp_dump_agent on this host.
*/
PG_FUNCTION_INFO_V1(gp_backup_launch__);
Datum
gp_backup_launch__(PG_FUNCTION_ARGS)
{
#ifdef GPDUMP_SUPPORT_IN_HAWQ
/* Fetch the parameters from the argument list. */
char *pszBackupDirectory = "./";
char *pszBackupKey = "";
char *pszCompressionProgram = "";
char *pszPassThroughParameters = "";
char *pszPassThroughCredentials = "";
char *pszDataOption = "";
char *pszBackupFileName,
*pszStatusFileName;
char *pszSaveBackupfileName;
char *pszDBName,
*pszUserName;
pid_t newpid;
char *pszCmdLine;
int port;
char *pszKeyParm; /* dispatch node */
int segid;
int len;
int instid; /* dispatch node */
bool is_compress;
bool rsyncable;
itimers savetimers;
char *pszThrottleCmd;
char emptyStr = EMPTY_STR;
rsyncable = false;
// instid = AmActiveMaster() ? 1 : 0; /* dispatch node */
// segid = GpIdentity.dbid;
if (!PG_ARGISNULL(0))
{
pszBackupDirectory = GET_STR(PG_GETARG_TEXT_P(0));
if (*pszBackupDirectory == '\0')
pszBackupDirectory = "./";
}
if (!PG_ARGISNULL(1))
pszBackupKey = GET_STR(PG_GETARG_TEXT_P(1));
if (!PG_ARGISNULL(2))
pszCompressionProgram = GET_STR(PG_GETARG_TEXT_P(2));
if (!PG_ARGISNULL(3))
pszPassThroughParameters = GET_STR(PG_GETARG_TEXT_P(3));
if (!PG_ARGISNULL(4))
pszPassThroughCredentials = GET_STR(PG_GETARG_TEXT_P(4));
/*
* if BackupDirectory is relative, make it absolute based on the directory
* where the database resides
*/
pszBackupDirectory = relativeToAbsolute(pszBackupDirectory);
/*
* See whether the directory exists.
* If not, try to create it.
* If so, make sure it's a directory
*/
validateBackupDirectory(pszBackupDirectory);
/*
* Validate existence of gp_dump_agent and compression program, if
* possible
*/
testCmdsExist(pszCompressionProgram, "gp_dump_agent");
/* are we going to use a compression program? */
is_compress = (pszCompressionProgram != NULL && *pszCompressionProgram != '\0' ? true : false);
/* Form backup file path name */
pszBackupFileName = formBackupFilePathName(pszBackupDirectory, pszBackupKey, is_compress, false);
pszStatusFileName = formStatusFilePathName(pszBackupDirectory, pszBackupKey, true);
pszSaveBackupfileName = pszBackupFileName;
pszDBName = NULL;
pszUserName = (char *) NULL;
if (MyProcPort != NULL)
{
pszDBName = MyProcPort->database_name;
pszUserName = MyProcPort->user_name;
}
if (pszDBName == NULL)
pszDBName = "";
if (pszUserName == NULL)
pszUserName = "";
if (strstr(pszPassThroughParameters,"--rsyncable") != NULL)
{
rsyncable = true;
elog(DEBUG1,"--rsyncable found, ptp %s",pszPassThroughParameters);
/* Remove from gp_dump_agent parameters, because this parameter is for gzip */
strncpy(strstr(pszPassThroughParameters,"--rsyncable")," ",strlen("--rsyncable"));
elog(DEBUG1,"modified to ptp %s",pszPassThroughParameters);
/* If a compression program is set, and doesn't already include the parameter */
if (strlen(pszCompressionProgram) > 1 && strstr(pszCompressionProgram, "--rsyncable") == NULL)
{
/* add the parameter */
/*
* You would think we'd add this to pszCompressionProgram, but actually
* testCommandExists() moved it to compPg, where we use it later.
*
* But, much of the code assumes that if pszCompressionProgram is set, we WILL have a compression program,
* and the code will fail if we don't, so why we do this is a mystery.
*/
char * newComp = palloc(strlen(compPg) + strlen(" --rsyncable") + 1);
strcpy(newComp,compPg);
strcat(newComp," --rsyncable");
compPg = newComp;
elog(DEBUG1,"compPG %s",compPg);
}
}
/* If a compression program is set, and doesn't already include the parameter */
if (strlen(pszCompressionProgram) > 1 && strstr(pszCompressionProgram, " -1") == NULL &&
pszCompressionProgram[0] == 'g')
{
/* add the -1 parameter to gzip */
/*
* You would think we'd add this to pszCompressionProgram, but actually
* testCommandExists() moved it to compPg, where we use it later.
*
* But, much of the code assumes that if pszCompressionProgram is set, we WILL have a compression program,
* and the code will fail if we don't, so why we do this is a mystery.
*/
char * newComp = palloc(strlen(compPg) + strlen(" -1") + 1);
strcpy(newComp,compPg);
strcat(newComp," -1");
compPg = newComp;
elog(DEBUG1,"new compPG %s",compPg);
}
/* Clear process interval timers */
resetTimers(&savetimers);
/* Child Process */
port = PostPortNumber;
/* Create the --gp-k parameter string */
pszKeyParm = (char *) palloc(strlen(pszBackupKey) +
strlen(pszPassThroughCredentials) +
3 + 10 + 10 + 1);
if (pszKeyParm == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
}
pszThrottleCmd = &emptyStr; /* We do this to point to prevent memory leak*/
if(gp_backup_directIO)
{
if (testProgramExists("throttlingD.py") == NULL)
{
/*throttlingD.py does not exist*/
const char *gp_home = getenv("GPHOME");
if (!gp_home)
gp_home = "";
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("throttlingD.py not found in GPHOME\\bin (GPHOME: %s)", gp_home)));
}
pszThrottleCmd = formThrottleCmd(pszBackupFileName, gp_backup_directIO_read_chunk_mb, true);
}
else
{
// when directIO is disabled --> need to pipe the dump to backupFile (No throttling will be done)
pszThrottleCmd = formThrottleCmd(pszBackupFileName, 0, false);
}
/*
* dump schema and data for entry db, and data only for segment db.
* why dump data for entry db? because that will include statements
* to update the sequence tables (last values). As for actual data -
* there isn't any, so we will just dump empty COPY statements...
*/
pszDataOption = (instid == 1) ? "--pre-data-schema-only" : "-a";
sprintf(pszKeyParm, "%s_%d_%d_%s", pszBackupKey, instid, segid, pszPassThroughCredentials);
/*
* In the following calc for the number of characters in the
* pszCmdLine, I show each piece as the count of the string for that
* piece + 1 for the space after that piece, e.g. -p comes out 2 for
* the -p and 1 for the space after the -p The last 1 is for the
* binary zero at the end.
*/
len = strlen(bkPg) + 1 /* gp_dump_agent */
+ 7 + 1 /* --gp-k */
+ strlen(pszKeyParm) + 1
+ 7 + 1 /* --gp-d */
+ strlen(pszBackupDirectory) + 1
+ 2 + 1 /* -p */
+ 5 + 1 /* port */
+ 2 + 1 /* -U */
+ strlen(pszUserName) + 1 /* pszUserName */
+ strlen(pszPassThroughParameters) + 1
+ 23 + 1 /* " " or "--post-data-schema-only" */
+ strlen("_post_data")
+ strlen(pszDBName) + 1
+ 2 + 1 /* 2> */
+ strlen(pszStatusFileName) + 1
+ strlen(pszThrottleCmd)
+ 1;
/* if user selected a compression program */
if (pszCompressionProgram[0] != '\0')
{
len += 1 + 1 /* | */
+ strlen(compPg) + 1;
}
pszCmdLine = (char *) palloc(len);
if (pszCmdLine == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
}
/* if user selected a compression program */
if (pszCompressionProgram[0] != '\0')
{
/* gp_dump_agent + options, pipe into compression program, direct
* stdout to backup file and stderr to status file */
sprintf(pszCmdLine, "%s --gp-k %s --gp-d %s -p %d -U %s %s %s %s 2> %s | %s %s",
bkPg, pszKeyParm, pszBackupDirectory, port, pszUserName, pszPassThroughParameters,
pszDataOption, pszDBName, pszStatusFileName, compPg, pszThrottleCmd);
}
else
{
sprintf(pszCmdLine, "%s --gp-k %s --gp-d %s -p %d -U %s %s %s %s 2> %s %s",
bkPg, pszKeyParm, pszBackupDirectory,
port, pszUserName, pszPassThroughParameters, pszDataOption, pszDBName,
pszStatusFileName, pszThrottleCmd);
}
elog(LOG, "gp_dump_agent command line: %s", pszCmdLine),
/* Fork off gp_dump_agent */
#ifdef _WIN32
exit(1);
#else
newpid = fork();
#endif
if (newpid < 0)
{
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("Could not fork a process for backup of database %s", pszDBName)));
}
else if (newpid == 0)
{
/* This execs a shell that runs the gp_dump_agent program */
execl("/bin/sh", "sh", "-c", pszCmdLine, NULL);
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("Error in gp_backup_launch - execl of %s with Command Line %s failed",
"/bin/sh", pszCmdLine)));
_exit(127);
}
/*
* If we are the master, we do two calls to gp_dump_agent, one for the pre-schema, one for the post
*/
if (instid == 1)
{
int stat;
waitpid(newpid, &stat, 0);
/* Since we going to re-formated the file names, we need to re-create the command line with the correct length */
int newlen = len -
strlen(pszBackupFileName) -
strlen(pszStatusFileName) -
strlen(pszDataOption) -
strlen(pszThrottleCmd);
/* re-format the file names */
pszBackupFileName = formBackupFilePathName(pszBackupDirectory, pszBackupKey, is_compress, true);
pszStatusFileName = formStatusFilePathName(pszBackupDirectory, pszBackupKey, true);
pszDataOption = "--post-data-schema-only" ;
if(gp_backup_directIO)
{
pszThrottleCmd = formThrottleCmd(pszBackupFileName, gp_backup_directIO_read_chunk_mb, true);
}
else
{
pszThrottleCmd = formThrottleCmd(pszBackupFileName, 0, false);
}
/* Delete the old command */
pfree(pszCmdLine);
/* Calculate the new length */
newlen += strlen(pszBackupFileName) +
strlen(pszStatusFileName) +
strlen(pszDataOption) +
strlen(pszThrottleCmd);
pszCmdLine = (char *) palloc(newlen);
if (pszCmdLine == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
}
/* if user selected a compression program */
if (pszCompressionProgram[0] != '\0')
{
/* gp_dump_agent + options, pipe into compression program, direct
* stdout to backup file and stderr to status file */
sprintf(pszCmdLine, "%s --gp-k %s --gp-d %s -p %d -U %s %s %s %s 2>> %s | %s %s",
bkPg, pszKeyParm, pszBackupDirectory, port, pszUserName, pszPassThroughParameters,
pszDataOption, pszDBName, pszStatusFileName, compPg, pszThrottleCmd);
}
else
{
sprintf(pszCmdLine, "%s --gp-k %s --gp-d %s -p %d -U %s %s %s %s 2>> %s %s",
bkPg, pszKeyParm, pszBackupDirectory,
port, pszUserName, pszPassThroughParameters, pszDataOption, pszDBName,
pszStatusFileName, pszThrottleCmd);
}
elog(LOG, "gp_dump_agent command line : %s", pszCmdLine),
#ifdef _WIN32
exit(1);
#else
newpid = fork();
#endif
if (newpid < 0)
{
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("Could not fork a process for backup of database %s", pszDBName)));
}
else if (newpid == 0)
{
/* This execs a shell that runs the gp_dump_agent program */
execl("/bin/sh", "sh", "-c", pszCmdLine, NULL);
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("Error in gp_backup_launch - execl of %s with Command Line %s failed",
"/bin/sh", pszCmdLine)));
_exit(127);
}
}
/* Restore process interval timers */
restoreTimers(&savetimers);
assert(pszSaveBackupfileName != NULL && pszSaveBackupfileName[0] != '\0');
return DirectFunctionCall1(textin, CStringGetDatum(pszSaveBackupfileName));
#else
PG_RETURN_NULL();
#endif
}
/*
* gp_restore_launch__( TEXT, TEXT, TEXT, TEXT, TEXT, TEXT, INT, bool ) returns TEXT
*
* Called by gp_restore.c to launch gp_restore_agent on this host.
*/
PG_FUNCTION_INFO_V1(gp_restore_launch__);
Datum
gp_restore_launch__(PG_FUNCTION_ARGS)
{
#ifdef GPDUMP_SUPPORT_IN_HAWQ
/* Fetch the parameters from the argument list. */
char *pszBackupDirectory = "./";
char *pszBackupKey = "";
char *pszCompressionProgram = "";
char *pszPassThroughParameters = "";
char *pszPassThroughCredentials = "";
char *pszPassThroughTargetInfo = "";
bool bOnErrorStop = false;
int instid; /* dispatch node */
int segid;
int target_dbid = 0; /* keep compiler quiet. value will get replaced */
char *pszOnErrorStop;
char *pszBackupFileName;
char *pszStatusFileName;
struct stat info;
char *pszDBName;
char *pszUserName;
pid_t newpid;
char *pszCmdLine;
int port;
char *pszKeyParm;
int len,
len_name;
bool is_decompress;
bool is_file_compressed; /* is the dump file ending with '.gz'*/
bool postDataSchemaOnly;
itimers savetimers;
postDataSchemaOnly = false;
// instid = (GpIdentity.segindex == -1) ? 1 : 0; /* dispatch node */
// segid = GpIdentity.dbid;
if (!PG_ARGISNULL(0))
{
pszBackupDirectory = GET_STR(PG_GETARG_TEXT_P(0));
if (*pszBackupDirectory == '\0')
pszBackupDirectory = "./";
}
if (!PG_ARGISNULL(1))
pszBackupKey = GET_STR(PG_GETARG_TEXT_P(1));
if (!PG_ARGISNULL(2))
pszCompressionProgram = GET_STR(PG_GETARG_TEXT_P(2));
if (!PG_ARGISNULL(3))
pszPassThroughParameters = GET_STR(PG_GETARG_TEXT_P(3));
if (!PG_ARGISNULL(4))
pszPassThroughCredentials = GET_STR(PG_GETARG_TEXT_P(4));
if (!PG_ARGISNULL(5))
pszPassThroughTargetInfo = GET_STR(PG_GETARG_TEXT_P(5));
if (!PG_ARGISNULL(6))
target_dbid = PG_GETARG_INT32(6);
if (!PG_ARGISNULL(7))
bOnErrorStop = PG_GETARG_BOOL(7);
pszOnErrorStop = bOnErrorStop ? "--gp-e" : "";
/*
* if BackupDirectory is relative, make it absolute based on the directory
* where the database resides
*/
pszBackupDirectory = relativeToAbsolute(pszBackupDirectory);
/* Validate existence of compression program and gp_restore_agent program */
testCmdsExist(pszCompressionProgram, "gp_restore_agent");
/* are we going to use a decompression program? */
is_decompress = (pszCompressionProgram != NULL && *pszCompressionProgram != '\0' ? true : false);
/* Post data pass? */
postDataSchemaOnly = strstr(pszPassThroughParameters, "--post-data-schema-only") != NULL;
/* Form backup file path name */
pszBackupFileName = formBackupFilePathName(pszBackupDirectory, pszBackupKey, is_decompress, postDataSchemaOnly);
pszStatusFileName = formStatusFilePathName(pszBackupDirectory, pszBackupKey, false);
if (0 != stat(pszBackupFileName, &info))
{
pszBackupFileName = findAcceptableBackupFilePathName(pszBackupDirectory, pszBackupKey, segid);
if (pszBackupFileName == NULL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Attempt to restore dbid %d failed. No acceptable dump file name found "
" in directory %s with backup key %s and source dbid key %d", target_dbid,
pszBackupDirectory, pszBackupKey, segid)));
/*
* If --gp-c is on make sure our file name has a .gz suffix.
* If no decompression requested make sure the file *doesn't* have
* a .gz suffix. This fixes the problem with forgetting to use
* the compression flags and gp_restore completing silently with
* no errors (hard to reproduce, but this check doesn't hurt)
*/
len_name = strlen(pszBackupFileName);
is_file_compressed = (strcmp(pszBackupFileName + (len_name - 3), ".gz") == 0 ? true : false);
if (is_file_compressed && !is_decompress)
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("dump file appears to be compressed. Pass a decompression "
"option to gp_restore to decompress it on the fly")));
if (!is_file_compressed && is_decompress)
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("a decompression cmd line option is used but the dump "
"file does not appear to be compressed.")));
}
len_name = strlen(pszBackupFileName);
pszDBName = NULL;
pszUserName = NULL;
if (MyProcPort != NULL)
{
pszDBName = MyProcPort->database_name;
pszUserName = MyProcPort->user_name;
}
if (pszDBName == NULL)
pszDBName = "";
if (pszUserName == NULL)
pszUserName = "";
/* Clear process interval timers */
resetTimers(&savetimers);
/* Fork off gp_restore_agent */
#ifdef _WIN32
exit(1);
#else
newpid = fork();
#endif
if (newpid < 0)
{
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("Could not fork a process for backup of database %s", pszDBName)));
}
else if (newpid == 0)
{
/* Child Process */
port = PostPortNumber;
/* Create the --gp-k parameter string */
pszKeyParm = (char *) palloc(strlen(pszBackupKey) +
strlen(pszPassThroughCredentials) +
3 + 10 + 10 + 1);
if (pszKeyParm == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
}
sprintf(pszKeyParm, "%s_%d_%d_%s", pszBackupKey, instid, segid, pszPassThroughCredentials);
/*
* In the following calc for the number of characters in the
* pszCmdLine, I show each piece as the count of the string for that
* piece + 1 for the space after that piece, e.g. -p comes out 2 for
* the -p and 1 for the space after the -p The last 1 is for the
* binary zero at the end.
*/
len = strlen(bkPg) + 1 /* gp_restore_agent */
+ 7 + 1 /* --gp-k */
+ strlen(pszKeyParm) + 1
+ 7 + 1 /* --gp-d */
+ strlen(pszBackupDirectory) + 1
+ 7 + 1 /* --gp-e */
+ 2 + 1 /* -p */
+ 5 + 1 /* port */
+ 2 + 1 /* -U */
+ strlen(pszUserName) + 1 /* pszUserName */
+ strlen(pszPassThroughParameters) + 1
+ strlen(pszPassThroughTargetInfo) + 1
+ 2 + 1 /* -d */
+ strlen(pszDBName) + 1
+ 1 + 1 /* > */
+ strlen(pszStatusFileName)
+ 4 + 1 /* 2>&1 */
+ 1;
/*
* if compression was requested with --gp-c
*/
if (is_decompress)
{
len += 4 + strlen(compPg) +
+strlen(pszBackupFileName);
len += 8; /* pass along "--gp-c" to tell restore file is compressed */
pszCmdLine = (char *) palloc(len);
if (pszCmdLine == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
}
/*
* de-compress backupfile and pipe into stdin of gp_restore_agent along with its
* options. Redirect both stdout and stderr into the status file.
*/
sprintf(pszCmdLine, "%s --gp-c %s --gp-k %s --gp-d %s %s -p %d -U %s %s %s -d %s %s %s %s 2>&2",
bkPg, compPg, pszKeyParm, pszBackupDirectory, pszOnErrorStop, port, pszUserName,
pszPassThroughParameters, pszPassThroughTargetInfo, pszDBName, pszBackupFileName,
postDataSchemaOnly ? "2>>" : "2>", pszStatusFileName);
}
else
{
len += strlen(pszBackupFileName) + 1;
pszCmdLine = (char *) palloc(len);
if (pszCmdLine == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
}
/* Format gp_restore_agent with options, and Redirect both stdout and stderr into the status file */
sprintf(pszCmdLine, "%s --gp-k %s --gp-d %s %s -p %d -U %s %s %s -d %s %s %s %s 2>&2",
bkPg, pszKeyParm, pszBackupDirectory, pszOnErrorStop, port, pszUserName, pszPassThroughParameters,
pszPassThroughTargetInfo, pszDBName, pszBackupFileName,
postDataSchemaOnly ? "2>>" : "2>", pszStatusFileName);
}
elog(LOG, "gp_restore_agent command line: %s", pszCmdLine),
/* This execs a shell that runs the gp_restore_agent program */
execl("/bin/sh", "sh", "-c", pszCmdLine, NULL);
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("Error in gp_restore_launch - execl of %s with Command Line %s failed",
"/bin/sh", pszCmdLine)));
_exit(127);
}
/* Restore process interval timers */
restoreTimers(&savetimers);
assert(pszBackupFileName != NULL && pszBackupFileName[0] != '\0');
return DirectFunctionCall1(textin, CStringGetDatum(pszBackupFileName));
#else
PG_RETURN_NULL();
#endif
}
/*
* gp_read_backup_file__( TEXT, TEXT, int ) returns TEXT
*
*/
PG_FUNCTION_INFO_V1(gp_read_backup_file__);
Datum
gp_read_backup_file__(PG_FUNCTION_ARGS)
{
#ifdef GPDUMP_SUPPORT_IN_HAWQ
/* Fetch the parameters from the argument list. */
char *pszBackupDirectory = "./";
char *pszBackupKey = "";
int fileType = 0;
char *pszFileName;
struct stat info;
char *pszFullStatus = NULL;
FILE *f;
if (!PG_ARGISNULL(0))
{
pszBackupDirectory = GET_STR(PG_GETARG_TEXT_P(0));
if (*pszBackupDirectory == '\0')
pszBackupDirectory = "./";
}
if (!PG_ARGISNULL(1))
pszBackupKey = GET_STR(PG_GETARG_TEXT_P(1));
if (!PG_ARGISNULL(2))
fileType = PG_GETARG_INT32(2);
/*
* if BackupDirectory is relative, make it absolute based on the directory
* where the database resides
*/
pszBackupDirectory = relativeToAbsolute(pszBackupDirectory);
/* Form backup file path name */
pszFileName = NULL;
switch (fileType)
{
case BFT_BACKUP:
/*pszFileName = formBackupFilePathName(pszBackupDirectory, pszBackupKey);*/
/*we can't support this without making a change to this function arguments, which
will require an initdb, which we want to avoid. This is due to the fact that in
this call to formBackupFilePathName we don't know if the file is compressed or
not and formBackupFilePathName will not know if to create a file with a compression
suffix or not */
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("Only status files are currently supported in gp_read_backup_file")));
break;
case BFT_BACKUP_STATUS:
case BFT_RESTORE_STATUS:
pszFileName = formStatusFilePathName(pszBackupDirectory, pszBackupKey, (fileType == BFT_BACKUP_STATUS));
break;
default:
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("Invalid filetype %d passed to gp_read_backup_file", fileType)));
break;
}
/* Make sure pszFileName exists */
if (0 != stat(pszFileName, &info))
{
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("Backup File %s Type %d could not be be found", pszFileName, fileType)));
}
/* Read file */
if (info.st_size > INT_MAX)
{
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("Backup File %s Type %d too large to read", pszFileName, fileType)));
}
/* Allocate enough memory for entire file, and read it in. */
pszFullStatus = (char *) palloc(info.st_size + 1);
if (pszFullStatus == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
}
f = fopen(pszFileName, "r");
if (f == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("Backup File %s Type %d cannot be opened", pszFileName, fileType)));
}
if (info.st_size != fread(pszFullStatus, 1, info.st_size, f))
{
fclose(f);
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("Error reading Backup File %s Type %d", pszFileName, fileType)));
}
fclose(f);
f = NULL;
pszFullStatus[info.st_size] = '\0';
return DirectFunctionCall1(textin, CStringGetDatum(positionToError(pszFullStatus)));
#else
PG_RETURN_NULL();
#endif
}
/*
* gp_write_backup_file__( TEXT, TEXT, TEXT ) returns TEXT
*
*/
/* NOT IN USE ANYMORE! */
/*
* gp_write_backup_file__( TEXT, TEXT, TEXT ) returns TEXT
*
*/
/*
* Greenplum TODO: This function assumes the file is not compressed.
* This is not always the case. Therefore it should be used carefully
* by the caller, until we decide to make the change and pass in an
* isCompressed boolean, which unfortunately will require an initdb for
* the change to take effect.
*/
PG_FUNCTION_INFO_V1(gp_write_backup_file__);
Datum
gp_write_backup_file__(PG_FUNCTION_ARGS)
{
#ifdef GPDUMP_SUPPORT_IN_HAWQ
/* Fetch the parameters from the argument list. */
char *pszBackupDirectory = "./";
char *pszBackupKey = "";
char *pszBackup = "";
char *pszFileName;
FILE *f;
int nBytes;
if (!PG_ARGISNULL(0))
{
pszBackupDirectory = GET_STR(PG_GETARG_TEXT_P(0));
if (*pszBackupDirectory == '\0')
pszBackupDirectory = "./";
}
if (!PG_ARGISNULL(1))
pszBackupKey = GET_STR(PG_GETARG_TEXT_P(1));
if (!PG_ARGISNULL(2))
pszBackup = GET_STR(PG_GETARG_TEXT_P(2));
/*
* if BackupDirectory is relative, make it absolute based on the directory
* where the database resides
*/
pszBackupDirectory = relativeToAbsolute(pszBackupDirectory);
/*
* See whether the directory exists.
* If not, try to create it.
* If so, make sure it's a directory
*/
validateBackupDirectory(pszBackupDirectory);
/* Form backup file path name */
pszFileName = formBackupFilePathName(pszBackupDirectory, pszBackupKey, false, false);
/* Write file */
f = fopen(pszFileName, "w");
if (f == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("Backup File %s cannot be opened", pszFileName)));
}
nBytes = strlen(pszBackup);
if (nBytes != fwrite(pszBackup, 1, nBytes, f))
{
fclose(f);
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("Error writing Backup File %s", pszFileName)));
}
fclose(f);
f = NULL;
assert(pszFileName != NULL && pszFileName[0] != '\0');
return DirectFunctionCall1(textin, CStringGetDatum(pszFileName));
#else
PG_RETURN_NULL();
#endif
}
#ifdef GPDUMP_SUPPORT_IN_HAWQ
/*
* createBackupDirectory( char* pszPathName ) return bool
*
* This will create a directory if it doesn't exist.
*
* This routine first attempts to create the directory using the full path (in case the
* mkdir function is able to create the full directory). If this mkdir fails with EEXIST,
* filesystem object is checked to ensure it is a directory and fails with ENOTDIR if it
* is not.
*
* If the "full" mkdir fails with a code other than ENOENT, this routine fails. For the
* ENOENT code, the routine attempts to create the directory tree from the top, one level
* at a time, failing if unable to create a directory with a code other than EEXIST and
* the EEXIST level is not a directory. Being tolerant of EEXIST failures allows for
* multiple segments to attempt to create the directory simultaneously.
*/
bool
createBackupDirectory(char *pszPathName)
{
int rc;
struct stat info;
char *pSlash;
/* Can we make it in its entirety? If so, return */
if (0 == mkdir(pszPathName, S_IRWXU))
return true;
if (errno == ENOENT)
{
/* Try to create each level of the hierarchy that doesn't already exist */
pSlash = pszPathName + 1;
while (pSlash != NULL && *pSlash != '\0')
{
/* Find next subdirectory delimiter */
pSlash = strchr(pSlash, '/');
if (pSlash == NULL)
{
/*
* If no more slashes we're at the last level. Attempt to
* create it; if it fails for any reason other than EEXIST,
* fail the call.
*/
rc = mkdir(pszPathName, S_IRWXU);
if (rc != 0 && errno != EEXIST)
{
return false;
}
}
else
{
/* Temporarily end the string at the / we just found. */
*pSlash = '\0';
/* Attempt to create the level; return code checked below. */
rc = mkdir(pszPathName, S_IRWXU);
/*
* If failed and the directory level does not exist, fail the call.
* Is it possible for a directory level to exist and the mkdir() call
* fail with a code such as ENOSYS (and possibly others) instead of
* EEXIST. So, if mkdir() fails, the directory level's existence is
* checked. If the level does't exist or isn't a directory, the
* call is failed.
*/
if (rc != 0)
{
struct stat statbuf;
int errsave = errno; /* Save mkdir error code over stat() call. */
if ( stat(pszPathName, &statbuf) < 0 || !S_ISDIR(statbuf.st_mode) )
{
errno = errsave; /* Restore mkdir error code. */
*pSlash = '/'; /* Put back the slash we overwrote above. */
return false;
}
}
/* Put back the slash we overwrote above. */
*pSlash = '/';
/*
* At this point, this level exists -- either because it was created
* or it already exists. If it already exists, it *could* be a
* non-directory. This will be caught when attempting to create the
* next level.
*/
/* Advance past the slash. */
pSlash++;
}
}
}
else if (errno != EEXIST)
{
return false;
}
/* At this point, the path has been created or otherwise exists. Ensure it's a directory. */
if (0 != stat(pszPathName, &info))
{
/* An unexpected failure obtaining the file info. errno is set. */
return false;
}
else if (!S_ISDIR(info.st_mode))
{
/* The pathname exists but is not a directory! */
errno = ENOTDIR;
return false;
}
return true;
}
/* findAcceptableBackupFilePathName( char* pszBackupDirectory, char* pszBackupKey ) returns char*
*
* This function takes the directory and timestamp key and finds an existing file that matches
* the naming convention for a backup file with the proper key and segid, but any instid.
* In case it was backed up from a redundant instance of the same segment.
*/
char *
findAcceptableBackupFilePathName(char *pszBackupDirectory, char *pszBackupKey, int segid)
{
/* Make sure that pszBackupDirectory exists and is in fact a directory. */
struct stat buf;
struct dirent *dirp = NULL;
DIR *dp = NULL;
char *pszBackupFilePathName = NULL;
static regex_t rFindFileName;
static bool bFirstTime = true;
char *pszRegex;
char *pszFileName;
char *pszSep;
struct stat info;
if (bFirstTime)
{
int wmasklen,
masklen;
pg_wchar *mask;
pszRegex = (char *) palloc(34 + strlen(pszBackupKey) + 1);
if (pszRegex == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
}
/* dump file can start with gp_ and also mpp_ for 2.3 backward compatibility */
sprintf(pszRegex, "^(gp|mpp)_dump_[0-9]+_%d_%s(.gz)?$", segid, pszBackupKey);
masklen = strlen(pszRegex);
mask = (pg_wchar *) palloc((masklen + 1) * sizeof(pg_wchar));
wmasklen = pg_mb2wchar_with_len(pszRegex, mask, masklen);
if (0 != pg_regcomp(&rFindFileName, mask, wmasklen, REG_EXTENDED))
{
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("Could not compile regular expression for backup filename matching")));
}
bFirstTime = false;
}
if (lstat(pszBackupDirectory, &buf) < 0)
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Backup Directory %s does not exist.", pszBackupDirectory)));
}
if (S_ISDIR(buf.st_mode) == 0)
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Backup Location %s is not a directory.", pszBackupDirectory)));
}
dp = opendir(pszBackupDirectory);
if (dp == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Backup Directory %s cannot be opened for enumerating files.", pszBackupDirectory)));
}
while (NULL != (dirp = readdir(dp)))
{
pg_wchar *data;
size_t data_len;
int newfile_len;
pszFileName = dirp->d_name;
/* Convert data string to wide characters */
newfile_len = strlen(pszFileName);
data = (pg_wchar *) palloc((newfile_len + 1) * sizeof(pg_wchar));
data_len = pg_mb2wchar_with_len(pszFileName, data, newfile_len);
if (0 == pg_regexec(&rFindFileName, data, data_len, 0, NULL, 0, NULL, 0))
{
pszSep = "/";
if (strlen(pszBackupDirectory) >= 1 && pszBackupDirectory[strlen(pszBackupDirectory) - 1] == '/')
pszSep = "";
pszBackupFilePathName = (char *) palloc(strlen(pszBackupDirectory) + strlen(pszFileName) + strlen(pszSep) + 1);
sprintf(pszBackupFilePathName, "%s%s%s", pszBackupDirectory, pszSep, pszFileName);
/* Make sure that this is a regular file */
if (0 == stat(pszBackupFilePathName, &info) && S_ISREG(info.st_mode))
{
break;
}
}
}
closedir(dp);
return pszBackupFilePathName;
}
/* formBackupFilePathName( char* pszBackupDirectory, char* pszBackupKey ) returns char*
*
* This function takes the directory and timestamp key and creates
* the path and filename of the backup output file
* based on the naming convention for this.
*/
char *
formBackupFilePathName(char *pszBackupDirectory, char *pszBackupKey, bool is_compress, bool isPostData)
{
#ifndef PATH_MAX
#define PATH_MAX 1024
#endif
/* First form the prefix */
char szFileNamePrefix[1 + PATH_MAX];
int instid; /* dispatch node */
int segid;
int len;
char *pszBackupFileName;
// instid = (GpIdentity.segindex == -1) ? 1 : 0; /* dispatch node */
// segid = GpIdentity.dbid;
sprintf(szFileNamePrefix, "gp_dump_%d_%d_", instid, segid);
/* Now add up the length of the pieces */
len = strlen(pszBackupDirectory);
assert(len >= 1);
if (pszBackupDirectory[strlen(pszBackupDirectory) - 1] != '/')
len++;
len += strlen(szFileNamePrefix);
len += strlen(pszBackupKey);
/* if gzip/gunzip is used, the suffix is gz */
if (is_compress)
len += strlen(".gz");
if (isPostData)
len += strlen("_post_data");
if (len > PATH_MAX)
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Backup FileName based on path %s and key %s too long", pszBackupDirectory, pszBackupKey)));
}
pszBackupFileName = (char *) palloc(sizeof(char) * (1 + len));
if (pszBackupFileName == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
}
strcpy(pszBackupFileName, pszBackupDirectory);
if (pszBackupDirectory[strlen(pszBackupDirectory) - 1] != '/')
strcat(pszBackupFileName, "/");
strcat(pszBackupFileName, szFileNamePrefix);
strcat(pszBackupFileName, pszBackupKey);
if (isPostData)
strcat(pszBackupFileName, "_post_data");
/* if gzip/gunzip is used, the suffix is gz */
if (is_compress)
strcat(pszBackupFileName, ".gz");
return pszBackupFileName;
}
/* formStatusFilePathName( char* pszBackupDirectory, char* pszBackupKey, bool bIsBackup ) returns char*
*
* This function takes the directory and timestamp key and IsBackup flag and creates
* the path and filename of the backup or restore status file
* based on the naming convention for this.
*/
char *
formStatusFilePathName(char *pszBackupDirectory, char *pszBackupKey, bool bIsBackup)
{
/* First form the prefix */
char szFileNamePrefix[1 + PATH_MAX];
int instid; /* dispatch node */
int segid;
int len;
char *pszFileName;
// instid = (GpIdentity.segindex == -1) ? 1 : 0; /* dispatch node */
// segid = GpIdentity.dbid;
sprintf(szFileNamePrefix, "gp_%s_status_%d_%d_", (bIsBackup ? "dump" : "restore"),
instid, segid);
/* Now add up the length of the pieces */
len = strlen(pszBackupDirectory);
assert(len >= 1);
if (pszBackupDirectory[strlen(pszBackupDirectory) - 1] != '/')
len++;
len += strlen(szFileNamePrefix);
len += strlen(pszBackupKey);
if (len > PATH_MAX)
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Status FileName based on path %s and key %s too long", pszBackupDirectory, pszBackupKey)));
}
pszFileName = (char *) palloc(sizeof(char) * (1 + len));
if (pszFileName == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
}
strcpy(pszFileName, pszBackupDirectory);
if (pszBackupDirectory[strlen(pszBackupDirectory) - 1] != '/')
strcat(pszFileName, "/");
strcat(pszFileName, szFileNamePrefix);
strcat(pszFileName, pszBackupKey);
return pszFileName;
}
/* formThrottleCmd(char *pszBackupFileName, int directIO_read_chunk_mb, bool bIsThrottlingEnabled) returns char*
*
* This function takes the backup file name and creates
* the throttlin command
*/
char *
formThrottleCmd(char *pszBackupFileName, int directIO_read_chunk_mb, bool bIsThrottlingEnabled)
{
int throttleCMDLen = 0;
char *pszThrottleCmd;
if(bIsThrottlingEnabled)
{
throttleCMDLen = strlen("| throttlingD.py ")
+ 3 + 1 /* gp_backup_directIO_read_chunk_mb */
+ strlen(pszBackupFileName)
+ 1;
/* Create the throttlingD.py string */
pszThrottleCmd = (char *)palloc(throttleCMDLen);
if (pszThrottleCmd == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory")));
}
sprintf(pszThrottleCmd, "| throttlingD.py %d %s",directIO_read_chunk_mb ,pszBackupFileName);
}
else
{
// when directIO is disabled --> need to pipe the dump to backupFile (No throttling will be done)
throttleCMDLen = 2 + 1 /* > */
+ strlen(pszBackupFileName)
+ 1;
/* Create the empty throttling string */
pszThrottleCmd = (char *)palloc(throttleCMDLen);
if (pszThrottleCmd == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory")));
}
sprintf(pszThrottleCmd, " > %s",pszBackupFileName);
}
return pszThrottleCmd;
}
/*
* relativeToAbsolute( char* pszRelativeDirectory ) returns char*
*
* This will turn a relative path into an absolute one, based off of the Data Directory
* for this database.
*
* If the path isn't absolute, it is prefixed with DataDir/.
*/
char *
relativeToAbsolute(char *pszRelativeDirectory)
{
char *pszAbsolutePath;
if (pszRelativeDirectory[0] == '/')
return pszRelativeDirectory;
pszAbsolutePath = (char *) palloc(strlen(DataDir) + 1 + strlen(pszRelativeDirectory) + 1);
if (pszAbsolutePath == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
}
sprintf(pszAbsolutePath, "%s/%s", DataDir, pszRelativeDirectory);
return pszAbsolutePath;
}
/* testCmdsExist( char* pszCompressionProgram ) returns void
*
* This function tests whether pszBackupProgram and the pszCompressionProgram
* (if non-empty) are in the path. If not, it calls ereport.
*/
void
testCmdsExist(char *pszCompressionProgram, char *pszBackupProgram)
{
/* Does pszBackupProgram exist? */
if (pszBackupProgram != NULL &&
*pszBackupProgram != '\0' &&
(bkPg = testProgramExists(pszBackupProgram)) == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("%s not found in PGPATH or PATH (PGPATH: %s | PATH: %s)",
pszBackupProgram, getenv("PGPATH"), getenv("PATH")),
errhint("Restart the server and try again")));
}
/* Does compression utility exist? */
if (pszCompressionProgram != NULL &&
*pszCompressionProgram != '\0' &&
(compPg = testProgramExists(pszCompressionProgram)) == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
errmsg("%s (compression utility) not found in in PGPATH or "
"PATH (PGPATH: %s | PATH: %s)",
pszCompressionProgram, getenv("PGPATH"), getenv("PATH"))));
}
}
/* testProgramExists(char* pszProgramName) returns bool
* This runs a shell with which pszProgramName > tempfile
* piping the output to a temp file.
* If the file is empty, then pszProgramName didn't exist
* on the path.
*/
char *
testProgramExists(char *pszProgramName)
{
char *pszProgramNameLocal;
char *p;
/* Now see what the length of the file is - 0 means not there */
struct stat buf;
char *pszEnvPath;
char *pszPath;
char *pColon;
char *pColonNext;
char *pszTestPath;
if (pszProgramName == NULL || *pszProgramName == '\0')
return NULL;
/*
* The pszProgramName might have command line arguments, so we need to
* stop at the first whitespace
*/
pszProgramNameLocal = palloc(strlen(pszProgramName) + 1);
if (pszProgramNameLocal == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
}
strcpy(pszProgramNameLocal, pszProgramName);
p = pszProgramNameLocal;
while (*p != '\0')
{
if (*p == ' ' || *p == '\t' || *p == '\n')
break;
p++;
}
*p = '\0';
pszEnvPath = getenv("PGPATH");
if (pszEnvPath != NULL)
{
pszTestPath = (char *) palloc(strlen(pszEnvPath) + 1 + strlen(pszProgramNameLocal) + 1);
if (pszTestPath == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
}
sprintf(pszTestPath, "%s/%s", pszEnvPath, pszProgramNameLocal);
if (stat(pszTestPath, &buf) >= 0)
return pszTestPath;
}
pszEnvPath = getenv("PATH");
if (pszEnvPath == NULL)
return NULL;
pszPath = (char *) palloc(strlen(pszEnvPath) + 1);
if (pszPath == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
}
strcpy(pszPath, pszEnvPath);
/* Try to create each level of the hierarchy that doesn't already exist */
pColon = pszPath;
while (pColon != NULL && *pColon != '\0')
{
/* Find next delimiter */
pColonNext = strchr(pColon, ':');
if (pColonNext == pColon)
{
pColon++;
continue;
}
if (pColonNext == NULL)
{
/* See whether pszProgramName exists in subdirectory pColon */
pszTestPath = (char *) palloc(strlen(pColon) + 1 + strlen(pszProgramNameLocal) + 1);
if (pszTestPath == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
}
sprintf(pszTestPath, "%s/%s", pColon, pszProgramNameLocal);
if (stat(pszTestPath, &buf) >= 0)
return pszTestPath;
else
return NULL;
}
/* Temporarily end the string at the : we just found. */
*pColonNext = '\0';
/* See whether pszProgramName exists in subdirectory pColon */
pszTestPath = (char *) palloc(strlen(pColon) + 1 + strlen(pszProgramNameLocal) + 1);
if (pszTestPath == NULL)
{
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
}
sprintf(pszTestPath, "%s/%s", pColon, pszProgramNameLocal);
if (stat(pszTestPath, &buf) >= 0)
{
*pColonNext = ':';
return pszTestPath;
}
/* Put back the colon we overwrote above. */
*pColonNext = ':';
/* Advance past the colon. */
pColon = pColonNext + 1;
/* free */
pfree(pszTestPath);
}
return NULL;
}
/* validateBackupDirectory( char* pszBackupDirectory ) returns void
* This sees whether the backupDirectory exists
* If so, it makes sure its a directory and not a file.
* If not, it tries to create it. If it cannot it calls ereport
*/
void
validateBackupDirectory(char *pszBackupDirectory)
{
struct stat info;
if (stat(pszBackupDirectory, &info) < 0)
{
elog(DEBUG1, "Backup Directory %s does not exist", pszBackupDirectory);
/* Attempt to create it. */
if (!createBackupDirectory(pszBackupDirectory))
{
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not create backup directory \"%s\": %m",
pszBackupDirectory)));
}
else
{
elog(DEBUG1, "Successfully created Backup Directory %s", pszBackupDirectory);
}
}
else
{
if (!S_ISDIR(info.st_mode))
{
ereport(ERROR,
(errcode_for_file_access(),
errmsg("BackupDirectory %s exists but is not a directory.", pszBackupDirectory)));
}
}
}
/*
* positionToError - given a char buffer, position to the first error msg in it.
*
* we are only interested in ERRORs. therefore, search for the first ERROR
* and return the rest of the status file starting from that point. if no error
* found, return a "no error found" string. We are only interested in occurrences
* of "ERROR:" (from the backend), or [ERROR] (from dump/restore log). others,
* like "ON_ERROR_STOP" should be ignored.
*/
static char *positionToError(char *source)
{
char* sourceCopy = source;
char* firstErr = NULL;
char* defaultNoErr = "No extra error information available. Please examine status file.";
while(true)
{
firstErr = strstr((const char*)sourceCopy, "ERROR");
if (!firstErr)
{
break;
}
else
{
/* found "ERROR". only done if it's "ERROR:" or "[ERROR]" */
if (firstErr[5] == ']' || firstErr[5] == ':')
{
break; /* done */
}
else
{
sourceCopy = ++firstErr; /* start from stopping point */
firstErr = NULL;
}
}
}
if (!firstErr)
{
/* no [ERROR] found */
firstErr = defaultNoErr;
}
else
{
/* found [ERROR]. go back to beginning of the line */
char *p;
for (p = firstErr ; p > source ; p--)
{
if (*p == '\n')
{
p++; /* skip the LF */
break;
}
}
firstErr = p;
}
return firstErr;
}
#endif