Skip to content

Commit b8d0b1b

Browse files
author
Tor Didriksen
committed
WL#13759 MySQL 8.0: Support linking with 3rd party OpenLDAP and Cyrus SASL
This worklog affects standalone Linux builds only, i.e. where we build MySQL with a custom version of OpenSSL 1.1.1 rather than the system version. In the past we have been using "system" versions of LDAP and SASL (which then use system SSL), while still building the rest of MySQL with the custom SSL. This does not work: all executables and libraries must use the same version of OpenSSL. Pushbuild will now build LDAP/SASL/SSL as one "package" and invoke cmake with -DWITH_LDAP=</path/to/ldap> -DWITH_SASL=</path/to/sasl> -DWITH_SSL=<path/to/ssl> These builds depend on each other, so that the SASL build will use the SSL build, the LDAP build will use the SASL and SSL build. The result is a set of headers and shared libraries which consistently use OpenSSL 1.1.1 The following libraries are copied into <build_dir>/library_output_directory during build: libcrypto.so liblber.so libldap_r.so libsasl2.so libssl.so The SASL SCRAM plugin (libscram.so) is copied to <build_dir>/library_output_directory/sasl2 During 'make install' they are all copied into <install dir>/lib/private/ and <install dir>/lib/private/sasl2. Libraries are patched with patchelf(1), to set RPATH to $ORIGIN. For standalone linux builds, the cmake options for LDAP/SASL/SSL must either all be "system", or they must all be custom paths (in which case the patchelf(1) utility is required on the build host). The patch also changes the default value for the cmake option WITH_AUTHENTICATION_LDAP to ON for commercial builds. We also enable some tests which were previously skipped because of bad feature tests. Bugfix for windows: copy SCRAM plugins to runtime_output_directory. This fixes four broken tests for mtr --suite=auth_ldap Bugfix for windows: move code for finding and setting SASL plugin path to plugin initialization, where it is thread safe. Move calls to sasl_client_init() and sasl_done()/sasl_client_done() to plugin initializer/deinitializer respectively. Add LeakSanitizer and Valgrind suppressions for a known OpenLDAP bug. The patch also implements -DWITH_KERBEROS=system for more Unices: debian/ubuntu and Solaris -DWITH_KERBEROS=<path/to/kerberos> is *not* implemented, we postpone that to a followup patch. Change-Id: Ibdfcd703243d34a33b35cc7df77c153edf5acbd0
1 parent 02fe858 commit b8d0b1b

21 files changed

+1070
-325
lines changed

CMakeLists.txt

Lines changed: 88 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ INCLUDE(icu)
516516
INCLUDE(libevent)
517517
INCLUDE(ssl)
518518
INCLUDE(sasl)
519+
INCLUDE(ldap)
519520
INCLUDE(kerberos)
520521
INCLUDE(rpc)
521522
INCLUDE(readline)
@@ -558,8 +559,16 @@ IF(NOT WITHOUT_SERVER)
558559
OPTION(WITH_UNIT_TESTS "Compile MySQL with unit tests" ON)
559560
OPTION(WITH_ROUTER "Build MySQL Router" ON)
560561
ENDIF()
562+
563+
IF(EXISTS ${CMAKE_SOURCE_DIR}/internal/CMakeLists.txt)
564+
SET(WITH_AUTHENTICATION_LDAP_DEFAULT ON)
565+
ELSE()
566+
SET(WITH_AUTHENTICATION_LDAP_DEFAULT OFF)
567+
ENDIF()
568+
561569
OPTION(WITH_AUTHENTICATION_LDAP
562-
"Report error if the LDAP authentication plugin cannot be built." OFF)
570+
"Report error if the LDAP authentication plugin cannot be built."
571+
${WITH_AUTHENTICATION_LDAP_DEFAULT})
563572

564573
OPTION(WITH_LOCK_ORDER
565574
"Build the lock order mutex instrumentation code." OFF)
@@ -1214,10 +1223,82 @@ IF(BUILD_BUNDLED_ZSTD)
12141223
ADD_SUBDIRECTORY(extra/zstd)
12151224
ENDIF()
12161225

1226+
IF(LINUX_STANDALONE)
1227+
FIND_PROGRAM(PATCHELF_EXECUTABLE patchelf)
1228+
ADD_CUSTOM_TARGET(copy_linux_custom_dlls)
1229+
SET(KNOWN_CUSTOM_LIBRARIES "" CACHE INTERNAL "" FORCE)
1230+
ENDIF()
1231+
12171232
# Add system openssl.
12181233
MYSQL_CHECK_SSL()
12191234
MYSQL_CHECK_SSL_DLLS()
12201235

1236+
# Add Kerberos library (custom DLLS not yet supported)
1237+
MYSQL_CHECK_KERBEROS()
1238+
1239+
# Add SASL library
1240+
MYSQL_CHECK_SASL()
1241+
MYSQL_CHECK_SASL_DLLS()
1242+
1243+
# Add LDAP library
1244+
MYSQL_CHECK_LDAP()
1245+
MYSQL_CHECK_LDAP_DLLS()
1246+
1247+
# Custom versions of LDAP / SASL / SSL.
1248+
IF(LINUX_STANDALONE AND KNOWN_CUSTOM_LIBRARIES)
1249+
SET(LINUX_INSTALL_RPATH_ORIGIN 1)
1250+
# Debug versions of plugins may be installed to <root>/lib/plugin/debug
1251+
FOREACH(LINK_FLAG
1252+
CMAKE_MODULE_LINKER_FLAGS_DEBUG
1253+
CMAKE_SHARED_LINKER_FLAGS_DEBUG
1254+
)
1255+
STRING_APPEND(${LINK_FLAG} " -Wl,-rpath,'\$ORIGIN/../../private'")
1256+
MESSAGE(STATUS "${LINK_FLAG} ${${LINK_FLAG}}")
1257+
ENDFOREACH()
1258+
IF(NOT PATCHELF_EXECUTABLE)
1259+
MESSAGE(FATAL_ERROR "Please install the patchelf(1) utility.")
1260+
ENDIF()
1261+
VERIFY_CUSTOM_LIBRARY_DEPENDENCIES()
1262+
ENDIF()
1263+
1264+
IF(WITH_AUTHENTICATION_LDAP)
1265+
IF(LINUX_STANDALONE AND KNOWN_CUSTOM_LIBRARIES)
1266+
# LDAP / SASL / SSL must all be "system" or "custom", not a mix.
1267+
IF(WITH_LDAP STREQUAL "system" OR
1268+
WITH_SASL STREQUAL "system" OR
1269+
WITH_SSL STREQUAL "system")
1270+
MESSAGE(WARNING "-DWITH_AUTHENTICATION_LDAP=ON")
1271+
MESSAGE(FATAL_ERROR "Inconsistent options for LDAP/SASL/SSL")
1272+
ENDIF()
1273+
ELSEIF(WIN32)
1274+
# system LDAP, but SASL needs to be explicitly added
1275+
IF(NOT SASL_LIBRARY_DLL OR NOT SASL_SCRAM_PLUGIN)
1276+
MESSAGE(WARNING "-DWITH_AUTHENTICATION_LDAP=ON")
1277+
MESSAGE(FATAL_ERROR "Missing SASL and/or SCRAM libraries.")
1278+
ENDIF()
1279+
ELSEIF(APPLE)
1280+
# system LDAP and SASL, but there is no scram plugin.
1281+
ELSEIF(SOLARIS)
1282+
# 11.3 has system LDAP and SASL, but there is no scram plugin.
1283+
ELSE()
1284+
# We must have "system" LDAP and SASL (SSL is always required)
1285+
SET(LDAP_WARN_GIVEN)
1286+
SET(SASL_WARN_GIVEN)
1287+
WARN_MISSING_SYSTEM_LDAP(LDAP_WARN_GIVEN)
1288+
WARN_MISSING_SYSTEM_SASL(SASL_WARN_GIVEN)
1289+
IF(LDAP_WARN_GIVEN OR SASL_WARN_GIVEN)
1290+
# SUSE linux: may or may not have SCRAM, do not break the build.
1291+
IF(LINUX_SUSE)
1292+
MESSAGE(WARNING
1293+
"-DWITH_AUTHENTICATION_LDAP=ON, but missing system libraries")
1294+
ELSE()
1295+
MESSAGE(FATAL_ERROR
1296+
"-DWITH_AUTHENTICATION_LDAP=ON, but missing system libraries")
1297+
ENDIF()
1298+
ENDIF()
1299+
ENDIF()
1300+
ENDIF()
1301+
12211302
# Add system/bundled editline.
12221303
MYSQL_CHECK_EDITLINE()
12231304
# Add libevent
@@ -1226,10 +1307,6 @@ MYSQL_CHECK_LIBEVENT()
12261307
MYSQL_CHECK_LZ4()
12271308
# Add icu library
12281309
MYSQL_CHECK_ICU()
1229-
# Add SASL library
1230-
MYSQL_CHECK_SASL()
1231-
# Add Kerberos library
1232-
MYSQL_CHECK_KERBEROS()
12331310
# Add protoc and libprotobuf
12341311
IF(NOT WITHOUT_SERVER)
12351312
MYSQL_CHECK_PROTOBUF()
@@ -1546,7 +1623,12 @@ IF(UNIX)
15461623
COMMAND ${CMAKE_COMMAND} -E make_directory plugin_output_directory
15471624
COMMAND ${CMAKE_COMMAND} -E make_directory runtime_output_directory
15481625
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
1549-
)
1626+
)
1627+
IF(SASL_CUSTOM_LIBRARY)
1628+
EXECUTE_PROCESS(
1629+
COMMAND ${CMAKE_COMMAND} -E make_directory library_output_directory/sasl2
1630+
)
1631+
ENDIF()
15501632
ENDIF()
15511633

15521634
# Pre-create directories for library_output_directory and

cmake/copy_custom_library.cmake

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
2+
#
3+
# This program is free software; you can redistribute it and/or modify
4+
# it under the terms of the GNU General Public License, version 2.0,
5+
# as published by the Free Software Foundation.
6+
#
7+
# This program is also distributed with certain software (including
8+
# but not limited to OpenSSL) that is licensed under separate terms,
9+
# as designated in a particular file or component or in included license
10+
# documentation. The authors of MySQL hereby grant you an additional
11+
# permission to link the program and your derivative works with the
12+
# separately licensed software that they have included with MySQL.
13+
#
14+
# This program is distributed in the hope that it will be useful,
15+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
# GNU General Public License, version 2.0, for more details.
18+
#
19+
# You should have received a copy of the GNU General Public License
20+
# along with this program; if not, write to the Free Software
21+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22+
23+
IF(EXISTS "./${library_version}")
24+
RETURN()
25+
ENDIF()
26+
27+
EXECUTE_PROCESS(
28+
COMMAND ${CMAKE_COMMAND} -E copy
29+
"${library_directory}/${library_version}" "./${library_version}"
30+
)
31+
32+
IF(NOT "${library_version}" STREQUAL "${library_name}")
33+
EXECUTE_PROCESS(
34+
COMMAND ${CMAKE_COMMAND} -E create_symlink
35+
"${library_version}" "${library_name}"
36+
)
37+
ENDIF()
38+
39+
IF(NOT "${library_version}" STREQUAL "${library_soname}")
40+
EXECUTE_PROCESS(
41+
COMMAND ${CMAKE_COMMAND} -E create_symlink
42+
"${library_version}" "${library_soname}"
43+
)
44+
ENDIF()
45+
46+
# Some of the pre-built libraries come without execute bit set.
47+
EXECUTE_PROCESS(
48+
COMMAND chmod +x "./${library_version}")
49+
50+
# Patch RPATH so that we find NEEDED libraries at load time.
51+
IF(subdir)
52+
EXECUTE_PROCESS(
53+
COMMAND ${PATCHELF_EXECUTABLE} --set-rpath "$ORIGIN/.."
54+
"./${library_version}"
55+
)
56+
ELSE()
57+
EXECUTE_PROCESS(
58+
COMMAND ${PATCHELF_EXECUTABLE} --set-rpath "$ORIGIN" "./${library_version}"
59+
)
60+
ENDIF()

cmake/fileutils.cmake

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
22
#
33
# This program is free software; you can redistribute it and/or modify
44
# it under the terms of the GNU General Public License, version 2.0,
@@ -57,7 +57,7 @@ IF(WIN32)
5757
STRING(REPLACE "\n" ";" DUMPBIN_OUTPUT_LIST "${DUMPBIN_OUTPUT}")
5858
SET(DEPENDENCIES)
5959
FOREACH(LINE ${DUMPBIN_OUTPUT_LIST})
60-
STRING(REGEX MATCH "^[\r\n\t ]*([A-Za-z0-9_]*\.dll)" XXX ${LINE})
60+
STRING(REGEX MATCH "^[\r\n\t ]*([A-Za-z0-9_]*\.dll)" UNUSED ${LINE})
6161
IF(CMAKE_MATCH_1)
6262
LIST(APPEND DEPENDENCIES ${CMAKE_MATCH_1})
6363
ENDIF()
@@ -66,3 +66,60 @@ IF(WIN32)
6666
ENDIF()
6767
ENDFUNCTION()
6868
ENDIF()
69+
70+
IF(LINUX)
71+
FUNCTION(FIND_OBJECT_DEPENDENCIES FILE_NAME RETURN_VALUE)
72+
SET(${RETURN_VALUE} PARENT_SCOPE)
73+
EXECUTE_PROCESS(COMMAND
74+
objdump -p "${FILE_NAME}"
75+
OUTPUT_VARIABLE OBJDUMP_OUTPUT
76+
RESULT_VARIABLE OBJDUMP_RESULT
77+
OUTPUT_STRIP_TRAILING_WHITESPACE
78+
)
79+
STRING(REPLACE "\n" ";" OBJDUMP_OUTPUT_LIST "${OBJDUMP_OUTPUT}")
80+
SET(DEPENDENCIES)
81+
FOREACH(LINE ${OBJDUMP_OUTPUT_LIST})
82+
STRING(REGEX MATCH
83+
"^[ ]+NEEDED[ ]+([-_A-Za-z0-9\\.]+)" UNUSED ${LINE})
84+
IF(CMAKE_MATCH_1)
85+
LIST(APPEND DEPENDENCIES ${CMAKE_MATCH_1})
86+
ENDIF()
87+
ENDFOREACH()
88+
SET(${RETURN_VALUE} ${DEPENDENCIES} PARENT_SCOPE)
89+
ENDFUNCTION()
90+
91+
FUNCTION(FIND_SONAME FILE_NAME RETURN_VALUE)
92+
SET(${RETURN_VALUE} PARENT_SCOPE)
93+
EXECUTE_PROCESS(COMMAND
94+
objdump -p "${FILE_NAME}"
95+
OUTPUT_VARIABLE OBJDUMP_OUTPUT
96+
RESULT_VARIABLE OBJDUMP_RESULT
97+
OUTPUT_STRIP_TRAILING_WHITESPACE
98+
)
99+
STRING(REPLACE "\n" ";" OBJDUMP_OUTPUT_LIST "${OBJDUMP_OUTPUT}")
100+
FOREACH(LINE ${OBJDUMP_OUTPUT_LIST})
101+
STRING(REGEX MATCH
102+
"^[ ]+SONAME[ ]+([-_A-Za-z0-9\\.]+)" UNUSED ${LINE})
103+
IF(CMAKE_MATCH_1)
104+
SET(${RETURN_VALUE} ${CMAKE_MATCH_1} PARENT_SCOPE)
105+
ENDIF()
106+
ENDFOREACH()
107+
ENDFUNCTION()
108+
109+
FUNCTION(VERIFY_CUSTOM_LIBRARY_DEPENDENCIES)
110+
FOREACH(lib ${KNOWN_CUSTOM_LIBRARIES})
111+
FOREACH(lib_needs ${NEEDED_${lib}})
112+
GET_FILENAME_COMPONENT(library_name_we "${lib_needs}" NAME_WE)
113+
SET(SONAME ${SONAME_${library_name_we}})
114+
IF(SONAME)
115+
MESSAGE(STATUS
116+
"${lib} needs ${lib_needs} from ${library_name_we}")
117+
IF(NOT "${lib_needs}" STREQUAL "${SONAME}")
118+
MESSAGE(WARNING "${library_name_we} provides ${SONAME}")
119+
ENDIF()
120+
ENDIF()
121+
ENDFOREACH()
122+
ENDFOREACH()
123+
ENDFUNCTION()
124+
125+
ENDIF()

cmake/install_layout.cmake

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2010, 2020, Oracle and/or its affiliates. All rights reserved.
22
#
33
# This program is free software; you can redistribute it and/or modify
44
# it under the terms of the GNU General Public License, version 2.0,
@@ -110,6 +110,10 @@ IF(UNIX)
110110
MARK_AS_ADVANCED(SYSCONFDIR)
111111
ENDIF()
112112

113+
IF(LINUX AND INSTALL_LAYOUT MATCHES "STANDALONE")
114+
SET(LINUX_STANDALONE 1)
115+
ENDIF()
116+
113117
IF(WIN32)
114118
SET(VALID_INSTALL_LAYOUTS "TARGZ" "STANDALONE")
115119
LIST(FIND VALID_INSTALL_LAYOUTS "${INSTALL_LAYOUT}" ind)

cmake/install_macros.cmake

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2009, 2019, Oracle and/or its affiliates. All rights reserved.
1+
# Copyright (c) 2009, 2020, Oracle and/or its affiliates. All rights reserved.
22
#
33
# This program is free software; you can redistribute it and/or modify
44
# it under the terms of the GNU General Public License, version 2.0,
@@ -385,3 +385,103 @@ FUNCTION(SET_PATH_TO_SSL target target_out_dir)
385385
ENDIF()
386386
ENDIF()
387387
ENDFUNCTION()
388+
389+
390+
# For standalone Linux build and -DWITH_LDAP -DWITH_SASL -DWITH_SSL
391+
# set to custom path.
392+
#
393+
# Move the custom shared library and symlinks to library_output_directory.
394+
# The subdir argument is typically empty, but set to "sasl2" for SASL plugins,
395+
# in which case we move library_full_filename and its symlinks to
396+
# library_output_directory/sasl2.
397+
#
398+
# We ensure that the copied custom libraries have the execute bit set.
399+
# We also update the RUNPATH of libraries to be '$ORIGIN' to ensure that
400+
# libraries get correct load-time dependencies. This is done using the
401+
# linux tool patchelf(1)
402+
#
403+
# Set ${OUTPUT_LIBRARY_NAME} to the new location.
404+
# Set ${OUTPUT_TARGET_NAME} to the name of a target which will do the copying.
405+
# Add an INSTALL(FILES ....) rule to install library and symlinks into
406+
# ${INSTALL_PRIV_LIBDIR} or ${INSTALL_PRIV_LIBDIR}/sasl2
407+
FUNCTION(COPY_CUSTOM_SHARED_LIBRARY library_full_filename subdir
408+
OUTPUT_LIBRARY_NAME
409+
OUTPUT_TARGET_NAME
410+
)
411+
IF(NOT LINUX_STANDALONE)
412+
RETURN()
413+
ENDIF()
414+
GET_FILENAME_COMPONENT(LIBRARY_EXT "${library_full_filename}" EXT)
415+
IF(NOT LIBRARY_EXT STREQUAL ".so")
416+
RETURN()
417+
ENDIF()
418+
EXECUTE_PROCESS(
419+
COMMAND readlink "${library_full_filename}" OUTPUT_VARIABLE library_version
420+
OUTPUT_STRIP_TRAILING_WHITESPACE)
421+
GET_FILENAME_COMPONENT(library_directory "${library_full_filename}" DIRECTORY)
422+
GET_FILENAME_COMPONENT(library_name "${library_full_filename}" NAME)
423+
GET_FILENAME_COMPONENT(library_name_we "${library_full_filename}" NAME_WE)
424+
425+
FIND_SONAME(${library_full_filename} library_soname)
426+
FIND_OBJECT_DEPENDENCIES(${library_full_filename} library_dependencies)
427+
428+
MESSAGE(STATUS "CUSTOM library ${library_full_filename}")
429+
MESSAGE(STATUS "CUSTOM version ${library_version}")
430+
MESSAGE(STATUS "CUSTOM directory ${library_directory}")
431+
MESSAGE(STATUS "CUSTOM name ${library_name}")
432+
MESSAGE(STATUS "CUSTOM name_we ${library_name_we}")
433+
MESSAGE(STATUS "CUSTOM soname ${library_soname}")
434+
435+
SET(COPIED_LIBRARY_NAME
436+
"${CMAKE_BINARY_DIR}/library_output_directory/${subdir}/${library_name}")
437+
SET(COPY_TARGET_NAME "copy_${library_name_we}_dll")
438+
439+
# Keep track of libraries and dependencies.
440+
SET(SONAME_${library_name_we} "${library_soname}"
441+
CACHE INTERNAL "SONAME for ${library_name_we}" FORCE)
442+
SET(NEEDED_${library_name_we} "${library_dependencies}"
443+
CACHE INTERNAL "" FORCE)
444+
SET(KNOWN_CUSTOM_LIBRARIES
445+
${KNOWN_CUSTOM_LIBRARIES} ${library_name_we} CACHE INTERNAL "" FORCE)
446+
447+
# Do copying and patching in a sub-process, so that we can skip it if
448+
# already done. The BYPRODUCTS arguments is needed by Ninja, and is
449+
# ignored on non-Ninja generators except to mark byproducts GENERATED.
450+
ADD_CUSTOM_TARGET(${COPY_TARGET_NAME} ALL
451+
COMMAND ${CMAKE_COMMAND}
452+
-Dlibrary_directory="${library_directory}"
453+
-Dlibrary_name="${library_name}"
454+
-Dlibrary_soname="${library_soname}"
455+
-Dlibrary_version="${library_version}"
456+
-Dsubdir="${subdir}"
457+
-DPATCHELF_EXECUTABLE="${PATCHELF_EXECUTABLE}"
458+
-P ${CMAKE_SOURCE_DIR}/cmake/copy_custom_library.cmake
459+
460+
BYPRODUCTS
461+
"${CMAKE_BINARY_DIR}/library_output_directory/${subdir}/${library_name}"
462+
463+
WORKING_DIRECTORY
464+
"${CMAKE_BINARY_DIR}/library_output_directory/${subdir}"
465+
)
466+
467+
# Link with the copied library, rather than the original one.
468+
SET(${OUTPUT_LIBRARY_NAME} "${COPIED_LIBRARY_NAME}" PARENT_SCOPE)
469+
SET(${OUTPUT_TARGET_NAME} "${COPY_TARGET_NAME}" PARENT_SCOPE)
470+
471+
ADD_DEPENDENCIES(copy_linux_custom_dlls ${COPY_TARGET_NAME})
472+
473+
MESSAGE(STATUS "INSTALL ${library_name} to ${INSTALL_PRIV_LIBDIR}")
474+
475+
# Cannot use INSTALL_PRIVATE_LIBRARY because these are not targets.
476+
INSTALL(FILES
477+
${CMAKE_BINARY_DIR}/library_output_directory/${subdir}/${library_name}
478+
${CMAKE_BINARY_DIR}/library_output_directory/${subdir}/${library_soname}
479+
${CMAKE_BINARY_DIR}/library_output_directory/${subdir}/${library_version}
480+
DESTINATION "${INSTALL_PRIV_LIBDIR}/${subdir}" COMPONENT SharedLibraries
481+
PERMISSIONS
482+
OWNER_READ OWNER_WRITE OWNER_EXECUTE
483+
GROUP_READ GROUP_EXECUTE
484+
WORLD_READ WORLD_EXECUTE
485+
)
486+
487+
ENDFUNCTION()

0 commit comments

Comments
 (0)