]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Have the writefile() function optionally set the modification-time of the
authordan <dan@noemail.net>
Thu, 14 Dec 2017 19:15:07 +0000 (19:15 +0000)
committerdan <dan@noemail.net>
Thu, 14 Dec 2017 19:15:07 +0000 (19:15 +0000)
files it writes or creates. And many small fixes to the new code on this
branch.

FossilOrigin-Name: 7b51269caebe1492885fe9b965892f49a3f8bdb1d666b0203d594c30f9e83938

ext/misc/fileio.c
manifest
manifest.uuid
src/shell.c.in
test/shell8.test

index 23201739971f60e5ce20739e1bae55cd96d699cf..7dbac4043fd53ff4131456bd27ab8772c5fe83b4 100644 (file)
 ******************************************************************************
 **
 ** This SQLite extension implements SQL functions readfile() and
-** writefile().
+** writefile(), and eponymous virtual type "fsdir".
 **
-** Also, an eponymous virtual table type "fsdir". Used as follows:
+** WRITEFILE(FILE, DATA [, MODE [, MTIME]]):
 **
-**   SELECT * FROM fsdir($dirname);
+**   If neither of the optional arguments is present, then this UDF
+**   function writes blob DATA to file FILE. If successful, the number
+**   of bytes written is returned. If an error occurs, NULL is returned.
 **
-** Returns one row for each entry in the directory $dirname. No row is
-** returned for "." or "..". Row columns are as follows:
+**   If the first option argument - MODE - is present, then it must
+**   be passed an integer value that corresponds to a POSIX mode
+**   value (file type + permissions, as returned in the stat.st_mode
+**   field by the stat() system call). Three types of files may
+**   be written/created:
 **
-**   name:  Name of directory entry.
-**   mode:  Value of stat.st_mode for directory entry.
-**   mtime: Value of stat.st_mtime for directory entry.
-**   data:  For a regular file, a blob containing the file data. For a
-**          symlink, a text value containing the text of the link. For a
-**          directory, NULL.
+**     regular files:  (mode & 0170000)==0100000
+**     symbolic links: (mode & 0170000)==0120000
+**     directories:    (mode & 0170000)==0040000
+**
+**   For a directory, the DATA is ignored. For a symbolic link, it is
+**   interpreted as text and used as the target of the link. For a
+**   regular file, it is interpreted as a blob and written into the
+**   named file. Regardless of the type of file, its permissions are
+**   set to (mode & 0777) before returning.
+**
+**   If the optional MTIME argument is present, then it is interpreted
+**   as an integer - the number of seconds since the unix epoch. The
+**   modification-time of the target file is set to this value before
+**   returning.
+**
+**   If three or more arguments are passed to this function and an
+**   error is encountered, an exception is raised.
+**
+** READFILE(FILE):
+**
+**   Read and return the contents of file FILE (type blob) from disk.
+**
+** FSDIR:
+**
+**   Used as follows:
+**
+**     SELECT * FROM fsdir($path [, $dir]);
+**
+**   Parameter $path is an absolute or relative pathname. If the file that it
+**   refers to does not exist, it is an error. If the path refers to a regular
+**   file or symbolic link, it returns a single row. Or, if the path refers
+**   to a directory, it returns one row for the directory, and one row for each
+**   file within the hierarchy rooted at $path.
+**
+**   Each row has the following columns:
+**
+**     name:  Path to file or directory (text value).
+**     mode:  Value of stat.st_mode for directory entry (an integer).
+**     mtime: Value of stat.st_mtime for directory entry (an integer).
+**     data:  For a regular file, a blob containing the file data. For a
+**            symlink, a text value containing the text of the link. For a
+**            directory, NULL.
+**
+**   If a non-NULL value is specified for the optional $dir parameter and
+**   $path is a relative path, then $path is interpreted relative to $dir. 
+**   And the paths returned in the "name" column of the table are also 
+**   relative to directory $dir.
 */
 #include "sqlite3ext.h"
 SQLITE_EXTENSION_INIT1
@@ -45,6 +91,10 @@ SQLITE_EXTENSION_INIT1
 
 #define FSDIR_SCHEMA "(name,mode,mtime,data,path HIDDEN,dir HIDDEN)"
 
+/*
+** Set the result stored by context ctx to a blob containing the 
+** contents of file zName.
+*/
 static void readFileContents(sqlite3_context *ctx, const char *zName){
   FILE *in;
   long nIn;
@@ -81,6 +131,10 @@ static void readfileFunc(
   readFileContents(context, zName);
 }
 
+/*
+** Set the error message contained in context ctx to the results of
+** vprintf(zFmt, ...).
+*/
 static void ctxErrorMsg(sqlite3_context *ctx, const char *zFmt, ...){
   char *zMsg = 0;
   va_list ap;
@@ -138,11 +192,16 @@ static int makeDirectory(
   return rc;
 }
 
+/*
+** This function does the work for the writefile() UDF. Refer to 
+** header comments at the top of this file for details.
+*/
 static int writeFile(
-  sqlite3_context *pCtx, 
-  const char *zFile, 
-  mode_t mode, 
-  sqlite3_value *pData
+  sqlite3_context *pCtx,          /* Context to return bytes written in */
+  const char *zFile,              /* File to write */
+  sqlite3_value *pData,           /* Data to write */
+  mode_t mode,                    /* MODE parameter passed to writefile() */
+  sqlite3_int64 mtime             /* MTIME parameter (or -1 to not set time) */
 ){
   if( S_ISLNK(mode) ){
     const char *zTo = (const char*)sqlite3_value_text(pData);
@@ -178,22 +237,30 @@ static int writeFile(
         }
       }
       fclose(out);
-      if( rc==0 && chmod(zFile, mode & 0777) ){
+      if( rc==0 && mode && chmod(zFile, mode & 0777) ){
         rc = 1;
       }
       if( rc ) return 2;
       sqlite3_result_int64(pCtx, nWrite);
     }
   }
+
+  if( mtime>=0 ){
+    struct timespec times[2];
+    times[0].tv_nsec = times[1].tv_nsec = 0;
+    times[0].tv_sec = time(0);
+    times[1].tv_sec = mtime;
+    if( utimensat(AT_FDCWD, zFile, times, AT_SYMLINK_NOFOLLOW) ){
+      return 1;
+    }
+  }
+
   return 0;
 }
 
 /*
-** Implementation of the "writefile(W,X[,Y]])" SQL function.  
-**
-** The argument X is written into file W.  The number of bytes written is
-** returned. Or NULL is returned if something goes wrong, such as being unable
-** to open file X for writing.
+** Implementation of the "writefile(W,X[,Y[,Z]]])" SQL function.  
+** Refer to header comments at the top of this file for details.
 */
 static void writefileFunc(
   sqlite3_context *context,
@@ -203,8 +270,9 @@ static void writefileFunc(
   const char *zFile;
   mode_t mode = 0;
   int res;
+  sqlite3_int64 mtime = -1;
 
-  if( argc<2 || argc>3 ){
+  if( argc<2 || argc>4 ){
     sqlite3_result_error(context, 
         "wrong number of arguments to function writefile()", -1
     );
@@ -214,18 +282,20 @@ static void writefileFunc(
   zFile = (const char*)sqlite3_value_text(argv[0]);
   if( zFile==0 ) return;
   if( argc>=3 ){
-    sqlite3_result_int(context, 0);
     mode = sqlite3_value_int(argv[2]);
   }
+  if( argc==4 ){
+    mtime = sqlite3_value_int64(argv[3]);
+  }
 
-  res = writeFile(context, zFile, mode, argv[1]);
+  res = writeFile(context, zFile, argv[1], mode, mtime);
   if( res==1 && errno==ENOENT ){
     if( makeDirectory(zFile, mode)==SQLITE_OK ){
-      res = writeFile(context, zFile, mode, argv[1]);
+      res = writeFile(context, zFile, argv[1], mode, mtime);
     }
   }
 
-  if( res!=0 ){
+  if( argc>2 && res!=0 ){
     if( S_ISLNK(mode) ){
       ctxErrorMsg(context, "failed to create symlink: %s", zFile);
     }else if( S_ISDIR(mode) ){
@@ -313,6 +383,10 @@ static int fsdirOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
   return SQLITE_OK;
 }
 
+/*
+** Reset a cursor back to the state it was in when first returned
+** by fsdirOpen().
+*/
 static void fsdirResetCursor(fsdir_cursor *pCur){
   int i;
   for(i=0; i<=pCur->iLvl; i++){
@@ -341,6 +415,10 @@ static int fsdirClose(sqlite3_vtab_cursor *cur){
   return SQLITE_OK;
 }
 
+/*
+** Set the error message for the virtual table associated with cursor
+** pCur to the results of vprintf(zFmt, ...).
+*/
 static void fsdirSetErrmsg(fsdir_cursor *pCur, const char *zFmt, ...){
   va_list ap;
   va_start(ap, zFmt);
@@ -586,6 +664,9 @@ static int fsdirBestIndex(
   return SQLITE_OK;
 }
 
+/*
+** Register the "fsdir" virtual table.
+*/
 static int fsdirRegister(sqlite3 *db){
   static sqlite3_module fsdirModule = {
     0,                         /* iVersion */
@@ -611,9 +692,6 @@ static int fsdirRegister(sqlite3 *db){
   };
 
   int rc = sqlite3_create_module(db, "fsdir", &fsdirModule, 0);
-  if( rc==SQLITE_OK ){
-    rc = sqlite3_create_module(db, "fsentry", &fsdirModule, (void*)1);
-  }
   return rc;
 }
 #else         /* SQLITE_OMIT_VIRTUALTABLE */
index dda85f828721e4ee39da3ecec7ad73a39a8e6574..2ee47c023eb7c4ee6e8365201051519d0507ee88 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Improve\serror\sand\susage\smessages\soutput\sby\sthe\sshell\s".ar"\scommand.
-D 2017-12-14T15:40:42.931
+C Have\sthe\swritefile()\sfunction\soptionally\sset\sthe\smodification-time\sof\sthe\nfiles\sit\swrites\sor\screates.\sAnd\smany\ssmall\sfixes\sto\sthe\snew\scode\son\sthis\nbranch.
+D 2017-12-14T19:15:07.381
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F Makefile.in 6a879cbf01e37f9eac131414955f71774b566502d9a57ded1b8585b507503cb8
@@ -270,7 +270,7 @@ F ext/misc/compress.c 122faa92d25033d6c3f07c39231de074ab3d2e83
 F ext/misc/csv.c 1a009b93650732e22334edc92459c4630b9fa703397cbb3c8ca279921a36ca11
 F ext/misc/dbdump.c 3509fa6b8932d04e932d6b6b827b6a82ca362781b8e8f3c77336f416793e215e
 F ext/misc/eval.c f971962e92ebb8b0a4e6b62949463ee454d88fa2
-F ext/misc/fileio.c 29b7fc94752fff6245cf4a81455f98cf6778ec1102ca7e67bf693d41a7db4307
+F ext/misc/fileio.c 014152d4133e7b29eab8eb39d0c640659c23a6d23d882b4778f487ae7d1a457b
 F ext/misc/fuzzer.c 7c64b8197bb77b7d64eff7cac7848870235d4c25
 F ext/misc/ieee754.c f190d0cc5182529acb15babd177781be1ac1718c
 F ext/misc/json1.c dbe086615b9546c156bf32b9378fc09383b58bd17513b866cfd24c1e15281984
@@ -475,7 +475,7 @@ F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384
 F src/resolve.c bbee7e31d369a18a2f4836644769882e9c5d40ef4a3af911db06410b65cb3730
 F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac
 F src/select.c 17e220191860a64a18c084141e1a8b7309e166a6f2d42c02021af27ea080d157
-F src/shell.c.in 4bdd2efe722005180365698f2a3de51e22ae1e9bb61c868006bc8f2c5e02eb98
+F src/shell.c.in 074b2129559a0aa712a367317f7e7daf4740925ec2c123b529800628eb10dc73
 F src/sqlite.h.in 364515dd186285f3c01f5cab42e7db7edc47c70e87b6a25de389a2e6b8c413fd
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h c02d628cca67f3889c689d82d25c3eb45e2c155db08e4c6089b5840d64687d34
@@ -1215,7 +1215,7 @@ F test/shell4.test 89ad573879a745974ff2df20ff97c5d6ffffbd5d
 F test/shell5.test 23939a4c51f0421330ea61dbd3c74f9c215f5f8d3d1a94846da6ffc777a35458
 F test/shell6.test 1ceb51b2678c472ba6cf1e5da96679ce8347889fe2c3bf93a0e0fa73f00b00d3
 F test/shell7.test 115132f66d0463417f408562cc2cf534f6bbc6d83a6d50f0072a9eb171bae97f
-F test/shell8.test 0f7dfc5b33bde7143df8e37cbb4ae6ccc7e91f87232dc8e5e02be03117cdebb8
+F test/shell8.test 96f35965fe84d633fb2338696f5cbc1bcf6bdbdd79677244bc617a8452851dc7
 F test/shortread1.test bb591ef20f0fd9ed26d0d12e80eee6d7ac8897a3
 F test/show_speedtest1_rtree.tcl 32e6c5f073d7426148a6936a0408f4b5b169aba5
 F test/shrink.test 1b4330b1fd9e818c04726d45cb28db73087535ce
@@ -1683,7 +1683,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 803156cba8b056a1cb8d1bb186a57454afe72341abe7de1dfe529234c3415cd2
-R 69895ee1194373ab4ba11578d798fe26
+P b9d2d5d97291bf3d1392232e3705cca89dc7b918db2b08067b2b013ea39320e0
+R 975f9981c88e18e3ddfa1fe4bb6e7fae
 U dan
-Z 5ea750b0cf239f9442948a3eda934166
+Z 1e84576de6ef44efce8abf1d868c1119
index ce1ae1dcb36e55344af2e046d6335f20d9ecef19..31073ba1157305df2fdf3dc57f84430361ebf9b6 100644 (file)
@@ -1 +1 @@
-b9d2d5d97291bf3d1392232e3705cca89dc7b918db2b08067b2b013ea39320e0
\ No newline at end of file
+7b51269caebe1492885fe9b965892f49a3f8bdb1d666b0203d594c30f9e83938
\ No newline at end of file
index 2a0f9fde9e0b0be65b200c1dfd90026be899909d..8b16082add599f667df2730b2300de0fe6300d7f 100644 (file)
@@ -4092,23 +4092,25 @@ static void shellPrepare(
   }
 }
 
-static void shellPrepare2(
+static void shellPreparePrintf(
   sqlite3 *db, 
   int *pRc, 
-  const char *zSql, 
-  const char *zTail
-  sqlite3_stmt **ppStmt
+  sqlite3_stmt **ppStmt,
+  const char *zFmt
+  ...
 ){
-  if( *pRc==SQLITE_OK && zTail ){
-    char *z = sqlite3_mprintf("%s %s", zSql, zTail);
+  *ppStmt = 0;
+  if( *pRc==SQLITE_OK ){
+    va_list ap;
+    char *z;
+    va_start(ap, zFmt);
+    z = sqlite3_vmprintf(zFmt, ap);
     if( z==0 ){
       *pRc = SQLITE_NOMEM;
     }else{
       shellPrepare(db, pRc, z, ppStmt);
       sqlite3_free(z);
     }
-  }else{
-    shellPrepare(db, pRc, zSql, ppStmt);
   }
 }
 
@@ -4429,23 +4431,27 @@ static int arCheckEntries(sqlite3 *db, ArCommand *pAr){
 static void arWhereClause(
   int *pRc, 
   ArCommand *pAr, 
-  char **pzWhere                  /* OUT: New WHERE clause (or NULL) */
+  char **pzWhere                  /* OUT: New WHERE clause */
 ){
   char *zWhere = 0;
   if( *pRc==SQLITE_OK ){
-    int i;
-    const char *zSep = "WHERE ";
-    for(i=0; i<pAr->nArg; i++){
-      const char *z = pAr->azArg[i];
-      zWhere = sqlite3_mprintf(
-          "%z%s name = '%q' OR name BETWEEN '%q/' AND '%q0'", 
-          zWhere, zSep, z, z, z
-      );
-      if( zWhere==0 ){
-        *pRc = SQLITE_NOMEM;
-        break;
+    if( pAr->nArg==0 ){
+      zWhere = sqlite3_mprintf("1");
+    }else{
+      int i;
+      const char *zSep = "";
+      for(i=0; i<pAr->nArg; i++){
+        const char *z = pAr->azArg[i];
+        zWhere = sqlite3_mprintf(
+            "%z%s name = '%q' OR name BETWEEN '%q/' AND '%q0'", 
+            zWhere, zSep, z, z, z
+            );
+        if( zWhere==0 ){
+          *pRc = SQLITE_NOMEM;
+          break;
+        }
+        zSep = " OR ";
       }
-      zSep = " OR ";
     }
   }
   *pzWhere = zWhere;
@@ -4455,7 +4461,7 @@ static void arWhereClause(
 ** Implementation of .ar "lisT" command. 
 */
 static int arListCommand(ShellState *p, sqlite3 *db, ArCommand *pAr){
-  const char *zSql = "SELECT name FROM sqlar"; 
+  const char *zSql = "SELECT name FROM sqlar WHERE %s"; 
   char *zWhere = 0;
   sqlite3_stmt *pSql = 0;
   int rc;
@@ -4463,7 +4469,7 @@ static int arListCommand(ShellState *p, sqlite3 *db, ArCommand *pAr){
   rc = arCheckEntries(db, pAr);
   arWhereClause(&rc, pAr, &zWhere);
 
-  shellPrepare2(db, &rc, zSql, zWhere, &pSql);
+  shellPreparePrintf(db, &rc, &pSql, zSql, zWhere);
   while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
     raw_printf(p->out, "%s\n", sqlite3_column_text(pSql, 0));
   }
@@ -4482,14 +4488,14 @@ static int arExtractCommand(ShellState *p, sqlite3 *db, ArCommand *pAr){
     "ELSE"
     "    data "
     "END, "
-    "mode) FROM sqlar";
-  const char *zSql2 = "SELECT :1 || name, mtime FROM sqlar"; 
+    "mode, mtime) FROM sqlar WHERE (%s) AND (data IS NULL OR :2 = 0)";
 
   struct timespec times[2];
   sqlite3_stmt *pSql = 0;
   int rc = SQLITE_OK;
   char *zDir = 0;
   char *zWhere = 0;
+  int i;
 
   /* If arguments are specified, check that they actually exist within
   ** the archive before proceeding. And formulate a WHERE clause to
@@ -4509,31 +4515,26 @@ static int arExtractCommand(ShellState *p, sqlite3 *db, ArCommand *pAr){
   memset(times, 0, sizeof(times));
   times[0].tv_sec = time(0);
 
-  shellPrepare2(db, &rc, zSql1, zWhere, &pSql);
+  shellPreparePrintf(db, &rc, &pSql, zSql1, zWhere);
   if( rc==SQLITE_OK ){
     sqlite3_bind_text(pSql, 1, zDir, -1, SQLITE_STATIC);
-  }
-  while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
-    if( pAr->bVerbose ){
-      raw_printf(p->out, "%s\n", sqlite3_column_text(pSql, 0));
-    }
-  }
-  shellFinalize(&rc, pSql);
 
-  shellPrepare2(db, &rc, zSql2, zWhere, &pSql);
-  if( rc==SQLITE_OK ){
-    sqlite3_bind_text(pSql, 1, zDir, -1, SQLITE_STATIC);
-  }
-  while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
-    const char *zPath = (const char*)sqlite3_column_text(pSql, 0);
-    times[1].tv_sec = (time_t)sqlite3_column_int64(pSql, 1);
-    if( utimensat(AT_FDCWD, zPath, times, AT_SYMLINK_NOFOLLOW) ){
-      raw_printf(stderr, "failed to set timestamp for %s\n", zPath);
-      rc = SQLITE_ERROR;
-      break;
+    /* Run the SELECT statement twice. The first time, writefile() is called
+    ** for all archive members that should be extracted. The second time,
+    ** only for the directories. This is because the timestamps for
+    ** extracted directories must be reset after they are populated (as
+    ** populating them changes the timestamp).  */
+    for(i=0; i<2; i++){
+      sqlite3_bind_int(pSql, 2, i);
+      while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pSql) ){
+        if( i==0 && pAr->bVerbose ){
+          raw_printf(p->out, "%s\n", sqlite3_column_text(pSql, 0));
+        }
+      }
+      shellReset(&rc, pSql);
     }
+    shellFinalize(&rc, pSql);
   }
-  shellFinalize(&rc, pSql);
 
   sqlite3_free(zDir);
   sqlite3_free(zWhere);
index 07065b56bdc3dd868d341a3c626565f73b02f415..14980a84a53b2a567350cbcf9673e5ecde98e69e 100644 (file)
@@ -139,6 +139,21 @@ foreach {tn tcl} {
     catchcmd ":memory:" $x3
     dir_to_list ar3
   } $expected
+
+  # This is a repeat of test 1.$tn.1, except that there is a 2 second 
+  # pause between creating the archive and extracting its contents.
+  # This is to test that timestamps are set correctly.
+  #
+  # Because it is slow, only do this for $tn==1.
+  if {$tn==1} {
+    do_test 1.$tn.1 {
+      catchcmd test_ar.db $c1
+      file delete -force ar1
+      after 2000
+      catchcmd test_ar.db $x1
+      dir_to_list ar1
+    } $expected
+  }
 }
 
 finish_test