Skip to content

Commit

Permalink
Added extflow.txt.
Browse files Browse the repository at this point in the history
Added technote.txt.
Added rar_file_t::entries_idx. It's a hashtable that stores the entries in rar_file_t::entries indexed by name. _rar_raw_entries_to_files uses it, if available.
Added an extension global, a per-request cache that will be used when directory streams are implemented.
Eliminated dependency on SPL.
Substituted several legacy macro names for new ones.
Stream close operation operates differently when close_handle is FALSE (not that I actually know what the correct behaviour would be...)
Added rar stream stat operation.
Added wrapper, not with only an opener. The syntax is "rar://<urlencoded path to RAR archive>#<urlencoded entry name>". Context options should be under "rar" and are "open_password", "file_password" and "volume_callback"
extract() and the wrapper opener should support RAR files with header passwords different from file passwords (but WinRAR does not generate them, so not tested).
Avoid test 46 infinite loop on resource opening failure.

git-svn-id: http://svn.php.net/repository/pecl/rar/trunk@298704 c90b9560-bf6c-de11-be94-00142212c4b1
  • Loading branch information
cataphract committed Apr 28, 2010
1 parent f92f731 commit a88a995
Show file tree
Hide file tree
Showing 15 changed files with 1,352 additions and 50 deletions.
60 changes: 60 additions & 0 deletions extflow.txt
@@ -0,0 +1,60 @@
rar_open/RarArchive::open()
gives
RarArchive object
-
. stores 2 open data structs (are used to tell the lib e.g. which file to open and the lib in return stores some data in them)
- list_open_data has open mode RAR_OM_LIST_INCSPLIT and is used to list the contents of the archive
- extract_open_data has open mode RAR_OM_EXTRACT and is used by RarEntry::extract
. stores one opened archive handle, opened with the list_open_data struct. This handle remains
open until the archive is closed or the object is destroyed
. a RarArchive object is considered closed when the opened archive handle created here is set to NULL


rar_list()/RarArchive::getEntries()
gives
RarEntry objects
-
. CALL _rar_list_files, which fills the lazy cache rar->entries by using the opened archive handle to retrieve ALL the RarHeaderDataEx headers
. CALL _rar_raw_entries_to_files to turn the rar->entries RarHeaderDataEx headers into zvals
- in turn, _rar_raw_entries_to_files creates the zval and sets the property that holds the zval reference to the RarArchive object (see below)
- calculates the packed size by summing over all the headers that concern each file (a file may have more than one header in case there are volumes)
- then CALLs _rar_entry_to_zval with the last header for each file and the packed size so that it can fill the remaining properties
. each of the RarEntry objects store a zval referencing the RarArchive object. The RarArchive object is not destroyed until all its spawned RarEntry objects are destroyed (it can however be closed)


rar_entry_get()/RarArchive::getEntry()
gives
RarEntry object
-
. CALL _rar_list_files, if it's necessary to fill the lazy cache rar->entries
. CALL _rar_raw_entries_to_files, which traverses rar->entries until it finds the request filename, the header(s) are then converted into one zval
. again, the RarEntry object stores a reference to the RarArchive object


RarArchive traversal with an iterator
gives
RarEntry objects (one at a time)
-
. iterator creation CALLs_rar_list_files, if it's necessary to fill the lazy cache rar->entries
. iterator stores the index (with respect to the rar->entries array) of the last header in rar->entries that is to be read (starts with 0)
. iterator CALLs _rar_raw_entries_to_files, which here stops after reading each file and advances the index


RarEntry::extract()
extracts the file
-
. uses the extract_open_data that's stored in the parent RarArchive object
. makes a shallow copy of parent RarArchive's rar->cb_userdata, eventually modified with the given file password.
. passes them to _rar_find_file to open the file with RAR_OM_EXTRACT and skip to the desired entry
. extracts the file
. closes the rar handle


RarEntry::getStream()
obtains stream
-
. CALL php_stream_rar_open with the archive name (obtained from parent RarArchive object's extract_open_data->ArcName), the filename of the entry and a shallow copy of parent RarArchive's rar->cb_userdata, eventually modified with the given file password.
. in turn, php_stream_rar_open CALLs _rar_find_file with a brand new rar open data struct with RAR_OM_EXTRACT. _rar_find_file opens the RAR archive and skips to the desired entry
. the resulting stream has no connection to the original RarArchive object or to the RarEntry object
. the rar archive is not closed until the stream is destroyed or closed

54 changes: 54 additions & 0 deletions php_rar.h
Expand Up @@ -74,6 +74,8 @@ typedef struct rar {
zend_object_handle id;
int entry_count; //>= number of files
struct RARHeaderDataEx **entries;
//key: entry name, value: index in entries
HashTable *entries_idx; /* TODO: merge into entries */
struct RAROpenArchiveDataEx *list_open_data;
struct RAROpenArchiveDataEx *extract_open_data;
//archive handle opened with RAR_OM_LIST_INCSPLIT open mode
Expand All @@ -82,8 +84,52 @@ typedef struct rar {
rar_cb_user_data cb_userdata;
} rar_file_t;

/* Per-request cache or make last the duration of the PHP lifespan?
* - per-request advantages: we can re-use rar_open and store close RarArchive
* objects. We store either pointers to the objects directly and manipulate
* the refcount in the store or we store zvals. Either way, we must decrement
* the refcounts on request shutdown. Also, the memory usage is best kept
* in check because the memory is freed after each request.
* - per PHP lifespan advantages: more cache hits. We can also re-use rar_open,
* but then we have to copy rar->entries and rar->entries_idx into
* persistently allocated buffers since the RarArchive objects cannot be made
* persistent themselves.
*
* I'll go with per-request and store zval pointers together with modification
* time.
* I'll also go with a FIFO eviction policy because it's simpler to implement
* (just delete the first element of the HashTable).
*/
#ifdef ZTS
# define RAR_TSRMLS_TC , void ***
#else
# define RAR_TSRMLS_TC
#endif

typedef struct _rar_contents_cache {
int max_size;
HashTable *data; //persistent HashTable, will hold rar_cache_entry
/* args: cache key, cache key size, cached object) */
void (*put)(const char *, uint, zval * RAR_TSRMLS_TC);
zval *(*get)(const char *, uint RAR_TSRMLS_TC);
} rar_contents_cache;

/* Module globals, currently used for dir wrappers cache */
ZEND_BEGIN_MODULE_GLOBALS(rar)
rar_contents_cache contents_cache;
ZEND_END_MODULE_GLOBALS(rar)

ZEND_EXTERN_MODULE_GLOBALS(rar);

#ifdef ZTS
# define RAR_G(v) TSRMG(rar_globals_id, zend_rar_globals *, v)
#else
# define RAR_G(v) (rar_globals.v)
#endif

//PHP 5.2 compatibility
#if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION < 3
# define STREAM_ASSUME_REALPATH 0
# define ALLOC_PERMANENT_ZVAL(z) \
(z) = (zval*) malloc(sizeof(zval));
# define OPENBASEDIR_CHECKPATH(filename) \
Expand Down Expand Up @@ -130,6 +176,7 @@ const char * _rar_error_to_string(int errcode);
void minit_rarerror(TSRMLS_D);

/* rararch.c */
int _rar_index_entries(rar_file_t *rar_file TSRMLS_DC);
int _rar_get_file_resource(zval *zval_file, rar_file_t **rar_file TSRMLS_DC);
int _rar_get_file_resource_ex(zval *zval_file, rar_file_t **rar_file, int silent TSRMLS_DC);
void minit_rararch(TSRMLS_D);
Expand All @@ -152,6 +199,13 @@ php_stream *php_stream_rar_open(char *arc_name,
char *utf_file_name,
rar_cb_user_data *cb_udata_ptr, /* will be copied */
char *mode STREAMS_DC TSRMLS_DC);
php_stream *php_stream_rar_opener(php_stream_wrapper *wrapper,
char *filename,
char *mode,
int options,
char **opened_path,
php_stream_context *context STREAMS_DC TSRMLS_DC);
extern php_stream_wrapper php_stream_rar_wrapper;

#endif /* PHP_RAR_H */

Expand Down
134 changes: 118 additions & 16 deletions rar.c
Expand Up @@ -201,6 +201,17 @@ void _rar_destroy_userdata(rar_cb_user_data *udata) /* {{{ */

/* WARNING: It's the caller who must close the archive and manage the lifecycle
of cb_udata (must be valid while the archive is opened). */
/*
* This function opens a RAR file and looks for the file with the
* name utf_file_name.
* If the operation is sucessful, arc_handle is populated with the RAR file
* handle, found is set to TRUE if the file is found and FALSE if it is not
* found; additionaly, the optional header_data is populated with the first
* header that corresponds to the request file. If the file is not found and
* header_data is specified, its values are undefined.
* Note that even when the file is not found, the caller must still close
* the archive.
*/
int _rar_find_file(struct RAROpenArchiveDataEx *open_data, /* IN */
const char *const utf_file_name, /* IN */
rar_cb_user_data *cb_udata, /* IN, must be managed outside */
Expand Down Expand Up @@ -269,7 +280,11 @@ int _rar_find_file(struct RAROpenArchiveDataEx *open_data, /* IN */
}
/* }}} */

/* Only processes password callbacks */
/* An unRAR callback.
* Processes requests for passwords and missing volumes
* If there is (userland) volume find callback specified, try to use that
* callback to retrieve the name of the missing volume. Otherwise, or if
* the volume find callback returns null, cancel the operation. */
int CALLBACK _rar_unrar_callback(UINT msg, LPARAM UserData, LPARAM P1, LPARAM P2) /* {{{ */
{
TSRMLS_FETCH();
Expand Down Expand Up @@ -327,14 +342,19 @@ PHP_FUNCTION(rar_bogus_ctor) /* {{{ */
/* This exception should not be thrown. The point is to add this as
* a class constructor and make it private. This code would be able to
* run only if the constructor were made public */
zend_throw_exception(spl_ce_RuntimeException,
zend_throw_exception(NULL,
"An object of this type cannot be created with the new operator.",
0 TSRMLS_CC);
}
/* }}} */
/* }}} */

/* {{{ Functions with internal linkage */
/*
* Only relevant when sizeof(wchar_t) > 2 (so not windows).
* Removes the characters use value if > 0x10ffff; these are not
* valid UTF characters.
*/
static void _rar_fix_wide(wchar_t *str, size_t max_size) /* {{{ */
{
wchar_t *write,
Expand All @@ -352,7 +372,7 @@ static void _rar_fix_wide(wchar_t *str, size_t max_size) /* {{{ */
/* called from the RAR callback; calls a user callback in case a volume was
* not found
* This function sends messages instead of calling _rar_handle_ext_error
* because, in case we're using exception, we want to let an exception with
* because, in case we're using exceptions, we want to let an exception with
* error code ERAR_EOPEN to be thrown.
*/
static int _rar_unrar_volume_user_callback(char* dst_buffer,
Expand All @@ -371,7 +391,7 @@ static int _rar_unrar_volume_user_callback(char* dst_buffer,
fci->retval_ptr_ptr = &retval_ptr;
fci->params = &params;
fci->param_count = 1;

if (zend_call_function(fci, cache TSRMLS_CC) != SUCCESS ||
fci->retval_ptr_ptr == NULL ||
*fci->retval_ptr_ptr == NULL) {
Expand Down Expand Up @@ -428,9 +448,7 @@ static int _rar_unrar_volume_user_callback(char* dst_buffer,
/* }}} */

#ifdef COMPILE_DL_RAR
BEGIN_EXTERN_C()
ZEND_GET_MODULE(rar)
END_EXTERN_C()
#endif

/* {{{ arginfo */
Expand Down Expand Up @@ -472,13 +490,82 @@ static zend_function_entry rar_functions[] = {
};
/* }}} */

/* {{{ PHP_MINIT_FUNCTION
*/
PHP_MINIT_FUNCTION(rar)
/* {{{ Globals' related activities */
/* actually, this is a tentative definition, since there's no initializer,
* but it will in fact become a definition */
ZEND_DECLARE_MODULE_GLOBALS(rar);

static int _rar_array_apply_remove_first(void *pDest TSRMLS_DC)
{
return (ZEND_HASH_APPLY_STOP | ZEND_HASH_APPLY_REMOVE);
}

static void _rar_contents_cache_put(const char *key,
uint key_len,
zval *zv TSRMLS_DC)
{
rar_contents_cache *cc = &RAR_G(contents_cache);
int cur_size;

cur_size = zend_hash_num_elements(cc->data);
if (cur_size == cc->max_size) {
zend_hash_apply(cc->data, _rar_array_apply_remove_first TSRMLS_CC);
assert(zend_hash_num_elements(cc->data) == cur_size - 1);
}
zend_hash_update(cc->data, key, key_len, &zv, sizeof(zv), NULL);
}

static zval *_rar_contents_cache_get(const char *key,
uint key_len TSRMLS_DC)
{
rar_contents_cache *cc = &RAR_G(contents_cache);
zval **element;
zend_hash_find(cc->data, key, key_len, (void **) &element);

return *element;
}

/* ZEND_MODULE_GLOBALS_CTOR_D declares it receiving zend_rar_globals*,
* which is incompatible; once cast into ts_allocate_ctor by the macro,
* ZEND_INIT_MODULE_GLOBALS, it cannot (per the spec) be used. */
static void ZEND_MODULE_GLOBALS_CTOR_N(rar)(void *arg TSRMLS_DC) /* {{{ */
{
zend_rar_globals *rar_globals = arg;
rar_globals->contents_cache.max_size = 5; /* TODO make configurable */
rar_globals->contents_cache.put = _rar_contents_cache_put;
rar_globals->contents_cache.get = _rar_contents_cache_get;
rar_globals->contents_cache.data =
pemalloc(sizeof *rar_globals->contents_cache.data, 1);
zend_hash_init(rar_globals->contents_cache.data,
rar_globals->contents_cache.max_size, NULL,
ZVAL_PTR_DTOR, 1);
}
/* }}} */

static void ZEND_MODULE_GLOBALS_DTOR_N(rar)(void *arg TSRMLS_DC) /* {{{ */
{
zend_rar_globals *rar_globals = arg;
zend_hash_destroy(rar_globals->contents_cache.data);
pefree(rar_globals->contents_cache.data, 1);
}
/* }}} */
/* }}} */

/* {{{ ZEND_MODULE_STARTUP */
ZEND_MODULE_STARTUP_D(rar)
{
minit_rararch(TSRMLS_C);
minit_rarentry(TSRMLS_C);
minit_rarerror(TSRMLS_C);

/* This doesn't work, it tries to call the destructor after the
* module has been unloaded. This information is in the zend_module_entry
* instead; that information is correctly used before the module is
* unloaded */
/* ZEND_INIT_MODULE_GLOBALS(rar, ZEND_MODULE_GLOBALS_CTOR_N(rar),
ZEND_MODULE_GLOBALS_DTOR_N(rar)); */

php_register_url_stream_wrapper("rar", &php_stream_rar_wrapper TSRMLS_CC);

REGISTER_LONG_CONSTANT("RAR_HOST_MSDOS", HOST_MSDOS, CONST_CS | CONST_PERSISTENT);
REGISTER_LONG_CONSTANT("RAR_HOST_OS2", HOST_OS2, CONST_CS | CONST_PERSISTENT);
Expand All @@ -494,9 +581,18 @@ PHP_MINIT_FUNCTION(rar)
}
/* }}} */

/* {{{ PHP_MINFO_FUNCTION
*/
PHP_MINFO_FUNCTION(rar)
/* {{{ ZEND_MODULE_DEACTIVATE */
ZEND_MODULE_DEACTIVATE_D(rar)
{
/* clean cache on request shutdown */
zend_hash_clean(RAR_G(contents_cache).data);

return SUCCESS;
}
/* }}} */

/* {{{ ZEND_MODULE_INFO */
ZEND_MODULE_INFO_D(rar)
{
char version[256];

Expand Down Expand Up @@ -525,13 +621,19 @@ zend_module_entry rar_module_entry = {
STANDARD_MODULE_HEADER,
"rar",
rar_functions,
PHP_MINIT(rar),
NULL,
ZEND_MODULE_STARTUP_N(rar),
//ZEND_MODULE_SHUTDOWN_N(rar),
NULL,
//ZEND_MODULE_ACTIVATE_N(rar),
NULL,
PHP_MINFO(rar),
ZEND_MODULE_DEACTIVATE_N(rar),
ZEND_MODULE_INFO_N(rar),
PHP_RAR_VERSION,
STANDARD_MODULE_PROPERTIES
ZEND_MODULE_GLOBALS(rar),
ZEND_MODULE_GLOBALS_CTOR_N(rar),
ZEND_MODULE_GLOBALS_DTOR_N(rar),
NULL, //post_deactivate_func
STANDARD_MODULE_PROPERTIES_EX,
};
/* }}} */

Expand Down

0 comments on commit a88a995

Please sign in to comment.