LCOV - code coverage report
Current view: top level - src/backend/storage/file - buffile.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 75.9 % 311 236
Test Date: 2026-08-11 03:17:40 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                 :        6511 : makeBufFileCommon(int nfiles)
     120                 :             : {
     121                 :        6511 :     BufFile    *file = palloc_object(BufFile);
     122                 :             : 
     123                 :        6511 :     file->numFiles = nfiles;
     124                 :        6511 :     file->isInterXact = false;
     125                 :        6511 :     file->dirty = false;
     126                 :        6511 :     file->resowner = CurrentResourceOwner;
     127                 :        6511 :     file->curFile = 0;
     128                 :        6511 :     file->curOffset = 0;
     129                 :        6511 :     file->pos = 0;
     130                 :        6511 :     file->nbytes = 0;
     131                 :             : 
     132                 :        6511 :     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                 :        2051 : makeBufFile(File firstfile)
     141                 :             : {
     142                 :        2051 :     BufFile    *file = makeBufFileCommon(1);
     143                 :             : 
     144                 :        2051 :     file->files = palloc_object(File);
     145                 :        2051 :     file->files[0] = firstfile;
     146                 :        2051 :     file->readOnly = false;
     147                 :        2051 :     file->fileset = NULL;
     148                 :        2051 :     file->name = NULL;
     149                 :             : 
     150                 :        2051 :     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 = repalloc_array(file->files, File, file->numFiles + 1);
     176                 :           0 :     file->files[file->numFiles] = pfile;
     177                 :           0 :     file->numFiles++;
     178                 :           0 : }
     179                 :             : 
     180                 :             : /*
     181                 :             :  * Create a BufFile for a new temporary file (which will expand to become
     182                 :             :  * multiple temporary files if more than MAX_PHYSICAL_FILESIZE bytes are
     183                 :             :  * written to it).
     184                 :             :  *
     185                 :             :  * If interXact is true, the temp file will not be automatically deleted
     186                 :             :  * at end of transaction.
     187                 :             :  *
     188                 :             :  * Note: if interXact is true, the caller had better be calling us in a
     189                 :             :  * memory context, and with a resource owner, that will survive across
     190                 :             :  * transaction boundaries.
     191                 :             :  */
     192                 :             : BufFile *
     193                 :        2051 : BufFileCreateTemp(bool interXact)
     194                 :             : {
     195                 :             :     BufFile    *file;
     196                 :             :     File        pfile;
     197                 :             : 
     198                 :             :     /*
     199                 :             :      * Ensure that temp tablespaces are set up for OpenTemporaryFile to use.
     200                 :             :      * Possibly the caller will have done this already, but it seems useful to
     201                 :             :      * double-check here.  Failure to do this at all would result in the temp
     202                 :             :      * files always getting placed in the default tablespace, which is a
     203                 :             :      * pretty hard-to-detect bug.  Callers may prefer to do it earlier if they
     204                 :             :      * want to be sure that any required catalog access is done in some other
     205                 :             :      * resource context.
     206                 :             :      */
     207                 :        2051 :     PrepareTempTablespaces();
     208                 :             : 
     209                 :        2051 :     pfile = OpenTemporaryFile(interXact);
     210                 :             :     Assert(pfile >= 0);
     211                 :             : 
     212                 :        2051 :     file = makeBufFile(pfile);
     213                 :        2051 :     file->isInterXact = interXact;
     214                 :             : 
     215                 :        2051 :     return file;
     216                 :             : }
     217                 :             : 
     218                 :             : /*
     219                 :             :  * Build the name for a given segment of a given BufFile.
     220                 :             :  */
     221                 :             : static void
     222                 :        9584 : FileSetSegmentName(char *name, const char *buffile_name, int segment)
     223                 :             : {
     224                 :        9584 :     snprintf(name, MAXPGPATH, "%s.%d", buffile_name, segment);
     225                 :        9584 : }
     226                 :             : 
     227                 :             : /*
     228                 :             :  * Create a new segment file backing a fileset based BufFile.
     229                 :             :  */
     230                 :             : static File
     231                 :        2014 : MakeNewFileSetSegment(BufFile *buffile, int segment)
     232                 :             : {
     233                 :             :     char        name[MAXPGPATH];
     234                 :             :     File        file;
     235                 :             : 
     236                 :             :     /*
     237                 :             :      * It is possible that there are files left over from before a crash
     238                 :             :      * restart with the same name.  In order for BufFileOpenFileSet() not to
     239                 :             :      * get confused about how many segments there are, we'll unlink the next
     240                 :             :      * segment number if it already exists.
     241                 :             :      */
     242                 :        2014 :     FileSetSegmentName(name, buffile->name, segment + 1);
     243                 :        2014 :     FileSetDelete(buffile->fileset, name, true);
     244                 :             : 
     245                 :             :     /* Create the new segment. */
     246                 :        2014 :     FileSetSegmentName(name, buffile->name, segment);
     247                 :        2014 :     file = FileSetCreate(buffile->fileset, name);
     248                 :             : 
     249                 :             :     /* FileSetCreate would've errored out */
     250                 :             :     Assert(file > 0);
     251                 :             : 
     252                 :        2014 :     return file;
     253                 :             : }
     254                 :             : 
     255                 :             : /*
     256                 :             :  * Create a BufFile that can be discovered and opened read-only by other
     257                 :             :  * backends that are attached to the same SharedFileSet using the same name.
     258                 :             :  *
     259                 :             :  * The naming scheme for fileset based BufFiles is left up to the calling code.
     260                 :             :  * The name will appear as part of one or more filenames on disk, and might
     261                 :             :  * provide clues to administrators about which subsystem is generating
     262                 :             :  * temporary file data.  Since each SharedFileSet object is backed by one or
     263                 :             :  * more uniquely named temporary directory, names don't conflict with
     264                 :             :  * unrelated SharedFileSet objects.
     265                 :             :  */
     266                 :             : BufFile *
     267                 :        2014 : BufFileCreateFileSet(FileSet *fileset, const char *name)
     268                 :             : {
     269                 :             :     BufFile    *file;
     270                 :             : 
     271                 :        2014 :     file = makeBufFileCommon(1);
     272                 :        2014 :     file->fileset = fileset;
     273                 :        2014 :     file->name = pstrdup(name);
     274                 :        2014 :     file->files = palloc_object(File);
     275                 :        2014 :     file->files[0] = MakeNewFileSetSegment(file, 0);
     276                 :        2014 :     file->readOnly = false;
     277                 :             : 
     278                 :        2014 :     return file;
     279                 :             : }
     280                 :             : 
     281                 :             : /*
     282                 :             :  * Open a file that was previously created in another backend (or this one)
     283                 :             :  * with BufFileCreateFileSet in the same FileSet using the same name.
     284                 :             :  * The backend that created the file must have called BufFileClose() or
     285                 :             :  * BufFileExportFileSet() to make sure that it is ready to be opened by other
     286                 :             :  * backends and render it read-only.  If missing_ok is true, which indicates
     287                 :             :  * that missing files can be safely ignored, then return NULL if the BufFile
     288                 :             :  * with the given name is not found, otherwise, throw an error.
     289                 :             :  */
     290                 :             : BufFile *
     291                 :        2719 : BufFileOpenFileSet(FileSet *fileset, const char *name, int mode,
     292                 :             :                    bool missing_ok)
     293                 :             : {
     294                 :             :     BufFile    *file;
     295                 :             :     char        segment_name[MAXPGPATH];
     296                 :        2719 :     Size        capacity = 16;
     297                 :             :     File       *files;
     298                 :        2719 :     int         nfiles = 0;
     299                 :             : 
     300                 :        2719 :     files = palloc_array(File, capacity);
     301                 :             : 
     302                 :             :     /*
     303                 :             :      * We don't know how many segments there are, so we'll probe the
     304                 :             :      * filesystem to find out.
     305                 :             :      */
     306                 :             :     for (;;)
     307                 :             :     {
     308                 :             :         /* See if we need to expand our file segment array. */
     309         [ -  + ]:        5165 :         if (nfiles + 1 > capacity)
     310                 :             :         {
     311                 :           0 :             capacity *= 2;
     312                 :           0 :             files = repalloc_array(files, File, capacity);
     313                 :             :         }
     314                 :             :         /* Try to load a segment. */
     315                 :        5165 :         FileSetSegmentName(segment_name, name, nfiles);
     316                 :        5165 :         files[nfiles] = FileSetOpen(fileset, segment_name, mode);
     317         [ +  + ]:        5165 :         if (files[nfiles] <= 0)
     318                 :        2719 :             break;
     319                 :        2446 :         ++nfiles;
     320                 :             : 
     321         [ +  + ]:        2446 :         CHECK_FOR_INTERRUPTS();
     322                 :             :     }
     323                 :             : 
     324                 :             :     /*
     325                 :             :      * If we didn't find any files at all, then no BufFile exists with this
     326                 :             :      * name.
     327                 :             :      */
     328         [ +  + ]:        2719 :     if (nfiles == 0)
     329                 :             :     {
     330                 :             :         /* free the memory */
     331                 :         273 :         pfree(files);
     332                 :             : 
     333         [ +  - ]:         273 :         if (missing_ok)
     334                 :         273 :             return NULL;
     335                 :             : 
     336         [ #  # ]:           0 :         ereport(ERROR,
     337                 :             :                 (errcode_for_file_access(),
     338                 :             :                  errmsg("could not open temporary file \"%s\" from BufFile \"%s\": %m",
     339                 :             :                         segment_name, name)));
     340                 :             :     }
     341                 :             : 
     342                 :        2446 :     file = makeBufFileCommon(nfiles);
     343                 :        2446 :     file->files = files;
     344                 :        2446 :     file->readOnly = (mode == O_RDONLY);
     345                 :        2446 :     file->fileset = fileset;
     346                 :        2446 :     file->name = pstrdup(name);
     347                 :             : 
     348                 :        2446 :     return file;
     349                 :             : }
     350                 :             : 
     351                 :             : /*
     352                 :             :  * Delete a BufFile that was created by BufFileCreateFileSet in the given
     353                 :             :  * FileSet using the given name.
     354                 :             :  *
     355                 :             :  * It is not necessary to delete files explicitly with this function.  It is
     356                 :             :  * provided only as a way to delete files proactively, rather than waiting for
     357                 :             :  * the FileSet to be cleaned up.
     358                 :             :  *
     359                 :             :  * Only one backend should attempt to delete a given name, and should know
     360                 :             :  * that it exists and has been exported or closed otherwise missing_ok should
     361                 :             :  * be passed true.
     362                 :             :  */
     363                 :             : void
     364                 :         352 : BufFileDeleteFileSet(FileSet *fileset, const char *name, bool missing_ok)
     365                 :             : {
     366                 :             :     char        segment_name[MAXPGPATH];
     367                 :         352 :     int         segment = 0;
     368                 :         352 :     bool        found = false;
     369                 :             : 
     370                 :             :     /*
     371                 :             :      * We don't know how many segments the file has.  We'll keep deleting
     372                 :             :      * until we run out.  If we don't manage to find even an initial segment,
     373                 :             :      * raise an error.
     374                 :             :      */
     375                 :             :     for (;;)
     376                 :             :     {
     377                 :         391 :         FileSetSegmentName(segment_name, name, segment);
     378         [ +  + ]:         391 :         if (!FileSetDelete(fileset, segment_name, true))
     379                 :         352 :             break;
     380                 :          39 :         found = true;
     381                 :          39 :         ++segment;
     382                 :             : 
     383         [ -  + ]:          39 :         CHECK_FOR_INTERRUPTS();
     384                 :             :     }
     385                 :             : 
     386   [ +  +  -  + ]:         352 :     if (!found && !missing_ok)
     387         [ #  # ]:           0 :         elog(ERROR, "could not delete unknown BufFile \"%s\"", name);
     388                 :         352 : }
     389                 :             : 
     390                 :             : /*
     391                 :             :  * BufFileExportFileSet --- flush and make read-only, in preparation for sharing.
     392                 :             :  */
     393                 :             : void
     394                 :         379 : BufFileExportFileSet(BufFile *file)
     395                 :             : {
     396                 :             :     /* Must be a file belonging to a FileSet. */
     397                 :             :     Assert(file->fileset != NULL);
     398                 :             : 
     399                 :             :     /* It's probably a bug if someone calls this twice. */
     400                 :             :     Assert(!file->readOnly);
     401                 :             : 
     402                 :         379 :     BufFileFlush(file);
     403                 :         379 :     file->readOnly = true;
     404                 :         379 : }
     405                 :             : 
     406                 :             : /*
     407                 :             :  * Close a BufFile
     408                 :             :  *
     409                 :             :  * Like fclose(), this also implicitly FileCloses the underlying File.
     410                 :             :  */
     411                 :             : void
     412                 :        6350 : BufFileClose(BufFile *file)
     413                 :             : {
     414                 :             :     int         i;
     415                 :             : 
     416                 :             :     /* flush any unwritten data */
     417                 :        6350 :     BufFileFlush(file);
     418                 :             :     /* close and delete the underlying file(s) */
     419         [ +  + ]:       12853 :     for (i = 0; i < file->numFiles; i++)
     420                 :        6503 :         FileClose(file->files[i]);
     421                 :             :     /* release the buffer space */
     422                 :        6350 :     pfree(file->files);
     423                 :        6350 :     pfree(file);
     424                 :        6350 : }
     425                 :             : 
     426                 :             : /*
     427                 :             :  * BufFileLoadBuffer
     428                 :             :  *
     429                 :             :  * Load some data into buffer, if possible, starting from curOffset.
     430                 :             :  * At call, must have dirty = false, pos and nbytes = 0.
     431                 :             :  * On exit, nbytes is number of bytes loaded.
     432                 :             :  */
     433                 :             : static void
     434                 :       64309 : BufFileLoadBuffer(BufFile *file)
     435                 :             : {
     436                 :             :     File        thisfile;
     437                 :             :     instr_time  io_start;
     438                 :             :     instr_time  io_time;
     439                 :             :     ssize_t     rc;
     440                 :             : 
     441                 :             :     /*
     442                 :             :      * Advance to next component file if necessary and possible.
     443                 :             :      */
     444         [ -  + ]:       64309 :     if (file->curOffset >= MAX_PHYSICAL_FILESIZE &&
     445         [ #  # ]:           0 :         file->curFile + 1 < file->numFiles)
     446                 :             :     {
     447                 :           0 :         file->curFile++;
     448                 :           0 :         file->curOffset = 0;
     449                 :             :     }
     450                 :             : 
     451                 :       64309 :     thisfile = file->files[file->curFile];
     452                 :             : 
     453         [ -  + ]:       64309 :     if (track_io_timing)
     454                 :           0 :         INSTR_TIME_SET_CURRENT(io_start);
     455                 :             :     else
     456                 :       64309 :         INSTR_TIME_SET_ZERO(io_start);
     457                 :             : 
     458                 :             :     /*
     459                 :             :      * Read whatever we can get, up to a full bufferload.
     460                 :             :      */
     461                 :       64309 :     rc = FileRead(thisfile,
     462                 :       64309 :                   file->buffer.data,
     463                 :             :                   sizeof(file->buffer.data),
     464                 :             :                   file->curOffset,
     465                 :             :                   WAIT_EVENT_BUFFILE_READ);
     466         [ -  + ]:       64309 :     if (rc < 0)
     467                 :             :     {
     468                 :           0 :         file->nbytes = 0;
     469         [ #  # ]:           0 :         ereport(ERROR,
     470                 :             :                 (errcode_for_file_access(),
     471                 :             :                  errmsg("could not read file \"%s\": %m",
     472                 :             :                         FilePathName(thisfile))));
     473                 :             :     }
     474                 :             : 
     475                 :       64309 :     file->nbytes = rc;
     476                 :             : 
     477         [ -  + ]:       64309 :     if (track_io_timing)
     478                 :             :     {
     479                 :           0 :         INSTR_TIME_SET_CURRENT(io_time);
     480                 :           0 :         INSTR_TIME_ACCUM_DIFF(pgBufferUsage.temp_blk_read_time, io_time, io_start);
     481                 :             :     }
     482                 :             : 
     483                 :             :     /* we choose not to advance curOffset here */
     484                 :             : 
     485         [ +  + ]:       64309 :     if (file->nbytes > 0)
     486                 :       62543 :         pgBufferUsage.temp_blks_read++;
     487                 :       64309 : }
     488                 :             : 
     489                 :             : /*
     490                 :             :  * BufFileDumpBuffer
     491                 :             :  *
     492                 :             :  * Dump buffer contents starting at curOffset.
     493                 :             :  * At call, should have dirty = true, nbytes > 0.
     494                 :             :  * On exit, dirty is cleared if successful write, and curOffset is advanced.
     495                 :             :  */
     496                 :             : static void
     497                 :       69532 : BufFileDumpBuffer(BufFile *file)
     498                 :             : {
     499                 :       69532 :     int64       wpos = 0;
     500                 :             :     File        thisfile;
     501                 :             : 
     502                 :             :     /*
     503                 :             :      * Unlike BufFileLoadBuffer, we must dump the whole buffer even if it
     504                 :             :      * crosses a component-file boundary; so we need a loop.
     505                 :             :      */
     506         [ +  + ]:      139064 :     while (wpos < file->nbytes)
     507                 :             :     {
     508                 :             :         int64       availbytes;
     509                 :             :         instr_time  io_start;
     510                 :             :         instr_time  io_time;
     511                 :             :         size_t      bytestowrite;
     512                 :             :         ssize_t     rc;
     513                 :             : 
     514                 :             :         /*
     515                 :             :          * Advance to next component file if necessary and possible.
     516                 :             :          */
     517         [ -  + ]:       69532 :         if (file->curOffset >= MAX_PHYSICAL_FILESIZE)
     518                 :             :         {
     519         [ #  # ]:           0 :             while (file->curFile + 1 >= file->numFiles)
     520                 :           0 :                 extendBufFile(file);
     521                 :           0 :             file->curFile++;
     522                 :           0 :             file->curOffset = 0;
     523                 :             :         }
     524                 :             : 
     525                 :             :         /*
     526                 :             :          * Determine how much we need to write into this file.
     527                 :             :          */
     528                 :       69532 :         bytestowrite = file->nbytes - wpos;
     529                 :       69532 :         availbytes = MAX_PHYSICAL_FILESIZE - file->curOffset;
     530                 :             : 
     531         [ -  + ]:       69532 :         if (bytestowrite > availbytes)
     532                 :           0 :             bytestowrite = availbytes;
     533                 :             : 
     534                 :       69532 :         thisfile = file->files[file->curFile];
     535                 :             : 
     536         [ -  + ]:       69532 :         if (track_io_timing)
     537                 :           0 :             INSTR_TIME_SET_CURRENT(io_start);
     538                 :             :         else
     539                 :       69532 :             INSTR_TIME_SET_ZERO(io_start);
     540                 :             : 
     541                 :       69532 :         rc = FileWrite(thisfile,
     542                 :       69532 :                        file->buffer.data + wpos,
     543                 :             :                        bytestowrite,
     544                 :             :                        file->curOffset,
     545                 :             :                        WAIT_EVENT_BUFFILE_WRITE);
     546         [ -  + ]:       69532 :         if (rc <= 0)
     547         [ #  # ]:           0 :             ereport(ERROR,
     548                 :             :                     (errcode_for_file_access(),
     549                 :             :                      errmsg("could not write to file \"%s\": %m",
     550                 :             :                             FilePathName(thisfile))));
     551                 :             : 
     552         [ -  + ]:       69532 :         if (track_io_timing)
     553                 :             :         {
     554                 :           0 :             INSTR_TIME_SET_CURRENT(io_time);
     555                 :           0 :             INSTR_TIME_ACCUM_DIFF(pgBufferUsage.temp_blk_write_time, io_time, io_start);
     556                 :             :         }
     557                 :             : 
     558                 :       69532 :         file->curOffset += rc;
     559                 :       69532 :         wpos += rc;
     560                 :             : 
     561                 :       69532 :         pgBufferUsage.temp_blks_written++;
     562                 :             :     }
     563                 :       69532 :     file->dirty = false;
     564                 :             : 
     565                 :             :     /*
     566                 :             :      * At this point, curOffset has been advanced to the end of the buffer,
     567                 :             :      * ie, its original value + nbytes.  We need to make it point to the
     568                 :             :      * logical file position, ie, original value + pos, in case that is less
     569                 :             :      * (as could happen due to a small backwards seek in a dirty buffer!)
     570                 :             :      */
     571                 :       69532 :     file->curOffset -= (file->nbytes - file->pos);
     572         [ -  + ]:       69532 :     if (file->curOffset < 0)  /* handle possible segment crossing */
     573                 :             :     {
     574                 :           0 :         file->curFile--;
     575                 :             :         Assert(file->curFile >= 0);
     576                 :           0 :         file->curOffset += MAX_PHYSICAL_FILESIZE;
     577                 :             :     }
     578                 :             : 
     579                 :             :     /*
     580                 :             :      * Now we can set the buffer empty without changing the logical position
     581                 :             :      */
     582                 :       69532 :     file->pos = 0;
     583                 :       69532 :     file->nbytes = 0;
     584                 :       69532 : }
     585                 :             : 
     586                 :             : /*
     587                 :             :  * BufFileRead variants
     588                 :             :  *
     589                 :             :  * Like fread() except we assume 1-byte element size and report I/O errors via
     590                 :             :  * ereport().
     591                 :             :  *
     592                 :             :  * If 'exact' is true, then an error is also raised if the number of bytes
     593                 :             :  * read is not exactly 'size' (no short reads).  If 'exact' and 'eofOK' are
     594                 :             :  * true, then reading zero bytes is ok.
     595                 :             :  */
     596                 :             : static size_t
     597                 :    16209488 : BufFileReadCommon(BufFile *file, void *ptr, size_t size, bool exact, bool eofOK)
     598                 :             : {
     599                 :    16209488 :     size_t      start_size = size;
     600                 :    16209488 :     size_t      nread = 0;
     601                 :             :     size_t      nthistime;
     602                 :             : 
     603                 :    16209488 :     BufFileFlush(file);
     604                 :             : 
     605         [ +  + ]:    32433712 :     while (size > 0)
     606                 :             :     {
     607         [ +  + ]:    16225990 :         if (file->pos >= file->nbytes)
     608                 :             :         {
     609                 :             :             /* Try to load more data into buffer. */
     610                 :       64309 :             file->curOffset += file->pos;
     611                 :       64309 :             file->pos = 0;
     612                 :       64309 :             file->nbytes = 0;
     613                 :       64309 :             BufFileLoadBuffer(file);
     614         [ +  + ]:       64309 :             if (file->nbytes <= 0)
     615                 :        1766 :                 break;          /* no more data available */
     616                 :             :         }
     617                 :             : 
     618                 :    16224224 :         nthistime = file->nbytes - file->pos;
     619         [ +  + ]:    16224224 :         if (nthistime > size)
     620                 :    16163746 :             nthistime = size;
     621                 :             :         Assert(nthistime > 0);
     622                 :             : 
     623                 :    16224224 :         memcpy(ptr, file->buffer.data + file->pos, nthistime);
     624                 :             : 
     625                 :    16224224 :         file->pos += nthistime;
     626                 :    16224224 :         ptr = (char *) ptr + nthistime;
     627                 :    16224224 :         size -= nthistime;
     628                 :    16224224 :         nread += nthistime;
     629                 :             :     }
     630                 :             : 
     631   [ +  -  +  + ]:    16209488 :     if (exact &&
     632   [ +  -  -  + ]:        1766 :         (nread != start_size && !(nread == 0 && eofOK)))
     633   [ #  #  #  # ]:           0 :         ereport(ERROR,
     634                 :             :                 errcode_for_file_access(),
     635                 :             :                 file->name ?
     636                 :             :                 errmsg("could not read from file set \"%s\": read only %zu of %zu bytes",
     637                 :             :                        file->name, nread, start_size) :
     638                 :             :                 errmsg("could not read from temporary file: read only %zu of %zu bytes",
     639                 :             :                        nread, start_size));
     640                 :             : 
     641                 :    16209488 :     return nread;
     642                 :             : }
     643                 :             : 
     644                 :             : /*
     645                 :             :  * Legacy interface where the caller needs to check for end of file or short
     646                 :             :  * reads.
     647                 :             :  */
     648                 :             : size_t
     649                 :           0 : BufFileRead(BufFile *file, void *ptr, size_t size)
     650                 :             : {
     651                 :           0 :     return BufFileReadCommon(file, ptr, size, false, false);
     652                 :             : }
     653                 :             : 
     654                 :             : /*
     655                 :             :  * Require read of exactly the specified size.
     656                 :             :  */
     657                 :             : void
     658                 :    10552681 : BufFileReadExact(BufFile *file, void *ptr, size_t size)
     659                 :             : {
     660                 :    10552681 :     BufFileReadCommon(file, ptr, size, true, false);
     661                 :    10552681 : }
     662                 :             : 
     663                 :             : /*
     664                 :             :  * Require read of exactly the specified size, but optionally allow end of
     665                 :             :  * file (in which case 0 is returned).
     666                 :             :  */
     667                 :             : size_t
     668                 :     5656807 : BufFileReadMaybeEOF(BufFile *file, void *ptr, size_t size, bool eofOK)
     669                 :             : {
     670                 :     5656807 :     return BufFileReadCommon(file, ptr, size, true, eofOK);
     671                 :             : }
     672                 :             : 
     673                 :             : /*
     674                 :             :  * BufFileWrite
     675                 :             :  *
     676                 :             :  * Like fwrite() except we assume 1-byte element size and report errors via
     677                 :             :  * ereport().
     678                 :             :  */
     679                 :             : void
     680                 :    11752176 : BufFileWrite(BufFile *file, const void *ptr, size_t size)
     681                 :             : {
     682                 :             :     size_t      nthistime;
     683                 :             : 
     684                 :             :     Assert(!file->readOnly);
     685                 :             : 
     686         [ +  + ]:    23529440 :     while (size > 0)
     687                 :             :     {
     688         [ +  + ]:    11777264 :         if (file->pos >= BLCKSZ)
     689                 :             :         {
     690                 :             :             /* Buffer full, dump it out */
     691         [ +  + ]:       42452 :             if (file->dirty)
     692                 :       42136 :                 BufFileDumpBuffer(file);
     693                 :             :             else
     694                 :             :             {
     695                 :             :                 /* Hmm, went directly from reading to writing? */
     696                 :         316 :                 file->curOffset += file->pos;
     697                 :         316 :                 file->pos = 0;
     698                 :         316 :                 file->nbytes = 0;
     699                 :             :             }
     700                 :             :         }
     701                 :             : 
     702                 :    11777264 :         nthistime = BLCKSZ - file->pos;
     703         [ +  + ]:    11777264 :         if (nthistime > size)
     704                 :    11710094 :             nthistime = size;
     705                 :             :         Assert(nthistime > 0);
     706                 :             : 
     707                 :    11777264 :         memcpy(file->buffer.data + file->pos, ptr, nthistime);
     708                 :             : 
     709                 :    11777264 :         file->dirty = true;
     710                 :    11777264 :         file->pos += nthistime;
     711         [ +  + ]:    11777264 :         if (file->nbytes < file->pos)
     712                 :    11774688 :             file->nbytes = file->pos;
     713                 :    11777264 :         ptr = (const char *) ptr + nthistime;
     714                 :    11777264 :         size -= nthistime;
     715                 :             :     }
     716                 :    11752176 : }
     717                 :             : 
     718                 :             : /*
     719                 :             :  * BufFileFlush
     720                 :             :  *
     721                 :             :  * Like fflush(), except that I/O errors are reported with ereport().
     722                 :             :  */
     723                 :             : static void
     724                 :    16251866 : BufFileFlush(BufFile *file)
     725                 :             : {
     726         [ +  + ]:    16251866 :     if (file->dirty)
     727                 :       27396 :         BufFileDumpBuffer(file);
     728                 :             : 
     729                 :             :     Assert(!file->dirty);
     730                 :    16251866 : }
     731                 :             : 
     732                 :             : /*
     733                 :             :  * BufFileSeek
     734                 :             :  *
     735                 :             :  * Like fseek(), except that target position needs two values in order to
     736                 :             :  * work when logical filesize exceeds maximum value representable by pgoff_t.
     737                 :             :  * We do not support relative seeks across more than that, however.
     738                 :             :  * I/O errors are reported by ereport().
     739                 :             :  *
     740                 :             :  * Result is 0 if OK, EOF if not.  Logical position is not moved if an
     741                 :             :  * impossible seek is attempted.
     742                 :             :  */
     743                 :             : int
     744                 :       72623 : BufFileSeek(BufFile *file, int fileno, pgoff_t offset, int whence)
     745                 :             : {
     746                 :             :     int         newFile;
     747                 :             :     pgoff_t     newOffset;
     748                 :             : 
     749   [ +  -  +  - ]:       72623 :     switch (whence)
     750                 :             :     {
     751                 :       72288 :         case SEEK_SET:
     752         [ -  + ]:       72288 :             if (fileno < 0)
     753                 :           0 :                 return EOF;
     754                 :       72288 :             newFile = fileno;
     755                 :       72288 :             newOffset = offset;
     756                 :       72288 :             break;
     757                 :           0 :         case SEEK_CUR:
     758                 :             : 
     759                 :             :             /*
     760                 :             :              * Relative seek considers only the signed offset, ignoring
     761                 :             :              * fileno.
     762                 :             :              */
     763                 :           0 :             newFile = file->curFile;
     764                 :           0 :             newOffset = (file->curOffset + file->pos) + offset;
     765                 :           0 :             break;
     766                 :         335 :         case SEEK_END:
     767                 :             : 
     768                 :             :             /*
     769                 :             :              * The file size of the last file gives us the end offset of that
     770                 :             :              * file.
     771                 :             :              */
     772                 :         335 :             newFile = file->numFiles - 1;
     773                 :         335 :             newOffset = FileSize(file->files[file->numFiles - 1]);
     774         [ -  + ]:         335 :             if (newOffset < 0)
     775         [ #  # ]:           0 :                 ereport(ERROR,
     776                 :             :                         (errcode_for_file_access(),
     777                 :             :                          errmsg("could not determine size of temporary file \"%s\" from BufFile \"%s\": %m",
     778                 :             :                                 FilePathName(file->files[file->numFiles - 1]),
     779                 :             :                                 file->name)));
     780                 :         335 :             break;
     781                 :           0 :         default:
     782         [ #  # ]:           0 :             elog(ERROR, "invalid whence: %d", whence);
     783                 :             :             return EOF;
     784                 :             :     }
     785         [ -  + ]:       72623 :     while (newOffset < 0)
     786                 :             :     {
     787         [ #  # ]:           0 :         if (--newFile < 0)
     788                 :           0 :             return EOF;
     789                 :           0 :         newOffset += MAX_PHYSICAL_FILESIZE;
     790                 :             :     }
     791         [ +  + ]:       72623 :     if (newFile == file->curFile &&
     792         [ +  + ]:       72470 :         newOffset >= file->curOffset &&
     793         [ +  + ]:       52967 :         newOffset <= file->curOffset + file->nbytes)
     794                 :             :     {
     795                 :             :         /*
     796                 :             :          * Seek is to a point within existing buffer; we can just adjust
     797                 :             :          * pos-within-buffer, without flushing buffer.  Note this is OK
     798                 :             :          * whether reading or writing, but buffer remains dirty if we were
     799                 :             :          * writing.
     800                 :             :          */
     801                 :       36974 :         file->pos = (int64) (newOffset - file->curOffset);
     802                 :       36974 :         return 0;
     803                 :             :     }
     804                 :             :     /* Otherwise, must reposition buffer, so flush any dirty data */
     805                 :       35649 :     BufFileFlush(file);
     806                 :             : 
     807                 :             :     /*
     808                 :             :      * At this point and no sooner, check for seek past last segment. The
     809                 :             :      * above flush could have created a new segment, so checking sooner would
     810                 :             :      * not work (at least not with this code).
     811                 :             :      */
     812                 :             : 
     813                 :             :     /* convert seek to "start of next seg" to "end of last seg" */
     814   [ -  +  -  - ]:       35649 :     if (newFile == file->numFiles && newOffset == 0)
     815                 :             :     {
     816                 :           0 :         newFile--;
     817                 :           0 :         newOffset = MAX_PHYSICAL_FILESIZE;
     818                 :             :     }
     819         [ -  + ]:       35649 :     while (newOffset > MAX_PHYSICAL_FILESIZE)
     820                 :             :     {
     821         [ #  # ]:           0 :         if (++newFile >= file->numFiles)
     822                 :           0 :             return EOF;
     823                 :           0 :         newOffset -= MAX_PHYSICAL_FILESIZE;
     824                 :             :     }
     825         [ -  + ]:       35649 :     if (newFile >= file->numFiles)
     826                 :           0 :         return EOF;
     827                 :             :     /* Seek is OK! */
     828                 :       35649 :     file->curFile = newFile;
     829                 :       35649 :     file->curOffset = newOffset;
     830                 :       35649 :     file->pos = 0;
     831                 :       35649 :     file->nbytes = 0;
     832                 :       35649 :     return 0;
     833                 :             : }
     834                 :             : 
     835                 :             : void
     836                 :       88672 : BufFileTell(BufFile *file, int *fileno, pgoff_t *offset)
     837                 :             : {
     838                 :       88672 :     *fileno = file->curFile;
     839                 :       88672 :     *offset = file->curOffset + file->pos;
     840                 :       88672 : }
     841                 :             : 
     842                 :             : /*
     843                 :             :  * BufFileSeekBlock --- block-oriented seek
     844                 :             :  *
     845                 :             :  * Performs absolute seek to the start of the n'th BLCKSZ-sized block of
     846                 :             :  * the file.  Note that users of this interface will fail if their files
     847                 :             :  * exceed BLCKSZ * PG_INT64_MAX bytes, but that is quite a lot; we don't
     848                 :             :  * work with tables bigger than that, either...
     849                 :             :  *
     850                 :             :  * Result is 0 if OK, EOF if not.  Logical position is not moved if an
     851                 :             :  * impossible seek is attempted.
     852                 :             :  */
     853                 :             : int
     854                 :       70357 : BufFileSeekBlock(BufFile *file, int64 blknum)
     855                 :             : {
     856                 :      140714 :     return BufFileSeek(file,
     857                 :       70357 :                        (int) (blknum / BUFFILE_SEG_SIZE),
     858                 :       70357 :                        (pgoff_t) (blknum % BUFFILE_SEG_SIZE) * BLCKSZ,
     859                 :             :                        SEEK_SET);
     860                 :             : }
     861                 :             : 
     862                 :             : /*
     863                 :             :  * Returns the amount of data in the given BufFile, in bytes.
     864                 :             :  *
     865                 :             :  * Returned value includes the size of any holes left behind by BufFileAppend.
     866                 :             :  * ereport()s on failure.
     867                 :             :  */
     868                 :             : int64
     869                 :         291 : BufFileSize(BufFile *file)
     870                 :             : {
     871                 :             :     int64       lastFileSize;
     872                 :             : 
     873                 :             :     /* Get the size of the last physical file. */
     874                 :         291 :     lastFileSize = FileSize(file->files[file->numFiles - 1]);
     875         [ -  + ]:         291 :     if (lastFileSize < 0)
     876         [ #  # ]:           0 :         ereport(ERROR,
     877                 :             :                 (errcode_for_file_access(),
     878                 :             :                  errmsg("could not determine size of temporary file \"%s\" from BufFile \"%s\": %m",
     879                 :             :                         FilePathName(file->files[file->numFiles - 1]),
     880                 :             :                         file->name)));
     881                 :             : 
     882                 :         291 :     return ((file->numFiles - 1) * (int64) MAX_PHYSICAL_FILESIZE) +
     883                 :             :         lastFileSize;
     884                 :             : }
     885                 :             : 
     886                 :             : /*
     887                 :             :  * Append the contents of the source file to the end of the target file.
     888                 :             :  *
     889                 :             :  * Note that operation subsumes ownership of underlying resources from
     890                 :             :  * "source".  Caller should never call BufFileClose against source having
     891                 :             :  * called here first.  Resource owners for source and target must match,
     892                 :             :  * too.
     893                 :             :  *
     894                 :             :  * This operation works by manipulating lists of segment files, so the
     895                 :             :  * file content is always appended at a MAX_PHYSICAL_FILESIZE-aligned
     896                 :             :  * boundary, typically creating empty holes before the boundary.  These
     897                 :             :  * areas do not contain any interesting data, and cannot be read from by
     898                 :             :  * caller.
     899                 :             :  *
     900                 :             :  * Returns the block number within target where the contents of source
     901                 :             :  * begins.  Caller should apply this as an offset when working off block
     902                 :             :  * positions that are in terms of the original BufFile space.
     903                 :             :  */
     904                 :             : int64
     905                 :         153 : BufFileAppend(BufFile *target, BufFile *source)
     906                 :             : {
     907                 :         153 :     int64       startBlock = (int64) target->numFiles * BUFFILE_SEG_SIZE;
     908                 :         153 :     int         newNumFiles = target->numFiles + source->numFiles;
     909                 :             :     int         i;
     910                 :             : 
     911                 :             :     Assert(source->readOnly);
     912                 :             :     Assert(!source->dirty);
     913                 :             : 
     914         [ -  + ]:         153 :     if (target->resowner != source->resowner)
     915         [ #  # ]:           0 :         elog(ERROR, "could not append BufFile with non-matching resource owner");
     916                 :             : 
     917                 :         153 :     target->files = repalloc_array(target->files, File, newNumFiles);
     918         [ +  + ]:         306 :     for (i = target->numFiles; i < newNumFiles; i++)
     919                 :         153 :         target->files[i] = source->files[i - target->numFiles];
     920                 :         153 :     target->numFiles = newNumFiles;
     921                 :             : 
     922                 :         153 :     return startBlock;
     923                 :             : }
     924                 :             : 
     925                 :             : /*
     926                 :             :  * Truncate a BufFile created by BufFileCreateFileSet up to the given fileno
     927                 :             :  * and the offset.
     928                 :             :  */
     929                 :             : void
     930                 :           9 : BufFileTruncateFileSet(BufFile *file, int fileno, pgoff_t offset)
     931                 :             : {
     932                 :           9 :     int         numFiles = file->numFiles;
     933                 :           9 :     int         newFile = fileno;
     934                 :           9 :     pgoff_t     newOffset = file->curOffset;
     935                 :             :     char        segment_name[MAXPGPATH];
     936                 :             :     int         i;
     937                 :             : 
     938                 :             :     /*
     939                 :             :      * Loop over all the files up to the given fileno and remove the files
     940                 :             :      * that are greater than the fileno and truncate the given file up to the
     941                 :             :      * offset. Note that we also remove the given fileno if the offset is 0
     942                 :             :      * provided it is not the first file in which we truncate it.
     943                 :             :      */
     944         [ +  + ]:          18 :     for (i = file->numFiles - 1; i >= fileno; i--)
     945                 :             :     {
     946   [ +  -  -  +  :           9 :         if ((i != fileno || offset == 0) && i != 0)
                   -  - ]
     947                 :             :         {
     948                 :           0 :             FileSetSegmentName(segment_name, file->name, i);
     949                 :           0 :             FileClose(file->files[i]);
     950         [ #  # ]:           0 :             if (!FileSetDelete(file->fileset, segment_name, true))
     951         [ #  # ]:           0 :                 ereport(ERROR,
     952                 :             :                         (errcode_for_file_access(),
     953                 :             :                          errmsg("could not delete fileset \"%s\": %m",
     954                 :             :                                 segment_name)));
     955                 :           0 :             numFiles--;
     956                 :           0 :             newOffset = MAX_PHYSICAL_FILESIZE;
     957                 :             : 
     958                 :             :             /*
     959                 :             :              * This is required to indicate that we have deleted the given
     960                 :             :              * fileno.
     961                 :             :              */
     962         [ #  # ]:           0 :             if (i == fileno)
     963                 :           0 :                 newFile--;
     964                 :             :         }
     965                 :             :         else
     966                 :             :         {
     967         [ -  + ]:           9 :             if (FileTruncate(file->files[i], offset,
     968                 :             :                              WAIT_EVENT_BUFFILE_TRUNCATE) < 0)
     969         [ #  # ]:           0 :                 ereport(ERROR,
     970                 :             :                         (errcode_for_file_access(),
     971                 :             :                          errmsg("could not truncate file \"%s\": %m",
     972                 :             :                                 FilePathName(file->files[i]))));
     973                 :           9 :             newOffset = offset;
     974                 :             :         }
     975                 :             :     }
     976                 :             : 
     977                 :           9 :     file->numFiles = numFiles;
     978                 :             : 
     979                 :             :     /*
     980                 :             :      * If the truncate point is within existing buffer then we can just adjust
     981                 :             :      * pos within buffer.
     982                 :             :      */
     983         [ +  - ]:           9 :     if (newFile == file->curFile &&
     984         [ +  - ]:           9 :         newOffset >= file->curOffset &&
     985         [ -  + ]:           9 :         newOffset <= file->curOffset + file->nbytes)
     986                 :             :     {
     987                 :             :         /* No need to reset the current pos if the new pos is greater. */
     988         [ #  # ]:           0 :         if (newOffset <= file->curOffset + file->pos)
     989                 :           0 :             file->pos = (int64) newOffset - file->curOffset;
     990                 :             : 
     991                 :             :         /* Adjust the nbytes for the current buffer. */
     992                 :           0 :         file->nbytes = (int64) newOffset - file->curOffset;
     993                 :             :     }
     994         [ +  - ]:           9 :     else if (newFile == file->curFile &&
     995         [ -  + ]:           9 :              newOffset < file->curOffset)
     996                 :             :     {
     997                 :             :         /*
     998                 :             :          * The truncate point is within the existing file but prior to the
     999                 :             :          * current position, so we can forget the current buffer and reset the
    1000                 :             :          * current position.
    1001                 :             :          */
    1002                 :           0 :         file->curOffset = newOffset;
    1003                 :           0 :         file->pos = 0;
    1004                 :           0 :         file->nbytes = 0;
    1005                 :             :     }
    1006         [ -  + ]:           9 :     else if (newFile < file->curFile)
    1007                 :             :     {
    1008                 :             :         /*
    1009                 :             :          * The truncate point is prior to the current file, so need to reset
    1010                 :             :          * the current position accordingly.
    1011                 :             :          */
    1012                 :           0 :         file->curFile = newFile;
    1013                 :           0 :         file->curOffset = newOffset;
    1014                 :           0 :         file->pos = 0;
    1015                 :           0 :         file->nbytes = 0;
    1016                 :             :     }
    1017                 :             :     /* Nothing to do, if the truncate point is beyond current file. */
    1018                 :           9 : }
        

Generated by: LCOV version 2.0-1