struct LogicalTapeSet
 {
    BufFile    *pfile;          /* underlying file for whole tape set */
-   long        nFileBlocks;    /* # of blocks used in underlying file */
+
+   /*
+    * File size tracking.  nBlocksWritten is the size of the underlying file,
+    * in BLCKSZ blocks.  nBlocksAllocated is the number of blocks allocated
+    * by ltsGetFreeBlock(), and it is always greater than or equal to
+    * nBlocksWritten.  Blocks between nBlocksAllocated and nBlocksWritten are
+    * blocks that have been allocated for a tape, but have not been written
+    * to the underlying file yet.
+    */
+   long        nBlocksAllocated;       /* # of blocks allocated */
+   long        nBlocksWritten; /* # of blocks used in underlying file */
 
    /*
     * We store the numbers of recycled-and-available blocks in freeBlocks[].
 /*
  * Write a block-sized buffer to the specified block of the underlying file.
  *
- * NB: should not attempt to write beyond current end of file (ie, create
- * "holes" in file), since BufFile doesn't allow that.  The first write pass
- * must write blocks sequentially.
- *
  * No need for an error return convention; we ereport() on any error.
  */
 static void
 ltsWriteBlock(LogicalTapeSet *lts, long blocknum, void *buffer)
 {
+   /*
+    * BufFile does not support "holes", so if we're about to write a block
+    * that's past the current end of file, fill the space between the current
+    * end of file and the target block with zeros.
+    *
+    * This should happen rarely, otherwise you are not writing very
+    * sequentially.  In current use, this only happens when the sort ends
+    * writing a run, and switches to another tape.  The last block of the
+    * previous tape isn't flushed to disk until the end of the sort, so you
+    * get one-block hole, where the last block of the previous tape will
+    * later go.
+    */
+   while (blocknum > lts->nBlocksWritten)
+   {
+       char        zerobuf[BLCKSZ];
+
+       MemSet(zerobuf, 0, sizeof(zerobuf));
+
+       ltsWriteBlock(lts, lts->nBlocksWritten, zerobuf);
+   }
+
+   /* Write the requested block */
    if (BufFileSeekBlock(lts->pfile, blocknum) != 0 ||
        BufFileWrite(lts->pfile, buffer, BLCKSZ) != BLCKSZ)
        ereport(ERROR,
                (errcode_for_file_access(),
                 errmsg("could not write block %ld of temporary file: %m",
                        blocknum)));
+
+   /* Update nBlocksWritten, if we extended the file */
+   if (blocknum == lts->nBlocksWritten)
+       lts->nBlocksWritten++;
 }
 
 /*
 
 /*
  * Select a currently unused block for writing to.
- *
- * NB: should only be called when writer is ready to write immediately,
- * to ensure that first write pass is sequential.
  */
 static long
 ltsGetFreeBlock(LogicalTapeSet *lts)
        return lts->freeBlocks[--lts->nFreeBlocks];
    }
    else
-       return lts->nFileBlocks++;
+       return lts->nBlocksAllocated++;
 }
 
 /*
    lts = (LogicalTapeSet *) palloc(offsetof(LogicalTapeSet, tapes) +
                                    ntapes * sizeof(LogicalTape));
    lts->pfile = BufFileCreateTemp(false);
-   lts->nFileBlocks = 0L;
+   lts->nBlocksAllocated = 0L;
+   lts->nBlocksWritten = 0L;
    lts->forgetFreeSpace = false;
    lts->blocksSorted = true;   /* a zero-length array is sorted ... */
    lts->freeBlocksLen = 32;    /* reasonable initial guess */
 long
 LogicalTapeSetBlocks(LogicalTapeSet *lts)
 {
-   return lts->nFileBlocks;
+   return lts->nBlocksAllocated;
 }