Files
genode-world/src/server/lz_rom/main.cc
Emery Hemingway 7267d88760 server/lz_rom: decompress lzipped files to ROM sessions
See 'src/server/lz_rom/README' and 'run/lz_rom_noux.run' for more
information.

Fix #70
2017-04-19 11:51:33 +02:00

333 lines
8.1 KiB
C++

/*
* \brief ROM Lzlip decompressor
* \author Emery Hemingway
* \date 2017-04-10
*/
/*
* Copyright (C) 2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
/*
* TODO: Multithreaded decompression.
*/
/* Genode includes */
#include <os/session_policy.h>
#include <rom_session/connection.h>
#include <base/attached_ram_dataspace.h>
#include <base/attached_rom_dataspace.h>
#include <base/heap.h>
#include <base/service.h>
#include <base/session_label.h>
#include <libc/component.h>
#include <base/log.h>
namespace {
using namespace Genode;
/* Lzlib includes */
#include <lzlib.h>
}
namespace Lz_rom {
using namespace Genode;
struct Session;
struct Main;
typedef Session_state::Args Args;
struct File_error { };
struct Decompression_error { };
}
struct Lz_rom::Session :
Genode::Rpc_object<Genode::Rom_session>,
Genode::Parent::Server
{
Parent::Client parent_client;
Id_space<Parent::Server>::Element server_id;
Session(Id_space<Parent::Server> &server_space,
Parent::Server::Id server_id,
Libc::Env &env, Genode::Allocator &alloc,
Session_label const &label,
LZ_Decoder *decoder);
Attached_ram_dataspace ram_ds;
/***************************
** ROM session interface **
***************************/
Rom_dataspace_capability dataspace() override
{
Genode::Dataspace_capability ds_cap = ram_ds.cap();
return static_cap_cast<Rom_dataspace>(ds_cap);
}
void sigh(Signal_context_capability sigh) override { }
};
Lz_rom::Session::Session(Id_space<Parent::Server> &server_space,
Parent::Server::Id server_id,
Libc::Env &env, Genode::Allocator &alloc,
Session_label const &label,
LZ_Decoder *decoder)
:
server_id(*this, server_space, server_id),
ram_ds(env.ram(), env.rm(), 0)
{
using namespace Vfs;
typedef Vfs::Directory_service::Stat_result Stat_result;
typedef Vfs::Directory_service::Open_result Open_result;
typedef Vfs::File_io_service::Read_result Read_result;
/* Get file size */
Vfs::Directory_service::Stat stat;
if (env.vfs().stat(label.string(), stat) != Stat_result::STAT_OK)
throw File_error();
if (!stat.size)
throw File_error();
/* Open file */
Vfs_handle *fh;
Open_result res = env.vfs().open(
label.string(), Vfs::Directory_service::OPEN_MODE_RDONLY, &fh, alloc);
if (res != Open_result::OPEN_OK)
throw File_error();
Vfs_handle::Guard handle_guard(fh);
size_t compressed_size = stat.size;
size_t uncompressed_size;
/* read the uncompressed file size from the end of the file */
fh->seek(stat.size - 16);
{
uint64_t data_size = 0;
file_size n = 0;
Read_result res = fh->fs().read(fh, (char*)&data_size, sizeof(data_size), n);
if (res != Read_result::READ_OK || n != sizeof(data_size))
throw File_error();
/* XXX: little-endian only */
uncompressed_size = data_size;
}
if (uncompressed_size == 0)
throw File_error();
/*
* TODO: check the compressed member size
* to see if this is a concatenated file
*/
LZ_decompress_reset(decoder);
/* Allocate the ROM buffer now that the size is known */
ram_ds.realloc(&env.ram(), uncompressed_size);
uint8_t *rom_buf = ram_ds.local_addr<uint8_t>();
/* Page aligned size of ROM dataspace */
size_t const rom_size = ram_ds.size();
/* Offset of encoded data*/
size_t enc_off = rom_size - compressed_size;
/* Offset of decoded data */
size_t dec_off = 0;
/* Read the compressed data into the back of the ROM dataspace */
{
file_size read_off = enc_off;
file_size read_len = compressed_size;
fh->seek(0);
while (read_off < rom_size) {
file_size n = 0;
Read_result res = fh->fs().read(
fh, (char*)(rom_buf+read_off), read_len, n);
if (res != Read_result::READ_OK)
throw File_error();
fh->advance_seek(n);
read_len -= n;
read_off += n;
}
}
/* Decode from the back of the dataspace to the front */
while (dec_off < uncompressed_size) {
if (enc_off < rom_size) {
int write_size = min(LZ_decompress_write_size(decoder),
int(rom_size - enc_off));
/* write to the decoder */
write_size = LZ_decompress_write(decoder, rom_buf+enc_off, write_size);
if (write_size < 0)
throw Decompression_error();
enc_off += write_size;
}
/* read from the decoder */
int read_size = LZ_decompress_read(
decoder, rom_buf+dec_off, uncompressed_size-dec_off);
if (read_size < 0)
throw Decompression_error();
dec_off += read_size;
}
/* Sweep the crumbs out of the page boundry gap */
memset(rom_buf+uncompressed_size, 0x00, rom_size - uncompressed_size);
LZ_decompress_finish(decoder);
}
struct Lz_rom::Main
{
Id_space<Parent::Server> server_id_space;
Libc::Env &env;
Attached_rom_dataspace config_rom { env, "config" };
Attached_rom_dataspace session_requests { env, "session_requests" };
Sliced_heap session_alloc { env.ram(), env.rm() };
Heap vfs_alloc { env.ram(), env.rm() };
bool config_stale = false;
void handle_config() {
config_stale = true; }
void handle_session_request(Xml_node request);
void handle_session_requests()
{
if (config_stale) {
config_rom.update();
config_stale = false;
}
session_requests.update();
Xml_node const requests = session_requests.xml();
requests.for_each_sub_node([&] (Xml_node request) {
handle_session_request(request);
});
}
Signal_handler<Main> config_handler {
env.ep(), *this, &Main::handle_config };
Signal_handler<Main> session_request_handler {
env.ep(), *this, &Main::handle_session_requests };
LZ_Decoder *decoder = LZ_decompress_open();
Main(Libc::Env &env) : env(env)
{
config_rom.sigh(config_handler);
session_requests.sigh(session_request_handler);
/* handle requests that have queued before or during construction */
handle_session_requests();
}
~Main()
{
LZ_decompress_close(decoder);
}
};
void Lz_rom::Main::handle_session_request(Xml_node request)
{
if (!request.has_attribute("id"))
return;
Id_space<Parent::Server>::Id const server_id {
request.attribute_value("id", 0UL) };
if (request.has_type("create")) {
if (!request.has_sub_node("args"))
return;
typedef Session_state::Args Args;
Args const args = request.sub_node("args").decoded_content<Args>();
Session_label const request_label =
label_from_args(args.string()).last_element();
char new_label[request_label.capacity()];
snprintf(new_label, sizeof(new_label), "/%s.lz", request_label.string());
Session_label const filename(new_label);
try {
Session *session = new (session_alloc)
Session(server_id_space, server_id, env, vfs_alloc, filename, decoder);
env.parent().deliver_session_cap(
server_id, env.ep().manage(*session));
return;
} catch (File_error) {
log("failed to open or read file '", filename, "'");
} catch (Decompression_error) {
char const *msg = "";
switch (LZ_decompress_errno(decoder)) {
case LZ_ok:
error("no error"); break;
case LZ_bad_argument:
msg = "at least one of the arguments passed to the library function was invalid"; break;
case LZ_mem_error:
msg = "no memory available"; break;
case LZ_sequence_error:
msg = "a library function was called in the wrong order"; break;
case LZ_header_error:
msg = "an invalid member header was read"; break;
case LZ_unexpected_eof:
msg = "the end of the data stream was reached in the middle of a member"; break;
case LZ_data_error:
msg = "the data stream is corrupt"; break;
case LZ_library_error:
msg = "a bug was detected in the library"; break;
}
error("failed to decompress '", filename, "', ", msg);
} catch (...) { }
env.parent().session_response(server_id, Parent::INVALID_ARGS);
}
if (request.has_type("close")) {
server_id_space.apply<Session>(server_id, [&] (Session &session) {
destroy(session_alloc, &session);
env.parent().session_response(server_id, Parent::SESSION_CLOSED);
});
}
}
/***************
** Component **
***************/
void Libc::Component::construct(Libc::Env &env)
{
Libc::with_libc([&] () {
static Lz_rom::Main inst(env);
env.parent().announce("ROM");
});
}