diff options
author | Heikki Linnakangas <heikki.linnakangas@iki.fi> | 2015-03-23 19:47:52 +0200 |
---|---|---|
committer | Heikki Linnakangas <heikki.linnakangas@iki.fi> | 2015-03-23 19:47:52 +0200 |
commit | 61081e75c6741024f7717ade0450090590e96c85 (patch) | |
tree | 9b4fde1d8bae566036df68eecdfd84b896e14b57 /src/bin/pg_rewind/file_ops.c | |
parent | 87cec51d3ad1107f6f224ed7d773e70c8896e4c0 (diff) | |
download | postgresql-61081e75c6741024f7717ade0450090590e96c85.tar.gz postgresql-61081e75c6741024f7717ade0450090590e96c85.zip |
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.
Diffstat (limited to 'src/bin/pg_rewind/file_ops.c')
-rw-r--r-- | src/bin/pg_rewind/file_ops.c | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c new file mode 100644 index 00000000000..589a01a4340 --- /dev/null +++ b/src/bin/pg_rewind/file_ops.c @@ -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; +} |