/*
* unbuffered I / O
* Copyright ( c ) 2001 Fabrice Bellard
*
* This file is part of Libav .
*
* Libav is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2.1 of the License , or ( at your option ) any later version .
*
* Libav 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
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with Libav ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA
*/
# include "libavutil/avstring.h"
# include "libavutil/dict.h"
# include "libavutil/opt.h"
# include "libavutil/time.h"
# include "os_support.h"
# include "avformat.h"
# if CONFIG_NETWORK
# include "network.h"
# endif
# include "url.h"
/** @name Logging context. */
/*@{*/
static const char * urlcontext_to_name ( void * ptr )
{
URLContext * h = ( URLContext * ) ptr ;
if ( h - > prot )
return h - > prot - > name ;
else
return " NULL " ;
}
static void * urlcontext_child_next ( void * obj , void * prev )
{
URLContext * h = obj ;
if ( ! prev & & h - > priv_data & & h - > prot - > priv_data_class )
return h - > priv_data ;
return NULL ;
}
static const AVOption options [ ] = {
{ " rw_timeout " , " Timeout for IO operations (in microseconds) " , offsetof ( URLContext , rw_timeout ) , AV_OPT_TYPE_INT64 , { . i64 = 0 } , 0 , INT64_MAX , AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_DECODING_PARAM } ,
{ NULL }
} ;
const AVClass ffurl_context_class = {
. class_name = " URLContext " ,
. item_name = urlcontext_to_name ,
. option = options ,
. version = LIBAVUTIL_VERSION_INT ,
. child_next = urlcontext_child_next ,
. child_class_next = ff_urlcontext_child_class_next ,
} ;
/*@}*/
static int url_alloc_for_protocol ( URLContext * * puc , const URLProtocol * up ,
const char * filename , int flags ,
const AVIOInterruptCB * int_cb ,
const URLProtocol * * protocols )
{
URLContext * uc ;
int err ;
# if CONFIG_NETWORK
if ( up - > flags & URL_PROTOCOL_FLAG_NETWORK & & ! ff_network_init ( ) )
return AVERROR ( EIO ) ;
# endif
uc = av_mallocz ( sizeof ( URLContext ) + strlen ( filename ) + 1 ) ;
if ( ! uc ) {
err = AVERROR ( ENOMEM ) ;
goto fail ;
}
uc - > av_class = & ffurl_context_class ;
uc - > filename = ( char * ) & uc [ 1 ] ;
strcpy ( uc - > filename , filename ) ;
uc - > prot = up ;
uc - > flags = flags ;
uc - > is_streamed = 0 ; /* default = not streamed */
uc - > max_packet_size = 0 ; /* default: stream file */
uc - > protocols = protocols ;
if ( up - > priv_data_size ) {
uc - > priv_data = av_mallocz ( up - > priv_data_size ) ;
if ( ! uc - > priv_data ) {
err = AVERROR ( ENOMEM ) ;
goto fail ;
}
if ( up - > priv_data_class ) {
* ( const AVClass * * ) uc - > priv_data = up - > priv_data_class ;
av_opt_set_defaults ( uc - > priv_data ) ;
}
}
if ( int_cb )
uc - > interrupt_callback = * int_cb ;
* puc = uc ;
return 0 ;
fail :
* puc = NULL ;
if ( uc )
av_freep ( & uc - > priv_data ) ;
av_freep ( & uc ) ;
# if CONFIG_NETWORK
if ( up - > flags & URL_PROTOCOL_FLAG_NETWORK )
ff_network_close ( ) ;
# endif
return err ;
}
int ffurl_connect ( URLContext * uc , AVDictionary * * options )
{
int err =
uc - > prot - > url_open2 ? uc - > prot - > url_open2 ( uc ,
uc - > filename ,
uc - > flags ,
options ) :
uc - > prot - > url_open ( uc , uc - > filename , uc - > flags ) ;
if ( err )
return err ;
uc - > is_connected = 1 ;
/* We must be careful here as ffurl_seek() could be slow,
* for example for http */
if ( ( uc - > flags & AVIO_FLAG_WRITE ) | | ! strcmp ( uc - > prot - > name , " file " ) )
if ( ! uc - > is_streamed & & ffurl_seek ( uc , 0 , SEEK_SET ) < 0 )
uc - > is_streamed = 1 ;
return 0 ;
}
# define URL_SCHEME_CHARS \
" abcdefghijklmnopqrstuvwxyz " \
" ABCDEFGHIJKLMNOPQRSTUVWXYZ " \
" 0123456789+-. "
int ffurl_alloc ( URLContext * * puc , const char * filename , int flags ,
const AVIOInterruptCB * int_cb ,
const URLProtocol * * protocols )
{
char proto_str [ 128 ] , proto_nested [ 128 ] , * ptr ;
size_t proto_len = strspn ( filename , URL_SCHEME_CHARS ) ;
int i ;
if ( filename [ proto_len ] ! = ' : ' | | is_dos_path ( filename ) )
strcpy ( proto_str , " file " ) ;
else
av_strlcpy ( proto_str , filename ,
FFMIN ( proto_len + 1 , sizeof ( proto_str ) ) ) ;
av_strlcpy ( proto_nested , proto_str , sizeof ( proto_nested ) ) ;
if ( ( ptr = strchr ( proto_nested , ' + ' ) ) )
* ptr = ' \0 ' ;
for ( i = 0 ; protocols [ i ] ; i + + ) {
const URLProtocol * up = protocols [ i ] ;
if ( ! strcmp ( proto_str , up - > name ) )
return url_alloc_for_protocol ( puc , up , filename , flags , int_cb ,
protocols ) ;
if ( up - > flags & URL_PROTOCOL_FLAG_NESTED_SCHEME & &
! strcmp ( proto_nested , up - > name ) )
return url_alloc_for_protocol ( puc , up , filename , flags , int_cb ,
protocols ) ;
}
* puc = NULL ;
return AVERROR_PROTOCOL_NOT_FOUND ;
}
int ffurl_open ( URLContext * * puc , const char * filename , int flags ,
const AVIOInterruptCB * int_cb , AVDictionary * * options ,
const URLProtocol * * protocols ,
URLContext * parent )
{
int ret = ffurl_alloc ( puc , filename , flags , int_cb , protocols ) ;
if ( ret )
return ret ;
if ( parent )
av_opt_copy ( * puc , parent ) ;
if ( options & &
( ret = av_opt_set_dict ( * puc , options ) ) < 0 )
goto fail ;
if ( options & & ( * puc ) - > prot - > priv_data_class & &
( ret = av_opt_set_dict ( ( * puc ) - > priv_data , options ) ) < 0 )
goto fail ;
ret = ffurl_connect ( * puc , options ) ;
if ( ! ret )
return 0 ;
fail :
ffurl_close ( * puc ) ;
* puc = NULL ;
return ret ;
}
static inline int retry_transfer_wrapper ( URLContext * h , uint8_t * buf ,
int size , int size_min ,
int ( * transfer_func ) ( URLContext * h ,
uint8_t * buf ,
int size ) )
{
int ret , len ;
int fast_retries = 5 ;
int64_t wait_since = 0 ;
len = 0 ;
while ( len < size_min ) {
ret = transfer_func ( h , buf + len , size - len ) ;
if ( ret = = AVERROR ( EINTR ) )
continue ;
if ( h - > flags & AVIO_FLAG_NONBLOCK )
return ret ;
if ( ret = = AVERROR ( EAGAIN ) ) {
ret = 0 ;
if ( fast_retries ) {
fast_retries - - ;
} else {
if ( h - > rw_timeout ) {
if ( ! wait_since )
wait_since = av_gettime_relative ( ) ;
else if ( av_gettime_relative ( ) > wait_since + h - > rw_timeout )
return AVERROR ( EIO ) ;
}
av_usleep ( 1000 ) ;
}
} else if ( ret < 1 )
return ( ret < 0 & & ret ! = AVERROR_EOF ) ? ret : len ;
if ( ret ) {
fast_retries = FFMAX ( fast_retries , 2 ) ;
wait_since = 0 ;
}
len + = ret ;
if ( ff_check_interrupt ( & h - > interrupt_callback ) )
return AVERROR_EXIT ;
}
return len ;
}
int ffurl_read ( URLContext * h , unsigned char * buf , int size )
{
if ( ! ( h - > flags & AVIO_FLAG_READ ) )
return AVERROR ( EIO ) ;
return retry_transfer_wrapper ( h , buf , size , 1 , h - > prot - > url_read ) ;
}
int ffurl_read_complete ( URLContext * h , unsigned char * buf , int size )
{
if ( ! ( h - > flags & AVIO_FLAG_READ ) )
return AVERROR ( EIO ) ;
return retry_transfer_wrapper ( h , buf , size , size , h - > prot - > url_read ) ;
}
int ffurl_write ( URLContext * h , const unsigned char * buf , int size )
{
if ( ! ( h - > flags & AVIO_FLAG_WRITE ) )
return AVERROR ( EIO ) ;
/* avoid sending too big packets */
if ( h - > max_packet_size & & size > h - > max_packet_size )
return AVERROR ( EIO ) ;
return retry_transfer_wrapper ( h , buf , size , size ,
( int ( * ) ( struct URLContext * , uint8_t * , int ) )
h - > prot - > url_write ) ;
}
int64_t ffurl_seek ( URLContext * h , int64_t pos , int whence )
{
int64_t ret ;
if ( ! h - > prot - > url_seek )
return AVERROR ( ENOSYS ) ;
ret = h - > prot - > url_seek ( h , pos , whence & ~ AVSEEK_FORCE ) ;
return ret ;
}
int ffurl_close ( URLContext * h )
{
int ret = 0 ;
if ( ! h )
return 0 ; /* can happen when ffurl_open fails */
if ( h - > is_connected & & h - > prot - > url_close )
ret = h - > prot - > url_close ( h ) ;
# if CONFIG_NETWORK
if ( h - > prot - > flags & URL_PROTOCOL_FLAG_NETWORK )
ff_network_close ( ) ;
# endif
if ( h - > prot - > priv_data_size ) {
if ( h - > prot - > priv_data_class )
av_opt_free ( h - > priv_data ) ;
av_free ( h - > priv_data ) ;
}
av_free ( h ) ;
return ret ;
}
int avio_check ( const char * url , int flags )
{
const URLProtocol * * protocols ;
URLContext * h ;
int ret ;
protocols = ffurl_get_protocols ( NULL , NULL ) ;
if ( ! protocols )
return AVERROR ( ENOMEM ) ;
ret = ffurl_alloc ( & h , url , flags , NULL , protocols ) ;
if ( ret ) {
av_freep ( & protocols ) ;
return ret ;
}
if ( h - > prot - > url_check ) {
ret = h - > prot - > url_check ( h , flags ) ;
} else {
ret = ffurl_connect ( h , NULL ) ;
if ( ret > = 0 )
ret = flags ;
}
ffurl_close ( h ) ;
av_freep ( & protocols ) ;
return ret ;
}
int64_t ffurl_size ( URLContext * h )
{
int64_t pos , size ;
size = ffurl_seek ( h , 0 , AVSEEK_SIZE ) ;
if ( size < 0 ) {
pos = ffurl_seek ( h , 0 , SEEK_CUR ) ;
if ( ( size = ffurl_seek ( h , - 1 , SEEK_END ) ) < 0 )
return size ;
size + + ;
ffurl_seek ( h , pos , SEEK_SET ) ;
}
return size ;
}
int ffurl_get_file_handle ( URLContext * h )
{
if ( ! h - > prot - > url_get_file_handle )
return - 1 ;
return h - > prot - > url_get_file_handle ( h ) ;
}
int ffurl_get_multi_file_handle ( URLContext * h , int * * handles , int * numhandles )
{
if ( ! h - > prot - > url_get_multi_file_handle ) {
if ( ! h - > prot - > url_get_file_handle )
return AVERROR ( ENOSYS ) ;
* handles = av_malloc ( sizeof ( * * handles ) ) ;
if ( ! * handles )
return AVERROR ( ENOMEM ) ;
* numhandles = 1 ;
* handles [ 0 ] = h - > prot - > url_get_file_handle ( h ) ;
return 0 ;
}
return h - > prot - > url_get_multi_file_handle ( h , handles , numhandles ) ;
}
int ffurl_shutdown ( URLContext * h , int flags )
{
if ( ! h - > prot - > url_shutdown )
return AVERROR ( EINVAL ) ;
return h - > prot - > url_shutdown ( h , flags ) ;
}
int ff_check_interrupt ( AVIOInterruptCB * cb )
{
int ret ;
if ( cb & & cb - > callback & & ( ret = cb - > callback ( cb - > opaque ) ) )
return ret ;
return 0 ;
}