zisofs: C++98/STL parsing library

zisofs is a compression format for files on ISO9660 file system. It has limited support across operating systems, mainly Linux kernel. Typically a directory tree is first preprocessed by mkzftree (from the zisofs-tools package before being turned into an ISO9660 image by mkisofs, genisoimage or similar tool. The data is zlib compressed.

The specification here describes the structure of a file that has been preprocessed by mkzftree, not of a full ISO9660 ziso. Data is not decompressed, as blocks with length 0 have a special meaning. Decompression and deconstruction of this data should be done outside of Kaitai Struct.

KS implementation details

License: CC0-1.0

References

This page hosts a formal specification of zisofs using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All parsing code for C++98/STL generated by Kaitai Struct depends on the C++/STL runtime library. You have to install it before you can parse data.

For C++, the easiest way is to clone the runtime library sources and build them along with your project.

Code

Using Kaitai Struct in C++/STL usually consists of 3 steps.

  1. We need to create an STL input stream (std::istream). One can open local file for that, or use existing std::string or char* buffer.
    #include <fstream>
    
    std::ifstream is("path/to/local/file.bin", std::ifstream::binary);
    
    #include <sstream>
    
    std::istringstream is(str);
    
    #include <sstream>
    
    const char buf[] = { ... };
    std::string str(buf, sizeof buf);
    std::istringstream is(str);
    
  2. We need to wrap our input stream into Kaitai stream:
    #include "kaitai/kaitaistream.h"
    
    kaitai::kstream ks(&is);
    
  3. And finally, we can invoke the parsing:
    zisofs_t data(&ks);
    

After that, one can get various attributes from the structure by invoking getter methods like:

data.block_pointers() // => The final pointer (`block_pointers[header.num_blocks]`) indicates the end
of the last block. Typically this is also the end of the file data.

C++98/STL source code to parse zisofs

zisofs.h

#ifndef ZISOFS_H_
#define ZISOFS_H_

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "kaitai/kaitaistruct.h"
#include <stdint.h>
#include <vector>

#if KAITAI_STRUCT_VERSION < 9000L
#error "Incompatible Kaitai Struct C++/STL API: version 0.9 or later is required"
#endif

/**
 * zisofs is a compression format for files on ISO9660 file system. It has
 * limited support across operating systems, mainly Linux kernel. Typically a
 * directory tree is first preprocessed by mkzftree (from the zisofs-tools
 * package before being turned into an ISO9660 image by mkisofs, genisoimage
 * or similar tool. The data is zlib compressed.
 * 
 * The specification here describes the structure of a file that has been
 * preprocessed by mkzftree, not of a full ISO9660 ziso. Data is not
 * decompressed, as blocks with length 0 have a special meaning. Decompression
 * and deconstruction of this data should be done outside of Kaitai Struct.
 * \sa https://web.archive.org/web/20200612093441/https://dev.lovelyhq.com/libburnia/web/-/wikis/zisofs Source
 */

class zisofs_t : public kaitai::kstruct {

public:
    class header_t;
    class block_t;

    zisofs_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, zisofs_t* p__root = 0);

private:
    void _read();
    void _clean_up();

public:
    ~zisofs_t();

    class header_t : public kaitai::kstruct {

    public:

        header_t(kaitai::kstream* p__io, zisofs_t* p__parent = 0, zisofs_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~header_t();

    private:
        bool f_block_size;
        int32_t m_block_size;

    public:
        int32_t block_size();

    private:
        bool f_num_blocks;
        int32_t m_num_blocks;

    public:

        /**
         * ceil(uncompressed_size / block_size)
         */
        int32_t num_blocks();

    private:
        std::string m_magic;
        uint32_t m_uncompressed_size;
        uint8_t m_len_header;
        uint8_t m_block_size_log2;
        std::string m_reserved;
        zisofs_t* m__root;
        zisofs_t* m__parent;

    public:
        std::string magic() const { return m_magic; }

        /**
         * Size of the original uncompressed file
         */
        uint32_t uncompressed_size() const { return m_uncompressed_size; }

        /**
         * header_size >> 2 (currently 4)
         */
        uint8_t len_header() const { return m_len_header; }
        uint8_t block_size_log2() const { return m_block_size_log2; }
        std::string reserved() const { return m_reserved; }
        zisofs_t* _root() const { return m__root; }
        zisofs_t* _parent() const { return m__parent; }
    };

    class block_t : public kaitai::kstruct {

    public:

        block_t(uint32_t p_ofs_start, uint32_t p_ofs_end, kaitai::kstream* p__io, zisofs_t* p__parent = 0, zisofs_t* p__root = 0);

    private:
        void _read();
        void _clean_up();

    public:
        ~block_t();

    private:
        bool f_len_data;
        int32_t m_len_data;

    public:
        int32_t len_data();

    private:
        bool f_data;
        std::string m_data;

    public:
        std::string data();

    private:
        uint32_t m_ofs_start;
        uint32_t m_ofs_end;
        zisofs_t* m__root;
        zisofs_t* m__parent;

    public:
        uint32_t ofs_start() const { return m_ofs_start; }
        uint32_t ofs_end() const { return m_ofs_end; }
        zisofs_t* _root() const { return m__root; }
        zisofs_t* _parent() const { return m__parent; }
    };

private:
    bool f_blocks;
    std::vector<block_t*>* m_blocks;

public:
    std::vector<block_t*>* blocks();

private:
    header_t* m_header;
    std::vector<uint32_t>* m_block_pointers;
    zisofs_t* m__root;
    kaitai::kstruct* m__parent;
    std::string m__raw_header;
    kaitai::kstream* m__io__raw_header;

public:
    header_t* header() const { return m_header; }

    /**
     * The final pointer (`block_pointers[header.num_blocks]`) indicates the end
     * of the last block. Typically this is also the end of the file data.
     */
    std::vector<uint32_t>* block_pointers() const { return m_block_pointers; }
    zisofs_t* _root() const { return m__root; }
    kaitai::kstruct* _parent() const { return m__parent; }
    std::string _raw_header() const { return m__raw_header; }
    kaitai::kstream* _io__raw_header() const { return m__io__raw_header; }
};

#endif  // ZISOFS_H_

zisofs.cpp

// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

#include "zisofs.h"
#include "kaitai/exceptions.h"

zisofs_t::zisofs_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, zisofs_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = this;
    m_header = 0;
    m__io__raw_header = 0;
    m_block_pointers = 0;
    m_blocks = 0;
    f_blocks = false;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void zisofs_t::_read() {
    m__raw_header = m__io->read_bytes(16);
    m__io__raw_header = new kaitai::kstream(m__raw_header);
    m_header = new header_t(m__io__raw_header, this, m__root);
    m_block_pointers = new std::vector<uint32_t>();
    const int l_block_pointers = (header()->num_blocks() + 1);
    for (int i = 0; i < l_block_pointers; i++) {
        m_block_pointers->push_back(m__io->read_u4le());
    }
}

zisofs_t::~zisofs_t() {
    _clean_up();
}

void zisofs_t::_clean_up() {
    if (m__io__raw_header) {
        delete m__io__raw_header; m__io__raw_header = 0;
    }
    if (m_header) {
        delete m_header; m_header = 0;
    }
    if (m_block_pointers) {
        delete m_block_pointers; m_block_pointers = 0;
    }
    if (f_blocks) {
        if (m_blocks) {
            for (std::vector<block_t*>::iterator it = m_blocks->begin(); it != m_blocks->end(); ++it) {
                delete *it;
            }
            delete m_blocks; m_blocks = 0;
        }
    }
}

zisofs_t::header_t::header_t(kaitai::kstream* p__io, zisofs_t* p__parent, zisofs_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    f_block_size = false;
    f_num_blocks = false;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void zisofs_t::header_t::_read() {
    m_magic = m__io->read_bytes(8);
    if (!(magic() == std::string("\x37\xE4\x53\x96\xC9\xDB\xD6\x07", 8))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x37\xE4\x53\x96\xC9\xDB\xD6\x07", 8), magic(), _io(), std::string("/types/header/seq/0"));
    }
    m_uncompressed_size = m__io->read_u4le();
    m_len_header = m__io->read_u1();
    if (!(len_header() == 4)) {
        throw kaitai::validation_not_equal_error<uint8_t>(4, len_header(), _io(), std::string("/types/header/seq/2"));
    }
    m_block_size_log2 = m__io->read_u1();
    if (!( ((block_size_log2() == 15) || (block_size_log2() == 16) || (block_size_log2() == 17)) )) {
        throw kaitai::validation_not_any_of_error<uint8_t>(block_size_log2(), _io(), std::string("/types/header/seq/3"));
    }
    m_reserved = m__io->read_bytes(2);
    if (!(reserved() == std::string("\x00\x00", 2))) {
        throw kaitai::validation_not_equal_error<std::string>(std::string("\x00\x00", 2), reserved(), _io(), std::string("/types/header/seq/4"));
    }
}

zisofs_t::header_t::~header_t() {
    _clean_up();
}

void zisofs_t::header_t::_clean_up() {
}

int32_t zisofs_t::header_t::block_size() {
    if (f_block_size)
        return m_block_size;
    m_block_size = (1 << block_size_log2());
    f_block_size = true;
    return m_block_size;
}

int32_t zisofs_t::header_t::num_blocks() {
    if (f_num_blocks)
        return m_num_blocks;
    m_num_blocks = ((uncompressed_size() / block_size()) + ((kaitai::kstream::mod(uncompressed_size(), block_size()) != 0) ? (1) : (0)));
    f_num_blocks = true;
    return m_num_blocks;
}

zisofs_t::block_t::block_t(uint32_t p_ofs_start, uint32_t p_ofs_end, kaitai::kstream* p__io, zisofs_t* p__parent, zisofs_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = p__root;
    m_ofs_start = p_ofs_start;
    m_ofs_end = p_ofs_end;
    f_len_data = false;
    f_data = false;

    try {
        _read();
    } catch(...) {
        _clean_up();
        throw;
    }
}

void zisofs_t::block_t::_read() {
}

zisofs_t::block_t::~block_t() {
    _clean_up();
}

void zisofs_t::block_t::_clean_up() {
    if (f_data) {
    }
}

int32_t zisofs_t::block_t::len_data() {
    if (f_len_data)
        return m_len_data;
    m_len_data = (ofs_end() - ofs_start());
    f_len_data = true;
    return m_len_data;
}

std::string zisofs_t::block_t::data() {
    if (f_data)
        return m_data;
    kaitai::kstream *io = _root()->_io();
    std::streampos _pos = io->pos();
    io->seek(ofs_start());
    m_data = io->read_bytes(len_data());
    io->seek(_pos);
    f_data = true;
    return m_data;
}

std::vector<zisofs_t::block_t*>* zisofs_t::blocks() {
    if (f_blocks)
        return m_blocks;
    m_blocks = new std::vector<block_t*>();
    const int l_blocks = header()->num_blocks();
    for (int i = 0; i < l_blocks; i++) {
        m_blocks->push_back(new block_t(block_pointers()->at(i), block_pointers()->at((i + 1)), m__io, this, m__root));
    }
    f_blocks = true;
    return m_blocks;
}