]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Improvements to temp-file cleanup on close in the CLI.
authordrh <>
Sun, 26 Apr 2026 18:35:26 +0000 (18:35 +0000)
committerdrh <>
Sun, 26 Apr 2026 18:35:26 +0000 (18:35 +0000)
FossilOrigin-Name: 048c969c34eaaf8c203b996e999a7dbc94c47b4959719af9e6625052520f7135

manifest
manifest.uuid
src/shell.c.in

index 63c8de67089fbf273fe3e323a59ca7c1b53d0146..121cc8a1eac714fd01ce34e6fd341d9ed524bd8c 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C In\sthe\sCLI,\seliminate\sthe\s2-second\sdelay\sbefore\sgetting\sthe\sprompt\sback\nafter\srunning\sa\s".www"\sor\s".excel".\s\sInstead,\sremember\sthe\sname\sof\sthe\ntemporary\sfile\sthat\sis\sto\sbe\sdeleted\sand\sdelete\sit\slater.
-D 2026-04-25T16:17:23.297
+C Improvements\sto\stemp-file\scleanup\son\sclose\sin\sthe\sCLI.
+D 2026-04-26T18:35:26.126
 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -736,7 +736,7 @@ F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
 F src/resolve.c 928ff887f2a7c64275182060d94d06fdddbe32226c569781cf7e7edc6f58d7fd
 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
 F src/select.c 4c05cde130f26991b7411d8c6809e0630625e18078742c963a047b4b9cc01d49
-F src/shell.c.in 0c22dd2c11ff87d4904d990dba632940102012d55a9d221940958ad6afe09506
+F src/shell.c.in 6bf45ed0430cc23686697a6c1fdd756247f3e859d0140454afb884c6a775456d
 F src/sqlite.h.in 39d2e09114d2bdb7afd998f4a469c8f8cd065f8093835a7d0422f260fc78fb4f
 F src/sqlite3.rc 015537e6ac1eec6c7050e17b616c2ffe6f70fca241835a84a4f0d5937383c479
 F src/sqlite3ext.h 9788c301f95370fa30e808861f1d2e6f022a816ddbe2a4f67486784c1b31db2e
@@ -2203,8 +2203,8 @@ F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee
 F tool/warnings.sh a554d13f6e5cf3760f041b87939e3d616ec6961859c3245e8ef701d1eafc2ca2
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
 F tool/winmain.c 00c8fb88e365c9017db14c73d3c78af62194d9644feaf60e220ab0f411f3604c
-P 92cad2cd7284d3a969b713336865007da2b0b2a8eef367d4185802174cbd027a
-R d17ad9dd57a963733572b44ccb3c359d
+P 4e9ee211ca661fe8978b7f676f33ef962ff5c8177bc7c5ad42b19a2fa4482d90
+R 3dad6c18c2c8aca0242ada41edaef876
 U drh
-Z 936ee9dc91c7afce31c375f047aeb7da
+Z d5ab748eb3952f1319907fdc974e52fb
 # Remove this line to create a well-formed Fossil manifest.
index b2e522f26496669e92c1f45a365ad70a222701a2..3566ced503e4c01b5e9ee13ea7d21df693b95518 100644 (file)
@@ -1 +1 @@
-4e9ee211ca661fe8978b7f676f33ef962ff5c8177bc7c5ad42b19a2fa4482d90
+048c969c34eaaf8c203b996e999a7dbc94c47b4959719af9e6625052520f7135
index 38e556bcbad8188470f404a28e5b822a22c5808e..117d0262f00ee3aabf18737f6cf90a7a7ce5e08f 100644 (file)
@@ -663,6 +663,13 @@ static int stdout_tty_width = -1;
 */
 static sqlite3 *globalDb = 0;
 
+/*
+** This is a global pointer to the main ShellState variable.  This
+** exists so that the atexit() callback can access the ShellState
+** object to do some cleanup.
+*/
+static struct ShellState *globalShellState = 0;
+
 /*
 ** True if an interrupt (Control-C) has been received.
 */
@@ -1764,6 +1771,7 @@ static void shellExpandPrompt(
   sqlite3_free(zRes);
 }
 
+
 /************************* BEGIN PERFORMANCE TIMER *****************************/
 #if !defined(_WIN32) && !defined(WIN32) && !defined(__minux)
 #include <sys/time.h>
@@ -2530,6 +2538,11 @@ static void interrupt_handler(int NotUsed){
   if( ++seenInterrupt>1 ) cli_exit(1);
   if( globalDb ) sqlite3_interrupt(globalDb);
 }
+/* No-masking interrupts (SIGTERM or SIGHUP) */
+static void sigterm_handler(int NotUsed){
+  UNUSED_PARAMETER(NotUsed);
+  cli_exit(1);
+}
 
 /* Try to determine the screen width.  Use the default if unable.
 */
@@ -4850,6 +4863,9 @@ static void shellModuleSchema(
 #define OPEN_DB_KEEPALIVE   0x001   /* Return after error if true */
 #define OPEN_DB_ZIPFILE     0x002   /* Open as ZIP if name matches *.zip */
 
+/* Forward reference */
+static void shellTempFilenameFunc(sqlite3_context*,int,sqlite3_value**);
+
 /*
 ** Make sure the database is open.  If it is not, then open it.  If
 ** the database fails to open, print an error message and exit.
@@ -5022,6 +5038,8 @@ static void open_db(ShellState *p, int openFlags){
                             p, shellExpandPrompt, 0, 0);
     sqlite3_create_function(p->db, "shell_prompt_test", 3, SQLITE_UTF8,
                             p, shellExpandPrompt, 0, 0);
+    sqlite3_create_function(p->db, "shell_temp_filename", 1, SQLITE_UTF8,
+                            p, shellTempFilenameFunc, 0, 0);
 
 
     if( p->openMode==SHELL_OPEN_ZIPFILE ){
@@ -6258,7 +6276,9 @@ static char *shellFilenameFromUri(const char *zFN){
 **
 ** If unsuccessful on the first attempt and if pRetry!=NULL and pRetry[0]
 ** is positive, then delay for pRetry[0] milliseconds and try again.  On
-** a retry, pRetry[0] is set to zero.
+** a retry, pRetry[0] is set to zero.  Unsuccessful unlinks usually only
+** happen on Windows.  It is possible (in theory) for unlink() to fail
+** on Unix, but it rarely happens.
 **
 ** Return 0 on success and non-zero if unable.
 */
@@ -6268,9 +6288,11 @@ int shellDeleteFile(const char *zFilename, int *pRetry){
 #ifdef _WIN32
     wchar_t *z = sqlite3_win32_utf8_to_unicode(zFilename);
     rc = _wunlink(z);
+    if( rc && _waccess(z,0)!=0 ) rc = 0;
     sqlite3_free(z);
 #else
     rc = unlink(zFilename);
+    if( rc && access(zFilename, 0)!=0 ) rc = 0;
 #endif
     if( rc && pRetry && pRetry[0] ){
       sqlite3_sleep(pRetry[0]);
@@ -6283,27 +6305,43 @@ int shellDeleteFile(const char *zFilename, int *pRetry){
 }
 
 /*
-** Try to delete the temporary file (if there is one) with the name
-** p->zTempFile and free the memory used to hold the name of the temp file.
-** If there are any entries in p->aUnlink[] that are old enough, then
-** delete those too, also reclaiming memory.
+** This routine does two things:
+**
+**    (1)  Delete (unlink) temporary files that are no longer needed.
+**    (2)  Free memory used to hold the names of those temporary files.
 **
-** If retry is non-zero and a deletion attempt is not successful,
-** wait for retry milliseconds and try again before giving up.
-** When retry is non-zero, memory is always reclaimed even if the
-** file deletion attempt fails.
+** Temp filenames are stored in p->zTempName and in the p->aUnlink[]
+** array.  The p->zTempFile is always subject to immediate deletion.  The
+** temp files named in p->aUnlink[] are usually not deleted until after
+** the timestamp stored in the p->aUnlink[].tm field, except if
+** bForce is true, the p->aUnlink[] files will be deleted even if they
+** have not reached their expiration, as long as a delay of at least
+** nDelay milliseconds occurs first.
+**
+** If nDelay is non-zero and a deletion attempt is not successful,
+** wait for nDelay milliseconds and try again before giving up.  Only
+** a single wait occurs, even if problems are encountered with multiple
+** temp files.  Only the first problem seen takes the delay.  Thus
+** the total delay never exceeds nDelay milliseconds.  When nDelay
+** is non-zero, memory used to hold the filename is reclaimed regardless
+** of whether or not the temporary files were successfully deleted.
 **
 ** If bForce is true, deletion is attempted on p->aUnlink[] files
-** even time has not expired.
+** even if their time has not expired.  However, there is a pause
+** of up to nDelay milliseconds before doing the deletion.
+**
+** When nDelay is zero and the deletion attempt fails, memory used to
+** store temporary filesnames is not reclaimed.
 **
-** When retry is zero and the deletion attempt fails,
-** memory is not reclaimed.
+** The bForce flag is used only when the process is about to exit.  When
+** bForce is set, that indicates that this is our last opportunity to
+** clean up temporary files.
 */
-static void clearTempFile(ShellState *p, int retry, int bForce){
-  int alwaysFree = retry>0;
+static void clearTempFile(ShellState *p, int nDelay, int bForce){
+  int alwaysFree = (nDelay>0) || bForce;
   int rc = 0;
-  if( p->zTempFile && !p->doXdgOpen ){
-    rc = shellDeleteFile(p->zTempFile, &retry);
+  if( p->zTempFile && (!p->doXdgOpen || bForce) ){
+    rc = shellDeleteFile(p->zTempFile, &nDelay);
     if( rc==0 || alwaysFree ){
       sqlite3_free(p->zTempFile);
       p->zTempFile = 0;
@@ -6312,11 +6350,18 @@ static void clearTempFile(ShellState *p, int retry, int bForce){
   if( p->nUnlink ){
     unsigned int i;
     for(i=0; i<p->nUnlink; i++){
-      if( p->aUnlink[i].tm<timeOfDay()
-       || (bForce && alwaysFree && sqlite3_sleep(retry)>=0)
-      ){
+      sqlite3_int64 tmToGo = p->aUnlink[i].tm - timeOfDay();
+      int doDelete =  tmToGo<=0;
+      if( !doDelete && alwaysFree ){
+        int tmSleep = nDelay;
+        if( tmSleep > tmToGo ) tmSleep = (int)tmToGo;
+        sqlite3_sleep(tmSleep);
+        nDelay -= tmSleep;
+        doDelete = 1;
+      }
+      if( doDelete ){
         char *zFN = p->aUnlink[i].zFN;
-        rc = shellDeleteFile(zFN, &retry);
+        rc = shellDeleteFile(zFN, &nDelay);
         if( rc==0 || alwaysFree ){
           sqlite3_free(p->aUnlink[i].zFN);
           p->nUnlink--;
@@ -6362,7 +6407,7 @@ static void newTempFile(ShellState *p, const char *zSuffix){
     r /= 36;
   }
   zRand[i] = 0;
-  clearTempFile(p,1000,0);
+  clearTempFile(p,10,0);
   sqlite3_free(p->zTempFile);
   p->zTempFile = 0;
   zHome = find_home_dir(0);
@@ -6371,6 +6416,26 @@ static void newTempFile(ShellState *p, const char *zSuffix){
   shell_check_oom(p->zTempFile);
 }
 
+/*
+** SQL function:  shell_temp_filename(SUFFIX)
+**
+** Return a randomly generated temporary filename.
+*/
+static void shellTempFilenameFunc(
+  sqlite3_context *pCtx,
+  int nVal,
+  sqlite3_value **apVal
+){
+  ShellState *p = (ShellState*)sqlite3_user_data(pCtx);
+  const char *zSuffix = (const char*)sqlite3_value_text(apVal[0]);
+  (void)nVal;
+  if( zSuffix==0 ) zSuffix = "";
+  newTempFile(p, zSuffix);
+  sqlite3_result_text(pCtx, p->zTempFile, -1, SQLITE_TRANSIENT);
+  sqlite3_free(p->zTempFile);
+  p->zTempFile = 0;
+}
+
 /*
 ** The implementation of SQL scalar function fkey_collate_clause(), used
 ** by the ".lint fkey-indexes" command. This scalar function is always
@@ -13285,6 +13350,7 @@ static void main_init(ShellState *p) {
 #endif
   sqlite3_config(SQLITE_CONFIG_URI, 1);
   sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
+  globalShellState = p;
 }
 
 /*
@@ -13320,8 +13386,14 @@ static char *cmdline_option_value(int argc, char **argv, int i){
   return argv[i];
 }
 
-static void sayAbnormalExit(void){
+/*
+** The callback from atexit().
+*/
+static void abnormalExit(void){
   if( seenInterrupt ) eputz("Program interrupted.\n");
+  if( globalShellState ){
+    clearTempFile(globalShellState, 1, 1);
+  }
 }
 
 /* Routine to output from vfstrace
@@ -13439,7 +13511,7 @@ int SQLITE_CDECL main(int argc, char **argv){
   stdin_is_interactive = isatty(0);
   stdout_is_console = isatty(1);
 #endif
-  atexit(sayAbnormalExit);
+  atexit(abnormalExit);
 #ifdef SQLITE_DEBUG
   mem_main_enter = sqlite3_memory_used();
 #endif
@@ -13472,6 +13544,13 @@ int SQLITE_CDECL main(int argc, char **argv){
     eputz("No ^C handler.\n");
   }
 #endif
+#ifdef SIGTERM
+  signal(SIGTERM, sigterm_handler);
+#endif
+#ifdef SIGHUP
+  signal(SIGHUP, sigterm_handler);
+#endif
+
 
 #if USE_SYSTEM_SQLITE+0!=1
   if( cli_strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,60)!=0 ){
@@ -14111,7 +14190,8 @@ int SQLITE_CDECL main(int argc, char **argv){
   find_home_dir(1);
   output_reset(&data);
   data.doXdgOpen = 0;
-  clearTempFile(&data,1000,1);
+  clearTempFile(&data,2500,1);
+  globalShellState = 0;
   while( data.nModeStack ) modePop(&data);
   free(data.aModeStack);
   modeFree(&data.mode);