// file : bbot/machine-manifest.cxx -*- C++ -*- // license : MIT; see accompanying LICENSE file #include #include #include #include #include #include using namespace std; 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) : machine_header_manifest (p, move (nv), manifest_unknown_mode::stop, &nv) { auto bad_name = [&p, &nv] (const string& d) { throw parsing (p.name (), nv.name_line, nv.name_column, d); }; // Offsets are used to tie an error to the specific position inside a // manifest value (possibly a multiline one). // auto bad_value = [&p, &nv] ( const string& d, uint64_t column_offset = 0, uint64_t line_offset = 0) { throw parsing (p.name (), nv.value_line + line_offset, (line_offset == 0 ? nv.value_column : 1) + column_offset, d); }; optional type; for (; !nv.empty (); nv = p.next ()) { string& n (nv.name); string& v (nv.value); 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"); // @@ Should we check that the value is a valid mac? // mac = move (v); } else if (n == "options") { if (options) bad_name ("machine options redefinition"); strings op; // Note that when reporting errors we combine the manifest value // position with the respective error position. // try { istringstream is (v); tab_parser parser (is, ""); tab_fields tl; while (!(tl = parser.next ()).empty ()) { for (auto& tf: tl) op.emplace_back (move (tf.value)); } } catch (const tab_parsing& e) { bad_value ("invalid machine options: " + e.description, e.column - 1, e.line - 1); } if (op.empty ()) bad_value ("empty machine options"); options = move (op); } else if (n == "changes") { if (v.empty ()) bad_value ("empty machine changes"); changes.emplace_back (move (v)); } else if (!iu) bad_name ("unknown name '" + n + "' in machine manifest"); } // Verify all non-optional values were 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? // machine_header_manifest::serialize (s, false); 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); } for (const string& c: changes) s.next ("changes", c); 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"); if (machine.effective_role () == machine_role::build) { 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"); } else { // Make sure this is the end. // nv = p.next (); if (!nv.empty ()) throw parsing (p.name (), nv.name_line, nv.name_column, "single 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); if (machine.effective_role () == machine_role::build) { toolchain.serialize (s); bootstrap.serialize (s); } s.next ("", ""); // End of stream. } }