Skip to content

Commit 015e899

Browse files
committed
Read until EOF vice stat-reported size in read_binary_file
read_binary_file(), used by SQL functions pg_read_file() and friends, uses stat to determine file length to read, when not passed an explicit length as an argument. This is problematic, for example, if the file being read is a virtual file with a stat-reported length of zero. Arrange to read until EOF, or StringInfo data string lenth limit, is reached instead. Original complaint and patch by me, with significant review, corrections, advice, and code optimizations by Tom Lane. Backpatched to v11. Prior to that only paths relative to the data and log dirs were allowed for files, so no "zero length" files were reachable anyway. Reviewed-By: Tom Lane Discussion: https://postgr.es/m/flat/969b8d82-5bb2-5fa8-4eb1-f0e685c5d736%40joeconway.com Backpatch-through: 11
1 parent 153c14c commit 015e899

File tree

2 files changed

+66
-29
lines changed

2 files changed

+66
-29
lines changed

contrib/adminpack/expected/adminpack.out

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ SELECT pg_file_rename('test_file1', 'test_file2');
6464
(1 row)
6565

6666
SELECT pg_read_file('test_file1'); -- not there
67-
ERROR: could not stat file "test_file1": No such file or directory
67+
ERROR: could not open file "test_file1" for reading: No such file or directory
6868
SELECT pg_read_file('test_file2');
6969
pg_read_file
7070
--------------
@@ -93,7 +93,7 @@ SELECT pg_file_rename('test_file2', 'test_file3', 'test_file3_archive');
9393
(1 row)
9494

9595
SELECT pg_read_file('test_file2'); -- not there
96-
ERROR: could not stat file "test_file2": No such file or directory
96+
ERROR: could not open file "test_file2" for reading: No such file or directory
9797
SELECT pg_read_file('test_file3');
9898
pg_read_file
9999
--------------

src/backend/utils/adt/genfile.c

Lines changed: 64 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -105,33 +105,11 @@ read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read,
105105
bool missing_ok)
106106
{
107107
bytea *buf;
108-
size_t nbytes;
108+
size_t nbytes = 0;
109109
FILE *file;
110110

111-
if (bytes_to_read < 0)
112-
{
113-
if (seek_offset < 0)
114-
bytes_to_read = -seek_offset;
115-
else
116-
{
117-
struct stat fst;
118-
119-
if (stat(filename, &fst) < 0)
120-
{
121-
if (missing_ok && errno == ENOENT)
122-
return NULL;
123-
else
124-
ereport(ERROR,
125-
(errcode_for_file_access(),
126-
errmsg("could not stat file \"%s\": %m", filename)));
127-
}
128-
129-
bytes_to_read = fst.st_size - seek_offset;
130-
}
131-
}
132-
133-
/* not sure why anyone thought that int64 length was a good idea */
134-
if (bytes_to_read > (MaxAllocSize - VARHDRSZ))
111+
/* clamp request size to what we can actually deliver */
112+
if (bytes_to_read > (int64) (MaxAllocSize - VARHDRSZ))
135113
ereport(ERROR,
136114
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
137115
errmsg("requested length too large")));
@@ -153,9 +131,68 @@ read_binary_file(const char *filename, int64 seek_offset, int64 bytes_to_read,
153131
(errcode_for_file_access(),
154132
errmsg("could not seek in file \"%s\": %m", filename)));
155133

156-
buf = (bytea *) palloc((Size) bytes_to_read + VARHDRSZ);
134+
if (bytes_to_read >= 0)
135+
{
136+
/* If passed explicit read size just do it */
137+
buf = (bytea *) palloc((Size) bytes_to_read + VARHDRSZ);
138+
139+
nbytes = fread(VARDATA(buf), 1, (size_t) bytes_to_read, file);
140+
}
141+
else
142+
{
143+
/* Negative read size, read rest of file */
144+
StringInfoData sbuf;
145+
146+
initStringInfo(&sbuf);
147+
/* Leave room in the buffer for the varlena length word */
148+
sbuf.len += VARHDRSZ;
149+
Assert(sbuf.len < sbuf.maxlen);
150+
151+
while (!(feof(file) || ferror(file)))
152+
{
153+
size_t rbytes;
154+
155+
/* Minimum amount to read at a time */
156+
#define MIN_READ_SIZE 4096
157+
158+
/*
159+
* If not at end of file, and sbuf.len is equal to
160+
* MaxAllocSize - 1, then either the file is too large, or
161+
* there is nothing left to read. Attempt to read one more
162+
* byte to see if the end of file has been reached. If not,
163+
* the file is too large; we'd rather give the error message
164+
* for that ourselves.
165+
*/
166+
if (sbuf.len == MaxAllocSize - 1)
167+
{
168+
char rbuf[1];
157169

158-
nbytes = fread(VARDATA(buf), 1, (size_t) bytes_to_read, file);
170+
fread(rbuf, 1, 1, file);
171+
if (!feof(file))
172+
ereport(ERROR,
173+
(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
174+
errmsg("file length too large")));
175+
else
176+
break;
177+
}
178+
179+
/* OK, ensure that we can read at least MIN_READ_SIZE */
180+
enlargeStringInfo(&sbuf, MIN_READ_SIZE);
181+
182+
/*
183+
* stringinfo.c likes to allocate in powers of 2, so it's likely
184+
* that much more space is available than we asked for. Use all
185+
* of it, rather than making more fread calls than necessary.
186+
*/
187+
rbytes = fread(sbuf.data + sbuf.len, 1,
188+
(size_t) (sbuf.maxlen - sbuf.len - 1), file);
189+
sbuf.len += rbytes;
190+
nbytes += rbytes;
191+
}
192+
193+
/* Now we can commandeer the stringinfo's buffer as the result */
194+
buf = (bytea *) sbuf.data;
195+
}
159196

160197
if (ferror(file))
161198
ereport(ERROR,

0 commit comments

Comments
 (0)