diff --git a/ext/misc/fileio.c b/ext/misc/fileio.c index eafade010a..9aad27e0c4 100644 --- a/ext/misc/fileio.c +++ b/ext/misc/fileio.c @@ -221,8 +221,10 @@ static int fileio_mkdir(const char *zPath){ */ static void readFileContents(sqlite3_context *ctx, const char *zName){ FILE *in; - sqlite3_int64 nIn; - void *pBuf; + sqlite3_int64 nIn; /* Size of the file according to ftell() */ + sqlite3_int64 nAlloc; /* Bytes allocated for pBuf */ + sqlite3_int64 nRead = 0; /* Bytes read so far */ + unsigned char *pBuf; sqlite3 *db; int mxBlob; @@ -231,9 +233,12 @@ static void readFileContents(sqlite3_context *ctx, const char *zName){ /* File does not exist or is unreadable. Leave the result set to NULL. */ return; } + /* The size reported by ftell() is only a hint. It is -1 for pipes and + ** FIFOs and 0 for many files under /proc. Keep reading until EOF. */ fseek(in, 0, SEEK_END); nIn = ftell(in); rewind(in); + if( nIn<0 ) nIn = 0; db = sqlite3_context_db_handle(ctx); mxBlob = sqlite3_limit(db, SQLITE_LIMIT_LENGTH, -1); if( nIn>mxBlob ){ @@ -241,17 +246,41 @@ static void readFileContents(sqlite3_context *ctx, const char *zName){ fclose(in); return; } - pBuf = sqlite3_malloc64( nIn ? nIn : 1 ); + /* One extra byte so that EOF on a regular file is seen without a + ** reallocation. */ + nAlloc = nIn+1; + pBuf = sqlite3_malloc64( nAlloc ); if( pBuf==0 ){ sqlite3_result_error_nomem(ctx); fclose(in); return; } - if( nIn==(sqlite3_int64)fread(pBuf, 1, (size_t)nIn, in) ){ - sqlite3_result_blob64(ctx, pBuf, nIn, sqlite3_free); - }else{ + while( 1 ){ + unsigned char *pNew; + nRead += fread(pBuf+nRead, 1, (size_t)(nAlloc-nRead), in); + if( nRead>mxBlob ){ + sqlite3_result_error_code(ctx, SQLITE_TOOBIG); + sqlite3_free(pBuf); + fclose(in); + return; + } + if( nRead(sqlite3_int64)mxBlob+1 ) nAlloc = (sqlite3_int64)mxBlob+1; + pNew = sqlite3_realloc64(pBuf, nAlloc); + if( pNew==0 ){ + sqlite3_result_error_nomem(ctx); + sqlite3_free(pBuf); + fclose(in); + return; + } + pBuf = pNew; + } + if( ferror(in) ){ sqlite3_result_error_code(ctx, SQLITE_IOERR); sqlite3_free(pBuf); + }else{ + sqlite3_result_blob64(ctx, pBuf, nRead, sqlite3_free); } fclose(in); } diff --git a/manifest b/manifest index 6ebb1c935e..56d9963e2c 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Remove\sa\sdebugging\s".mode\s-v"\sin\sthe\sdump.sql\stest\sscript\sthat\swas\naccidentally\sleft\sin\sthe\sprevious\scheck-in. -D 2026-10-20T17:42:52.465 +C Enhance\sthe\sreadfile()\sfunction\sin\sthe\sfileio.c\sextension\sso\sthat\sit\nis\sable\sto\sread\sfrom\spipes.\n[bugs:/info/2026-10-20T14:02:37Z|Enhancement\srequest\s2026-10-20T14:02:37Z] +D 2026-10-20T17:46:19.164 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea @@ -379,7 +379,7 @@ F ext/misc/decimal.c 68e987966e4772efdb8df03e2bf253d02bae3254af29fd4a99d34fc6e6f F ext/misc/diskused.c 444139c01398df6f73e8a021c91e24d97d7914f5c1b49f55dd503abbca347c6e F ext/misc/eval.c d4ea2d9898fbe4d30d38ff675ee51958d3bb0601a145bade56dee8b8f438cc5b F ext/misc/explain.c 04c9270fd8cf93a9bceb12d2f5f67e3f09a4e58b3e0efe14d24531444d2fffdf -F ext/misc/fileio.c f32d7488e229cc52283b6f8d9370b004aa92e7a3054aef320cbdbdecb08c4ee5 +F ext/misc/fileio.c 6bf2543ae190401f5fda74661c20387a450071eb5d17bfc05d3b45416bbedcb7 F ext/misc/fossildelta.c 0af6a5ef3db6bba299271c29b604489e4a5d9e75395c6b83460405a9ccf05c2a F ext/misc/fuzzer.c 32cef9a29864a1bc1329ccb6dfa613f197bb83491a7612c2ca2c83ee26cd8f14 F ext/misc/ieee754.c 2901d08a586d00a1d3c0fd89e03c57ee9e2b5f013b0daab9e49c7a48a9d5946b @@ -1189,7 +1189,7 @@ F test/exprfault.test 7475e363c7221847855722e9dd2459cf60088f6b172c0c808dba75f89e F test/exprfault2.test c49e84273898969af5dbc4fe6a3f4335f14639799f343590336c9ddf84425965 F test/expridx1.test b464520126e1d781a7800f8540621c82e8bbc526f089ff8b0b57ffc51feea6b7 F test/expridx2.test 13348ed208a5d18d9cef7d0a70f8e17948d3c0b5b46508c0573b7e983456165f -F test/extension01.test 5de412c66276105901c370770175003381fdcb0c4da7054fa43cf4a31e0bfa3a +F test/extension01.test abdb32528871c4daa445bc5a18e75873cd1257dae05af2703838b744b9913534 F test/external_reader.test 6fdec43eeca23eb32faad1e95a4d1abc402bc8b3db70df12d6fc08a637f4a2b5 F test/extraquick.test cb254400bd42bfb777ff675356aabf3287978f79 F test/fallocate.test 37a62e396a68eeede8f8d2ecf23573a80faceb630788d314d0a073d862616717 @@ -2295,8 +2295,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c -P ecd172bb44be40841336cf2ec42f2505c4f98a96eace3e4862e038a3c147770d -R 15e9f2d7ecdc986aac547ffba1bf6e5b +P 632943da2d3e58696ec0a7548e18a99a57a3dd51f700f24144377a6d8fdb8c06 +R 975aef2d01879a69ec2ec2c8d7f2e5c6 U drh -Z 5f956cdbddb3debc5fab777fad1cc812 +Z c01cc06719b3a1f0fd241b4960900888 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 167083b883..c9f12e7d5c 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -632943da2d3e58696ec0a7548e18a99a57a3dd51f700f24144377a6d8fdb8c06 +49d185f2ac2d01c6b1bd9c20d13628eff3803529faaf969bace3981f6acc7a28 diff --git a/test/extension01.test b/test/extension01.test index ba8a44edb5..4e648ef4f5 100644 --- a/test/extension01.test +++ b/test/extension01.test @@ -80,4 +80,33 @@ do_test 1.7 { } } {nil} +# readfile() must read until EOF rather than trusting the size reported +# by ftell(). That size is -1 for a pipe or FIFO and 0 for many files +# under /proc. +# +forcedelete fifo1 +if {$::tcl_platform(platform)=="unix" && ![catch {exec mkfifo fifo1}]} { + do_test 2.1 { + exec sh -c {printf hello >fifo1} & + db eval { SELECT CAST(readfile('fifo1') AS TEXT) } + } {hello} + do_test 2.2 { + exec sh -c {head -c 100000 /dev/zero >fifo1} & + db eval { SELECT length(readfile('fifo1')) } + } {100000} + do_test 2.3 { + set mx [sqlite3_limit db SQLITE_LIMIT_LENGTH 1000] + exec sh -c {head -c 100000 /dev/zero >fifo1 2>/dev/null} & + set res [catchsql { SELECT length(readfile('fifo1')) }] + sqlite3_limit db SQLITE_LIMIT_LENGTH $mx + set res + } {1 {string or blob too big}} + forcedelete fifo1 +} +if {[file readable /proc/self/status]} { + do_execsql_test 2.4 { + SELECT length(readfile('/proc/self/status'))>0 + } {1} +} + finish_test