]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Improve kvvfs file name validation. Add sqlite3.kvvfs.unlink().
authorstephan <stephan@noemail.net>
Tue, 25 Nov 2025 18:52:08 +0000 (18:52 +0000)
committerstephan <stephan@noemail.net>
Tue, 25 Nov 2025 18:52:08 +0000 (18:52 +0000)
FossilOrigin-Name: 0dfdf4681cf63541de971a20be21b33d0d3b38e8281f302d20aca9492df3da42

ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js
ext/wasm/tester1.c-pp.js
manifest
manifest.uuid

index 6bf7371cb12977b4c83be3964b10edaa5214ddc9..e0146deda474df4b8078000d9a3414af034029d5 100644 (file)
@@ -100,10 +100,17 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
         toss3 = util.toss3,
         hop = (o,k)=>Object.prototype.hasOwnProperty.call(o,k);
 
+  const kvvfsMethods = new sqlite3_kvvfs_methods(
+    /* Wraps the static sqlite3_api_methods singleton */
+    wasm.exports.sqlite3__wasm_kvvfs_methods()
+  );
+  util.assert( 32<=kvvfsMethods.$nKeySize, "unexpected kvvfsMethods.$nKeySize: "+kvvfsMethods.$nKeySize);
+
   const cache = Object.assign(Object.create(null),{
-    rxJournalSuffix: /-journal$/, // TOOD: lazily init once we figure out where
+    rxJournalSuffix: /-journal$/,
     zKeyJrnl: wasm.allocCString("jrnl"),
-    zKeySz: wasm.allocCString("sz")
+    zKeySz: wasm.allocCString("sz"),
+    keySize: kvvfsMethods.$nKeySize
   });
 
   const debug = sqlite3.__isUnderTest
@@ -185,6 +192,33 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
   */
   const kvvfsKeyPrefix = (v)=>kvvfsIsPersistentName(v) ? 'kvvfs-'+v+'-' : '';
 
+  /**
+     Throws if storage name n is not valid for use as a storage name.
+     This is intended for the high-level APIs, not the low-level ones.
+  */
+  const validateStorageName = function(n,mayBeJournal=false){
+    let maxLen = cache.keySize - 1;
+    if( cache.rxJournalSuffix.test(n) ){
+      if( !mayBeJournal ){
+        toss3(capi.SQLITE_MISUSE, "Storage names may not have a '-journal' suffix.");
+      }
+    }else{
+      maxLen -= 8 /* "-journal" */;
+    }
+    const len = n.length;
+    if( len > maxLen ){
+      toss3(capi.SQLITE_RANGE, "Storage name is too long.");
+    }
+    let i;
+    for( i = 0; i < len; ++i ){
+      const ch = n.codePointAt(i);
+      if( ch<45 || (ch & 0x80) ){
+        toss3(capi.SQLITE_RANGE,
+              "Illegal character ("+ch+"d) in storage name:",n);
+      }
+    }
+  };
+
   /**
      Create a new instance of the objects which go into
      cache.storagePool.
@@ -203,7 +237,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
     /**
        deleteAtRefc0 objects will be removed by xClose() when refc
        reaches 0. The others will persist, to give the illusion of
-       real back-end storage. Managed by xOpen().
+       real back-end storage. Managed by xOpen(). By default this is
+       false but the delete-on-close=1 flag can be used to set this to
+       true.
      */
     deleteAtRefc0: false,
     /**
@@ -227,9 +263,38 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
        xOpen() and xClose().
     */
     files: [],
+    /**
+       A list of objects with various event callbacks. See
+       sqlite3_js_kvvfs_listen().
+     */
     listeners: []
   });
 
+  /**
+     Deletes the cache.storagePool entries for store and its
+     db/journal counterpart.
+  */
+  const deleteStorage = function(store){
+    const other = cache.rxJournalSuffix.test(store.jzClass)
+          ? store.jzClass.replace(cache.rxJournalSuffix,'')
+          : store.jzClass+'-journal';
+    debug("cleaning up storage handles [", store.jzClass, other,"]",store);
+    delete cache.storagePool[store.jzClass];
+    delete cache.storagePool[other];
+    if( !sqlite3.__isUnderTest ){
+      /* In test runs, leave these for inspection. If we delete them here,
+         any prior dumps of them emitted via the console get cleared out
+         because the console shows live objects instead of call-time
+         static dumps. */
+      delete store.storage;
+      delete store.refc;
+    }
+  };
+
+  /**
+     Add both store.jzClass and store.jzClass+"-journal"
+     to cache,storagePool.
+  */
   const installStorageAndJournal = (store)=>
         cache.storagePool[store.jzClass] =
         cache.storagePool[store.jzClass+'-journal'] = store;
@@ -248,6 +313,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
   });
 
   if( globalThis.Storage ){
+    /* If available, install local/session storage. */
     if( globalThis.localStorage instanceof globalThis.Storage ){
       cache.storagePool.local = newStorageObj('local', globalThis.localStorage);
     }
@@ -256,6 +322,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
     }
   }
 
+  /* Add "-journal" twins for each cache.storagePool entry... */
   for(const k of Object.keys(cache.storagePool)){
     /* Journals in kvvfs are are stored as individual records within
        their Storage-ish object, named "{storage.keyPrefix}jrnl".  We
@@ -293,7 +360,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
 
      'delete': key
   */
-  const notifyListners = async function(eventName,store,...args){
+  const notifyListeners = async function(eventName,store,...args){
     store.listeners.forEach((v)=>{
       const f = v?.[eventName];
       if( !f ) return;
@@ -410,10 +477,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
     const originalIoMethods = (kvvfsFile)=>
           originalMethods[kvvfsFile.$isJournal ? 'ioJrnl' : 'ioDb'];
 
-    const kvvfsMethods = new sqlite3_kvvfs_methods(
-      /* Wraps the static sqlite3_api_methods singleton */
-      wasm.exports.sqlite3__wasm_kvvfs_methods()
-    );
     const pVfs = new capi.sqlite3_vfs(kvvfsMethods.$pVfs);
     const pIoDb = new capi.sqlite3_io_methods(kvvfsMethods.$pIoDb);
     const pIoJrnl = new capi.sqlite3_io_methods(kvvfsMethods.$pIoJrnl);
@@ -519,7 +582,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
             const jxKey = wasm.cstrToJs(zXKey);
             const jData = wasm.cstrToJs(zData);
             store.storage.setItem(jxKey, jData);
-            notifyListners('write', store, jxKey, jData);
+            notifyListeners('write', store, jxKey, jData);
             return 0;
           }catch(e){
             error("kvrecordWrite()",e);
@@ -534,7 +597,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
             //if(!zXKey) return capi.SQLITE_NOMEM;
             const jxKey = wasm.cstrToJs(zXKey);
             store.storage.removeItem(jxKey);
-            notifyListners('delete', store, jxKey);
+            notifyListeners('delete', store, jxKey);
             return 0;
           }catch(e){
             error("kvrecordDelete()",e);
@@ -557,24 +620,10 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
             if( !zName ){
               zName = (cache.zEmpty ??= wasm.allocCString(""));
             }
-            const n = wasm.cstrlen(zName);
-            if( !n ){
-              toss3(capi.SQLITE_RANGE,
-                    "Storage name may not be empty (backwards",
-                    "compatibilty constraint)");
-            }else if( n > kvvfsMethods.$nKeySize - 8 /*"-journal"*/ - 1 ){
-              toss3(capi.SQLITE_RANGE,
-                    "Storage name is too long:", wasm.cstrToJs(zName));
-            }
-            let i = 0;
-            for( ; i < n; ++i ){
-              const ch = wasm.peek8(wasm.ptr.add(zName, i));
-              if( ch < 45 || (ch & 0x80) ){
-                toss3(capi.SQLITE_RANGE,
-                      "Illegal character ("+ch+"d) in storage name.");
-              }
-            }
             const jzClass = wasm.cstrToJs(zName);
+            validateStorageName(jzClass, true);
+            util.assert( jzClass.length===wasm.cstrlen(zName),
+                         "ASCII-only validation failed" );
             if( (flags & (capi.SQLITE_OPEN_MAIN_DB
                           | capi.SQLITE_OPEN_TEMP_DB
                           | capi.SQLITE_OPEN_TRANSIENT_DB))
@@ -586,13 +635,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
                                                  flags, pOutFlags);
             if( rc ) return rc;
             let deleteAt0 = false;
-            if(n && wasm.isPtr(zName)){
+            if(wasm.isPtr(arguments[1]/*original zName*/)){
               if(capi.sqlite3_uri_boolean(zName, "delete-on-close", 0)){
                 deleteAt0 = true;
-                //warn("transient=",deleteAt0);
-              }
-              if(capi.sqlite3_uri_boolean(zName, "wipe-before-open", 0)){
-                // TODO?
               }
             }
             const f = new KVVfsFile(pProtoFile);
@@ -619,7 +664,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
               debug("xOpen installed storage handle [",nm, nm+"-journal","]", s);
             }
             pFileHandles.set(pProtoFile, {storage: s, file: f, jzClass});
-            notifyListners('open', s, s.files.length);
+            notifyListeners('open', s, s.files.length);
             return 0;
           }catch(e){
             warn("xOpen:",e);
@@ -753,20 +798,11 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
               const s = storageForZClass(h.jzClass);
               s.files = s.files.filter((v)=>v!==h.file);
               if( --s.refc<=0 && s.deleteAtRefc0 ){
-                const other = h.file.$isJournal
-                      ? h.jzClass.replace(cache.rxJournalSuffix,'')
-                      : h.jzClass+'-journal';
-                debug("cleaning up storage handles [", h.jzClass, other,"]",s);
-                delete cache.storagePool[h.jzClass];
-                delete cache.storagePool[other];
-                if( !sqlite3.__isUnderTest ){
-                  delete s.storage;
-                  delete s.refc;
-                }
+                deleteStorage(s);
               }
               originalIoMethods(h.file).xClose(pFile);
               h.file.dispose();
-              notifyListners('close', s, s.files.length);
+              notifyListeners('close', s, s.files.length);
             }else{
               /* Can happen if xOpen fails */
             }
@@ -901,7 +937,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
      - It accepts an arbitrary storage name. In v1 this was a silent
      no-op for any names other than ('local','session','').
 
-     - The second argument was added.
+     - The second argument was added. Its default value reflects the
+     legacy behavior.
 
      - It throws if a db currently has the storage opened. That
      version 1 did not throw for this case was due to an architectural
@@ -933,7 +970,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
   };
 
   /**
-     This routine guesses the approximate amount of
+     This routine estimates the approximate amount of
      storage used by the given kvvfs back-end.
 
      Its arguments are as documented for sqlite3_js_kvvfs_clear(),
@@ -967,20 +1004,6 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
     return sz * 2 /* because JS uses 2-byte char encoding */;
   };
 
-  /**
-     Throws if storage name n is not valid for use as a storage name.
-     This is intended for the high-level APIs, not the low-level ones.
-  */
-  const validateStorageName = function(n){
-    if( cache.rxJournalSuffix.test(n) ){
-      toss3(capi.SQLITE_MISUSE, "Storage names may not have a '-journal' suffix.");
-    }
-    if( n.length>23 ){
-      toss3(capi.SQLITE_RANGE, "Storage name is too long.");
-    }
-    // TODO: check all of kvvfs's name constraints
-  };
-
   /**
      Copies the entire contents of the given transient storage object
      into a JSON-friendly form.  The returned object is structured as
@@ -1066,7 +1089,7 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
   }/* sqlite3_js_kvvfs_export */;
 
   /**
-     INCOMPLETE. This interface is subject to change.
+     EXPERIMENTAL. This interface is subject to change.
 
      The counterpart of sqlite3_js_kvvfs_export(). Its
      argument must be the result of that function() or
@@ -1143,6 +1166,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
   };
 
   /**
+     EXPERIMENTAL. This interface is subject to change.
+
      If no kvvfs storage exists with the given name, one is
      installed. If one exists, its reference count is increased so
      that it won't be freed by the closing of a database or journal
@@ -1161,6 +1186,42 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
   };
 
   /**
+     Conditionally "unlinks" a kvvfs storage object, reducing its
+     reference count by 1.
+
+     This is a no-op if name ends in "-journal" or refers to a
+     built-in storage object ('local', 'session', or 'localThread').
+
+     It will not lower the refcount below the number of
+     currently-opened db/journal files for the storage (so that it
+     cannot delete it out from under them).
+
+     If the refcount reaches 0 then the storage object is
+     removed.
+
+     Returns true if it reduces the refcount, else false.  A result of
+     true does not necessarily mean that the storage unit was removed,
+     just that its refcount was lowered.
+  */
+  const sqlite3_js_kvvfs_unlink = function(name){
+    const store = storageForZClass(name);
+    if( !store
+        || kvvfsIsPersistentName(store.jzClass)
+        || 'localThread'===store.jzClass
+        || cache.rxJournalSuffix.test(name) ) return false;
+    if( store.refc > store.files.length || 0===store.files.length ){
+      if( --store.refc<=0 ){
+        /* Ignoring deleteAtRefc0 for an explicit unlink */
+        deleteStorage(store);
+      }
+      return true;
+    }
+    return false;
+  };
+
+  /**
+     EXPERIMENTAL. This interface is subject to change.
+
      Adds an event listener to a kvvfs storage object. The idea is
      that this can be used to asynchronously back up one kvvfs storage
      object to another or another channel entirely. (The caveat in the
@@ -1172,7 +1233,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
      - storage: the name of the kvvfs storage object.
 
      - reserve [=false]: if true, sqlite3_js_kvvfs_reserve() is used
-     to ensure that the storage exists.
+     to ensure that the storage exists if it does not already.
+     If this is false and the storage does not exist then an
+     exception is thrown.
 
      - events: an object which may have any of the following
      callback function properties: open, close, write, delete.
@@ -1198,6 +1261,27 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
      were written (both strings).
 
      - 'delete' gets the string-type key of the deleted record.
+
+     Passing the same object ot sqlite3_js_kvvfs_unlisten() will
+     remove the listener.
+
+     The arguments to 'write' and 'delete' are in one of the following
+     forms:
+
+     - 'sz' = the unencoded db size as a string
+
+     - 'jrnl' = the current db journal as a string
+
+     - '[1-9][0-9]*' (a db page number) = an encoded db page
+
+     For 'local' and 'session' storage, all of those keys have a
+     prefix of 'kvvfs-local-' resp. 'kvvfs-session-'. This is required
+     both for backwards compatibility and to enable dbs in those
+     storage objects to coexit with client data. Other storage objects
+     do not have a prefix.
+
+     Design note: JS has StorageEvents but only in the main thread,
+     which is why the listeners are not based on that.
   */
   const sqlite3_js_kvvfs_listen = function(opt){
     if( !opt || 'object'!==typeof opt ){
@@ -1221,7 +1305,9 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
   };
 
   /**
-     Removes all kvvfs event listeners for the given options
+     EXPERIMENTAL. This interface is subject to change.
+
+     Removes the kvvfs event listeners for the given options
      object. It must be passed the same object instance which was
      passed to sqlite3_js_kvvfs_listen().
 
@@ -1235,7 +1321,26 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
     }
   };
 
-  if(sqlite3?.oo1?.DB){
+  /**
+     Public interface for kvvfs v2. The capi.sqlite3_js_kvvfs_...()
+     routines remain in place for v1. Some members of this class proxy
+     to those functions but use different default argument values in
+     some cases.
+  */
+  sqlite3.kvvfs = Object.assign(Object.create(null),{
+    reserve:  sqlite3_js_kvvfs_reserve,
+    import:   sqlite3_js_kvvfs_import,
+    export:   sqlite3_js_kvvfs_export,
+    unlink:   sqlite3_js_kvvfs_unlink,
+    listen:   sqlite3_js_kvvfs_listen,
+    unlisten: sqlite3_js_kvvfs_unlisten,
+    exists:   (name)=>!!storageForZClass(name),
+    // DIFFERENT DEFAULTS for the arguments:
+    size:     (which,emptyIsAName=true)=>capi.sqlite3_js_kvvfs_size(which,emptyIsAName),
+    clear:    (which,emptyIsAName=true)=>capi.sqlite3_js_kvvfs_clear(which,emptyIsAName),
+  });
+
+  if(sqlite3.oo1?.DB){
     /**
        Functionally equivalent to DB(storageName,'c','kvvfs') except
        that it throws if the given storage name is not one of 'local'
@@ -1265,6 +1370,8 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
         case ":sessionStorage:": opt.filename = 'session'; break;
         case ":localStorage:": opt.filename = 'local'; break;
       }
+      const m = /(file:(\/\/)?)([^?]+)/.exec(opt.filename);
+      validateStorageName( m ? m[3] : opt.filename);
       DB.dbCtorHelper.call(this, opt);
     };
     sqlite3.oo1.JsStorageDb.defaultStorageName = 'session';
@@ -1297,22 +1404,5 @@ globalThis.sqlite3ApiBootstrap.initializers.push(function(sqlite3){
     }/* __isUnderTest */
   }/*sqlite3.oo1.JsStorageDb*/
 
-  /**
-     Public interface for kvvfs v2. The capi.sqlite3_js_kvvfs_...()
-     routines remain in place for v1. Some members of this class proxy
-     to those functions but use different default argument values in
-     some cases.
-  */
-  sqlite3.kvvfs = Object.assign(Object.create(null),{
-    reserve:  sqlite3_js_kvvfs_reserve,
-    import:   sqlite3_js_kvvfs_import,
-    export:   sqlite3_js_kvvfs_export,
-    listen:   sqlite3_js_kvvfs_listen,
-    unlisten: sqlite3_js_kvvfs_unlisten,
-    // DIFFERENT DEFAULTS for the second arguments:
-    size:     (which,emptyIsAName=true)=>capi.sqlite3_js_kvvfs_size(which,emptyIsAName),
-    clear:    (which,emptyIsAName=true)=>capi.sqlite3_js_kvvfs_clear(which,emptyIsAName),
-  });
-
 })/*globalThis.sqlite3ApiBootstrap.initializers*/;
 //#endif not omit-kvvfs
index 9f9ed7e2e3a7548dce6b260f47b68f256953505f..d9d7430ed5561b62c05c885de24bcc6b28faf27d 100644 (file)
@@ -3018,6 +3018,14 @@ globalThis.sqlite3InitModule = sqlite3InitModule;
              db and SQLite is not calling xGetLastError() to fetch
              the error string. */
         }, capi.SQLITE_RANGE);
+        T.mustThrowMatching(()=>{
+          new JDb("012345678901234567890123"/*too long*/);
+        }, capi.SQLITE_RANGE);
+        {
+          const name = "01234567890123456789012" /* max name length */;
+          (new JDb(name)).close();
+          T.assert( sqlite3.kvvfs.unlink(name) );
+        }
 
         try {
           const exportDb = sqlite3.kvvfs.export;
index 7103ec9a3e83b3d9cad7a7111861f472c59f9fe0..642f1872bf305659ced032ea020ccedf8b3f4699 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Add\sthe\ssqlite3.kvvfs\snamespace\sfor\sthe\snew\skvvfs\sAPIs\sinstead\sof\sadding\smore\ssqlite3_js_kvvfs_...()\smethods.\sReinstate\sthat\sclearing\skvvfs\sstorage\sis\sillegal\swhen\sdb\shandles\sare\sopened,\ssolely\sfor\ssanity's\ssake\s(they\scan\sactually\srecover\sfrom\sthat\sbut\ssupporting\ssuch\suse\sfeels\sill-advised).
-D 2025-11-25T16:41:56.975
+C Improve\skvvfs\sfile\sname\svalidation.\sAdd\ssqlite3.kvvfs.unlink().
+D 2025-11-25T18:52:08.177
 F .fossil-settings/binary-glob 61195414528fb3ea9693577e1980230d78a1f8b0a54c78cf1b9b24d0a409ed6a x
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
@@ -600,7 +600,7 @@ F ext/wasm/api/sqlite3-api-worker1.c-pp.js 1041dd645e8e821c082b628cd8d9acf70c667
 F ext/wasm/api/sqlite3-license-version-header.js 0c807a421f0187e778dc1078f10d2994b915123c1223fe752b60afdcd1263f89
 F ext/wasm/api/sqlite3-opfs-async-proxy.js 9654b565b346dc609b75d15337f20acfa7af7d9d558da1afeb9b6d8eaa404966
 F ext/wasm/api/sqlite3-vfs-helper.c-pp.js 3f828cc66758acb40e9c5b4dcfd87fd478a14c8fb7f0630264e6c7fa0e57515d
-F ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js c9dcbfb0aca85c11491427c9980174294022a0982f6aaf8c6a8374fa5ca76c83
+F ext/wasm/api/sqlite3-vfs-kvvfs.c-pp.js bd901a57c958a31bbbcaecdb7993467161f38e72b8b5e8c8eaaa898a078b85ea
 F ext/wasm/api/sqlite3-vfs-opfs-sahpool.c-pp.js a2eea6442556867b589e04107796c6e1d04a472219529eeb45b7cd221d7d048b
 F ext/wasm/api/sqlite3-vfs-opfs.c-pp.js 88ce2078267a2d1af57525a32d896295f4a8db7664de0e17e82dc9ff006ed8d3
 F ext/wasm/api/sqlite3-vtab-helper.c-pp.js 9097074724172e31e56ce20ccd7482259cf72a76124213cbc9469d757676da86
@@ -647,7 +647,7 @@ F ext/wasm/test-opfs-vfs.html 1f2d672f3f3fce810dfd48a8d56914aba22e45c6834e262555
 F ext/wasm/test-opfs-vfs.js 1618670e466f424aa289859fe0ec8ded223e42e9e69b5c851f809baaaca1a00c
 F ext/wasm/tester1-worker.c-pp.html 0e432ec2c0d99cd470484337066e8d27e7aee4641d97115338f7d962bf7b081a
 F ext/wasm/tester1.c-pp.html 52d88fe2c6f21a046030a36410b4839b632f4424028197a45a3d5669ea724ddb
-F ext/wasm/tester1.c-pp.js 5030080f96b0f85ce78e3937fffd51e2a54b32d1fd7e6d3bce6c6bf7dcc5646a
+F ext/wasm/tester1.c-pp.js 87a6276e7f5970d5c6bd51a00972ee22230ada535793518b90f3524969592a56
 F ext/wasm/tests/opfs/concurrency/index.html 657578a6e9ce1e9b8be951549ed93a6a471f4520a99e5b545928668f4285fb5e
 F ext/wasm/tests/opfs/concurrency/test.js d08889a5bb6e61937d0b8cbb78c9efbefbf65ad09f510589c779b7cc6a803a88
 F ext/wasm/tests/opfs/concurrency/worker.js 0a8c1a3e6ebb38aabbee24f122693f1fb29d599948915c76906681bb7da1d3d2
@@ -2178,8 +2178,8 @@ F tool/version-info.c 33d0390ef484b3b1cb685d59362be891ea162123cea181cb8e6d2cf6dd
 F tool/warnings-clang.sh bbf6a1e685e534c92ec2bfba5b1745f34fb6f0bc2a362850723a9ee87c1b31a7
 F tool/warnings.sh d924598cf2f55a4ecbc2aeb055c10bd5f48114793e7ba25f9585435da29e7e98
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 2bf31ef8027a3e15887d4dcd26fe09463b5f8852c5ce443f7d07c23d29c37311
-R 30d74c08232d6eedb21ecc8d5c58c72f
+P 02793c5905e6b99379cd5ad6bfe1eb6cccf839da081fc174dce7b06245e212fb
+R ed6be124fc5c288abe328108bd2f7a96
 U stephan
-Z 2911ac5a013b42fb37b269f1bdfc6178
+Z 630b67750a1abe72dfc01e3136fc3599
 # Remove this line to create a well-formed Fossil manifest.
index c62c523ce79825f312bac83d8488e0e52b912d57..0c5819bac04c98d88b40ec12569fd31d2c88c1ac 100644 (file)
@@ -1 +1 @@
-02793c5905e6b99379cd5ad6bfe1eb6cccf839da081fc174dce7b06245e212fb
+0dfdf4681cf63541de971a20be21b33d0d3b38e8281f302d20aca9492df3da42