add higher-level dox, fix some bugs, made case-insensitive
This was SVN commit r973.
This commit is contained in:
parent
e67ed4acc0
commit
c5701c8299
@ -16,6 +16,22 @@
|
||||
// Jan.Wassenberg@stud.uni-karlsruhe.de
|
||||
// http://www.stud.uni-karlsruhe.de/~urkt/
|
||||
|
||||
// components:
|
||||
// - z_*: Zip-specific code
|
||||
// passes the list of files in an archive to lookup.
|
||||
// - lookup_*: file lookup
|
||||
// per archive: return file info (e.g. offset, size), given filename.
|
||||
// - ZArchive_*: Handle-based container for archive info
|
||||
// owns archive file and its lookup mechanism.
|
||||
// - inf_*: in-memory inflate routines (zlib wrapper)
|
||||
// decompresses blocks from file_io callback.
|
||||
// - zip_*: file from Zip archive
|
||||
// uses lookup to get file information; holds inflate state.
|
||||
// - sync and async I/O
|
||||
// uses file_* and inf_*.
|
||||
// - file mapping
|
||||
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "lib.h"
|
||||
@ -89,8 +105,8 @@ static inline int z_validate(const void* const file, const size_t size)
|
||||
if(size < 22)
|
||||
return -1;
|
||||
|
||||
// check "header" (first CDFH) signature
|
||||
return (*(u32*)file == *(u32*)&cdfh_id)? 0 : -1;
|
||||
// check "header" (first LFH) signature
|
||||
return (*(u32*)file == *(u32*)&lfh_id)? 0 : -1;
|
||||
}
|
||||
|
||||
|
||||
@ -199,7 +215,7 @@ static int z_read_cdfh(const u8*& cdfh, const char*& fn, size_t& fn_len, ZLoc* c
|
||||
debug_warn("warning: unknown compression method");
|
||||
goto skip_file;
|
||||
}
|
||||
// tell is_compressed that the file is uncompressed,
|
||||
// tell zfile_compressed that the file is uncompressed,
|
||||
// by setting csize_ to 0.
|
||||
if(method == 0)
|
||||
csize_ = 0;
|
||||
@ -301,7 +317,7 @@ static int z_enum_files(const void* const file, const size_t size, const CDFH_CB
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// lookup_*: file lookup
|
||||
// per archive: retrieve file information (e.g. offset, size), given filename.
|
||||
// per archive: return file info (e.g. offset, size), given filename.
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@ -359,6 +375,22 @@ struct LookupInfo
|
||||
};
|
||||
|
||||
|
||||
// support for case-insensitive filenames: the FNV hash of each
|
||||
// filename string is saved in lookup_add_file_cb and searched for by
|
||||
// lookup_get_file_info. in both cases, we convert a temporary to
|
||||
// lowercase before hashing it.
|
||||
static void strcpy_lower(char* dst, const char* src)
|
||||
{
|
||||
int c;
|
||||
do
|
||||
{
|
||||
c = *src++;
|
||||
*dst++ = tolower(c);
|
||||
}
|
||||
while(c != '\0');
|
||||
}
|
||||
|
||||
|
||||
// add file <fn> to the lookup data structure.
|
||||
// called from z_enum_files in order (0 <= idx < num_files).
|
||||
// the first call notifies us of # files, so we can allocate memory.
|
||||
@ -392,7 +424,9 @@ static int lookup_add_file_cb(const uintptr_t user, const i32 idx, const char* c
|
||||
|
||||
ZEnt* ent = li->ents + idx;
|
||||
|
||||
FnHash fn_hash = fnv_hash(fn, fn_len);
|
||||
char lc_fn[PATH_MAX];
|
||||
strcpy_lower(lc_fn, fn);
|
||||
FnHash fn_hash = fnv_hash(lc_fn, fn_len);
|
||||
|
||||
(*li->idx)[fn_hash] = idx;
|
||||
li->fn_hashes[idx] = fn_hash;
|
||||
@ -400,6 +434,7 @@ static int lookup_add_file_cb(const uintptr_t user, const i32 idx, const char* c
|
||||
// valid file - write out its info.
|
||||
if(loc)
|
||||
{
|
||||
// copy filename, so we can 0-terminate it
|
||||
ent->fn = (const char*)malloc(fn_len+1);
|
||||
if(!ent->fn)
|
||||
return ERR_NO_MEM;
|
||||
@ -473,9 +508,11 @@ static int lookup_free(LookupInfo* const li)
|
||||
// return file information of file <fn>.
|
||||
static int lookup_get_file_info(LookupInfo* const li, const char* fn, ZLoc* const loc)
|
||||
{
|
||||
const FnHash fn_hash = fnv_hash(fn);
|
||||
const FnHash* fn_hashes = li->fn_hashes;
|
||||
char lc_fn[PATH_MAX];
|
||||
strcpy_lower(lc_fn, fn);
|
||||
const FnHash fn_hash = fnv_hash(lc_fn);
|
||||
|
||||
const FnHash* fn_hashes = li->fn_hashes;
|
||||
const i32 num_files = li->num_files;
|
||||
i32 i = li->next_file;
|
||||
|
||||
@ -501,19 +538,17 @@ static int lookup_get_file_info(LookupInfo* const li, const char* fn, ZLoc* cons
|
||||
}
|
||||
|
||||
|
||||
typedef ZipFileCB LookupFileCB;
|
||||
|
||||
static int lookup_enum_files(LookupInfo* const li, LookupFileCB cb, uintptr_t user)
|
||||
static int lookup_enum_files(LookupInfo* const li, FileCB cb, uintptr_t user)
|
||||
{
|
||||
const ZEnt* ent = li->ents;
|
||||
for(i32 i = 0; i < li->num_files; i++, ent++)
|
||||
{
|
||||
// is this entry a directory?
|
||||
int flags = 0;
|
||||
if(ent->loc.csize == 0 && ent->loc.ucsize == 0)
|
||||
flags |= LOC_DIR;
|
||||
ssize_t size = (ssize_t)ent->loc.ucsize;
|
||||
if(size == 0) // it's a directory
|
||||
size = -1;
|
||||
|
||||
CHECK_ERR(cb(ent->fn, flags, (ssize_t)ent->loc.ucsize, user));
|
||||
CHECK_ERR(cb(ent->fn, size, user));
|
||||
// pass in complete path (see file_enum rationale).
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -522,7 +557,7 @@ static int lookup_enum_files(LookupInfo* const li, LookupFileCB cb, uintptr_t us
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// container with handle for archive info
|
||||
// ZArchive_*: Handle-based container for archive info
|
||||
// owns archive file and its lookup mechanism.
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@ -534,9 +569,17 @@ struct ZArchive
|
||||
|
||||
LookupInfo li;
|
||||
|
||||
// hack: on first open, file is invalid (fn_hash isn't set),
|
||||
// and file validate in file_close fails.
|
||||
// workaround: only close if open.
|
||||
// problem:
|
||||
// if ZArchive_reload aborts due to file_open failing, ZArchive_dtor
|
||||
// is called by h_alloc, and file_close complains the File is
|
||||
// invalid (wasn't open). this happens if e.g. vfs_mount blindly
|
||||
// tries to open a directory as an archive.
|
||||
// workaround:
|
||||
// only free the above if ZArchive_reload succeeds, i.e. is_open.
|
||||
// note:
|
||||
// if lookup_init fails after file_open opened the file,
|
||||
// we wouldn't file_close in the dtor,
|
||||
// but it's taken care of by ZArchive_reload.
|
||||
bool is_open;
|
||||
};
|
||||
|
||||
@ -569,7 +612,9 @@ static int ZArchive_reload(ZArchive* za, const char* fn, Handle h)
|
||||
int err;
|
||||
|
||||
err = file_open(fn, 0, &za->f);
|
||||
if(err < 0) // don't CHECK_ERR; this happens often.
|
||||
if(err < 0)
|
||||
// don't complain - this happens when vfs_mount blindly
|
||||
// zip_archive_opens a dir.
|
||||
return err;
|
||||
|
||||
// map
|
||||
@ -594,26 +639,29 @@ exit_unmap_close:
|
||||
file_unmap(&za->f);
|
||||
exit_close:
|
||||
file_close(&za->f);
|
||||
|
||||
// don't complain here either; this happens when vfs_mount
|
||||
// zip_archive_opens an invalid file that's in a mount point dir.
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
// open and return a handle to the zip archive indicated by <fn>
|
||||
Handle zip_archive_open(const char* const fn)
|
||||
inline Handle zip_archive_open(const char* const fn)
|
||||
{
|
||||
return h_alloc(H_ZArchive, fn);
|
||||
}
|
||||
|
||||
|
||||
// close the archive <ha> and set ha to 0
|
||||
int zip_archive_close(Handle& ha)
|
||||
inline int zip_archive_close(Handle& ha)
|
||||
{
|
||||
return h_free(ha, H_ZArchive);
|
||||
}
|
||||
|
||||
|
||||
// see lookup rationale for not passing along a key.
|
||||
int zip_enum(const Handle ha, const ZipFileCB cb, const uintptr_t user)
|
||||
// call <cb>, passing <user>, for all files in archive <ha>
|
||||
int zip_enum(const Handle ha, const FileCB cb, const uintptr_t user)
|
||||
{
|
||||
H_DEREF(ha, ZArchive, za);
|
||||
|
||||
@ -623,7 +671,8 @@ int zip_enum(const Handle ha, const ZipFileCB cb, const uintptr_t user)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// in-memory inflate routines (zlib wrapper)
|
||||
// inf_*: in-memory inflate routines (zlib wrapper)
|
||||
// decompresses blocks from file_io callback.
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@ -749,8 +798,8 @@ int inf_free_ctx(uintptr_t ctx)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// file from Zip archive
|
||||
// on top of inflate and lookup
|
||||
// zip_*: file from Zip archive
|
||||
// uses lookup to get file information; holds inflate state.
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@ -758,7 +807,8 @@ int inf_free_ctx(uintptr_t ctx)
|
||||
enum ZFileFlags
|
||||
{
|
||||
// the ZFile has been successfully zip_map-ped.
|
||||
// we store this so that the archive mapping refcount remains balanced.
|
||||
// used to make sure the archive's mmap refcount remains balanced,
|
||||
// i.e. no one double-frees the mapping.
|
||||
ZF_HAS_MAPPING = 0x4000
|
||||
};
|
||||
|
||||
@ -788,7 +838,7 @@ static int zfile_validate(uint line, ZFile* zf)
|
||||
#endif
|
||||
else if(!zf->ucsize)
|
||||
msg = "ucsize = 0";
|
||||
else if(!zf->read_ctx)
|
||||
else if(!zf->inf_ctx)
|
||||
msg = "read context invalid";
|
||||
// everything is OK
|
||||
else
|
||||
@ -811,6 +861,35 @@ do\
|
||||
while(0);
|
||||
|
||||
|
||||
// convenience function, allows implementation change in ZFile.
|
||||
// note that size == ucsize isn't foolproof, and adding a flag to
|
||||
// ofs or size is ugly and error-prone.
|
||||
// no error checking - always called from functions that check zf.
|
||||
static inline bool zfile_compressed(ZFile* zf)
|
||||
{
|
||||
return zf->csize != 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// return file information for <fn> in archive <ha>
|
||||
int zip_stat(Handle ha, const char* fn, struct stat* s)
|
||||
{
|
||||
// zero output param in case we fail below.
|
||||
memset(s, 0, sizeof(struct stat));
|
||||
|
||||
H_DEREF(ha, ZArchive, za);
|
||||
LookupInfo* li = &za->li;
|
||||
|
||||
ZLoc loc;
|
||||
CHECK_ERR(lookup_get_file_info(li, fn, &loc));
|
||||
|
||||
s->st_size = loc.ucsize;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int zip_open(const Handle ha, const char* fn, ZFile* zf)
|
||||
{
|
||||
H_DEREF(ha, ZArchive, za);
|
||||
@ -842,7 +921,7 @@ int zip_open(const Handle ha, const char* fn, ZFile* zf)
|
||||
zf->csize = loc.csize;
|
||||
|
||||
zf->ha = ha;
|
||||
zf->read_ctx = inf_init_ctx();
|
||||
zf->inf_ctx = inf_init_ctx();
|
||||
}
|
||||
|
||||
invalid_zf:
|
||||
@ -857,41 +936,14 @@ int zip_close(ZFile* zf)
|
||||
CHECK_ZFILE(zf);
|
||||
|
||||
// remaining ZFile fields don't need to be freed/cleared
|
||||
return inf_free_ctx(zf->read_ctx);
|
||||
}
|
||||
|
||||
|
||||
// return file information for <fn> in archive <ha>
|
||||
int zip_stat(Handle ha, const char* fn, struct stat* s)
|
||||
{
|
||||
// zero output param in case we fail below.
|
||||
memset(s, 0, sizeof(struct stat));
|
||||
|
||||
H_DEREF(ha, ZArchive, za);
|
||||
LookupInfo* li = &za->li;
|
||||
|
||||
ZLoc loc;
|
||||
CHECK_ERR(lookup_get_file_info(li, fn, &loc));
|
||||
|
||||
s->st_size = loc.ucsize;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// convenience function, allows implementation change in ZFile.
|
||||
// note that size == ucsize isn't foolproof, and adding a flag to
|
||||
// ofs or size is ugly and error-prone.
|
||||
// no error checking - always called from functions that check zf.
|
||||
static inline bool is_compressed(ZFile* zf)
|
||||
{
|
||||
return zf->csize != 0;
|
||||
return inf_free_ctx(zf->inf_ctx);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// synchronous I/O
|
||||
// sync and async I/O
|
||||
// uses file_* and inf_*.
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@ -913,7 +965,7 @@ ssize_t zip_read(ZFile* zf, off_t raw_ofs, size_t size, void** p)
|
||||
|
||||
// not compressed - just pass it on to file_io
|
||||
// (avoid the Zip inflate start/finish stuff below)
|
||||
if(!is_compressed(zf))
|
||||
if(!zfile_compressed(zf))
|
||||
return file_io(&za->f, ofs, size, p);
|
||||
// no need to set last_raw_ofs - only checked if compressed.
|
||||
|
||||
@ -947,7 +999,7 @@ ssize_t zip_read(ZFile* zf, off_t raw_ofs, size_t size, void** p)
|
||||
*p = buf;
|
||||
}
|
||||
|
||||
err = (ssize_t)inf_start_read(zf->read_ctx, buf, size);
|
||||
err = (ssize_t)inf_start_read(zf->inf_ctx, buf, size);
|
||||
if(err < 0)
|
||||
{
|
||||
fail:
|
||||
@ -964,11 +1016,11 @@ fail:
|
||||
// zip_inflate, until all compressed data has been read, or it indicates
|
||||
// the desired output amount has been reached.
|
||||
const size_t raw_size = zf->csize;
|
||||
raw_bytes_read = file_io(&za->f, ofs, raw_size, (void**)0, inf_inflate, zf->read_ctx);
|
||||
raw_bytes_read = file_io(&za->f, ofs, raw_size, (void**)0, inf_inflate, zf->inf_ctx);
|
||||
|
||||
zf->last_raw_ofs = raw_ofs + (off_t)raw_bytes_read;
|
||||
|
||||
err = inf_finish_read(zf->read_ctx);
|
||||
err = inf_finish_read(zf->inf_ctx);
|
||||
if(err < 0)
|
||||
goto fail;
|
||||
|
||||
@ -982,6 +1034,47 @@ fail:
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
// rationale for not supporting aio for compressed files:
|
||||
// would complicate things considerably (could no longer just
|
||||
// return the file I/O handle, since we have to decompress in wait_io),
|
||||
// yet it isn't really useful - aio is used to stream music,
|
||||
// which is already compressed.
|
||||
|
||||
|
||||
// begin transferring <size> bytes, starting at <ofs>. get result
|
||||
// with zip_wait_io; when no longer needed, free via zip_discard_io.
|
||||
Handle zip_start_io(ZFile* const zf, off_t ofs, size_t size, void* buf)
|
||||
{
|
||||
CHECK_ZFILE(zf);
|
||||
if(zfile_compressed(zf))
|
||||
{
|
||||
debug_warn("Zip aio doesn't currently support compressed files (see rationale above)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
H_DEREF(zf->ha, ZArchive, za);
|
||||
return file_start_io(&za->f, zf->ofs+ofs, size, buf);
|
||||
}
|
||||
|
||||
|
||||
// wait until the transfer <hio> completes, and return its buffer.
|
||||
// output parameters are zeroed on error.
|
||||
inline int zip_wait_io(Handle hio, void*& p, size_t& size)
|
||||
{
|
||||
return file_wait_io(hio, p, size);
|
||||
}
|
||||
|
||||
|
||||
// finished with transfer <hio> - free its buffer (returned by vfs_wait_io)
|
||||
inline int zip_discard_io(Handle& hio)
|
||||
{
|
||||
return file_discard_io(hio);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// file mapping
|
||||
@ -1005,14 +1098,22 @@ int zip_map(ZFile* const zf, void*& p, size_t& size)
|
||||
|
||||
// mapping compressed files doesn't make sense because the
|
||||
// compression algorithm is unspecified - disallow it.
|
||||
if(is_compressed(zf))
|
||||
if(zfile_compressed(zf))
|
||||
{
|
||||
debug_warn("zip_map: file is compressed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
H_DEREF(zf->ha, ZArchive, za)
|
||||
CHECK_ERR(file_map(&za->f, p, size));
|
||||
// note: we mapped the archive in zip_archive_open, but unmapped it
|
||||
// in the meantime to save memory in case it wasn't going to be mapped.
|
||||
// now we do so again; it's unmapped in zip_unmap (refcounted).
|
||||
H_DEREF(zf->ha, ZArchive, za);
|
||||
void* archive_p;
|
||||
size_t archive_size;
|
||||
CHECK_ERR(file_map(&za->f, archive_p, archive_size));
|
||||
|
||||
p = (char*)archive_p + zf->ofs;
|
||||
size = zf->ucsize;
|
||||
|
||||
zf->flags |= ZF_HAS_MAPPING;
|
||||
return 0;
|
||||
@ -1034,51 +1135,6 @@ int zip_unmap(ZFile* const zf)
|
||||
return -1;
|
||||
zf->flags &= ~ZF_HAS_MAPPING;
|
||||
|
||||
H_DEREF(zf->ha, ZArchive, za)
|
||||
H_DEREF(zf->ha, ZArchive, za);
|
||||
return file_unmap(&za->f);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// asynchronous I/O
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
// rationale for not supporting aio for compressed files:
|
||||
// would complicate things considerably (could no longer just
|
||||
// return the file I/O handle, since we have to decompress in wait_io),
|
||||
// yet it isn't really useful - aio is used to stream music,
|
||||
// which is already compressed.
|
||||
|
||||
|
||||
// begin transferring <size> bytes, starting at <ofs>. get result
|
||||
// with zip_wait_io; when no longer needed, free via zip_discard_io.
|
||||
Handle zip_start_io(ZFile* const zf, off_t ofs, size_t size, void* buf)
|
||||
{
|
||||
CHECK_ZFILE(zf);
|
||||
if(is_compressed(zf))
|
||||
{
|
||||
debug_warn("Zip aio doesn't currently support compressed files (see rationale above)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
H_DEREF(zf->ha, ZArchive, za);
|
||||
return file_start_io(&za->f, zf->ofs+ofs, size, buf);
|
||||
}
|
||||
|
||||
|
||||
// wait until the transfer <hio> completes, and return its buffer.
|
||||
// output parameters are zeroed on error.
|
||||
inline int zip_wait_io(Handle hio, void*& p, size_t& size)
|
||||
{
|
||||
return file_wait_io(hio, p, size);
|
||||
}
|
||||
|
||||
|
||||
// finished with transfer <hio> - free its buffer (returned by vfs_wait_io)
|
||||
inline int zip_discard_io(Handle& hio)
|
||||
{
|
||||
return file_discard_io(hio);
|
||||
}
|
||||
|
@ -20,23 +20,10 @@
|
||||
#define ZIP_H__
|
||||
|
||||
#include "h_mgr.h"
|
||||
#include "lib.h"
|
||||
#include "file.h"
|
||||
#include "file.h" // FileCB for zip_enum
|
||||
|
||||
|
||||
//
|
||||
// in-memory inflate routines (zlib wrapper)
|
||||
//
|
||||
|
||||
extern uintptr_t inf_init_ctx();
|
||||
|
||||
extern int inf_start_read(uintptr_t ctx, void* out, size_t out_size);
|
||||
|
||||
extern ssize_t inf_inflate(uintptr_t ctx, void* in, size_t in_size);
|
||||
|
||||
extern int inf_finish_read(uintptr_t ctx);
|
||||
|
||||
extern int inf_free_ctx(uintptr_t ctx);
|
||||
// note: filenames are case-insensitive.
|
||||
|
||||
|
||||
//
|
||||
@ -49,10 +36,10 @@ extern Handle zip_archive_open(const char* fn);
|
||||
// close the archive <ha> and set ha to 0
|
||||
extern int zip_archive_close(Handle& ha);
|
||||
|
||||
// for all files in archive <ha>: call <cb>, passing <user>
|
||||
// and the entries's complete path.
|
||||
extern int zip_enum(const Handle ha, const FileCB cb, const uintptr_t user);
|
||||
|
||||
// all files in archive!
|
||||
typedef int(*ZipFileCB)(const char* const fn, const uint flags, const ssize_t size, const uintptr_t user);
|
||||
extern int zip_enum(const Handle ha, const ZipFileCB cb, const uintptr_t user);
|
||||
|
||||
//
|
||||
// file
|
||||
@ -64,7 +51,7 @@ struct ZFile
|
||||
u32 magic;
|
||||
#endif
|
||||
|
||||
// keep offset of flags and size members in sync with struct ZFile!
|
||||
// keep offset of flags and size members in sync with struct File!
|
||||
// it is accessed by VFS and must be the same for both (union).
|
||||
// dirty, but necessary because VFile is pushing the HDATA size limit.
|
||||
uint flags;
|
||||
@ -76,10 +63,10 @@ struct ZFile
|
||||
off_t last_raw_ofs;
|
||||
|
||||
Handle ha;
|
||||
uintptr_t read_ctx;
|
||||
uintptr_t inf_ctx;
|
||||
};
|
||||
|
||||
// return file information for <fn> (may include path) in archive <ha>
|
||||
// return file information for <fn> in archive <ha>
|
||||
extern int zip_stat(Handle ha, const char* fn, struct stat* s);
|
||||
|
||||
// open the file <fn> in archive <ha>, and fill *zf with information about it.
|
||||
@ -125,7 +112,6 @@ extern Handle zip_start_io(ZFile* const zf, off_t ofs, size_t size, void* buf);
|
||||
// output parameters are zeroed on error.
|
||||
extern int zip_wait_io(Handle hio, void*& p, size_t& size);
|
||||
|
||||
|
||||
// finished with transfer <hio> - free its buffer (returned by vfs_wait_io)
|
||||
extern int zip_discard_io(Handle& hio);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user