2121import stroom .util .logging .LambdaLogger ;
2222import stroom .util .logging .LambdaLoggerFactory ;
2323import stroom .util .logging .LogUtil ;
24+ import stroom .util .shared .NullSafe ;
2425
2526import jakarta .inject .Inject ;
2627import jakarta .inject .Provider ;
3132import java .nio .file .Files ;
3233import java .nio .file .Path ;
3334import java .util .Objects ;
34- import java .util .Optional ;
3535
3636@ Singleton // The LMDB lib is dealt with statically by LMDB java so only want to initialise it once
3737public class LmdbLibrary {
@@ -67,49 +67,107 @@ private synchronized void configureLibraryUnderLock(final LmdbLibraryConfig lmdb
6767 if (!HAS_LIBRARY_BEEN_CONFIGURED ) {
6868 LOGGER .info ("Configuring LMDB system library" );
6969
70- final Path lmdbSystemLibraryPath = Optional .ofNullable (lmdbLibraryConfig .getProvidedSystemLibraryPath ())
71- .map (pathCreator ::toAppPath )
72- .orElse (null );
70+ LOGGER .debug (() -> LogUtil .message ("""
71+ configureLibraryUnderLock() - lmdbLibraryConfig: {},
72+ {},
73+ {},
74+ {},
75+ {}""" ,
76+ lmdbLibraryConfig ,
77+ dumpSystemProp (LmdbLibraryConfig .LMDB_EXTRACT_DIR_PROP ),
78+ dumpSystemProp (LmdbLibraryConfig .LMDB_NATIVE_LIB_PROP ),
79+ dumpSystemProp (LmdbLibraryConfig .JFFI_EXTRACT_DIR_PROP ),
80+ dumpSystemProp (LmdbLibraryConfig .JFFI_NATIVE_LIB_PROP )));
81+
82+ final Path lmdbSystemLibraryPath = NullSafe .get (
83+ lmdbLibraryConfig .getProvidedSystemLibraryPath (),
84+ pathCreator ::toAppPath );
85+ final Path jffiNativeLibraryPath = NullSafe .get (
86+ lmdbLibraryConfig .getProvidedJffiLibraryPath (),
87+ pathCreator ::toAppPath );
88+
89+ boolean extractLmdb = false ;
90+ boolean extractJffi = false ;
7391
7492 if (lmdbSystemLibraryPath != null ) {
7593 if (!Files .isReadable (lmdbSystemLibraryPath )) {
76- throw new RuntimeException ("Unable to read LMDB system library at " +
77- lmdbSystemLibraryPath .toAbsolutePath ().normalize ());
94+ throw new RuntimeException ("Unable to read LMDB system library at " + lmdbSystemLibraryPath );
7895 }
7996 // jakarta.validation should ensure the path is valid if set
8097 final String lmdbNativeLibProp = LmdbLibraryConfig .LMDB_NATIVE_LIB_PROP ;
81- System .setProperty (lmdbNativeLibProp , lmdbSystemLibraryPath .toAbsolutePath (). normalize (). toString ());
82- LOGGER .info ("Using provided LMDB system library file. Setting prop {} to '{}'" ,
98+ System .setProperty (lmdbNativeLibProp , lmdbSystemLibraryPath .toString ());
99+ LOGGER .info ("Using provided LMDB native library file. Setting prop {} to '{}'" ,
83100 lmdbNativeLibProp , lmdbSystemLibraryPath );
84101 } else {
85- final Path systemLibraryExtractDir = getLibraryExtractDir (lmdbLibraryConfig );
102+ extractLmdb = true ;
103+ }
104+
105+ final String jffiNativeLibProp = LmdbLibraryConfig .JFFI_NATIVE_LIB_PROP ;
106+ final String jffiPropVal = System .getenv (jffiNativeLibProp );
107+ if (NullSafe .isNonBlankString (jffiPropVal )) {
108+ LOGGER .info ("Using provided JFFI native library file. Property {} already set to '{}'" ,
109+ jffiNativeLibProp , jffiPropVal );
110+ } else if (jffiNativeLibraryPath != null ) {
111+ if (!Files .isReadable (jffiNativeLibraryPath )) {
112+ throw new RuntimeException ("Unable to read JFFI native library at " + jffiNativeLibraryPath );
113+ }
114+ // jakarta.validation should ensure the path is valid if set
115+ System .setProperty (jffiNativeLibProp , jffiNativeLibraryPath .toString ());
116+ LOGGER .info ("Using provided JFFI native library file. Setting prop {} to '{}'" ,
117+ jffiNativeLibProp , lmdbSystemLibraryPath );
118+ } else {
119+ extractJffi = true ;
120+ }
86121
87- // LMDB extracts the lib on boot to a unique temp file and should delete on JVM exit,
122+ LOGGER .debug ("configureLibraryUnderLock() - extractLmdb: {}, extractJffi: {}" , extractLmdb , extractJffi );
123+ if (extractLmdb || extractJffi ) {
124+ final Path systemLibraryExtractDir = getLibraryExtractDir (lmdbLibraryConfig );
125+ // LMDB/JFFI extract the lib on boot to a unique temp file and should delete on JVM exit,
88126 // but just in case clear out any old ones.
89127 cleanUpExtractDir (systemLibraryExtractDir );
90128
129+ // Extract them both to the same place as both will need a non 'noexec' mount.
130+
91131 // Set the location to extract the bundled LMDB binary to
92- final String lmdbExtractDirProp = LmdbLibraryConfig .LMDB_EXTRACT_DIR_PROP ;
93- System .setProperty (
94- lmdbExtractDirProp ,
95- systemLibraryExtractDir .toAbsolutePath ().normalize ().toString ());
96- LOGGER .info ("Bundled LMDB system library binary will be extracted. Setting prop {} to '{}'" ,
97- lmdbExtractDirProp , systemLibraryExtractDir );
98- HAS_LIBRARY_BEEN_CONFIGURED = true ;
132+ if (extractLmdb ) {
133+ final String lmdbExtractDirProp = LmdbLibraryConfig .LMDB_EXTRACT_DIR_PROP ;
134+ System .setProperty (lmdbExtractDirProp , systemLibraryExtractDir .toString ());
135+ LOGGER .info ("Bundled LMDB native library binary will be extracted and used. " +
136+ "Setting system prop '{}' to '{}'" ,
137+ lmdbExtractDirProp , systemLibraryExtractDir );
138+ }
139+
140+ // Set the location to extract the bundled Jffi binary to
141+ if (extractJffi ) {
142+ final String jffiExtractDirProp = LmdbLibraryConfig .JFFI_EXTRACT_DIR_PROP ;
143+ if (System .getProperty (jffiExtractDirProp ) == null ) {
144+ // Allow a jvm arg to override our config
145+ System .setProperty (jffiExtractDirProp , systemLibraryExtractDir .toString ());
146+ LOGGER .info ("Bundled JFFI native library binary will be extracted and used. " +
147+ "Setting system prop '{}' to '{}'" ,
148+ jffiExtractDirProp , systemLibraryExtractDir );
149+ }
150+ }
99151 }
152+ HAS_LIBRARY_BEEN_CONFIGURED = true ;
100153 } else {
101154 LOGGER .debug ("Another thread beat us to it" );
102155 }
103156 }
104157
158+ private String dumpSystemProp (final String propName ) {
159+ return "'" + propName + "': '" + System .getProperty (propName ) + "'" ;
160+ }
161+
105162 private Path getLibraryExtractDir (final LmdbLibraryConfig lmdbLibraryConfig ) {
106163 final String extractDirStr = lmdbLibraryConfig .getSystemLibraryExtractDir ();
107164
108165 final String extractDirPropName = lmdbLibraryConfig .getFullPathStr (LmdbLibraryConfig .EXTRACT_DIR_PROP_NAME );
109166
110167 Path extractDir ;
111168 if (extractDirStr == null ) {
112- LOGGER .warn ("LMDB system library extract dir is not set ({}), falling back to temporary directory {}." ,
169+ LOGGER .warn ("LMDB system library extract dir is not set ({}), falling back to temporary directory {}. " +
170+ "If the temporary directory is mounted with 'noexec' any use of LMDB will error." ,
113171 extractDirPropName ,
114172 tempDirProvider .get ());
115173
@@ -119,6 +177,7 @@ private Path getLibraryExtractDir(final LmdbLibraryConfig lmdbLibraryConfig) {
119177 } else {
120178 extractDir = pathCreator .toAppPath (extractDirStr );
121179 }
180+ extractDir = extractDir .toAbsolutePath ().normalize ();
122181
123182 try {
124183 if (LOGGER .isDebugEnabled ()) {
@@ -152,21 +211,27 @@ private Path getLibraryExtractDir(final LmdbLibraryConfig lmdbLibraryConfig) {
152211 }
153212
154213 private void cleanUpExtractDir (final Path extractDir ) {
155- try (final DirectoryStream <Path > directoryStream = Files .newDirectoryStream (
156- extractDir , "lmdbjava-native-library-*.so" )) {
214+ cleanUpExtractDir (extractDir , "lmdbjava-native-library-*.so" , "LMDB native library" );
215+ // The jffi file should not really ever be there because it is deleted once loaded, but just in case
216+ // See com.kenai.jffi.internal.StubLoader
217+ cleanUpExtractDir (extractDir , "jffi*.so" , "JFFI native library" );
218+ }
157219
158- for (final Path redundantLibraryFilePath : directoryStream ) {
159- LOGGER .info ("Deleting redundant LMDB library file "
160- + redundantLibraryFilePath .toAbsolutePath ().normalize ());
220+ private static void cleanUpExtractDir (final Path extractDir , final String glob , final String name ) {
221+ try (final DirectoryStream <Path > directoryStream = Files .newDirectoryStream (extractDir , glob )) {
222+ for (final Path file : directoryStream ) {
223+ LOGGER .info ("Deleting redundant {} file {}" , name , file .toAbsolutePath ().normalize ());
161224 try {
162- Files .deleteIfExists (redundantLibraryFilePath );
225+ Files .deleteIfExists (file );
163226 } catch (final IOException e ) {
164- LOGGER .error ("Unable to delete file " + extractDir .toAbsolutePath ().normalize (), e );
227+ LOGGER .error (() -> LogUtil .message ("Unable to delete {} file {}" ,
228+ name , file .toAbsolutePath ().normalize ()), e );
165229 // swallow error as these old files don't matter really
166230 }
167231 }
168232 } catch (final IOException e ) {
169- throw new RuntimeException ("Error listing contents of " + extractDir .toAbsolutePath ().normalize ());
233+ throw new RuntimeException (LogUtil .message ("Error listing contents of {} with glob '{}'" ,
234+ extractDir .toAbsolutePath ().normalize (), glob ));
170235 }
171236 }
172237}
0 commit comments