summaryrefslogtreecommitdiff
path: root/contrib/pg_upgrade/file.c
diff options
context:
space:
mode:
authorBruce Momjian <bruce@momjian.us>2010-05-12 02:19:11 +0000
committerBruce Momjian <bruce@momjian.us>2010-05-12 02:19:11 +0000
commitc2e9b2f288185a8569f6391ea250c7eeafa6c14b (patch)
tree408e8eb0c0aacaf177602789c02d7a416bbd59e1 /contrib/pg_upgrade/file.c
parent28e1742217716076da0700094a369eae5766974c (diff)
Add pg_upgrade to /contrib; will be in 9.0 beta2.
Add documentation. Supports migration from PG 8.3 and 8.4.
Diffstat (limited to 'contrib/pg_upgrade/file.c')
-rw-r--r--contrib/pg_upgrade/file.c478
1 files changed, 478 insertions, 0 deletions
diff --git a/contrib/pg_upgrade/file.c b/contrib/pg_upgrade/file.c
new file mode 100644
index 00000000000..f9ed3d46133
--- /dev/null
+++ b/contrib/pg_upgrade/file.c
@@ -0,0 +1,478 @@
+/*
+ * file.c
+ *
+ * file system operations
+ */
+
+#include "pg_upgrade.h"
+
+#include <sys/types.h>
+#include <fcntl.h>
+
+#ifdef EDB_NATIVE_LANG
+#include <fcntl.h>
+#endif
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#ifndef WIN32
+char pathSeparator = '/';
+#else
+char pathSeparator = '\\';
+#endif
+
+
+static int copy_file(const char *fromfile, const char *tofile, bool force);
+
+#ifdef WIN32
+static int win32_pghardlink(const char *src, const char *dst);
+#endif
+#ifdef NOT_USED
+static int copy_dir(const char *from, const char *to, bool force);
+#endif
+
+#if defined(sun) || defined(WIN32)
+static int pg_scandir_internal(migratorContext *ctx, const char *dirname,
+ struct dirent *** namelist,
+ int (*selector) (const struct dirent *));
+#endif
+
+
+/*
+ * copyAndUpdateFile()
+ *
+ * Copies a relation file from src to dst. If pageConverter is non-NULL, this function
+ * uses that pageConverter to do a page-by-page conversion.
+ */
+const char *
+copyAndUpdateFile(migratorContext *ctx, pageCnvCtx *pageConverter,
+ const char *src, const char *dst, bool force)
+{
+ if (pageConverter == NULL)
+ {
+ if (pg_copy_file(src, dst, force) == -1)
+ return getErrorText(errno);
+ else
+ return NULL;
+ }
+ else
+ {
+ /*
+ * We have a pageConverter object - that implies that the
+ * PageLayoutVersion differs between the two clusters so we have to
+ * perform a page-by-page conversion.
+ *
+ * If the pageConverter can convert the entire file at once, invoke
+ * that plugin function, otherwise, read each page in the relation
+ * file and call the convertPage plugin function.
+ */
+
+#ifdef PAGE_CONVERSION
+ if (pageConverter->convertFile)
+ return pageConverter->convertFile(pageConverter->pluginData,
+ dst, src);
+ else
+#endif
+ {
+ int src_fd;
+ int dstfd;
+ char buf[BLCKSZ];
+ ssize_t bytesRead;
+ const char *msg = NULL;
+
+ if ((src_fd = open(src, O_RDONLY, 0)) < 0)
+ return "can't open source file";
+
+ if ((dstfd = open(dst, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR)) < 0)
+ return "can't create destination file";
+
+ while ((bytesRead = read(src_fd, buf, BLCKSZ)) == BLCKSZ)
+ {
+#ifdef PAGE_CONVERSION
+ if ((msg = pageConverter->convertPage(pageConverter->pluginData, buf, buf)) != NULL)
+ break;
+#endif
+ if (write(dstfd, buf, BLCKSZ) != BLCKSZ)
+ {
+ msg = "can't write new page to destination";
+ break;
+ }
+ }
+
+ close(src_fd);
+ close(dstfd);
+
+ if (msg)
+ return msg;
+ else if (bytesRead != 0)
+ return "found partial page in source file";
+ else
+ return NULL;
+ }
+ }
+}
+
+
+/*
+ * linkAndUpdateFile()
+ *
+ * Creates a symbolic link between the given relation files. We use
+ * this function to perform a true in-place update. If the on-disk
+ * format of the new cluster is bit-for-bit compatible with the on-disk
+ * format of the old cluster, we can simply symlink each relation
+ * instead of copying the data from the old cluster to the new cluster.
+ */
+const char *
+linkAndUpdateFile(migratorContext *ctx, pageCnvCtx *pageConverter,
+ const char *src, const char *dst)
+{
+ if (pageConverter != NULL)
+ return "Can't in-place update this cluster, page-by-page conversion is required";
+
+ if (pg_link_file(src, dst) == -1)
+ return getErrorText(errno);
+ else
+ return NULL;
+}
+
+
+static int
+copy_file(const char *srcfile, const char *dstfile, bool force)
+{
+
+#define COPY_BUF_SIZE (50 * BLCKSZ)
+
+ int src_fd;
+ int dest_fd;
+ char *buffer;
+
+ if ((srcfile == NULL) || (dstfile == NULL))
+ return -1;
+
+ if ((src_fd = open(srcfile, O_RDONLY, 0)) < 0)
+ return -1;
+
+ if ((dest_fd = open(dstfile, O_RDWR | O_CREAT | (force ? 0 : O_EXCL), S_IRUSR | S_IWUSR)) < 0)
+ {
+ if (src_fd != 0)
+ close(src_fd);
+
+ return -1;
+ }
+
+ buffer = (char *) malloc(COPY_BUF_SIZE);
+
+ if (buffer == NULL)
+ {
+ if (src_fd != 0)
+ close(src_fd);
+
+ if (dest_fd != 0)
+ close(dest_fd);
+
+ return -1;
+ }
+
+ /* perform data copying i.e read src source, write to destination */
+ while (true)
+ {
+ ssize_t nbytes = read(src_fd, buffer, COPY_BUF_SIZE);
+
+ if (nbytes < 0)
+ {
+ if (buffer != NULL)
+ free(buffer);
+
+ if (src_fd != 0)
+ close(src_fd);
+
+ if (dest_fd != 0)
+ close(dest_fd);
+
+ return -1;
+ }
+
+ if (nbytes == 0)
+ break;
+
+ errno = 0;
+
+ if (write(dest_fd, buffer, nbytes) != nbytes)
+ {
+ /* if write didn't set errno, assume problem is no disk space */
+ if (errno == 0)
+ errno = ENOSPC;
+
+ if (buffer != NULL)
+ free(buffer);
+
+ if (src_fd != 0)
+ close(src_fd);
+
+ if (dest_fd != 0)
+ close(dest_fd);
+
+ return -1;
+ }
+ }
+
+ if (buffer != NULL)
+ free(buffer);
+
+ if (src_fd != 0)
+ close(src_fd);
+
+ if (dest_fd != 0)
+ close(dest_fd);
+
+ return 1;
+}
+
+
+/*
+ * pg_scandir()
+ *
+ * Wrapper for portable scandir functionality
+ *
+ */
+int
+pg_scandir(migratorContext *ctx, const char *dirname,
+ struct dirent *** namelist, int (*selector) (const struct dirent *),
+ int (*cmp) (const void *, const void *))
+{
+#if defined(sun) || defined(WIN32)
+ return pg_scandir_internal(ctx, dirname, namelist, selector);
+
+ /*
+ * Here we try to guess which libc's need const, and which don't. The net
+ * goal here is to try to supress a compiler warning due to a prototype
+ * mismatch of const usage. Ideally we would do this via autoconf, but
+ * Postgres's autoconf doesn't test for this and it is overkill to add
+ * autoconf just for this. scandir() is from BSD 4.3, which had the third
+ * argument as non-const. Linux and other C libraries have updated it to
+ * use a const.
+ * http://unix.derkeiler.com/Mailing-Lists/FreeBSD/questions/2005-12/msg002
+ * 14.html
+ */
+#elif defined(freebsd) || defined(bsdi) || defined(darwin) || defined(openbsd)
+ /* no const */
+ return scandir(dirname, namelist, (int (*) (struct dirent *)) selector, cmp);
+#else
+ /* use const */
+ return scandir(dirname, namelist, selector, cmp);
+#endif
+}
+
+
+#if defined(sun) || defined(WIN32)
+/*
+ * pg_scandir_internal()
+ *
+ * We'll provide our own scandir function for sun, since it is not
+ * part of the standard system library.
+ *
+ * Returns count of files that meet the selection criteria coded in
+ * the function pointed to by selector. Creates an array of pointers
+ * to dirent structures. Address of array returned in namelist.
+ *
+ * Note that the number of dirent structures needed is dynamically
+ * allocated using realloc. Realloc can be inneficient if invoked a
+ * large number of times. Its use in pg_upgrade is to find filesystem
+ * filenames that have extended beyond the initial segment (file.1,
+ * .2, etc.) and should therefore be invoked a small number of times.
+ */
+static int
+pg_scandir_internal(migratorContext *ctx, const char *dirname,
+ struct dirent *** namelist, int (*selector) (const struct dirent *))
+{
+ DIR *dirdesc;
+ struct dirent *direntry;
+ int count = 0;
+ int name_num = 0;
+ size_t entrysize;
+
+ if ((dirdesc = opendir(dirname)) == NULL)
+ pg_log(ctx, PG_FATAL, "Could not open directory \"%s\": %m\n", dirname);
+
+ *namelist = NULL;
+
+ while ((direntry = readdir(dirdesc)) != NULL)
+ {
+ /* Invoke the selector function to see if the direntry matches */
+ if ((*selector) (direntry))
+ {
+ count++;
+
+ *namelist = (struct dirent **) realloc((void *) (*namelist),
+ (size_t) ((name_num + 1) * sizeof(struct dirent *)));
+
+ if (*namelist == NULL)
+ return -1;
+
+ entrysize = sizeof(struct dirent) - sizeof(direntry->d_name) +
+ strlen(direntry->d_name) + 1;
+
+ (*namelist)[name_num] = (struct dirent *) malloc(entrysize);
+
+ if ((*namelist)[name_num] == NULL)
+ return -1;
+
+ memcpy((*namelist)[name_num], direntry, entrysize);
+
+ name_num++;
+ }
+ }
+
+ closedir(dirdesc);
+
+ return count;
+}
+#endif
+
+
+/*
+ * dir_matching_filenames
+ *
+ * Return only matching file names during directory scan
+ */
+int
+dir_matching_filenames(const struct dirent * scan_ent)
+{
+ /* we only compare for string length because the number suffix varies */
+ if (!strncmp(scandir_file_pattern, scan_ent->d_name, strlen(scandir_file_pattern)))
+ return 1;
+
+ return 0;
+}
+
+
+void
+check_hard_link(migratorContext *ctx)
+{
+ char existing_file[MAXPGPATH];
+ char new_link_file[MAXPGPATH];
+
+ snprintf(existing_file, sizeof(existing_file), "%s/PG_VERSION", ctx->old.pgdata);
+ snprintf(new_link_file, sizeof(new_link_file), "%s/PG_VERSION.linktest", ctx->new.pgdata);
+ unlink(new_link_file); /* might fail */
+
+ if (pg_link_file(existing_file, new_link_file) == -1)
+ {
+ pg_log(ctx, PG_FATAL,
+ "Could not create hard link between old and new data directories: %s\n"
+ "In link mode the old and new data directories must be on the same file system volume.\n",
+ getErrorText(errno));
+ }
+ unlink(new_link_file);
+}
+
+#ifdef WIN32
+static int
+win32_pghardlink(const char *src, const char *dst)
+{
+ /*
+ * CreateHardLinkA returns zero for failure
+ * http://msdn.microsoft.com/en-us/library/aa363860(VS.85).aspx
+ */
+ if (CreateHardLinkA(dst, src, NULL) == 0)
+ return -1;
+ else
+ return 0;
+}
+#endif
+
+
+#ifdef NOT_USED
+/*
+ * copy_dir()
+ *
+ * Copies either a directory or a single file within a directory. If the
+ * source argument names a directory, we recursively copy that directory,
+ * otherwise we copy a single file.
+ */
+static int
+copy_dir(const char *src, const char *dst, bool force)
+{
+ DIR *srcdir;
+ struct dirent *de = NULL;
+ struct stat fst;
+
+ if (src == NULL || dst == NULL)
+ return -1;
+
+ /*
+ * Try to open the source directory - if it turns out not to be a
+ * directory, assume that it's a file and copy that instead.
+ */
+ if ((srcdir = opendir(src)) == NULL)
+ {
+ if (errno == ENOTDIR)
+ return copy_file(src, dst, true);
+ return -1;
+ }
+
+ if (mkdir(dst, S_IRWXU) != 0)
+ {
+ /*
+ * ignore directory already exist error
+ */
+ if (errno != EEXIST)
+ return -1;
+ }
+
+ while ((de = readdir(srcdir)) != NULL)
+ {
+ char src_file[MAXPGPATH];
+ char dest_file[MAXPGPATH];
+
+ if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
+ continue;
+
+ memset(src_file, 0, sizeof(src_file));
+ memset(dest_file, 0, sizeof(dest_file));
+
+ snprintf(src_file, sizeof(src_file), "%s/%s", src, de->d_name);
+ snprintf(dest_file, sizeof(dest_file), "%s/%s", dst, de->d_name);
+
+ if (stat(src_file, &fst) < 0)
+ {
+ if (srcdir != NULL)
+ {
+ closedir(srcdir);
+ srcdir = NULL;
+ }
+
+ return -1;
+ }
+
+ if (fst.st_mode & S_IFDIR)
+ {
+ /* recurse to handle subdirectories */
+ if (force)
+ copy_dir(src_file, dest_file, true);
+ }
+ else if (fst.st_mode & S_IFREG)
+ {
+ if ((copy_file(src_file, dest_file, 1)) == -1)
+ {
+ if (srcdir != NULL)
+ {
+ closedir(srcdir);
+ srcdir = NULL;
+ }
+ return -1;
+ }
+ }
+ }
+
+ if (srcdir != NULL)
+ {
+ closedir(srcdir);
+ srcdir = NULL;
+ }
+ return 1;
+}
+
+#endif