:-'Generic Commit'

This commit is contained in:
Your Name 2025-05-28 22:03:14 +12:00
parent 8556b683c7
commit 3bc0c65c40
6 changed files with 331 additions and 15 deletions

View File

@ -83,6 +83,12 @@
"variant": "cpp",
"format": "cpp",
"stdfloat": "cpp",
"__nullptr": "cpp"
"__nullptr": "cpp",
"*.ipp": "cpp",
"__hash_table": "cpp",
"__split_buffer": "cpp",
"__tree": "cpp",
"queue": "cpp",
"stack": "cpp"
}
}

View File

@ -1,6 +1,7 @@
cmake_minimum_required(VERSION 3.10)
set(PROJECT_EXE_NAME dropshell-tool)
project(${PROJECT_EXE_NAME} VERSION 1.0.0 LANGUAGES CXX)
cmake_policy(SET CMP0135 NEW)
# Force static linking globally
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
@ -9,7 +10,15 @@ set(BUILD_SHARED_LIBS OFF CACHE BOOL "Build shared libraries" FORCE)
set(CMAKE_POSITION_INDEPENDENT_CODE OFF)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -static")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static")
set(ZLIB_USE_STATIC_LIBS "ON")
set(ZLIB_USE_STATIC_LIBS ON)
# Ensure zlib is built as static only
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(ZLIB_BUILD_SHARED OFF CACHE BOOL "" FORCE)
set(ZLIB_BUILD_STATIC ON CACHE BOOL "" FORCE)
# Disable zlib examples and minigzip
set(ZLIB_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(ZLIB_BUILD_MINIGZIP OFF CACHE BOOL "" FORCE)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_C_STANDARD 23)
@ -86,18 +95,23 @@ FetchContent_MakeAvailable(cpptrace)
FetchContent_Declare(
nlohmann_json
GIT_REPOSITORY https://github.com/nlohmann/json.git
GIT_TAG v3.11.3
GIT_TAG v3.12.0
)
FetchContent_MakeAvailable(nlohmann_json)
find_package(OpenSSL REQUIRED)
target_link_libraries(${PROJECT_EXE_NAME} PRIVATE OpenSSL::SSL OpenSSL::Crypto)
# Fetch and build zlib from source (static)
FetchContent_Declare(
zlib
URL https://zlib.net/zlib-1.3.1.tar.gz
)
FetchContent_MakeAvailable(zlib)
# Link libraries
target_link_libraries(${PROJECT_EXE_NAME} PRIVATE
libassert::assert
cpptrace::cpptrace
nlohmann_json::nlohmann_json
zlibstatic
)
# Set static linking flags
@ -109,3 +123,9 @@ find_package(OpenSSL REQUIRED)
target_compile_definitions(${PROJECT_EXE_NAME} PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
target_link_libraries(${PROJECT_EXE_NAME} PRIVATE OpenSSL::SSL OpenSSL::Crypto)
# --- MUSL CROSS-COMPILATION ---
# To ensure all dependencies (including zlib) are built with musl, set your toolchain file or:
# set(CMAKE_C_COMPILER "/path/to/musl-gcc")
# set(CMAKE_CXX_COMPILER "/path/to/musl-g++")
# BEFORE the project() call above.

View File

@ -1,24 +1,212 @@
#include "ArchiveManager.hpp"
#include <string>
#include <vector>
#include <fstream>
#include <filesystem>
#include <cstring>
#include <zlib.h>
#include <sstream>
#include <span>
#include "tar_to_stream.hpp"
#include "ArchiveManager.hpp"
namespace fs = std::filesystem;
namespace {
constexpr size_t TAR_BLOCK_SIZE = 512;
// --- Minimal tar extraction logic for unpacking and config extraction ---
struct TarHeader {
char name[100];
char mode[8];
char uid[8];
char gid[8];
char size[12];
char mtime[12];
char chksum[8];
char typeflag;
char linkname[100];
char magic[6];
char version[2];
char uname[32];
char gname[32];
char devmajor[8];
char devminor[8];
char prefix[155];
char pad[12];
};
size_t tarFileSize(const TarHeader* hdr) {
return std::strtol(hdr->size, nullptr, 8);
}
std::string tarFileName(const TarHeader* hdr) {
std::string name(hdr->name);
if (hdr->prefix[0]) {
std::string prefix(hdr->prefix);
return prefix + "/" + name;
}
return name;
}
bool readTar(const std::vector<uint8_t>& tarData, const std::string& outDir, std::string* configJson = nullptr) {
size_t pos = 0;
while (pos + sizeof(TarHeader) <= tarData.size()) {
const TarHeader* hdr = reinterpret_cast<const TarHeader*>(&tarData[pos]);
if (hdr->name[0] == '\0') break;
size_t filesize = tarFileSize(hdr);
std::string filename = tarFileName(hdr);
size_t fileStart = pos + sizeof(TarHeader);
if (configJson && filename == "dropshell-tool-config.json") {
*configJson = std::string(reinterpret_cast<const char*>(&tarData[fileStart]), filesize);
} else if (!outDir.empty()) {
fs::path outPath = fs::path(outDir) / filename;
fs::create_directories(outPath.parent_path());
std::ofstream ofs(outPath, std::ios::binary);
ofs.write(reinterpret_cast<const char*>(&tarData[fileStart]), filesize);
}
pos = fileStart + filesize;
pos += (TAR_BLOCK_SIZE - (filesize % TAR_BLOCK_SIZE)) % TAR_BLOCK_SIZE;
}
return true;
}
bool extractConfigJson(const std::vector<uint8_t>& tarData, std::string& outJson) {
return readTar(tarData, "", &outJson);
}
bool replaceConfigJson(std::vector<uint8_t>& tarData, const std::string& json) {
// Remove old config, add new one at end
std::vector<uint8_t> newTar;
size_t pos = 0;
bool replaced = false;
while (pos + sizeof(TarHeader) <= tarData.size()) {
const TarHeader* hdr = reinterpret_cast<const TarHeader*>(&tarData[pos]);
if (hdr->name[0] == '\0') break;
size_t filesize = tarFileSize(hdr);
std::string filename = tarFileName(hdr);
size_t fileStart = pos + sizeof(TarHeader);
if (filename != "dropshell-tool-config.json") {
newTar.insert(newTar.end(), &tarData[pos], &tarData[fileStart + filesize]);
size_t pad = (TAR_BLOCK_SIZE - (filesize % TAR_BLOCK_SIZE)) % TAR_BLOCK_SIZE;
newTar.insert(newTar.end(), pad, 0);
} else {
replaced = true;
}
pos = fileStart + filesize;
pos += (TAR_BLOCK_SIZE - (filesize % TAR_BLOCK_SIZE)) % TAR_BLOCK_SIZE;
}
// Add new config
// Use tar_to_stream to add the config file
std::ostringstream oss(std::ios::binary);
oss.write(reinterpret_cast<const char*>(newTar.data()), newTar.size());
std::string uname = "root";
std::string gname = "root";
tar_to_stream_properties props = {
"dropshell-tool-config.json",
std::as_bytes(std::span(json.data(), json.size())),
0u, "644", 0u, 0u, uname, gname
};
tar_to_stream(oss, std::move(props));
tar_to_stream_tail(oss);
std::string tarStr = oss.str();
tarData.assign(tarStr.begin(), tarStr.end());
return true;
}
// --- zlib helpers ---
bool gzipCompress(const std::string& in, std::vector<uint8_t>& out) {
z_stream strm{};
deflateInit2(&strm, Z_BEST_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY);
out.resize(compressBound(in.size()));
strm.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(in.data()));
strm.avail_in = in.size();
strm.next_out = out.data();
strm.avail_out = out.size();
int ret = deflate(&strm, Z_FINISH);
if (ret != Z_STREAM_END) { deflateEnd(&strm); return false; }
out.resize(strm.total_out);
deflateEnd(&strm);
return true;
}
bool gzipDecompress(const std::string& inPath, std::vector<uint8_t>& out) {
std::ifstream ifs(inPath, std::ios::binary);
if (!ifs) return false;
ifs.seekg(0, std::ios::end);
size_t insize = ifs.tellg();
ifs.seekg(0, std::ios::beg);
std::vector<uint8_t> inbuf(insize);
ifs.read(reinterpret_cast<char*>(inbuf.data()), insize);
z_stream strm{};
inflateInit2(&strm, 15 + 16);
out.resize(insize * 10); // crude guess
strm.next_in = inbuf.data();
strm.avail_in = inbuf.size();
strm.next_out = out.data();
strm.avail_out = out.size();
int ret = inflate(&strm, Z_FINISH);
if (ret != Z_STREAM_END) { inflateEnd(&strm); return false; }
out.resize(strm.total_out);
inflateEnd(&strm);
return true;
}
bool gzipDecompressToTar(const std::string& inPath, std::vector<uint8_t>& tarData) {
return gzipDecompress(inPath, tarData);
}
bool gzipCompressToFile(const std::string& tarData, const std::string& outPath) {
std::vector<uint8_t> gz;
if (!gzipCompress(tarData, gz)) return false;
std::ofstream ofs(outPath, std::ios::binary);
if (!ofs) return false;
ofs.write(reinterpret_cast<const char*>(gz.data()), gz.size());
return ofs.good();
}
}
ArchiveManager::ArchiveManager() {}
bool ArchiveManager::pack(const std::string& folderPath, const std::string& archivePath) {
// TODO: Implement packing logic
return false;
// Use tar_to_stream to create tar in memory
std::ostringstream tarStream(std::ios::binary);
for (auto& p : fs::recursive_directory_iterator(folderPath)) {
if (!fs::is_regular_file(p)) continue;
std::ifstream ifs(p.path(), std::ios::binary);
if (!ifs) return false;
std::vector<char> data((std::istreambuf_iterator<char>(ifs)), {});
std::string uname = "root";
std::string gname = "root";
tar_to_stream_properties props = {
fs::relative(p.path(), folderPath).generic_string(),
std::as_bytes(std::span(data.data(), data.size())),
0u, "644", 0u, 0u, uname, gname
};
tar_to_stream(tarStream, std::move(props));
}
tar_to_stream_tail(tarStream);
std::string tarData = tarStream.str();
return gzipCompressToFile(tarData, archivePath);
}
bool ArchiveManager::unpack(const std::string& archivePath, const std::string& outDir) {
// TODO: Implement unpacking logic
return false;
std::vector<uint8_t> tarData;
if (!gzipDecompressToTar(archivePath, tarData)) return false;
return readTar(tarData, outDir);
}
bool ArchiveManager::readConfigJson(const std::string& archivePath, std::string& outJson) {
// TODO: Implement config extraction logic
return false;
std::vector<uint8_t> tarData;
if (!gzipDecompressToTar(archivePath, tarData)) return false;
return extractConfigJson(tarData, outJson);
}
bool ArchiveManager::writeConfigJson(const std::string& archivePath, const std::string& json) {
// TODO: Implement config writing logic
return false;
std::vector<uint8_t> tarData;
if (!gzipDecompressToTar(archivePath, tarData)) return false;
if (!replaceConfigJson(tarData, json)) return false;
// Write back to archivePath
std::string tarStr(reinterpret_cast<const char*>(tarData.data()), tarData.size());
return gzipCompressToFile(tarStr, archivePath);
}

View File

@ -173,7 +173,9 @@ int publish_tool(int argc, char* argv[]) {
std::cerr << "dropshell-tool-config.json not found in " << folder << std::endl;
return 1;
}
std::filesystem::path archivePath = std::filesystem::path(folder) / (labeltag + ".tgz");
std::filesystem::path archivePath = std::filesystem::path(home) / ".tmp" / (labeltag + ".tgz");
std::filesystem::create_directories(archivePath.parent_path());
ArchiveManager archiver;
if (!archiver.pack(folder, archivePath.string())) {
std::cerr << "Failed to create archive." << std::endl;

View File

@ -0,0 +1,100 @@
#pragma once
#include <span>
#include <string>
#include <string_view>
#include <cstring>
#include <cstdint>
struct tar_to_stream_properties {
/// Properties of the file to enter into the stream
std::string const &filename; /// name of the file to write
std::span<std::byte const> data; /// the location of the file's contents in memory
uint64_t mtime{0u}; /// file modification time, in seconds since epoch
std::string filemode{"644"}; /// file mode
unsigned int uid{0u}; /// file owner user ID
unsigned int gid{0u}; /// file owner group ID
std::string const &uname{"root"}; /// file owner username
std::string const &gname{"root"}; /// file owner group name
};
template<typename T>
void tar_to_stream(T &stream, /// stream to write to, e.g. ostream or ofstream
tar_to_stream_properties &&file) { /// properties of the file to enter into the stream
/// Read a "file" in memory, and write it as a TAR archive to the stream
struct { // offset
char name[100]{}; // 0 filename
char mode[8]{}; // 100 file mode: 0000644 etc
char uid[8]{}; // 108 user id, ascii representation of octal value: "0001750" (for UID 1000)
char gid[8]{}; // 116 group id, ascii representation of octal value: "0001750" (for GID 1000)
char size[12]{}; // 124 file size, ascii representation of octal value
char mtime[12]{"00000000000"}; // 136 modification time, seconds since epoch
char chksum[8]{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '}; // 148 checksum: six octal bytes followed by null and ' '. Checksum is the octal sum of all bytes in the header, with chksum field set to 8 spaces.
char typeflag{'0'}; // 156 '0'
char linkname[100]{}; // 157 null bytes when not a link
char magic[6]{'u', 's', 't', 'a', 'r', ' '}; // 257 format: Unix Standard TAR: "ustar ", not null-terminated
char version[2]{" "}; // 263 " "
char uname[32]{}; // 265 user name
char gname[32]{}; // 297 group name
char devmajor[8]{}; // 329 null bytes
char devminor[8]{}; // 337 null bytes
char prefix[155]{}; // 345 null bytes
char padding[12]{}; // 500 padding to reach 512 block size
} header; // 512
file.filemode.insert(file.filemode.begin(), 7 - file.filemode.length(), '0'); // zero-pad the file mode
std::strncpy(header.name, file.filename.c_str(), sizeof(header.name ) - 1); // leave one char for the final null
std::strncpy(header.mode, file.filemode.c_str(), sizeof(header.mode ) - 1);
std::strncpy(header.uname, file.uname.c_str(), sizeof(header.uname) - 1);
std::strncpy(header.gname, file.gname.c_str(), sizeof(header.gname) - 1);
sprintf(header.size, "%011lo", file.data.size());
sprintf(header.mtime, "%011llo", file.mtime);
sprintf(header.uid, "%07o", file.uid);
sprintf(header.gid, "%07o", file.gid);
{
unsigned int checksum_value = 0;
for(size_t i{0}; i != sizeof(header); ++i) {
checksum_value += reinterpret_cast<uint8_t*>(&header)[i];
}
sprintf(header.chksum, "%06o", checksum_value);
}
size_t const padding{(512u - file.data.size() % 512) & 511u};
stream << std::string_view{header.name, sizeof(header)}
<< std::string_view{reinterpret_cast<char const*>(file.data.data()), file.data.size()}
<< std::string(padding, '\0');
}
template<typename T>
[[deprecated("Use tar_to_stream_properties as argument: tar_to_stream(stream, {...}) - this allows use of designated initialisers and cleaner code. Refer to tar_to_stream's README for example usage")]]
void tar_to_stream(T &stream, /// stream to write to, e.g. ostream or ofstream
std::string const &filename, /// name of the file to write
char const *data_ptr, /// pointer to the data in this archive segment
size_t data_size, /// size of the data
uint64_t mtime = 0u, /// file modification time, in seconds since epoch
std::string filemode = "644", /// file mode
unsigned int uid = 0u, /// file owner user ID
unsigned int gid = 0u, /// file owner group ID
std::string const &uname = "root", /// file owner username
std::string const &gname = "root") { /// file owner group name
/// Explicit argument constructor, for backwards compatibility
tar_to_stream(stream, tar_to_stream_properties{
.filename{filename},
.data{std::as_bytes(std::span{data_ptr, data_size})},
.mtime{mtime},
.filemode{filemode},
.uid{uid},
.gid{gid},
.uname{uname},
.gname{gname},
});
}
template<typename T>
void tar_to_stream_tail(T &stream, unsigned int tail_length = 512u * 2u) {
/// TAR archives expect a tail of null bytes at the end - min of 512 * 2, but implementations often add more
stream << std::string(tail_length, '\0');
}

Binary file not shown.