fix zip archives paths and add zip files test

- create_zip from a subdirectory wrote entry names with a leading
  '/', so the mounted archive looked empty
- memory_ostream did not support tellp(), so create_zip into a memory
  device wrote invalid offsets (-1) to the archive
- ZipFileDevice added missing parent directories for the first entry
  only
- ZipFileDevice::exists returned false for the archive root
- MemoryDevice::mkdirs recreated existing directories, dropping their
  content (mkdir("a/b") cleared "a"); overwriting a file duplicated its
  name in the directory listing

Added dev/tests/zip_files.lua: builds archives with file.create_zip
from memory device root, subdirectories and the real filesystem,
mounts them and checks reading, listing and read-only access.
memory_filesystem.lua got checks for the MemoryDevice issues.

Closes #544
This commit is contained in:
ostermine 2026-10-10 15:54:28 +03:00
parent 1dd1af1a72
commit fa5466f675
5 changed files with 170 additions and 3 deletions

View file

@ -180,6 +180,10 @@ std::unique_ptr<io::PathsGenerator> io::MemoryDevice::list(std::string_view path
}
io::MemoryDevice::Dir* io::MemoryDevice::createDir(std::string path) {
const auto& found = nodes.find(path);
if (found != nodes.end()) {
return found->second.get_if<Dir>();
}
io::path filePath = path;
io::path parent = filePath.parent();
auto parentDir = getDir(parent.string());
@ -195,6 +199,15 @@ io::MemoryDevice::Dir* io::MemoryDevice::createDir(std::string path) {
io::MemoryDevice::Node* io::MemoryDevice::createFile(
std::string path, util::Buffer<char>&& content
) {
const auto& found = nodes.find(path);
if (found != nodes.end()) {
auto& node = found->second;
if (!node.holds_alternative<File>()) {
return nullptr;
}
node.data = File {std::move(content)};
return &node;
}
io::path filePath = path;
io::path parent = filePath.parent();
auto dir = getDir(parent.string());

View file

@ -173,7 +173,13 @@ ZipFileDevice::ZipFileDevice(
entries[entry.fileName] = std::move(entry);
}
for (auto& [name, _] : entries) {
// Add missing parent directories entries
std::vector<std::string> names;
names.reserve(entries.size());
for (const auto& [name, _] : entries) {
names.push_back(name);
}
for (const auto& name : names) {
io::path path = name;
while (!(path = path.parent()).pathPart().empty()) {
@ -184,7 +190,6 @@ ZipFileDevice::ZipFileDevice(
entry.isDirectory = true;
entries[path.pathPart()] = entry;
}
break;
}
for (auto& [_, entry] : entries) {
@ -253,6 +258,9 @@ file_time_type ZipFileDevice::lastWriteTime(std::string_view path) {
}
bool ZipFileDevice::exists(std::string_view path) {
if (path.empty()) {
return true;
}
return entries.find(std::string(path)) != entries.end();
}
@ -441,7 +449,11 @@ static size_t write_zip(
void io::write_zip(const path& folder, const path& file) {
ByteBuilder central_dir;
auto out = io::write(file);
size_t entries = write_zip(folder.pathPart(), folder, *out, central_dir);
auto root = folder.pathPart();
if (!root.empty() && root.back() != '/') {
root += '/';
}
size_t entries = write_zip(root, folder, *out, central_dir);
size_t central_dir_offset = out->tellp();
out->write(reinterpret_cast<const char*>(central_dir.data()), central_dir.size());

View file

@ -50,6 +50,19 @@ protected:
return c;
}
pos_type seekoff(
off_type off,
std::ios_base::seekdir way,
std::ios_base::openmode mode = std::ios_base::out
) override {
// only position query (tellp) is supported
if (off == 0 && way == std::ios_base::cur &&
(mode & std::ios_base::out)) {
return pos_type(off_type(size()));
}
return pos_type(off_type(-1));
}
std::streamsize xsputn(const char* s, std::streamsize count) override {
const std::streamsize avail = epptr() - pptr();