1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +03:00

Code restructuring. Force SQLITE_THREADSAFE in JNI builds for the time being, as threadsafe==0 leads to as-yet-mysterious JNI-level reference errors.

FossilOrigin-Name: 5a099caa2c21bec647f0a521e7f5d0d1cc2f96d388d3d6c53d5ec80947f33e8d
This commit is contained in:
stephan
2023-08-25 11:32:56 +00:00
parent 35f9b1719b
commit 0199669fa1
5 changed files with 102 additions and 47 deletions

View File

@ -105,6 +105,14 @@
# define SQLITE_THREADSAFE 1 # define SQLITE_THREADSAFE 1
#endif #endif
/*
** 2023-08-25: initial attempts at running with SQLITE_THREADSAFE=0
** lead to as-yet-uninvestigated bad reference errors from JNI.
*/
#if SQLITE_THREADSAFE==0
# error "This code currently requires SQLITE_THREADSAFE!=0."
#endif
/**********************************************************************/ /**********************************************************************/
/* SQLITE_USE_... */ /* SQLITE_USE_... */
#ifndef SQLITE_USE_URI #ifndef SQLITE_USE_URI
@ -445,11 +453,12 @@ struct S3JniGlobalType {
** threads. Caching a copy of the JavaVM object enables any thread ** threads. Caching a copy of the JavaVM object enables any thread
** with access to the cached object to get access to its own ** with access to the cached object to get access to its own
** JNIEnv when necessary. ** JNIEnv when necessary.
**
*/ */
JavaVM * jvm; JavaVM * jvm;
/* /*
** Cache of Java refs/IDs for NativePointerHolder subclasses. ** Cache of Java refs and method IDs for NativePointerHolder
** Initialized on demand. ** subclasses. Initialized on demand.
*/ */
S3JniNphClass nph[S3Jni_NphCache_size]; S3JniNphClass nph[S3Jni_NphCache_size];
/* /*
@ -459,24 +468,23 @@ struct S3JniGlobalType {
S3JniEnv * aHead /* Linked list of in-use instances */; S3JniEnv * aHead /* Linked list of in-use instances */;
S3JniEnv * aFree /* Linked list of free instances */; S3JniEnv * aFree /* Linked list of free instances */;
sqlite3_mutex * mutex /* mutex for aHead and aFree as well for sqlite3_mutex * mutex /* mutex for aHead and aFree as well for
first-time inits of nph members. */; first-time inits of nph[] entries. */;
void const * locker /* env mutex is held on this object's behalf. void const * locker /* env mutex is held on this object's behalf.
Used only for sanity checking. */; Used only for sanity checking. */;
} envCache; } envCache;
/*
** Per-db state. This can move into the core library once we can tie
** client-defined state to db handles there.
*/
struct { struct {
S3JniDb * aUsed /* Linked list of in-use instances */; S3JniDb * aHead /* Linked list of in-use instances */;
S3JniDb * aFree /* Linked list of free instances */; S3JniDb * aFree /* Linked list of free instances */;
sqlite3_mutex * mutex /* mutex for aUsed and aFree */; sqlite3_mutex * mutex /* mutex for aHead and aFree */;
void const * locker /* perDb mutex is held on this object's void const * locker /* perDb mutex is held on this object's
behalf. Unlike envCache.locker, we cannot behalf. Unlike envCache.locker, we cannot
always have this set to the current JNIEnv always have this set to the current JNIEnv
object. Used only for sanity checking. */; object. Used only for sanity checking. */;
} perDb; } perDb;
#ifdef SQLITE_ENABLE_SQLLOG
struct {
S3JniHook sqllog /* sqlite3_config(SQLITE_CONFIG_SQLLOG) callback */;
} hooks;
#endif
/* /*
** Refs to global classes and methods. Obtained during static init ** Refs to global classes and methods. Obtained during static init
** and never released. ** and never released.
@ -490,6 +498,22 @@ struct S3JniGlobalType {
jmethodID ctorStringBA /* the String(byte[],Charset) constructor */; jmethodID ctorStringBA /* the String(byte[],Charset) constructor */;
jmethodID stringGetBytes /* the String.getBytes(Charset) method */; jmethodID stringGetBytes /* the String.getBytes(Charset) method */;
} g /* refs to global Java state */; } g /* refs to global Java state */;
/**
The list of bound auto-extensions (Java-side:
org.sqlite.jni.auto_extension objects).
*/
struct {
S3JniAutoExtension *pExt /* The auto-extension list. It is
maintained such that all active
entries are in the first contiguous
nExt array elements. */;
int nAlloc /* number of entries allocated for pExt,
as distinct from the number of active
entries. */;
int nExt /* number of active entries in pExt, all in the
first nExt'th array elements. */;
sqlite3_mutex * mutex /* mutex for manipulation/traversal of pExt */;
} autoExt;
#ifdef SQLITE_ENABLE_FTS5 #ifdef SQLITE_ENABLE_FTS5
struct { struct {
volatile jobject jFtsExt /* Global ref to Java singleton for the volatile jobject jFtsExt /* Global ref to Java singleton for the
@ -500,6 +524,11 @@ struct S3JniGlobalType {
} jPhraseIter; } jPhraseIter;
} fts5; } fts5;
#endif #endif
#ifdef SQLITE_ENABLE_SQLLOG
struct {
S3JniHook sqllog /* sqlite3_config(SQLITE_CONFIG_SQLLOG) callback */;
} hooks;
#endif
#ifdef SQLITE_JNI_ENABLE_METRICS #ifdef SQLITE_JNI_ENABLE_METRICS
/* Internal metrics. */ /* Internal metrics. */
struct { struct {
@ -529,18 +558,6 @@ struct S3JniGlobalType {
#endif #endif
} metrics; } metrics;
#endif /* SQLITE_JNI_ENABLE_METRICS */ #endif /* SQLITE_JNI_ENABLE_METRICS */
/**
The list of bound auto-extensions (Java-side:
org.sqlite.jni.auto_extension objects).
*/
struct {
S3JniAutoExtension *pExt /* Head of the auto-extension list */;
int nAlloc /* number of entries allocated for pExt,
as distinct from the number of active
entries. */;
int nExt /* number of active entries in pExt. */;
sqlite3_mutex * mutex /* mutex for manipulation/traversal of pExt */;
} autoExt;
}; };
static S3JniGlobalType S3JniGlobal = {}; static S3JniGlobalType S3JniGlobal = {};
#define SJG S3JniGlobal #define SJG S3JniGlobal
@ -550,23 +567,24 @@ static S3JniGlobalType S3JniGlobal = {};
#define s3jni_incr(PTR) #define s3jni_incr(PTR)
#elif S3JNI_METRICS_MUTEX #elif S3JNI_METRICS_MUTEX
static void s3jni_incr( volatile unsigned int * const p ){ static void s3jni_incr( volatile unsigned int * const p ){
sqlite3_mutex * const m = SJG.metrics.mutex; sqlite3_mutex_enter(SJG.metrics.mutex);
sqlite3_mutex_enter(m);
++SJG.metrics.nMetrics; ++SJG.metrics.nMetrics;
++(*p); ++(*p);
sqlite3_mutex_leave(m); sqlite3_mutex_leave(SJG.metrics.mutex);
} }
#else #else
#define s3jni_incr(PTR) ++(*(PTR)) #define s3jni_incr(PTR) ++(*(PTR))
#endif #endif
/* Helpers for working with specific mutexes. */ /* Helpers for working with specific mutexes. */
#if SQLITE_THREADSAFE
#define S3JniMutex_Env_assertLocked \ #define S3JniMutex_Env_assertLocked \
assert( 0 != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) assert( 0 != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
#define S3JniMutex_Env_assertLocker \ #define S3JniMutex_Env_assertLocker \
assert( (env) == SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) assert( (env) == SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
#define S3JniMutex_Env_assertNotLocker \ #define S3JniMutex_Env_assertNotLocker \
assert( (env) != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" ) assert( (env) != SJG.envCache.locker && "Misuse of S3JniGlobal.envCache.mutex" )
#define S3JniMutex_Env_enter \ #define S3JniMutex_Env_enter \
S3JniMutex_Env_assertNotLocker; \ S3JniMutex_Env_assertNotLocker; \
/*MARKER(("Entering ENV mutex@%p %s.\n", env));*/ \ /*MARKER(("Entering ENV mutex@%p %s.\n", env));*/ \
@ -606,6 +624,19 @@ static void s3jni_incr( volatile unsigned int * const p ){
assert( env == SJG.perDb.locker ); \ assert( env == SJG.perDb.locker ); \
SJG.perDb.locker = 0; \ SJG.perDb.locker = 0; \
sqlite3_mutex_leave( SJG.perDb.mutex ) sqlite3_mutex_leave( SJG.perDb.mutex )
#else /* SQLITE_THREADSAFE==0 */
#define S3JniMutex_Env_assertLocked
#define S3JniMutex_Env_assertLocker
#define S3JniMutex_Env_assertNotLocker
#define S3JniMutex_Env_enter
#define S3JniMutex_Env_leave
#define S3JniMutex_Ext_enter
#define S3JniMutex_Ext_leave
#define S3JniMutex_Nph_enter
#define S3JniMutex_Nph_leave
#define S3JniMutex_S3JniDb_enter
#define S3JniMutex_S3JniDb_leave
#endif
#define s3jni_oom_check(VAR) if( !(VAR) ) s3jni_oom(env) #define s3jni_oom_check(VAR) if( !(VAR) ) s3jni_oom(env)
static inline void s3jni_oom(JNIEnv * const env){ static inline void s3jni_oom(JNIEnv * const env){
@ -917,15 +948,17 @@ static void S3JniHook_unref(JNIEnv * const env, S3JniHook * const s, int doXDest
*/ */
static void S3JniDb_set_aside_unlocked(JNIEnv * env, S3JniDb * const s){ static void S3JniDb_set_aside_unlocked(JNIEnv * env, S3JniDb * const s){
if( s ){ if( s ){
#if SQLITE_THREADSAFE
assert( S3JniGlobal.perDb.locker == env ); assert( S3JniGlobal.perDb.locker == env );
#endif
assert(s->pPrev != s); assert(s->pPrev != s);
assert(s->pNext != s); assert(s->pNext != s);
assert(s->pPrev ? (s->pPrev!=s->pNext) : 1); assert(s->pPrev ? (s->pPrev!=s->pNext) : 1);
if(s->pNext) s->pNext->pPrev = s->pPrev; if(s->pNext) s->pNext->pPrev = s->pPrev;
if(s->pPrev) s->pPrev->pNext = s->pNext; if(s->pPrev) s->pPrev->pNext = s->pNext;
else if(SJG.perDb.aUsed == s){ else if(SJG.perDb.aHead == s){
assert(!s->pPrev); assert(!s->pPrev);
SJG.perDb.aUsed = s->pNext; SJG.perDb.aHead = s->pNext;
} }
sqlite3_free( s->zMainDbName ); sqlite3_free( s->zMainDbName );
#define UNHOOK(MEMBER,XDESTROY) S3JniHook_unref(env, &s->hooks.MEMBER, XDESTROY) #define UNHOOK(MEMBER,XDESTROY) S3JniHook_unref(env, &s->hooks.MEMBER, XDESTROY)
@ -1107,8 +1140,8 @@ static S3JniDb * S3JniDb_alloc(JNIEnv * const env, sqlite3 *pDb,
} }
} }
if( rv ){ if( rv ){
rv->pNext = SJG.perDb.aUsed; rv->pNext = SJG.perDb.aHead;
SJG.perDb.aUsed = rv; SJG.perDb.aHead = rv;
if( rv->pNext ){ if( rv->pNext ){
assert(!rv->pNext->pPrev); assert(!rv->pNext->pPrev);
rv->pNext->pPrev = rv; rv->pNext->pPrev = rv;
@ -1135,7 +1168,7 @@ static S3JniDb * S3JniDb_for_db(JNIEnv * const env, jobject jDb, sqlite3 *pDb){
S3JniDb * s = 0; S3JniDb * s = 0;
if( jDb || pDb ){ if( jDb || pDb ){
S3JniMutex_S3JniDb_enter; S3JniMutex_S3JniDb_enter;
s = SJG.perDb.aUsed; s = SJG.perDb.aHead;
if( !pDb ){ if( !pDb ){
assert( jDb ); assert( jDb );
pDb = PtrGet_sqlite3(jDb); pDb = PtrGet_sqlite3(jDb);
@ -4707,6 +4740,7 @@ Java_org_sqlite_jni_SQLite3Jni_init(JniArgsEnvClass){
{"SQLITE_MAX_TRIGGER_DEPTH", JTYPE_INT, SQLITE_MAX_TRIGGER_DEPTH}, {"SQLITE_MAX_TRIGGER_DEPTH", JTYPE_INT, SQLITE_MAX_TRIGGER_DEPTH},
{"SQLITE_LIMIT_WORKER_THREADS", JTYPE_INT, SQLITE_LIMIT_WORKER_THREADS}, {"SQLITE_LIMIT_WORKER_THREADS", JTYPE_INT, SQLITE_LIMIT_WORKER_THREADS},
{"SQLITE_MAX_WORKER_THREADS", JTYPE_INT, SQLITE_MAX_WORKER_THREADS}, {"SQLITE_MAX_WORKER_THREADS", JTYPE_INT, SQLITE_MAX_WORKER_THREADS},
{"SQLITE_THREADSAFE", JTYPE_INT, SQLITE_THREADSAFE},
{0,0} {0,0}
}; };
jfieldID fieldId; jfieldID fieldId;
@ -4714,7 +4748,7 @@ Java_org_sqlite_jni_SQLite3Jni_init(JniArgsEnvClass){
const ConfigFlagEntry * pConfFlag; const ConfigFlagEntry * pConfFlag;
if( 0==sqlite3_threadsafe() ){ if( 0==sqlite3_threadsafe() ){
(*env)->FatalError(env, "sqlite3 was not built with SQLITE_THREADSAFE."); (*env)->FatalError(env, "sqlite3 currently requires SQLITE_THREADSAFE!=0.");
return; return;
} }
memset(&S3JniGlobal, 0, sizeof(S3JniGlobal)); memset(&S3JniGlobal, 0, sizeof(S3JniGlobal));

View File

@ -1318,6 +1318,10 @@ public final class SQLite3Jni {
public static final String SQLITE_VERSION = sqlite3_libversion(); public static final String SQLITE_VERSION = sqlite3_libversion();
public static final String SQLITE_SOURCE_ID = sqlite3_sourceid(); public static final String SQLITE_SOURCE_ID = sqlite3_sourceid();
// Initialized at static init time to the build-time value of
// SQLITE_THREADSAFE.
public static int SQLITE_THREADSAFE = -1;
// access // access
public static final int SQLITE_ACCESS_EXISTS = 0; public static final int SQLITE_ACCESS_EXISTS = 0;
public static final int SQLITE_ACCESS_READWRITE = 1; public static final int SQLITE_ACCESS_READWRITE = 1;

View File

@ -1530,12 +1530,29 @@ public class Tester1 implements Runnable {
final long timeStart = System.currentTimeMillis(); final long timeStart = System.currentTimeMillis();
int nLoop = 0; int nLoop = 0;
affirm( 0==sqlite3_config( SQLITE_CONFIG_SINGLETHREAD ), switch( SQLITE_THREADSAFE ){ /* Sanity checking */
"Could not switch to single-thread mode." ); case 0:
affirm( 0==sqlite3_config( SQLITE_CONFIG_MULTITHREAD ), affirm( 0==sqlite3_config( SQLITE_CONFIG_SINGLETHREAD ),
"Could not switch to multithread mode." ); "Could not switch to single-thread mode." );
affirm( 0==sqlite3_config( SQLITE_CONFIG_SERIALIZED ), affirm( 0!=sqlite3_config( SQLITE_CONFIG_MULTITHREAD ),
"Could not switch to serialized threading mode." ); "Could switch to multithread mode." );
affirm( 0!=sqlite3_config( SQLITE_CONFIG_SERIALIZED ),
"Could not switch to serialized threading mode." );
outln("This is a single-threaded build. Not using threads.");
nThread = 1;
break;
case 1:
case 2:
affirm( 0==sqlite3_config( SQLITE_CONFIG_SINGLETHREAD ),
"Could not switch to single-thread mode." );
affirm( 0==sqlite3_config( SQLITE_CONFIG_MULTITHREAD ),
"Could not switch to multithread mode." );
affirm( 0==sqlite3_config( SQLITE_CONFIG_SERIALIZED ),
"Could not switch to serialized threading mode." );
break;
default:
affirm( false, "Unhandled SQLITE_THREADSAFE value." );
}
outln("libversion_number: ", outln("libversion_number: ",
sqlite3_libversion_number(),"\n", sqlite3_libversion_number(),"\n",
sqlite3_libversion(),"\n",SQLITE_SOURCE_ID); sqlite3_libversion(),"\n",SQLITE_SOURCE_ID);

View File

@ -1,5 +1,5 @@
C Remove\sincorrect\s(but\sharmless)\sdependency\son\ssource\scode\sfile\s"sessionfuzz-data1.db"\sfrom\smain.mk\sand\sMakefile.in. C Code\srestructuring.\sForce\sSQLITE_THREADSAFE\sin\sJNI\sbuilds\sfor\sthe\stime\sbeing,\sas\sthreadsafe==0\sleads\sto\sas-yet-mysterious\sJNI-level\sreference\serrors.
D 2023-08-25T11:06:26.590 D 2023-08-25T11:32:56.589
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -236,7 +236,7 @@ F ext/icu/sqliteicu.h fa373836ed5a1ee7478bdf8a1650689294e41d0c89c1daab26e9ae78a3
F ext/jni/GNUmakefile 2fe04e7a7534a069ea8448f42e95bb5f8fc279ea3c9883598acedc99bbc254a7 F ext/jni/GNUmakefile 2fe04e7a7534a069ea8448f42e95bb5f8fc279ea3c9883598acedc99bbc254a7
F ext/jni/README.md 1332b1fa27918bd5d9ca2d0d4f3ac3a6ab86b9e3699dc5bfe32904a027f3d2a9 F ext/jni/README.md 1332b1fa27918bd5d9ca2d0d4f3ac3a6ab86b9e3699dc5bfe32904a027f3d2a9
F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa F ext/jni/jar-dist.make 030aaa4ae71dd86e4ec5e7c1e6cd86f9dfa47c4592c070d2e35157e42498e1fa
F ext/jni/src/c/sqlite3-jni.c 969e59cdf9ad2394dcfc7b7fd3e259ff27cacfbe4946ccfdb8de6554745a32cb F ext/jni/src/c/sqlite3-jni.c 2d13c93fbf83feb2725922cc63e3157284848a435a9ff5faa8949d9157e1490f
F ext/jni/src/c/sqlite3-jni.h 3d8cdacce26d20fd967d67a2e8539d38fc2e9fe13598147399db4b2c303a89c8 F ext/jni/src/c/sqlite3-jni.h 3d8cdacce26d20fd967d67a2e8539d38fc2e9fe13598147399db4b2c303a89c8
F ext/jni/src/org/sqlite/jni/Fts5.java a45cd890202d72c3bfe8aea69b57b02b6dd588361af81d8b921954c37940b2f7 F ext/jni/src/org/sqlite/jni/Fts5.java a45cd890202d72c3bfe8aea69b57b02b6dd588361af81d8b921954c37940b2f7
F ext/jni/src/org/sqlite/jni/Fts5Context.java 0a5a02047a6a1dd3e4a38b0e542a8dd2de365033ba30e6ae019a676305959890 F ext/jni/src/org/sqlite/jni/Fts5Context.java 0a5a02047a6a1dd3e4a38b0e542a8dd2de365033ba30e6ae019a676305959890
@ -250,8 +250,8 @@ F ext/jni/src/org/sqlite/jni/Nullable.java b2f8755970e9dd0e917a505638d036ccc699c
F ext/jni/src/org/sqlite/jni/OutputPointer.java 4ae06135decef35eb04498daa2868939d91a294e948747c580ef9ce31563a6b3 F ext/jni/src/org/sqlite/jni/OutputPointer.java 4ae06135decef35eb04498daa2868939d91a294e948747c580ef9ce31563a6b3
F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7ce7797f2c6c7fca2004ff12ce20f86 F ext/jni/src/org/sqlite/jni/ResultCode.java ba701f20213a5f259e94cfbfdd36eb7ac7ce7797f2c6c7fca2004ff12ce20f86
F ext/jni/src/org/sqlite/jni/SQLFunction.java 4d6291fa14fcca1a040609378f9f00a193145d79c3abbda98ba32c340904cbeb F ext/jni/src/org/sqlite/jni/SQLFunction.java 4d6291fa14fcca1a040609378f9f00a193145d79c3abbda98ba32c340904cbeb
F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 92d0711f004327728704cc7ff33d9923be6c98dad50515093c96a99f34e13f9d F ext/jni/src/org/sqlite/jni/SQLite3Jni.java 582242f27aea55bce0d3b88dd63bea03e4cb49a8c40950ef99a7e19d9307dfb9
F ext/jni/src/org/sqlite/jni/Tester1.java 3bfbcbf0720f9b71e461eb016b8bc30289a7ceaab1aa5da13e319fd303bf19fd F ext/jni/src/org/sqlite/jni/Tester1.java c2e3d18229e9443c3e6cf54a150cfa832219ba63a0b17f7bef102e5e4b2b2a8d
F ext/jni/src/org/sqlite/jni/TesterFts5.java 6f135c60e24c89e8eecb9fe61dde0f3bb2906de668ca6c9186bcf34bdaf94629 F ext/jni/src/org/sqlite/jni/TesterFts5.java 6f135c60e24c89e8eecb9fe61dde0f3bb2906de668ca6c9186bcf34bdaf94629
F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee F ext/jni/src/org/sqlite/jni/ValueHolder.java f022873abaabf64f3dd71ab0d6037c6e71cece3b8819fa10bf26a5461dc973ee
F ext/jni/src/org/sqlite/jni/authorizer_callback.java 1d2d7fd584f917afa507820644d95504bcc9c5d7363a7afeb58de3b256851bfe F ext/jni/src/org/sqlite/jni/authorizer_callback.java 1d2d7fd584f917afa507820644d95504bcc9c5d7363a7afeb58de3b256851bfe
@ -2100,8 +2100,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P 646e7fc3b5ba81c207f013c9a06781986138379f20e787320a811ba3ed5489dc P 17d56c0207f63614b34ef3594d06602ab7a6e85604f3589b30aa79316f1744ee
R b08c9d8a6a2483e5f535446bff4911b8 R 0d04424045f1de02bc280794aad48b2b
U dan U stephan
Z 116a690cb7924a8fc2ec464347e1c482 Z a29af0815bfb2df6f8dd8b5fbb60db4d
# Remove this line to create a well-formed Fossil manifest. # Remove this line to create a well-formed Fossil manifest.

View File

@ -1 +1 @@
17d56c0207f63614b34ef3594d06602ab7a6e85604f3589b30aa79316f1744ee 5a099caa2c21bec647f0a521e7f5d0d1cc2f96d388d3d6c53d5ec80947f33e8d