LCOV - code coverage report
Current view: top level - src/backend/storage/file - buffile.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 75.7 % 313 237
Test Date: 2026-07-21 14:15:50 Functions: 92.0 % 25 23
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 51.2 % 164 84

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * buffile.c
       4                 :             :  *    Management of large buffered temporary files.
       5                 :             :  *
       6                 :             :  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
       7                 :             :  * Portions Copyright (c) 1994, Regents of the University of California
       8                 :             :  *
       9                 :             :  * IDENTIFICATION
      10                 :             :  *    src/backend/storage/file/buffile.c
      11                 :             :  *
      12                 :             :  * NOTES:
      13                 :             :  *
      14                 :             :  * BufFiles provide a very incomplete emulation of stdio atop virtual Files
      15                 :             :  * (as managed by fd.c).  Currently, we only support the buffered-I/O
      16                 :             :  * aspect of stdio: a read or write of the low-level File occurs only
      17                 :             :  * when the buffer is filled or emptied.  This is an even bigger win
      18                 :             :  * for virtual Files than for ordinary kernel files, since reducing the
      19                 :             :  * frequency with which a virtual File is touched reduces "thrashing"
      20                 :             :  * of opening/closing file descriptors.
      21                 :             :  *
      22                 :             :  * Note that BufFile structs are allocated with palloc(), and therefore
      23                 :             :  * will go away automatically at query/transaction end.  Since the underlying
      24                 :             :  * virtual Files are made with OpenTemporaryFile, all resources for
      25                 :             :  * the file are certain to be cleaned up even if processing is aborted
      26                 :             :  * by ereport(ERROR).  The data structures required are made in the
      27                 :             :  * palloc context that was current when the BufFile was created, and
      28                 :             :  * any external resources such as temp files are owned by the ResourceOwner
      29                 :             :  * that was current at that time.
      30                 :             :  *
      31                 :             :  * BufFile also supports temporary files that exceed the OS file size limit
      32                 :             :  * (by opening multiple fd.c temporary files).  This is an essential feature
      33                 :             :  * for sorts and hashjoins on large amounts of data.
      34                 :             :  *
      35                 :             :  * BufFile supports temporary files that can be shared with other backends, as
      36                 :             :  * infrastructure for parallel execution.  Such files need to be created as a
      37                 :             :  * member of a SharedFileSet that all participants are attached to.
      38                 :             :  *
      39                 :             :  * BufFile also supports temporary files that can be used by the single backend
      40                 :             :  * when the corresponding files need to be survived across the transaction and
      41                 :             :  * need to be opened and closed multiple times.  Such files need to be created
      42                 :             :  * as a member of a FileSet.
      43                 :             :  *-------------------------------------------------------------------------
      44                 :             :  */
      45                 :             : 
      46                 :             : #include "postgres.h"
      47                 :             : 
      48                 :             : #include "commands/tablespace.h"
      49                 :             : #include "executor/instrument.h"
      50                 :             : #include "miscadmin.h"
      51                 :             : #include "pgstat.h"
      52                 :             : #include "storage/buffile.h"
      53                 :             : #include "storage/bufmgr.h"
      54                 :             : #include "storage/fd.h"
      55                 :             : #include "utils/resowner.h"
      56                 :             : #include "utils/wait_event.h"
      57                 :             : 
      58                 :             : /*
      59                 :             :  * We break BufFiles into gigabyte-sized segments, regardless of RELSEG_SIZE.
      60                 :             :  * The reason is that we'd like large BufFiles to be spread across multiple
      61                 :             :  * tablespaces when available.
      62                 :             :  */
      63                 :             : #define MAX_PHYSICAL_FILESIZE   0x40000000
      64                 :             : #define BUFFILE_SEG_SIZE        (MAX_PHYSICAL_FILESIZE / BLCKSZ)
      65                 :             : 
      66                 :             : /*
      67                 :             :  * This data structure represents a buffered file that consists of one or
      68                 :             :  * more physical files (each accessed through a virtual file descriptor
      69                 :             :  * managed by fd.c).
      70                 :             :  */
      71                 :             : struct BufFile
      72                 :             : {
      73                 :             :     int         numFiles;       /* number of physical files in set */
      74                 :             :     /* all files except the last have length exactly MAX_PHYSICAL_FILESIZE */
      75                 :             :     File       *files;          /* palloc'd array with numFiles entries */
      76                 :             : 
      77                 :             :     bool        isInterXact;    /* keep open over transactions? */
      78                 :             :     bool        dirty;          /* does buffer need to be written? */
      79                 :             :     bool        readOnly;       /* has the file been set to read only? */
      80                 :             : 
      81                 :             :     FileSet    *fileset;        /* space for fileset based segment files */
      82                 :             :     const char *name;           /* name of fileset based BufFile */
      83                 :             : 
      84                 :             :     /*
      85                 :             :      * resowner is the ResourceOwner to use for underlying temp files.  (We
      86                 :             :      * don't need to remember the memory context we're using explicitly,
      87                 :             :      * because after creation we only repalloc our arrays larger.)
      88                 :             :      */
      89                 :             :     ResourceOwner resowner;
      90                 :             : 
      91                 :             :     /*
      92                 :             :      * "current pos" is position of start of buffer within the logical file.
      93                 :             :      * Position as seen by user of BufFile is (curFile, curOffset + pos).
      94                 :             :      */
      95                 :             :     int         curFile;        /* file index (0..n) part of current pos */
      96                 :             :     pgoff_t     curOffset;      /* offset part of current pos */
      97                 :             :     int64       pos;            /* next read/write position in buffer */
      98                 :             :     int64       nbytes;         /* total # of valid bytes in buffer */
      99                 :             : 
     100                 :             :     /*
     101                 :             :      * XXX Should ideally use PGIOAlignedBlock, but might need a way to avoid
     102                 :             :      * wasting per-file alignment padding when some users create many files.
     103                 :             :      */
     104                 :             :     PGAlignedBlock buffer;
     105                 :             : };
     106                 :             : 
     107                 :             : static BufFile *makeBufFileCommon(int nfiles);
     108                 :             : static BufFile *makeBufFile(File firstfile);
     109                 :             : static void extendBufFile(BufFile *file);
     110                 :             : static void BufFileLoadBuffer(BufFile *file);
     111                 :             : static void BufFileDumpBuffer(BufFile *file);
     112                 :             : static void BufFileFlush(BufFile *file);
     113                 :             : static File MakeNewFileSetSegment(BufFile *buffile, int segment);
     114                 :             : 
     115                 :             : /*
     116                 :             :  * Create BufFile and perform the common initialization.
     117                 :             :  */
     118                 :             : static BufFile *
     119                 :        6376 : makeBufFileCommon(int nfiles)
     120                 :             : {
     121                 :        6376 :     BufFile    *file = palloc_object(BufFile);
     122                 :             : 
     123                 :        6376 :     file->numFiles = nfiles;
     124                 :        6376 :     file->isInterXact = false;
     125                 :        6376 :     file->dirty = false;
     126                 :        6376 :     file->resowner = CurrentResourceOwner;
     127                 :        6376 :     file->curFile = 0;
     128                 :        6376 :     file->curOffset = 0;
     129                 :        6376 :     file->pos = 0;
     130                 :        6376 :     file->nbytes = 0;
     131                 :             : 
     132                 :        6376 :     return file;
     133                 :             : }
     134                 :             : 
     135                 :             : /*
     136                 :             :  * Create a BufFile given the first underlying physical file.
     137                 :             :  * NOTE: caller must set isInterXact if appropriate.
     138                 :             :  */
     139                 :             : static BufFile *
     140                 :        2019 : makeBufFile(File firstfile)
     141                 :             : {
     142                 :        2019 :     BufFile    *file = makeBufFileCommon(1);
     143                 :             : 
     144                 :        2019 :     file->files = palloc_object(File);
     145                 :        2019 :     file->files[0] = firstfile;
     146                 :        2019 :     file->readOnly = false;
     147                 :        2019 :     file->fileset = NULL;
     148                 :        2019 :     file->name = NULL;
     149                 :             : 
     150                 :        2019 :     return file;
     151                 :             : }
     152                 :             : 
     153                 :             : /*
     154                 :             :  * Add another component temp file.
     155                 :             :  */
     156                 :             : static void
     157                 :           0 : extendBufFile(BufFile *file)
     158                 :             : {
     159                 :             :     File        pfile;
     160                 :             :     ResourceOwner oldowner;
     161                 :             : 
     162                 :             :     /* Be sure to associate the file with the BufFile's resource owner */
     163                 :           0 :     oldowner = CurrentResourceOwner;
     164                 :           0 :     CurrentResourceOwner = file->resowner;
     165                 :             : 
     166         [ #  # ]:           0 :     if (file->fileset == NULL)
     167                 :           0 :         pfile = OpenTemporaryFile(file->isInterXact);
     168                 :             :     else
     169                 :           0 :         pfile = MakeNewFileSetSegment(file, file->numFiles);
     170                 :             : 
     171                 :             :     Assert(pfile >= 0);
     172                 :             : 
     173                 :           0 :     CurrentResourceOwner = oldowner;
     174                 :             : 
     175                 :           0 :     file->files = (File *) repalloc(file->files,
     176                 :           0 :                                     (file->numFiles + 1) * sizeof(File));
     177                 :           0 :     file->files[file->numFiles] = pfile;
     178                 :           0 :     file->numFiles++;
     179                 :           0 : }
     180                 :             : 
     181                 :             : /*
     182                 :             :  * Create a BufFile for a new temporary file (which will expand to become
     183                 :             :  * multiple temporary files if more than MAX_PHYSICAL_FILESIZE bytes are
     184                 :             :  * written to it).
     185                 :             :  *
     186                 :             :  * If interXact is true, the temp file will not be automatically deleted
     187                 :             :  * at end of transaction.
     188                 :             :  *
     189                 :             :  * Note: if interXact is true, the caller had better be calling us in a
     190                 :             :  * memory context, and with a resource owner, that will survive across
     191                 :             :  * transaction boundaries.
     192                 :             :  */
     193                 :             : BufFile *
     194                 :        2019 : BufFileCreateTemp(bool interXact)
     195                 :             : {
     196                 :             :     BufFile    *file;
     197                 :             :     File        pfile;
     198                 :             : 
     199                 :             :     /*
     200                 :             :      * Ensure that temp tablespaces are set up for OpenTemporaryFile to use.
     201                 :             :      * Possibly the caller will have done this already, but it seems useful to
     202                 :             :      * double-check here.  Failure to do this at all would result in the temp
     203                 :             :      * files always getting placed in the default tablespace, which is a
     204                 :             :      * pretty hard-to-detect bug.  Callers may prefer to do it earlier if they
     205                 :             :      * want to be sure that any required catalog access is done in some other
     206                 :             :      * resource context.
     207                 :             :      */
     208                 :        2019 :     PrepareTempTablespaces();
     209                 :             : 
     210                 :        2019 :     pfile = OpenTemporaryFile(interXact);
     211                 :             :     Assert(pfile >= 0);
     212                 :             : 
     213                 :        2019 :     file = makeBufFile(pfile);
     214                 :        2019 :     file->isInterXact = interXact;
     215                 :             : 
     216                 :        2019 :     return file;
     217                 :             : }
     218                 :             : 
     219                 :             : /*
     220                 :             :  * Build the name for a given segment of a given BufFile.
     221                 :             :  */
     222                 :             : static void
     223                 :        9378 : FileSetSegmentName(char *name, const char *buffile_name, int segment)
     224                 :             : {
     225                 :        9378 :     snprintf(name, MAXPGPATH, "%s.%d", buffile_name, segment);
     226                 :        9378 : }
     227                 :             : 
     228                 :             : /*
     229                 :             :  * Create a new segment file backing a fileset based BufFile.
     230                 :             :  */
     231                 :             : static File
     232                 :        1966 : MakeNewFileSetSegment(BufFile *buffile, int segment)
     233                 :             : {
     234                 :             :     char        name[MAXPGPATH];
     235                 :             :     File        file;
     236                 :             : 
     237                 :             :     /*
     238                 :             :      * It is possible that there are files left over from before a crash
     239                 :             :      * restart with the same name.  In order for BufFileOpenFileSet() not to
     240                 :             :      * get confused about how many segments there are, we'll unlink the next
     241                 :             :      * segment number if it already exists.
     242                 :             :      */
     243                 :        1966 :     FileSetSegmentName(name, buffile->name, segment + 1);
     244                 :        1966 :     FileSetDelete(buffile->fileset, name, true);
     245                 :             : 
     246                 :             :     /* Create the new segment. */
     247                 :        1966 :     FileSetSegmentName(name, buffile->name, segment);
     248                 :        1966 :     file = FileSetCreate(buffile->fileset, name);
     249                 :             : 
     250                 :             :     /* FileSetCreate would've errored out */
     251                 :             :     Assert(file > 0);
     252                 :             : 
     253                 :        1966 :     return file;
     254                 :             : }
     255                 :             : 
     256                 :             : /*
     257                 :             :  * Create a BufFile that can be discovered and opened read-only by other
     258                 :             :  * backends that are attached to the same SharedFileSet using the same name.
     259                 :             :  *
     260                 :             :  * The naming scheme for fileset based BufFiles is left up to the calling code.
     261                 :             :  * The name will appear as part of one or more filenames on disk, and might
     262                 :             :  * provide clues to administrators about which subsystem is generating
     263                 :             :  * temporary file data.  Since each SharedFileSet object is backed by one or
     264                 :             :  * more uniquely named temporary directory, names don't conflict with
     265                 :             :  * unrelated SharedFileSet objects.
     266                 :             :  */
     267                 :             : BufFile *
     268                 :        1966 : BufFileCreateFileSet(FileSet *fileset, const char *name)
     269                 :             : {
     270                 :             :     BufFile    *file;
     271                 :             : 
     272                 :        1966 :     file = makeBufFileCommon(1);
     273                 :        1966 :     file->fileset = fileset;
     274                 :        1966 :     file->name = pstrdup(name);
     275                 :        1966 :     file->files = palloc_object(File);
     276                 :        1966 :     file->files[0] = MakeNewFileSetSegment(file, 0);
     277                 :        1966 :     file->readOnly = false;
     278                 :             : 
     279                 :        1966 :     return file;
     280                 :             : }
     281                 :             : 
     282                 :             : /*
     283                 :             :  * Open a file that was previously created in another backend (or this one)
     284                 :             :  * with BufFileCreateFileSet in the same FileSet using the same name.
     285                 :             :  * The backend that created the file must have called BufFileClose() or
     286                 :             :  * BufFileExportFileSet() to make sure that it is ready to be opened by other
     287                 :             :  * backends and render it read-only.  If missing_ok is true, which indicates
     288                 :             :  * that missing files can be safely ignored, then return NULL if the BufFile
     289                 :             :  * with the given name is not found, otherwise, throw an error.
     290                 :             :  */
     291                 :             : BufFile *
     292                 :        2664 : BufFileOpenFileSet(FileSet *fileset, const char *name, int mode,
     293                 :             :                    bool missing_ok)
     294                 :             : {
     295                 :             :     BufFile    *file;
     296                 :             :     char        segment_name[MAXPGPATH];
     297                 :        2664 :     Size        capacity = 16;
     298                 :             :     File       *files;
     299                 :        2664 :     int         nfiles = 0;
     300                 :             : 
     301                 :        2664 :     files = palloc_array(File, capacity);
     302                 :             : 
     303                 :             :     /*
     304                 :             :      * We don't know how many segments there are, so we'll probe the
     305                 :             :      * filesystem to find out.
     306                 :             :      */
     307                 :             :     for (;;)
     308                 :             :     {
     309                 :             :         /* See if we need to expand our file segment array. */
     310         [ -  + ]:        5055 :         if (nfiles + 1 > capacity)
     311                 :             :         {
     312                 :           0 :             capacity *= 2;
     313                 :           0 :             files = repalloc_array(files, File, capacity);
     314                 :             :         }
     315                 :             :         /* Try to load a segment. */
     316                 :        5055 :         FileSetSegmentName(segment_name, name, nfiles);
     317                 :        5055 :         files[nfiles] = FileSetOpen(fileset, segment_name, mode);
     318         [ +  + ]:        5055 :         if (files[nfiles] <= 0)
     319                 :        2664 :             break;
     320                 :        2391 :         ++nfiles;
     321                 :             : 
     322         [ +  + ]:        2391 :         CHECK_FOR_INTERRUPTS();
     323                 :             :     }
     324                 :             : 
     325                 :             :     /*
     326                 :             :      * If we didn't find any files at all, then no BufFile exists with this
     327                 :             :      * name.
     328                 :             :      */
     329         [ +  + ]:        2664 :     if (nfiles == 0)
     330                 :             :     {
     331                 :             :         /* free the memory */
     332                 :         273 :         pfree(files);
     333                 :             : 
     334         [ +  - ]:         273 :         if (missing_ok)
     335                 :         273 :             return NULL;
     336                 :             : 
     337         [ #  # ]:           0 :         ereport(ERROR,
     338                 :             :                 (errcode_for_file_access(),
     339                 :             :                  errmsg("could not open temporary file \"%s\" from BufFile \"%s\": %m",
     340                 :             :                         segment_name, name)));
     341                 :             :     }
     342                 :             : 
     343                 :        2391 :     file = makeBufFileCommon(nfiles);
     344                 :        2391 :     file->files = files;
     345                 :        2391 :     file->readOnly = (mode == O_RDONLY);
     346                 :        2391 :     file->fileset = fileset;
     347                 :        2391 :     file->name = pstrdup(name);
     348                 :             : 
     349                 :        2391 :     return file;
     350                 :             : }
     351                 :             : 
     352                 :             : /*
     353                 :             :  * Delete a BufFile that was created by BufFileCreateFileSet in the given
     354                 :             :  * FileSet using the given name.
     355                 :             :  *
     356                 :             :  * It is not necessary to delete files explicitly with this function.  It is
     357                 :             :  * provided only as a way to delete files proactively, rather than waiting for
     358                 :             :  * the FileSet to be cleaned up.
     359                 :             :  *
     360                 :             :  * Only one backend should attempt to delete a given name, and should know
     361                 :             :  * that it exists and has been exported or closed otherwise missing_ok should
     362                 :             :  * be passed true.
     363                 :             :  */
     364                 :             : void
     365                 :         352 : BufFileDeleteFileSet(FileSet *fileset, const char *name, bool missing_ok)
     366                 :             : {
     367                 :             :     char        segment_name[MAXPGPATH];
     368                 :         352 :     int         segment = 0;
     369                 :         352 :     bool        found = false;
     370                 :             : 
     371                 :             :     /*
     372                 :             :      * We don't know how many segments the file has.  We'll keep deleting
     373                 :             :      * until we run out.  If we don't manage to find even an initial segment,
     374                 :             :      * raise an error.
     375                 :             :      */
     376                 :             :     for (;;)
     377                 :             :     {
     378                 :         391 :         FileSetSegmentName(segment_name, name, segment);
     379         [ +  + ]:         391 :         if (!FileSetDelete(fileset, segment_name, true))
     380                 :         352 :             break;
     381                 :          39 :         found = true;
     382                 :          39 :         ++segment;
     383                 :             : 
     384         [ -  + ]:          39 :         CHECK_FOR_INTERRUPTS();
     385                 :             :     }
     386                 :             : 
     387   [ +  +  -  + ]:         352 :     if (!found && !missing_ok)
     388         [ #  # ]:           0 :         elog(ERROR, "could not delete unknown BufFile \"%s\"", name);
     389                 :         352 : }
     390                 :             : 
     391                 :             : /*
     392                 :             :  * BufFileExportFileSet --- flush and make read-only, in preparation for sharing.
     393                 :             :  */
     394                 :             : void
     395                 :         379 : BufFileExportFileSet(BufFile *file)
     396                 :             : {
     397                 :             :     /* Must be a file belonging to a FileSet. */
     398                 :             :     Assert(file->fileset != NULL);
     399                 :             : 
     400                 :             :     /* It's probably a bug if someone calls this twice. */
     401                 :             :     Assert(!file->readOnly);
     402                 :             : 
     403                 :         379 :     BufFileFlush(file);
     404                 :         379 :     file->readOnly = true;
     405                 :         379 : }
     406                 :             : 
     407                 :             : /*
     408                 :             :  * Close a BufFile
     409                 :             :  *
     410                 :             :  * Like fclose(), this also implicitly FileCloses the underlying File.
     411                 :             :  */
     412                 :             : void
     413                 :        6216 : BufFileClose(BufFile *file)
     414                 :             : {
     415                 :             :     int         i;
     416                 :             : 
     417                 :             :     /* flush any unwritten data */
     418                 :        6216 :     BufFileFlush(file);
     419                 :             :     /* close and delete the underlying file(s) */
     420         [ +  + ]:       12585 :     for (i = 0; i < file->numFiles; i++)
     421                 :        6369 :         FileClose(file->files[i]);
     422                 :             :     /* release the buffer space */
     423                 :        6216 :     pfree(file->files);
     424                 :        6216 :     pfree(file);
     425                 :        6216 : }
     426                 :             : 
     427                 :             : /*
     428                 :             :  * BufFileLoadBuffer
     429                 :             :  *
     430                 :             :  * Load some data into buffer, if possible, starting from curOffset.
     431                 :             :  * At call, must have dirty = false, pos and nbytes = 0.
     432                 :             :  * On exit, nbytes is number of bytes loaded.
     433                 :             :  */
     434                 :             : static void
     435                 :       64068 : BufFileLoadBuffer(BufFile *file)
     436                 :             : {
     437                 :             :     File        thisfile;
     438                 :             :     instr_time  io_start;
     439                 :             :     instr_time  io_time;
     440                 :             :     ssize_t     rc;
     441                 :             : 
     442                 :             :     /*
     443                 :             :      * Advance to next component file if necessary and possible.
     444                 :             :      */
     445         [ -  + ]:       64068 :     if (file->curOffset >= MAX_PHYSICAL_FILESIZE &&
     446         [ #  # ]:           0 :         file->curFile + 1 < file->numFiles)
     447                 :             :     {
     448                 :           0 :         file->curFile++;
     449                 :           0 :         file->curOffset = 0;
     450                 :             :     }
     451                 :             : 
     452                 :       64068 :     thisfile = file->files[file->curFile];
     453                 :             : 
     454         [ -  + ]:       64068 :     if (track_io_timing)
     455                 :           0 :         INSTR_TIME_SET_CURRENT(io_start);
     456                 :             :     else
     457                 :       64068 :         INSTR_TIME_SET_ZERO(io_start);
     458                 :             : 
     459                 :             :     /*
     460                 :             :      * Read whatever we can get, up to a full bufferload.
     461                 :             :      */
     462                 :       64068 :     rc = FileRead(thisfile,
     463                 :       64068 :                   file->buffer.data,
     464                 :             :                   sizeof(file->buffer.data),
     465                 :             :                   file->curOffset,
     466                 :             :                   WAIT_EVENT_BUFFILE_READ);
     467         [ -  + ]:       64068 :     if (rc < 0)
     468                 :             :     {
     469                 :           0 :         file->nbytes = 0;
     470         [ #  # ]:           0 :         ereport(ERROR,
     471                 :             :                 (errcode_for_file_access(),
     472                 :             :                  errmsg("could not read file \"%s\": %m",
     473                 :             :                         FilePathName(thisfile))));
     474                 :             :     }
     475                 :             : 
     476                 :       64068 :     file->nbytes = rc;
     477                 :             : 
     478         [ -  + ]:       64068 :     if (track_io_timing)
     479                 :             :     {
     480                 :           0 :         INSTR_TIME_SET_CURRENT(io_time);
     481                 :           0 :         INSTR_TIME_ACCUM_DIFF(pgBufferUsage.temp_blk_read_time, io_time, io_start);
     482                 :             :     }
     483                 :             : 
     484                 :             :     /* we choose not to advance curOffset here */
     485                 :             : 
     486         [ +  + ]:       64068 :     if (file->nbytes > 0)
     487                 :       62322 :         pgBufferUsage.temp_blks_read++;
     488                 :       64068 : }
     489                 :             : 
     490                 :             : /*
     491                 :             :  * BufFileDumpBuffer
     492                 :             :  *
     493                 :             :  * Dump buffer contents starting at curOffset.
     494                 :             :  * At call, should have dirty = true, nbytes > 0.
     495                 :             :  * On exit, dirty is cleared if successful write, and curOffset is advanced.
     496                 :             :  */
     497                 :             : static void
     498                 :       69307 : BufFileDumpBuffer(BufFile *file)
     499                 :             : {
     500                 :       69307 :     int64       wpos = 0;
     501                 :             :     File        thisfile;
     502                 :             : 
     503                 :             :     /*
     504                 :             :      * Unlike BufFileLoadBuffer, we must dump the whole buffer even if it
     505                 :             :      * crosses a component-file boundary; so we need a loop.
     506                 :             :      */
     507         [ +  + ]:      138614 :     while (wpos < file->nbytes)
     508                 :             :     {
     509                 :             :         int64       availbytes;
     510                 :             :         instr_time  io_start;
     511                 :             :         instr_time  io_time;
     512                 :             :         size_t      bytestowrite;
     513                 :             :         ssize_t     rc;
     514                 :             : 
     515                 :             :         /*
     516                 :             :          * Advance to next component file if necessary and possible.
     517                 :             :          */
     518         [ -  + ]:       69307 :         if (file->curOffset >= MAX_PHYSICAL_FILESIZE)
     519                 :             :         {
     520         [ #  # ]:           0 :             while (file->curFile + 1 >= file->numFiles)
     521                 :           0 :                 extendBufFile(file);
     522                 :           0 :             file->curFile++;
     523                 :           0 :             file->curOffset = 0;
     524                 :             :         }
     525                 :             : 
     526                 :             :         /*
     527                 :             :          * Determine how much we need to write into this file.
     528                 :             :          */
     529                 :       69307 :         bytestowrite = file->nbytes - wpos;
     530                 :       69307 :         availbytes = MAX_PHYSICAL_FILESIZE - file->curOffset;
     531                 :             : 
     532         [ -  + ]:       69307 :         if (bytestowrite > availbytes)
     533                 :           0 :             bytestowrite = availbytes;
     534                 :             : 
     535                 :       69307 :         thisfile = file->files[file->curFile];
     536                 :             : 
     537         [ -  + ]:       69307 :         if (track_io_timing)
     538                 :           0 :             INSTR_TIME_SET_CURRENT(io_start);
     539                 :             :         else
     540                 :       69307 :             INSTR_TIME_SET_ZERO(io_start);
     541                 :             : 
     542                 :       69307 :         rc = FileWrite(thisfile,
     543                 :       69307 :                        file->buffer.data + wpos,
     544                 :             :                        bytestowrite,
     545                 :             :                        file->curOffset,
     546                 :             :                        WAIT_EVENT_BUFFILE_WRITE);
     547         [ -  + ]:       69307 :         if (rc <= 0)
     548         [ #  # ]:           0 :             ereport(ERROR,
     549                 :             :                     (errcode_for_file_access(),
     550                 :             :                      errmsg("could not write to file \"%s\": %m",
     551                 :             :                             FilePathName(thisfile))));
     552                 :             : 
     553         [ -  + ]:       69307 :         if (track_io_timing)
     554                 :             :         {
     555                 :           0 :             INSTR_TIME_SET_CURRENT(io_time);
     556                 :           0 :             INSTR_TIME_ACCUM_DIFF(pgBufferUsage.temp_blk_write_time, io_time, io_start);
     557                 :             :         }
     558                 :             : 
     559                 :       69307 :         file->curOffset += rc;
     560                 :       69307 :         wpos += rc;
     561                 :             : 
     562                 :       69307 :         pgBufferUsage.temp_blks_written++;
     563                 :             :     }
     564                 :       69307 :     file->dirty = false;
     565                 :             : 
     566                 :             :     /*
     567                 :             :      * At this point, curOffset has been advanced to the end of the buffer,
     568                 :             :      * ie, its original value + nbytes.  We need to make it point to the
     569                 :             :      * logical file position, ie, original value + pos, in case that is less
     570                 :             :      * (as could happen due to a small backwards seek in a dirty buffer!)
     571                 :             :      */
     572                 :       69307 :     file->curOffset -= (file->nbytes - file->pos);
     573         [ -  + ]:       69307 :     if (file->curOffset < 0)  /* handle possible segment crossing */
     574                 :             :     {
     575                 :           0 :         file->curFile--;
     576                 :             :         Assert(file->curFile >= 0);
     577                 :           0 :         file->curOffset += MAX_PHYSICAL_FILESIZE;
     578                 :             :     }
     579                 :             : 
     580                 :             :     /*
     581                 :             :      * Now we can set the buffer empty without changing the logical position
     582                 :             :      */
     583                 :       69307 :     file->pos = 0;
     584                 :       69307 :     file->nbytes = 0;
     585                 :       69307 : }
     586                 :             : 
     587                 :             : /*
     588                 :             :  * BufFileRead variants
     589                 :             :  *
     590                 :             :  * Like fread() except we assume 1-byte element size and report I/O errors via
     591                 :             :  * ereport().
     592                 :             :  *
     593                 :             :  * If 'exact' is true, then an error is also raised if the number of bytes
     594                 :             :  * read is not exactly 'size' (no short reads).  If 'exact' and 'eofOK' are
     595                 :             :  * true, then reading zero bytes is ok.
     596                 :             :  */
     597                 :             : static size_t
     598                 :    16156450 : BufFileReadCommon(BufFile *file, void *ptr, size_t size, bool exact, bool eofOK)
     599                 :             : {
     600                 :    16156450 :     size_t      start_size = size;
     601                 :    16156450 :     size_t      nread = 0;
     602                 :             :     size_t      nthistime;
     603                 :             : 
     604                 :    16156450 :     BufFileFlush(file);
     605                 :             : 
     606         [ +  + ]:    32327528 :     while (size > 0)
     607                 :             :     {
     608         [ +  + ]:    16172824 :         if (file->pos >= file->nbytes)
     609                 :             :         {
     610                 :             :             /* Try to load more data into buffer. */
     611                 :       64068 :             file->curOffset += file->pos;
     612                 :       64068 :             file->pos = 0;
     613                 :       64068 :             file->nbytes = 0;
     614                 :       64068 :             BufFileLoadBuffer(file);
     615         [ +  + ]:       64068 :             if (file->nbytes <= 0)
     616                 :        1746 :                 break;          /* no more data available */
     617                 :             :         }
     618                 :             : 
     619                 :    16171078 :         nthistime = file->nbytes - file->pos;
     620         [ +  + ]:    16171078 :         if (nthistime > size)
     621                 :    16110769 :             nthistime = size;
     622                 :             :         Assert(nthistime > 0);
     623                 :             : 
     624                 :    16171078 :         memcpy(ptr, file->buffer.data + file->pos, nthistime);
     625                 :             : 
     626                 :    16171078 :         file->pos += nthistime;
     627                 :    16171078 :         ptr = (char *) ptr + nthistime;
     628                 :    16171078 :         size -= nthistime;
     629                 :    16171078 :         nread += nthistime;
     630                 :             :     }
     631                 :             : 
     632   [ +  -  +  + ]:    16156450 :     if (exact &&
     633   [ +  -  -  + ]:        1746 :         (nread != start_size && !(nread == 0 && eofOK)))
     634   [ #  #  #  # ]:           0 :         ereport(ERROR,
     635                 :             :                 errcode_for_file_access(),
     636                 :             :                 file->name ?
     637                 :             :                 errmsg("could not read from file set \"%s\": read only %zu of %zu bytes",
     638                 :             :                        file->name, nread, start_size) :
     639                 :             :                 errmsg("could not read from temporary file: read only %zu of %zu bytes",
     640                 :             :                        nread, start_size));
     641                 :             : 
     642                 :    16156450 :     return nread;
     643                 :             : }
     644                 :             : 
     645                 :             : /*
     646                 :             :  * Legacy interface where the caller needs to check for end of file or short
     647                 :             :  * reads.
     648                 :             :  */
     649                 :             : size_t
     650                 :           0 : BufFileRead(BufFile *file, void *ptr, size_t size)
     651                 :             : {
     652                 :           0 :     return BufFileReadCommon(file, ptr, size, false, false);
     653                 :             : }
     654                 :             : 
     655                 :             : /*
     656                 :             :  * Require read of exactly the specified size.
     657                 :             :  */
     658                 :             : void
     659                 :    10539595 : BufFileReadExact(BufFile *file, void *ptr, size_t size)
     660                 :             : {
     661                 :    10539595 :     BufFileReadCommon(file, ptr, size, true, false);
     662                 :    10539595 : }
     663                 :             : 
     664                 :             : /*
     665                 :             :  * Require read of exactly the specified size, but optionally allow end of
     666                 :             :  * file (in which case 0 is returned).
     667                 :             :  */
     668                 :             : size_t
     669                 :     5616855 : BufFileReadMaybeEOF(BufFile *file, void *ptr, size_t size, bool eofOK)
     670                 :             : {
     671                 :     5616855 :     return BufFileReadCommon(file, ptr, size, true, eofOK);
     672                 :             : }
     673                 :             : 
     674                 :             : /*
     675                 :             :  * BufFileWrite
     676                 :             :  *
     677                 :             :  * Like fwrite() except we assume 1-byte element size and report errors via
     678                 :             :  * ereport().
     679                 :             :  */
     680                 :             : void
     681                 :    11669169 : BufFileWrite(BufFile *file, const void *ptr, size_t size)
     682                 :             : {
     683                 :             :     size_t      nthistime;
     684                 :             : 
     685                 :             :     Assert(!file->readOnly);
     686                 :             : 
     687         [ +  + ]:    23363260 :     while (size > 0)
     688                 :             :     {
     689         [ +  + ]:    11694091 :         if (file->pos >= BLCKSZ)
     690                 :             :         {
     691                 :             :             /* Buffer full, dump it out */
     692         [ +  + ]:       42306 :             if (file->dirty)
     693                 :       41990 :                 BufFileDumpBuffer(file);
     694                 :             :             else
     695                 :             :             {
     696                 :             :                 /* Hmm, went directly from reading to writing? */
     697                 :         316 :                 file->curOffset += file->pos;
     698                 :         316 :                 file->pos = 0;
     699                 :         316 :                 file->nbytes = 0;
     700                 :             :             }
     701                 :             :         }
     702                 :             : 
     703                 :    11694091 :         nthistime = BLCKSZ - file->pos;
     704         [ +  + ]:    11694091 :         if (nthistime > size)
     705                 :    11627115 :             nthistime = size;
     706                 :             :         Assert(nthistime > 0);
     707                 :             : 
     708                 :    11694091 :         memcpy(file->buffer.data + file->pos, ptr, nthistime);
     709                 :             : 
     710                 :    11694091 :         file->dirty = true;
     711                 :    11694091 :         file->pos += nthistime;
     712         [ +  + ]:    11694091 :         if (file->nbytes < file->pos)
     713                 :    11691515 :             file->nbytes = file->pos;
     714                 :    11694091 :         ptr = (const char *) ptr + nthistime;
     715                 :    11694091 :         size -= nthistime;
     716                 :             :     }
     717                 :    11669169 : }
     718                 :             : 
     719                 :             : /*
     720                 :             :  * BufFileFlush
     721                 :             :  *
     722                 :             :  * Like fflush(), except that I/O errors are reported with ereport().
     723                 :             :  */
     724                 :             : static void
     725                 :    16198690 : BufFileFlush(BufFile *file)
     726                 :             : {
     727         [ +  + ]:    16198690 :     if (file->dirty)
     728                 :       27317 :         BufFileDumpBuffer(file);
     729                 :             : 
     730                 :             :     Assert(!file->dirty);
     731                 :    16198690 : }
     732                 :             : 
     733                 :             : /*
     734                 :             :  * BufFileSeek
     735                 :             :  *
     736                 :             :  * Like fseek(), except that target position needs two values in order to
     737                 :             :  * work when logical filesize exceeds maximum value representable by pgoff_t.
     738                 :             :  * We do not support relative seeks across more than that, however.
     739                 :             :  * I/O errors are reported by ereport().
     740                 :             :  *
     741                 :             :  * Result is 0 if OK, EOF if not.  Logical position is not moved if an
     742                 :             :  * impossible seek is attempted.
     743                 :             :  */
     744                 :             : int
     745                 :       72586 : BufFileSeek(BufFile *file, int fileno, pgoff_t offset, int whence)
     746                 :             : {
     747                 :             :     int         newFile;
     748                 :             :     pgoff_t     newOffset;
     749                 :             : 
     750   [ +  -  +  - ]:       72586 :     switch (whence)
     751                 :             :     {
     752                 :       72251 :         case SEEK_SET:
     753         [ -  + ]:       72251 :             if (fileno < 0)
     754                 :           0 :                 return EOF;
     755                 :       72251 :             newFile = fileno;
     756                 :       72251 :             newOffset = offset;
     757                 :       72251 :             break;
     758                 :           0 :         case SEEK_CUR:
     759                 :             : 
     760                 :             :             /*
     761                 :             :              * Relative seek considers only the signed offset, ignoring
     762                 :             :              * fileno.
     763                 :             :              */
     764                 :           0 :             newFile = file->curFile;
     765                 :           0 :             newOffset = (file->curOffset + file->pos) + offset;
     766                 :           0 :             break;
     767                 :         335 :         case SEEK_END:
     768                 :             : 
     769                 :             :             /*
     770                 :             :              * The file size of the last file gives us the end offset of that
     771                 :             :              * file.
     772                 :             :              */
     773                 :         335 :             newFile = file->numFiles - 1;
     774                 :         335 :             newOffset = FileSize(file->files[file->numFiles - 1]);
     775         [ -  + ]:         335 :             if (newOffset < 0)
     776         [ #  # ]:           0 :                 ereport(ERROR,
     777                 :             :                         (errcode_for_file_access(),
     778                 :             :                          errmsg("could not determine size of temporary file \"%s\" from BufFile \"%s\": %m",
     779                 :             :                                 FilePathName(file->files[file->numFiles - 1]),
     780                 :             :                                 file->name)));
     781                 :         335 :             break;
     782                 :           0 :         default:
     783         [ #  # ]:           0 :             elog(ERROR, "invalid whence: %d", whence);
     784                 :             :             return EOF;
     785                 :             :     }
     786         [ -  + ]:       72586 :     while (newOffset < 0)
     787                 :             :     {
     788         [ #  # ]:           0 :         if (--newFile < 0)
     789                 :           0 :             return EOF;
     790                 :           0 :         newOffset += MAX_PHYSICAL_FILESIZE;
     791                 :             :     }
     792         [ +  + ]:       72586 :     if (newFile == file->curFile &&
     793         [ +  + ]:       72433 :         newOffset >= file->curOffset &&
     794         [ +  + ]:       52965 :         newOffset <= file->curOffset + file->nbytes)
     795                 :             :     {
     796                 :             :         /*
     797                 :             :          * Seek is to a point within existing buffer; we can just adjust
     798                 :             :          * pos-within-buffer, without flushing buffer.  Note this is OK
     799                 :             :          * whether reading or writing, but buffer remains dirty if we were
     800                 :             :          * writing.
     801                 :             :          */
     802                 :       36941 :         file->pos = (int64) (newOffset - file->curOffset);
     803                 :       36941 :         return 0;
     804                 :             :     }
     805                 :             :     /* Otherwise, must reposition buffer, so flush any dirty data */
     806                 :       35645 :     BufFileFlush(file);
     807                 :             : 
     808                 :             :     /*
     809                 :             :      * At this point and no sooner, check for seek past last segment. The
     810                 :             :      * above flush could have created a new segment, so checking sooner would
     811                 :             :      * not work (at least not with this code).
     812                 :             :      */
     813                 :             : 
     814                 :             :     /* convert seek to "start of next seg" to "end of last seg" */
     815   [ -  +  -  - ]:       35645 :     if (newFile == file->numFiles && newOffset == 0)
     816                 :             :     {
     817                 :           0 :         newFile--;
     818                 :           0 :         newOffset = MAX_PHYSICAL_FILESIZE;
     819                 :             :     }
     820         [ -  + ]:       35645 :     while (newOffset > MAX_PHYSICAL_FILESIZE)
     821                 :             :     {
     822         [ #  # ]:           0 :         if (++newFile >= file->numFiles)
     823                 :           0 :             return EOF;
     824                 :           0 :         newOffset -= MAX_PHYSICAL_FILESIZE;
     825                 :             :     }
     826         [ -  + ]:       35645 :     if (newFile >= file->numFiles)
     827                 :           0 :         return EOF;
     828                 :             :     /* Seek is OK! */
     829                 :       35645 :     file->curFile = newFile;
     830                 :       35645 :     file->curOffset = newOffset;
     831                 :       35645 :     file->pos = 0;
     832                 :       35645 :     file->nbytes = 0;
     833                 :       35645 :     return 0;
     834                 :             : }
     835                 :             : 
     836                 :             : void
     837                 :       88672 : BufFileTell(BufFile *file, int *fileno, pgoff_t *offset)
     838                 :             : {
     839                 :       88672 :     *fileno = file->curFile;
     840                 :       88672 :     *offset = file->curOffset + file->pos;
     841                 :       88672 : }
     842                 :             : 
     843                 :             : /*
     844                 :             :  * BufFileSeekBlock --- block-oriented seek
     845                 :             :  *
     846                 :             :  * Performs absolute seek to the start of the n'th BLCKSZ-sized block of
     847                 :             :  * the file.  Note that users of this interface will fail if their files
     848                 :             :  * exceed BLCKSZ * PG_INT64_MAX bytes, but that is quite a lot; we don't
     849                 :             :  * work with tables bigger than that, either...
     850                 :             :  *
     851                 :             :  * Result is 0 if OK, EOF if not.  Logical position is not moved if an
     852                 :             :  * impossible seek is attempted.
     853                 :             :  */
     854                 :             : int
     855                 :       70351 : BufFileSeekBlock(BufFile *file, int64 blknum)
     856                 :             : {
     857                 :      140702 :     return BufFileSeek(file,
     858                 :       70351 :                        (int) (blknum / BUFFILE_SEG_SIZE),
     859                 :       70351 :                        (pgoff_t) (blknum % BUFFILE_SEG_SIZE) * BLCKSZ,
     860                 :             :                        SEEK_SET);
     861                 :             : }
     862                 :             : 
     863                 :             : /*
     864                 :             :  * Returns the amount of data in the given BufFile, in bytes.
     865                 :             :  *
     866                 :             :  * Returned value includes the size of any holes left behind by BufFileAppend.
     867                 :             :  * ereport()s on failure.
     868                 :             :  */
     869                 :             : int64
     870                 :         291 : BufFileSize(BufFile *file)
     871                 :             : {
     872                 :             :     int64       lastFileSize;
     873                 :             : 
     874                 :             :     /* Get the size of the last physical file. */
     875                 :         291 :     lastFileSize = FileSize(file->files[file->numFiles - 1]);
     876         [ -  + ]:         291 :     if (lastFileSize < 0)
     877         [ #  # ]:           0 :         ereport(ERROR,
     878                 :             :                 (errcode_for_file_access(),
     879                 :             :                  errmsg("could not determine size of temporary file \"%s\" from BufFile \"%s\": %m",
     880                 :             :                         FilePathName(file->files[file->numFiles - 1]),
     881                 :             :                         file->name)));
     882                 :             : 
     883                 :         291 :     return ((file->numFiles - 1) * (int64) MAX_PHYSICAL_FILESIZE) +
     884                 :             :         lastFileSize;
     885                 :             : }
     886                 :             : 
     887                 :             : /*
     888                 :             :  * Append the contents of the source file to the end of the target file.
     889                 :             :  *
     890                 :             :  * Note that operation subsumes ownership of underlying resources from
     891                 :             :  * "source".  Caller should never call BufFileClose against source having
     892                 :             :  * called here first.  Resource owners for source and target must match,
     893                 :             :  * too.
     894                 :             :  *
     895                 :             :  * This operation works by manipulating lists of segment files, so the
     896                 :             :  * file content is always appended at a MAX_PHYSICAL_FILESIZE-aligned
     897                 :             :  * boundary, typically creating empty holes before the boundary.  These
     898                 :             :  * areas do not contain any interesting data, and cannot be read from by
     899                 :             :  * caller.
     900                 :             :  *
     901                 :             :  * Returns the block number within target where the contents of source
     902                 :             :  * begins.  Caller should apply this as an offset when working off block
     903                 :             :  * positions that are in terms of the original BufFile space.
     904                 :             :  */
     905                 :             : int64
     906                 :         153 : BufFileAppend(BufFile *target, BufFile *source)
     907                 :             : {
     908                 :         153 :     int64       startBlock = (int64) target->numFiles * BUFFILE_SEG_SIZE;
     909                 :         153 :     int         newNumFiles = target->numFiles + source->numFiles;
     910                 :             :     int         i;
     911                 :             : 
     912                 :             :     Assert(source->readOnly);
     913                 :             :     Assert(!source->dirty);
     914                 :             : 
     915         [ -  + ]:         153 :     if (target->resowner != source->resowner)
     916         [ #  # ]:           0 :         elog(ERROR, "could not append BufFile with non-matching resource owner");
     917                 :             : 
     918                 :         153 :     target->files = (File *)
     919                 :         153 :         repalloc(target->files, sizeof(File) * newNumFiles);
     920         [ +  + ]:         306 :     for (i = target->numFiles; i < newNumFiles; i++)
     921                 :         153 :         target->files[i] = source->files[i - target->numFiles];
     922                 :         153 :     target->numFiles = newNumFiles;
     923                 :             : 
     924                 :         153 :     return startBlock;
     925                 :             : }
     926                 :             : 
     927                 :             : /*
     928                 :             :  * Truncate a BufFile created by BufFileCreateFileSet up to the given fileno
     929                 :             :  * and the offset.
     930                 :             :  */
     931                 :             : void
     932                 :           9 : BufFileTruncateFileSet(BufFile *file, int fileno, pgoff_t offset)
     933                 :             : {
     934                 :           9 :     int         numFiles = file->numFiles;
     935                 :           9 :     int         newFile = fileno;
     936                 :           9 :     pgoff_t     newOffset = file->curOffset;
     937                 :             :     char        segment_name[MAXPGPATH];
     938                 :             :     int         i;
     939                 :             : 
     940                 :             :     /*
     941                 :             :      * Loop over all the files up to the given fileno and remove the files
     942                 :             :      * that are greater than the fileno and truncate the given file up to the
     943                 :             :      * offset. Note that we also remove the given fileno if the offset is 0
     944                 :             :      * provided it is not the first file in which we truncate it.
     945                 :             :      */
     946         [ +  + ]:          18 :     for (i = file->numFiles - 1; i >= fileno; i--)
     947                 :             :     {
     948   [ +  -  -  +  :           9 :         if ((i != fileno || offset == 0) && i != 0)
                   -  - ]
     949                 :             :         {
     950                 :           0 :             FileSetSegmentName(segment_name, file->name, i);
     951                 :           0 :             FileClose(file->files[i]);
     952         [ #  # ]:           0 :             if (!FileSetDelete(file->fileset, segment_name, true))
     953         [ #  # ]:           0 :                 ereport(ERROR,
     954                 :             :                         (errcode_for_file_access(),
     955                 :             :                          errmsg("could not delete fileset \"%s\": %m",
     956                 :             :                                 segment_name)));
     957                 :           0 :             numFiles--;
     958                 :           0 :             newOffset = MAX_PHYSICAL_FILESIZE;
     959                 :             : 
     960                 :             :             /*
     961                 :             :              * This is required to indicate that we have deleted the given
     962                 :             :              * fileno.
     963                 :             :              */
     964         [ #  # ]:           0 :             if (i == fileno)
     965                 :           0 :                 newFile--;
     966                 :             :         }
     967                 :             :         else
     968                 :             :         {
     969         [ -  + ]:           9 :             if (FileTruncate(file->files[i], offset,
     970                 :             :                              WAIT_EVENT_BUFFILE_TRUNCATE) < 0)
     971         [ #  # ]:           0 :                 ereport(ERROR,
     972                 :             :                         (errcode_for_file_access(),
     973                 :             :                          errmsg("could not truncate file \"%s\": %m",
     974                 :             :                                 FilePathName(file->files[i]))));
     975                 :           9 :             newOffset = offset;
     976                 :             :         }
     977                 :             :     }
     978                 :             : 
     979                 :           9 :     file->numFiles = numFiles;
     980                 :             : 
     981                 :             :     /*
     982                 :             :      * If the truncate point is within existing buffer then we can just adjust
     983                 :             :      * pos within buffer.
     984                 :             :      */
     985         [ +  - ]:           9 :     if (newFile == file->curFile &&
     986         [ +  - ]:           9 :         newOffset >= file->curOffset &&
     987         [ -  + ]:           9 :         newOffset <= file->curOffset + file->nbytes)
     988                 :             :     {
     989                 :             :         /* No need to reset the current pos if the new pos is greater. */
     990         [ #  # ]:           0 :         if (newOffset <= file->curOffset + file->pos)
     991                 :           0 :             file->pos = (int64) newOffset - file->curOffset;
     992                 :             : 
     993                 :             :         /* Adjust the nbytes for the current buffer. */
     994                 :           0 :         file->nbytes = (int64) newOffset - file->curOffset;
     995                 :             :     }
     996         [ +  - ]:           9 :     else if (newFile == file->curFile &&
     997         [ -  + ]:           9 :              newOffset < file->curOffset)
     998                 :             :     {
     999                 :             :         /*
    1000                 :             :          * The truncate point is within the existing file but prior to the
    1001                 :             :          * current position, so we can forget the current buffer and reset the
    1002                 :             :          * current position.
    1003                 :             :          */
    1004                 :           0 :         file->curOffset = newOffset;
    1005                 :           0 :         file->pos = 0;
    1006                 :           0 :         file->nbytes = 0;
    1007                 :             :     }
    1008         [ -  + ]:           9 :     else if (newFile < file->curFile)
    1009                 :             :     {
    1010                 :             :         /*
    1011                 :             :          * The truncate point is prior to the current file, so need to reset
    1012                 :             :          * the current position accordingly.
    1013                 :             :          */
    1014                 :           0 :         file->curFile = newFile;
    1015                 :           0 :         file->curOffset = newOffset;
    1016                 :           0 :         file->pos = 0;
    1017                 :           0 :         file->nbytes = 0;
    1018                 :             :     }
    1019                 :             :     /* Nothing to do, if the truncate point is beyond current file. */
    1020                 :           9 : }
        

Generated by: LCOV version 2.0-1