1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-12 21:01:52 +03:00

Add pg_rewind, for re-synchronizing a master server after failback.

Earlier versions of this tool were available (and still are) on github.

Thanks to Michael Paquier, Alvaro Herrera, Peter Eisentraut, Amit Kapila,
and Satoshi Nagayasu for review.
This commit is contained in:
Heikki Linnakangas
2015-03-23 19:47:52 +02:00
parent 87cec51d3a
commit 61081e75c6
29 changed files with 4141 additions and 2 deletions

View File

@ -0,0 +1,305 @@
/*-------------------------------------------------------------------------
*
* file_ops.c
* Helper functions for operating on files.
*
* Most of the functions in this file are helper functions for writing to
* the target data directory. The functions check the --dry-run flag, and
* do nothing if it's enabled. You should avoid accessing the target files
* directly but if you do, make sure you honor the --dry-run mode!
*
* Portions Copyright (c) 2013-2015, PostgreSQL Global Development Group
*
*-------------------------------------------------------------------------
*/
#include "postgres_fe.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "file_ops.h"
#include "filemap.h"
#include "logging.h"
#include "pg_rewind.h"
/*
* Currently open destination file.
*/
static int dstfd = -1;
static char dstpath[MAXPGPATH] = "";
static void remove_target_file(const char *path);
static void create_target_dir(const char *path);
static void remove_target_dir(const char *path);
static void create_target_symlink(const char *path, const char *link);
static void remove_target_symlink(const char *path);
/*
* Open a target file for writing. If 'trunc' is true and the file already
* exists, it will be truncated.
*/
void
open_target_file(const char *path, bool trunc)
{
int mode;
if (dry_run)
return;
if (dstfd != -1 && !trunc &&
strcmp(path, &dstpath[strlen(datadir_target) + 1]) == 0)
return; /* already open */
close_target_file();
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
mode = O_WRONLY | O_CREAT | PG_BINARY;
if (trunc)
mode |= O_TRUNC;
dstfd = open(dstpath, mode, 0600);
if (dstfd < 0)
pg_fatal("could not open destination file \"%s\": %s\n",
dstpath, strerror(errno));
}
/*
* Close target file, if it's open.
*/
void
close_target_file(void)
{
if (dstfd == -1)
return;
if (close(dstfd) != 0)
pg_fatal("error closing destination file \"%s\": %s\n",
dstpath, strerror(errno));
dstfd = -1;
/* fsync? */
}
void
write_target_range(char *buf, off_t begin, size_t size)
{
int writeleft;
char *p;
/* update progress report */
fetch_done += size;
progress_report(false);
if (dry_run)
return;
if (lseek(dstfd, begin, SEEK_SET) == -1)
pg_fatal("could not seek in destination file \"%s\": %s\n",
dstpath, strerror(errno));
writeleft = size;
p = buf;
while (writeleft > 0)
{
int writelen;
writelen = write(dstfd, p, writeleft);
if (writelen < 0)
pg_fatal("could not write file \"%s\": %s\n",
dstpath, strerror(errno));
p += writelen;
writeleft -= writelen;
}
/* keep the file open, in case we need to copy more blocks in it */
}
void
remove_target(file_entry_t *entry)
{
Assert(entry->action == FILE_ACTION_REMOVE);
switch (entry->type)
{
case FILE_TYPE_DIRECTORY:
remove_target_dir(entry->path);
break;
case FILE_TYPE_REGULAR:
remove_target_file(entry->path);
break;
case FILE_TYPE_SYMLINK:
remove_target_symlink(entry->path);
break;
}
}
void
create_target(file_entry_t *entry)
{
Assert(entry->action == FILE_ACTION_CREATE);
switch (entry->type)
{
case FILE_TYPE_DIRECTORY:
create_target_dir(entry->path);
break;
case FILE_TYPE_SYMLINK:
create_target_symlink(entry->path, entry->link_target);
break;
case FILE_TYPE_REGULAR:
/* can't happen. Regular files are created with open_target_file. */
pg_fatal("invalid action (CREATE) for regular file\n");
break;
}
}
static void
remove_target_file(const char *path)
{
char dstpath[MAXPGPATH];
if (dry_run)
return;
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
if (unlink(dstpath) != 0)
pg_fatal("could not remove file \"%s\": %s\n",
dstpath, strerror(errno));
}
void
truncate_target_file(const char *path, off_t newsize)
{
char dstpath[MAXPGPATH];
int fd;
if (dry_run)
return;
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
fd = open(dstpath, O_WRONLY, 0);
if (fd < 0)
pg_fatal("could not open file \"%s\" for truncation: %s\n",
dstpath, strerror(errno));
if (ftruncate(fd, newsize) != 0)
pg_fatal("could not truncate file \"%s\" to %u bytes: %s\n",
dstpath, (unsigned int) newsize, strerror(errno));
close(fd);
}
static void
create_target_dir(const char *path)
{
char dstpath[MAXPGPATH];
if (dry_run)
return;
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
if (mkdir(dstpath, S_IRWXU) != 0)
pg_fatal("could not create directory \"%s\": %s\n",
dstpath, strerror(errno));
}
static void
remove_target_dir(const char *path)
{
char dstpath[MAXPGPATH];
if (dry_run)
return;
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
if (rmdir(dstpath) != 0)
pg_fatal("could not remove directory \"%s\": %s\n",
dstpath, strerror(errno));
}
static void
create_target_symlink(const char *path, const char *link)
{
char dstpath[MAXPGPATH];
if (dry_run)
return;
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
if (symlink(link, dstpath) != 0)
pg_fatal("could not create symbolic link at \"%s\": %s\n",
dstpath, strerror(errno));
}
static void
remove_target_symlink(const char *path)
{
char dstpath[MAXPGPATH];
if (dry_run)
return;
snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path);
if (unlink(dstpath) != 0)
pg_fatal("could not remove symbolic link \"%s\": %s\n",
dstpath, strerror(errno));
}
/*
* Read a file into memory. The file to be read is <datadir>/<path>.
* The file contents are returned in a malloc'd buffer, and *filesize
* is set to the length of the file.
*
* The returned buffer is always zero-terminated; the size of the returned
* buffer is actually *filesize + 1. That's handy when reading a text file.
* This function can be used to read binary files as well, you can just
* ignore the zero-terminator in that case.
*
* This function is used to implement the fetchFile function in the "fetch"
* interface (see fetch.c), but is also called directly.
*/
char *
slurpFile(const char *datadir, const char *path, size_t *filesize)
{
int fd;
char *buffer;
struct stat statbuf;
char fullpath[MAXPGPATH];
int len;
snprintf(fullpath, sizeof(fullpath), "%s/%s", datadir, path);
if ((fd = open(fullpath, O_RDONLY | PG_BINARY, 0)) == -1)
pg_fatal("could not open file \"%s\" for reading: %s\n",
fullpath, strerror(errno));
if (fstat(fd, &statbuf) < 0)
pg_fatal("could not open file \"%s\" for reading: %s\n",
fullpath, strerror(errno));
len = statbuf.st_size;
buffer = pg_malloc(len + 1);
if (read(fd, buffer, len) != len)
pg_fatal("could not read file \"%s\": %s\n",
fullpath, strerror(errno));
close(fd);
/* Zero-terminate the buffer. */
buffer[len] = '\0';
if (filesize)
*filesize = len;
return buffer;
}