https://codeberg.org/interpeer/vessel.git
Tip revision: 5c7a39214ce9b5396b7aafb9d576b159b57ef04a authored by Jens Finkhaeuser on 09 January 2024, 12:10:19 UTC
(void) and () have different meanings in C
(void) and () have different meanings in C
Tip revision: 5c7a392
main.cpp
/*
* This file is part of vessel.
*
* Author(s): Jens Finkhaeuser <jens@finkhaeuser.de>
*
* Copyright (c) 2023 Interpeer gUG (haftungsbeschränkt).
*
* SPDX-License-Identifier: GPL-3.0-only
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <iostream>
#include <fstream>
#include <clipp.h>
#include <vessel.h>
#include "options.h"
#include "context.h"
#include "errors.h"
#include "commands.h"
namespace vessel::cli {
inline clipp::parameter
filename(std::string const & label, std::filesystem::path & path, bool required = true)
{
auto func = [&](char const * arg)
{
path = arg;
};
if (required) {
return clipp::value(label).call(func);
}
return clipp::opt_value(label).call(func);
}
inline clipp::parameter
filenames(std::string const & label, std::vector<std::filesystem::path> & paths, bool required = true)
{
auto func = [&](char const * arg)
{
paths.push_back(arg);
};
if (required) {
return clipp::values(label).call(func);
}
return clipp::opt_values(label).call(func);
}
static constexpr char const * intro =
" This command line utility allows you to create, inspect and manipulate\n"
" vessel resources.\n\n"
" Vessel defines a generic container format for encapsulating arbitrary\n"
" data - you can use it to wrap entire files or as a basis for your own\n"
" storage format. Vessel provides authenticated and encrypted storage,\n"
" chunking of storage files, multiplexing separate data streams, etc.\n\n"
" The effect is that vessel resources can be securely distributed via\n"
" any transport medium, from sneakernet to the internet.\n"
;
VESSEL_PRIVATE
void
parse_cli(options & opts, int argc, char **argv)
{
using namespace clipp;
auto resource = filename("resource", opts.resource_file)
.doc("The resource to create, inspect or manipulate.");
group commands;
commands.scoped(false).exclusive(true);
commands.push_back(
"Import command" %
(command("import")
.set(opts.command, CMD_IMPORT)
.doc("Imports an arbitrary file into a vessel resource. If no input "
"file is provided, data to import is read from standard input.")
, resource
, filenames("input file", opts.input_files, false)
.doc("File(s) to import.")
, (
(option("-a", "--append")
.set(opts.import_append))
.doc("If set, appends the file to an existing resource, "
"otherwise the command overwrites the existing resource. "
"If no resource exists yet, the option makes no difference.")
, (option("-m", "--mime-type")
& value("type", opts.mime_type))
.doc("If the input file MIME type cannot be detected, set it "
"with this option. Defaults to '" + opts.mime_type + "'. "
"Note that when importing multiple files of different types, "
"this option forces all files to be of the same type. It's "
"better to use --append in that case and add files one by one.")
, (option("-s", "--extent-size-multiplier")
& number("size", opts.extent_size_multiplier))
.doc("Extents are 4096 bytes in size times this multiplier; "
"the default multiplier is: "
+ std::to_string(opts.extent_size_multiplier))
, (option("-t", "--topic")
& number("topic", opts.topic))
.doc("If specified, the topic to give to sections containing "
"the imported file's data. If none is given or a topic below "
"1024 is provided, a random topic above 1024 is chosen. "
"If multiple input files are provided on the command line, "
"each is placed into a separate topic with the topic number "
"incremented from the one set here (or randomly chosen).")
)
)
);
commands.push_back(
"Export command" %
(command("export")
.set(opts.command, CMD_EXPORT)
.doc("Export a file (BLOB sections) from a resource and/or topic.\n"
"\n"
"Resources can contain many topics, and each of them can contain "
"multiple files. If neither --topic nor --all are given, the command "
"exports the first file it detects and exits.\n"
"\n"
"If --topic is given, all files from that topic are exported. If "
"--all is provided, all files from all topics are exported."
)
, resource
, (
option("-a", "--all")
.set(opts.export_all)
.doc("If provided, export all files from the topic(s), otherwise stop "
"after the first file.")
, (option("-t", "--topic")
& number("topic", opts.topic))
.doc("If specified, export only the file(s) from this topic, otherwise "
"process all topics.")
, (option("-o", "--output-name")
& value("filename", opts.export_filename))
.doc("Use this output file name instead of the name provided in the "
"content type. If '-' is given, write to stdout.")
)
)
);
commands.push_back(
"Info command" %
(command("info")
.set(opts.command, CMD_INFO)
.doc("Displays information about a vessel resource.")
, resource
, (
option("-f", "--full-ids")
.set(opts.full_ids)
.doc("If set, does not truncate extent and author identifiers.")
, option("-s", "--sections")
.set(opts.sections)
.doc("If set, also display information about individual sections "
"in the resource.")
)
)
);
commands.push_back(
"List command" %
(command("ls")
.set(opts.command, CMD_LS)
.doc("Scans a resource for embedded files, and displays information about them.")
, resource
)
);
bool help = false;
bool version = false;
auto global_opts = "Global Options" % (
option("-?", "--help")
.set(help)
.doc("Display this help.")
, option("-v", "--version")
.set(version)
.doc("Display version information and exit.")
);
auto shared_opts = "Shared Options" % (
(option("--algorithms")
& value("specs", opts.algorithms))
.doc("Specify the combination of (cryptograhic) algorithms to use for"
" this resource. Defaults to '" + opts.algorithms + "'.")
, (option("-k", "--author-key")
& filename("file", opts.author_filename))
.doc("A file containing an authoring key pair such as e.g. generated "
"by OpenSSL, etc.")
, option("--verbose")
.set(opts.verbose)
.doc("Verbose output; the interpration of this depends on the command.")
);
// TODO
// "used by many commands"
// - encrypt or not
auto cli = (global_opts, shared_opts, commands);
auto result = parse(argc, argv, cli);
#if 0
// Parameter debugging
auto doc_label = [](parameter const & p) {
if (!p.flags().empty()) {
return p.flags().front();
}
if (!p.label().empty()) {
return p.label();
}
return doc_string{"<?>"};
};
for (auto const & m : result) {
std::cerr << "#" << m.index() << " " << m.arg() << " -> ";
auto p = m.param();
if (p) {
std::cerr << doc_label(*p) << " \t";
if (m.repeat() > 0) {
std::cerr << (m.bad_repeat() ? "[bad repeat " : "[repeat ")
<< m.repeat() << "]";
}
if (m.blocked()) {
std::cerr << " [blocked]";
}
if (m.conflict()) {
std::cerr << " [conflict]";
}
std::cerr << std::endl;
}
else {
std::cerr << " [unmapped]";
}
}
//cout << "missing parameters:\n";
//for(const auto& m : result.missing()) {
// auto p = m.param();
// if(p) {
// os << doc_label(*p) << " \t";
// os << " [missing after " << m.after_index() << "]\n";
// }
//}
#endif
// Help
if (!result || help) {
auto fmt = doc_formatting{}
.first_column(3)
.last_column(79);
exit_log(VEXIT_HELP)
<< make_man_page(cli, argv[0], fmt)
.prepend_section("INTRODUCTION", intro)
.prepend_section("VERSION INFORMATION", vessel_copyright_string())
;
}
// FIXME version alone does not work at the moment
// Version
if (version) {
exit_log(VEXIT_OK, std::cout) << vessel_copyright_string();
}
}
VESSEL_PRIVATE
void
initialize_context(context & ctx, options const & opts)
{
// Options
ctx.opts = opts;
// Algorithms
// TODO if none was given, create the algorithm context from one of the
// plugins - or always do that, even if one was given?
ctx.algos = vessel_algorithm_choices_create(opts.algorithms.c_str());
if (!ctx.algos.version_tag) {
exit_log(VEXIT_ALGOS) << "Bad algorithm choices: " << opts.algorithms;
}
auto err = vessel_algorithm_context_create_from_choices(&ctx.algo_ctx,
&(ctx.algos), 1);
if (VESSEL_ERR_SUCCESS != err) {
exit_log(VEXIT_ALGOS, err) << "Error creating algorithm context.";
}
// Context
err = vessel_context_create(&ctx.ctx, ctx.algo_ctx);
if (VESSEL_ERR_SUCCESS != err) {
exit_log(VEXIT_INIT, err) << "Error creating context.";
}
// Author key
if (!opts.author_filename.empty()) {
std::ifstream keyfile;
keyfile.open(opts.author_filename);
if (!keyfile.is_open()) {
exit_log(VEXIT_AUTHOR) << "Could not open author key file: " << opts.author_filename;
}
std::vector<char> keybuf;
keybuf.resize(4096); // Assume this is enough.
keyfile.read(&keybuf[0], keybuf.size());
keybuf.resize(keyfile.gcount());
if (!keybuf.size()) {
exit_log(VEXIT_AUTHOR) << "Author key file was empty!";
}
err = vessel_author_from_buffer(&ctx.author, &ctx.algos,
keybuf.data(), keybuf.size());
if (VESSEL_ERR_SUCCESS != err) {
exit_log(VEXIT_AUTHOR, err) << "Could not create author from key file.";
}
// Author ID
err = vessel_author_id_from_author(&ctx.author_id, &ctx.algos, ctx.author);
if (VESSEL_ERR_SUCCESS != err) {
exit_log(VEXIT_AUTHOR, err) << "Could not create author ID without author.";
}
}
}
} // namsepace vessel::cli
int main(int argc, char **argv)
{
using namespace vessel::cli;
// Parse CLI options
options opts;
parse_cli(opts, argc, argv);
// Try to initialize context from parsed options
context ctx;
initialize_context(ctx, opts);
// Find (and run) command
auto iter = command_registry.find(opts.command);
if (iter == command_registry.end() || !iter->second) {
exit_log(VEXIT_NO_CMD) << "Command not implemented!";
}
return iter->second(ctx);
}