Skip to content

Commit ee43633

Browse files
Revert "Use Android shared library loader for JNI libraries (#10376)"
This reverts commit 064f23f. We are seeing `dotnet new maui` projects fail to debug, as they appear to be stuck in a loop on startup: 08-22 11:39:14.148 W/monodroid-assembly(10269): Timeout while waiting for shared library '/data/user/0/com.companyname.mauiapp14/files/.__override__/arm64-v8a/libSystem.Security.Cryptography.Native.Android.so' to load. 08-22 11:39:17.155 W/monodroid-assembly(10269): Timeout while waiting for shared library '/data/app/~~Q5yyfDmzDqX9Z8UwQnLoFA==/com.companyname.mauiapp14-ZPk2_y6fT3b_3leM8xhcAw==/lib/arm64/libSystem.Security.Cryptography.Native.Android.so' to load. 08-22 11:39:20.164 W/monodroid-assembly(10269): Timeout while waiting for shared library '/data/user/0/com.companyname.mauiapp14/files/.__override__/arm64-v8a/libSystem.Security.Cryptography.Native.Android' to load. 08-22 11:39:23.172 W/monodroid-assembly(10269): Timeout while waiting for shared library '/data/app/~~Q5yyfDmzDqX9Z8UwQnLoFA==/com.companyname.mauiapp14-ZPk2_y6fT3b_3leM8xhcAw==/lib/arm64/libSystem.Security.Cryptography.Native.Android' to load. 08-22 11:39:23.172 W/monodroid-assembly(10269): Shared library 'libSystem.Security.Cryptography.Native.Android' not loaded, p/invoke 'AndroidCryptoNative_SSLStreamInitialize' may fail 08-22 11:39:23.172 F/monodroid-assembly(10269): Failed to load symbol 'AndroidCryptoNative_SSLStreamInitialize' from shared library 'libSystem.Security.Cryptography.Native.Android' 08-22 11:39:26.187 W/monodroid-assembly(10269): Timeout while waiting for shared library '/data/user/0/com.companyname.mauiapp14/files/.__override__/arm64-v8a/libSystem.Security.Cryptography.Native.Android.so' to load. 08-22 11:39:29.190 W/monodroid-assembly(10269): Timeout while waiting for shared library '/data/app/~~Q5yyfDmzDqX9Z8UwQnLoFA==/com.companyname.mauiapp14-ZPk2_y6fT3b_3leM8xhcAw==/lib/arm64/libSystem.Security.Cryptography.Native.Android.so' to load. 08-22 11:39:32.198 W/monodroid-assembly(10269): Timeout while waiting for shared library '/data/user/0/com.companyname.mauiapp14/files/.__override__/arm64-v8a/libSystem.Security.Cryptography.Native.Android' to load. repeating...
1 parent f234612 commit ee43633

30 files changed

+263
-844
lines changed

src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGenerator.cs

Lines changed: 12 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ sealed class DSOCacheEntry
6161
[NativeAssembler (NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)]
6262
public ulong real_name_hash;
6363
public bool ignore;
64-
public bool is_jni_library;
6564

6665
[NativeAssembler (UsesDataProvider = true)]
6766
public string? name;
@@ -157,19 +156,14 @@ sealed class XamarinAndroidBundledAssembly
157156
}
158157
#pragma warning restore CS0649
159158

160-
sealed class DsoCacheState
161-
{
162-
public List<StructureInstance<DSOCacheEntry>> DsoCache = [];
163-
public List<DSOCacheEntry> JniPreloadDSOs = [];
164-
public List<StructureInstance<DSOCacheEntry>> AotDsoCache = [];
165-
}
166-
167159
// Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh
168160
const ulong FORMAT_TAG = 0x00025E6972616D58; // 'Xmari^XY' where XY is the format version
169161

170162
SortedDictionary <string, string>? environmentVariables;
171163
SortedDictionary <string, string>? systemProperties;
172164
StructureInstance? application_config;
165+
List<StructureInstance<DSOCacheEntry>>? dsoCache;
166+
List<StructureInstance<DSOCacheEntry>>? aotDsoCache;
173167
List<StructureInstance<XamarinAndroidBundledAssembly>>? xamarinAndroidBundledAssemblies;
174168

175169
StructureInfo? applicationConfigStructureInfo;
@@ -235,7 +229,7 @@ protected override void Construct (LlvmIrModule module)
235229
};
236230
module.Add (sysProps, stringGroupName: "sysprop", stringGroupComment: " System properties name:value pairs");
237231

238-
DsoCacheState dsoState = InitDSOCache ();
232+
(dsoCache, aotDsoCache) = InitDSOCache ();
239233
var app_cfg = new ApplicationConfig {
240234
uses_mono_llvm = UsesMonoLLVM,
241235
uses_mono_aot = UsesMonoAOT,
@@ -255,8 +249,8 @@ protected override void Construct (LlvmIrModule module)
255249
number_of_assemblies_in_apk = (uint)NumberOfAssembliesInApk,
256250
number_of_shared_libraries = (uint)NativeLibraries.Count,
257251
bundled_assembly_name_width = (uint)BundledAssemblyNameWidth,
258-
number_of_dso_cache_entries = (uint)dsoState.DsoCache.Count,
259-
number_of_aot_cache_entries = (uint)dsoState.AotDsoCache.Count,
252+
number_of_dso_cache_entries = (uint)dsoCache.Count,
253+
number_of_aot_cache_entries = (uint)aotDsoCache.Count,
260254
android_runtime_jnienv_class_token = (uint)AndroidRuntimeJNIEnvToken,
261255
jnienv_initialize_method_token = (uint)JNIEnvInitializeToken,
262256
jnienv_registerjninatives_method_token = (uint)JNIEnvRegisterJniNativesToken,
@@ -268,23 +262,13 @@ protected override void Construct (LlvmIrModule module)
268262
application_config = new StructureInstance<ApplicationConfig> (applicationConfigStructureInfo, app_cfg);
269263
module.AddGlobalVariable ("application_config", application_config);
270264

271-
var dso_cache = new LlvmIrGlobalVariable (dsoState.DsoCache, "dso_cache", LlvmIrVariableOptions.GlobalWritable) {
265+
var dso_cache = new LlvmIrGlobalVariable (dsoCache, "dso_cache", LlvmIrVariableOptions.GlobalWritable) {
272266
Comment = " DSO cache entries",
273267
BeforeWriteCallback = HashAndSortDSOCache,
274268
};
275269
module.Add (dso_cache);
276270

277-
// This variable MUST be written after `dso_cache` since it relies on sorting performed by HashAndSortDSOCache
278-
var dso_jni_preloads_idx = new LlvmIrGlobalVariable (new List<uint> (), "dso_jni_preloads_idx", LlvmIrVariableOptions.GlobalConstant) {
279-
Comment = " Indices into dso_cache[] of DSO libraries to preload because of JNI use",
280-
ArrayItemCount = (uint)dsoState.JniPreloadDSOs.Count,
281-
BeforeWriteCallback = PopulatePreloadIndices,
282-
BeforeWriteCallbackCallerState = dsoState,
283-
};
284-
module.AddGlobalVariable ("dso_jni_preloads_idx_count", dso_jni_preloads_idx.ArrayItemCount);
285-
module.Add (dso_jni_preloads_idx);
286-
287-
var aot_dso_cache = new LlvmIrGlobalVariable (dsoState.AotDsoCache, "aot_dso_cache", LlvmIrVariableOptions.GlobalWritable) {
271+
var aot_dso_cache = new LlvmIrGlobalVariable (aotDsoCache, "aot_dso_cache", LlvmIrVariableOptions.GlobalWritable) {
288272
Comment = " AOT DSO cache entries",
289273
BeforeWriteCallback = HashAndSortDSOCache,
290274
};
@@ -346,36 +330,6 @@ void AddAssemblyStores (LlvmIrModule module)
346330
module.Add (assembly_store);
347331
}
348332

349-
void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
350-
{
351-
var indices = variable.Value as List<uint>;
352-
if (indices == null) {
353-
throw new InvalidOperationException ($"Internal error: DSO preload indices list instance not present.");
354-
}
355-
356-
var dsoState = state as DsoCacheState;
357-
if (dsoState == null) {
358-
throw new InvalidOperationException ($"Internal error: DSO state not present.");
359-
}
360-
361-
foreach (DSOCacheEntry preload in dsoState.JniPreloadDSOs) {
362-
int dsoIdx = dsoState.DsoCache.FindIndex (entry => {
363-
if (entry.Instance == null) {
364-
return false;
365-
}
366-
367-
return entry.Instance.hash == preload.hash && entry.Instance.real_name_hash == preload.real_name_hash;
368-
});
369-
370-
if (dsoIdx == -1) {
371-
throw new InvalidOperationException ($"Internal error: DSO entry in JNI preload list not found in the DSO cache list.");
372-
}
373-
374-
indices.Add ((uint)dsoIdx);
375-
}
376-
indices.Sort ();
377-
}
378-
379333
void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
380334
{
381335
var cache = variable.Value as List<StructureInstance<DSOCacheEntry>>;
@@ -404,9 +358,9 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob
404358
});
405359
}
406360

407-
DsoCacheState InitDSOCache ()
361+
(List<StructureInstance<DSOCacheEntry>> dsoCache, List<StructureInstance<DSOCacheEntry>> aotDsoCache) InitDSOCache ()
408362
{
409-
var dsos = new List<(string name, string nameLabel, bool ignore, ITaskItem item)> ();
363+
var dsos = new List<(string name, string nameLabel, bool ignore)> ();
410364
var nameCache = new HashSet<string> (StringComparer.OrdinalIgnoreCase);
411365

412366
foreach (ITaskItem item in NativeLibraries) {
@@ -420,37 +374,26 @@ DsoCacheState InitDSOCache ()
420374
continue;
421375
}
422376

423-
dsos.Add ((name, $"dsoName{dsos.Count.ToString (CultureInfo.InvariantCulture)}", ELFHelper.IsEmptyAOTLibrary (Log, item.ItemSpec), item));
377+
dsos.Add ((name, $"dsoName{dsos.Count.ToString (CultureInfo.InvariantCulture)}", ELFHelper.IsEmptyAOTLibrary (Log, item.ItemSpec)));
424378
}
425379

426380
var dsoCache = new List<StructureInstance<DSOCacheEntry>> ();
427-
var jniPreloads = new List<DSOCacheEntry> ();
428381
var aotDsoCache = new List<StructureInstance<DSOCacheEntry>> ();
429382
var nameMutations = new List<string> ();
430383

431384
for (int i = 0; i < dsos.Count; i++) {
432385
string name = dsos[i].name;
433-
434-
bool isJniLibrary = ELFHelper.IsJniLibrary (Log, dsos[i].item.ItemSpec);
435-
bool ignore = dsos[i].ignore;
436-
437386
nameMutations.Clear();
438387
AddNameMutations (name);
439-
440388
// All mutations point to the actual library name, but have hash of the mutated one
441389
foreach (string entryName in nameMutations) {
442390
var entry = new DSOCacheEntry {
443391
HashedName = entryName,
444392
hash = 0, // Hash is arch-specific, we compute it before writing
445-
ignore = ignore,
446-
is_jni_library = isJniLibrary,
393+
ignore = dsos[i].ignore,
447394
name = name,
448395
};
449396

450-
if (entry.is_jni_library && entry.HashedName == name && !ApplicationConfigNativeAssemblyGeneratorCLR.DsoCacheJniPreloadIgnore.Contains (name)) {
451-
jniPreloads.Add (entry);
452-
}
453-
454397
var item = new StructureInstance<DSOCacheEntry> (dsoCacheEntryStructureInfo, entry);
455398
if (name.StartsWith ("libaot-", StringComparison.OrdinalIgnoreCase)) {
456399
aotDsoCache.Add (item);
@@ -460,11 +403,7 @@ DsoCacheState InitDSOCache ()
460403
}
461404
}
462405

463-
return new DsoCacheState {
464-
DsoCache = dsoCache,
465-
AotDsoCache = aotDsoCache,
466-
JniPreloadDSOs = jniPreloads,
467-
};
406+
return (dsoCache, aotDsoCache);
468407

469408
void AddNameMutations (string name)
470409
{

src/Xamarin.Android.Build.Tasks/Utilities/ApplicationConfigNativeAssemblyGeneratorCLR.cs

Lines changed: 16 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ sealed class DSOCacheEntry
5959
[NativeAssembler (NumberFormat = LlvmIrVariableNumberFormat.Hexadecimal)]
6060
public ulong real_name_hash;
6161
public bool ignore;
62-
public bool is_jni_library;
6362

6463
[NativeAssembler (UsesDataProvider = true)]
6564
public uint name_index;
@@ -239,27 +238,15 @@ sealed class XamarinAndroidBundledAssembly
239238
}
240239
#pragma warning restore CS0649
241240

242-
sealed class DsoCacheState
243-
{
244-
public List<StructureInstance<DSOCacheEntry>> DsoCache = [];
245-
public List<DSOCacheEntry> JniPreloadDSOs = [];
246-
public List<StructureInstance<DSOCacheEntry>> AotDsoCache = [];
247-
public LlvmIrStringBlob NamesBlob = null!;
248-
}
249-
250241
// Keep in sync with FORMAT_TAG in src/monodroid/jni/xamarin-app.hh
251242
const ulong FORMAT_TAG = 0x00025E6972616D58; // 'Xmari^XY' where XY is the format version
252243

253-
// List of library names to ignore when generating the list of JNI-using libraries to preload
254-
internal static readonly HashSet<string> DsoCacheJniPreloadIgnore = new (StringComparer.OrdinalIgnoreCase) {
255-
"libmonodroid.so",
256-
};
257-
258244
SortedDictionary <string, string>? environmentVariables;
259245
SortedDictionary <string, string>? systemProperties;
260246
SortedDictionary <string, string>? runtimeProperties;
261247
StructureInstance? application_config;
262-
248+
List<StructureInstance<DSOCacheEntry>>? dsoCache;
249+
List<StructureInstance<DSOCacheEntry>>? aotDsoCache;
263250
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value - assigned conditionally by build process
264251
List<StructureInstance<XamarinAndroidBundledAssembly>>? xamarinAndroidBundledAssemblies;
265252
#pragma warning restore CS0649
@@ -274,10 +261,10 @@ sealed class DsoCacheState
274261
StructureInfo? assemblyStoreRuntimeDataStructureInfo;
275262
StructureInfo? runtimePropertyStructureInfo;
276263
StructureInfo? runtimePropertyIndexEntryStructureInfo;
277-
#pragma warning disable CS0169 // Field is never used - might be used in future versions
264+
#pragma warning disable CS0169 // Field is never used - might be used in future versions
278265
StructureInfo? hostConfigurationPropertyStructureInfo;
279266
#pragma warning restore CS0169
280-
#pragma warning disable CS0169 // Field is never used - might be used in future versions
267+
#pragma warning disable CS0169 // Field is never used - might be used in future versions
281268
StructureInfo? hostConfigurationPropertiesStructureInfo;
282269
#pragma warning restore CS0169
283270
StructureInfo? appEnvironmentVariableStructureInfo;
@@ -362,7 +349,7 @@ protected override void Construct (LlvmIrModule module)
362349
};
363350
module.Add (sysProps, stringGroupName: "sysprop", stringGroupComment: " System properties name:value pairs");
364351

365-
DsoCacheState dsoState = InitDSOCache ();
352+
(dsoCache, aotDsoCache, LlvmIrStringBlob dsoNamesBlob) = InitDSOCache ();
366353
var app_cfg = new ApplicationConfigCLR {
367354
uses_assembly_preload = UsesAssemblyPreload,
368355
jni_add_native_method_registration_attribute_present = JniAddNativeMethodRegistrationAttributePresent,
@@ -376,8 +363,8 @@ protected override void Construct (LlvmIrModule module)
376363
number_of_assemblies_in_apk = (uint)NumberOfAssembliesInApk,
377364
number_of_shared_libraries = (uint)NativeLibraries.Count,
378365
bundled_assembly_name_width = (uint)BundledAssemblyNameWidth,
379-
number_of_dso_cache_entries = (uint)dsoState.DsoCache.Count,
380-
number_of_aot_cache_entries = (uint)dsoState.AotDsoCache.Count,
366+
number_of_dso_cache_entries = (uint)dsoCache.Count,
367+
number_of_aot_cache_entries = (uint)aotDsoCache.Count,
381368
android_runtime_jnienv_class_token = (uint)AndroidRuntimeJNIEnvToken,
382369
jnienv_initialize_method_token = (uint)JNIEnvInitializeToken,
383370
jnienv_registerjninatives_method_token = (uint)JNIEnvRegisterJniNativesToken,
@@ -388,28 +375,18 @@ protected override void Construct (LlvmIrModule module)
388375
application_config = new StructureInstance<ApplicationConfigCLR> (applicationConfigStructureInfo, app_cfg);
389376
module.AddGlobalVariable ("application_config", application_config);
390377

391-
var dso_cache = new LlvmIrGlobalVariable (dsoState.DsoCache, "dso_cache", LlvmIrVariableOptions.GlobalWritable) {
378+
var dso_cache = new LlvmIrGlobalVariable (dsoCache, "dso_cache", LlvmIrVariableOptions.GlobalWritable) {
392379
Comment = " DSO cache entries",
393380
BeforeWriteCallback = HashAndSortDSOCache,
394381
};
395382
module.Add (dso_cache);
396383

397-
// This variable MUST be written after `dso_cache` since it relies on sorting performed by HashAndSortDSOCache
398-
var dso_jni_preloads_idx = new LlvmIrGlobalVariable (new List<uint> (), "dso_jni_preloads_idx", LlvmIrVariableOptions.GlobalConstant) {
399-
Comment = " Indices into dso_cache[] of DSO libraries to preload because of JNI use",
400-
ArrayItemCount = (uint)dsoState.JniPreloadDSOs.Count,
401-
BeforeWriteCallback = PopulatePreloadIndices,
402-
BeforeWriteCallbackCallerState = dsoState,
403-
};
404-
module.AddGlobalVariable ("dso_jni_preloads_idx_count", dso_jni_preloads_idx.ArrayItemCount);
405-
module.Add (dso_jni_preloads_idx);
406-
407-
var aot_dso_cache = new LlvmIrGlobalVariable (dsoState.AotDsoCache, "aot_dso_cache", LlvmIrVariableOptions.GlobalWritable) {
384+
var aot_dso_cache = new LlvmIrGlobalVariable (aotDsoCache, "aot_dso_cache", LlvmIrVariableOptions.GlobalWritable) {
408385
Comment = " AOT DSO cache entries",
409386
BeforeWriteCallback = HashAndSortDSOCache,
410387
};
411388
module.Add (aot_dso_cache);
412-
module.AddGlobalVariable ("dso_names_data", dsoState.NamesBlob, LlvmIrVariableOptions.GlobalConstant);
389+
module.AddGlobalVariable ("dso_names_data", dsoNamesBlob, LlvmIrVariableOptions.GlobalConstant);
413390

414391
var dso_apk_entries = new LlvmIrGlobalVariable (typeof(List<StructureInstance<DSOApkEntry>>), "dso_apk_entries") {
415392
ArrayItemCount = (ulong)NativeLibraries.Count,
@@ -566,36 +543,6 @@ void AddAssemblyStores (LlvmIrModule module)
566543
module.Add (assembly_store);
567544
}
568545

569-
void PopulatePreloadIndices (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
570-
{
571-
var indices = variable.Value as List<uint>;
572-
if (indices == null) {
573-
throw new InvalidOperationException ($"Internal error: DSO preload indices list instance not present.");
574-
}
575-
576-
var dsoState = state as DsoCacheState;
577-
if (dsoState == null) {
578-
throw new InvalidOperationException ($"Internal error: DSO state not present.");
579-
}
580-
581-
foreach (DSOCacheEntry preload in dsoState.JniPreloadDSOs) {
582-
int dsoIdx = dsoState.DsoCache.FindIndex (entry => {
583-
if (entry.Instance == null) {
584-
return false;
585-
}
586-
587-
return entry.Instance.hash == preload.hash && entry.Instance.real_name_hash == preload.real_name_hash;
588-
});
589-
590-
if (dsoIdx == -1) {
591-
throw new InvalidOperationException ($"Internal error: DSO entry in JNI preload list not found in the DSO cache list.");
592-
}
593-
594-
indices.Add ((uint)dsoIdx);
595-
}
596-
indices.Sort ();
597-
}
598-
599546
void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, object? state)
600547
{
601548
var cache = variable.Value as List<StructureInstance<DSOCacheEntry>>;
@@ -624,9 +571,10 @@ void HashAndSortDSOCache (LlvmIrVariable variable, LlvmIrModuleTarget target, ob
624571
});
625572
}
626573

627-
DsoCacheState InitDSOCache ()
574+
(List<StructureInstance<DSOCacheEntry>> dsoCache, List<StructureInstance<DSOCacheEntry>> aotDsoCache, LlvmIrStringBlob namesBlob)
575+
InitDSOCache ()
628576
{
629-
var dsos = new List<(string name, string nameLabel, bool ignore, ITaskItem item)> ();
577+
var dsos = new List<(string name, string nameLabel, bool ignore)> ();
630578
var nameCache = new HashSet<string> (StringComparer.OrdinalIgnoreCase);
631579

632580
foreach (ITaskItem item in NativeLibraries) {
@@ -640,11 +588,10 @@ DsoCacheState InitDSOCache ()
640588
continue;
641589
}
642590

643-
dsos.Add ((name, $"dsoName{dsos.Count.ToString (CultureInfo.InvariantCulture)}", ELFHelper.IsEmptyAOTLibrary (Log, item.ItemSpec), item));
591+
dsos.Add ((name, $"dsoName{dsos.Count.ToString (CultureInfo.InvariantCulture)}", ELFHelper.IsEmptyAOTLibrary (Log, item.ItemSpec)));
644592
}
645593

646594
var dsoCache = new List<StructureInstance<DSOCacheEntry>> ();
647-
var jniPreloads = new List<DSOCacheEntry> ();
648595
var aotDsoCache = new List<StructureInstance<DSOCacheEntry>> ();
649596
var nameMutations = new List<string> ();
650597
var dsoNamesBlob = new LlvmIrStringBlob ();
@@ -653,9 +600,6 @@ DsoCacheState InitDSOCache ()
653600
string name = dsos[i].name;
654601
(int nameOffset, _) = dsoNamesBlob.Add (name);
655602

656-
bool isJniLibrary = ELFHelper.IsJniLibrary (Log, dsos[i].item.ItemSpec);
657-
bool ignore = dsos[i].ignore;
658-
659603
nameMutations.Clear();
660604
AddNameMutations (name);
661605
// All mutations point to the actual library name, but have hash of the mutated one
@@ -665,15 +609,10 @@ DsoCacheState InitDSOCache ()
665609
RealName = name,
666610

667611
hash = 0, // Hash is arch-specific, we compute it before writing
668-
ignore = ignore,
669-
is_jni_library = isJniLibrary,
612+
ignore = dsos[i].ignore,
670613
name_index = (uint)nameOffset,
671614
};
672615

673-
if (entry.is_jni_library && entry.HashedName == name && !DsoCacheJniPreloadIgnore.Contains (name)) {
674-
jniPreloads.Add (entry);
675-
}
676-
677616
var item = new StructureInstance<DSOCacheEntry> (dsoCacheEntryStructureInfo, entry);
678617
if (name.StartsWith ("libaot-", StringComparison.OrdinalIgnoreCase)) {
679618
aotDsoCache.Add (item);
@@ -683,12 +622,7 @@ DsoCacheState InitDSOCache ()
683622
}
684623
}
685624

686-
return new DsoCacheState {
687-
DsoCache = dsoCache,
688-
JniPreloadDSOs = jniPreloads,
689-
AotDsoCache = aotDsoCache,
690-
NamesBlob = dsoNamesBlob,
691-
};
625+
return (dsoCache, aotDsoCache, dsoNamesBlob);
692626

693627
void AddNameMutations (string name)
694628
{

0 commit comments

Comments
 (0)