From 678669b3dc93869ea49d008abdeec4ea4bcd39b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Konstantin=20K=C3=A4fer?= Date: Mon, 28 May 2018 16:23:17 +0200 Subject: [linux] load cURL dynamically for wider compatibility --- platform/default/http_file_source.cpp | 165 +++++++++++++++++++++++++--------- platform/linux/config.cmake | 4 +- 2 files changed, 125 insertions(+), 44 deletions(-) diff --git a/platform/default/http_file_source.cpp b/platform/default/http_file_source.cpp index a9c442c2de..9166d92fb7 100644 --- a/platform/default/http_file_source.cpp +++ b/platform/default/http_file_source.cpp @@ -13,6 +13,85 @@ #include +// Dynamically load all cURL functions. Debian-derived systems upgraded the OpenSSL version linked +// to in https://salsa.debian.org/debian/curl/commit/95c94957bb7e89e36e78b995fed468c42f64d18d +// They state: +// Rename libcurl3 to libcurl4, because libcurl exposes an SSL_CTX via +// CURLOPT_SSL_CTX_FUNCTION, and this object changes incompatibly between +// openssl 1.0 and openssl 1.1. +// Since we are not accessing the underlying OpenSSL context, we don't care whether we're linking +// against libcurl3 or libcurl4; both use the ABI version 4 which hasn't changed since 2006 +// (see https://curl.haxx.se/libcurl/abi.html). In fact, cURL's ABI compatibility is very good as +// shown on https://abi-laboratory.pro/tracker/timeline/curl/ +// Therefore, we're dynamically loading the cURL symbols we need to avoid linking against versioned +// symbols. +#include + +namespace curl { + +#define CURL_FUNCTIONS \ + X(global_init) \ + X(getdate) \ + X(easy_strerror) \ + X(easy_init) \ + X(easy_setopt) \ + X(easy_cleanup) \ + X(easy_getinfo) \ + X(easy_reset) \ + X(multi_init) \ + X(multi_add_handle) \ + X(multi_remove_handle) \ + X(multi_cleanup) \ + X(multi_info_read) \ + X(multi_strerror) \ + X(multi_socket_action) \ + X(multi_setopt) \ + X(share_init) \ + X(share_cleanup) \ + X(slist_append) \ + X(slist_free_all) + +#define X(name) static decltype(&curl_ ## name) name = nullptr; +CURL_FUNCTIONS +#undef X + +static void* handle = nullptr; + +static void* load(const char* name) { + void* symbol = dlsym(handle, name); + if (const char* error = dlerror()) { + fprintf(stderr, "Cannot load symbol '%s': %s\n", name, error); + dlclose(handle); + handle = nullptr; + abort(); + } + return symbol; +} + +__attribute__((constructor)) +static void load() { + assert(!handle); + handle = dlopen("libcurl.so.4", RTLD_LAZY | RTLD_LOCAL); + if (!handle) { + fprintf(stderr, "Could not open shared library '%s'\n", "libcurl.so.4"); + abort(); + } + + #define X(name) name = (decltype(&curl_ ## name))load("curl_" #name); + CURL_FUNCTIONS + #undef X +} + +__attribute__((constructor)) +static void unload() { + if (handle) { + dlclose(handle); + } +} + +} // namespace curl + + #include #include #include @@ -21,13 +100,13 @@ static void handleError(CURLMcode code) { if (code != CURLM_OK) { - throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(code)); + throw std::runtime_error(std::string("CURL multi error: ") + curl::multi_strerror(code)); } } static void handleError(CURLcode code) { if (code != CURLE_OK) { - throw std::runtime_error(std::string("CURL easy error: ") + curl_easy_strerror(code)); + throw std::runtime_error(std::string("CURL easy error: ") + curl::easy_strerror(code)); } } @@ -91,29 +170,29 @@ private: }; HTTPFileSource::Impl::Impl() { - if (curl_global_init(CURL_GLOBAL_ALL)) { + if (curl::global_init(CURL_GLOBAL_ALL)) { throw std::runtime_error("Could not init cURL"); } - share = curl_share_init(); + share = curl::share_init(); - multi = curl_multi_init(); - handleError(curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, handleSocket)); - handleError(curl_multi_setopt(multi, CURLMOPT_SOCKETDATA, this)); - handleError(curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, startTimeout)); - handleError(curl_multi_setopt(multi, CURLMOPT_TIMERDATA, this)); + multi = curl::multi_init(); + handleError(curl::multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, handleSocket)); + handleError(curl::multi_setopt(multi, CURLMOPT_SOCKETDATA, this)); + handleError(curl::multi_setopt(multi, CURLMOPT_TIMERFUNCTION, startTimeout)); + handleError(curl::multi_setopt(multi, CURLMOPT_TIMERDATA, this)); } HTTPFileSource::Impl::~Impl() { while (!handles.empty()) { - curl_easy_cleanup(handles.front()); + curl::easy_cleanup(handles.front()); handles.pop(); } - curl_multi_cleanup(multi); + curl::multi_cleanup(multi); multi = nullptr; - curl_share_cleanup(share); + curl::share_cleanup(share); share = nullptr; timeout.stop(); @@ -125,12 +204,12 @@ CURL *HTTPFileSource::Impl::getHandle() { handles.pop(); return handle; } else { - return curl_easy_init(); + return curl::easy_init(); } } void HTTPFileSource::Impl::returnHandle(CURL *handle) { - curl_easy_reset(handle); + curl::easy_reset(handle); handles.push(handle); } @@ -138,11 +217,11 @@ void HTTPFileSource::Impl::checkMultiInfo() { CURLMsg *message = nullptr; int pending = 0; - while ((message = curl_multi_info_read(multi, &pending))) { + while ((message = curl::multi_info_read(multi, &pending))) { switch (message->msg) { case CURLMSG_DONE: { HTTPRequest *baton = nullptr; - curl_easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&baton); + curl::easy_getinfo(message->easy_handle, CURLINFO_PRIVATE, (char *)&baton); assert(baton); baton->handleResult(message->data.result); } break; @@ -166,7 +245,7 @@ void HTTPFileSource::Impl::perform(curl_socket_t s, util::RunLoop::Event events) int running_handles = 0; - curl_multi_socket_action(multi, s, flags, &running_handles); + curl::multi_socket_action(multi, s, flags, &running_handles); checkMultiInfo(); } @@ -200,9 +279,9 @@ int HTTPFileSource::Impl::handleSocket(CURL * /* handle */, curl_socket_t s, int void HTTPFileSource::Impl::onTimeout(Impl *context) { int running_handles; - CURLMcode error = curl_multi_socket_action(context->multi, CURL_SOCKET_TIMEOUT, 0, &running_handles); + CURLMcode error = curl::multi_socket_action(context->multi, CURL_SOCKET_TIMEOUT, 0, &running_handles); if (error != CURLM_OK) { - throw std::runtime_error(std::string("CURL multi error: ") + curl_multi_strerror(error)); + throw std::runtime_error(std::string("CURL multi error: ") + curl::multi_strerror(error)); } context->checkMultiInfo(); } @@ -233,45 +312,45 @@ HTTPRequest::HTTPRequest(HTTPFileSource::Impl* context_, Resource resource_, Fil // getting a 304 response if possible. This avoids redownloading unchanged data. if (resource.priorEtag) { const std::string header = std::string("If-None-Match: ") + *resource.priorEtag; - headers = curl_slist_append(headers, header.c_str()); + headers = curl::slist_append(headers, header.c_str()); } else if (resource.priorModified) { const std::string time = std::string("If-Modified-Since: ") + util::rfc1123(*resource.priorModified); - headers = curl_slist_append(headers, time.c_str()); + headers = curl::slist_append(headers, time.c_str()); } if (headers) { - curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); + curl::easy_setopt(handle, CURLOPT_HTTPHEADER, headers); } - handleError(curl_easy_setopt(handle, CURLOPT_PRIVATE, this)); - handleError(curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, error)); - handleError(curl_easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt")); - handleError(curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1)); - handleError(curl_easy_setopt(handle, CURLOPT_URL, resource.url.c_str())); - handleError(curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writeCallback)); - handleError(curl_easy_setopt(handle, CURLOPT_WRITEDATA, this)); - handleError(curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, headerCallback)); - handleError(curl_easy_setopt(handle, CURLOPT_HEADERDATA, this)); + handleError(curl::easy_setopt(handle, CURLOPT_PRIVATE, this)); + handleError(curl::easy_setopt(handle, CURLOPT_ERRORBUFFER, error)); + handleError(curl::easy_setopt(handle, CURLOPT_CAINFO, "ca-bundle.crt")); + handleError(curl::easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1)); + handleError(curl::easy_setopt(handle, CURLOPT_URL, resource.url.c_str())); + handleError(curl::easy_setopt(handle, CURLOPT_WRITEFUNCTION, writeCallback)); + handleError(curl::easy_setopt(handle, CURLOPT_WRITEDATA, this)); + handleError(curl::easy_setopt(handle, CURLOPT_HEADERFUNCTION, headerCallback)); + handleError(curl::easy_setopt(handle, CURLOPT_HEADERDATA, this)); #if LIBCURL_VERSION_NUM >= ((7) << 16 | (21) << 8 | 6) // Renamed in 7.21.6 - handleError(curl_easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate")); + handleError(curl::easy_setopt(handle, CURLOPT_ACCEPT_ENCODING, "gzip, deflate")); #else - handleError(curl_easy_setopt(handle, CURLOPT_ENCODING, "gzip, deflate")); + handleError(curl::easy_setopt(handle, CURLOPT_ENCODING, "gzip, deflate")); #endif - handleError(curl_easy_setopt(handle, CURLOPT_USERAGENT, "MapboxGL/1.0")); - handleError(curl_easy_setopt(handle, CURLOPT_SHARE, context->share)); + handleError(curl::easy_setopt(handle, CURLOPT_USERAGENT, "MapboxGL/1.0")); + handleError(curl::easy_setopt(handle, CURLOPT_SHARE, context->share)); // Start requesting the information. - handleError(curl_multi_add_handle(context->multi, handle)); + handleError(curl::multi_add_handle(context->multi, handle)); } HTTPRequest::~HTTPRequest() { - handleError(curl_multi_remove_handle(context->multi, handle)); + handleError(curl::multi_remove_handle(context->multi, handle)); context->returnHandle(handle); handle = nullptr; if (headers) { - curl_slist_free_all(headers); + curl::slist_free_all(headers); headers = nullptr; } } @@ -320,7 +399,7 @@ size_t HTTPRequest::headerCallback(char *const buffer, const size_t size, const // Always overwrite the modification date; We might already have a value here from the // Date header, but this one is more accurate. const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n - baton->response->modified = Timestamp{ Seconds(curl_getdate(value.c_str(), nullptr)) }; + baton->response->modified = Timestamp{ Seconds(curl::getdate(value.c_str(), nullptr)) }; } else if ((begin = headerMatches("etag: ", buffer, length)) != std::string::npos) { baton->response->etag = std::string(buffer + begin, length - begin - 2); // remove \r\n } else if ((begin = headerMatches("cache-control: ", buffer, length)) != std::string::npos) { @@ -330,7 +409,7 @@ size_t HTTPRequest::headerCallback(char *const buffer, const size_t size, const baton->response->mustRevalidate = cc.mustRevalidate; } else if ((begin = headerMatches("expires: ", buffer, length)) != std::string::npos) { const std::string value { buffer + begin, length - begin - 2 }; // remove \r\n - baton->response->expires = Timestamp{ Seconds(curl_getdate(value.c_str(), nullptr)) }; + baton->response->expires = Timestamp{ Seconds(curl::getdate(value.c_str(), nullptr)) }; } else if ((begin = headerMatches("retry-after: ", buffer, length)) != std::string::npos) { baton->retryAfter = std::string(buffer + begin, length - begin - 2); // remove \r\n } else if ((begin = headerMatches("x-rate-limit-reset: ", buffer, length)) != std::string::npos) { @@ -357,17 +436,17 @@ void HTTPRequest::handleResult(CURLcode code) { case CURLE_OPERATION_TIMEDOUT: response->error = std::make_unique( - Error::Reason::Connection, std::string{ curl_easy_strerror(code) } + ": " + error); + Error::Reason::Connection, std::string{ curl::easy_strerror(code) } + ": " + error); break; default: response->error = std::make_unique( - Error::Reason::Other, std::string{ curl_easy_strerror(code) } + ": " + error); + Error::Reason::Other, std::string{ curl::easy_strerror(code) } + ": " + error); break; } } else { long responseCode = 0; - curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &responseCode); + curl::easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &responseCode); if (responseCode == 200) { if (data) { diff --git a/platform/linux/config.cmake b/platform/linux/config.cmake index 57411f916d..5f4bf143b0 100644 --- a/platform/linux/config.cmake +++ b/platform/linux/config.cmake @@ -121,8 +121,10 @@ macro(mbgl_filesource) target_add_mason_package(mbgl-filesource PUBLIC sqlite) + # We're not referencing any cURL symbols since we're dynamically loading it. However, we want to + # link the library anyway since we're definitely going to load it on startup anyway. target_link_libraries(mbgl-filesource - PUBLIC -lcurl + PUBLIC -Wl,--no-as-needed -lcurl -Wl,--as-needed ) endmacro() -- cgit v1.2.1