#include "archive_ml.h" #include #include #include "meat_io.h" /* Returns pointer and size of next block of data from archive. */ // The read callback returns the number of bytes read, zero for end-of-file, or a negative failure code as above. // It also returns a pointer to the block of data read. // https://github.com/libarchive/libarchive/wiki/LibarchiveIO // // This callback is just a way to get bytes from srcStream into libarchive for processing ssize_t cb_read(struct archive *a, void *userData, const void **buff) { ArchiveStreamData *streamData = (ArchiveStreamData *)userData; // 1. we have to call srcStr.read(...) ssize_t bc = streamData->srcStream->read(streamData->srcBuffer, ArchiveStream::buffSize); //std::string dump((char*)streamData->srcBuffer, bc); //Debug_printv("Libarch pulling data from src MStream, got bytes:%d", bc); //Debug_printv("Dumping bytes: %s", dump.c_str()); // 2. set *buff to the bufer read in 1. *buff = streamData->srcBuffer; // 3. return read bytes count return bc; } /* It must return the number of bytes actually skipped, or a negative failure code if skipping cannot be done. It can skip fewer bytes than requested but must never skip more. Only positive/forward skips will ever be requested. If skipping is not provided or fails, libarchive will call the read() function and simply ignore any data that it does not need. * Skips at most request bytes from archive and returns the skipped amount. * This may skip fewer bytes than requested; it may even skip zero bytes. * If you do skip fewer bytes than requested, libarchive will invoke your * read callback and discard data as necessary to make up the full skip. */ // https://github.com/libarchive/libarchive/wiki/LibarchiveIO int64_t cb_skip(struct archive *a, void *userData, int64_t request) { Debug_printv("bytes[%d]", request); ArchiveStreamData *streamData = (ArchiveStreamData *)userData; if (streamData->srcStream->isOpen()) { bool rc = streamData->srcStream->seek(request, SEEK_CUR); return (rc) ? request : ARCHIVE_WARN; } else { Debug_printv("ERROR! skip failed"); return ARCHIVE_FATAL; } } int64_t cb_seek(struct archive *a, void *userData, int64_t offset, int whence) { Debug_printv("offset[%d] whence[%d] (0=begin, 1=curr, 2=end)", offset, whence); ArchiveStreamData *streamData = (ArchiveStreamData *)userData; if (streamData->srcStream->isOpen()) { bool rc = streamData->srcStream->seek(offset, whence); return (rc) ? offset : ARCHIVE_WARN; } else { Debug_printv("ERROR! seek failed"); return ARCHIVE_FATAL; } } int cb_close(struct archive *a, void *userData) { ArchiveStreamData *src_str = (ArchiveStreamData *)userData; Debug_printv("Libarch wants to close, but we do nothing here..."); // do we want to close srcStream here??? return (ARCHIVE_OK); } int cb_open(struct a *arch, void *userData) { // maybe we can use open for something? Check if stream is open? return (ARCHIVE_OK); } /******************************************************** * Streams implementations ********************************************************/ ArchiveStream::ArchiveStream(std::shared_ptr srcStr) { // it should be possible to to pass a password parameter here and somehow // call archive_passphrase_callback(password) from here, right? streamData.srcStream = srcStr; a = archive_read_new(); archive_read_support_filter_all(a); archive_read_support_format_all(a); streamData.srcBuffer = new uint8_t[buffSize]; open(); } ArchiveStream::~ArchiveStream() { close(); if (streamData.srcBuffer != nullptr) delete[] streamData.srcBuffer; Debug_printv("Stream destructor OK!"); } bool ArchiveStream::open() { if (!is_open) { // TODO enable seek only if the stream is random access archive_read_set_read_callback(a, cb_read); archive_read_set_skip_callback(a, cb_skip); archive_read_set_seek_callback(a, cb_seek); archive_read_set_close_callback(a, cb_close); // archive_read_set_open_callback(mpa->arch, cb_open); - what does it do? archive_read_set_callback_data(a, &streamData); Debug_printv("== BEGIN Calling open1 on archive instance =========================="); int r = archive_read_open1(a); Debug_printv("== END opening archive result=%d! (OK should be 0!) =======================================", r); //int r = archive_read_open2(a, &streamData, NULL, myRead, myskip, myclose); if (r == ARCHIVE_OK) is_open = true; } return is_open; }; void ArchiveStream::close() { if (is_open) { archive_read_close(a); archive_read_free(a); is_open = false; } Debug_printv("Close called"); } bool ArchiveStream::isOpen() { return is_open; }; std::vector leftovers; uint32_t ArchiveStream::read(uint8_t *buf, uint32_t size) { Debug_printv("calling read size[%d]", size); const void *incomingBuffer; size_t incomingSize; int64_t offset; int r = archive_read_data_block(a, &incomingBuffer, &incomingSize, &offset); Debug_printv("r[%d]", r); if ( r == ARCHIVE_EOF ) return 0; if ( r < ARCHIVE_OK ) return 0; // 'buff' contains the data of the current block // 'size' is the size of the current block std::vector incomingVector((uint8_t*)incomingBuffer, (uint8_t*)incomingBuffer + incomingSize); // concatenate intermediate buffer with incomingVector leftovers.insert(leftovers.end(), incomingVector.begin(), incomingVector.end()); if(leftovers.size() <= size) { // ok, we can fit everything that was left and new data to our buffer auto size = leftovers.size(); _position += size; leftovers.clear(); } else { // ok, so we can only write up to size and we have to keep leftovers for next time std::copy(leftovers.begin(), leftovers.begin() + size, buf); std::vector leftovers2(leftovers.begin() + size, leftovers.end()); leftovers = leftovers2; _position += size; } _position += size; Debug_printv("size[%d] position[%d]", size, _position); return size; } uint32_t ArchiveStream::write(const uint8_t *buf, uint32_t size) { return -1; } // For files with a browsable random access directory structure // d64, d74, d81, dnp, etc. bool ArchiveStream::seekPath(std::string path) { Debug_printv("seekPath called for path: %s", path.c_str()); if ( seekEntry( path ) ) { Debug_printv("entry[%s]", archive_entry_pathname(entry)); return true; } return false; } bool ArchiveStream::seekEntry( std::string filename ) { Debug_printv( "filename[%s] size[%d]", filename.c_str(), filename.size()); // Read Directory Entries if ( filename.size() > 0 ) { bool found = false; bool wildcard = ( mstr::contains(filename, "*") || mstr::contains(filename, "?") ); while ( archive_read_next_header(a, &entry) == ARCHIVE_OK ) { std::string entryPath = ""; //archive_entry_sourcepath(entry); std::string entryFilename = archive_entry_pathname(entry); Debug_printv("path[%s] filename[%s] entry.filename[%.16s]", entryPath.c_str(), filename.c_str(), entryFilename.c_str()); // Check filetype if ( archive_entry_filetype(entry) != AE_IFDIR ) { // Read Entry From Stream if (filename == "*") // Match first entry { filename = entryFilename; found = true; } else if ( filename == entryFilename ) // Match exact { found = true; } else if ( wildcard ) { if ( mstr::compare(filename, entryFilename) ) // X?XX?X* Wildcard match { // Set filename to this filename Debug_printv( "Found! file[%s] -> entry[%s]", filename.c_str(), entryFilename.c_str() ); found = true; } } if ( found ) { _size = archive_entry_size(entry); return true; } } } Debug_printv( "Not Found! file[%s]", filename.c_str() ); } return false; } // // For files with no directory structure // // tap, crt, tar // std::string ArchiveStream::seekNextEntry() // { // struct archive_entry *entry; // if (archive_read_next_header(a, &entry) == ARCHIVE_OK) // return archive_entry_pathname(entry); // else // return ""; // }; bool ArchiveStream::seek(uint32_t pos) { return streamData.srcStream->seek(pos); } /******************************************************** * Files implementations ********************************************************/ MStream *ArchiveContainerFile::getDecodedStream(std::shared_ptr containerIstream) { // TODO - we can get password from this URL and pass it as a parameter to this constructor Debug_printv("calling getDecodedStream for ArchiveContainerFile, we should return open stream"); auto stream = new ArchiveStream(containerIstream); return stream; } // archive file is always a directory bool ArchiveContainerFile::isDirectory() { //Debug_printv("pathInStream[%s]", pathInStream.c_str()); if ( pathInStream == "" ) return true; else return false; }; bool ArchiveContainerFile::rewindDirectory() { dirIsOpen = true; return prepareDirListing(); } MFile *ArchiveContainerFile::getNextFileInDir() { if(!dirIsOpen) rewindDirectory(); struct archive_entry *entry; Debug_printv("getNextFileInDir calling archive_read_next_header"); if (archive_read_next_header(getArchive(), &entry) == ARCHIVE_OK) { std::string fileName = archive_entry_pathname(entry); auto file = MFSOwner::File(streamFile->url + "/" + fileName); file->_size = archive_entry_size(entry); file->_exists = true; return file; } else { //Debug_printv( "END OF DIRECTORY"); dirStream->close(); dirIsOpen = false; return nullptr; } } bool ArchiveContainerFile::prepareDirListing() { if (dirStream.get() != nullptr) { dirStream->close(); } Debug_printv("w prepare dir listing"); dirStream = std::shared_ptr(this->getSourceStream()); if(dirStream->isOpen()) { return true; } else { Debug_printv("opening Archive for dir nok"); return false; } }