]> granicus.if.org Git - postgresql/commitdiff
Fix two timeline handling bugs in pg_receivexlog.
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>
Mon, 23 Sep 2013 07:17:52 +0000 (10:17 +0300)
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>
Mon, 23 Sep 2013 07:40:25 +0000 (10:40 +0300)
When a timeline history file is fetched from server, it is initially created
with a temporary file name, and renamed to place. However, the temporary
file name was constructed using an uninitialized buffer. Usually that meant
that the file was created in current directory instead of the target, which
usually goes unnoticed, but if the target is on a different filesystem than
the current dir, the rename() would fail. Fix that.

The second issue is that pg_receivexlog would not take .partial files into
account when determining when scanning the target directory for existing
WAL files. If the timeline has switched in the server several times in the
last WAL segment, and pg_receivexlog is restarted, it would choose a too
old starting point. That's not a problem as long as the old WAL segment
exists in the server and can be streamed over, but will cause a failure if
it's not.

Backpatch to 9.3, where this timeline handling code was written.

Analysed by Andrew Gierth, bug #8453, based on a bug report on IRC.

src/bin/pg_basebackup/pg_receivexlog.c
src/bin/pg_basebackup/receivelog.c

index 787a3951bda3577208f05bdbbe9d9333a3634b45..031ec1aa97ce8f621fede20bf99e29b889848eea 100644 (file)
@@ -121,6 +121,7 @@ FindStreamingStart(uint32 *tli)
        struct dirent *dirent;
        XLogSegNo       high_segno = 0;
        uint32          high_tli = 0;
+       bool            high_ispartial = false;
 
        dir = opendir(basedir);
        if (dir == NULL)
@@ -132,20 +133,32 @@ FindStreamingStart(uint32 *tli)
 
        while ((dirent = readdir(dir)) != NULL)
        {
-               char            fullpath[MAXPGPATH];
-               struct stat statbuf;
                uint32          tli;
                unsigned int log,
                                        seg;
                XLogSegNo       segno;
+               bool            ispartial;
 
                /*
                 * Check if the filename looks like an xlog file, or a .partial file.
                 * Xlog files are always 24 characters, and .partial files are 32
                 * characters.
                 */
-               if (strlen(dirent->d_name) != 24 ||
-                       strspn(dirent->d_name, "0123456789ABCDEF") != 24)
+               if (strlen(dirent->d_name) == 24)
+               {
+                       if (strspn(dirent->d_name, "0123456789ABCDEF") != 24)
+                               continue;
+                       ispartial = false;
+               }
+               else if (strlen(dirent->d_name) == 32)
+               {
+                       if (strspn(dirent->d_name, "0123456789ABCDEF") != 24)
+                               continue;
+                       if (strcmp(&dirent->d_name[24], ".partial") != 0)
+                               continue;
+                       ispartial = true;
+               }
+               else
                        continue;
 
                /*
@@ -160,31 +173,40 @@ FindStreamingStart(uint32 *tli)
                }
                segno = ((uint64) log) << 32 | seg;
 
-               /* Check if this is a completed segment or not */
-               snprintf(fullpath, sizeof(fullpath), "%s/%s", basedir, dirent->d_name);
-               if (stat(fullpath, &statbuf) != 0)
+               /*
+                * Check that the segment has the right size, if it's supposed to be
+                * completed.
+                */
+               if (!ispartial)
                {
-                       fprintf(stderr, _("%s: could not stat file \"%s\": %s\n"),
-                                       progname, fullpath, strerror(errno));
-                       disconnect_and_exit(1);
-               }
+                       struct stat statbuf;
+                       char            fullpath[MAXPGPATH];
 
-               if (statbuf.st_size == XLOG_SEG_SIZE)
-               {
-                       /* Completed segment */
-                       if (segno > high_segno || (segno == high_segno && tli > high_tli))
+                       snprintf(fullpath, sizeof(fullpath), "%s/%s", basedir, dirent->d_name);
+                       if (stat(fullpath, &statbuf) != 0)
+                       {
+                               fprintf(stderr, _("%s: could not stat file \"%s\": %s\n"),
+                                               progname, fullpath, strerror(errno));
+                               disconnect_and_exit(1);
+                       }
+
+                       if (statbuf.st_size != XLOG_SEG_SIZE)
                        {
-                               high_segno = segno;
-                               high_tli = tli;
+                               fprintf(stderr,
+                                               _("%s: segment file \"%s\" has incorrect size %d, skipping\n"),
+                                               progname, dirent->d_name, (int) statbuf.st_size);
                                continue;
                        }
                }
-               else
+
+               /* Looks like a valid segment. Remember that we saw it. */
+               if ((segno > high_segno) ||
+                       (segno == high_segno && tli > high_tli) ||
+                       (segno == high_segno && tli == high_tli && high_ispartial && !ispartial))
                {
-                       fprintf(stderr,
-                         _("%s: segment file \"%s\" has incorrect size %d, skipping\n"),
-                                       progname, dirent->d_name, (int) statbuf.st_size);
-                       continue;
+                       high_segno = segno;
+                       high_tli = tli;
+                       high_ispartial = ispartial;
                }
        }
 
@@ -195,10 +217,12 @@ FindStreamingStart(uint32 *tli)
                XLogRecPtr      high_ptr;
 
                /*
-                * Move the starting pointer to the start of the next segment, since
-                * the highest one we've seen was completed.
+                * Move the starting pointer to the start of the next segment, if
+                * the highest one we saw was completed. Otherwise start streaming
+                * from the beginning of the .partial segment.
                 */
-               high_segno++;
+               if (!high_ispartial)
+                       high_segno++;
 
                XLogSegNoOffsetToRecPtr(high_segno, 0, high_ptr);
 
index d56a4d71ea2e71c5f0237cea1ed34f3fe6feee11..02643eaea9441e630794599d6304530a06112547 100644 (file)
@@ -166,8 +166,7 @@ close_walfile(char *basedir, char *partial_suffix)
        walfile = -1;
 
        /*
-        * Rename the .partial file only if we've completed writing the whole
-        * segment or segment_complete is true.
+        * If we finished writing a .partial file, rename it into place.
         */
        if (currpos == XLOG_SEG_SIZE && partial_suffix)
        {
@@ -306,6 +305,8 @@ writeTimeLineHistoryFile(char *basedir, TimeLineID tli, char *filename, char *co
                return false;
        }
 
+       snprintf(path, sizeof(path), "%s/%s", basedir, histfname);
+
        /*
         * Write into a temp file name.
         */
@@ -356,8 +357,6 @@ writeTimeLineHistoryFile(char *basedir, TimeLineID tli, char *filename, char *co
        /*
         * Now move the completed history file into place with its final name.
         */
-
-       snprintf(path, sizeof(path), "%s/%s", basedir, histfname);
        if (rename(tmppath, path) < 0)
        {
                fprintf(stderr, _("%s: could not rename file \"%s\" to \"%s\": %s\n"),