#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <zlib.h>
#include "windef.h"
#include "winbase.h"
#include "winerror.h"
#include "winternl.h"
#include "fci.h"
#include "cabinet.h"
#include "wine/list.h"
#include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(cabinet);
#ifdef WORDS_BIGENDIAN
#define fci_endian_ulong(x) RtlUlongByteSwap(x)
#define fci_endian_uword(x) RtlUshortByteSwap(x)
#else
#define fci_endian_ulong(x) (x)
#define fci_endian_uword(x) (x)
#endif
typedef struct {
cab_UBYTE signature[4];
cab_ULONG reserved1;
cab_ULONG cbCabinet;
cab_ULONG reserved2;
cab_ULONG coffFiles;
cab_ULONG reserved3;
cab_UBYTE versionMinor;
cab_UBYTE versionMajor;
cab_UWORD cFolders;
cab_UWORD cFiles;
cab_UWORD flags;
cab_UWORD setID;
cab_UWORD iCabinet;
} CFHEADER;
typedef struct {
cab_ULONG coffCabStart;
cab_UWORD cCFData;
cab_UWORD typeCompress;
} CFFOLDER;
typedef struct {
cab_ULONG cbFile;
cab_ULONG uoffFolderStart;
cab_UWORD iFolder;
cab_UWORD date;
cab_UWORD time;
cab_UWORD attribs;
} CFFILE;
typedef struct {
cab_ULONG csum;
cab_UWORD cbData;
cab_UWORD cbUncomp;
} CFDATA;
struct temp_file
{
INT_PTR handle;
char name[CB_MAX_FILENAME];
};
struct folder
{
struct list entry;
struct list files_list;
struct list blocks_list;
struct temp_file data;
cab_ULONG data_start;
cab_UWORD data_count;
TCOMP compression;
};
struct file
{
struct list entry;
cab_ULONG size;
cab_ULONG offset;
cab_UWORD folder;
cab_UWORD date;
cab_UWORD time;
cab_UWORD attribs;
char name[1];
};
struct data_block
{
struct list entry;
cab_UWORD compressed;
cab_UWORD uncompressed;
};
typedef struct FCI_Int
{
unsigned int magic;
PERF perf;
PFNFCIFILEPLACED fileplaced;
PFNFCIALLOC alloc;
PFNFCIFREE free;
PFNFCIOPEN open;
PFNFCIREAD read;
PFNFCIWRITE write;
PFNFCICLOSE close;
PFNFCISEEK seek;
PFNFCIDELETE delete;
PFNFCIGETTEMPFILE gettemp;
CCAB ccab;
PCCAB pccab;
BOOL fPrevCab;
BOOL fNextCab;
BOOL fSplitFolder;
cab_ULONG statusFolderCopied;
cab_ULONG statusFolderTotal;
BOOL fGetNextCabInVain;
void *pv;
char szPrevCab[CB_MAX_CABINET_NAME];
char szPrevDisk[CB_MAX_DISK_NAME];
unsigned char data_in[CAB_BLOCKMAX];
unsigned char data_out[2 * CAB_BLOCKMAX];
cab_UWORD cdata_in;
ULONG cCompressedBytesInFolder;
cab_UWORD cFolders;
cab_UWORD cFiles;
cab_ULONG cDataBlocks;
cab_ULONG cbFileRemainder;
struct temp_file data;
BOOL fNewPrevious;
cab_ULONG estimatedCabinetSize;
struct list folders_list;
struct list files_list;
struct list blocks_list;
cab_ULONG folders_size;
cab_ULONG files_size;
cab_ULONG placed_files_size;
cab_ULONG pending_data_size;
cab_ULONG folders_data_size;
TCOMP compression;
cab_UWORD (*compress)(struct FCI_Int *);
} FCI_Int;
#define FCI_INT_MAGIC 0xfcfcfc05
static void set_error( FCI_Int *fci, int oper, int err )
{
fci->perf->erfOper = oper;
fci->perf->erfType = err;
fci->perf->fError = TRUE;
if (err) SetLastError( err );
}
static FCI_Int *get_fci_ptr( HFCI hfci )
{
FCI_Int *fci= (FCI_Int *)hfci;
if (!fci || fci->magic != FCI_INT_MAGIC)
{
SetLastError( ERROR_INVALID_HANDLE );
return NULL;
}
return fci;
}
static cab_ULONG get_header_size( FCI_Int *fci )
{
cab_ULONG ret = sizeof(CFHEADER) + fci->ccab.cbReserveCFHeader;
if (fci->ccab.cbReserveCFHeader || fci->ccab.cbReserveCFFolder || fci->ccab.cbReserveCFData)
ret += 4;
if (fci->fPrevCab)
ret += strlen( fci->szPrevCab ) + 1 + strlen( fci->szPrevDisk ) + 1;
if (fci->fNextCab)
ret += strlen( fci->pccab->szCab ) + 1 + strlen( fci->pccab->szDisk ) + 1;
return ret;
}
static BOOL create_temp_file( FCI_Int *fci, struct temp_file *file )
{
int err;
if (!fci->gettemp( file->name, CB_MAX_FILENAME, fci->pv ))
{
set_error( fci, FCIERR_TEMP_FILE, ERROR_FUNCTION_FAILED );
return FALSE;
}
if ((file->handle = fci->open( file->name, _O_RDWR | _O_CREAT | _O_EXCL | _O_BINARY,
_S_IREAD | _S_IWRITE, &err, fci->pv )) == -1)
{
set_error( fci, FCIERR_TEMP_FILE, err );
return FALSE;
}
return TRUE;
}
static BOOL close_temp_file( FCI_Int *fci, struct temp_file *file )
{
int err;
if (file->handle == -1) return TRUE;
if (fci->close( file->handle, &err, fci->pv ) == -1)
{
set_error( fci, FCIERR_TEMP_FILE, err );
return FALSE;
}
file->handle = -1;
if (fci->delete( file->name, &err, fci->pv ) == -1)
{
set_error( fci, FCIERR_TEMP_FILE, err );
return FALSE;
}
return TRUE;
}
static struct file *add_file( FCI_Int *fci, const char *filename )
{
unsigned int size = FIELD_OFFSET( struct file, name[strlen(filename) + 1] );
struct file *file = fci->alloc( size );
if (!file)
{
set_error( fci, FCIERR_ALLOC_FAIL, ERROR_NOT_ENOUGH_MEMORY );
return NULL;
}
file->size = 0;
file->offset = fci->cDataBlocks * CAB_BLOCKMAX + fci->cdata_in;
file->folder = fci->cFolders;
file->date = 0;
file->time = 0;
file->attribs = 0;
strcpy( file->name, filename );
list_add_tail( &fci->files_list, &file->entry );
fci->files_size += sizeof(CFFILE) + strlen(filename) + 1;
return file;
}
static struct file *copy_file( FCI_Int *fci, const struct file *orig )
{
unsigned int size = FIELD_OFFSET( struct file, name[strlen(orig->name) + 1] );
struct file *file = fci->alloc( size );
if (!file)
{
set_error( fci, FCIERR_ALLOC_FAIL, ERROR_NOT_ENOUGH_MEMORY );
return NULL;
}
memcpy( file, orig, size );
return file;
}
static void free_file( FCI_Int *fci, struct file *file )
{
list_remove( &file->entry );
fci->free( file );
}
static BOOL add_data_block( FCI_Int *fci, PFNFCISTATUS status_callback )
{
int err;
struct data_block *block;
if (!fci->cdata_in) return TRUE;
if (fci->data.handle == -1 && !create_temp_file( fci, &fci->data )) return FALSE;
if (!(block = fci->alloc( sizeof(*block) )))
{
set_error( fci, FCIERR_ALLOC_FAIL, ERROR_NOT_ENOUGH_MEMORY );
return FALSE;
}
block->uncompressed = fci->cdata_in;
block->compressed = fci->compress( fci );
if (fci->write( fci->data.handle, fci->data_out,
block->compressed, &err, fci->pv ) != block->compressed)
{
set_error( fci, FCIERR_TEMP_FILE, err );
fci->free( block );
return FALSE;
}
fci->cdata_in = 0;
fci->pending_data_size += sizeof(CFDATA) + fci->ccab.cbReserveCFData + block->compressed;
fci->cCompressedBytesInFolder += block->compressed;
fci->cDataBlocks++;
list_add_tail( &fci->blocks_list, &block->entry );
if (status_callback( statusFile, block->compressed, block->uncompressed, fci->pv ) == -1)
{
set_error( fci, FCIERR_USER_ABORT, 0 );
return FALSE;
}
return TRUE;
}
static BOOL add_file_data( FCI_Int *fci, char *sourcefile, char *filename, BOOL execute,
PFNFCIGETOPENINFO get_open_info, PFNFCISTATUS status_callback )
{
int err, len;
INT_PTR handle;
struct file *file;
if (!(file = add_file( fci, filename ))) return FALSE;
handle = get_open_info( sourcefile, &file->date, &file->time, &file->attribs, &err, fci->pv );
if (handle == -1)
{
free_file( fci, file );
set_error( fci, FCIERR_OPEN_SRC, err );
return FALSE;
}
if (execute) file->attribs |= _A_EXEC;
for (;;)
{
len = fci->read( handle, fci->data_in + fci->cdata_in,
CAB_BLOCKMAX - fci->cdata_in, &err, fci->pv );
if (!len) break;
if (len == -1)
{
set_error( fci, FCIERR_READ_SRC, err );
return FALSE;
}
file->size += len;
fci->cdata_in += len;
if (fci->cdata_in == CAB_BLOCKMAX && !add_data_block( fci, status_callback )) return FALSE;
}
fci->close( handle, &err, fci->pv );
return TRUE;
}
static void free_data_block( FCI_Int *fci, struct data_block *block )
{
list_remove( &block->entry );
fci->free( block );
}
static struct folder *add_folder( FCI_Int *fci )
{
struct folder *folder = fci->alloc( sizeof(*folder) );
if (!folder)
{
set_error( fci, FCIERR_ALLOC_FAIL, ERROR_NOT_ENOUGH_MEMORY );
return NULL;
}
folder->data.handle = -1;
folder->data_start = fci->folders_data_size;
folder->data_count = 0;
folder->compression = fci->compression;
list_init( &folder->files_list );
list_init( &folder->blocks_list );
list_add_tail( &fci->folders_list, &folder->entry );
fci->folders_size += sizeof(CFFOLDER) + fci->ccab.cbReserveCFFolder;
fci->cFolders++;
return folder;
}
static void free_folder( FCI_Int *fci, struct folder *folder )
{
struct file *file, *file_next;
struct data_block *block, *block_next;
LIST_FOR_EACH_ENTRY_SAFE( file, file_next, &folder->files_list, struct file, entry )
free_file( fci, file );
LIST_FOR_EACH_ENTRY_SAFE( block, block_next, &folder->blocks_list, struct data_block, entry )
free_data_block( fci, block );
close_temp_file( fci, &folder->data );
list_remove( &folder->entry );
fci->free( folder );
}
static void reset_cabinet( FCI_Int *fci )
{
struct folder *folder, *folder_next;
LIST_FOR_EACH_ENTRY_SAFE( folder, folder_next, &fci->folders_list, struct folder, entry )
free_folder( fci, folder );
fci->cFolders = 0;
fci->cFiles = 0;
fci->folders_size = 0;
fci->placed_files_size = 0;
fci->folders_data_size = 0;
}
static cab_ULONG fci_get_checksum( const void *pv, UINT cb, cab_ULONG seed )
{
cab_ULONG csum;
cab_ULONG ul;
int cUlong;
const BYTE *pb;
csum = seed;
cUlong = cb / 4;
pb = pv;
while (cUlong-- > 0) {
ul = *pb++;
ul |= (((cab_ULONG)(*pb++)) << 8);
ul |= (((cab_ULONG)(*pb++)) << 16);
ul |= (((cab_ULONG)(*pb++)) << 24);
csum ^= ul;
}
ul = 0;
switch (cb % 4) {
case 3:
ul |= (((ULONG)(*pb++)) << 16);
case 2:
ul |= (((ULONG)(*pb++)) << 8);
case 1:
ul |= *pb;
default:
break;
}
csum ^= ul;
return csum;
}
static BOOL copy_data_blocks( FCI_Int *fci, INT_PTR handle, cab_ULONG start_pos,
struct temp_file *temp, PFNFCISTATUS status_callback )
{
struct data_block *block;
int err;
if (fci->seek( handle, start_pos, SEEK_SET, &err, fci->pv ) != start_pos)
{
set_error( fci, FCIERR_TEMP_FILE, err );
return FALSE;
}
if (!create_temp_file( fci, temp )) return FALSE;
LIST_FOR_EACH_ENTRY( block, &fci->blocks_list, struct data_block, entry )
{
if (fci->read( handle, fci->data_out, block->compressed,
&err, fci->pv ) != block->compressed)
{
close_temp_file( fci, temp );
set_error( fci, FCIERR_TEMP_FILE, err );
return FALSE;
}
if (fci->write( temp->handle, fci->data_out, block->compressed,
&err, fci->pv ) != block->compressed)
{
close_temp_file( fci, temp );
set_error( fci, FCIERR_TEMP_FILE, err );
return FALSE;
}
fci->pending_data_size += sizeof(CFDATA) + fci->ccab.cbReserveCFData + block->compressed;
fci->statusFolderCopied += block->compressed;
if (status_callback( statusFolder, fci->statusFolderCopied,
fci->statusFolderTotal, fci->pv) == -1)
{
close_temp_file( fci, temp );
set_error( fci, FCIERR_USER_ABORT, 0 );
return FALSE;
}
}
return TRUE;
}
static BOOL write_folders( FCI_Int *fci, INT_PTR handle, cab_ULONG header_size, PFNFCISTATUS status_callback )
{
struct folder *folder;
int err;
CFFOLDER *cffolder = (CFFOLDER *)fci->data_out;
cab_ULONG folder_size = sizeof(CFFOLDER) + fci->ccab.cbReserveCFFolder;
memset( cffolder, 0, folder_size );
LIST_FOR_EACH_ENTRY( folder, &fci->folders_list, struct folder, entry )
{
cffolder->coffCabStart = fci_endian_ulong( folder->data_start + header_size );
cffolder->cCFData = fci_endian_uword( folder->data_count );
cffolder->typeCompress = fci_endian_uword( folder->compression );
if (fci->write( handle, cffolder, folder_size, &err, fci->pv ) != folder_size)
{
set_error( fci, FCIERR_CAB_FILE, err );
return FALSE;
}
}
return TRUE;
}
static BOOL write_files( FCI_Int *fci, INT_PTR handle, PFNFCISTATUS status_callback )
{
cab_ULONG file_size;
struct folder *folder;
struct file *file;
int err;
CFFILE *cffile = (CFFILE *)fci->data_out;
LIST_FOR_EACH_ENTRY( folder, &fci->folders_list, struct folder, entry )
{
LIST_FOR_EACH_ENTRY( file, &folder->files_list, struct file, entry )
{
cffile->cbFile = fci_endian_ulong( file->size );
cffile->uoffFolderStart = fci_endian_ulong( file->offset );
cffile->iFolder = fci_endian_uword( file->folder );
cffile->date = fci_endian_uword( file->date );
cffile->time = fci_endian_uword( file->time );
cffile->attribs = fci_endian_uword( file->attribs );
lstrcpynA( (char *)(cffile + 1), file->name, CB_MAX_FILENAME );
file_size = sizeof(CFFILE) + strlen( (char *)(cffile + 1) ) + 1;
if (fci->write( handle, cffile, file_size, &err, fci->pv ) != file_size)
{
set_error( fci, FCIERR_CAB_FILE, err );
return FALSE;
}
if (!fci->fSplitFolder)
{
fci->statusFolderCopied = 0;
fci->statusFolderTotal = fci->folders_data_size + fci->placed_files_size;
}
fci->statusFolderCopied += file_size;
if (status_callback( statusFolder, fci->statusFolderCopied,
fci->statusFolderTotal, fci->pv ) == -1)
{
set_error( fci, FCIERR_USER_ABORT, 0 );
return FALSE;
}
}
}
return TRUE;
}
static BOOL write_data_blocks( FCI_Int *fci, INT_PTR handle, PFNFCISTATUS status_callback )
{
struct folder *folder;
struct data_block *block;
int err, len;
CFDATA *cfdata;
void *data;
cab_UWORD header_size;
header_size = sizeof(CFDATA) + fci->ccab.cbReserveCFData;
cfdata = (CFDATA *)fci->data_out;
memset( cfdata, 0, header_size );
data = (char *)cfdata + header_size;
LIST_FOR_EACH_ENTRY( folder, &fci->folders_list, struct folder, entry )
{
if (fci->seek( folder->data.handle, 0, SEEK_SET, &err, fci->pv ) != 0)
{
set_error( fci, FCIERR_CAB_FILE, err );
return FALSE;
}
LIST_FOR_EACH_ENTRY( block, &folder->blocks_list, struct data_block, entry )
{
len = fci->read( folder->data.handle, data, block->compressed, &err, fci->pv );
if (len != block->compressed) return FALSE;
cfdata->cbData = fci_endian_uword( block->compressed );
cfdata->cbUncomp = fci_endian_uword( block->uncompressed );
cfdata->csum = fci_endian_ulong( fci_get_checksum( &cfdata->cbData,
header_size - FIELD_OFFSET(CFDATA, cbData),
fci_get_checksum( data, len, 0 )));
fci->statusFolderCopied += len;
len += header_size;
if (fci->write( handle, fci->data_out, len, &err, fci->pv ) != len)
{
set_error( fci, FCIERR_CAB_FILE, err );
return FALSE;
}
if (status_callback( statusFolder, fci->statusFolderCopied, fci->statusFolderTotal, fci->pv) == -1)
{
set_error( fci, FCIERR_USER_ABORT, 0 );
return FALSE;
}
}
}
return TRUE;
}
static BOOL write_cabinet( FCI_Int *fci, PFNFCISTATUS status_callback )
{
char filename[CB_MAX_CAB_PATH + CB_MAX_CABINET_NAME];
int err;
char *ptr;
INT_PTR handle;
CFHEADER *cfheader = (CFHEADER *)fci->data_out;
cab_UWORD flags = 0;
cab_ULONG header_size = get_header_size( fci );
cab_ULONG total_size = header_size + fci->folders_size +
fci->placed_files_size + fci->folders_data_size;
assert( header_size <= sizeof(fci->data_out) );
memset( cfheader, 0, header_size );
if (fci->fPrevCab) flags |= cfheadPREV_CABINET;
if (fci->fNextCab) flags |= cfheadNEXT_CABINET;
if (fci->ccab.cbReserveCFHeader || fci->ccab.cbReserveCFFolder || fci->ccab.cbReserveCFData)
flags |= cfheadRESERVE_PRESENT;
memcpy( cfheader->signature, "!CAB", 4 );
cfheader->cbCabinet = fci_endian_ulong( total_size );
cfheader->coffFiles = fci_endian_ulong( header_size + fci->folders_size );
cfheader->versionMinor = 3;
cfheader->versionMajor = 1;
cfheader->cFolders = fci_endian_uword( fci->cFolders );
cfheader->cFiles = fci_endian_uword( fci->cFiles );
cfheader->flags = fci_endian_uword( flags );
cfheader->setID = fci_endian_uword( fci->ccab.setID );
cfheader->iCabinet = fci_endian_uword( fci->ccab.iCab );
ptr = (char *)(cfheader + 1);
if (flags & cfheadRESERVE_PRESENT)
{
struct
{
cab_UWORD cbCFHeader;
cab_UBYTE cbCFFolder;
cab_UBYTE cbCFData;
} *reserve = (void *)ptr;
reserve->cbCFHeader = fci_endian_uword( fci->ccab.cbReserveCFHeader );
reserve->cbCFFolder = fci->ccab.cbReserveCFFolder;
reserve->cbCFData = fci->ccab.cbReserveCFData;
ptr = (char *)(reserve + 1);
}
ptr += fci->ccab.cbReserveCFHeader;
if (flags & cfheadPREV_CABINET)
{
strcpy( ptr, fci->szPrevCab );
ptr += strlen( ptr ) + 1;
strcpy( ptr, fci->szPrevDisk );
ptr += strlen( ptr ) + 1;
}
if (flags & cfheadNEXT_CABINET)
{
strcpy( ptr, fci->pccab->szCab );
ptr += strlen( ptr ) + 1;
strcpy( ptr, fci->pccab->szDisk );
ptr += strlen( ptr ) + 1;
}
assert( ptr - (char *)cfheader == header_size );
strcpy( filename, fci->ccab.szCabPath );
strcat( filename, fci->ccab.szCab );
if ((handle = fci->open( filename, _O_RDWR | _O_CREAT | _O_TRUNC | _O_BINARY,
_S_IREAD | _S_IWRITE, &err, fci->pv )) == -1)
{
set_error( fci, FCIERR_CAB_FILE, err );
return FALSE;
}
if (fci->write( handle, cfheader, header_size, &err, fci->pv ) != header_size)
{
set_error( fci, FCIERR_CAB_FILE, err );
goto failed;
}
header_size += fci->placed_files_size + fci->folders_size;
if (!write_folders( fci, handle, header_size, status_callback )) goto failed;
if (!write_files( fci, handle, status_callback )) goto failed;
if (!write_data_blocks( fci, handle, status_callback )) goto failed;
if (fci->seek( handle, 0, SEEK_SET, &err, fci->pv ) != 0 )
{
set_error( fci, FCIERR_CAB_FILE, err );
goto failed;
}
memcpy( cfheader->signature, "MSCF", 4 );
if (fci->write( handle, cfheader->signature, 4, &err, fci->pv ) != 4)
{
set_error( fci, FCIERR_CAB_FILE, err );
goto failed;
}
fci->close( handle, &err, fci->pv );
reset_cabinet( fci );
status_callback( statusCabinet, fci->estimatedCabinetSize, total_size, fci->pv );
return TRUE;
failed:
fci->close( handle, &err, fci->pv );
fci->delete( filename, &err, fci->pv );
return FALSE;
}
static BOOL add_data_to_folder( FCI_Int *fci, struct folder *folder, cab_ULONG *payload,
PFNFCISTATUS status_callback )
{
struct data_block *block, *new, *next;
BOOL split_block = FALSE;
cab_ULONG current_size, start_pos = 0;
*payload = 0;
current_size = get_header_size( fci ) + fci->folders_size +
fci->files_size + fci->placed_files_size + fci->folders_data_size;
folder->data = fci->data;
fci->data.handle = -1;
fci->pending_data_size = 0;
LIST_FOR_EACH_ENTRY_SAFE( block, next, &fci->blocks_list, struct data_block, entry )
{
if (fci->fNextCab && (fci->ccab.cb <= sizeof(CFDATA) + fci->ccab.cbReserveCFData +
current_size + sizeof(CFFOLDER) + fci->ccab.cbReserveCFFolder))
break;
if (!(new = fci->alloc( sizeof(*new) )))
{
set_error( fci, FCIERR_ALLOC_FAIL, ERROR_NOT_ENOUGH_MEMORY );
return FALSE;
}
if( fci->fNextCab &&
(fci->ccab.cb < sizeof(CFDATA) + fci->ccab.cbReserveCFData +
block->compressed + current_size + sizeof(CFFOLDER) + fci->ccab.cbReserveCFFolder))
{
new->compressed = fci->ccab.cb - (sizeof(CFDATA) + fci->ccab.cbReserveCFData + current_size +
sizeof(CFFOLDER) + fci->ccab.cbReserveCFFolder );
new->uncompressed = 0;
block->compressed -= new->compressed;
split_block = TRUE;
}
else
{
new->compressed = block->compressed;
new->uncompressed = block->uncompressed;
}
start_pos += new->compressed;
current_size += sizeof(CFDATA) + fci->ccab.cbReserveCFData + new->compressed;
fci->folders_data_size += sizeof(CFDATA) + fci->ccab.cbReserveCFData + new->compressed;
fci->statusFolderCopied += new->compressed;
(*payload) += new->uncompressed;
list_add_tail( &folder->blocks_list, &new->entry );
folder->data_count++;
if (status_callback( statusFolder, fci->statusFolderCopied,
fci->statusFolderTotal, fci->pv ) == -1)
{
set_error( fci, FCIERR_USER_ABORT, 0 );
return FALSE;
}
if (split_block) break;
free_data_block( fci, block );
fci->cDataBlocks--;
}
if (list_empty( &fci->blocks_list )) return TRUE;
return copy_data_blocks( fci, folder->data.handle, start_pos, &fci->data, status_callback );
}
static BOOL add_files_to_folder( FCI_Int *fci, struct folder *folder, cab_ULONG payload )
{
cab_ULONG sizeOfFiles = 0, sizeOfFilesPrev;
cab_ULONG cbFileRemainder = 0;
struct file *file, *next;
LIST_FOR_EACH_ENTRY_SAFE( file, next, &fci->files_list, struct file, entry )
{
cab_ULONG size = sizeof(CFFILE) + strlen(file->name) + 1;
fci->fileplaced( &fci->ccab, file->name, file->size,
(file->folder == cffileCONTINUED_FROM_PREV), fci->pv );
sizeOfFilesPrev = sizeOfFiles;
if (file->folder == cffileCONTINUED_FROM_PREV && fci->cbFileRemainder != 0)
{
sizeOfFiles += fci->cbFileRemainder;
fci->cbFileRemainder = 0;
}
else sizeOfFiles += file->size;
if (sizeOfFiles > payload)
{
if (file->folder == cffileCONTINUED_FROM_PREV)
file->folder = cffileCONTINUED_PREV_AND_NEXT;
else
file->folder = cffileCONTINUED_TO_NEXT;
}
list_remove( &file->entry );
list_add_tail( &folder->files_list, &file->entry );
fci->placed_files_size += size;
fci->cFiles++;
if (sizeOfFiles > payload)
{
if (!(file = copy_file( fci, file ))) return FALSE;
list_add_before( &next->entry, &file->entry );
if (file->folder == cffileCONTINUED_PREV_AND_NEXT || file->folder == cffileCONTINUED_TO_NEXT)
{
if (sizeOfFilesPrev <= payload)
{
cbFileRemainder = sizeOfFiles - payload;
}
file->folder = cffileCONTINUED_FROM_PREV;
}
else file->folder = 0;
}
else
{
fci->files_size -= size;
}
}
fci->cbFileRemainder = cbFileRemainder;
return TRUE;
}
static cab_UWORD compress_NONE( FCI_Int *fci )
{
memcpy( fci->data_out, fci->data_in, fci->cdata_in );
return fci->cdata_in;
}
static void *zalloc( void *opaque, unsigned int items, unsigned int size )
{
FCI_Int *fci = opaque;
return fci->alloc( items * size );
}
static void zfree( void *opaque, void *ptr )
{
FCI_Int *fci = opaque;
fci->free( ptr );
}
static cab_UWORD compress_MSZIP( FCI_Int *fci )
{
z_stream stream;
stream.zalloc = zalloc;
stream.zfree = zfree;
stream.opaque = fci;
if (deflateInit2( &stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY ) != Z_OK)
{
set_error( fci, FCIERR_ALLOC_FAIL, ERROR_NOT_ENOUGH_MEMORY );
return 0;
}
stream.next_in = fci->data_in;
stream.avail_in = fci->cdata_in;
stream.next_out = fci->data_out + 2;
stream.avail_out = sizeof(fci->data_out) - 2;
fci->data_out[0] = 'C';
fci->data_out[1] = 'K';
deflate( &stream, Z_FINISH );
deflateEnd( &stream );
return stream.total_out + 2;
}
HFCI __cdecl FCICreate(
PERF perf,
PFNFCIFILEPLACED pfnfiledest,
PFNFCIALLOC pfnalloc,
PFNFCIFREE pfnfree,
PFNFCIOPEN pfnopen,
PFNFCIREAD pfnread,
PFNFCIWRITE pfnwrite,
PFNFCICLOSE pfnclose,
PFNFCISEEK pfnseek,
PFNFCIDELETE pfndelete,
PFNFCIGETTEMPFILE pfnfcigtf,
PCCAB pccab,
void *pv)
{
FCI_Int *p_fci_internal;
if (!perf) {
SetLastError(ERROR_BAD_ARGUMENTS);
return NULL;
}
if ((!pfnalloc) || (!pfnfree) || (!pfnopen) || (!pfnread) ||
(!pfnwrite) || (!pfnclose) || (!pfnseek) || (!pfndelete) ||
(!pfnfcigtf) || (!pccab)) {
perf->erfOper = FCIERR_NONE;
perf->erfType = ERROR_BAD_ARGUMENTS;
perf->fError = TRUE;
SetLastError(ERROR_BAD_ARGUMENTS);
return NULL;
}
if (!((p_fci_internal = pfnalloc(sizeof(FCI_Int))))) {
perf->erfOper = FCIERR_ALLOC_FAIL;
perf->erfType = ERROR_NOT_ENOUGH_MEMORY;
perf->fError = TRUE;
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
return NULL;
}
memset(p_fci_internal, 0, sizeof(FCI_Int));
p_fci_internal->magic = FCI_INT_MAGIC;
p_fci_internal->perf = perf;
p_fci_internal->fileplaced = pfnfiledest;
p_fci_internal->alloc = pfnalloc;
p_fci_internal->free = pfnfree;
p_fci_internal->open = pfnopen;
p_fci_internal->read = pfnread;
p_fci_internal->write = pfnwrite;
p_fci_internal->close = pfnclose;
p_fci_internal->seek = pfnseek;
p_fci_internal->delete = pfndelete;
p_fci_internal->gettemp = pfnfcigtf;
p_fci_internal->ccab = *pccab;
p_fci_internal->pccab = pccab;
p_fci_internal->pv = pv;
p_fci_internal->data.handle = -1;
p_fci_internal->compress = compress_NONE;
list_init( &p_fci_internal->folders_list );
list_init( &p_fci_internal->files_list );
list_init( &p_fci_internal->blocks_list );
memcpy(p_fci_internal->szPrevCab, pccab->szCab, CB_MAX_CABINET_NAME);
memcpy(p_fci_internal->szPrevDisk, pccab->szDisk, CB_MAX_DISK_NAME);
return (HFCI)p_fci_internal;
}
static BOOL fci_flush_folder( FCI_Int *p_fci_internal,
BOOL fGetNextCab,
PFNFCIGETNEXTCABINET pfnfcignc,
PFNFCISTATUS pfnfcis)
{
cab_ULONG payload;
cab_ULONG read_result;
struct folder *folder;
if ((!pfnfcignc) || (!pfnfcis)) {
set_error( p_fci_internal, FCIERR_NONE, ERROR_BAD_ARGUMENTS );
return FALSE;
}
if( p_fci_internal->fGetNextCabInVain &&
p_fci_internal->fNextCab ){
set_error( p_fci_internal, FCIERR_NONE, ERROR_GEN_FAILURE );
return FALSE;
}
if( p_fci_internal->files_size == 0 ) {
if ( p_fci_internal->pending_data_size != 0 ) {
set_error( p_fci_internal, FCIERR_NONE, ERROR_GEN_FAILURE );
return FALSE;
}
return TRUE;
}
if (p_fci_internal->fSplitFolder && p_fci_internal->placed_files_size!=0) {
return TRUE;
}
p_fci_internal->fSplitFolder=FALSE;
if (!add_data_block( p_fci_internal, pfnfcis )) return FALSE;
p_fci_internal->cDataBlocks=0;
p_fci_internal->statusFolderTotal = get_header_size( p_fci_internal ) +
sizeof(CFFOLDER) + p_fci_internal->ccab.cbReserveCFFolder +
p_fci_internal->placed_files_size+
p_fci_internal->folders_data_size + p_fci_internal->files_size+
p_fci_internal->pending_data_size + p_fci_internal->folders_size;
p_fci_internal->statusFolderCopied = 0;
if( (*pfnfcis)(statusFolder, p_fci_internal->statusFolderCopied,
p_fci_internal->statusFolderTotal,
p_fci_internal->pv) == -1) {
set_error( p_fci_internal, FCIERR_USER_ABORT, 0 );
return FALSE;
}
read_result = get_header_size( p_fci_internal ) + p_fci_internal->folders_data_size +
p_fci_internal->placed_files_size + p_fci_internal->folders_size;
if(p_fci_internal->files_size!=0) {
read_result+= sizeof(CFFOLDER)+p_fci_internal->ccab.cbReserveCFFolder;
}
if( p_fci_internal->fGetNextCabInVain==FALSE &&
p_fci_internal->fNextCab==FALSE &&
(
(
p_fci_internal->ccab.cb < read_result +
p_fci_internal->pending_data_size +
p_fci_internal->files_size +
CB_MAX_CABINET_NAME +
CB_MAX_DISK_NAME
) || fGetNextCab
)
) {
++(p_fci_internal->pccab->iCab);
p_fci_internal->estimatedCabinetSize=p_fci_internal->statusFolderTotal;
if (!(*pfnfcignc)(p_fci_internal->pccab,
p_fci_internal->estimatedCabinetSize,
p_fci_internal->pv)) {
set_error( p_fci_internal, FCIERR_NONE, ERROR_FUNCTION_FAILED );
return FALSE;
}
p_fci_internal->fGetNextCabInVain=TRUE;
}
if( (p_fci_internal->fGetNextCabInVain ||
p_fci_internal->fNextCab ) &&
(
(
p_fci_internal->ccab.cb < read_result +
p_fci_internal->pending_data_size +
p_fci_internal->files_size +
strlen(p_fci_internal->pccab->szCab)+1 +
strlen(p_fci_internal->pccab->szDisk)+1
) || fGetNextCab
)
) {
p_fci_internal->fGetNextCabInVain=FALSE;
p_fci_internal->fNextCab=TRUE;
if (p_fci_internal->ccab.cb <=
p_fci_internal->files_size +
read_result +
strlen(p_fci_internal->pccab->szCab)+1 +
strlen(p_fci_internal->pccab->szDisk)+1
) {
return FALSE;
}
p_fci_internal->fSplitFolder=TRUE;
} else {
if (p_fci_internal->fNextCab) {
set_error( p_fci_internal, FCIERR_NONE, ERROR_GEN_FAILURE );
return FALSE;
}
}
if (!(folder = add_folder( p_fci_internal ))) return FALSE;
if (!add_data_to_folder( p_fci_internal, folder, &payload, pfnfcis )) return FALSE;
if (!add_files_to_folder( p_fci_internal, folder, payload )) return FALSE;
p_fci_internal->cDataBlocks=0;
p_fci_internal->cCompressedBytesInFolder=0;
return TRUE;
}
static BOOL fci_flush_cabinet( FCI_Int *p_fci_internal,
BOOL fGetNextCab,
PFNFCIGETNEXTCABINET pfnfcignc,
PFNFCISTATUS pfnfcis)
{
cab_ULONG read_result=0;
BOOL returntrue=FALSE;
if( p_fci_internal->files_size==0 && fGetNextCab ) {
returntrue=TRUE;
}
if (!fci_flush_folder(p_fci_internal,fGetNextCab,pfnfcignc,pfnfcis)){
return FALSE;
}
if(returntrue) return TRUE;
if ( (p_fci_internal->fSplitFolder && p_fci_internal->fNextCab==FALSE)||
(p_fci_internal->folders_size==0 &&
(p_fci_internal->files_size!=0 ||
p_fci_internal->placed_files_size!=0 )
) )
{
set_error( p_fci_internal, FCIERR_NONE, ERROR_GEN_FAILURE );
return FALSE;
}
if (!write_cabinet( p_fci_internal, pfnfcis )) return FALSE;
p_fci_internal->fPrevCab=TRUE;
if (p_fci_internal->fNextCab) {
p_fci_internal->fNextCab=FALSE;
if (p_fci_internal->files_size==0 && p_fci_internal->pending_data_size!=0) {
set_error( p_fci_internal, FCIERR_NONE, ERROR_GEN_FAILURE );
return FALSE;
}
if( p_fci_internal->fNewPrevious ) {
memcpy(p_fci_internal->szPrevCab, p_fci_internal->ccab.szCab,
CB_MAX_CABINET_NAME);
memcpy(p_fci_internal->szPrevDisk, p_fci_internal->ccab.szDisk,
CB_MAX_DISK_NAME);
p_fci_internal->fNewPrevious=FALSE;
}
p_fci_internal->ccab = *p_fci_internal->pccab;
read_result=get_header_size( p_fci_internal );
if(p_fci_internal->files_size!=0) {
read_result+=p_fci_internal->ccab.cbReserveCFFolder;
}
read_result+= p_fci_internal->pending_data_size +
p_fci_internal->files_size + p_fci_internal->folders_data_size +
p_fci_internal->placed_files_size + p_fci_internal->folders_size +
sizeof(CFFOLDER);
if( p_fci_internal->fGetNextCabInVain==FALSE &&
p_fci_internal->ccab.cb < read_result ) {
return fci_flush_cabinet( p_fci_internal, FALSE, pfnfcignc, pfnfcis);
}
if (p_fci_internal->fGetNextCabInVain==FALSE && (
p_fci_internal->ccab.cb < read_result +
CB_MAX_CABINET_NAME + CB_MAX_DISK_NAME
)) {
++(p_fci_internal->pccab->iCab);
p_fci_internal->estimatedCabinetSize=p_fci_internal->statusFolderTotal;
if (!(*pfnfcignc)(p_fci_internal->pccab,
p_fci_internal->estimatedCabinetSize,
p_fci_internal->pv)) {
set_error( p_fci_internal, FCIERR_NONE, ERROR_FUNCTION_FAILED );
return FALSE;
}
p_fci_internal->fGetNextCabInVain=TRUE;
}
if (p_fci_internal->fGetNextCabInVain && (
p_fci_internal->ccab.cb < read_result +
strlen(p_fci_internal->ccab.szCab)+1+
strlen(p_fci_internal->ccab.szDisk)+1
)) {
p_fci_internal->fGetNextCabInVain=FALSE;
p_fci_internal->fNextCab=TRUE;
return fci_flush_cabinet( p_fci_internal, FALSE, pfnfcignc, pfnfcis);
}
if (p_fci_internal->cCompressedBytesInFolder >= p_fci_internal->ccab.cbFolderThresh)
return fci_flush_folder(p_fci_internal, FALSE, pfnfcignc, pfnfcis);
if( p_fci_internal->files_size>0 ) {
if( !fci_flush_folder(p_fci_internal, FALSE, pfnfcignc, pfnfcis) ) return FALSE;
p_fci_internal->fNewPrevious=TRUE;
}
} else {
p_fci_internal->fNewPrevious=FALSE;
if( p_fci_internal->files_size>0 || p_fci_internal->pending_data_size) {
set_error( p_fci_internal, FCIERR_NONE, ERROR_GEN_FAILURE );
return FALSE;
}
}
return TRUE;
}
BOOL __cdecl FCIAddFile(
HFCI hfci,
char *pszSourceFile,
char *pszFileName,
BOOL fExecute,
PFNFCIGETNEXTCABINET pfnfcignc,
PFNFCISTATUS pfnfcis,
PFNFCIGETOPENINFO pfnfcigoi,
TCOMP typeCompress)
{
cab_ULONG read_result;
FCI_Int *p_fci_internal = get_fci_ptr( hfci );
if (!p_fci_internal) return FALSE;
if ((!pszSourceFile) || (!pszFileName) || (!pfnfcignc) || (!pfnfcis) ||
(!pfnfcigoi) || strlen(pszFileName)>=CB_MAX_FILENAME) {
set_error( p_fci_internal, FCIERR_NONE, ERROR_BAD_ARGUMENTS );
return FALSE;
}
if (typeCompress != p_fci_internal->compression)
{
if (!FCIFlushFolder( hfci, pfnfcignc, pfnfcis )) return FALSE;
switch (typeCompress)
{
case tcompTYPE_MSZIP:
p_fci_internal->compression = tcompTYPE_MSZIP;
p_fci_internal->compress = compress_MSZIP;
break;
default:
FIXME( "compression %x not supported, defaulting to none\n", typeCompress );
case tcompTYPE_NONE:
p_fci_internal->compression = tcompTYPE_NONE;
p_fci_internal->compress = compress_NONE;
break;
}
}
if(p_fci_internal->fGetNextCabInVain && p_fci_internal->fNextCab) {
set_error( p_fci_internal, FCIERR_NONE, ERROR_GEN_FAILURE );
return FALSE;
}
if(p_fci_internal->fNextCab) {
set_error( p_fci_internal, FCIERR_NONE, ERROR_GEN_FAILURE );
return FALSE;
}
read_result=get_header_size( p_fci_internal ) + p_fci_internal->ccab.cbReserveCFFolder;
read_result+= sizeof(CFFILE) + strlen(pszFileName)+1 +
p_fci_internal->files_size + p_fci_internal->folders_data_size +
p_fci_internal->placed_files_size + p_fci_internal->folders_size +
sizeof(CFFOLDER);
if( p_fci_internal->fGetNextCabInVain==FALSE &&
p_fci_internal->fNextCab==FALSE &&
( p_fci_internal->ccab.cb < read_result +
CB_MAX_CABINET_NAME + CB_MAX_DISK_NAME
)
) {
++(p_fci_internal->pccab->iCab);
p_fci_internal->estimatedCabinetSize=p_fci_internal->statusFolderTotal;
if (!(*pfnfcignc)(p_fci_internal->pccab,
p_fci_internal->estimatedCabinetSize,
p_fci_internal->pv)) {
set_error( p_fci_internal, FCIERR_NONE, ERROR_FUNCTION_FAILED );
return FALSE;
}
p_fci_internal->fGetNextCabInVain=TRUE;
}
if( p_fci_internal->fGetNextCabInVain &&
p_fci_internal->fNextCab
) {
set_error( p_fci_internal, FCIERR_NONE, ERROR_GEN_FAILURE );
return FALSE;
}
if( p_fci_internal->fGetNextCabInVain &&
(
p_fci_internal->ccab.cb < read_result +
strlen(p_fci_internal->pccab->szCab)+1+
strlen(p_fci_internal->pccab->szDisk)+1
)) {
p_fci_internal->fGetNextCabInVain=FALSE;
p_fci_internal->fNextCab=TRUE;
if(!fci_flush_cabinet( p_fci_internal, FALSE, pfnfcignc, pfnfcis)) return FALSE;
}
if( p_fci_internal->fNextCab ) {
set_error( p_fci_internal, FCIERR_NONE, ERROR_GEN_FAILURE );
return FALSE;
}
if (!add_file_data( p_fci_internal, pszSourceFile, pszFileName, fExecute, pfnfcigoi, pfnfcis ))
return FALSE;
read_result = get_header_size( p_fci_internal ) + p_fci_internal->ccab.cbReserveCFFolder;
read_result+= p_fci_internal->pending_data_size +
p_fci_internal->files_size + p_fci_internal->folders_data_size +
p_fci_internal->placed_files_size + p_fci_internal->folders_size +
sizeof(CFFOLDER);
if( p_fci_internal->fGetNextCabInVain==FALSE &&
p_fci_internal->fNextCab==FALSE &&
p_fci_internal->ccab.cb < read_result ) {
return fci_flush_cabinet( p_fci_internal, FALSE, pfnfcignc, pfnfcis);
}
if( p_fci_internal->fGetNextCabInVain==FALSE &&
p_fci_internal->fNextCab==FALSE &&
( p_fci_internal->ccab.cb < read_result +
CB_MAX_CABINET_NAME + CB_MAX_DISK_NAME
)
) {
++(p_fci_internal->pccab->iCab);
p_fci_internal->estimatedCabinetSize=p_fci_internal->statusFolderTotal;
if (!(*pfnfcignc)(p_fci_internal->pccab,
p_fci_internal->estimatedCabinetSize,
p_fci_internal->pv)) {
set_error( p_fci_internal, FCIERR_NONE, ERROR_FUNCTION_FAILED );
return FALSE;
}
p_fci_internal->fGetNextCabInVain=TRUE;
}
if( p_fci_internal->fGetNextCabInVain &&
p_fci_internal->fNextCab
) {
set_error( p_fci_internal, FCIERR_NONE, ERROR_GEN_FAILURE );
return FALSE;
}
if( (p_fci_internal->fGetNextCabInVain ||
p_fci_internal->fNextCab) && (
p_fci_internal->ccab.cb < read_result +
strlen(p_fci_internal->pccab->szCab)+1+
strlen(p_fci_internal->pccab->szDisk)+1
)) {
p_fci_internal->fGetNextCabInVain=FALSE;
p_fci_internal->fNextCab=TRUE;
return fci_flush_cabinet( p_fci_internal, FALSE, pfnfcignc, pfnfcis);
}
if( p_fci_internal->fNextCab ) {
set_error( p_fci_internal, FCIERR_NONE, ERROR_GEN_FAILURE );
return FALSE;
}
if (p_fci_internal->cCompressedBytesInFolder >= p_fci_internal->ccab.cbFolderThresh)
return FCIFlushFolder(hfci, pfnfcignc, pfnfcis);
return TRUE;
}
BOOL __cdecl FCIFlushFolder(
HFCI hfci,
PFNFCIGETNEXTCABINET pfnfcignc,
PFNFCISTATUS pfnfcis)
{
FCI_Int *p_fci_internal = get_fci_ptr( hfci );
if (!p_fci_internal) return FALSE;
return fci_flush_folder(p_fci_internal,FALSE,pfnfcignc,pfnfcis);
}
BOOL __cdecl FCIFlushCabinet(
HFCI hfci,
BOOL fGetNextCab,
PFNFCIGETNEXTCABINET pfnfcignc,
PFNFCISTATUS pfnfcis)
{
FCI_Int *p_fci_internal = get_fci_ptr( hfci );
if (!p_fci_internal) return FALSE;
if(!fci_flush_cabinet(p_fci_internal,fGetNextCab,pfnfcignc,pfnfcis)) return FALSE;
while( p_fci_internal->files_size>0 ||
p_fci_internal->placed_files_size>0 ) {
if(!fci_flush_cabinet(p_fci_internal,fGetNextCab,pfnfcignc,pfnfcis)) return FALSE;
}
return TRUE;
}
BOOL __cdecl FCIDestroy(HFCI hfci)
{
struct folder *folder, *folder_next;
struct file *file, *file_next;
struct data_block *block, *block_next;
FCI_Int *p_fci_internal = get_fci_ptr( hfci );
if (!p_fci_internal) return FALSE;
p_fci_internal->magic = 0;
LIST_FOR_EACH_ENTRY_SAFE( folder, folder_next, &p_fci_internal->folders_list, struct folder, entry )
{
free_folder( p_fci_internal, folder );
}
LIST_FOR_EACH_ENTRY_SAFE( file, file_next, &p_fci_internal->files_list, struct file, entry )
{
free_file( p_fci_internal, file );
}
LIST_FOR_EACH_ENTRY_SAFE( block, block_next, &p_fci_internal->blocks_list, struct data_block, entry )
{
free_data_block( p_fci_internal, block );
}
close_temp_file( p_fci_internal, &p_fci_internal->data );
p_fci_internal->free(hfci);
return TRUE;
}