diff options
| author | Bruce Momjian <bruce@momjian.us> | 2010-05-12 02:19:11 +0000 |
|---|---|---|
| committer | Bruce Momjian <bruce@momjian.us> | 2010-05-12 02:19:11 +0000 |
| commit | c2e9b2f288185a8569f6391ea250c7eeafa6c14b (patch) | |
| tree | 408e8eb0c0aacaf177602789c02d7a416bbd59e1 /contrib/pg_upgrade/file.c | |
| parent | 28e1742217716076da0700094a369eae5766974c (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.c | 478 |
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 |
