/* vim: ts=4:sw=4:ft=xs:fdm=marker
*
* Copyright 2011-2015 (C) Przemyslaw Iskra <sparky at pld-linux.org>
*
* Loosely based on code by Cris Bailiff <c.bailiff+curl at devsecure.com>,
* and subsequent fixes by other contributors.
*/
/* make a new multi */
static perl_curl_multi_t *
perl_curl_multi_new( void )
/*{{{*/ {
perl_curl_multi_t *multi;
Newxz( multi, 1, perl_curl_multi_t );
multi->handle = curl_multi_init();
return multi;
} /*}}}*/
/* delete the multi */
static void
perl_curl_multi_delete( pTHX_ perl_curl_multi_t *multi )
/*{{{*/ {
perl_curl_multi_callback_code_t i;
if ( multi->handle ) {
curl_multi_setopt( multi->handle, CURLMOPT_SOCKETFUNCTION, NULL );
#ifdef CURLMOPT_TIMERFUNCTION
curl_multi_setopt( multi->handle, CURLMOPT_TIMERFUNCTION, NULL );
#endif
}
/* remove and mortalize all easy handles */
if ( multi->easies ) {
simplell_t *next, *now = multi->easies;
do {
perl_curl_easy_t *easy;
easy = INT2PTR( perl_curl_easy_t *, now->key );
next = now->next;
/* This frees `now` and does sv_2mortal(now->value): */
perl_curl_easy_remove_from_multi( aTHX_ easy );
} while ( ( now = next ) != NULL );
}
if ( multi->handle )
curl_multi_cleanup( multi->handle );
SIMPLELL_FREE( multi->socket_data, sv_2mortal );
for( i = 0; i < CB_MULTI_LAST; i++ ) {
sv_2mortal( multi->cb[i].func );
sv_2mortal( multi->cb[i].data );
}
Safefree( multi );
} /*}}}*/
static int
cb_multi_socket( CURL *easy_handle, curl_socket_t s, int what, void *userptr,
void *socketp )
/*{{{*/ {
dTHX;
perl_curl_multi_t *multi;
perl_curl_easy_t *easy;
multi = (perl_curl_multi_t *) userptr;
(void) curl_easy_getinfo( easy_handle, CURLINFO_PRIVATE, (void *) &easy );
/* $multi, $easy, $socket, $what, $socketdata, $userdata */
SV *args[] = {
/* 0 */ SELF2PERL( multi ),
/* 1 */ SELF2PERL( easy ),
/* 2 */ newSVuv( s ),
/* 3 */ newSViv( what ),
/* 4 */ &PL_sv_undef
};
if ( socketp )
args[4] = newSVsv( (SV *) socketp );
return PERL_CURL_CALL( &multi->cb[ CB_MULTI_SOCKET ], args );
} /*}}}*/
static int
cb_multi_timer( CURLM *multi_handle, long timeout_ms, void *userptr )
/*{{{*/ {
dTHX;
perl_curl_multi_t *multi;
multi = (perl_curl_multi_t *) userptr;
/* $multi, $timeout, $userdata */
SV *args[] = {
SELF2PERL( multi ),
newSViv( timeout_ms )
};
return PERL_CURL_CALL( &multi->cb[ CB_MULTI_TIMER ], args );
} /*}}}*/
#ifdef CALLBACK_TYPECHECK
static curl_socket_callback pct_socket __attribute__((unused)) = cb_multi_socket;
static curl_multi_timer_callback pct_timer __attribute__((unused)) = cb_multi_timer;
#endif
static int
perl_curl_multi_magic_free( pTHX_ SV *sv, MAGIC *mg )
{
if ( mg->mg_ptr ) {
/* prevent recursive destruction */
SvREFCNT( sv ) = 1 << 30;
perl_curl_multi_delete( aTHX_ (void *) mg->mg_ptr );
SvREFCNT( sv ) = 0;
}
return 0;
}
char **
perl_curl_multi_blacklist( pTHX_ SV *arrayref )
{
AV *array;
int array_len, i;
char **blacklist;
if ( !SvOK( arrayref ) )
return NULL;
if ( !SvROK( arrayref ) )
croak( "not an array" );
array = (AV *) SvRV( arrayref );
array_len = av_len( array );
if ( array_len == -1 )
return NULL;
Newxz( blacklist, array_len + 2, char * );
for ( i = 0; i <= array_len; i++ ) {
SV **sv;
sv = av_fetch( array, i, 0 );
if ( !SvOK( *sv ) )
continue;
blacklist[i] = SvPV_nolen( *sv );
}
return blacklist;
}
static MGVTBL perl_curl_multi_vtbl = {
NULL, NULL, NULL, NULL
,perl_curl_multi_magic_free
,NULL
,perl_curl_any_magic_nodup
#ifdef MGf_LOCAL
,NULL
#endif
};
#define MULTI_DIE( ret ) \
STMT_START { \
CURLMcode code = (ret); \
if ( code != CURLM_OK ) \
die_code( "Multi", code ); \
} STMT_END
MODULE = Net::Curl PACKAGE = Net::Curl::Multi
INCLUDE: const-multi-xs.inc
PROTOTYPES: ENABLE
void
new( sclass="Net::Curl::Multi", base=HASHREF_BY_DEFAULT )
const char *sclass
SV *base
PREINIT:
perl_curl_multi_t *multi;
HV *stash;
PPCODE:
if ( ! SvOK( base ) || ! SvROK( base ) )
croak( "object base must be a valid reference\n" );
multi = perl_curl_multi_new();
perl_curl_setptr( aTHX_ base, &perl_curl_multi_vtbl, multi );
/* those must be set or else socket_action() segfaults */
curl_multi_setopt( multi->handle, CURLMOPT_SOCKETFUNCTION,
cb_multi_socket );
curl_multi_setopt( multi->handle, CURLMOPT_SOCKETDATA, multi );
stash = gv_stashpv( sclass, 0 );
ST(0) = sv_bless( base, stash );
multi->perl_self = SvRV( ST(0) );
XSRETURN(1);
void
add_handle( multi, easy )
Net::Curl::Multi multi
Net::Curl::Easy easy
PREINIT:
CURLMcode ret;
CODE:
if ( easy->multi )
croak( "Specified easy handle is attached to %s multi handle already",
easy->multi == multi ? "this" : "another" );
ret = curl_multi_add_handle( multi->handle, easy->handle );
if ( !ret ) {
SV **easysv_ptr;
easysv_ptr = perl_curl_simplell_add( aTHX_ &multi->easies,
PTR2nat( easy ) );
*easysv_ptr = SELF2PERL( easy );
easy->multi = multi;
}
MULTI_DIE( ret );
void
remove_handle( multi, easy )
Net::Curl::Multi multi
Net::Curl::Easy easy
PREINIT:
CURLMcode ret;
CODE:
CLEAR_ERRSV();
if ( easy->multi != multi )
croak( "Specified easy handle is not attached to %s multi handle",
easy->multi ? "this" : "any" );
ret = perl_curl_easy_remove_from_multi( aTHX_ easy );
/* rethrow errors */
if ( SvTRUE( ERRSV ) )
croak( NULL );
MULTI_DIE( ret );
void
info_read( multi )
Net::Curl::Multi multi
PREINIT:
int queue;
CURLMsg *msg;
PPCODE:
CLEAR_ERRSV();
while ( (msg = curl_multi_info_read( multi->handle, &queue ) ) ) {
/* most likely CURLMSG_DONE */
if ( msg->msg != CURLMSG_NONE && msg->msg != CURLMSG_LAST ) {
Net__Curl__Easy easy;
SV *errsv;
curl_easy_getinfo( msg->easy_handle,
CURLINFO_PRIVATE, (void *) &easy );
EXTEND( SP, 3 );
mPUSHs( newSViv( msg->msg ) );
mPUSHs( SELF2PERL( easy ) );
errsv = sv_newmortal();
sv_setref_iv( errsv, "Net::Curl::Easy::Code",
msg->data.result );
PUSHs( errsv );
/* cannot rethrow errors, because we want to make sure we
* return the easy, but $@ should be set */
XSRETURN( 3 );
}
/* rethrow errors */
if ( SvTRUE( ERRSV ) )
croak( NULL );
};
/* rethrow errors */
if ( SvTRUE( ERRSV ) )
croak( NULL );
XSRETURN_EMPTY;
void
fdset( multi )
Net::Curl::Multi multi
PREINIT:
CURLMcode ret;
fd_set fdread, fdwrite, fdexcep;
int maxfd, i;
int readsize, writesize, excepsize;
unsigned char readset[ sizeof( fd_set ) ] = { 0 };
unsigned char writeset[ sizeof( fd_set ) ] = { 0 };
unsigned char excepset[ sizeof( fd_set ) ] = { 0 };
PPCODE:
FD_ZERO( &fdread );
FD_ZERO( &fdwrite );
FD_ZERO( &fdexcep );
ret = curl_multi_fdset( multi->handle,
&fdread, &fdwrite, &fdexcep, &maxfd );
MULTI_DIE( ret );
readsize = writesize = excepsize = 0;
/* TODO: this is rather slow, should copy whole bytes instead, but
* some fdset implementations may be hard to predict */
if ( maxfd != -1 ) {
for ( i = 0; i <= maxfd; i++ ) {
if ( FD_ISSET( i, &fdread ) ) {
readsize = i / 8 + 1;
readset[ i / 8 ] |= 1 << ( i % 8 );
}
if ( FD_ISSET( i, &fdwrite ) ) {
writesize = i / 8 + 1;
writeset[ i / 8 ] |= 1 << ( i % 8 );
}
if ( FD_ISSET( i, &fdexcep ) ) {
excepsize = i / 8 + 1;
excepset[ i / 8 ] |= 1 << ( i % 8 );
}
}
}
EXTEND( SP, 3 );
mPUSHs( newSVpvn( (char *) readset, readsize ) );
mPUSHs( newSVpvn( (char *) writeset, writesize ) );
mPUSHs( newSVpvn( (char *) excepset, excepsize ) );
long
timeout( multi )
Net::Curl::Multi multi
PREINIT:
long timeout;
CURLMcode ret;
CODE:
ret = curl_multi_timeout( multi->handle, &timeout );
MULTI_DIE( ret );
RETVAL = timeout;
OUTPUT:
RETVAL
void
setopt( multi, option, value )
Net::Curl::Multi multi
int option
SV *value
PREINIT:
CURLMcode ret1 = CURLM_OK, ret2 = CURLM_OK;
#ifdef CURLMOPT_PIPELINING_SERVER_BL
#ifdef CURLMOPT_PIPELINING_SITE_BL
char **blacklist;
#endif
#endif
CODE:
switch ( option ) {
case CURLMOPT_SOCKETDATA:
SvREPLACE( multi->cb[ CB_MULTI_SOCKET ].data, value );
break;
case CURLMOPT_SOCKETFUNCTION:
SvREPLACE( multi->cb[ CB_MULTI_SOCKET ].func, value );
break;
/* introduced in 7.16.0 */
#ifdef CURLMOPT_TIMERDATA
#ifdef CURLMOPT_TIMERFUNCTION
case CURLMOPT_TIMERDATA:
SvREPLACE( multi->cb[ CB_MULTI_TIMER ].data, value );
break;
case CURLMOPT_TIMERFUNCTION:
SvREPLACE( multi->cb[ CB_MULTI_TIMER ].func, value );
ret2 = curl_multi_setopt( multi->handle, CURLMOPT_TIMERFUNCTION,
SvOK( value ) ? cb_multi_timer : NULL );
ret1 = curl_multi_setopt( multi->handle, CURLMOPT_TIMERDATA, multi );
break;
#endif
#endif
/* introduced in 7.30.0 */
#ifdef CURLMOPT_PIPELINING_SERVER_BL
#ifdef CURLMOPT_PIPELINING_SITE_BL
case CURLMOPT_PIPELINING_SERVER_BL:
case CURLMOPT_PIPELINING_SITE_BL:
blacklist = perl_curl_multi_blacklist( aTHX_ value );
ret1 = curl_multi_setopt( multi->handle, option, blacklist );
if ( blacklist )
Safefree( blacklist );
break;
#endif
#endif
/* default cases */
default:
if ( option < CURLOPTTYPE_OBJECTPOINT ) {
/* A long (integer) value */
ret1 = curl_multi_setopt( multi->handle, option,
(long) SvIV( value ) );
} else {
croak( "Unknown curl multi option" );
}
break;
};
MULTI_DIE( ret2 );
MULTI_DIE( ret1 );
int
perform( multi )
Net::Curl::Multi multi
PREINIT:
int remaining;
CURLMcode ret;
CODE:
CLEAR_ERRSV();
do {
ret = curl_multi_perform( multi->handle, &remaining );
} while ( ret == CURLM_CALL_MULTI_PERFORM );
/* rethrow errors */
if ( SvTRUE( ERRSV ) )
croak( NULL );
MULTI_DIE( ret );
RETVAL = remaining;
OUTPUT:
RETVAL
#if LIBCURL_VERSION_NUM >= 0x071C00
int
wait( multi, ... )
Net::Curl::Multi multi
PROTOTYPE: $;$$
PREINIT:
int timeout = -1;
SV *extra_fds = NULL;
int remaining;
CURLMcode ret;
struct curl_waitfd *wait_for = NULL;
unsigned int extra_nfds = 0;
CODE:
CLEAR_ERRSV();
if ( items > 1 )
timeout = SvIV( ST( items - 1 ) );
if ( items > 2 )
extra_fds = ST( 1 );
if ( extra_fds && SvOK( extra_fds ) )
{
int i;
AV *array;
if ( !SvROK( extra_fds ) || SvTYPE( SvRV( extra_fds ) ) != SVt_PVAV )
croak( "must be an arrayref" );
array = (AV *) SvRV( extra_fds );
extra_nfds = 1 + av_len( array );
Newxz( wait_for, extra_nfds, struct curl_waitfd );
for ( i = 0; i < extra_nfds; i++ )
{
HV *hash;
SV **tmp, **sv;
sv = av_fetch( array, i, 0 );
if ( !SvOK( *sv ) )
continue;
if ( !SvROK( *sv ) || SvTYPE( SvRV( *sv ) ) != SVt_PVHV )
croak( "must be a hashref" );
hash = (HV *) SvRV( *sv );
tmp = hv_fetchs( hash, "fd", 0 );
if ( tmp && *tmp && SvOK( *tmp ) )
wait_for[i].fd = SvIV( *tmp );
tmp = hv_fetchs( hash, "events", 0 );
if ( tmp && *tmp && SvOK( *tmp ) )
wait_for[i].events = SvIV( *tmp );
/* there is also revents which will be returned by curl */
tmp = hv_fetchs( hash, "revents", 0 );
if ( tmp && *tmp && SvOK( *tmp ) )
wait_for[i].revents = SvIV( *tmp );
}
}
ret = curl_multi_wait( multi->handle, wait_for, extra_nfds, timeout,
&remaining );
if ( wait_for )
{
int i;
AV *array = (AV *) SvRV( extra_fds );
for ( i = 0; i < extra_nfds; i++ )
{
HV *hash;
SV **sv;
short revents = wait_for[i].revents;
sv = av_fetch( array, i, 0 );
hash = (HV *) SvRV( *sv );
(void) hv_stores( hash, "revents", newSViv( revents ) );
}
Safefree( wait_for );
}
/* rethrow errors */
if ( SvTRUE( ERRSV ) )
croak( NULL );
MULTI_DIE( ret );
RETVAL = remaining;
OUTPUT:
RETVAL
#endif
int
socket_action( multi, sockfd=CURL_SOCKET_BAD, ev_bitmask=0 )
Net::Curl::Multi multi
int sockfd
int ev_bitmask
PREINIT:
int remaining;
CURLMcode ret;
CODE:
CLEAR_ERRSV();
do {
#ifdef CURL_CSELECT_IN
ret = curl_multi_socket_action( multi->handle,
(curl_socket_t) sockfd, ev_bitmask, &remaining );
#else
ret = curl_multi_socket( multi->handle,
(curl_socket_t) sockfd, &remaining );
#endif
} while ( ret == CURLM_CALL_MULTI_PERFORM );
/* rethrow errors */
if ( SvTRUE( ERRSV ) )
croak( NULL );
MULTI_DIE( ret );
RETVAL = remaining;
OUTPUT:
RETVAL
#if LIBCURL_VERSION_NUM >= 0x070f05
void
assign( multi, sockfd, value=NULL )
Net::Curl::Multi multi
unsigned long sockfd
SV *value
PREINIT:
CURLMcode ret;
void *sockptr;
CODE:
if ( value && SvOK( value ) ) {
SV **valueptr;
valueptr = perl_curl_simplell_add( aTHX_ &multi->socket_data,
sockfd );
if ( !valueptr )
croak( "internal Net::Curl error" );
if ( *valueptr )
sv_2mortal( *valueptr );
sockptr = *valueptr = newSVsv( value );
} else {
SV *oldvalue;
oldvalue = perl_curl_simplell_del( aTHX_ &multi->socket_data, sockfd );
if ( oldvalue )
sv_2mortal( oldvalue );
sockptr = NULL;
}
ret = curl_multi_assign( multi->handle, sockfd, sockptr );
MULTI_DIE( ret );
#endif
SV *
strerror( ... )
PROTOTYPE: $;$
PREINIT:
const char *errstr;
CODE:
if ( items < 1 || items > 2 )
croak( "Usage: Net::Curl::Multi::strerror( [multi], errnum )" );
errstr = curl_multi_strerror( SvIV( ST( items - 1 ) ) );
RETVAL = newSVpv( errstr, 0 );
OUTPUT:
RETVAL
# /* Extensions: Functions that do not have libcurl equivalents. */
void
handles( multi )
Net::Curl::Multi multi
PREINIT:
simplell_t *now;
PPCODE:
if ( GIMME_V == G_VOID )
XSRETURN( 0 );
now = multi->easies;
if ( GIMME_V == G_SCALAR ) {
IV i = 0;
while ( now ) {
i++;
now = now->next;
}
ST(0) = newSViv( i );
XSRETURN( 1 );
}
while ( now ) {
XPUSHs( newSVsv( now->value ) );
now = now->next;
}
int
CLONE_SKIP( pkg )
SV *pkg
CODE:
(void ) pkg;
RETVAL = 1;
OUTPUT:
RETVAL