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