mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-07 12:07:09 +03:00
333 lines
9.2 KiB
C++
333 lines
9.2 KiB
C++
#include "BellTar.h"
|
|
|
|
#include <sys/stat.h> // for mkdir
|
|
|
|
using namespace bell::BellTar;
|
|
|
|
#include <algorithm> // for min
|
|
#include <cassert> // for assert
|
|
#include <cstdint> // for uint8_t
|
|
#include <cstdio> // for sprintf, size_t, sscanf, EOF, NULL
|
|
#include <cstdlib> // for rand
|
|
#include <cstring> // for memset, strlen
|
|
#include <ctime> // for time
|
|
#include <fstream> // for ofstream
|
|
#include <vector> // for vector
|
|
#ifdef _WIN32
|
|
#include <direct.h>
|
|
#endif
|
|
|
|
#ifdef ENABLE_LOGGING
|
|
#define LOG printf
|
|
#else
|
|
#ifdef _WIN32
|
|
#define LOG(fmt, ...) ((void)0)
|
|
#else
|
|
#define LOG(fmt, args...) ((void)0)
|
|
#endif
|
|
#endif
|
|
|
|
const char FILL_CHAR = '\0';
|
|
const int FILE_NAME_LENGTH = 100;
|
|
|
|
// From http://en.wikipedia.org/wiki/Tar_(computing)#UStar_format
|
|
typedef enum tar_file_type {
|
|
tar_file_type_normal = '0',
|
|
tar_file_type_hard_link = '1',
|
|
tar_file_type_soft_link = '2',
|
|
tar_file_type_directory = '5'
|
|
} tar_file_type_t;
|
|
|
|
struct tar_header {
|
|
char name[FILE_NAME_LENGTH]; // file name
|
|
char mode[8]; // file mode
|
|
char uid[8]; // Owner's numeric user ID
|
|
char gid[8]; // Group's numeric user ID
|
|
char size[12]; // File size in bytes (octal base)
|
|
char mtime[12]; // Last modification time in
|
|
// numeric Unix time format (octal)
|
|
char checksum[8]; // Checksum for header record
|
|
char typeflag[1]; // file type, see tar_file_type_t
|
|
char linkname[100]; // Name of linked file
|
|
char magic[6]; // UStar indicator "ustar"
|
|
char version[2]; // UStar version "00"
|
|
char uname[32]; // Owner user name
|
|
char gname[32]; // Owner group name
|
|
char devmajor[8]; // Device major number
|
|
char devminor[8]; // Device minor number
|
|
char prefix[155]; // Filename prefix
|
|
char pad[12]; // padding
|
|
};
|
|
|
|
void header_set_metadata(tar_header* header) {
|
|
std::memset(header, 0, sizeof(tar_header));
|
|
|
|
std::sprintf(header->magic, "ustar");
|
|
std::sprintf(header->mtime, "%011lo", (unsigned long)std::time(NULL));
|
|
std::sprintf(header->mode, "%07o", 0644);
|
|
std::sprintf(header->uname, "unkown"); // ... a bit random
|
|
std::sprintf(header->gname, "users");
|
|
header->typeflag[0] = 0; // always just a normal file
|
|
}
|
|
|
|
/* From Wikipedia: The checksum is calculated by taking the sum of the
|
|
* unsigned byte values of the header record with the eight checksum
|
|
* bytes taken to be ascii spaces. */
|
|
void header_set_checksum(tar_header* header) {
|
|
unsigned int sum = 0;
|
|
|
|
char* pointer = (char*)header;
|
|
char* end = pointer + sizeof(tar_header);
|
|
|
|
// Iterate over header struct until we are at checksum field.
|
|
while (pointer < header->checksum) {
|
|
sum += *pointer & 0xff;
|
|
pointer++;
|
|
}
|
|
|
|
// ... then add eight 'ascii spaces' ...
|
|
sum += ' ' * 8;
|
|
pointer += 8;
|
|
|
|
// ... and go until the end.
|
|
while (pointer < end) {
|
|
sum += *pointer & 0xff;
|
|
pointer++;
|
|
}
|
|
|
|
std::sprintf(header->checksum, "%06o", sum);
|
|
}
|
|
|
|
void header_set_filetype(tar_header* header, tar_file_type_t file_type) {
|
|
header->typeflag[0] = file_type;
|
|
}
|
|
|
|
tar_file_type_t header_get_filetype(tar_header* header) {
|
|
return tar_file_type_t(header->typeflag[0]);
|
|
}
|
|
|
|
void header_set_filesize(tar_header* header, file_size_t file_size) {
|
|
std::sprintf(header->size, "%011llo", file_size);
|
|
}
|
|
|
|
file_size_t header_get_filesize(tar_header* header) {
|
|
file_size_t file_size;
|
|
std::sscanf(header->size, "%011llo", &file_size);
|
|
return file_size;
|
|
}
|
|
|
|
void header_set_filename(tar_header* header, const char* file_name) {
|
|
size_t len = std::strlen(file_name);
|
|
|
|
// len > 0 also ensures that the header does not start with \0
|
|
if (len == 0 || len >= FILE_NAME_LENGTH) {
|
|
LOG("Invalid file name for tar: %s\n", file_name);
|
|
std::sprintf(header->name, "INVALID_%d", std::rand());
|
|
} else {
|
|
std::sprintf(header->name, "%s", file_name);
|
|
}
|
|
}
|
|
|
|
std::string header_get_filename(tar_header* header) {
|
|
return std::string(header->name);
|
|
}
|
|
|
|
////////////////////////////////////////
|
|
|
|
/* Every file in a tar file starts with the tar header */
|
|
void _write_header(std::ostream& dst, const char* file_name,
|
|
file_size_t file_size,
|
|
tar_file_type_t file_type = tar_file_type_normal) {
|
|
tar_header header;
|
|
header_set_metadata(&header);
|
|
header_set_filename(&header, file_name);
|
|
header_set_filesize(&header, file_size);
|
|
header_set_filetype(&header, file_type);
|
|
header_set_checksum(&header);
|
|
|
|
dst.write((const char*)&header, sizeof(tar_header));
|
|
}
|
|
|
|
void _read_header(std::istream& inp, tar_header* header) {
|
|
inp.read((char*)header, sizeof(tar_header));
|
|
}
|
|
|
|
/* The length of the data after the header must be rounded up to a
|
|
multiple of 512 bytes, the length of the header. */
|
|
void _fill(std::ostream& dst, unsigned long file_size) {
|
|
while (file_size % sizeof(tar_header) != 0) {
|
|
dst.put(FILL_CHAR);
|
|
file_size++;
|
|
}
|
|
}
|
|
bool _check_if_header_is_next(std::istream& inp) {
|
|
if (inp.eof() || inp.peek() == EOF) {
|
|
LOG("Can not read next file info, istream at EOF.\n");
|
|
return false;
|
|
}
|
|
|
|
if (inp.peek() == FILL_CHAR) {
|
|
LOG("Can not read next file info, istream is pointing "
|
|
"to %d, which a tar header can not start with.\n",
|
|
FILL_CHAR);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void _seek_to_next_header(std::istream& inp) {
|
|
// Advance to start of next header or to end of file
|
|
// Works because
|
|
// - header never starts with FILL_CHAR
|
|
// - at end of file, peek() returns EOF.
|
|
// - FILL_CHAR != EOF
|
|
while (inp.peek() == FILL_CHAR)
|
|
inp.get();
|
|
}
|
|
|
|
////////////////////////////////////////
|
|
// writer Implementation
|
|
////////////////////////////////////////
|
|
|
|
void writer::put(std::string path_in_tar, char const* const data,
|
|
const file_size_t data_size) {
|
|
_write_header(_dst, path_in_tar.c_str(), data_size);
|
|
_dst.write(data, data_size);
|
|
_fill(_dst, data_size);
|
|
}
|
|
|
|
void writer::put_directory(std::string path_in_tar) {
|
|
_write_header(_dst, path_in_tar.c_str(), 0, tar_file_type_directory);
|
|
}
|
|
|
|
/* The end of an tar is marked by at least two consecutive zero-filled
|
|
* records, a record having the size of the header. */
|
|
void writer::finish() {
|
|
unsigned long i = 0;
|
|
while (i < 2 * sizeof(tar_header)) {
|
|
_dst.put(FILL_CHAR);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////
|
|
// reader Implementation
|
|
////////////////////////////////////////
|
|
|
|
bool reader::contains_another_file() {
|
|
return _check_if_header_is_next(_inp);
|
|
}
|
|
|
|
void reader::_cache_header() {
|
|
if (_cached_header_data_valid)
|
|
return;
|
|
|
|
assert(contains_another_file());
|
|
|
|
tar_header h;
|
|
_read_header(_inp, &h);
|
|
|
|
_cached_header_data.file_name = header_get_filename(&h);
|
|
_cached_header_data.file_size = header_get_filesize(&h);
|
|
_cached_header_data.file_type = h.typeflag[0];
|
|
_cached_header_data_valid = true;
|
|
}
|
|
|
|
std::string reader::get_next_file_name() {
|
|
_cache_header();
|
|
return _cached_header_data.file_name;
|
|
}
|
|
|
|
file_size_t reader::get_next_file_size() {
|
|
_cache_header();
|
|
return _cached_header_data.file_size;
|
|
}
|
|
|
|
void reader::read_next_file(char* const data) {
|
|
_inp.read(data, get_next_file_size());
|
|
|
|
_cached_header_data_valid = false;
|
|
_seek_to_next_header(_inp);
|
|
}
|
|
|
|
void reader::skip_next_file() {
|
|
_inp.seekg(get_next_file_size(), std::ios::cur);
|
|
|
|
_cached_header_data_valid = false;
|
|
_seek_to_next_header(_inp);
|
|
}
|
|
|
|
char reader::get_next_file_type() {
|
|
_cache_header();
|
|
return _cached_header_data.file_type;
|
|
}
|
|
|
|
int reader::number_of_files() {
|
|
if (_number_of_files == -1) {
|
|
std::streampos current_position = _inp.tellg();
|
|
_inp.seekg(0, std::ios::beg);
|
|
|
|
_number_of_files = 0;
|
|
while (contains_another_file()) {
|
|
_number_of_files++;
|
|
skip_next_file();
|
|
}
|
|
|
|
_inp.seekg(current_position);
|
|
}
|
|
|
|
return _number_of_files;
|
|
}
|
|
|
|
void reader::extract_all_files(std::string dest_directory) {
|
|
std::vector<uint8_t> scratch_buffer(1024);
|
|
|
|
while (contains_another_file()) {
|
|
char fileType = get_next_file_type();
|
|
auto fileName = get_next_file_name();
|
|
|
|
// 0 is the normal file type, skip apple's ._ files
|
|
#if __cplusplus >= 202002L
|
|
if (fileType == '0' && !fileName.starts_with("._")) {
|
|
#else
|
|
if (fileType == '0' && fileName.find("._") != 0) {
|
|
#endif
|
|
std::string path = dest_directory + "/" + fileName;
|
|
|
|
size_t pos = 0;
|
|
while ((pos = path.find('/', pos)) != std::string::npos) {
|
|
std::string dir = path.substr(0, pos);
|
|
// Create the directory if it doesn't exist
|
|
#ifdef _WIN32
|
|
mkdir(dir.c_str());
|
|
#else
|
|
mkdir(dir.c_str(), 0777);
|
|
#endif
|
|
pos++;
|
|
}
|
|
|
|
std::ofstream out(path, std::ios::binary);
|
|
|
|
size_t read_size = 0;
|
|
size_t file_size = get_next_file_size();
|
|
|
|
while (read_size < file_size) {
|
|
size_t to_read = std::min(file_size - read_size, scratch_buffer.size());
|
|
|
|
_inp.read((char*)scratch_buffer.data(), to_read);
|
|
|
|
// Move the read size forward
|
|
read_size += _inp.gcount();
|
|
|
|
// Write the data to the destination file
|
|
out.write((char*)scratch_buffer.data(), _inp.gcount());
|
|
}
|
|
|
|
_cached_header_data_valid = false;
|
|
_seek_to_next_header(_inp);
|
|
} else {
|
|
skip_next_file();
|
|
}
|
|
}
|
|
} |