Skip to content

Instantly share code, notes, and snippets.

@jay
Last active August 29, 2023 07:14
Show Gist options
  • Save jay/5b68c1bde985b1b2d23f2b583a26e5ad to your computer and use it in GitHub Desktop.
Save jay/5b68c1bde985b1b2d23f2b583a26e5ad to your computer and use it in GitHub Desktop.
Use libcurl to add some easy handles to a multi handle, then list them.
/* Use libcurl to add some easy handles to a multi handle, then list them.
Usage: ListEasyHandles
curl-library mailing list thread:
'How to list easy handles from a multi handle'
https://curl.haxx.se/mail/lib-2016-06/0000.html
* Copyright (C) 2016 Jay Satiro <raysatiro@yahoo.com>
https://curl.se/docs/copyright.html
https://gist.github.com/jay/5b68c1bde985b1b2d23f2b583a26e5ad
*/
#define _CRT_NONSTDC_NO_DEPRECATE
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
/* https://curl.se/download.html */
#include <curl/curl.h>
#undef FALSE
#define FALSE 0
#undef TRUE
#define TRUE 1
/* To access the linked list of easy handles in a multi handle we are accessing
the internal multi and easy libcurl structs that are different depending on
the version.
To support libcurl versions later than 8.2.0 would require reviewing the
structs of every release since then to see if the layout of the members used
has changed. If either has changed then a 'ver4' would be needed with the
new version range and structs.
CURL (internal: Curl_easy, or SessionHandle prior to 7.50.0)
CURLM (internal: Curl_multi)
*/
struct curl_pseudo_easy;
struct curl_pseudo_multi;
/* Version #1: 7.32.0 to 7.74.0
https://github.com/curl/curl/blob/curl-7_32_0/lib/urldata.h#L1614-L1617
https://github.com/curl/curl/blob/curl-7_32_0/lib/multihandle.h#L63-L69
[...]
https://github.com/curl/curl/blob/curl-7_74_0/lib/urldata.h#L1876-L1879
https://github.com/curl/curl/blob/curl-7_74_0/lib/multihandle.h#L79-L85
*/
const unsigned int ver1_minver = 0x072000; /* 7.32.0 */
const unsigned int ver1_maxver = 0x074A00; /* 7.74.0 */
struct curl_pseudo_easy_ver1 {
struct curl_pseudo_easy *next;
struct curl_pseudo_easy *prev;
};
struct curl_pseudo_multi_ver1 {
long type;
struct curl_pseudo_easy *easy;
};
/* Version #2: 7.75.0 to 8.1.2
https://github.com/curl/curl/blob/curl-7_75_0/lib/urldata.h#L1885-L1892
https://github.com/curl/curl/blob/curl-7_75_0/lib/multihandle.h#L83-L89
[...]
https://github.com/curl/curl/blob/curl-8_1_2/lib/urldata.h#L1901-L1908
https://github.com/curl/curl/blob/curl-8_1_2/lib/multihandle.h#L87-L93
*/
const unsigned int ver2_minver = 0x074B00; /* 7.75.0 */
const unsigned int ver2_maxver = 0x080102; /* 8.1.2 */
struct curl_pseudo_easy_ver2 {
unsigned int magic;
struct curl_pseudo_easy *next;
struct curl_pseudo_easy *prev;
};
struct curl_pseudo_multi_ver2 {
unsigned int magic;
struct curl_pseudo_easy *easy;
};
/* Version #3: 8.2.0 to 8.2.0
https://github.com/curl/curl/blob/curl-8_2_0/lib/urldata.h#L1904-L1918
https://github.com/curl/curl/blob/curl-8_2_0/lib/multihandle.h#L87-L93
*/
const unsigned int ver3_minver = 0x080200; /* 8.2.0 */
const unsigned int ver3_maxver = 0x080200; /* 8.2.0 */
struct curl_pseudo_easy_ver3 {
unsigned int magic;
curl_off_t id;
struct curl_pseudo_easy *next;
struct curl_pseudo_easy *prev;
};
struct curl_pseudo_multi_ver3 {
unsigned int magic;
struct curl_pseudo_easy *easy;
};
struct curl_pseudo_easy {
union {
struct curl_pseudo_easy_ver1 ver1;
struct curl_pseudo_easy_ver2 ver2;
struct curl_pseudo_easy_ver3 ver3;
} u;
};
struct curl_pseudo_multi {
union {
struct curl_pseudo_multi_ver1 ver1;
struct curl_pseudo_multi_ver2 ver2;
struct curl_pseudo_multi_ver3 ver3;
} u;
};
/* This list is a copy of the easy handle pointers in a multi handle. */
struct easy_handle_list {
struct easy_handle_list *next;
CURL *easy;
};
void free_easy_handle_list(struct easy_handle_list *list)
{
while(list) {
struct easy_handle_list *next = list->next;
free(list);
list = next;
}
return;
}
/*
Copy the easy handle pointers in multi to *list.
*list may be NULL even on success: that means no easy handles are in multi.
Free *list by calling free_easy_handle_list.
Returns TRUE on success or FALSE on failure.
*/
int GetEasyHandles(CURLM *multi, struct easy_handle_list **list)
{
CURL *easy, *first;
struct curl_pseudo_multi multi_container;
struct curl_pseudo_easy easy_container;
struct easy_handle_list *prev_item;
unsigned int curlver;
*list = NULL;
curlver = curl_version_info(CURLVERSION_NOW)->version_num;
memcpy(&multi_container, multi, sizeof(multi_container));
if(ver1_minver <= curlver && curlver <= ver1_maxver)
first = multi_container.u.ver1.easy;
else if(ver2_minver <= curlver && curlver <= ver2_maxver)
first = multi_container.u.ver2.easy;
else if(ver3_minver <= curlver && curlver <= ver3_maxver)
first = multi_container.u.ver3.easy;
else /* shouldn't happen */
return FALSE;
easy = first;
prev_item = NULL;
while(easy) {
struct easy_handle_list *item = calloc(1, sizeof(*item));
if(!item) {
free_easy_handle_list(*list);
*list = NULL;
return FALSE;
}
item->easy = easy;
if(prev_item)
prev_item->next = item;
else
*list = item;
/* get the next easy handle in the multi handle */
memcpy(&easy_container, easy, sizeof(easy_container));
if(ver1_minver <= curlver && curlver <= ver1_maxver)
easy = easy_container.u.ver1.next;
else if(ver2_minver <= curlver && curlver <= ver2_maxver)
easy = easy_container.u.ver2.next;
else if(ver3_minver <= curlver && curlver <= ver3_maxver)
easy = easy_container.u.ver3.next;
else { /* shouldn't happen */
free_easy_handle_list(*list);
*list = NULL;
return FALSE;
}
if(!easy || easy == first)
break;
prev_item = item;
}
return TRUE;
}
/*
Show the address of every easy handle in a multi handle.
Returns TRUE on success or FALSE on failure.
*/
int ListEasyHandles(CURLM *multi)
{
struct easy_handle_list *list, *p;
if(!GetEasyHandles(multi, &list)) {
fprintf(stderr, "Error: GetEasyHandles() failed.\n");
return FALSE;
}
if(!list) {
printf("Found no easy handles in multi handle %p.\n", multi);
return TRUE;
}
for(p = list; p; p = p->next) {
printf("Found easy handle %p in multi handle %p.\n", p->easy, multi);
}
free_easy_handle_list(list);
return TRUE;
}
int main(int argc, char *argv[])
{
CURL *easy[3];
CURLM *multi;
unsigned int i, libcurl_version_num;
(void)argc, (void)argv;
if(curl_global_init(CURL_GLOBAL_ALL)) {
fprintf(stderr, "Fatal: The initialization of libcurl has failed.\n");
return EXIT_FAILURE;
}
if(atexit(curl_global_cleanup)) {
fprintf(stderr, "Fatal: atexit failed to register curl_global_cleanup.\n");
curl_global_cleanup();
return EXIT_FAILURE;
}
libcurl_version_num = curl_version_info(CURLVERSION_NOW)->version_num;
if(libcurl_version_num < ver1_minver) {
fprintf(stderr, "Fatal: libcurl version unsupported: too old.\n");
return EXIT_FAILURE;
}
if(libcurl_version_num > ver3_maxver) {
fprintf(stderr, "Fatal: libcurl version unsupported: too new.\n");
return EXIT_FAILURE;
}
if(strstr(curl_version_info(CURLVERSION_NOW)->version, "-DEV") &&
(libcurl_version_num == ver1_minver ||
libcurl_version_num == ver1_maxver ||
libcurl_version_num == ver2_minver ||
libcurl_version_num == ver2_maxver ||
libcurl_version_num == ver3_minver ||
libcurl_version_num == ver3_maxver)) {
fprintf(stderr, "Fatal: libcurl version unsupported: dev build and its "
"revision may come before or after a struct change.\n");
return EXIT_FAILURE;
}
multi = curl_multi_init();
if(!multi) {
fprintf(stderr, "Fatal: Couldn't create a multi handle.\n");
return EXIT_FAILURE;
}
for(i = 0; i < sizeof(easy) / sizeof(easy[0]); ++i) {
easy[i] = curl_easy_init();
if(!easy[i]) {
fprintf(stderr, "Fatal: Couldn't create an easy handle.\n");
return EXIT_FAILURE;
}
curl_multi_add_handle(multi, easy[i]);
printf("Added easy handle %p to multi handle %p.\n", easy[i], multi);
}
if(!ListEasyHandles(multi)) {
fprintf(stderr, "Fatal: ListEasyHandles() failed.\n");
return EXIT_FAILURE;
}
curl_multi_cleanup(multi);
for(i = 0; i < sizeof(easy) / sizeof(easy[0]); ++i) {
curl_easy_cleanup(easy[i]);
}
return EXIT_SUCCESS;
}
@jeroen
Copy link

jeroen commented Apr 17, 2018

Was this ever merged into libcurl?

@jay
Copy link
Author

jay commented Jan 8, 2019

@jeroen No since the method relies on internal structures that are subject to change. I didn't see your comment until just now by happenstance, it's possible GitHub doesn't notify of gist comments or I missed the notification.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment