├── README.md └── wrapper.c /README.md: -------------------------------------------------------------------------------- 1 | # A wrapper to conditionally override some Linux system libs with newer ones 2 | 3 | This is a simple wrapper for your Linux x86 or x86_64 application that 4 | checks the versions of some system libs and compares it with the versions 5 | of the same libs you supply, and sets LD_LIBRARY_PATH in such a way that 6 | your version of the libs is used if it's newer than the one on the system 7 | and then executes your application. 8 | (If an LD_LIBRARY_PATH is already set, the changes from the wrapper will 9 | be prepended to it) 10 | 11 | I wrote a lengthy blogpost about this: http://blog.gibson.sh/2017/11/26/creating-portable-linux-binaries/ 12 | 13 | In short, this is helpful to create programs for Linux that work on a wide range of 14 | distributions (incl. older and bleeding edge ones), even if they need 15 | fairly recent versions of the supported libs (e.g. to support C++11 or C++14). 16 | By making sure you only override the system libs if the supplied version is newer, 17 | the wrapper prevents conflicts with other system libs that depend on the 18 | overriden ones, for example the Mesa libGL uses libstdc++, and if it gets one 19 | that's older than the one it's linked against (available on the system) 20 | it fails to load. 21 | 22 | Currently supported libs: 23 | - libstdc++.so.6 24 | - libgcc_so.so.1 25 | - libSDL2.so.0 26 | - libcurl.so.4 (only used if not available on the system at all) 27 | 28 | It has no dependencies except for a C compiler that supports C99 (any GCC 29 | version of the last decade should work) and a libc incl. libdl (glibc tested). 30 | The wrapper can be built with `$ gcc -std=gnu99 -o YourGameWrapper wrapper.c -ldl` 31 | 32 | It can be configured by changing/commenting out some #defines at the beginning 33 | of the source file. 34 | 35 | When executing the wrapper and the environment variable WRAPPER_DEBUG 36 | is set 1, some helpful messages about the detected versions and the used 37 | LD_LIBRARY_PATH will be printed. This is helpful to debug problems, 38 | especially when your users are having them. 39 | e.g. `$ WRAPPER_DEBUG=1 ./YourGameWrapper` 40 | 41 | ### License 42 | 43 | (C) 2017 Daniel Gibson 44 | 45 | This software is released to the Public Domain, see header of source code for details. 46 | -------------------------------------------------------------------------------- /wrapper.c: -------------------------------------------------------------------------------- 1 | /* 2 | * !! Make sure to build this with an old version of GCC so it doesn't !! 3 | * !! depend on new libgcc_s.so.1 features! !! 4 | * 5 | * => on debian 7 "wheezy" you could do: "gcc-4.7 -std=gnu99 -o YourGameWrapper wrapper.c -ldl" 6 | * 7 | * This is a simple wrapper for your Linux x86 or x86_64 application that 8 | * checks the versions of some system libs and compares it with the versions 9 | * of the same libs you supply, and sets LD_LIBRARY_PATH in such a way that 10 | * your version of the libs is used if it's newer than the one on the system. 11 | * (If an LD_LIBRARY_PATH is already set, the changes from the wrapper will 12 | * be prepended to it) 13 | * Currently supported libs: 14 | * - libstdc++.so.6 15 | * - libgcc_so.so.1 16 | * - libSDL2.so.0 17 | * - libcurl.so.4 (only used if not available on the system at all) 18 | * 19 | * Directly under this comment are a few #defines you can tweak to your needs 20 | * to set your wrapped executable, the paths to your fallback libs and it 21 | * also lets you disable the check for each lib. 22 | * 23 | * When executing the wrapper and the environment variable WRAPPER_DEBUG 24 | * is set 1, some helpful messages about the detected versions and the used 25 | * LD_LIBRARY_PATH will be printed. This is helpful to debug problems, 26 | * especially when your users are having them. 27 | * e.g. $ WRAPPER_DEBUG=1 ./YourGameWrapper 28 | * 29 | * (C) 2017-2023 Daniel Gibson 30 | * 31 | * LICENSE 32 | * This software is dual-licensed to the public domain and under the following 33 | * license: you are granted a perpetual, irrevocable license to copy, modify, 34 | * publish, and distribute this file as you see fit. 35 | * No warranty implied; use at your own risk. 36 | * 37 | * So you can do whatever you want with this code, including copying it 38 | * (or parts of it) into your own source. 39 | * No need to mention me or this "license" in your code or docs, even though 40 | * it would be appreciated, of course. 41 | */ 42 | 43 | // path to the app you wanna launch, relative to the directory this wrapper is in 44 | #define APP_EXECUTABLE "bin/YourGame" 45 | 46 | // uncomment (and adjust) the following line to change the application name 47 | // that will be shown in ps, top etc (if not set, APP_EXECUTABLE will be used) 48 | //#define APP_NAME "YourGame" 49 | 50 | // comment out the following line if you don't want to chdir() into the 51 | // directory this wrapper lies in 52 | #define CHANGE_TO_WRAPPER_DIR 53 | 54 | // just comment out the ones you don't care about 55 | #define CHECK_LIBSTDCPP 56 | #define CHECK_LIBGCC 57 | #define CHECK_LIBSDL2 58 | #define CHECK_LIBCURL4 59 | 60 | // change these defines to the directories you put your fallback libs in 61 | // (relative to the directory this wrapper is in) 62 | #define FALLBACK_DIR_STDCPP "libs/stdcpp" 63 | #define FALLBACK_DIR_GCC "libs/gcc" 64 | #define FALLBACK_DIR_SDL2 "libs/sdl2" 65 | #define FALLBACK_DIR_CURL "libs/curl" // only used if no libcurl.so.4 is found on system at all 66 | 67 | 68 | // 69 | // usually, you won't have to change anything below this line, unless you want 70 | // to support newer GCC versions or additional libs - please send a patch :) - 71 | // or different architectures with different name mangling 72 | // 73 | 74 | #define _GNU_SOURCE 75 | #include 76 | #include 77 | #include 78 | #include 79 | #include 80 | #include 81 | #include 82 | 83 | enum { HAVE_64_BIT = (sizeof(void*) == 8) }; 84 | 85 | static int debugOutput = 0; 86 | #define dprintf(...) do { if(debugOutput){ printf(__VA_ARGS__); } } while(0) 87 | 88 | #define eprintf(...) fprintf(stderr, __VA_ARGS__) 89 | 90 | #if defined(CHECK_LIBSTDCPP) || defined(CHECK_LIBGCC) 91 | struct gcc_version_check 92 | { 93 | const char* gcc_ver_name; 94 | const char* fn_version; 95 | const char* fn_name; 96 | }; 97 | 98 | static int get_gcc_version(const char* libpath, const struct gcc_version_check checks[], const int num_checks) 99 | { 100 | void* handle = dlopen(libpath, RTLD_LAZY); 101 | int i, ret = -1; 102 | if(handle == NULL) 103 | { 104 | eprintf("couldn't dlopen() %s : %s\n", libpath, dlerror()); 105 | return ret; 106 | } 107 | 108 | for(i=0; i < num_checks; ++i) 109 | { 110 | const char* fn_name = checks[i].fn_name; 111 | if(fn_name != NULL) 112 | { 113 | void* fn = dlvsym(handle, fn_name, checks[i].fn_version); 114 | if(fn == NULL) 115 | { 116 | dlerror(); // clear it 117 | break; 118 | } 119 | ret = i; 120 | } 121 | } 122 | 123 | dlclose(handle); 124 | 125 | // if ret == num_checks-1, the real version could potentially be even newer 126 | return ret; 127 | } 128 | 129 | static const char* get_gcc_version_name(const struct gcc_version_check checks[], int idx) 130 | { 131 | if(idx < 0) return "Not found"; 132 | return checks[idx].gcc_ver_name; 133 | } 134 | #endif // defined(CHECK_LIBSTDCPP) || defined(CHECK_LIBGCC) 135 | 136 | #ifdef CHECK_LIBSTDCPP 137 | enum stdcpp_gcc_version { 138 | STDCPP_GCC_OLDER = -1, 139 | // see https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html#abi.versioning 140 | // Note that this does not list all released GCC versions, but only 141 | // the ones that bumped the libstdc++ ABI version (GLIBCXX_3.4.x) 142 | // BTW, the value of MY_GCC_* corresponds with x in GLIBCXX_3.4.x 143 | STDCPP_GCC_3_4_0 = 0, // GLIBCXX_3.4, CXXABI_1.3 144 | STDCPP_GCC_3_4_1, // GLIBCXX_3.4.1, CXXABI_1.3 145 | STDCPP_GCC_3_4_2, // GLIBCXX_3.4.2 146 | STDCPP_GCC_3_4_3, // GLIBCXX_3.4.3 - unfortunately there's no function with that version in libstdc++ 147 | STDCPP_GCC_4_0_0, // GLIBCXX_3.4.4, CXXABI_1.3.1 148 | STDCPP_GCC_4_0_1, // GLIBCXX_3.4.5 149 | STDCPP_GCC_4_0_2, // GLIBCXX_3.4.6 150 | STDCPP_GCC_4_0_3, // GLIBCXX_3.4.7 151 | STDCPP_GCC_4_1_1, // GLIBCXX_3.4.8 - no function for x86?! 152 | STDCPP_GCC_4_2_0, // GLIBCXX_3.4.9, 153 | STDCPP_GCC_4_3_0, // GLIBCXX_3.4.10, CXXABI_1.3.2 154 | STDCPP_GCC_4_4_0, // GLIBCXX_3.4.11, CXXABI_1.3.3 155 | STDCPP_GCC_4_4_1, // GLIBCXX_3.4.12, CXXABI_1.3.3 - no function with that version 156 | STDCPP_GCC_4_4_2, // GLIBCXX_3.4.13, CXXABI_1.3.3 157 | STDCPP_GCC_4_5_0, // GLIBCXX_3.4.14, CXXABI_1.3.4 158 | STDCPP_GCC_4_6_0, // GLIBCXX_3.4.15, CXXABI_1.3.5 159 | STDCPP_GCC_4_6_1, // GLIBCXX_3.4.16, CXXABI_1.3.5 160 | STDCPP_GCC_4_7_0, // GLIBCXX_3.4.17, CXXABI_1.3.6 161 | STDCPP_GCC_4_8_0, // GLIBCXX_3.4.18, CXXABI_1.3.7 162 | STDCPP_GCC_4_8_3, // GLIBCXX_3.4.19, CXXABI_1.3.7 163 | STDCPP_GCC_4_9_0, // GLIBCXX_3.4.20, CXXABI_1.3.8 164 | STDCPP_GCC_5_1_0, // GLIBCXX_3.4.21, CXXABI_1.3.9 165 | STDCPP_GCC_6_1_0, // GLIBCXX_3.4.22, CXXABI_1.3.10 166 | STDCPP_GCC_7_1_0, // GLIBCXX_3.4.23, CXXABI_1.3.11 167 | STDCPP_GCC_7_2_0, // GLIBCXX_3.4.24, CXXABI_1.3.11 - no function for x86_64 168 | STDCPP_GCC_8_1_0, // GLIBCXX_3.4.25, CXXABI_1.3.11 169 | STDCPP_GCC_9_1_0, // GLIBCXX_3.4.26, CXXABI_1.3.12 170 | STDCPP_GCC_9_2_0, // GLIBCXX_3.4.27, CXXABI_1.3.12 171 | STDCPP_GCC_9_3_0, // GLIBCXX_3.4.28, CXXABI_1.3.12 172 | //STDCPP_GCC_10_1_0,// GLIBCXX_3.4.28, CXXABI_1.3.12 - same as 9.3.0 ?! 173 | STDCPP_GCC_11_1_0, // GLIBCXX_3.4.29, CXXABI_1.3.13 174 | STDCPP_GCC_12_1_0, // GLIBCXX_3.4.30, CXXABI_1.3.13 175 | STDCPP_GCC_13_1_0, // GLIBCXX_3.4.31, CXXABI_1.3.14 176 | STDCPP_GCC_13_2_0, // GLIBCXX_3.4.32, CXXABI_1.3.14 177 | // TODO: add newer versions, once available (also in libstdcpp_version_checks below) 178 | _NUM_STDCPP_GCC_VERSIONS 179 | }; 180 | 181 | 182 | // I found the entries for this table with: objdump -T /path/to/libstdc++.so.6 | grep " DF .text" | less 183 | // (and then searched for GLIBCXX_3.4.5 or whatever) 184 | // Note that readelf does truncate the function names in output and thus is not suitable for this! 185 | static const struct gcc_version_check 186 | libstdcpp_version_checks[_NUM_STDCPP_GCC_VERSIONS] = 187 | { 188 | { "3.4.0", "GLIBCXX_3.4", "_ZNSt5ctypeIwED2Ev" }, 189 | { "3.4.1", "GLIBCXX_3.4.1", "_ZNSt12__basic_fileIcE4fileEv" }, 190 | { "3.4.2", "GLIBCXX_3.4.2", "_ZN9__gnu_cxx17__pool_alloc_base12_M_get_mutexEv" }, 191 | { "3.4.3", "GLIBCXX_3.4.3", NULL }, // FIXME: couldn't find a function with that version in libstdc++.so.6 192 | { "4.0.0", "GLIBCXX_3.4.4", "_ZN9__gnu_cxx6__poolILb0EE10_M_destroyEv" }, 193 | { "4.0.1", "GLIBCXX_3.4.5", "_ZNKSs11_M_disjunctEPKc" }, 194 | { "4.0.2", "GLIBCXX_3.4.6", "_ZNSt6locale5facet13_S_get_c_nameEv" }, 195 | // the following has a different signature for 32bit vs 64bit 196 | { "4.0.3", "GLIBCXX_3.4.7", HAVE_64_BIT ? "_ZNSt6locale5_Impl16_M_install_cacheEPKNS_5facetEm" 197 | : "_ZNSt6locale5_Impl16_M_install_cacheEPKNS_5facetEj" }, 198 | { "4.1.1", "GLIBCXX_3.4.8", HAVE_64_BIT ? "_ZSt17__copy_streambufsIcSt11char_traitsIcEElPSt15basic_streambufIT_T0_ES6_" 199 | : NULL }, // FIXME: couldn't find a function for i686 ?! 200 | { "4.2.0", "GLIBCXX_3.4.9", "_ZNSo9_M_insertIdEERSoT_" }, 201 | { "4.3.0", "GLIBCXX_3.4.10", "_ZNKSt4hashISbIwSt11char_traitsIwESaIwEEEclES3_" }, 202 | { "4.4.0", "GLIBCXX_3.4.11", "_ZSt16generic_categoryv" }, 203 | { "4.4.1", "GLIBCXX_3.4.12", NULL }, // FIXME: couldn't find a function with that version in libstdc++.so.6 204 | { "4.4.2", "GLIBCXX_3.4.13", "_ZNSt14basic_ofstreamIcSt11char_traitsIcEEC2ERKSsSt13_Ios_Openmode" }, 205 | { "4.5.0", "GLIBCXX_3.4.14", "_ZNSs13shrink_to_fitEv" }, 206 | { "4.6.0", "GLIBCXX_3.4.15", "_ZNSt17bad_function_callD2Ev" }, 207 | { "4.6.1", "GLIBCXX_3.4.16", HAVE_64_BIT ? "_ZNSs10_S_compareEmm" : "_ZNSs10_S_compareEjj" }, 208 | { "4.7.0", "GLIBCXX_3.4.17", "_ZNSt6thread20hardware_concurrencyEv" }, 209 | { "4.8.0", "GLIBCXX_3.4.18", "_ZNSt13random_device9_M_getvalEv" }, 210 | { "4.8.3", "GLIBCXX_3.4.19", "_ZNSt6chrono3_V212steady_clock3nowEv" }, 211 | { "4.9.0", "GLIBCXX_3.4.20", "_ZSt14get_unexpectedv" }, 212 | { "5.1.0", "GLIBCXX_3.4.21", "_ZNSt13runtime_errorC1EPKc" }, 213 | { "6.1.0", "GLIBCXX_3.4.22", "_ZNSt6thread6_StateD1Ev" }, 214 | { "7.1.0", "GLIBCXX_3.4.23", HAVE_64_BIT ? "_ZNSsC1ERKSsmRKSaIcE" : "_ZNSsC1ERKSsjRKSaIcE" }, 215 | { "7.2.0", "GLIBCXX_3.4.24", HAVE_64_BIT ? NULL // couldn't find a 64bit function with this version 216 | : "_ZNSbIwSt11char_traitsIwESaIwEEC2ERKS2_jRKS1_" }, 217 | { "8.1.0", "GLIBCXX_3.4.25", "_ZNKSt13random_device13_M_getentropyEv" }, 218 | { "9.1.0", "GLIBCXX_3.4.26", "_ZNSs4dataEv" }, 219 | { "9.2.0", "GLIBCXX_3.4.27", "_ZNSt10filesystem28recursive_directory_iteratoraSERKS0_" }, 220 | { "9.3.0", "GLIBCXX_3.4.28", "_ZNSt3pmr15memory_resourceD0Ev" }, 221 | // { "10.1.0" "GLIBCXX_3.4.28", "" }, - has same symbol version as 9.3.0 222 | { "11.1.0", "GLIBCXX_3.4.29", "_ZNSs7reserveEv" }, 223 | { "12.1.0", "GLIBCXX_3.4.30", "_ZSt21__glibcxx_assert_failPKciS0_S0_" }, 224 | { "13.1.0", "GLIBCXX_3.4.31", "_ZSt8to_charsPcS_DF128_" }, 225 | { "13.2.0", "GLIBCXX_3.4.32", "_ZSt21ios_base_library_initv" }, 226 | // TODO: add new versions once available 227 | }; 228 | 229 | static enum stdcpp_gcc_version get_libstdcpp_version(const char* path) 230 | { 231 | return get_gcc_version(path, libstdcpp_version_checks, _NUM_STDCPP_GCC_VERSIONS); 232 | } 233 | #endif // CHECK_LIBSTDCPP 234 | 235 | #ifdef CHECK_LIBGCC 236 | enum libgcc_version { 237 | LIBGCC_OLDER = -1, 238 | LIBGCC_3_0_0 = 0, // GCC_3.0 239 | LIBGCC_3_3_0, // GCC_3.3 240 | LIBGCC_3_3_1, // GCC_3.3.1 241 | LIBGCC_3_3_2, // GCC_3.3.2 - no function with that version 242 | LIBGCC_3_3_4, // GCC_3.3.4 - ditto 243 | LIBGCC_3_4_0, // GCC_3.4 244 | LIBGCC_3_4_2, // GCC_3.4.2 245 | LIBGCC_3_4_4, // GCC_3.4.4 - no x86 function with that version 246 | LIBGCC_4_0_0, // GCC_4.0.0 247 | LIBGCC_4_1_0, // GCC_4.1.0 - no function with that version 248 | LIBGCC_4_2_0, // GCC_4.2.0 249 | LIBGCC_4_3_0, // GCC_4.3.0 250 | LIBGCC_4_4_0, // GCC_4.4.0 - no x86_64 function with that version 251 | LIBGCC_4_5_0, // GCC_4.5.0 - ditto 252 | LIBGCC_4_6_0, // GCC_4.6.0 - no function with that version 253 | LIBGCC_4_7_0, // GCC_4.7.0 254 | LIBGCC_4_8_0, // GCC_4.8.0 255 | // apparently no new functions were added (in x86/amd64) until 7.0.0 256 | LIBGCC_7_0_0, // GCC_7.0.0 257 | // apparently no new functions were added (in x86/amd64) until 12.0.0 258 | LIBGCC_12_1_0, // GCC_12.0.0 259 | LIBGCC_13_1_0, // GCC_13.0.0 260 | // TODO: add new versions, if any 261 | _NUM_LIBGCC_VERSIONS 262 | }; 263 | 264 | static const struct gcc_version_check 265 | libgcc_version_checks[_NUM_LIBGCC_VERSIONS] = 266 | { 267 | { "3.0.0", "GCC_3.0", "__mulvsi3" }, 268 | { "3.3.0", "GCC_3.3", "_Unwind_Backtrace" }, 269 | { "3.3.1", "GCC_3.3.1", "__gcc_personality_v0" }, 270 | { "3.3.2", "GCC_3.3.2", NULL }, // no function with this version in i686 and amd64 libgcc 271 | { "3.3.4", "GCC_3.3.4", NULL }, // ditto 272 | { "3.4.0", "GCC_3.4", "__paritydi2" }, 273 | { "3.4.2", "GCC_3.4.2", "__enable_execute_stack" }, 274 | { "3.4.4", "GCC_3.4.4", HAVE_64_BIT ? "__addvti3" : NULL }, // no x86 function with that version 275 | { "4.0.0", "GCC_4.0.0", "__muldc3" }, 276 | { "4.1.0", "GCC_4.1.0", NULL }, // no function for this version 277 | { "4.2.0", "GCC_4.2.0", "_Unwind_GetIPInfo" }, 278 | { "4.3.0", "GCC_4.3.0", "__bswapdi2" }, 279 | { "4.4.0", "GCC_4.4.0", HAVE_64_BIT ? NULL : "__letf2" }, // no 64bit function for this version 280 | { "4.5.0", "GCC_4.5.0", HAVE_64_BIT ? NULL : "__extendxftf2" }, // ditto 281 | { "4.6.0", "GCC_4.6.0", NULL }, // no function for that version at all 282 | { "4.7.0", "GCC_4.7.0", "__clrsbdi2" }, 283 | { "4.8.0", "GCC_4.8.0", "__cpu_indicator_init" }, 284 | { "7.0.0", "GCC_7.0.0", HAVE_64_BIT ? "__divmodti4" : "__divmoddi4" }, 285 | { "12.1.0","GCC_12.0.0", "__nehf2" }, 286 | { "13.1.0","GCC_13.0.0", "__truncdfbf2" }, 287 | // TODO: add new versions 288 | }; 289 | 290 | static enum libgcc_version get_libgcc_version(const char* path) 291 | { 292 | return get_gcc_version(path, libgcc_version_checks, _NUM_LIBGCC_VERSIONS); 293 | } 294 | #endif // CHECK_LIBGCC 295 | 296 | 297 | #ifdef CHECK_LIBSDL2 298 | typedef struct My_SDL2_version 299 | { 300 | unsigned char major; 301 | unsigned char minor; 302 | unsigned char patch; // update version, e.g. 5 in 2.0.5 303 | } My_SDL2_version; 304 | 305 | static My_SDL2_version get_libsdl2_version(const char* path) 306 | { 307 | My_SDL2_version ret = {0}; 308 | void* handle = dlopen(path, RTLD_LAZY); 309 | if(handle == NULL) 310 | { 311 | dprintf("couldn't dlopen() %s : %s\n", path, dlerror()); 312 | return ret; 313 | } 314 | 315 | void (*sdl_getversion)(My_SDL2_version* ver); 316 | 317 | sdl_getversion = dlsym(handle, "SDL_GetVersion"); 318 | if(sdl_getversion == NULL) 319 | { 320 | eprintf("Couldn't find SDL_GetVersion() in %s !\n", path); 321 | } 322 | else 323 | { 324 | sdl_getversion(&ret); 325 | } 326 | 327 | dlclose(handle); 328 | return ret; 329 | } 330 | #endif // CHECK_LIBSDL2 331 | 332 | 333 | 334 | // directory the wrapper executable is in, *without* trailing slash 335 | static char wrapper_exe_dir[PATH_MAX] = {0}; 336 | 337 | // returns 1 after successfully setting wrapper_exe_dir to the directory this wrapper is in, 338 | // otherwise it returns 0 339 | static int set_wrapper_dir(void) 340 | { 341 | { 342 | // taken from https://github.com/DanielGibson/Snippets/blob/master/DG_misc.h 343 | #if defined(__linux) 344 | // readlink() doesn't null-terminate! 345 | int len = readlink("/proc/self/exe", wrapper_exe_dir, PATH_MAX-1); 346 | if (len <= 0) 347 | { 348 | // an error occured, clear exe path 349 | wrapper_exe_dir[0] = '\0'; 350 | } 351 | else 352 | { 353 | wrapper_exe_dir[len] = '\0'; 354 | } 355 | #elif defined(__FreeBSD__) // TODO: really keep this? 356 | // the sysctl should also work when /proc/ is not mounted (which seems to 357 | // be common on FreeBSD), so use it.. 358 | int name[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}; 359 | size_t len = PATH_MAX-1; 360 | int ret = sysctl(name, sizeof(name)/sizeof(name[0]), wrapper_exe_dir, &len, NULL, 0); 361 | if(ret != 0) 362 | { 363 | // an error occured, clear exe path 364 | wrapper_exe_dir[0] = '\0'; 365 | } 366 | #else 367 | error "Unsupported Platform!" // not sure how useful this even is on other platforms 368 | #endif 369 | } 370 | 371 | if(wrapper_exe_dir[0] != '\0') 372 | { 373 | // find the last slash before the executable name and set it to '\0' 374 | // so wrapper_exe_dir contains the full path of the directory the executable is in 375 | // then change to that directory 376 | char* lastSlash = strrchr(wrapper_exe_dir, '/'); 377 | if(lastSlash != NULL) 378 | { 379 | *lastSlash = '\0'; 380 | 381 | return 1; 382 | } 383 | } 384 | return 0; 385 | } 386 | 387 | // returns 1 after successfully changing to the directory this executable is in, 388 | // otherwise it returns 0 389 | static int change_to_wrapper_dir(void) 390 | { 391 | return chdir(wrapper_exe_dir) == 0; 392 | } 393 | 394 | struct fallback_lib { 395 | const char* name; 396 | const char* dir; 397 | int use; // set by check_fallback_libs() 398 | }; 399 | 400 | static struct fallback_lib fallback_libs[] = { 401 | #ifdef CHECK_LIBSTDCPP 402 | { "libstdc++.so.6", FALLBACK_DIR_STDCPP, 0 }, 403 | #endif 404 | #ifdef CHECK_LIBGCC 405 | { "libgcc_s.so.1", FALLBACK_DIR_GCC, 0 }, 406 | #endif 407 | #ifdef CHECK_LIBSDL2 408 | { "libSDL2-2.0.so.0", FALLBACK_DIR_SDL2, 0 }, 409 | #endif 410 | #ifdef CHECK_LIBCURL4 411 | { "libcurl.so.4", FALLBACK_DIR_CURL, 0 }, 412 | #endif 413 | }; 414 | 415 | static const int NUM_FALLBACK_LIBS = sizeof(fallback_libs)/sizeof(fallback_libs[0]); 416 | 417 | static int check_fallback_libs(void) 418 | { 419 | int sys_ver, our_ver; 420 | char local_path[PATH_MAX]; 421 | 422 | int fb_lib_idx = 0; 423 | 424 | #ifdef CHECK_LIBSTDCPP 425 | sys_ver = get_libstdcpp_version(fallback_libs[fb_lib_idx].name); 426 | snprintf(local_path, PATH_MAX, "%s/%s", fallback_libs[fb_lib_idx].dir, fallback_libs[fb_lib_idx].name); 427 | our_ver = get_libstdcpp_version(local_path); 428 | dprintf("System libstdc++ version: %s ours: %s\n", get_gcc_version_name(libstdcpp_version_checks, sys_ver), 429 | get_gcc_version_name(libstdcpp_version_checks, our_ver)); 430 | if(our_ver > sys_ver) 431 | { 432 | dprintf("Overwriting System libstdc++\n"); 433 | fallback_libs[fb_lib_idx].use = 1; 434 | } 435 | 436 | ++fb_lib_idx; 437 | #endif 438 | 439 | #ifdef CHECK_LIBGCC 440 | sys_ver = get_libgcc_version(fallback_libs[fb_lib_idx].name); 441 | snprintf(local_path, PATH_MAX, "%s/%s", fallback_libs[fb_lib_idx].dir, fallback_libs[fb_lib_idx].name); 442 | our_ver = get_libgcc_version(local_path); 443 | dprintf("System libgcc version: %s ours: %s\n", get_gcc_version_name(libgcc_version_checks, sys_ver), 444 | get_gcc_version_name(libgcc_version_checks, our_ver)); 445 | if(our_ver > sys_ver) 446 | { 447 | dprintf("Overwriting System libgcc\n"); 448 | fallback_libs[fb_lib_idx].use = 1; 449 | } 450 | 451 | ++fb_lib_idx; 452 | #endif 453 | 454 | #ifdef CHECK_LIBSDL2 455 | My_SDL2_version sdl_sys_ver = get_libsdl2_version(fallback_libs[fb_lib_idx].name); 456 | snprintf(local_path, PATH_MAX, "%s/%s", fallback_libs[fb_lib_idx].dir, fallback_libs[fb_lib_idx].name); 457 | My_SDL2_version sdl_our_ver = get_libsdl2_version(local_path); 458 | dprintf("System SDL2 version: %d.%d.%d ours: %d.%d.%d\n", (int)sdl_sys_ver.major, (int)sdl_sys_ver.minor, (int)sdl_sys_ver.patch, 459 | (int)sdl_our_ver.major, (int)sdl_our_ver.minor, (int)sdl_our_ver.patch); 460 | if( sdl_our_ver.major != 0 // otherwise it hasn't been found 461 | && (sdl_sys_ver.major != sdl_our_ver.major // changes to the major version break the API/ABI 462 | // minor versions don't break API/ABI (has been decided with 2.24.0 which followed 2.0.22) 463 | || sdl_sys_ver.minor < sdl_our_ver.minor 464 | || (sdl_sys_ver.minor == sdl_our_ver.minor && sdl_sys_ver.patch < sdl_our_ver.patch) ) ) 465 | { 466 | dprintf("Overwriting System libSDL2\n"); 467 | fallback_libs[fb_lib_idx].use = 1; 468 | } 469 | 470 | ++fb_lib_idx; 471 | #endif 472 | 473 | #ifdef CHECK_LIBCURL4 474 | { 475 | // only use the bundled libcurl.so.4 if none is available on the system 476 | // there should be no backwards compat problems as long as it's libcurl.so.4 477 | // (and you linked against a libcurl without versioned symbols) 478 | // This way a (hopefully) security patched version supplied 479 | // by the user's Linux distribution is used, if available 480 | void* curlHandle = dlopen(fallback_libs[fb_lib_idx].name, RTLD_LAZY); 481 | if(curlHandle == NULL) 482 | { 483 | dprintf("Couldn't find libcurl.so.4 on System, will use bundled version\n"); 484 | fallback_libs[fb_lib_idx].use = 1; 485 | } 486 | else 487 | { 488 | dprintf("Will use System's libcurl.so.4\n"); 489 | dlclose(curlHandle); 490 | } 491 | } 492 | 493 | ++fb_lib_idx; 494 | #endif 495 | 496 | return 1; 497 | } 498 | 499 | static int set_ld_library_path(void) 500 | { 501 | char* old_val = getenv("LD_LIBRARY_PATH"); 502 | size_t len = 1; // 1 for terminating \0, 503 | if(old_val != NULL) 504 | { 505 | if(old_val[0] != '\0') len += strlen(old_val) + 1; // +1 for ":" 506 | else old_val = NULL; 507 | } 508 | 509 | // Note: using absolute paths (=> prepending wrapper_exe_dir) 510 | 511 | // +1 for the '/' that will be added after wrapper_exe_dir before APP_EXECUTABLE 512 | const int wrapper_dir_len = strlen(wrapper_exe_dir) + 1; 513 | 514 | int num_used_overrides = 0; 515 | for(int i=0; i < NUM_FALLBACK_LIBS; ++i) 516 | { 517 | if(fallback_libs[i].use) 518 | { 519 | // + 1 for separating ":" between entries 520 | len += wrapper_dir_len + strlen(fallback_libs[i].dir) + 1; 521 | ++num_used_overrides; 522 | } 523 | } 524 | 525 | if(num_used_overrides == 0) 526 | { 527 | dprintf("no need to modify LD_LIBRARY_PATH\n"); 528 | return 1; // nothing to do 529 | } 530 | 531 | char* new_val = malloc(len); 532 | if(new_val == NULL) return 0; // malloc failed (unlikely) 533 | 534 | new_val[0] = '\0'; 535 | 536 | for(int i=0; i < NUM_FALLBACK_LIBS; ++i) 537 | { 538 | if(fallback_libs[i].use) 539 | { 540 | // using strcat() here is safe because we checked the lengths above 541 | // and set len accordingly 542 | strcat(new_val, wrapper_exe_dir); 543 | strcat(new_val, "/"); 544 | strcat(new_val, fallback_libs[i].dir); 545 | strcat(new_val, ":"); 546 | } 547 | } 548 | 549 | if(old_val != NULL) 550 | { 551 | strcat(new_val, old_val); 552 | } 553 | else // remove the last added ":" 554 | { 555 | new_val[strlen(new_val) - 1] = '\0'; 556 | } 557 | 558 | int ret = (setenv("LD_LIBRARY_PATH", new_val, 1) == 0); 559 | 560 | if(ret) { 561 | dprintf("Set LD_LIBRARY_PATH to '%s'\n", new_val); 562 | } else { 563 | int e = errno; 564 | eprintf("Failed to set LD_LIBRARY_PATH to '%s' : errnno %d (%s)\n", new_val, e, strerror(e)); 565 | } 566 | 567 | free(new_val); 568 | return ret; 569 | } 570 | 571 | static void run_executable(char** argv) 572 | { 573 | #ifdef APP_NAME 574 | if(APP_NAME[0] != '\0') 575 | { 576 | argv[0] = APP_NAME; // override argv[0] so it contains your configured app name 577 | } 578 | else 579 | #endif 580 | { 581 | argv[0] = APP_EXECUTABLE; // override argv[0] so it contains the name of the launched executable instead of this wrapper 582 | } 583 | 584 | char full_exe_path[PATH_MAX]; 585 | int len = snprintf(full_exe_path, sizeof(full_exe_path), "%s/%s", wrapper_exe_dir, APP_EXECUTABLE); 586 | 587 | if(len <= 0 || len >= sizeof(full_exe_path)) 588 | { 589 | eprintf("ERROR: Couldn't create full path to executable, snprintf() returned %d\n", len); 590 | } 591 | else if(execv(full_exe_path, argv) < 0) 592 | { 593 | int e = errno; 594 | eprintf("Executing %s failed: errno %d (%s)\n", APP_EXECUTABLE, e, strerror(e)); 595 | } 596 | // else, if execv() was successful, this function never returns 597 | } 598 | 599 | int main(int argc, char** argv) 600 | { 601 | char* debugVar = getenv("WRAPPER_DEBUG"); 602 | if(debugVar != NULL && atoi(debugVar) != 0) 603 | { 604 | debugOutput = 1; 605 | } 606 | 607 | if(!set_wrapper_dir()) 608 | { 609 | eprintf("Couldn't figure out the wrapper directory!\n"); 610 | return 1; 611 | } 612 | 613 | #ifdef CHANGE_TO_WRAPPER_DIR 614 | if(!change_to_wrapper_dir()) 615 | { 616 | eprintf("Couldn't change to wrapper directory!\n"); 617 | return 1; 618 | } 619 | #endif 620 | 621 | if(check_fallback_libs() && set_ld_library_path()) 622 | { 623 | run_executable(argv); // if it succeeds, it doesn't return. 624 | } 625 | return 1; 626 | } 627 | --------------------------------------------------------------------------------