Files
doos/src/cli.cpp
T

133 lines
4.5 KiB
C++
Raw Normal View History

#include "cli.hpp"
#include <ctime>
#include <iostream>
#include <optional>
#include "container.hpp"
#include "sandbox.hpp"
namespace doos {
namespace {
// Splits leading --options from the first positional. Everything after the positional is returned as-is.
struct Parsed {
std::optional<std::string> net;
bool yes = false;
std::string name;
std::vector<std::string> tail;
};
Parsed parse(const std::vector<std::string>& rest, bool allowNet, bool allowYes) {
Parsed p;
std::size_t i = 0;
for (; i < rest.size(); ++i) {
const std::string& a = rest[i];
if (a == "--") { ++i; break; }
if (allowNet && a == "--net") {
if (i + 1 >= rest.size()) throw std::runtime_error("--net needs a value (slirp, host, none)");
p.net = rest[++i];
} else if (allowNet && a.rfind("--net=", 0) == 0) {
p.net = a.substr(6);
} else if (allowYes && a == "--yes") {
p.yes = true;
} else if (a.size() > 1 && a[0] == '-') {
throw std::runtime_error("unknown option " + a);
} else {
break;
}
}
if (i >= rest.size()) throw std::runtime_error("missing container name");
p.name = rest[i++];
if (i < rest.size() && rest[i] == "--") ++i;
p.tail.assign(rest.begin() + static_cast<long>(i), rest.end());
return p;
}
std::string age(long created) {
long d = std::time(nullptr) - created;
if (d < 120) return std::to_string(d) + "s";
if (d < 7200) return std::to_string(d / 60) + "m";
if (d < 172800) return std::to_string(d / 3600) + "h";
return std::to_string(d / 86400) + "d";
}
} // namespace
void Cli::usage(std::FILE* out) {
std::fputs(
"doos " DOOS_VERSION " - user-level containers (overlayfs + namespaces)\n"
"\n"
"usage:\n"
" doos list\n"
" doos get_or_create [--net slirp|host|none] NAME\n"
" doos attach [--net slirp|host|none] NAME [-- CMD [ARGS...]]\n"
" doos delete [--yes] NAME\n"
"\n"
"attach creates NAME when missing. delete only reports unless --yes is given.\n"
"State lives in $DOOS_HOME or ~/.local/share/doos.\n",
out);
}
int Cli::run() {
if (args_.empty() || args_[0] == "-h" || args_[0] == "--help" || args_[0] == "help") {
usage(args_.empty() ? stderr : stdout);
return args_.empty() ? 2 : 0;
}
std::string cmd = args_[0];
for (auto& ch : cmd) if (ch == '-') ch = '_';
std::vector<std::string> rest(args_.begin() + 1, args_.end());
if (cmd == "--version" || cmd == "version") { std::puts(DOOS_VERSION); return 0; }
if (cmd == "list") return cmdList();
if (cmd == "get_or_create") return cmdGetOrCreate(rest);
if (cmd == "attach") return cmdAttach(rest);
if (cmd == "delete") return cmdDelete(rest);
std::fprintf(stderr, "doos: unknown command '%s'\n\n", args_[0].c_str());
usage(stderr);
return 2;
}
int Cli::cmdList() {
Store store = Store::fromEnvironment();
auto all = store.list();
if (all.empty()) { std::puts("no containers"); return 0; }
std::printf("%-*s %-8s %-6s %s\n", 20, "NAME", "STATE", "NET", "AGE");
for (const auto& c : all) {
std::printf("%-*s %-8s %-6s %s\n", 20, c.name().c_str(), c.runningPid() ? "running" : "stopped",
Network::name(c.net()).c_str(), age(c.created()).c_str());
}
return 0;
}
int Cli::cmdGetOrCreate(std::vector<std::string> rest) {
Parsed p = parse(rest, true, false);
if (!p.tail.empty()) throw std::runtime_error("get_or_create takes only a name");
std::optional<NetMode> net;
if (p.net) net = Network::parse(*p.net);
Store store = Store::fromEnvironment();
auto [c, created] = store.getOrCreate(p.name, net);
std::printf("%s %s\n", created ? "created" : "exists", c.name().c_str());
return 0;
}
int Cli::cmdAttach(std::vector<std::string> rest) {
Parsed p = parse(rest, true, false);
std::optional<NetMode> net;
if (p.net) net = Network::parse(*p.net);
Store store = Store::fromEnvironment();
auto [c, created] = store.getOrCreate(p.name, net);
(void)created;
return Sandbox(std::move(c), std::move(p.tail)).run();
}
int Cli::cmdDelete(std::vector<std::string> rest) {
Parsed p = parse(rest, false, true);
if (!p.tail.empty()) throw std::runtime_error("delete takes only a name");
Store store = Store::fromEnvironment();
std::puts(store.remove(p.name, p.yes).c_str());
return 0;
}
} // namespace doos