// file : bbot/machine-manifest.cxx -*- C++ -*- // copyright : Copyright (c) 2014-2017 Code Synthesis Ltd // license : TBC; see accompanying LICENSE file #include #include #include #include using namespace butl; namespace bbot { using parser = manifest_parser; using parsing = manifest_parsing; using serializer = manifest_serializer; using serialization = manifest_serialization; using name_value = manifest_name_value; // machine_type // string to_string (machine_type t) { switch (t) { case machine_type::kvm: return "kvm"; case machine_type::nspawn: return "nspawn"; } assert (false); return string (); } machine_type to_machine_type (const string& t) { if (t == "kvm") return machine_type::kvm; else if (t == "nspawn") return machine_type::nspawn; else throw invalid_argument ("invalid machine type '" + t + "'"); } // machine_manifest // machine_manifest:: machine_manifest (parser& p, bool iu) : machine_manifest (p, p.next (), iu) { // Make sure this is the end. // name_value nv (p.next ()); if (!nv.empty ()) throw parsing (p.name (), nv.name_line, nv.name_column, "single machine manifest expected"); } machine_manifest:: machine_manifest (parser& p, name_value nv, bool iu) { auto bad_name = [&p, &nv] (const string& d) { throw parsing (p.name (), nv.name_line, nv.name_column, d); }; auto bad_value = [&p, &nv] (const string& d, size_t offset = 0) { throw parsing (p.name (), nv.value_line, nv.value_column + offset, d); }; // Make sure this is the start and we support the version. // if (!nv.name.empty ()) bad_name ("start of machine manifest expected"); if (nv.value != "1") bad_value ("unsupported format version"); optional type; for (nv = p.next (); !nv.empty (); nv = p.next ()) { string& n (nv.name); string& v (nv.value); if (n == "id") { if (!id.empty ()) bad_name ("machine id redefinition"); if (v.empty ()) bad_value ("empty machine id"); id = move (v); } else if (n == "name") { if (!name.empty ()) bad_name ("machine name redefinition"); if (v.empty ()) bad_value ("empty machine name"); name = move (v); } else if (n == "summary") { if (!summary.empty ()) bad_name ("machine summary redefinition"); if (v.empty ()) bad_value ("empty machine summary"); summary = move (v); } else if (n == "type") { if (type) bad_name ("machine type redefinition"); try { type = to_machine_type (v); } catch (const invalid_argument&) { bad_value ("invalid machine type"); } } else if (n == "mac") { if (mac) bad_name ("machine mac redefinition"); mac = move (v); } else if (n == "options") { if (options) bad_name ("machine options redefinition"); strings op; try { op = string_parser::parse_quoted (v, false); } catch (const invalid_string& e) { bad_value (string ("invalid machine options: ") + e.what (), e.position); } if (op.empty ()) bad_value ("empty machine options"); options = move (op); } else if (!iu) bad_name ("unknown name '" + n + "' in machine manifest"); } // Verify all non-optional values were specified. // if (id.empty ()) bad_value ("no machine id specified"); if (name.empty ()) bad_value ("no machine name specified"); if (summary.empty ()) bad_value ("no machine summary specified"); if (!type) bad_value ("no machine type specified"); this->type = *type; } void machine_manifest:: serialize (serializer& s) const { // @@ Should we check that all non-optional values are specified and all // values are valid? // s.next ("", "1"); // Start of manifest. s.next ("id", id); s.next ("name", name); s.next ("summary", summary); s.next ("type", to_string (type)); if (mac) s.next ("mac", *mac); // Recompose options string as a space-separated option list, // if (options) { string v; for (auto b (options->cbegin ()), i (b), e (options->cend ()); i != e; ++i) { if (i != b) v += ' '; v += *i; } s.next ("options", v); } s.next ("", ""); // End of manifest. } strings machine_manifest:: unquoted_options () const { return options ? string_parser::unquote (*options) : strings (); } // toolchain_manifest // toolchain_manifest:: toolchain_manifest (parser& p, bool iu) : toolchain_manifest (p, p.next (), iu) { // Make sure this is the end. // name_value nv (p.next ()); if (!nv.empty ()) throw parsing (p.name (), nv.name_line, nv.name_column, "single toolchain manifest expected"); } toolchain_manifest:: toolchain_manifest (parser& p, name_value nv, bool iu) { auto bad_name = [&p, &nv] (const string& d) { throw parsing (p.name (), nv.name_line, nv.name_column, d); }; auto bad_value = [&p, &nv] (const string& d) { throw parsing (p.name (), nv.value_line, nv.value_column, d); }; // Make sure this is the start and we support the version. // if (!nv.name.empty ()) bad_name ("start of toolchain manifest expected"); if (nv.value != "1") bad_value ("unsupported format version"); // Parse the toolchain manifest. // for (nv = p.next (); !nv.empty (); nv = p.next ()) { string& n (nv.name); string& v (nv.value); if (n == "id") { if (!id.empty ()) bad_name ("toolchain id redefinition"); if (v.empty ()) bad_value ("empty toolchain id"); id = move (v); } else if (!iu) bad_name ("unknown name '" + n + "' in toolchain manifest"); } // Verify all non-optional values were specified. // if (id.empty ()) bad_value ("no toolchain id specified"); } void toolchain_manifest:: serialize (serializer& s) const { // @@ Should we check that all non-optional values are specified? // s.next ("", "1"); // Start of manifest. s.next ("id", id); s.next ("", ""); // End of manifest. } // bootstrapped_machine_manifest // bootstrapped_machine_manifest:: bootstrapped_machine_manifest (parser& p, bool iu) { name_value nv (p.next ()); auto bad_name = [&p, &nv] (const string& d) { throw parsing (p.name (), nv.name_line, nv.name_column, d); }; auto bad_value = [&p, &nv] (const string& d) { throw parsing (p.name (), nv.value_line, nv.value_column, d); }; // Make sure this is the start and we support the version. // if (!nv.name.empty ()) bad_name ("start of bootstrapped machine manifest expected"); if (nv.value != "1") bad_value ("unsupported format version"); // Parse the bootstrapped machine manifest. Currently there is no values // expected. // for (nv = p.next (); !nv.empty (); nv = p.next ()) { if (!iu) bad_name ("unknown name '" + nv.name + "' in bootstrapped machine manifest"); } nv = p.next (); if (nv.empty ()) bad_value ("machine manifest expected"); machine = machine_manifest (p, nv, iu); if (!machine.mac) bad_name ("mac address must be present in machine manifest"); nv = p.next (); if (nv.empty ()) bad_value ("toolchain manifest expected"); toolchain = toolchain_manifest (p, nv, iu); nv = p.next (); if (nv.empty ()) bad_value ("bootstrap manifest expected"); bootstrap = bootstrap_manifest (p, nv, iu); // Make sure this is the end. // nv = p.next (); if (!nv.empty ()) throw parsing (p.name (), nv.name_line, nv.name_column, "single bootstrapped machine manifest expected"); } void bootstrapped_machine_manifest:: serialize (serializer& s) const { // @@ Should we check that all non-optional values are specified? // s.next ("", "1"); // Start of manifest. s.next ("", ""); // End of manifest. if (!machine.mac) throw serialization (s.name (), "mac address must be present in machine manifest"); machine.serialize (s); toolchain.serialize (s); bootstrap.serialize (s); s.next ("", ""); // End of stream. } }