libMeatloaf/lib/meatloaf/archive/archive_ml.cpp
2024-01-08 11:58:15 -06:00

365 lines
11 KiB
C++

#include "archive_ml.h"
#include <archive.h>
#include <archive_entry.h>
#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<MStream> 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<uint8_t> 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<uint8_t> 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<uint8_t> 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<MStream> 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<MStream>(this->getSourceStream());
if(dirStream->isOpen())
{
return true;
}
else
{
Debug_printv("opening Archive for dir nok");
return false;
}
}