Load libcurl.so at runtime.

Loading libcurl at runtime instead of linking it at compile time makes
things a lot easier and more reliable on Windows. On other platform
libcurl can be installed as optional dependency instead as an hard one.
This commit is contained in:
Yamagi Burmeister 2018-12-11 14:11:30 +01:00
parent cd1b67e489
commit c0a6e4270f
4 changed files with 349 additions and 33 deletions

View File

@ -352,6 +352,10 @@ build/client/%.o: %.c
release/yquake2.exe : LDFLAGS += -mwindows
ifeq ($(WITH_CURL),yes)
release/quake2 : CFLAGS += -DUSE_CURL
endif
ifeq ($(WITH_OPENAL),yes)
release/yquake2.exe : CFLAGS += -DUSE_OPENAL -DDEFAULT_OPENAL_DRIVER='"openal32.dll"'
endif
@ -379,7 +383,6 @@ release/quake2 : CFLAGS += -Wno-unused-result
ifeq ($(WITH_CURL),yes)
release/quake2 : CFLAGS += -DUSE_CURL
release/quake2 : LDFLAGS += -lcurl
endif
ifeq ($(WITH_OPENAL),yes)
@ -690,6 +693,7 @@ CLIENT_OBJS_ := \
src/client/cl_screen.o \
src/client/cl_tempentities.o \
src/client/cl_view.o \
src/client/curl/qcurl.o \
src/client/input/sdl.o \
src/client/menu/menu.o \
src/client/menu/qmenu.o \

View File

@ -27,6 +27,7 @@
#include <ctype.h>
#include "header/client.h"
#include "curl/header/qcurl.h"
#ifdef USE_CURL
@ -66,7 +67,7 @@ static size_t CL_HTTP_Recv(void *ptr, size_t size, size_t nmemb, void *stream)
{
double length = 0;
curl_easy_getinfo(dl->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
qcurl_easy_getinfo(dl->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
// Mkay, the remote file should be at least one byte long.
// Since this is used for paclists only we assume that the
@ -198,33 +199,33 @@ static void CL_StartHTTPDownload (dlqueue_t *entry, dlhandle_t *dl)
// Setup and configure the CURL part of our download handle.
if (!dl->curl)
{
dl->curl = curl_easy_init();
dl->curl = qcurl_easy_init();
}
Com_sprintf(dl->URL, sizeof(dl->URL), "%s%s", cls.downloadServer, escapedFilePath);
curl_easy_setopt(dl->curl, CURLOPT_ENCODING, "");
qcurl_easy_setopt(dl->curl, CURLOPT_ENCODING, "");
if (dl->file)
{
curl_easy_setopt(dl->curl, CURLOPT_WRITEDATA, dl->file);
curl_easy_setopt(dl->curl, CURLOPT_WRITEFUNCTION, NULL);
qcurl_easy_setopt(dl->curl, CURLOPT_WRITEDATA, dl->file);
qcurl_easy_setopt(dl->curl, CURLOPT_WRITEFUNCTION, NULL);
}
else
{
curl_easy_setopt(dl->curl, CURLOPT_WRITEDATA, dl);
curl_easy_setopt(dl->curl, CURLOPT_WRITEFUNCTION, CL_HTTP_Recv);
qcurl_easy_setopt(dl->curl, CURLOPT_WRITEDATA, dl);
qcurl_easy_setopt(dl->curl, CURLOPT_WRITEFUNCTION, CL_HTTP_Recv);
}
curl_easy_setopt(dl->curl, CURLOPT_PROXY, cl_http_proxy->string);
curl_easy_setopt(dl->curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(dl->curl, CURLOPT_MAXREDIRS, 5);
curl_easy_setopt(dl->curl, CURLOPT_PROGRESSDATA, dl);
curl_easy_setopt(dl->curl, CURLOPT_USERAGENT, Cvar_VariableString ("version"));
curl_easy_setopt(dl->curl, CURLOPT_REFERER, cls.downloadReferer);
curl_easy_setopt(dl->curl, CURLOPT_URL, dl->URL);
qcurl_easy_setopt(dl->curl, CURLOPT_PROXY, cl_http_proxy->string);
qcurl_easy_setopt(dl->curl, CURLOPT_FOLLOWLOCATION, 1);
qcurl_easy_setopt(dl->curl, CURLOPT_MAXREDIRS, 5);
qcurl_easy_setopt(dl->curl, CURLOPT_PROGRESSDATA, dl);
qcurl_easy_setopt(dl->curl, CURLOPT_USERAGENT, Cvar_VariableString ("version"));
qcurl_easy_setopt(dl->curl, CURLOPT_REFERER, cls.downloadReferer);
qcurl_easy_setopt(dl->curl, CURLOPT_URL, dl->URL);
if (curl_multi_add_handle (multi, dl->curl) != CURLM_OK)
if (qcurl_multi_add_handle(multi, dl->curl) != CURLM_OK)
{
Com_Printf("HTTP download: cURL error\n");
dl->queueEntry->state = DLQ_STATE_DONE;
@ -473,7 +474,7 @@ static void CL_FinishHTTPDownload(void)
do
{
// Get a message from CURL.
CURLMsg *msg = curl_multi_info_read(multi, &msgs_in_queue);
CURLMsg *msg = qcurl_multi_info_read(multi, &msgs_in_queue);
if (!msg)
{
@ -551,7 +552,7 @@ static void CL_FinishHTTPDownload(void)
{
case CURLE_HTTP_RETURNED_ERROR:
case CURLE_OK:
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode);
qcurl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responseCode);
if (responseCode == 404)
{
@ -564,7 +565,7 @@ static void CL_FinishHTTPDownload(void)
}
// ...and remove it from the CURL multihandle.
curl_multi_remove_handle(multi, dl->curl);
qcurl_multi_remove_handle(multi, dl->curl);
Com_Printf("HTTP download: %s - File Not Found\n", dl->queueEntry->quakePath);
@ -597,7 +598,7 @@ static void CL_FinishHTTPDownload(void)
}
// ...and the handle from CURLs mutihandle.
curl_multi_remove_handle (multi, dl->curl);
qcurl_multi_remove_handle(multi, dl->curl);
// Special case: We're already aborting HTTP downloading,
// so we can't just kill everything. Otherwise we'll get
@ -614,7 +615,7 @@ static void CL_FinishHTTPDownload(void)
break;
default:
Com_Printf ("HTTP download: cURL error - %s\n", curl_easy_strerror(result));
Com_Printf ("HTTP download: cURL error - %s\n", qcurl_easy_strerror(result));
i = strlen(dl->queueEntry->quakePath);
@ -625,7 +626,7 @@ static void CL_FinishHTTPDownload(void)
}
// ...and the handle from CURLs mutihandle.
curl_multi_remove_handle (multi, dl->curl);
qcurl_multi_remove_handle (multi, dl->curl);
continue;
break;
@ -651,7 +652,7 @@ static void CL_FinishHTTPDownload(void)
}
// Remove the file fo CURLs multihandle.
curl_multi_remove_handle (multi, dl->curl);
qcurl_multi_remove_handle (multi, dl->curl);
} while (msgs_in_queue > 0);
@ -736,7 +737,11 @@ static void CL_StartNextHTTPDownload(void)
*/
void CL_InitHTTPDownloads (void)
{
curl_global_init (CURL_GLOBAL_NOTHING);
// We're initializing the cURL backend here because
// currently we're the only user. As soon as there
// are other users this must be moved up into the
// global client intialization.
qcurlInit();
}
/*
@ -771,8 +776,11 @@ void CL_HTTP_Cleanup(qboolean fullShutdown)
if (dl->curl)
{
if (multi)
curl_multi_remove_handle (multi, dl->curl);
curl_easy_cleanup (dl->curl);
{
qcurl_multi_remove_handle(multi, dl->curl);
}
qcurl_easy_cleanup(dl->curl);
dl->curl = NULL;
}
@ -782,14 +790,17 @@ void CL_HTTP_Cleanup(qboolean fullShutdown)
// Cleanup CURL multihandle.
if (multi)
{
curl_multi_cleanup(multi);
qcurl_multi_cleanup(multi);
multi = NULL;
}
// Shutdown CURL.
if (fullShutdown)
{
curl_global_cleanup ();
// This must be moved up into the generic
// client shutdown as soon as there're
// other users of the cURL backend.
qcurlShutdown();
httpDown = true;
}
}
@ -807,6 +818,16 @@ void CL_SetHTTPServer (const char *URL)
dlqueue_t *last = NULL;
dlqueue_t *q = &cls.downloadQueue;
// This code abuses the download server setting to
// determine if HTTP downloads are possible. So if
// we don't set a download server, no downloads are
// queued and run. :)
if (!qcurlInitialized)
{
cls.downloadServer[0] = 0;
return;
}
CL_HTTP_Cleanup(false);
// Cleanup download queues.
@ -841,7 +862,7 @@ void CL_SetHTTPServer (const char *URL)
Com_Error(ERR_DROP, "HTTP download: Still have old handle?!");
}
multi = curl_multi_init();
multi = qcurl_multi_init();
}
/*
@ -984,7 +1005,7 @@ void CL_RunHTTPDownloads(void)
int newHandleCount;
CURLMcode ret;
// No HTTP server given.
// No HTTP server given or not initialized.
if (!cls.downloadServer[0])
{
return;
@ -993,7 +1014,7 @@ void CL_RunHTTPDownloads(void)
// Kick CURL into action.
do
{
ret = curl_multi_perform(multi, &newHandleCount);
ret = qcurl_multi_perform(multi, &newHandleCount);
if (newHandleCount < handleCount)
{

View File

@ -0,0 +1,70 @@
/*
* Copyright (C) 2018 Yamagi
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* CURL backend for Quake II.
*
* =======================================================================
*/
#ifdef USE_CURL
#ifndef QCURL_H
#define QCURL_H
// --------
#include <curl/curl.h>
#include "../../../common/header/common.h"
// --------
// True if cURL is initialized.
extern qboolean qcurlInitialized;
// Function pointers to cURL.
extern void (*qcurl_easy_cleanup)(CURL *curl);
extern CURL *(*qcurl_easy_init)(void);
extern CURLcode (*qcurl_easy_getinfo)(CURL *curl, CURLINFO info, ...);
extern CURLcode (*qcurl_easy_setopt)(CURL *curl, CURLoption option, ...);
extern const char *(*qcurl_easy_strerror)(CURLcode);
extern void (*qcurl_global_cleanup)(void);
extern CURLcode (*qcurl_global_init)(long flags);
extern CURLMcode (*qcurl_multi_add_handle)(CURLM *multi_handle, CURL *curl_handle);
extern CURLMcode (*qcurl_multi_cleanup)(CURLM *multi_handle);
extern CURLMsg *(*qcurl_multi_info_read)(CURLM *multi_handle, int *msgs_in_queue);
extern CURLM *(*qcurl_multi_init)(void);
extern CURLMcode (*qcurl_multi_perform)(CURLM *multi_handle, int *running_handles);
extern CURLMcode (*qcurl_multi_remove_handle)(CURLM *multi_handle, CURL *curl_handle);
// --------
// Loads and initialized cURL.
qboolean qcurlInit(void);
// Shuts cURL down and unloads it.
void qcurlShutdown(void);
// --------
#endif // QCURL_H
#endif // USE_CURL

221
src/client/curl/qcurl.c Normal file
View File

@ -0,0 +1,221 @@
/*
* Copyright (C) 2018 Yamagi
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* CURL backend for Quake II.
*
* =======================================================================
*/
#ifdef USE_CURL
// --------
#include <curl/curl.h>
#include "header/qcurl.h"
#include "../../common/header/common.h"
// --------
// True if cURL is initialized.
qboolean qcurlInitialized;
// Pointer to the dynamic library.
static void *curlhandle;
// CVars
static cvar_t *cl_libcurl;
// Function pointers to cURL.
void (*qcurl_easy_cleanup)(CURL *curl);
CURL *(*qcurl_easy_init)(void);
CURLcode (*qcurl_easy_getinfo)(CURL *curl, CURLINFO info, ...);
CURLcode (*qcurl_easy_setopt)(CURL *curl, CURLoption option, ...);
const char *(*qcurl_easy_strerror)(CURLcode);
void (*qcurl_global_cleanup)(void);
CURLcode (*qcurl_global_init)(long flags);
CURLMcode (*qcurl_multi_add_handle)(CURLM *multi_handle, CURL *curl_handle);
CURLMcode (*qcurl_multi_cleanup)(CURLM *multi_handle);
CURLMsg *(*qcurl_multi_info_read)(CURLM *multi_handle, int *msgs_in_queue);
CURLM *(*qcurl_multi_init)(void);
CURLMcode (*qcurl_multi_perform)(CURLM *multi_handle, int *running_handles);
CURLMcode (*qcurl_multi_remove_handle)(CURLM *multi_handle, CURL *curl_handle);
// --------
/*
* Load libcurl, connect the function pointer
* and call cURLs global init function.
*/
qboolean qcurlInit(void)
{
Com_Printf("------- curl initialization -------\n");
assert(!qcurlInitialized && "cURL already initialized?!");
// Most systems have only one distinct name for the
// libcurl, loading that one will be successfull in
// at least 99% of all cases. But Linux, the mother
// of chaos, is more complicated. Debian / Ubuntu
// alone has 6 possible names... So:
// - Let's try whatever the user has written into
// the cl_libcurl cvar first.
// - If that fails try all possible names until we
// find a working one.
// - If we found one put it into cl_libcurl, so we
// don't need to iterate trought the whole list
// the next time the game is started.
// - If we found none error out.
#ifdef __APPLE__
const char *libcurl[] = { "libcurl.dylib", NULL };
#elif __linux__
const char *libcurl[] = { "libcurl.so", "libcurl.so.3", "libcurl.so.4", "libcurl-gnutls.so.3",
"libcurl-gnutls.so.4", "libcurl-nss.so.3", "libcurl-nss.so.4", NULL };
#elif _WIN32
const char *libcurl[] = { "curl.dll", NULL };
#else
const char *libcurl[] = { "libcurl.so", NULL };
#endif
// Mkay, let's try to find a working libcurl.
cl_libcurl = Cvar_Get("cl_libcurl", (char *)libcurl[0], CVAR_ARCHIVE);
Com_Printf("LoadLibrary(%s)\n", cl_libcurl->string);
Sys_LoadLibrary(cl_libcurl->string, NULL, &curlhandle);
if (!curlhandle)
{
Com_Printf("Loading %s failed!\n", cl_libcurl->string);
for (int i = 0; libcurl[i] != NULL; i++)
{
// Tried that one already.
if (!strcmp(cl_libcurl->string, libcurl[i]))
{
continue;
}
Com_Printf("LoadLibrary(%s)\n", libcurl[i]);
Sys_LoadLibrary(libcurl[i], NULL, &curlhandle);
if (!curlhandle)
{
// No luck with this one.
Com_Printf("Loading %s failed!\n", libcurl[i]);
continue;
}
else
{
// Got one!
Cvar_Set("cl_libcurl", (char *)libcurl[i]);
break;
}
}
if (!curlhandle)
{
goto error;
}
}
// libcurl loaded sucessfully, connect the pointers.
#define CONCURL(var, sym) do { \
var = Sys_GetProcAddress(curlhandle, sym); \
if (!var) goto error; \
} while(0)
CONCURL(qcurl_easy_cleanup, "curl_easy_cleanup");
CONCURL(qcurl_easy_init, "curl_easy_init");
CONCURL(qcurl_easy_getinfo, "curl_easy_getinfo");
CONCURL(qcurl_easy_setopt, "curl_easy_setopt");
CONCURL(qcurl_easy_strerror, "curl_easy_strerror");
CONCURL(qcurl_global_cleanup, "curl_global_cleanup");
CONCURL(qcurl_global_init, "curl_global_init");
CONCURL(qcurl_multi_add_handle, "curl_multi_add_handle");
CONCURL(qcurl_multi_cleanup, "curl_multi_cleanup");
CONCURL(qcurl_multi_info_read, "curl_multi_info_read");
CONCURL(qcurl_multi_init, "curl_multi_init");
CONCURL(qcurl_multi_perform, "curl_multi_perform");
CONCURL(qcurl_multi_remove_handle, "curl_multi_remove_handle");
#undef CONCURL
// And finally the global cURL initialization.
qcurl_global_init(CURL_GLOBAL_NOTHING);
qcurlInitialized = true;
Com_Printf("------------------------------------\n\n");
return true;
error:
qcurlShutdown();
Com_Printf("------------------------------------\n\n");
return false;
}
/*
* Calls cURLs global shutdown funtion,
* unloads libcurl and set the function
* pointers to NULL.
*/
void qcurlShutdown(void)
{
if (qcurlInitialized)
{
Com_Printf("Shutting down curl.\n");
qcurl_global_cleanup();
qcurlInitialized = false;
}
qcurl_easy_cleanup = NULL;
qcurl_easy_init = NULL;
qcurl_easy_getinfo = NULL;
qcurl_easy_setopt = NULL;
qcurl_easy_strerror = NULL;
qcurl_global_cleanup = NULL;
qcurl_global_init = NULL;
qcurl_multi_add_handle = NULL;
qcurl_multi_cleanup = NULL;
qcurl_multi_info_read = NULL;
qcurl_multi_init = NULL;
qcurl_multi_perform = NULL;
qcurl_multi_remove_handle = NULL;
if (curlhandle)
{
Sys_FreeLibrary(curlhandle);
curlhandle = NULL;
}
}
// --------
#endif // USE_CURL