// Program name: cfcreate.cpp // Programmed by: Anthony Barbachan // Programmed in: C++ (Turbo C++ 3.0 Compatable) // Purpose: Source file for a cabinet file object. // Version: 1.00 // Last modified on: 12/31/1998 // Changes: Created classes to be used to create cabinet files. // Version: 1.10 // Last modified on: 10/24/1999 // Changes: Changed cfc_folderinfo::open function to return an error value. // This is better than just dying if the compression failed to // initiate. The cabinet_creator::new_folder function was updated to // take this error code into account. Removed cerr reference. #ifndef __CFCREATE_CPP__ #define __CFCREATE_CPP__ #include #include #include "zlib.h" #include "cftypes.h" #include "cfcreate.h" #ifndef unix #include #include #else #include #include #endif using std::ios; ///////////////////////////////////////*************************************** int cfc_fileinfo::write_entry(ostream& out) { struct file_fixed_header header; header.size = size; header.folder_offset = folder_offset; header.folder = folder; header.date = date; header.time = time; header.attribs = attribs; if(out.write((char *) &header, sizeof(struct file_fixed_header)).fail()) return WRITE_ERROR; return (out.write(name, strlen(name) + 1u).fail()) ? WRITE_ERROR : OK; } ///////////////////////////////////////*************************************** ///////////////////////////////////////*************************************** int cfc_folderinfo::write_entry(ostream& out, dword hsize) { struct folder_fixed_header header; header.offset = offset + hsize; header.nblocks = data_blocks; header.compression_type = compression_type; return (out.write((char *) &header, sizeof(struct folder_fixed_header)).fail()) ? WRITE_ERROR : OK; } ///////////////////////////////////////*************************************** int cfc_folderinfo::open(ostream& temp, dword soffset, word comptype, word fno, byte resrv_len, byte* resrv) { int r = OK; reset(); // reset object variables tempfile = &temp; folderno = fno; offset = soffset; if((compression_type = comptype) == MSZIP_COMPRESSION) { compressor_data = new z_stream; compressor_data->zalloc = (alloc_func) Z_NULL; compressor_data->zfree = (free_func) Z_NULL; compressor_data->opaque = (voidpf) Z_NULL; if((r = deflateInit(compressor_data, Z_DEFAULT_COMPRESSION)) != Z_OK) { return convert_z_error_code(r); } } if(resrv_len > 0) { reserved_area = new byte[resrv_len]; memcpy(reserved_area, resrv, resrv_len); } return OK; } ///////////////////////////////////////*************************************** int cfc_folderinfo::add_file(const char* fname) { if(tempfile == NULL) return FOLDER_CREATOR_FROZEN; int retval = 0; byte* buf = NULL; int bytesread = 0; unsigned temp = 0u; unsigned attribs = 0u; struct stat statbuf; #ifndef unix struct ftime datetime; #else struct tm* ts = NULL; #endif ifstream in(fname, ios::in | ios::binary); if(!in) return FILE_OPEN_FAILURE; while(!in.eof()) // Read in blocks of data { if((retval = read_block(in, buf, bytesread)) != OK) return retval; if(buf != NULL) { retval = process_block(buf, bytesread); delete[] buf; buf = NULL; bytesread = 0; if(retval != OK) return retval; } } if(stat(fname, &statbuf) != 0) return FSTAT_FAILURE; #ifndef unix if(getftime(in.rdbuf()->fd(), &datetime) != 0) return GETTIME_FAILURE; #endif in.close(); cfc_fileinfo *file = new cfc_fileinfo; file->set_name(fname); file->set_size(statbuf.st_size); file->set_folder_offset(nbytes); nbytes += statbuf.st_size; // Update number of bytes in folder file->set_folder(folderno); #ifndef unix file->set_date(datetime.ft_month, datetime.ft_day, datetime.ft_year + 1980); file->set_time(datetime.ft_hour, datetime.ft_min, datetime.ft_tsec); if(_dos_getfileattr(fname, &temp) != 0) { return GETATTRIB_FAILURE; } if(temp & _A_RDONLY) attribs |= cfc_fileinfo::A_RDONLY; if(temp & _A_HIDDEN) attribs |= cfc_fileinfo::A_HIDDEN; if(temp & _A_SYSTEM) attribs |= cfc_fileinfo::A_SYSTEM; if(temp & _A_VOLID) attribs |= cfc_fileinfo::A_VOLUME; if(temp & _A_SUBDIR) attribs |= cfc_fileinfo::A_DIRECTORY; if(temp & _A_ARCH) attribs |= cfc_fileinfo::A_ARCHIVE; #else ts = localtime(&(statbuf.st_mtime)); file->set_date(ts->tm_mon + 1, ts->tm_mday, ts->tm_year + 1900); file->set_time(ts->tm_hour, ts->tm_min, ts->tm_sec); if(!(statbuf.st_mode & S_IWUSR)) { attribs |= cfc_fileinfo::A_RDONLY; } #endif file->set_attribs(attribs); fileq.Put(file); // Save file information return OK; } ///////////////////////////////////////*************************************** // Returns OK if operation was successful // If buf is set to NULL then not enougth data was read to fill a block, // in that case the unfilled buffer will be stored in unprocessed_data. // If enougth data was read to fill a block it will be returned through buf // If operation fails, buf and/or buf_len return invalid data int cfc_folderinfo::read_block(istream& in, byte* &buf, int& bytesread) { if(in.eof()) // If in is at the EOF { buf = NULL; bytesread = 0; return OK; } const int len = 32767; bytesread = (int) unprocessed_data_len; buf = (unprocessed_data != NULL) ? unprocessed_data : new byte[len]; unprocessed_data = NULL; // Reset buffer holder unprocessed_data_len = 0u; if(in.read((char*)buf + bytesread, len - bytesread).bad()) { delete[] buf; buf = NULL; bytesread = 0; return READ_ERROR; } bytesread += in.gcount(); // Adjust number of bytes read if(bytesread == 0) // If no bytes read, do nothing { delete[] buf; buf = NULL; bytesread = 0; } else if(bytesread < len) // If block was not filled { unprocessed_data = buf; unprocessed_data_len = (word) bytesread; buf = NULL; bytesread = 0; } return OK; // If entire block was filled it will be returned } ///////////////////////////////////////*************************************** int cfc_folderinfo::freeze() { int retval = OK; if(unprocessed_data_len > 0u) // Process any unprocessed data { if((retval = process_block(unprocessed_data, unprocessed_data_len)) != OK) return retval; delete[] unprocessed_data; unprocessed_data = NULL; unprocessed_data_len = 0u; } tempfile = NULL; // Prevent futher file additions return OK; } ///////////////////////////////////////*************************************** int cfc_folderinfo::process_block(const byte* data, word datalen) { int retval = 0; struct { dword csum; word compressed_len; word uncompressed_len; // byte* abReserve; // byte* ab; } blockinfo = { 0ul, 0u, 0u }; byte* compdata = NULL; blockinfo.uncompressed_len = datalen; // Note: add code to add reserve area if((retval = compress_block(compdata, blockinfo.compressed_len, (byte *) data, datalen)) != OK) return retval; // cout << compdata[0] << compdata[1] << endl; blockinfo.csum = CSUMCompute((byte *) &(blockinfo.compressed_len), sizeof(blockinfo.compressed_len) + sizeof(blockinfo.uncompressed_len), CSUMCompute(compdata, blockinfo.compressed_len, 0)); if(tempfile->write((char *) &blockinfo, sizeof(blockinfo)).fail()) { if(compdata != data) delete[] compdata; return WRITE_ERROR; } tempfile->write((char*)compdata, blockinfo.compressed_len); processed_bytes += sizeof(blockinfo) + blockinfo.compressed_len; if(compdata != data) delete[] compdata; // If buffer was allocated, free it data_blocks++; // Incriment block counter return (tempfile->fail()) ? WRITE_ERROR : OK; } ///////////////////////////////////////*************************************** // Returns OK if successful // If successful a buffer will be allocated and returned in dest, its size // will be return via destlen. The data will be compressed via the compression // method specified by the folder information. // If no compression was done dest and destlen are set to src and srclen. // If dest was created it must be freed by the calling function. int cfc_folderinfo::compress_block(byte* &dest, word &destlen, byte* src, word srclen) { int retval = 0; unsigned int buflen = 0u; switch(compression_type) { case NO_COMPRESSION: dest = src; destlen = srclen; break; case MSZIP_COMPRESSION: buflen = (srclen + (srclen / 10u)) + 12u; dest = new byte[(size_t) buflen]; compressor_data->next_in = (Bytef *) src; compressor_data->avail_in = srclen; compressor_data->next_out = dest; compressor_data->avail_out = buflen; // if((retval = deflate(compressor_data, Z_FULL_FLUSH)) != Z_OK) // works for mine not extract, cabarc if((retval = deflate(compressor_data, Z_FINISH)) != Z_STREAM_END) // works { delete[] dest; dest = NULL; destlen = 0u; return convert_z_error_code(retval); } destlen = buflen - compressor_data->avail_out; if((retval = deflateReset(compressor_data)) != Z_OK) { delete[] dest; dest = NULL; destlen = 0u; return convert_z_error_code(retval); } break; default: dest = NULL; destlen = 0u; break; } return OK; } ////////////////////////////////////////**************************************** ////////////////////////////////////////**************************************** // The number of bytes within this folder and the number of bytes that will be // occupied by the associated file entries will be added to size. void cfc_folderinfo::close(QueueOf &cabfileq, unsigned long& size, unsigned long& hsize) { unsigned long temp = 0ul; if(tempfile != NULL) freeze(); // If folder isn't fozen, freeze it if(fileq.NotEmpty()) { hsize += entry_size(); // Add total size of this entry size += entry_size() + processed_bytes; // Add total size of this folder do{ temp = fileq.Peek()->entry_size(); // Add file entry size hsize += temp; size += temp; cabfileq.Put(fileq.Get()); }while(fileq.NotEmpty()); } } ///////////////////////////////////////*************************************** cfc_folderinfo::CHECKSUM cfc_folderinfo::CSUMCompute(byte* pb, unsigned cb, CHECKSUM seed) { dword temp = 0ul; CHECKSUM csum = seed; // Init checksum accumulator // Checksum integral multiple of unsigned longs for(int numdw = cb / 4; numdw > 0; numdw--) // Loop through dwords { // NOTE: Building unsigned long in big/little-endian independent manner temp = (dword) (*pb++); // Get low-order byte temp |= ((dword) (*pb++)) << 8; // Add 2nd byte temp |= ((dword) (*pb++)) << 16; // Add 3nd byte temp |= ((dword) (*pb++)) << 24; // Add 4th byte csum ^= temp; // Update checksum } // Checksum remainder bytes temp = 0ul; switch (cb % 4) { case 3: temp |= ((dword) (*pb++)) << 16; // Add 3nd byte case 2: temp |= ((dword) (*pb++)) << 8; // Add 2nd byte case 1: temp |= (dword) (*pb++); // Get low-order byte csum ^= temp; // Update checksum break; } return csum; // Return computed checksum } ///////////////////////////////////////*************************************** ///////////////////////////////////////*************************************** // QueueOf tempfolderq; ///////////////////////////////////////*************************************** ///////////////////////////////////////*************************************** // This function initializes the creation of a new cabinet file. int cabinet_creator::open() { char buf[L_tmpnam + 1u]; reset(); // Reset object tmpnam(buf); temp_file_name = buf; temp.open(buf, ios::in | ios::out | ios::binary | ios::trunc); if(!temp) // If open operation failed { return OPEN_TEMP_FAILURE; } return OK; } ///////////////////////////////////////*************************************** // Note: must add support for reserved area in the future int cabinet_creator::new_folder(word comptype) { int retval = OK; if(folderq.NumObjects() >= ~((word)cfc_fileinfo::ifoldCONTINUED_FROM_PREV)) { return FOLDER_LIMIT_REACHED; } cfc_folderinfo *folder = new cfc_folderinfo; if(folderq.IsNotEmpty()) folderq.RearPeek()->freeze(); if((retval = folder->open(temp, temp.tellp(), comptype, (word) folderq.NumObjects())) != OK) { return retval; } folderq.Put(folder); return OK; } ///////////////////////////////////////*************************************** int cabinet_creator::add_file(const char* fname) { if(folderq.IsEmpty()) return NO_FOLDER; // Folder must exist to add a file return folderq.RearPeek()->add_file(fname); } ///////////////////////////////////////*************************************** int cabinet_creator::close(const char* fname) { int retval = 0; ofstream out(fname, ios::out | ios::binary | ios::trunc); if(!out) { return FILE_OPEN_FAILURE; } if((retval = close(out)) != OK) return retval; out.close(); return (out.fail()) ? FILE_CLOSE_FAILURE : OK; } ///////////////////////////////////////*************************************** int cabinet_creator::close(ostream& out) { int retval = 0; unsigned long header_size = sizeof(struct cabinet_fixed_header); QueueOf fileq; struct cabinet_fixed_header header; memset(&header, 0, sizeof(struct cabinet_fixed_header)); close_all_folders(fileq, header.size, header_size); memcpy(header.signature, "MSCF", 4); header.size += sizeof(struct cabinet_fixed_header); // Adjust size for header header.version_minor = 3; header.version_major = 1; header.nfolders = (word) folderq.NumObjects(); header.nfiles = (word) fileq.NumObjects(); header.files_offset = sizeof(struct cabinet_fixed_header) + (header.nfolders * sizeof(struct cfc_folderinfo::folder_fixed_header)); header.flags = 0u; header.id = 0u; header.cabinetno = 0u; // Note: Add code to include support for reserved areas and adjacent cabinets if(out.write((char *) &header, sizeof(struct cabinet_fixed_header)).fail()) return WRITE_ERROR; while(folderq.NotEmpty()) { if((retval = folderq.Peek()->write_entry(out, header_size)) != OK) return retval; delete folderq.Get(); // Delete folder entry } while(fileq.NotEmpty()) { if((retval = fileq.Peek()->write_entry(out)) != OK) return retval; delete fileq.Get(); // Delete folder entry } if(temp.seekg(0, ios::beg).fail()) return SEEK_ERROR; if((out << temp.rdbuf()).fail()) return WRITE_ERROR; temp.close(); if(temp_file_name.Exists()) { unlink((const char *) temp_file_name); temp_file_name.clear(); } return OK; } ///////////////////////////////////////*************************************** // The number of bytes in each folder will be added to nbytes void cabinet_creator::close_all_folders(QueueOf &fileq, unsigned long& nbytes, unsigned long& header_size) { if(folderq.IsNotEmpty()) folderq.RearPeek()->freeze(); for(unsigned long nfolders = 0ul; nfolders < folderq.NumObjects(); nfolders++) { folderq.Peek()->close(fileq, nbytes, header_size); folderq.Put(folderq.Get()); } } ///////////////////////////////////////*************************************** #endif