You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

777 lines
20 KiB

/*****************************************************************************
* archive.c: libarchive based stream filter
*****************************************************************************
* Copyright (C) 2016 VLC authors and VideoLAN
*
* Authors: Filip Roséen <filip@atch.se>
*
* This program 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.
*
* 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <vlc_common.h>
#include <vlc_plugin.h>
#include <vlc_stream.h>
#include <vlc_stream_extractor.h>
#include <vlc_dialog.h>
#include <vlc_input_item.h>
#include <assert.h>
#include <archive.h>
#include <archive_entry.h>
#if ARCHIVE_VERSION_NUMBER < 3002000
typedef __LA_INT64_T la_int64_t;
typedef __LA_SSIZE_T la_ssize_t;
#endif
static int ExtractorOpen( vlc_object_t* );
static void ExtractorClose( vlc_object_t* );
static int DirectoryOpen( vlc_object_t* );
static void DirectoryClose( vlc_object_t* );
vlc_module_begin()
set_subcategory( SUBCAT_INPUT_STREAM_FILTER )
set_capability( "stream_directory", 99 )
set_description( N_( "libarchive based stream directory" ) )
set_callbacks( DirectoryOpen, DirectoryClose )
add_submodule()
set_description( N_( "libarchive based stream extractor" ) )
set_capability( "stream_extractor", 99 )
set_callbacks( ExtractorOpen, ExtractorClose )
vlc_module_end()
typedef struct libarchive_callback_t libarchive_callback_t;
typedef struct private_sys_t private_sys_t;
typedef struct archive libarchive_t;
struct private_sys_t
{
libarchive_t* p_archive;
vlc_object_t* p_obj;
stream_t* source;
struct archive_entry* p_entry;
bool b_dead;
bool b_eof;
uint64_t i_offset;
uint8_t buffer[ 8192 ];
bool b_seekable_source;
bool b_seekable_archive;
libarchive_callback_t** pp_callback_data;
size_t i_callback_data;
};
struct libarchive_callback_t {
private_sys_t* p_sys;
stream_t* p_source;
char* psz_url;
};
/* ------------------------------------------------------------------------- */
static int libarchive_exit_cb( libarchive_t* p_arc, void* p_obj )
{
VLC_UNUSED( p_arc );
libarchive_callback_t* p_cb = (libarchive_callback_t*)p_obj;
if( p_cb->p_sys->source == p_cb->p_source )
{ /* DO NOT CLOSE OUR MOTHER STREAM */
if( !p_cb->p_sys->b_dead && vlc_stream_Seek( p_cb->p_source, 0 ) )
return ARCHIVE_FATAL;
}
else if( p_cb->p_source )
{
vlc_stream_Delete( p_cb->p_source );
p_cb->p_source = NULL;
}
return ARCHIVE_OK;
}
static int libarchive_jump_cb( libarchive_t* p_arc, void* p_obj_current,
void* p_obj_next )
{
libarchive_callback_t* p_current = (libarchive_callback_t*)p_obj_current;
libarchive_callback_t* p_next = (libarchive_callback_t*)p_obj_next;
if( libarchive_exit_cb( p_arc, p_current ) )
return ARCHIVE_FATAL;
if( p_next->p_source == NULL )
p_next->p_source = vlc_stream_NewURL( p_next->p_sys->p_obj,
p_next->psz_url );
return p_next->p_source ? ARCHIVE_OK : ARCHIVE_FATAL;
}
static la_int64_t libarchive_skip_cb( libarchive_t* p_arc, void* p_obj,
la_int64_t i_request )
{
VLC_UNUSED( p_arc );
libarchive_callback_t* p_cb = (libarchive_callback_t*)p_obj;
stream_t* p_source = p_cb->p_source;
private_sys_t* p_sys = p_cb->p_sys;
/* TODO: fix b_seekable_source on libarchive_callback_t */
if( p_sys->b_seekable_source )
{
if( vlc_stream_Seek( p_source, vlc_stream_Tell( p_source ) + i_request ) )
return ARCHIVE_FATAL;
return i_request;
}
ssize_t i_read = vlc_stream_Read( p_source, NULL, i_request );
return i_read >= 0 ? i_read : ARCHIVE_FATAL;
}
static la_int64_t libarchive_seek_cb( libarchive_t* p_arc, void* p_obj,
la_int64_t offset, int whence )
{
VLC_UNUSED( p_arc );
libarchive_callback_t* p_cb = (libarchive_callback_t*)p_obj;
stream_t* p_source = p_cb->p_source;
ssize_t whence_pos;
switch( whence )
{
case SEEK_SET: whence_pos = 0; break;
case SEEK_CUR: whence_pos = vlc_stream_Tell( p_source ); break;
case SEEK_END: whence_pos = stream_Size( p_source ); break;
default: vlc_assert_unreachable();
}
if( whence_pos < 0 || vlc_stream_Seek( p_source, whence_pos + offset ) )
return ARCHIVE_FATAL;
return vlc_stream_Tell( p_source );
}
static la_ssize_t libarchive_read_cb( libarchive_t* p_arc, void* p_obj,
const void** pp_dst )
{
VLC_UNUSED( p_arc );
libarchive_callback_t* p_cb = (libarchive_callback_t*)p_obj;
stream_t* p_source = p_cb->p_source;
private_sys_t* p_sys = p_cb->p_sys;
ssize_t i_ret = vlc_stream_Read( p_source, &p_sys->buffer,
sizeof( p_sys->buffer ) );
if( i_ret < 0 )
{
archive_set_error( p_sys->p_archive, ARCHIVE_FATAL,
"libarchive_read_cb failed = %zd", i_ret );
return ARCHIVE_FATAL;
}
*pp_dst = &p_sys->buffer;
return i_ret;
}
/* ------------------------------------------------------------------------- */
static int archive_push_resource( private_sys_t* p_sys,
stream_t* p_source, char const* psz_url )
{
libarchive_callback_t** pp_callback_data;
libarchive_callback_t* p_callback_data;
/* INCREASE BUFFER SIZE */
pp_callback_data = realloc( p_sys->pp_callback_data,
sizeof( *p_sys->pp_callback_data ) * ( p_sys->i_callback_data + 1 ) );
if( unlikely( !pp_callback_data ) )
goto error;
/* CREATE NEW NODE */
p_callback_data = malloc( sizeof( *p_callback_data ) );
if( unlikely( !p_callback_data ) )
goto error;
/* INITIALIZE AND APPEND */
p_callback_data->psz_url = psz_url ? strdup( psz_url ) : NULL;
p_callback_data->p_source = p_source;
p_callback_data->p_sys = p_sys;
if( unlikely( !p_callback_data->psz_url && psz_url ) )
{
free( p_callback_data );
goto error;
}
pp_callback_data[ p_sys->i_callback_data++ ] = p_callback_data;
p_sys->pp_callback_data = pp_callback_data;
return VLC_SUCCESS;
error:
free( pp_callback_data );
return VLC_ENOMEM;
}
static int archive_init( private_sys_t* p_sys, stream_t* source )
{
/* CREATE ARCHIVE HANDLE */
p_sys->p_archive = archive_read_new();
if( unlikely( !p_sys->p_archive ) )
{
msg_Dbg( p_sys->p_obj, "unable to create libarchive handle" );
return VLC_EGENERIC;
}
/* SETUP SEEKING */
p_sys->b_seekable_archive = false;
if( vlc_stream_Control( source, STREAM_CAN_SEEK,
&p_sys->b_seekable_source ) )
{
msg_Warn( p_sys->p_obj, "unable to query whether source stream can seek" );
p_sys->b_seekable_source = false;
}
if( p_sys->b_seekable_source )
{
if( archive_read_set_seek_callback( p_sys->p_archive,
libarchive_seek_cb ) )
{
msg_Err( p_sys->p_obj, "archive_read_set_callback failed, aborting." );
return VLC_EGENERIC;
}
}
/* ENABLE ALL FORMATS/FILTERS */
archive_read_support_filter_all( p_sys->p_archive );
archive_read_support_format_all( p_sys->p_archive );
/* REGISTER CALLBACK DATA */
if( archive_read_set_switch_callback( p_sys->p_archive,
libarchive_jump_cb ) )
{
msg_Err( p_sys->p_obj, "archive_read_set_switch_callback failed, aborting." );
return VLC_EGENERIC;
}
for( size_t i = 0; i < p_sys->i_callback_data; ++i )
{
if( archive_read_append_callback_data( p_sys->p_archive,
p_sys->pp_callback_data[i] ) )
{
return VLC_EGENERIC;
}
}
/* OPEN THE ARCHIVE */
if( archive_read_open2( p_sys->p_archive, p_sys->pp_callback_data[0], NULL,
libarchive_read_cb, libarchive_skip_cb, libarchive_exit_cb ) )
{
msg_Dbg( p_sys->p_obj, "libarchive: %s",
archive_error_string( p_sys->p_archive ) );
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
static int archive_clean( private_sys_t* p_sys )
{
libarchive_t* p_arc = p_sys->p_archive;
if( p_sys->p_entry )
archive_entry_free( p_sys->p_entry );
if( p_arc )
archive_read_free( p_arc );
p_sys->p_entry = NULL;
p_sys->p_archive = NULL;
return VLC_SUCCESS;
}
static int archive_seek_subentry( private_sys_t* p_sys, char const* psz_subentry )
{
libarchive_t* p_arc = p_sys->p_archive;
struct archive_entry* entry;
int archive_status;
while( !( archive_status = archive_read_next_header( p_arc, &entry ) ) )
{
char const* entry_path = archive_entry_pathname( entry );
if( strcmp( entry_path, psz_subentry ) == 0 )
{
p_sys->p_entry = archive_entry_clone( entry );
if( unlikely( !p_sys->p_entry ) )
return VLC_ENOMEM;
break;
}
archive_read_data_skip( p_arc );
}
switch( archive_status )
{
case ARCHIVE_WARN:
msg_Warn( p_sys->p_obj,
"libarchive: %s", archive_error_string( p_arc ) );
/* fall through */
case ARCHIVE_EOF:
case ARCHIVE_FATAL:
case ARCHIVE_RETRY:
archive_set_error( p_arc, ARCHIVE_FATAL,
"archive does not contain >>> %s <<<", psz_subentry );
return VLC_EGENERIC;
}
/* check if seeking is supported */
if( p_sys->b_seekable_source )
{
if( archive_seek_data( p_sys->p_archive, 0, SEEK_CUR ) >= 0 )
p_sys->b_seekable_archive = true;
}
return VLC_SUCCESS;
}
static int archive_extractor_reset( stream_extractor_t* p_extractor )
{
private_sys_t* p_sys = p_extractor->p_sys;
if( vlc_stream_Seek( p_extractor->source, 0 )
|| archive_clean( p_sys )
|| archive_init( p_sys, p_extractor->source )
|| archive_seek_subentry( p_sys, p_extractor->identifier ) )
{
p_sys->b_dead = true;
return VLC_EGENERIC;
}
p_sys->i_offset = 0;
p_sys->b_eof = false;
p_sys->b_dead = false;
return VLC_SUCCESS;
}
/* ------------------------------------------------------------------------- */
static private_sys_t* setup( vlc_object_t* obj, stream_t* source )
{
private_sys_t* p_sys = calloc( 1, sizeof( *p_sys ) );
char* psz_files = var_InheritString( obj, "concat-list" );
if( unlikely( !p_sys ) )
goto error;
if( archive_push_resource( p_sys, source, NULL ) )
goto error;
if( psz_files )
{
for( char* state,
* path = strtok_r( psz_files, ",", &state );
path; path = strtok_r( NULL, ",", &state ) )
{
if( path == psz_files )
continue;
if( archive_push_resource( p_sys, NULL, path ) )
goto error;
}
free( psz_files );
}
p_sys->source = source;
p_sys->p_obj = obj;
return p_sys;
error:
free( psz_files );
free( p_sys );
return NULL;
}
static int probe( stream_t* source )
{
struct
{
uint16_t i_offset;
uint8_t i_length;
char const * p_bytes;
} const magicbytes[] = {
/* keep heaviest at top */
{ 257, 5, "ustar" }, //TAR
#if ARCHIVE_VERSION_NUMBER >= 3004000
{ 0, 8, "Rar!\x1A\x07\x01" }, //RAR5.0
#endif
{ 0, 7, "Rar!\x1A\x07" }, //RAR4.x
{ 0, 6, "7z\xBC\xAF\x27\x1C" }, //7z
{ 0, 4, "xar!" }, //XAR
{ 0, 4, "PK\x03\x04" }, //ZIP
{ 0, 4, "PK\x05\x06" }, //ZIP
{ 0, 4, "PK\x07\x08" }, //ZIP
{ 2, 3, "-lh" }, //LHA/LHZ
{ 0, 3, "\x1f\x8b\x08" }, // Gzip
{ 0, 3, "PAX" }, //PAX
{ 0, 6, "070707" }, //CPIO
{ 0, 6, "070701" }, //CPIO
{ 0, 6, "070702" }, //CPIO
{ 0, 4, "MSCH" }, //CAB
};
const uint8_t *p_peek;
int i_peek = vlc_stream_Peek( source, &p_peek,
magicbytes[0].i_offset + magicbytes[0].i_length);
for(unsigned i=0; i < ARRAY_SIZE( magicbytes ); i++)
{
if (i_peek < magicbytes[i].i_offset + magicbytes[i].i_length)
continue;
if ( !memcmp(p_peek + magicbytes[i].i_offset,
magicbytes[i].p_bytes, magicbytes[i].i_length) )
return VLC_SUCCESS;
}
return VLC_EGENERIC;
}
/* ------------------------------------------------------------------------- */
static int Control( stream_extractor_t* p_extractor, int i_query, va_list args )
{
private_sys_t* p_sys = p_extractor->p_sys;
switch( i_query )
{
case STREAM_CAN_FASTSEEK:
*va_arg( args, bool* ) = false;
break;
case STREAM_CAN_SEEK:
*va_arg( args, bool* ) = p_sys->b_seekable_source;
break;
case STREAM_GET_SIZE:
if( p_sys->p_entry == NULL )
return VLC_EGENERIC;
if( !archive_entry_size_is_set( p_sys->p_entry ) )
return VLC_EGENERIC;
*va_arg( args, uint64_t* ) = archive_entry_size( p_sys->p_entry );
break;
case STREAM_GET_MTIME:
if( p_sys->p_entry == NULL )
return VLC_EGENERIC;
if( !archive_entry_mtime_is_set( p_sys->p_entry ) )
return VLC_EGENERIC;
*va_arg( args, uint64_t* ) = archive_entry_mtime( p_sys->p_entry );
break;
default:
return vlc_stream_vaControl( p_extractor->source, i_query, args );
}
return VLC_SUCCESS;
}
static int ReadDir( stream_directory_t* p_directory, input_item_node_t* p_node )
{
private_sys_t* p_sys = p_directory->p_sys;
libarchive_t* p_arc = p_sys->p_archive;
struct vlc_readdir_helper rdh;
vlc_readdir_helper_init( &rdh, p_directory, p_node);
struct archive_entry* entry;
int archive_status;
while( !( archive_status = archive_read_next_header( p_arc, &entry ) ) )
{
if( archive_entry_filetype( entry ) == AE_IFDIR )
continue;
char const* path = archive_entry_pathname( entry );
if( unlikely( !path ) )
break;
char* mrl = vlc_stream_extractor_CreateMRL( p_directory, path );
if( unlikely( !mrl ) )
break;
input_item_t *p_item;
if( vlc_readdir_helper_additem( &rdh, mrl, path, NULL, ITEM_TYPE_FILE,
ITEM_LOCAL, &p_item ) )
{
free( mrl );
break;
}
if ( p_item )
{
time_t mtime = archive_entry_mtime( entry );
if( mtime >= 0 )
input_item_AddStat( p_item, "mtime", mtime );
int64_t size = archive_entry_size( entry );
if( size >= 0 )
input_item_AddStat( p_item, "size", size );
}
free( mrl );
if( archive_read_data_skip( p_arc ) )
break;
}
vlc_readdir_helper_finish( &rdh, archive_status == ARCHIVE_EOF );
return archive_status == ARCHIVE_EOF ? VLC_SUCCESS : VLC_EGENERIC;
}
static ssize_t Read( stream_extractor_t *p_extractor, void* p_data, size_t i_size )
{
char dummy_buffer[ 8192 ];
private_sys_t* p_sys = p_extractor->p_sys;
libarchive_t* p_arc = p_sys->p_archive;
ssize_t i_ret;
if( p_sys->b_dead || p_sys->p_entry == NULL )
return 0;
if( p_sys->b_eof )
return 0;
i_ret = archive_read_data( p_arc,
p_data ? p_data : dummy_buffer,
p_data ? i_size : __MIN( i_size, sizeof( dummy_buffer ) ) );
switch( i_ret )
{
case ARCHIVE_RETRY:
case ARCHIVE_FAILED:
msg_Dbg( p_extractor, "libarchive: %s", archive_error_string( p_arc ) );
goto eof;
case ARCHIVE_WARN:
msg_Warn( p_extractor, "libarchive: %s", archive_error_string( p_arc ) );
goto eof;
case ARCHIVE_FATAL:
msg_Err( p_extractor, "libarchive: %s", archive_error_string( p_arc ) );
goto fatal_error;
}
p_sys->i_offset += i_ret;
return i_ret;
fatal_error:
p_sys->b_dead = true;
eof:
p_sys->b_eof = true;
return 0;
}
static int archive_skip_decompressed( stream_extractor_t* p_extractor, uint64_t *pi_skip )
{
while( *pi_skip )
{
ssize_t i_read = Read( p_extractor, NULL, *pi_skip );
if( i_read < 1 )
return VLC_EGENERIC;
*pi_skip -= i_read;
}
return VLC_SUCCESS;
}
static int Seek( stream_extractor_t* p_extractor, uint64_t i_req )
{
private_sys_t* p_sys = p_extractor->p_sys;
if( !p_sys->p_entry || !p_sys->b_seekable_source )
return VLC_EGENERIC;
if( archive_entry_size_is_set( p_sys->p_entry ) &&
(uint64_t)archive_entry_size( p_sys->p_entry ) <= i_req )
{
p_sys->b_eof = true;
return VLC_SUCCESS;
}
p_sys->b_eof = false;
if( !p_sys->b_seekable_archive || p_sys->b_dead
|| archive_seek_data( p_sys->p_archive, i_req, SEEK_SET ) < 0 )
{
msg_Dbg( p_extractor,
"intrinsic seek failed: '%s' (falling back to dumb seek)",
archive_error_string( p_sys->p_archive ) );
uint64_t i_skip = i_req - p_sys->i_offset;
/* RECREATE LIBARCHIVE HANDLE IF WE ARE SEEKING BACKWARDS */
if( i_req < p_sys->i_offset )
{
if( archive_extractor_reset( p_extractor ) )
{
msg_Err( p_extractor, "unable to reset libarchive handle" );
return VLC_EGENERIC;
}
i_skip = i_req;
}
if( archive_skip_decompressed( p_extractor, &i_skip ) )
{
msg_Warn( p_extractor, "failed to skip to seek position %" PRIu64 "/%" PRId64,
i_req, archive_entry_size( p_sys->p_entry ) );
p_sys->i_offset += i_skip;
return VLC_EGENERIC;
}
}
p_sys->i_offset = i_req;
return VLC_SUCCESS;
}
static void CommonClose( private_sys_t* p_sys )
{
p_sys->b_dead = true;
archive_clean( p_sys );
for( size_t i = 0; i < p_sys->i_callback_data; ++i )
{
free( p_sys->pp_callback_data[i]->psz_url );
free( p_sys->pp_callback_data[i] );
}
free( p_sys->pp_callback_data );
free( p_sys );
}
static void DirectoryClose( vlc_object_t* p_obj )
{
stream_directory_t* p_directory = (void*)p_obj;
return CommonClose( p_directory->p_sys );
}
static void ExtractorClose( vlc_object_t* p_obj )
{
stream_extractor_t* p_extractor = (void*)p_obj;
return CommonClose( p_extractor->p_sys );
}
static private_sys_t* CommonOpen( vlc_object_t* p_obj, stream_t* source )
{
if( probe( source ) )
return NULL;
private_sys_t* p_sys = setup( p_obj, source );
if( p_sys == NULL )
return NULL;
if( archive_init( p_sys, source ) )
{
CommonClose( p_sys );
return NULL;
}
return p_sys;
}
static int DirectoryOpen( vlc_object_t* p_obj )
{
stream_directory_t* p_directory = (void*)p_obj;
private_sys_t* p_sys = CommonOpen( p_obj, p_directory->source );
if( p_sys == NULL )
return VLC_EGENERIC;
p_directory->p_sys = p_sys;
p_directory->pf_readdir = ReadDir;
return VLC_SUCCESS;
}
static int ExtractorOpen( vlc_object_t* p_obj )
{
stream_extractor_t* p_extractor = (void*)p_obj;
private_sys_t* p_sys = CommonOpen( p_obj, p_extractor->source );
if( p_sys == NULL )
return VLC_EGENERIC;
if( archive_seek_subentry( p_sys, p_extractor->identifier ) )
{
CommonClose( p_sys );
return VLC_EGENERIC;
}
p_extractor->p_sys = p_sys;
p_extractor->pf_read = Read;
p_extractor->pf_control = Control;
p_extractor->pf_seek = Seek;
return VLC_SUCCESS;
}