/* * pgespresso_stop_backup: finish taking an on-line backup dump * * Only parameter is the labelfile returned from pg_start_concurrent_backup * * Return is the XLOG filename containing end of backup location, combining * both the TLI and the end location. NOTE: the user is responsible for * ensuring that the last file is correctly archived. */ Datum pgespresso_stop_backup(PG_FUNCTION_ARGS) { XLogRecPtr stoppoint; text *labelfile = PG_GETARG_TEXT_P(0); char *backupidstr; char xlogfilename[MAXFNAMELEN]; backupidstr = text_to_cstring(labelfile); if (!superuser() && !has_rolreplication(GetUserId())) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be superuser or replication role to run a backup")))); #if PG_VERSION_NUM >= 90300 { XLogSegNo xlogsegno; TimeLineID endtli; stoppoint = do_pg_stop_backup(backupidstr, false, /* don't wait for archive */ &endtli); XLByteToPrevSeg(stoppoint, xlogsegno); XLogFileName(xlogfilename, endtli, xlogsegno); } #else { uint32 xlogid; uint32 xlogseg; stoppoint = do_pg_stop_backup(backupidstr, false); /* don't wait for archive */ /* * In 9.2 the do_pg_stop_backup doesn't return the timeline ID and * ThisTimeLineID is always 0 in a normal backend during recovery. * We get latest redo apply position timeline and we update it globally */ if (RecoveryInProgress()) { TimeLineID replayTLI; GetXLogReplayRecPtr(&replayTLI); ThisTimeLineID = replayTLI; elog(DEBUG1, "updated ThisTimeLineID = %u", ThisTimeLineID); } XLByteToPrevSeg(stoppoint, xlogid, xlogseg); XLogFileName(xlogfilename, ThisTimeLineID, xlogid, xlogseg); } #endif PG_RETURN_TEXT_P(cstring_to_text(xlogfilename)); }
/* * Print the values to be changed. */ static void PrintNewControlValues() { char fname[MAXFNAMELEN]; /* This will be always printed in order to keep format same. */ printf(_("\n\nValues to be changed:\n\n")); XLogFileName(fname, ControlFile.checkPointCopy.ThisTimeLineID, newXlogSegNo); printf(_("First log segment after reset: %s\n"), fname); if (set_mxid != 0) { printf(_("NextMultiXactId: %u\n"), ControlFile.checkPointCopy.nextMulti); printf(_("OldestMultiXid: %u\n"), ControlFile.checkPointCopy.oldestMulti); printf(_("OldestMulti's DB: %u\n"), ControlFile.checkPointCopy.oldestMultiDB); } if (set_mxoff != -1) { printf(_("NextMultiOffset: %u\n"), ControlFile.checkPointCopy.nextMultiOffset); } if (set_oid != 0) { printf(_("NextOID: %u\n"), ControlFile.checkPointCopy.nextOid); } if (set_xid != 0) { printf(_("NextXID: %u\n"), ControlFile.checkPointCopy.nextXid); printf(_("OldestXID: %u\n"), ControlFile.checkPointCopy.oldestXid); printf(_("OldestXID's DB: %u\n"), ControlFile.checkPointCopy.oldestXidDB); } if (set_xid_epoch != -1) { printf(_("NextXID epoch: %u\n"), ControlFile.checkPointCopy.nextXidEpoch); } if (set_oldest_commit_ts != 0) { printf(_("oldestCommitTs: %u\n"), ControlFile.checkPointCopy.oldestCommitTs); } if (set_newest_commit_ts != 0) { printf(_("newestCommitTs: %u\n"), ControlFile.checkPointCopy.newestCommitTs); } }
/* * open xlog file */ static void openXlogNextFile(void) { char path[MAXPGPATH]; char *xlogDir = NULL; XLogFileName(xlogfilename, ThisTimeLineID, xlogid, xseg); elog((Debug_print_qd_mirroring ? LOG : DEBUG5), "QDSYNC: opening next logid %d seg %d file %s", xlogid, xseg, xlogfilename); xlogDir = makeRelativeToTxnFilespace(XLOGDIR); /* the first time or file is changed */ if (snprintf(path, MAXPGPATH, "%s/%s", xlogDir, xlogfilename) >= MAXPGPATH) { ereport(ERROR, (errcode_for_file_access(), errmsg("QDSYNC: could not create xlog file \"%s/%s\"", xlogDir, xlogfilename))); } pfree(xlogDir); xlogfilefd = open(path, O_RDWR, 0); if (xlogfilefd < 0) { createZeroFilledNewFile(path); } xlogfileoffset = 0; elog((Debug_print_qd_mirroring ? LOG : DEBUG5), "QDSYNC: openXlogNextFile: opened '%s' offset 0x%X", xlogfilename, xlogfileoffset); }
/* * Compute an xlog file name given a WAL location, * such as is returned by pg_stop_backup() or pg_xlog_switch(). */ Datum pg_xlogfile_name(PG_FUNCTION_ARGS) { text *location = PG_GETARG_TEXT_P(0); char *locationstr; unsigned int uxlogid; unsigned int uxrecoff; uint32 xlogid; uint32 xlogseg; XLogRecPtr locationpoint; char xlogfilename[MAXFNAMELEN]; if (RecoveryInProgress()) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("recovery is in progress"), errhint("pg_xlogfile_name() cannot be executed during recovery."))); locationstr = text_to_cstring(location); if (sscanf(locationstr, "%X/%X", &uxlogid, &uxrecoff) != 2) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not parse transaction log location \"%s\"", locationstr))); locationpoint.xlogid = uxlogid; locationpoint.xrecoff = uxrecoff; XLByteToPrevSeg(locationpoint, xlogid, xlogseg); XLogFileName(xlogfilename, ThisTimeLineID, xlogid, xlogseg); PG_RETURN_TEXT_P(cstring_to_text(xlogfilename)); }
/* * Convenience routine to notify using segment number representation of filename */ void XLogArchiveNotifySeg(XLogSegNo segno) { char xlog[MAXFNAMELEN]; XLogFileName(xlog, ThisTimeLineID, segno); XLogArchiveNotify(xlog); }
/* * Compute an xlog file name and decimal byte offset given a WAL location, * such as is returned by pg_stop_backup() or pg_xlog_switch(). * * Note that a location exactly at a segment boundary is taken to be in * the previous segment. This is usually the right thing, since the * expected usage is to determine which xlog file(s) are ready to archive. */ Datum pg_xlogfile_name_offset(PG_FUNCTION_ARGS) { XLogSegNo xlogsegno; uint32 xrecoff; XLogRecPtr locationpoint = PG_GETARG_LSN(0); char xlogfilename[MAXFNAMELEN]; Datum values[2]; bool isnull[2]; TupleDesc resultTupleDesc; HeapTuple resultHeapTuple; Datum result; if (RecoveryInProgress()) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("recovery is in progress"), errhint("pg_xlogfile_name_offset() cannot be executed during recovery."))); /* * Construct a tuple descriptor for the result row. This must match this * function's pg_proc entry! */ resultTupleDesc = CreateTemplateTupleDesc(2, false); TupleDescInitEntry(resultTupleDesc, (AttrNumber) 1, "file_name", TEXTOID, -1, 0); TupleDescInitEntry(resultTupleDesc, (AttrNumber) 2, "file_offset", INT4OID, -1, 0); resultTupleDesc = BlessTupleDesc(resultTupleDesc); /* * xlogfilename */ XLByteToPrevSeg(locationpoint, xlogsegno); XLogFileName(xlogfilename, ThisTimeLineID, xlogsegno); values[0] = CStringGetTextDatum(xlogfilename); isnull[0] = false; /* * offset */ xrecoff = locationpoint % XLogSegSize; values[1] = UInt32GetDatum(xrecoff); isnull[1] = false; /* * Tuple jam: Having first prepared your Datums, then squash together */ resultHeapTuple = heap_form_tuple(resultTupleDesc, values, isnull); result = HeapTupleGetDatum(resultHeapTuple); PG_RETURN_DATUM(result); }
/* * SetWALFileNameForCleanup() * * Set the earliest WAL filename that we want to keep on the archive * and decide whether we need_cleanup */ static bool SetWALFileNameForCleanup(void) { uint32 tli = 1, log = 0, seg = 0; uint32 log_diff = 0, seg_diff = 0; bool cleanup = false; 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 / MaxSegmentsPerLogFile; seg_diff = keepfiles % MaxSegmentsPerLogFile; if (seg_diff > seg) { log_diff++; seg = MaxSegmentsPerLogFile - (seg_diff - seg); } else seg -= seg_diff; if (log >= log_diff) { log -= log_diff; cleanup = true; } else { log = 0; seg = 0; } } } XLogFileName(exclusiveCleanupFileName, tli, log, seg); return cleanup; }
/* * open xlog file */ static void openXlogEnd(XLogRecPtr *endLocation) { char path[MAXPGPATH]; uint32 logid; uint32 seg; char *xlogDir = NULL; XLByteToSeg(*endLocation, logid, seg); XLogFileName(xlogfilename, ThisTimeLineID, logid, seg); elog((Debug_print_qd_mirroring ? LOG : DEBUG5), "QDSYNC: opening logid %d seg %d file %s", logid, seg, xlogfilename); xlogDir = makeRelativeToTxnFilespace(XLOGDIR); /* the first time or file is changed */ if (snprintf(path, MAXPGPATH, "%s/%s", xlogDir, xlogfilename) >= MAXPGPATH) { ereport(ERROR, (errcode_for_file_access(), errmsg("QDSYNC: could not create xlog file \"%s/%s\"", xlogDir, xlogfilename))); } if (xlogfilefd >= 0) { close(xlogfilefd); xlogfilefd = -1; xlogfileoffset = -1; } xlogfilefd = open(path, O_RDWR, 0); if (xlogfilefd < 0) { elog((Debug_print_qd_mirroring ? LOG : DEBUG5), "QDSYNC: creating xlog file %s", xlogfilename); xlogfilefd = open(path, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); if (xlogfilefd < 0) { ereport(ERROR, (errcode_for_file_access(), errmsg("QDSYNC: could not create xlog file \"%s\"", path))); } } xlogid = logid; xseg = seg; xlogfileoffset = endLocation->xrecoff % XLogSegSize; elog((Debug_print_qd_mirroring ? LOG : DEBUG5),"QDSYNC: opened '%s' offset 0x%X", xlogfilename, xlogfileoffset); pfree(xlogDir); }
/* * SetWALFileNameForCleanup() * * Set the earliest WAL filename that we want to keep on the archive * and decide whether we need_cleanup */ static void SetWALFileNameForCleanup(void) { bool fnameOK = false; TrimExtension(restartWALFileName, additional_ext); /* * If restartWALFileName is a WAL file name then just use it directly. If * restartWALFileName is a .backup filename, make sure we use the prefix * of the filename, otherwise we will remove wrong files since * 000000010000000000000010.00000020.backup is after * 000000010000000000000010. */ if (strlen(restartWALFileName) == XLOG_DATA_FNAME_LEN && strspn(restartWALFileName, "0123456789ABCDEF") == XLOG_DATA_FNAME_LEN) { strcpy(exclusiveCleanupFileName, restartWALFileName); fnameOK = true; } else if (strlen(restartWALFileName) == XLOG_BACKUP_FNAME_LEN) { int args; uint32 tli = 1, log = 0, seg = 0, offset = 0; args = sscanf(restartWALFileName, "%08X%08X%08X.%08X.backup", &tli, &log, &seg, &offset); if (args == 4) { fnameOK = true; /* * Use just the prefix of the filename, ignore everything after * first period */ XLogFileName(exclusiveCleanupFileName, tli, log, seg); } } if (!fnameOK) { fprintf(stderr, "%s: invalid filename input\n", progname); fprintf(stderr, "Try \"%s --help\" for more information.\n", progname); exit(2); } }
/* * Compute an xlog file name given a WAL location, * such as is returned by pg_stop_backup() or pg_xlog_switch(). */ Datum pg_xlogfile_name(PG_FUNCTION_ARGS) { XLogSegNo xlogsegno; XLogRecPtr locationpoint = PG_GETARG_LSN(0); char xlogfilename[MAXFNAMELEN]; if (RecoveryInProgress()) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("recovery is in progress"), errhint("pg_xlogfile_name() cannot be executed during recovery."))); XLByteToPrevSeg(locationpoint, xlogsegno); XLogFileName(xlogfilename, ThisTimeLineID, xlogsegno); PG_RETURN_TEXT_P(cstring_to_text(xlogfilename)); }
/* * Read 'nbytes' bytes from WAL into 'buf', starting at location 'recptr' * * XXX probably this should be improved to suck data directly from the * WAL buffers when possible. * * Will open, and keep open, one WAL segment stored in the global file * descriptor sendFile. This means if XLogRead is used once, there will * always be one descriptor left open until the process ends, but never * more than one. */ void XLogRead(char *buf, XLogRecPtr recptr, Size nbytes) { XLogRecPtr startRecPtr = recptr; char path[MAXPGPATH]; uint32 lastRemovedLog; uint32 lastRemovedSeg; uint32 log; uint32 seg; while (nbytes > 0) { uint32 startoff; int segbytes; int readbytes; startoff = recptr.xrecoff % XLogSegSize; if (sendFile < 0 || !XLByteInSeg(recptr, sendId, sendSeg)) { /* Switch to another logfile segment */ if (sendFile >= 0) close(sendFile); XLByteToSeg(recptr, sendId, sendSeg); XLogFilePath(path, ThisTimeLineID, sendId, sendSeg); sendFile = BasicOpenFile(path, O_RDONLY | PG_BINARY, 0); if (sendFile < 0) { /* * If the file is not found, assume it's because the standby * asked for a too old WAL segment that has already been * removed or recycled. */ if (errno == ENOENT) { char filename[MAXFNAMELEN]; XLogFileName(filename, ThisTimeLineID, sendId, sendSeg); ereport(ERROR, (errcode_for_file_access(), errmsg("requested WAL segment %s has already been removed", filename))); } else ereport(ERROR, (errcode_for_file_access(), errmsg("could not open file \"%s\" (log file %u, segment %u): %m", path, sendId, sendSeg))); } sendOff = 0; } /* Need to seek in the file? */ if (sendOff != startoff) { if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0) ereport(ERROR, (errcode_for_file_access(), errmsg("could not seek in log file %u, segment %u to offset %u: %m", sendId, sendSeg, startoff))); sendOff = startoff; } /* How many bytes are within this segment? */ if (nbytes > (XLogSegSize - startoff)) segbytes = XLogSegSize - startoff; else segbytes = nbytes; readbytes = read(sendFile, buf, segbytes); if (readbytes <= 0) ereport(ERROR, (errcode_for_file_access(), errmsg("could not read from log file %u, segment %u, offset %u, " "length %lu: %m", sendId, sendSeg, sendOff, (unsigned long) segbytes))); /* Update state for read */ XLByteAdvance(recptr, readbytes); sendOff += readbytes; nbytes -= readbytes; buf += readbytes; } /* * After reading into the buffer, check that what we read was valid. We do * this after reading, because even though the segment was present when we * opened it, it might get recycled or removed while we read it. The * read() succeeds in that case, but the data we tried to read might * already have been overwritten with new WAL records. */ XLogGetLastRemoved(&lastRemovedLog, &lastRemovedSeg); XLByteToSeg(startRecPtr, log, seg); if (log < lastRemovedLog || (log == lastRemovedLog && seg <= lastRemovedSeg)) { char filename[MAXFNAMELEN]; XLogFileName(filename, ThisTimeLineID, log, seg); ereport(ERROR, (errcode_for_file_access(), errmsg("requested WAL segment %s has already been removed", filename))); } }
/* * Read 'count' bytes from WAL into 'buf', starting at location 'startptr' * * XXX probably this should be improved to suck data directly from the * WAL buffers when possible. * * Will open, and keep open, one WAL segment stored in the global file * descriptor sendFile. This means if XLogRead is used once, there will * always be one descriptor left open until the process ends, but never * more than one. */ void XLogRead(char *buf, XLogRecPtr startptr, Size count) { char *p; XLogRecPtr recptr; Size nbytes; uint32 lastRemovedLog; uint32 lastRemovedSeg; uint32 log; uint32 seg; retry: p = buf; recptr = startptr; nbytes = count; while (nbytes > 0) { uint32 startoff; int segbytes; int readbytes; startoff = recptr.xrecoff % XLogSegSize; if (sendFile < 0 || !XLByteInSeg(recptr, sendId, sendSeg)) { char path[MAXPGPATH]; /* Switch to another logfile segment */ if (sendFile >= 0) close(sendFile); XLByteToSeg(recptr, sendId, sendSeg); XLogFilePath(path, ThisTimeLineID, sendId, sendSeg); sendFile = BasicOpenFile(path, O_RDONLY | PG_BINARY, 0); if (sendFile < 0) { /* * If the file is not found, assume it's because the standby * asked for a too old WAL segment that has already been * removed or recycled. */ if (errno == ENOENT) { char filename[MAXFNAMELEN]; XLogFileName(filename, ThisTimeLineID, sendId, sendSeg); ereport(ERROR, (errcode_for_file_access(), errmsg("requested WAL segment %s has already been removed", filename))); } else ereport(ERROR, (errcode_for_file_access(), errmsg("could not open file \"%s\" (log file %u, segment %u): %m", path, sendId, sendSeg))); } sendOff = 0; } /* Need to seek in the file? */ if (sendOff != startoff) { if (lseek(sendFile, (off_t) startoff, SEEK_SET) < 0) ereport(ERROR, (errcode_for_file_access(), errmsg("could not seek in log file %u, segment %u to offset %u: %m", sendId, sendSeg, startoff))); sendOff = startoff; } /* How many bytes are within this segment? */ if (nbytes > (XLogSegSize - startoff)) segbytes = XLogSegSize - startoff; else segbytes = nbytes; readbytes = read(sendFile, p, segbytes); if (readbytes <= 0) ereport(ERROR, (errcode_for_file_access(), errmsg("could not read from log file %u, segment %u, offset %u, " "length %lu: %m", sendId, sendSeg, sendOff, (unsigned long) segbytes))); /* Update state for read */ XLByteAdvance(recptr, readbytes); sendOff += readbytes; nbytes -= readbytes; p += readbytes; } /* * After reading into the buffer, check that what we read was valid. We do * this after reading, because even though the segment was present when we * opened it, it might get recycled or removed while we read it. The * read() succeeds in that case, but the data we tried to read might * already have been overwritten with new WAL records. */ XLogGetLastRemoved(&lastRemovedLog, &lastRemovedSeg); XLByteToSeg(startptr, log, seg); if (log < lastRemovedLog || (log == lastRemovedLog && seg <= lastRemovedSeg)) { char filename[MAXFNAMELEN]; XLogFileName(filename, ThisTimeLineID, log, seg); ereport(ERROR, (errcode_for_file_access(), errmsg("requested WAL segment %s has already been removed", filename))); } /* * During recovery, the currently-open WAL file might be replaced with * the file of the same name retrieved from archive. So we always need * to check what we read was valid after reading into the buffer. If it's * invalid, we try to open and read the file again. */ if (am_cascading_walsender) { /* use volatile pointer to prevent code rearrangement */ volatile WalSnd *walsnd = MyWalSnd; bool reload; SpinLockAcquire(&walsnd->mutex); reload = walsnd->needreload; walsnd->needreload = false; SpinLockRelease(&walsnd->mutex); if (reload && sendFile >= 0) { close(sendFile); sendFile = -1; goto retry; } } }
/* * Print the guessed pg_control values when we had to guess. * * NB: this display should be just those fields that will not be * reset by RewriteControlFile(). */ static void PrintControlValues(bool guessed) { char sysident_str[32]; char fname[MAXFNAMELEN]; if (guessed) printf(_("Guessed pg_control values:\n\n")); else printf(_("pg_control values:\n\n")); /* * Format system_identifier separately to keep platform-dependent format * code out of the translatable message string. */ snprintf(sysident_str, sizeof(sysident_str), UINT64_FORMAT, ControlFile.system_identifier); XLogFileName(fname, ControlFile.checkPointCopy.ThisTimeLineID, newXlogSegNo); printf(_("First log segment after reset: %s\n"), fname); printf(_("pg_control version number: %u\n"), ControlFile.pg_control_version); printf(_("Catalog version number: %u\n"), ControlFile.catalog_version_no); printf(_("Database system identifier: %s\n"), sysident_str); printf(_("Latest checkpoint's TimeLineID: %u\n"), ControlFile.checkPointCopy.ThisTimeLineID); printf(_("Latest checkpoint's full_page_writes: %s\n"), ControlFile.checkPointCopy.fullPageWrites ? _("on") : _("off")); printf(_("Latest checkpoint's NextXID: %u/%u\n"), ControlFile.checkPointCopy.nextXidEpoch, ControlFile.checkPointCopy.nextXid); printf(_("Latest checkpoint's NextOID: %u\n"), ControlFile.checkPointCopy.nextOid); printf(_("Latest checkpoint's NextMultiXactId: %u\n"), ControlFile.checkPointCopy.nextMulti); printf(_("Latest checkpoint's NextMultiOffset: %u\n"), ControlFile.checkPointCopy.nextMultiOffset); printf(_("Latest checkpoint's oldestXID: %u\n"), ControlFile.checkPointCopy.oldestXid); printf(_("Latest checkpoint's oldestXID's DB: %u\n"), ControlFile.checkPointCopy.oldestXidDB); printf(_("Latest checkpoint's oldestActiveXID: %u\n"), ControlFile.checkPointCopy.oldestActiveXid); printf(_("Maximum data alignment: %u\n"), ControlFile.maxAlign); /* we don't print floatFormat since can't say much useful about it */ printf(_("Database block size: %u\n"), ControlFile.blcksz); printf(_("Blocks per segment of large relation: %u\n"), ControlFile.relseg_size); printf(_("WAL block size: %u\n"), ControlFile.xlog_blcksz); printf(_("Bytes per WAL segment: %u\n"), ControlFile.xlog_seg_size); printf(_("Maximum length of identifiers: %u\n"), ControlFile.nameDataLen); printf(_("Maximum columns in an index: %u\n"), ControlFile.indexMaxKeys); printf(_("Maximum size of a TOAST chunk: %u\n"), ControlFile.toast_max_chunk_size); printf(_("Date/time type storage: %s\n"), (ControlFile.enableIntTimes ? _("64-bit integers") : _("floating-point numbers"))); printf(_("Float4 argument passing: %s\n"), (ControlFile.float4ByVal ? _("by value") : _("by reference"))); printf(_("Float8 argument passing: %s\n"), (ControlFile.float8ByVal ? _("by value") : _("by reference"))); }
/* * Open a new WAL file in the specified directory. Store the name * (not including the full directory) in namebuf. Assumes there is * enough room in this buffer... * * The file will be padded to 16Mb with zeroes. */ static int open_walfile(XLogRecPtr startpoint, uint32 timeline, char *basedir, char *namebuf) { int f; char fn[MAXPGPATH]; struct stat statbuf; char *zerobuf; int bytes; XLogFileName(namebuf, timeline, startpoint.xlogid, startpoint.xrecoff / XLOG_SEG_SIZE); snprintf(fn, sizeof(fn), "%s/%s.partial", basedir, namebuf); f = open(fn, O_WRONLY | O_CREAT | PG_BINARY, S_IRUSR | S_IWUSR); if (f == -1) { fprintf(stderr, _("%s: could not open transaction log file \"%s\": %s\n"), progname, fn, strerror(errno)); return -1; } /* * Verify that the file is either empty (just created), or a complete * XLogSegSize segment. Anything in between indicates a corrupt file. */ if (fstat(f, &statbuf) != 0) { fprintf(stderr, _("%s: could not stat transaction log file \"%s\": %s\n"), progname, fn, strerror(errno)); close(f); return -1; } if (statbuf.st_size == XLogSegSize) return f; /* File is open and ready to use */ if (statbuf.st_size != 0) { fprintf(stderr, _("%s: transaction log file \"%s\" has %d bytes, should be 0 or %d\n"), progname, fn, (int) statbuf.st_size, XLogSegSize); close(f); return -1; } /* New, empty, file. So pad it to 16Mb with zeroes */ zerobuf = xmalloc0(XLOG_BLCKSZ); for (bytes = 0; bytes < XLogSegSize; bytes += XLOG_BLCKSZ) { if (write(f, zerobuf, XLOG_BLCKSZ) != XLOG_BLCKSZ) { fprintf(stderr, _("%s: could not pad transaction log file \"%s\": %s\n"), progname, fn, strerror(errno)); free(zerobuf); close(f); unlink(fn); return -1; } } free(zerobuf); if (lseek(f, SEEK_SET, 0) != 0) { fprintf(stderr, _("%s: could not seek to beginning of transaction log file \"%s\": %s\n"), progname, fn, strerror(errno)); close(f); return -1; } return f; }
int do_delete(pgBackupRange *range) { int i; int ret; parray *backup_list; bool do_delete = false; XLogRecPtr oldest_lsn = InvalidXLogRecPtr; TimeLineID oldest_tli; /* DATE are always required */ if (!pgBackupRangeIsValid(range)) elog(ERROR, "required delete range option not specified: delete DATE"); /* Lock backup catalog */ ret = catalog_lock(); if (ret == -1) elog(ERROR, "can't lock backup catalog."); else if (ret == 1) elog(ERROR, "another pg_arman is running, stop delete."); /* Get complete list of backups */ backup_list = catalog_get_backup_list(NULL); if (!backup_list) elog(ERROR, "No backup list found, can't process any more."); /* Find backups to be deleted */ for (i = 0; i < parray_num(backup_list); i++) { pgBackup *backup = (pgBackup *) parray_get(backup_list, i); /* delete backup and update status to DELETED */ if (do_delete) { /* check for interrupt */ if (interrupted) elog(ERROR, "interrupted during delete backup"); pgBackupDeleteFiles(backup); continue; } /* Found the latest full backup */ if (backup->backup_mode >= BACKUP_MODE_FULL && backup->status == BACKUP_STATUS_OK && backup->start_time <= range->begin) { do_delete = true; oldest_lsn = backup->start_lsn; oldest_tli = backup->tli; } } /* release catalog lock */ catalog_unlock(); /* cleanup */ parray_walk(backup_list, pgBackupFree); parray_free(backup_list); /* * Delete in archive WAL segments that are not needed anymore. The oldest * segment to be kept is the first segment that the oldest full backup * found around needs to keep. */ if (!XLogRecPtrIsInvalid(oldest_lsn)) { XLogSegNo targetSegNo; char oldestSegmentNeeded[MAXFNAMELEN]; DIR *arcdir; struct dirent *arcde; char wal_file[MAXPGPATH]; int rc; XLByteToSeg(oldest_lsn, targetSegNo); XLogFileName(oldestSegmentNeeded, oldest_tli, targetSegNo); elog(LOG, "Removing segments older than %s", oldestSegmentNeeded); /* * Now is time to do the actual work and to remove all the segments * not needed anymore. */ if ((arcdir = opendir(arclog_path)) != NULL) { while (errno = 0, (arcde = readdir(arcdir)) != NULL) { /* * 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. * * 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(arcde->d_name) || IsPartialXLogFileName(arcde->d_name)) && strcmp(arcde->d_name + 8, oldestSegmentNeeded + 8) < 0) { /* * Use the original file name again now, including any * extension that might have been chopped off before testing * the sequence. */ snprintf(wal_file, MAXPGPATH, "%s/%s", arclog_path, arcde->d_name); rc = unlink(wal_file); if (rc != 0) { elog(WARNING, "could not remove file \"%s\": %s", wal_file, strerror(errno)); break; } elog(LOG, "removed WAL segment \"%s\"", wal_file); } } if (errno) elog(WARNING, "could not read archive location \"%s\": %s", arclog_path, strerror(errno)); if (closedir(arcdir)) elog(WARNING, "could not close archive location \"%s\": %s", arclog_path, strerror(errno)); } else elog(WARNING, "could not open archive location \"%s\": %s", arclog_path, strerror(errno)); } return 0; }
/* * Compute an xlog file name and decimal byte offset given a WAL location, * such as is returned by pg_stop_backup() or pg_xlog_switch(). * * Note that a location exactly at a segment boundary is taken to be in * the previous segment. This is usually the right thing, since the * expected usage is to determine which xlog file(s) are ready to archive. */ Datum pg_xlogfile_name_offset(PG_FUNCTION_ARGS) { text *location = PG_GETARG_TEXT_P(0); char *locationstr; unsigned int uxlogid; unsigned int uxrecoff; uint32 xlogid; uint32 xlogseg; uint32 xrecoff; XLogRecPtr locationpoint; char xlogfilename[MAXFNAMELEN]; Datum values[2]; bool isnull[2]; TupleDesc resultTupleDesc; HeapTuple resultHeapTuple; Datum result; if (RecoveryInProgress()) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("recovery is in progress"), errhint("pg_xlogfile_name_offset() cannot be executed during recovery."))); /* * Read input and parse */ locationstr = text_to_cstring(location); if (sscanf(locationstr, "%X/%X", &uxlogid, &uxrecoff) != 2) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not parse transaction log location \"%s\"", locationstr))); locationpoint.xlogid = uxlogid; locationpoint.xrecoff = uxrecoff; /* * Construct a tuple descriptor for the result row. This must match this * function's pg_proc entry! */ resultTupleDesc = CreateTemplateTupleDesc(2, false); TupleDescInitEntry(resultTupleDesc, (AttrNumber) 1, "file_name", TEXTOID, -1, 0); TupleDescInitEntry(resultTupleDesc, (AttrNumber) 2, "file_offset", INT4OID, -1, 0); resultTupleDesc = BlessTupleDesc(resultTupleDesc); /* * xlogfilename */ XLByteToPrevSeg(locationpoint, xlogid, xlogseg); XLogFileName(xlogfilename, ThisTimeLineID, xlogid, xlogseg); values[0] = CStringGetTextDatum(xlogfilename); isnull[0] = false; /* * offset */ xrecoff = locationpoint.xrecoff - xlogseg * XLogSegSize; values[1] = UInt32GetDatum(xrecoff); isnull[1] = false; /* * Tuple jam: Having first prepared your Datums, then squash together */ resultHeapTuple = heap_form_tuple(resultTupleDesc, values, isnull); result = HeapTupleGetDatum(resultHeapTuple); PG_RETURN_DATUM(result); }
/* * Attempt to execute an external shell command during recovery. * * 'command' is the shell command to be executed, 'commandName' is a * human-readable name describing the command emitted in the logs. If * 'failOnSignal' is true and the command is killed by a signal, a FATAL * error is thrown. Otherwise a WARNING is emitted. * * This is currently used for recovery_end_command and archive_cleanup_command. */ void ExecuteRecoveryCommand(char *command, char *commandName, bool failOnSignal) { char xlogRecoveryCmd[MAXPGPATH]; char lastRestartPointFname[MAXPGPATH]; char *dp; char *endp; const char *sp; int rc; bool signaled; XLogSegNo restartSegNo; XLogRecPtr restartRedoPtr; TimeLineID restartTli; Assert(command && commandName); /* * Calculate the archive file cutoff point for use during log shipping * replication. All files earlier than this point can be deleted from the * archive, though there is no requirement to do so. */ GetOldestRestartPoint(&restartRedoPtr, &restartTli); XLByteToSeg(restartRedoPtr, restartSegNo); XLogFileName(lastRestartPointFname, restartTli, restartSegNo); /* * construct the command to be executed */ dp = xlogRecoveryCmd; endp = xlogRecoveryCmd + MAXPGPATH - 1; *endp = '\0'; for (sp = command; *sp; sp++) { if (*sp == '%') { switch (sp[1]) { case 'r': /* %r: filename of last restartpoint */ sp++; StrNCpy(dp, lastRestartPointFname, endp - dp); dp += strlen(dp); break; case '%': /* convert %% to a single % */ sp++; if (dp < endp) *dp++ = *sp; break; default: /* otherwise treat the % as not special */ if (dp < endp) *dp++ = *sp; break; } } else { if (dp < endp) *dp++ = *sp; } } *dp = '\0'; ereport(DEBUG3, (errmsg_internal("executing %s \"%s\"", commandName, command))); /* * execute the constructed command */ rc = system(xlogRecoveryCmd); if (rc != 0) { /* * If the failure was due to any sort of signal, it's best to punt and * abort recovery. See also detailed comments on signals in * RestoreArchivedFile(). */ signaled = WIFSIGNALED(rc) || WEXITSTATUS(rc) > 125; ereport((signaled && failOnSignal) ? FATAL : WARNING, /*------ translator: First %s represents a recovery.conf parameter name like "recovery_end_command", the 2nd is the value of that parameter, the third an already translated error message. */ (errmsg("%s \"%s\": %s", commandName, command, wait_result_to_str(rc)))); } }
/* * Attempt to retrieve the specified file from off-line archival storage. * If successful, fill "path" with its complete path (note that this will be * a temp file name that doesn't follow the normal naming convention), and * return TRUE. * * If not successful, fill "path" with the name of the normal on-line file * (which may or may not actually exist, but we'll try to use it), and return * FALSE. * * For fixed-size files, the caller may pass the expected size as an * additional crosscheck on successful recovery. If the file size is not * known, set expectedSize = 0. * * When 'cleanupEnabled' is false, refrain from deleting any old WAL segments * in the archive. This is used when fetching the initial checkpoint record, * when we are not yet sure how far back we need the WAL. */ bool RestoreArchivedFile(char *path, const char *xlogfname, const char *recovername, off_t expectedSize, bool cleanupEnabled) { char xlogpath[MAXPGPATH]; char xlogRestoreCmd[MAXPGPATH]; char lastRestartPointFname[MAXPGPATH]; char *dp; char *endp; const char *sp; int rc; bool signaled; struct stat stat_buf; XLogSegNo restartSegNo; XLogRecPtr restartRedoPtr; TimeLineID restartTli; /* In standby mode, restore_command might not be supplied */ if (recoveryRestoreCommand == NULL) goto not_available; /* * When doing archive recovery, we always prefer an archived log file even * if a file of the same name exists in XLOGDIR. The reason is that the * file in XLOGDIR could be an old, un-filled or partly-filled version * that was copied and restored as part of backing up $PGDATA. * * We could try to optimize this slightly by checking the local copy * lastchange timestamp against the archived copy, but we have no API to * do this, nor can we guarantee that the lastchange timestamp was * preserved correctly when we copied to archive. Our aim is robustness, * so we elect not to do this. * * If we cannot obtain the log file from the archive, however, we will try * to use the XLOGDIR file if it exists. This is so that we can make use * of log segments that weren't yet transferred to the archive. * * Notice that we don't actually overwrite any files when we copy back * from archive because the restore_command may inadvertently restore * inappropriate xlogs, or they may be corrupt, so we may wish to fallback * to the segments remaining in current XLOGDIR later. The * copy-from-archive filename is always the same, ensuring that we don't * run out of disk space on long recoveries. */ snprintf(xlogpath, MAXPGPATH, XLOGDIR "/%s", recovername); /* * Make sure there is no existing file named recovername. */ if (stat(xlogpath, &stat_buf) != 0) { if (errno != ENOENT) ereport(FATAL, (errcode_for_file_access(), errmsg("could not stat file \"%s\": %m", xlogpath))); } else { if (unlink(xlogpath) != 0) ereport(FATAL, (errcode_for_file_access(), errmsg("could not remove file \"%s\": %m", xlogpath))); } /* * Calculate the archive file cutoff point for use during log shipping * replication. All files earlier than this point can be deleted from the * archive, though there is no requirement to do so. * * If cleanup is not enabled, initialise this with the filename of * InvalidXLogRecPtr, which will prevent the deletion of any WAL files * from the archive because of the alphabetic sorting property of WAL * filenames. * * Once we have successfully located the redo pointer of the checkpoint * from which we start recovery we never request a file prior to the redo * pointer of the last restartpoint. When redo begins we know that we have * successfully located it, so there is no need for additional status * flags to signify the point when we can begin deleting WAL files from * the archive. */ if (cleanupEnabled) { GetOldestRestartPoint(&restartRedoPtr, &restartTli); XLByteToSeg(restartRedoPtr, restartSegNo); XLogFileName(lastRestartPointFname, restartTli, restartSegNo); /* we shouldn't need anything earlier than last restart point */ Assert(strcmp(lastRestartPointFname, xlogfname) <= 0); } else XLogFileName(lastRestartPointFname, 0, 0L); /* * construct the command to be executed */ dp = xlogRestoreCmd; endp = xlogRestoreCmd + MAXPGPATH - 1; *endp = '\0'; for (sp = recoveryRestoreCommand; *sp; sp++) { if (*sp == '%') { switch (sp[1]) { case 'p': /* %p: relative path of target file */ sp++; StrNCpy(dp, xlogpath, endp - dp); make_native_path(dp); dp += strlen(dp); break; case 'f': /* %f: filename of desired file */ sp++; StrNCpy(dp, xlogfname, endp - dp); dp += strlen(dp); break; case 'r': /* %r: filename of last restartpoint */ sp++; StrNCpy(dp, lastRestartPointFname, endp - dp); dp += strlen(dp); break; case '%': /* convert %% to a single % */ sp++; if (dp < endp) *dp++ = *sp; break; default: /* otherwise treat the % as not special */ if (dp < endp) *dp++ = *sp; break; } } else { if (dp < endp) *dp++ = *sp; } } *dp = '\0'; ereport(DEBUG3, (errmsg_internal("executing restore command \"%s\"", xlogRestoreCmd))); /* * Check signals before restore command and reset afterwards. */ PreRestoreCommand(); /* * Copy xlog from archival storage to XLOGDIR */ rc = system(xlogRestoreCmd); PostRestoreCommand(); if (rc == 0) { /* * command apparently succeeded, but let's make sure the file is * really there now and has the correct size. */ if (stat(xlogpath, &stat_buf) == 0) { if (expectedSize > 0 && stat_buf.st_size != expectedSize) { int elevel; /* * If we find a partial file in standby mode, we assume it's * because it's just being copied to the archive, and keep * trying. * * Otherwise treat a wrong-sized file as FATAL to ensure the * DBA would notice it, but is that too strong? We could try * to plow ahead with a local copy of the file ... but the * problem is that there probably isn't one, and we'd * incorrectly conclude we've reached the end of WAL and we're * done recovering ... */ if (StandbyMode && stat_buf.st_size < expectedSize) elevel = DEBUG1; else elevel = FATAL; ereport(elevel, (errmsg("archive file \"%s\" has wrong size: %lu instead of %lu", xlogfname, (unsigned long) stat_buf.st_size, (unsigned long) expectedSize))); return false; } else { ereport(LOG, (errmsg("restored log file \"%s\" from archive", xlogfname))); strcpy(path, xlogpath); return true; } } else { /* stat failed */ if (errno != ENOENT) ereport(FATAL, (errcode_for_file_access(), errmsg("could not stat file \"%s\": %m", xlogpath))); } } /* * Remember, we rollforward UNTIL the restore fails so failure here is * just part of the process... that makes it difficult to determine * whether the restore failed because there isn't an archive to restore, * or because the administrator has specified the restore program * incorrectly. We have to assume the former. * * However, if the failure was due to any sort of signal, it's best to * punt and abort recovery. (If we "return false" here, upper levels will * assume that recovery is complete and start up the database!) It's * essential to abort on child SIGINT and SIGQUIT, because per spec * system() ignores SIGINT and SIGQUIT while waiting; if we see one of * those it's a good bet we should have gotten it too. * * On SIGTERM, assume we have received a fast shutdown request, and exit * cleanly. It's pure chance whether we receive the SIGTERM first, or the * child process. If we receive it first, the signal handler will call * proc_exit, otherwise we do it here. If we or the child process received * SIGTERM for any other reason than a fast shutdown request, postmaster * will perform an immediate shutdown when it sees us exiting * unexpectedly. * * Per the Single Unix Spec, shells report exit status > 128 when a called * command died on a signal. Also, 126 and 127 are used to report * problems such as an unfindable command; treat those as fatal errors * too. */ if (WIFSIGNALED(rc) && WTERMSIG(rc) == SIGTERM) proc_exit(1); signaled = WIFSIGNALED(rc) || WEXITSTATUS(rc) > 125; ereport(signaled ? FATAL : DEBUG2, (errmsg("could not restore file \"%s\" from archive: %s", xlogfname, wait_result_to_str(rc)))); not_available: /* * if an archived file is not available, there might still be a version of * this file in XLOGDIR, so return that as the filename to open. * * In many recovery scenarios we expect this to fail also, but if so that * just means we've reached the end of WAL. */ snprintf(path, MAXPGPATH, XLOGDIR "/%s", xlogfname); return false; }
Datum pg_control_checkpoint(PG_FUNCTION_ARGS) { Datum values[19]; bool nulls[19]; TupleDesc tupdesc; HeapTuple htup; ControlFileData *ControlFile; XLogSegNo segno; char xlogfilename[MAXFNAMELEN]; /* * Construct a tuple descriptor for the result row. This must match this * function's pg_proc entry! */ tupdesc = CreateTemplateTupleDesc(19, false); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "checkpoint_location", LSNOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 2, "prior_location", LSNOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 3, "redo_location", LSNOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 4, "redo_wal_file", TEXTOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 5, "timeline_id", INT4OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 6, "prev_timeline_id", INT4OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 7, "full_page_writes", BOOLOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 8, "next_xid", TEXTOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 9, "next_oid", OIDOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 10, "next_multixact_id", XIDOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 11, "next_multi_offset", XIDOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 12, "oldest_xid", XIDOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 13, "oldest_xid_dbid", OIDOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 14, "oldest_active_xid", XIDOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 15, "oldest_multi_xid", XIDOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 16, "oldest_multi_dbid", OIDOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 17, "oldest_commit_ts_xid", XIDOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 18, "newest_commit_ts_xid", XIDOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 19, "checkpoint_time", TIMESTAMPTZOID, -1, 0); tupdesc = BlessTupleDesc(tupdesc); /* Read the control file. */ ControlFile = get_controlfile(DataDir, NULL); /* * Calculate name of the WAL file containing the latest checkpoint's REDO * start point. */ XLByteToSeg(ControlFile->checkPointCopy.redo, segno); XLogFileName(xlogfilename, ControlFile->checkPointCopy.ThisTimeLineID, segno); /* Populate the values and null arrays */ values[0] = LSNGetDatum(ControlFile->checkPoint); nulls[0] = false; values[1] = LSNGetDatum(ControlFile->prevCheckPoint); nulls[1] = false; values[2] = LSNGetDatum(ControlFile->checkPointCopy.redo); nulls[2] = false; values[3] = CStringGetTextDatum(xlogfilename); nulls[3] = false; values[4] = Int32GetDatum(ControlFile->checkPointCopy.ThisTimeLineID); nulls[4] = false; values[5] = Int32GetDatum(ControlFile->checkPointCopy.PrevTimeLineID); nulls[5] = false; values[6] = BoolGetDatum(ControlFile->checkPointCopy.fullPageWrites); nulls[6] = false; values[7] = CStringGetTextDatum(psprintf("%u:%u", ControlFile->checkPointCopy.nextXidEpoch, ControlFile->checkPointCopy.nextXid)); nulls[7] = false; values[8] = ObjectIdGetDatum(ControlFile->checkPointCopy.nextOid); nulls[8] = false; values[9] = TransactionIdGetDatum(ControlFile->checkPointCopy.nextMulti); nulls[9] = false; values[10] = TransactionIdGetDatum(ControlFile->checkPointCopy.nextMultiOffset); nulls[10] = false; values[11] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestXid); nulls[11] = false; values[12] = ObjectIdGetDatum(ControlFile->checkPointCopy.oldestXidDB); nulls[12] = false; values[13] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestActiveXid); nulls[13] = false; values[14] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestMulti); nulls[14] = false; values[15] = ObjectIdGetDatum(ControlFile->checkPointCopy.oldestMultiDB); nulls[15] = false; values[16] = TransactionIdGetDatum(ControlFile->checkPointCopy.oldestCommitTsXid); nulls[16] = false; values[17] = TransactionIdGetDatum(ControlFile->checkPointCopy.newestCommitTsXid); nulls[17] = false; values[18] = TimestampTzGetDatum( time_t_to_timestamptz(ControlFile->checkPointCopy.time)); nulls[18] = false; htup = heap_form_tuple(tupdesc, values, nulls); PG_RETURN_DATUM(HeapTupleGetDatum(htup)); }
static void CustomizableCleanupPriorWALFiles(void) { uint32 tli, log, seg; int signed_log = 0; if (keepfiles > 0) { sscanf(nextWALFileName, "%08X%08X%08X", &tli, &log, &seg); signed_log = log - (keepfiles / MaxSegmentsPerLogFile); if (keepfiles <= seg) seg -= keepfiles; else { seg = MaxSegmentsPerLogFile - (keepfiles % MaxSegmentsPerLogFile); signed_log--; } log = (uint32) signed_log; } /* * Work out name of prior file from current filename */ if (keepfiles > 0 && signed_log >= 0 && nextWALFileType == XLOG_DATA) { int rc; DIR *xldir; struct dirent *xlde; XLogFileName(inclusiveCleanupFileName, tli, log, seg); /* * Assume its OK to keep failing. The failure situation may change over * time, so we'd rather keep going on the main processing than fail * because we couldnt clean up yet. */ if ((xldir = opendir(archiveLocation)) != NULL) { while ((xlde = readdir(xldir)) != NULL) { /* * 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. * * We use the alphanumeric sorting property of the filenames to decide * which ones are earlier than the inclusiveCleanupFileName file. */ if (strlen(xlde->d_name) == XLOG_DATA_FNAME_LEN && strspn(xlde->d_name, "0123456789ABCDEF") == XLOG_DATA_FNAME_LEN && strcmp(xlde->d_name + 8, inclusiveCleanupFileName + 8) <= 0) { #ifdef WIN32 snprintf(WALFilePath, MAXPGPATH, "%s\\%s", archiveLocation, xlde->d_name); #else snprintf(WALFilePath, MAXPGPATH, "%s/%s", archiveLocation, xlde->d_name); #endif rc = unlink(WALFilePath); if (debug) fprintf(stderr, "\npg_standby: removed \"%s\"\n", WALFilePath); } } } else fprintf(stderr, "pg_standby: archiveLocation \"%s\" open error\n", archiveLocation); closedir(xldir); } fflush(stderr); }