]> granicus.if.org Git - postgresql/blob - src/backend/storage/file/buffile.c
BufFileSeek's behavior at segment boundaries wasn't what
[postgresql] / src / backend / storage / file / buffile.c
1 /*-------------------------------------------------------------------------
2  *
3  * buffile.c
4  *        Management of large buffered files, primarily temporary files.
5  *
6  * Copyright (c) 1994, Regents of the University of California
7  *
8  * IDENTIFICATION
9  *        $Header: /cvsroot/pgsql/src/backend/storage/file/buffile.c,v 1.3 1999/10/19 02:34:45 tgl Exp $
10  *
11  * NOTES:
12  *
13  * BufFiles provide a very incomplete emulation of stdio atop virtual Files
14  * (as managed by fd.c).  Currently, we only support the buffered-I/O
15  * aspect of stdio: a read or write of the low-level File occurs only
16  * when the buffer is filled or emptied.  This is an even bigger win
17  * for virtual Files than for ordinary kernel files, since reducing the
18  * frequency with which a virtual File is touched reduces "thrashing"
19  * of opening/closing file descriptors.
20  *
21  * Note that BufFile structs are allocated with palloc(), and therefore
22  * will go away automatically at transaction end.  If the underlying
23  * virtual File is made with OpenTemporaryFile, then all resources for
24  * the file are certain to be cleaned up even if processing is aborted
25  * by elog(ERROR).  To avoid confusion, the caller should take care that
26  * all calls for a single BufFile are made in the same palloc context.
27  *
28  * BufFile also supports temporary files that exceed the OS file size limit
29  * (by opening multiple fd.c temporary files).  This is an essential feature
30  * for sorts and hashjoins on large amounts of data.
31  *-------------------------------------------------------------------------
32  */
33
34 #include <errno.h>
35
36 #include "postgres.h"
37
38 #include "storage/buffile.h"
39
40 /*
41  * The maximum safe file size is presumed to be RELSEG_SIZE * BLCKSZ.
42  * Note we adhere to this limit whether or not LET_OS_MANAGE_FILESIZE
43  * is defined, although md.c ignores it when that symbol is defined.
44  */
45 #define MAX_PHYSICAL_FILESIZE  (RELSEG_SIZE * BLCKSZ)
46
47 /*
48  * This data structure represents a buffered file that consists of one or
49  * more physical files (each accessed through a virtual file descriptor
50  * managed by fd.c).
51  */
52 struct BufFile
53 {
54         int                     numFiles;               /* number of physical files in set */
55         /* all files except the last have length exactly MAX_PHYSICAL_FILESIZE */
56         File       *files;                      /* palloc'd array with numFiles entries */
57         long       *offsets;            /* palloc'd array with numFiles entries */
58         /* offsets[i] is the current seek position of files[i].  We use this
59          * to avoid making redundant FileSeek calls.
60          */
61
62         bool            isTemp;                 /* can only add files if this is TRUE */
63         bool            dirty;                  /* does buffer need to be written? */
64         /*
65          * "current pos" is position of start of buffer within the logical file.
66          * Position as seen by user of BufFile is (curFile, curOffset + pos).
67          */
68         int                     curFile;                /* file index (0..n) part of current pos */
69         int                     curOffset;              /* offset part of current pos */
70         int                     pos;                    /* next read/write position in buffer */
71         int                     nbytes;                 /* total # of valid bytes in buffer */
72         char            buffer[BLCKSZ];
73 };
74
75 static BufFile *makeBufFile(File firstfile);
76 static void extendBufFile(BufFile *file);
77 static void BufFileLoadBuffer(BufFile *file);
78 static void BufFileDumpBuffer(BufFile *file);
79 static int      BufFileFlush(BufFile *file);
80
81
82 /*
83  * Create a BufFile given the first underlying physical file.
84  * NOTE: caller must set isTemp true if appropriate.
85  */
86 static BufFile *
87 makeBufFile(File firstfile)
88 {
89         BufFile    *file = (BufFile *) palloc(sizeof(BufFile));
90
91         file->numFiles = 1;
92         file->files = (File *) palloc(sizeof(File));
93         file->files[0] = firstfile;
94         file->offsets = (long *) palloc(sizeof(long));
95         file->offsets[0] = 0L;
96         file->isTemp = false;
97         file->dirty = false;
98         file->curFile = 0;
99         file->curOffset = 0L;
100         file->pos = 0;
101         file->nbytes = 0;
102
103         return file;
104 }
105
106 /*
107  * Add another component temp file.
108  */
109 static void
110 extendBufFile(BufFile *file)
111 {
112         File            pfile;
113
114         Assert(file->isTemp);
115         pfile = OpenTemporaryFile();
116         Assert(pfile >= 0);
117
118         file->files = (File *) repalloc(file->files,
119                                                                         (file->numFiles+1) * sizeof(File));
120         file->offsets = (long *) repalloc(file->offsets,
121                                                                           (file->numFiles+1) * sizeof(long));
122         file->files[file->numFiles] = pfile;
123         file->offsets[file->numFiles] = 0L;
124         file->numFiles++;
125 }
126
127 /*
128  * Create a BufFile for a new temporary file (which will expand to become
129  * multiple temporary files if more than MAX_PHYSICAL_FILESIZE bytes are
130  * written to it).
131  */
132 BufFile *
133 BufFileCreateTemp(void)
134 {
135         BufFile    *file;
136         File            pfile;
137
138         pfile = OpenTemporaryFile();
139         Assert(pfile >= 0);
140
141         file = makeBufFile(pfile);
142         file->isTemp = true;
143
144         return file;
145 }
146
147 /*
148  * Create a BufFile and attach it to an already-opened virtual File.
149  *
150  * This is comparable to fdopen() in stdio.  This is the only way at present
151  * to attach a BufFile to a non-temporary file.  Note that BufFiles created
152  * in this way CANNOT be expanded into multiple files.
153  */
154 BufFile *
155 BufFileCreate(File file)
156 {
157         return makeBufFile(file);
158 }
159
160 /*
161  * Close a BufFile
162  *
163  * Like fclose(), this also implicitly FileCloses the underlying File.
164  */
165 void
166 BufFileClose(BufFile *file)
167 {
168         int             i;
169
170         /* flush any unwritten data */
171         BufFileFlush(file);
172         /* close the underlying file(s) (with delete if it's a temp file) */
173         for (i = 0; i < file->numFiles; i++)
174                 FileClose(file->files[i]);
175         /* release the buffer space */
176         pfree(file->files);
177         pfree(file->offsets);
178         pfree(file);
179 }
180
181 /*
182  * BufFileLoadBuffer
183  *
184  * Load some data into buffer, if possible, starting from curOffset.
185  * At call, must have dirty = false, pos and nbytes = 0.
186  * On exit, nbytes is number of bytes loaded.
187  */
188 static void
189 BufFileLoadBuffer(BufFile *file)
190 {
191         File    thisfile;
192
193         /*
194          * Advance to next component file if necessary and possible.
195          *
196          * This path can only be taken if there is more than one component,
197          * so it won't interfere with reading a non-temp file that is over
198          * MAX_PHYSICAL_FILESIZE.
199          */
200         if (file->curOffset >= MAX_PHYSICAL_FILESIZE &&
201                 file->curFile+1 < file->numFiles)
202         {
203                 file->curFile++;
204                 file->curOffset = 0L;
205         }
206         /*
207          * May need to reposition physical file.
208          */
209         thisfile = file->files[file->curFile];
210         if (file->curOffset != file->offsets[file->curFile])
211         {
212                 if (FileSeek(thisfile, file->curOffset, SEEK_SET) != file->curOffset)
213                         return;                         /* seek failed, read nothing */
214                 file->offsets[file->curFile] = file->curOffset;
215         }
216         /*
217          * Read whatever we can get, up to a full bufferload.
218          */
219         file->nbytes = FileRead(thisfile, file->buffer, sizeof(file->buffer));
220         if (file->nbytes < 0)
221                 file->nbytes = 0;
222         file->offsets[file->curFile] += file->nbytes;
223         /* we choose not to advance curOffset here */
224 }
225
226 /*
227  * BufFileDumpBuffer
228  *
229  * Dump buffer contents starting at curOffset.
230  * At call, should have dirty = true, nbytes > 0.
231  * On exit, dirty is cleared if successful write, and curOffset is advanced.
232  */
233 static void
234 BufFileDumpBuffer(BufFile *file)
235 {
236         int                     wpos = 0;
237         int                     bytestowrite;
238         File            thisfile;
239
240         /*
241          * Unlike BufFileLoadBuffer, we must dump the whole buffer even if
242          * it crosses a component-file boundary; so we need a loop.
243          */
244         while (wpos < file->nbytes)
245         {
246                 /*
247                  * Advance to next component file if necessary and possible.
248                  */
249                 if (file->curOffset >= MAX_PHYSICAL_FILESIZE && file->isTemp)
250                 {
251                         while (file->curFile+1 >= file->numFiles)
252                                 extendBufFile(file);
253                         file->curFile++;
254                         file->curOffset = 0L;
255                 }
256                 /*
257                  * Enforce per-file size limit only for temp files, else just try
258                  * to write as much as asked...
259                  */
260                 bytestowrite = file->nbytes - wpos;
261                 if (file->isTemp)
262                 {
263                         long    availbytes = MAX_PHYSICAL_FILESIZE - file->curOffset;
264
265                         if ((long) bytestowrite > availbytes)
266                                 bytestowrite = (int) availbytes;
267                 }
268                 /*
269                  * May need to reposition physical file.
270                  */
271                 thisfile = file->files[file->curFile];
272                 if (file->curOffset != file->offsets[file->curFile])
273                 {
274                         if (FileSeek(thisfile, file->curOffset, SEEK_SET) != file->curOffset)
275                                 return;                 /* seek failed, give up */
276                         file->offsets[file->curFile] = file->curOffset;
277                 }
278                 bytestowrite = FileWrite(thisfile, file->buffer, bytestowrite);
279                 if (bytestowrite <= 0)
280                         return;                         /* failed to write */
281                 file->offsets[file->curFile] += bytestowrite;
282                 file->curOffset += bytestowrite;
283                 wpos += bytestowrite;
284         }
285         file->dirty = false;
286         /*
287          * At this point, curOffset has been advanced to the end of the buffer,
288          * ie, its original value + nbytes.  We need to make it point to the
289          * logical file position, ie, original value + pos, in case that is less
290          * (as could happen due to a small backwards seek in a dirty buffer!)
291          */
292         file->curOffset -= (file->nbytes - file->pos);
293         if (file->curOffset < 0)        /* handle possible segment crossing */
294         {
295                 file->curFile--;
296                 Assert(file->curFile >= 0);
297                 file->curOffset += MAX_PHYSICAL_FILESIZE;
298         }
299         /* Now we can set the buffer empty without changing the logical position */
300         file->pos = 0;
301         file->nbytes = 0;
302 }
303
304 /*
305  * BufFileRead
306  *
307  * Like fread() except we assume 1-byte element size.
308  */
309 size_t
310 BufFileRead(BufFile *file, void *ptr, size_t size)
311 {
312         size_t          nread = 0;
313         size_t          nthistime;
314
315         if (file->dirty)
316         {
317                 if (BufFileFlush(file) != 0)
318                         return 0;                       /* could not flush... */
319                 Assert(! file->dirty);
320         }
321
322         while (size > 0)
323         {
324                 if (file->pos >= file->nbytes)
325                 {
326                         /* Try to load more data into buffer. */
327                         file->curOffset += file->pos;
328                         file->pos = 0;
329                         file->nbytes = 0;
330                         BufFileLoadBuffer(file);
331                         if (file->nbytes <= 0)
332                                 break;                  /* no more data available */
333                 }
334
335                 nthistime = file->nbytes - file->pos;
336                 if (nthistime > size)
337                         nthistime = size;
338                 Assert(nthistime > 0);
339
340                 memcpy(ptr, file->buffer + file->pos, nthistime);
341
342                 file->pos += nthistime;
343                 ptr = (void *) ((char *) ptr + nthistime);
344                 size -= nthistime;
345                 nread += nthistime;
346         }
347
348         return nread;
349 }
350
351 /*
352  * BufFileWrite
353  *
354  * Like fwrite() except we assume 1-byte element size.
355  */
356 size_t
357 BufFileWrite(BufFile *file, void *ptr, size_t size)
358 {
359         size_t          nwritten = 0;
360         size_t          nthistime;
361
362         while (size > 0)
363         {
364                 if (file->pos >= BLCKSZ)
365                 {
366                         /* Buffer full, dump it out */
367                         if (file->dirty)
368                         {
369                                 BufFileDumpBuffer(file);
370                                 if (file->dirty)
371                                         break;          /* I/O error */
372                         }
373                         else
374                         {
375                                 /* Hmm, went directly from reading to writing? */
376                                 file->curOffset += file->pos;
377                                 file->pos = 0;
378                                 file->nbytes = 0;
379                         }
380                 }
381
382                 nthistime = BLCKSZ - file->pos;
383                 if (nthistime > size)
384                         nthistime = size;
385                 Assert(nthistime > 0);
386
387                 memcpy(file->buffer + file->pos, ptr, nthistime);
388
389                 file->dirty = true;
390                 file->pos += nthistime;
391                 if (file->nbytes < file->pos)
392                         file->nbytes = file->pos;
393                 ptr = (void *) ((char *) ptr + nthistime);
394                 size -= nthistime;
395                 nwritten += nthistime;
396         }
397
398         return nwritten;
399 }
400
401 /*
402  * BufFileFlush
403  *
404  * Like fflush()
405  */
406 static int
407 BufFileFlush(BufFile *file)
408 {
409         if (file->dirty)
410         {
411                 BufFileDumpBuffer(file);
412                 if (file->dirty)
413                         return EOF;
414         }
415
416         return 0;
417 }
418
419 /*
420  * BufFileSeek
421  *
422  * Like fseek(), except that target position needs two values in order to
423  * work when logical filesize exceeds maximum value representable by long.
424  * We do not support relative seeks across more than LONG_MAX, however.
425  *
426  * Result is 0 if OK, EOF if not.  Logical position is not moved if an
427  * impossible seek is attempted.
428  */
429 int
430 BufFileSeek(BufFile *file, int fileno, long offset, int whence)
431 {
432         int newFile;
433         long newOffset;
434         switch (whence)
435         {
436                 case SEEK_SET:
437                         if (fileno < 0)
438                                 return EOF;
439                         newFile = fileno;
440                         newOffset = offset;
441                         break;
442                 case SEEK_CUR:
443                         /*
444                          * Relative seek considers only the signed offset, ignoring fileno.
445                          * Note that large offsets (> 1 gig) risk overflow in this add...
446                          */
447                         newFile = file->curFile;
448                         newOffset = (file->curOffset + file->pos) + offset;
449                         break;
450 #ifdef NOT_USED
451                 case SEEK_END:
452                         /* could be implemented, not needed currently */
453                         break;
454 #endif
455                 default:
456                         elog(ERROR, "BufFileSeek: invalid whence: %d", whence);
457                         return EOF;
458         }
459         while (newOffset < 0)
460         {
461                 if (--newFile < 0)
462                         return EOF;
463                 newOffset += MAX_PHYSICAL_FILESIZE;
464         }
465         if (newFile == file->curFile &&
466                 newOffset >= file->curOffset &&
467                 newOffset <= file->curOffset + file->nbytes)
468         {
469                 /*
470                  * Seek is to a point within existing buffer; we can just adjust
471                  * pos-within-buffer, without flushing buffer.  Note this is OK
472                  * whether reading or writing, but buffer remains dirty if we
473                  * were writing.
474                  */
475                 file->pos = (int) (newOffset - file->curOffset);
476                 return 0;
477         }
478         /* Otherwise, must reposition buffer, so flush any dirty data */
479         if (BufFileFlush(file) != 0)
480                 return EOF;
481         /*
482          * At this point and no sooner, check for seek past last segment.
483          * The above flush could have created a new segment, so
484          * checking sooner would not work (at least not with this code).
485          */
486         if (file->isTemp)
487         {
488                 /* convert seek to "start of next seg" to "end of last seg" */
489                 if (newFile == file->numFiles && newOffset == 0)
490                 {
491                         newFile--;
492                         newOffset = MAX_PHYSICAL_FILESIZE;
493                 }
494                 while (newOffset > MAX_PHYSICAL_FILESIZE)
495                 {
496                         if (++newFile >= file->numFiles)
497                                 return EOF;
498                         newOffset -= MAX_PHYSICAL_FILESIZE;
499                 }
500         }
501         if (newFile >= file->numFiles)
502                 return EOF;
503         /* Seek is OK! */
504         file->curFile = newFile;
505         file->curOffset = newOffset;
506         file->pos = 0;
507         file->nbytes = 0;
508         return 0;
509 }
510
511 void
512 BufFileTell(BufFile *file, int *fileno, long *offset)
513 {
514         *fileno = file->curFile;
515         *offset = file->curOffset + file->pos;
516 }
517
518 /*
519  * BufFileSeekBlock --- block-oriented seek
520  *
521  * Performs absolute seek to the start of the n'th BLCKSZ-sized block of
522  * the file.  Note that users of this interface will fail if their files
523  * exceed BLCKSZ * LONG_MAX bytes, but that is quite a lot; we don't work
524  * with tables bigger than that, either...
525  *
526  * Result is 0 if OK, EOF if not.  Logical position is not moved if an
527  * impossible seek is attempted.
528  */
529 int
530 BufFileSeekBlock(BufFile *file, long blknum)
531 {
532         return BufFileSeek(file,
533                                            (int) (blknum / RELSEG_SIZE),
534                                            (blknum % RELSEG_SIZE) * BLCKSZ,
535                                            SEEK_SET);
536 }
537
538 /*
539  * BufFileTellBlock --- block-oriented tell
540  *
541  * Any fractional part of a block in the current seek position is ignored.
542  */
543 long
544 BufFileTellBlock(BufFile *file)
545 {
546         long    blknum;
547
548         blknum = (file->curOffset + file->pos) / BLCKSZ;
549         blknum += file->curFile * RELSEG_SIZE;
550         return blknum;
551 }