postgresql/contrib/pg_standby/pg_standby.c

910 lines
23 KiB
C
Raw Normal View History

/*
2010-09-20 22:08:53 +02:00
* contrib/pg_standby/pg_standby.c
*
*
* pg_standby.c
2007-11-15 22:14:46 +01:00
*
* Production-ready example of how to create a Warm Standby
2007-11-15 22:14:46 +01:00
* database server using continuous archiving as a
* replication mechanism
*
* We separate the parameters for archive and nextWALfile
2007-11-15 22:14:46 +01:00
* so that we can check the archive exists, even if the
* WAL file doesn't (yet).
*
* This program will be executed once in full for each file
* requested by the warm standby server.
*
* It is designed to cater to a variety of needs, as well
* providing a customizable section.
2007-11-15 22:14:46 +01:00
*
* Original author: Simon Riggs simon@2ndquadrant.com
* Current maintainer: Simon Riggs
*/
#include "postgres_fe.h"
#include <ctype.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/time.h>
#include "access/xlog_internal.h"
#include "pg_getopt.h"
const char *progname;
int WalSegSz = -1;
/* Options and defaults */
2007-11-15 22:14:46 +01:00
int sleeptime = 5; /* amount of time to sleep between file checks */
int waittime = -1; /* how long we have been waiting, -1 no wait
* yet */
int maxwaittime = 0; /* how long are we prepared to wait for? */
int keepfiles = 0; /* number of WAL files to keep, 0 keep all */
int maxretries = 3; /* number of retries on restore command */
bool debug = false; /* are we debugging? */
Phase 2 of pgindent updates. Change pg_bsd_indent to follow upstream rules for placement of comments to the right of code, and remove pgindent hack that caused comments following #endif to not obey the general rule. Commit e3860ffa4dd0dad0dd9eea4be9cc1412373a8c89 wasn't actually using the published version of pg_bsd_indent, but a hacked-up version that tried to minimize the amount of movement of comments to the right of code. The situation of interest is where such a comment has to be moved to the right of its default placement at column 33 because there's code there. BSD indent has always moved right in units of tab stops in such cases --- but in the previous incarnation, indent was working in 8-space tab stops, while now it knows we use 4-space tabs. So the net result is that in about half the cases, such comments are placed one tab stop left of before. This is better all around: it leaves more room on the line for comment text, and it means that in such cases the comment uniformly starts at the next 4-space tab stop after the code, rather than sometimes one and sometimes two tabs after. Also, ensure that comments following #endif are indented the same as comments following other preprocessor commands such as #else. That inconsistency turns out to have been self-inflicted damage from a poorly-thought-through post-indent "fixup" in pgindent. This patch is much less interesting than the first round of indent changes, but also bulkier, so I thought it best to separate the effects. Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:18:54 +02:00
bool need_cleanup = false; /* do we need to remove files from
* archive? */
#ifndef WIN32
static volatile sig_atomic_t signaled = false;
#endif
2007-11-15 22:14:46 +01:00
char *archiveLocation; /* where to find the archive? */
char *triggerPath; /* where to find the trigger file? */
char *xlogFilePath; /* where we are going to restore to */
char *nextWALFileName; /* the file we need to get from archive */
char *restartWALFileName; /* the file from which we can restart restore */
char WALFilePath[MAXPGPATH * 2]; /* the file path including archive */
2007-11-15 22:14:46 +01:00
char restoreCommand[MAXPGPATH]; /* run this to restore */
Phase 2 of pgindent updates. Change pg_bsd_indent to follow upstream rules for placement of comments to the right of code, and remove pgindent hack that caused comments following #endif to not obey the general rule. Commit e3860ffa4dd0dad0dd9eea4be9cc1412373a8c89 wasn't actually using the published version of pg_bsd_indent, but a hacked-up version that tried to minimize the amount of movement of comments to the right of code. The situation of interest is where such a comment has to be moved to the right of its default placement at column 33 because there's code there. BSD indent has always moved right in units of tab stops in such cases --- but in the previous incarnation, indent was working in 8-space tab stops, while now it knows we use 4-space tabs. So the net result is that in about half the cases, such comments are placed one tab stop left of before. This is better all around: it leaves more room on the line for comment text, and it means that in such cases the comment uniformly starts at the next 4-space tab stop after the code, rather than sometimes one and sometimes two tabs after. Also, ensure that comments following #endif are indented the same as comments following other preprocessor commands such as #else. That inconsistency turns out to have been self-inflicted damage from a poorly-thought-through post-indent "fixup" in pgindent. This patch is much less interesting than the first round of indent changes, but also bulkier, so I thought it best to separate the effects. Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:18:54 +02:00
char exclusiveCleanupFileName[MAXFNAMELEN]; /* the file we need to get
* from archive */
/*
* Two types of failover are supported (smart and fast failover).
*
* The content of the trigger file determines the type of failover. If the
* trigger file contains the word "smart" (or the file is empty), smart
* failover is chosen: pg_standby acts as cp or ln command itself, on
* successful completion all the available WAL records will be applied
* resulting in zero data loss. But, it might take a long time to finish
* recovery if there's a lot of unapplied WAL.
*
* On the other hand, if the trigger file contains the word "fast", the
* recovery is finished immediately even if unapplied WAL files remain. Any
* transactions in the unapplied WAL files are lost.
*
* An empty trigger file performs smart failover. SIGUSR or SIGINT triggers
* fast failover. A timeout causes fast failover (smart failover would have
* the same effect, since if the timeout is reached there is no unapplied WAL).
*/
#define NoFailover 0
#define SmartFailover 1
#define FastFailover 2
static int Failover = NoFailover;
#define RESTORE_COMMAND_COPY 0
#define RESTORE_COMMAND_LINK 1
2007-11-15 22:14:46 +01:00
int restoreCommandType;
#define XLOG_DATA 0
#define XLOG_HISTORY 1
2007-11-15 22:14:46 +01:00
int nextWALFileType;
#define SET_RESTORE_COMMAND(cmd, arg1, arg2) \
snprintf(restoreCommand, MAXPGPATH, cmd " \"%s\" \"%s\"", arg1, arg2)
struct stat stat_buf;
static bool SetWALFileNameForCleanup(void);
static bool SetWALSegSize(void);
/* =====================================================================
*
* Customizable section
*
* =====================================================================
*
* Currently, this section assumes that the Archive is a locally
* accessible directory. If you want to make other assumptions,
* such as using a vendor-specific archive and access API, these
* routines are the ones you'll need to change. You're
* encouraged to submit any changes to pgsql-hackers@lists.postgresql.org
2007-11-15 22:14:46 +01:00
* or personally to the current maintainer. Those changes may be
* folded in to later versions of this program.
*/
/*
2007-11-15 22:14:46 +01:00
* Initialize allows customized commands into the warm standby program.
*
2007-11-15 22:14:46 +01:00
* As an example, and probably the common case, we use either
* cp/ln commands on *nix, or copy/move command on Windows.
*/
static void
CustomizableInitialize(void)
{
#ifdef WIN32
snprintf(WALFilePath, MAXPGPATH, "%s\\%s", archiveLocation, nextWALFileName);
switch (restoreCommandType)
{
case RESTORE_COMMAND_LINK:
2007-11-15 22:14:46 +01:00
SET_RESTORE_COMMAND("mklink", WALFilePath, xlogFilePath);
break;
case RESTORE_COMMAND_COPY:
default:
2007-11-15 22:14:46 +01:00
SET_RESTORE_COMMAND("copy", WALFilePath, xlogFilePath);
break;
2007-11-15 22:14:46 +01:00
}
#else
snprintf(WALFilePath, MAXPGPATH, "%s/%s", archiveLocation, nextWALFileName);
switch (restoreCommandType)
{
case RESTORE_COMMAND_LINK:
#ifdef HAVE_WORKING_LINK
2007-11-15 22:14:46 +01:00
SET_RESTORE_COMMAND("ln -s -f", WALFilePath, xlogFilePath);
break;
#endif
case RESTORE_COMMAND_COPY:
default:
2007-11-15 22:14:46 +01:00
SET_RESTORE_COMMAND("cp", WALFilePath, xlogFilePath);
break;
2007-11-15 22:14:46 +01:00
}
#endif
/*
2007-11-15 22:14:46 +01:00
* This code assumes that archiveLocation is a directory You may wish to
* add code to check for tape libraries, etc.. So, since it is a
* directory, we use stat to test if it's accessible
*/
if (stat(archiveLocation, &stat_buf) != 0)
{
2011-08-14 20:03:08 +02:00
fprintf(stderr, "%s: archive location \"%s\" does not exist\n", progname, archiveLocation);
fflush(stderr);
2007-11-15 22:14:46 +01:00
exit(2);
}
}
/*
* CustomizableNextWALFileReady()
2007-11-15 22:14:46 +01:00
*
* Is the requested file ready yet?
*/
2007-11-15 22:14:46 +01:00
static bool
CustomizableNextWALFileReady(void)
{
if (stat(WALFilePath, &stat_buf) == 0)
{
/*
* If we've not seen any WAL segments, we don't know the WAL segment
* size, which we need. If it looks like a WAL segment, determine size
* of segments for the cluster.
*/
if (WalSegSz == -1 && IsXLogFileName(nextWALFileName))
{
if (SetWALSegSize())
{
/*
* Successfully determined WAL segment size. Can compute
* cleanup cutoff now.
*/
need_cleanup = SetWALFileNameForCleanup();
if (debug)
{
fprintf(stderr,
_("WAL segment size: %d \n"), WalSegSz);
fprintf(stderr, "Keep archive history: ");
if (need_cleanup)
fprintf(stderr, "%s and later\n",
exclusiveCleanupFileName);
else
fprintf(stderr, "no cleanup required\n");
}
}
}
/*
* Return only if it's the right size already.
*/
if (WalSegSz > 0 && stat_buf.st_size == WalSegSz)
2007-11-15 22:14:46 +01:00
{
#ifdef WIN32
2007-11-15 22:14:46 +01:00
/*
* Windows 'cp' sets the final file size before the copy is
* complete, and not yet ready to be opened by pg_standby. So we
* wait for sleeptime secs before attempting to restore. If that
* is not enough, we will rely on the retry/holdoff mechanism.
* GNUWin32's cp does not have this problem.
2007-11-15 22:14:46 +01:00
*/
pg_usleep(sleeptime * 1000000L);
#endif
2007-11-15 22:14:46 +01:00
nextWALFileType = XLOG_DATA;
return true;
}
/*
* If still too small, wait until it is the correct size
*/
if (WalSegSz > 0 && stat_buf.st_size > WalSegSz)
{
if (debug)
{
2007-11-15 22:14:46 +01:00
fprintf(stderr, "file size greater than expected\n");
fflush(stderr);
}
2007-11-15 22:14:46 +01:00
exit(3);
}
}
return false;
}
static void
CustomizableCleanupPriorWALFiles(void)
{
/*
* Work out name of prior file from current filename
*/
if (nextWALFileType == XLOG_DATA)
{
2007-11-15 22:14:46 +01:00
int rc;
DIR *xldir;
struct dirent *xlde;
/*
* Assume it's OK to keep failing. The failure situation may change
2007-11-15 22:14:46 +01:00
* over time, so we'd rather keep going on the main processing than
2011-02-02 01:07:42 +01:00
* fail because we couldn't clean up yet.
*/
if ((xldir = opendir(archiveLocation)) != NULL)
{
while (errno = 0, (xlde = readdir(xldir)) != NULL)
{
/*
2007-11-15 22:14:46 +01:00
* We ignore the timeline part of the XLOG segment identifiers
* in deciding whether a segment is still needed. This
* ensures that we won't prematurely remove a segment from a
* parent timeline. We could probably be a little more
* proactive about removing segments of non-parent timelines,
* but that would be a whole lot more complicated.
*
2007-11-15 22:14:46 +01:00
* We use the alphanumeric sorting property of the filenames
* to decide which ones are earlier than the
* exclusiveCleanupFileName file. Note that this means files
* are not removed in the order they were originally written,
* in case this worries you.
*/
if (IsXLogFileName(xlde->d_name) &&
strcmp(xlde->d_name + 8, exclusiveCleanupFileName + 8) < 0)
{
#ifdef WIN32
snprintf(WALFilePath, sizeof(WALFilePath), "%s\\%s", archiveLocation, xlde->d_name);
#else
snprintf(WALFilePath, sizeof(WALFilePath), "%s/%s", archiveLocation, xlde->d_name);
#endif
if (debug)
2011-08-14 20:03:08 +02:00
fprintf(stderr, "\nremoving file \"%s\"", WALFilePath);
rc = unlink(WALFilePath);
if (rc != 0)
{
2011-08-14 20:03:08 +02:00
fprintf(stderr, "\n%s: ERROR: could not remove file \"%s\": %s\n",
progname, WALFilePath, strerror(errno));
break;
}
}
}
if (errno)
fprintf(stderr, "%s: could not read archive location \"%s\": %s\n",
progname, archiveLocation, strerror(errno));
if (debug)
fprintf(stderr, "\n");
}
else
2011-08-14 20:03:08 +02:00
fprintf(stderr, "%s: could not open archive location \"%s\": %s\n",
progname, archiveLocation, strerror(errno));
if (closedir(xldir))
fprintf(stderr, "%s: could not close archive location \"%s\": %s\n",
progname, archiveLocation, strerror(errno));
fflush(stderr);
}
}
/* =====================================================================
* End of Customizable section
* =====================================================================
*/
/*
* SetWALFileNameForCleanup()
2007-11-15 22:14:46 +01:00
*
* Set the earliest WAL filename that we want to keep on the archive
2007-11-15 22:14:46 +01:00
* and decide whether we need_cleanup
*/
static bool
SetWALFileNameForCleanup(void)
{
2007-11-15 22:14:46 +01:00
uint32 tli = 1,
log = 0,
seg = 0;
uint32 log_diff = 0,
seg_diff = 0;
bool cleanup = false;
int max_segments_per_logfile = (0xFFFFFFFF / WalSegSz);
if (restartWALFileName)
{
/*
* Don't do cleanup if the restartWALFileName provided is later than
* the xlog file requested. This is an error and we must not remove
* these files from archive. This shouldn't happen, but better safe
* than sorry.
*/
if (strcmp(restartWALFileName, nextWALFileName) > 0)
return false;
strlcpy(exclusiveCleanupFileName, restartWALFileName, sizeof(exclusiveCleanupFileName));
return true;
}
if (keepfiles > 0)
{
sscanf(nextWALFileName, "%08X%08X%08X", &tli, &log, &seg);
if (tli > 0 && seg > 0)
{
log_diff = keepfiles / max_segments_per_logfile;
seg_diff = keepfiles % max_segments_per_logfile;
2007-11-15 22:14:46 +01:00
if (seg_diff > seg)
{
log_diff++;
seg = max_segments_per_logfile - (seg_diff - seg);
}
else
seg -= seg_diff;
if (log >= log_diff)
{
log -= log_diff;
cleanup = true;
}
else
{
log = 0;
seg = 0;
}
}
}
XLogFileNameById(exclusiveCleanupFileName, tli, log, seg);
return cleanup;
}
/*
* Try to set the wal segment size from the WAL file specified by WALFilePath.
*
* Return true if size could be determined, false otherwise.
*/
static bool
SetWALSegSize(void)
{
bool ret_val = false;
int fd;
PGAlignedXLogBlock buf;
Assert(WalSegSz == -1);
if ((fd = open(WALFilePath, O_RDWR, 0)) < 0)
{
fprintf(stderr, "%s: could not open WAL file \"%s\": %s\n",
progname, WALFilePath, strerror(errno));
return false;
}
errno = 0;
if (read(fd, buf.data, XLOG_BLCKSZ) == XLOG_BLCKSZ)
{
XLogLongPageHeader longhdr = (XLogLongPageHeader) buf.data;
WalSegSz = longhdr->xlp_seg_size;
if (IsValidWalSegSize(WalSegSz))
{
/* successfully retrieved WAL segment size */
ret_val = true;
}
else
fprintf(stderr,
"%s: WAL segment size must be a power of two between 1MB and 1GB, but the WAL file header specifies %d bytes\n",
progname, WalSegSz);
}
else
{
/*
* Don't complain loudly, this is to be expected for segments being
* created.
*/
if (errno != 0)
{
if (debug)
fprintf(stderr, "could not read file \"%s\": %s\n",
WALFilePath, strerror(errno));
}
else
{
if (debug)
fprintf(stderr, "not enough data in file \"%s\"\n",
WALFilePath);
}
}
fflush(stderr);
close(fd);
return ret_val;
}
/*
* CheckForExternalTrigger()
2007-11-15 22:14:46 +01:00
*
* Is there a trigger file? Sets global 'Failover' variable to indicate
* what kind of a trigger file it was. A "fast" trigger file is turned
* into a "smart" file as a side-effect.
*/
static void
CheckForExternalTrigger(void)
{
char buf[32];
int fd;
int len;
/*
2007-11-15 22:14:46 +01:00
* Look for a trigger file, if that option has been selected
*
2007-11-15 22:14:46 +01:00
* We use stat() here because triggerPath is always a file rather than
* potentially being in an archive
*/
if (!triggerPath || stat(triggerPath, &stat_buf) != 0)
return;
/*
* An empty trigger file performs smart failover. There's a little race
* condition here: if the writer of the trigger file has just created the
* file, but not yet written anything to it, we'll treat that as smart
* shutdown even if the other process was just about to write "fast" to
* it. But that's fine: we'll restore one more WAL file, and when we're
* invoked next time, we'll see the word "fast" and fail over immediately.
*/
if (stat_buf.st_size == 0)
{
Failover = SmartFailover;
fprintf(stderr, "trigger file found: smart failover\n");
fflush(stderr);
return;
}
if ((fd = open(triggerPath, O_RDWR, 0)) < 0)
{
fprintf(stderr, "WARNING: could not open \"%s\": %s\n",
triggerPath, strerror(errno));
fflush(stderr);
return;
}
if ((len = read(fd, buf, sizeof(buf) - 1)) < 0)
{
fprintf(stderr, "WARNING: could not read \"%s\": %s\n",
triggerPath, strerror(errno));
fflush(stderr);
close(fd);
return;
}
buf[len] = '\0';
if (strncmp(buf, "smart", 5) == 0)
{
Failover = SmartFailover;
fprintf(stderr, "trigger file found: smart failover\n");
fflush(stderr);
close(fd);
return;
}
if (strncmp(buf, "fast", 4) == 0)
{
Failover = FastFailover;
fprintf(stderr, "trigger file found: fast failover\n");
fflush(stderr);
/*
* Turn it into a "smart" trigger by truncating the file. Otherwise if
* the server asks us again to restore a segment that was restored
* already, we would return "not found" and upset the server.
*/
if (ftruncate(fd, 0) < 0)
{
fprintf(stderr, "WARNING: could not read \"%s\": %s\n",
triggerPath, strerror(errno));
fflush(stderr);
}
close(fd);
return;
}
close(fd);
fprintf(stderr, "WARNING: invalid content in \"%s\"\n", triggerPath);
fflush(stderr);
}
/*
* RestoreWALFileForRecovery()
2007-11-15 22:14:46 +01:00
*
* Perform the action required to restore the file from archive
*/
static bool
RestoreWALFileForRecovery(void)
{
2007-11-15 22:14:46 +01:00
int rc = 0;
int numretries = 0;
if (debug)
{
fprintf(stderr, "running restore: ");
fflush(stderr);
}
while (numretries <= maxretries)
{
rc = system(restoreCommand);
if (rc == 0)
{
if (debug)
{
fprintf(stderr, "OK\n");
fflush(stderr);
}
return true;
}
2007-11-15 22:14:46 +01:00
pg_usleep(numretries++ * sleeptime * 1000000L);
}
/*
* Allow caller to add additional info
*/
if (debug)
fprintf(stderr, "not restored\n");
return false;
}
static void
usage(void)
{
printf("%s allows PostgreSQL warm standby servers to be configured.\n\n", progname);
printf("Usage:\n");
printf(" %s [OPTION]... ARCHIVELOCATION NEXTWALFILE XLOGFILEPATH [RESTARTWALFILE]\n", progname);
printf("\nOptions:\n");
printf(" -c copy file from archive (default)\n");
printf(" -d generate lots of debugging output (testing only)\n");
printf(" -k NUMFILESTOKEEP if RESTARTWALFILE is not used, remove files prior to limit\n"
" (0 keeps all)\n");
printf(" -l does nothing; use of link is now deprecated\n");
printf(" -r MAXRETRIES max number of times to retry, with progressive wait\n"
" (default=3)\n");
printf(" -s SLEEPTIME seconds to wait between file checks (min=1, max=60,\n"
" default=5)\n");
printf(" -t TRIGGERFILE trigger file to initiate failover (no default)\n");
printf(" -V, --version output version information, then exit\n");
printf(" -w MAXWAITTIME max seconds to wait for a file (0=no limit) (default=0)\n");
printf(" -?, --help show this help, then exit\n");
printf("\n"
"Main intended use as restore_command in postgresql.conf:\n"
" restore_command = 'pg_standby [OPTION]... ARCHIVELOCATION %%f %%p %%r'\n"
"e.g.\n"
" restore_command = 'pg_standby /mnt/server/archiverdir %%f %%p %%r'\n");
printf("\nReport bugs to <%s>.\n", PACKAGE_BUGREPORT);
printf("%s home page: <%s>\n", PACKAGE_NAME, PACKAGE_URL);
}
#ifndef WIN32
static void
sighandler(int sig)
{
signaled = true;
}
/* We don't want SIGQUIT to core dump */
static void
sigquit_handler(int sig)
{
pqsignal(SIGINT, SIG_DFL);
kill(getpid(), SIGINT);
}
#endif
/*------------ MAIN ----------------------------------------*/
2007-11-15 22:14:46 +01:00
int
main(int argc, char **argv)
{
int c;
progname = get_progname(argv[0]);
if (argc > 1)
{
if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
{
usage();
exit(0);
}
if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
{
puts("pg_standby (PostgreSQL) " PG_VERSION);
exit(0);
}
}
#ifndef WIN32
2010-02-26 03:01:40 +01:00
/*
* You can send SIGUSR1 to trigger failover.
*
* Postmaster uses SIGQUIT to request immediate shutdown. The default
* action is to core dump, but we don't want that, so trap it and commit
* suicide without core dump.
*
* We used to use SIGINT and SIGQUIT to trigger failover, but that turned
* out to be a bad idea because postmaster uses SIGQUIT to request
* immediate shutdown. We still trap SIGINT, but that may change in a
* future release.
*
* There's no way to trigger failover via signal on Windows.
*/
(void) pqsignal(SIGUSR1, sighandler);
Phase 2 of pgindent updates. Change pg_bsd_indent to follow upstream rules for placement of comments to the right of code, and remove pgindent hack that caused comments following #endif to not obey the general rule. Commit e3860ffa4dd0dad0dd9eea4be9cc1412373a8c89 wasn't actually using the published version of pg_bsd_indent, but a hacked-up version that tried to minimize the amount of movement of comments to the right of code. The situation of interest is where such a comment has to be moved to the right of its default placement at column 33 because there's code there. BSD indent has always moved right in units of tab stops in such cases --- but in the previous incarnation, indent was working in 8-space tab stops, while now it knows we use 4-space tabs. So the net result is that in about half the cases, such comments are placed one tab stop left of before. This is better all around: it leaves more room on the line for comment text, and it means that in such cases the comment uniformly starts at the next 4-space tab stop after the code, rather than sometimes one and sometimes two tabs after. Also, ensure that comments following #endif are indented the same as comments following other preprocessor commands such as #else. That inconsistency turns out to have been self-inflicted damage from a poorly-thought-through post-indent "fixup" in pgindent. This patch is much less interesting than the first round of indent changes, but also bulkier, so I thought it best to separate the effects. Discussion: https://postgr.es/m/E1dAmxK-0006EE-1r@gemulon.postgresql.org Discussion: https://postgr.es/m/30527.1495162840@sss.pgh.pa.us
2017-06-21 21:18:54 +02:00
(void) pqsignal(SIGINT, sighandler); /* deprecated, use SIGUSR1 */
(void) pqsignal(SIGQUIT, sigquit_handler);
#endif
while ((c = getopt(argc, argv, "cdk:lr:s:t:w:")) != -1)
{
switch (c)
{
case 'c': /* Use copy */
restoreCommandType = RESTORE_COMMAND_COPY;
break;
case 'd': /* Debug mode */
debug = true;
break;
case 'k': /* keepfiles */
keepfiles = atoi(optarg);
if (keepfiles < 0)
{
fprintf(stderr, "%s: -k keepfiles must be >= 0\n", progname);
exit(2);
}
break;
case 'l': /* Use link */
2010-02-26 03:01:40 +01:00
/*
2010-02-26 03:01:40 +01:00
* Link feature disabled, possibly permanently. Linking causes
* a problem after recovery ends that is not currently
* resolved by PostgreSQL. 25 Jun 2009
2009-06-25 21:33:25 +02:00
*/
#ifdef NOT_USED
restoreCommandType = RESTORE_COMMAND_LINK;
#endif
break;
case 'r': /* Retries */
maxretries = atoi(optarg);
if (maxretries < 0)
{
fprintf(stderr, "%s: -r maxretries must be >= 0\n", progname);
exit(2);
}
break;
case 's': /* Sleep time */
sleeptime = atoi(optarg);
if (sleeptime <= 0 || sleeptime > 60)
{
fprintf(stderr, "%s: -s sleeptime incorrectly set\n", progname);
exit(2);
}
break;
case 't': /* Trigger file */
triggerPath = pg_strdup(optarg);
2007-11-15 22:14:46 +01:00
break;
case 'w': /* Max wait time */
maxwaittime = atoi(optarg);
if (maxwaittime < 0)
{
fprintf(stderr, "%s: -w maxwaittime incorrectly set\n", progname);
exit(2);
}
break;
default:
fprintf(stderr, "Try \"%s --help\" for more information.\n", progname);
exit(2);
break;
}
}
2007-11-15 22:14:46 +01:00
/*
* Parameter checking - after checking to see if trigger file present
*/
if (argc == 1)
{
fprintf(stderr, "%s: not enough command-line arguments\n", progname);
exit(2);
}
/*
* We will go to the archiveLocation to get nextWALFileName.
2007-11-15 22:14:46 +01:00
* nextWALFileName may not exist yet, which would not be an error, so we
* separate the archiveLocation and nextWALFileName so we can check
* separately whether archiveLocation exists, if not that is an error
*/
if (optind < argc)
{
archiveLocation = argv[optind];
optind++;
}
else
{
fprintf(stderr, "%s: must specify archive location\n", progname);
fprintf(stderr, "Try \"%s --help\" for more information.\n", progname);
exit(2);
}
if (optind < argc)
{
nextWALFileName = argv[optind];
optind++;
}
else
{
2011-08-14 20:03:08 +02:00
fprintf(stderr, "%s: must specify WAL file name as second non-option argument (use \"%%f\")\n", progname);
fprintf(stderr, "Try \"%s --help\" for more information.\n", progname);
exit(2);
}
if (optind < argc)
{
xlogFilePath = argv[optind];
optind++;
}
else
{
2011-08-14 20:03:08 +02:00
fprintf(stderr, "%s: must specify xlog destination as third non-option argument (use \"%%p\")\n", progname);
fprintf(stderr, "Try \"%s --help\" for more information.\n", progname);
exit(2);
}
if (optind < argc)
{
restartWALFileName = argv[optind];
optind++;
}
CustomizableInitialize();
if (debug)
{
fprintf(stderr, "Trigger file: %s\n", triggerPath ? triggerPath : "<not set>");
fprintf(stderr, "Waiting for WAL file: %s\n", nextWALFileName);
fprintf(stderr, "WAL file path: %s\n", WALFilePath);
fprintf(stderr, "Restoring to: %s\n", xlogFilePath);
fprintf(stderr, "Sleep interval: %d second%s\n",
2007-11-15 22:14:46 +01:00
sleeptime, (sleeptime > 1 ? "s" : " "));
fprintf(stderr, "Max wait interval: %d %s\n",
2007-11-15 22:14:46 +01:00
maxwaittime, (maxwaittime > 0 ? "seconds" : "forever"));
fprintf(stderr, "Command for restore: %s\n", restoreCommand);
fflush(stderr);
}
/*
* Check for initial history file: always the first file to be requested
* It's OK if the file isn't there - all other files need to wait
*/
if (IsTLHistoryFileName(nextWALFileName))
{
nextWALFileType = XLOG_HISTORY;
if (RestoreWALFileForRecovery())
exit(0);
else
{
if (debug)
{
fprintf(stderr, "history file not found\n");
fflush(stderr);
}
exit(1);
}
}
2007-11-15 22:14:46 +01:00
/*
* Main wait loop
*/
for (;;)
{
/* Check for trigger file or signal first */
CheckForExternalTrigger();
#ifndef WIN32
if (signaled)
{
Failover = FastFailover;
if (debug)
{
fprintf(stderr, "signaled to exit: fast failover\n");
fflush(stderr);
}
}
#endif
/*
* Check for fast failover immediately, before checking if the
* requested WAL file is available
*/
if (Failover == FastFailover)
exit(1);
if (CustomizableNextWALFileReady())
{
/*
* Once we have restored this file successfully we can remove some
* prior WAL files. If this restore fails we mustn't remove any
* file because some of them will be requested again immediately
* after the failed restore, or when we restart recovery.
*/
if (RestoreWALFileForRecovery())
{
if (need_cleanup)
CustomizableCleanupPriorWALFiles();
exit(0);
}
else
{
/* Something went wrong in copying the file */
exit(1);
}
}
/* Check for smart failover if the next WAL file was not available */
if (Failover == SmartFailover)
exit(1);
if (sleeptime <= 60)
pg_usleep(sleeptime * 1000000L);
2007-11-15 22:14:46 +01:00
waittime += sleeptime;
if (waittime >= maxwaittime && maxwaittime > 0)
{
Failover = FastFailover;
if (debug)
{
fprintf(stderr, "Timed out after %d seconds: fast failover\n",
waittime);
fflush(stderr);
}
}
if (debug)
{
fprintf(stderr, "WAL file not present yet.");
if (triggerPath)
fprintf(stderr, " Checking for trigger file...");
fprintf(stderr, "\n");
fflush(stderr);
}
}
}