diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:19:15 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:19:15 +0000 |
commit | 6eb9c5a5657d1fe77b55cc261450f3538d35a94d (patch) | |
tree | 657d8194422a5daccecfd42d654b8a245ef7b4c8 /src/bin/pg_rewind/file_ops.c | |
parent | Initial commit. (diff) | |
download | postgresql-13-upstream.tar.xz postgresql-13-upstream.zip |
Adding upstream version 13.4.upstream/13.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/bin/pg_rewind/file_ops.c')
-rw-r--r-- | src/bin/pg_rewind/file_ops.c | 325 |
1 files changed, 325 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 0000000..b3bf091 --- /dev/null +++ b/src/bin/pg_rewind/file_ops.c @@ -0,0 +1,325 @@ +/*------------------------------------------------------------------------- + * + * 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-2020, PostgreSQL Global Development Group + * + *------------------------------------------------------------------------- + */ +#include "postgres_fe.h" + +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include "common/file_perm.h" +#include "file_ops.h" +#include "filemap.h" +#include "pg_rewind.h" + +/* + * Currently open target file. + */ +static int dstfd = -1; +static char dstpath[MAXPGPATH] = ""; + +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, pg_file_create_mode); + if (dstfd < 0) + pg_fatal("could not open target file \"%s\": %m", + dstpath); +} + +/* + * Close target file, if it's open. + */ +void +close_target_file(void) +{ + if (dstfd == -1) + return; + + if (close(dstfd) != 0) + pg_fatal("could not close target file \"%s\": %m", + dstpath); + + dstfd = -1; +} + +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 target file \"%s\": %m", + dstpath); + + writeleft = size; + p = buf; + while (writeleft > 0) + { + int writelen; + + errno = 0; + writelen = write(dstfd, p, writeleft); + if (writelen < 0) + { + /* if write didn't set errno, assume problem is no disk space */ + if (errno == 0) + errno = ENOSPC; + pg_fatal("could not write file \"%s\": %m", + dstpath); + } + + 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, false); + 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"); + break; + } +} + +/* + * Remove a file from target data directory. If missing_ok is true, it + * is fine for the target file to not exist. + */ +void +remove_target_file(const char *path, bool missing_ok) +{ + char dstpath[MAXPGPATH]; + + if (dry_run) + return; + + snprintf(dstpath, sizeof(dstpath), "%s/%s", datadir_target, path); + if (unlink(dstpath) != 0) + { + if (errno == ENOENT && missing_ok) + return; + + pg_fatal("could not remove file \"%s\": %m", + dstpath); + } +} + +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, pg_file_create_mode); + if (fd < 0) + pg_fatal("could not open file \"%s\" for truncation: %m", + dstpath); + + if (ftruncate(fd, newsize) != 0) + pg_fatal("could not truncate file \"%s\" to %u: %m", + dstpath, (unsigned int) newsize); + + 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, pg_dir_create_mode) != 0) + pg_fatal("could not create directory \"%s\": %m", + dstpath); +} + +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\": %m", + dstpath); +} + +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\": %m", + dstpath); +} + +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\": %m", + dstpath); +} + + +/* + * 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; + int r; + + 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: %m", + fullpath); + + if (fstat(fd, &statbuf) < 0) + pg_fatal("could not open file \"%s\" for reading: %m", + fullpath); + + len = statbuf.st_size; + + buffer = pg_malloc(len + 1); + + r = read(fd, buffer, len); + if (r != len) + { + if (r < 0) + pg_fatal("could not read file \"%s\": %m", + fullpath); + else + pg_fatal("could not read file \"%s\": read %d of %zu", + fullpath, r, (Size) len); + } + close(fd); + + /* Zero-terminate the buffer. */ + buffer[len] = '\0'; + + if (filesize) + *filesize = len; + return buffer; +} |