# # # delete "automate.hh" # # patch "ChangeLog" # from [4d27cfc4d0f993af37e7931e28b23d744bad9826] # to [34e985d5726e7c0f57e125ac0919b3d940a6a193] # # patch "Makefile.am" # from [4098b391db7d342c7b043719248de79b40b5a1be] # to [d8df863b2e01eeb1bf82bf4826aefb59ed75de6d] # # patch "automate.cc" # from [8b18ec025d8bd4fe2a1a100de6f4bc8243911933] # to [a416a0118ff66f167504e583eda3d2f19a2a24c9] # # patch "cmd.hh" # from [7c227eab1cfe03fc9c73ca144270119bb0d69568] # to [b9f35eeccf525a4bc9dc9253a00989998e8bcb91] # # patch "cmd_automate.cc" # from [4ef7d2e08406ddc3dcfa5adc589285f1a39dddbe] # to [eca8e2b625e400ec961c9a2c9772097dbff8351c] # # patch "cmd_list.cc" # from [735a51c0fb2d30044075a34c908e639751adcc60] # to [9f507eccb08b8547c27b4056bedeb47036e5a61f] # ============================================================ --- ChangeLog 4d27cfc4d0f993af37e7931e28b23d744bad9826 +++ ChangeLog 34e985d5726e7c0f57e125ac0919b3d940a6a193 @@ -1,3 +1,19 @@ +2006-06-17 Timothy Brownawell + + (see bug #15995, asking for a more comprehensive automate command set) + Start splitting up automate.cc . There is now an AUTOMATE() macro in + cmd.hh , similar to the CMD() macro. Individual automate commands can + share a file with similar command-line commands, and many commands can + be factored into an implementation with CMD() and AUTOMATE() wrappers + to format input/output. + * cmd.hh: Add stuff needed to define a new automate command. + * cmd_automate.cc: This gets automate infrastructure similar to + what commands.cc is for command-line commands. And automate stdio. + * cmd_list.cc: "keys" and "certs" automate commands go here + * automate.hh: No longer needed. + * automate.cc: use AUTOMATE() macro for the automate commands that + haven't got a new home yet. + 2006-06-16 Richard Levitte * examples/display_branches.lua: Enhanced to display how many ============================================================ --- Makefile.am 4098b391db7d342c7b043719248de79b40b5a1be +++ Makefile.am d8df863b2e01eeb1bf82bf4826aefb59ed75de6d @@ -48,7 +48,7 @@ cset.cc cset.hh \ roster.cc roster.hh \ mt_version.cc mt_version.hh \ - automate.cc automate.hh \ + automate.cc \ database_check.cc database_check.hh \ epoch.cc epoch.hh \ inodeprint.cc inodeprint.hh \ ============================================================ --- automate.cc 8b18ec025d8bd4fe2a1a100de6f4bc8243911933 +++ automate.cc a416a0118ff66f167504e583eda3d2f19a2a24c9 @@ -22,6 +22,7 @@ #include "app_state.hh" #include "basic_io.hh" #include "cert.hh" +#include "cmd.hh" #include "commands.hh" #include "constants.hh" #include "keys.hh" @@ -49,29 +50,7 @@ using std::string; using std::vector; -static string const interface_version = "2.1"; -// Name: interface_version -// Arguments: none -// Added in: 0.0 -// Purpose: Prints version of automation interface. Major number increments -// whenever a backwards incompatible change is made; minor number increments -// whenever any change is made (but is reset when major number increments). -// Output format: ".\n". Always matches -// "[0-9]+\.[0-9]+\n". -// Error conditions: None. -static void -automate_interface_version(vector args, - string const & help_name, - app_state & app, - ostream & output) -{ - if (args.size() != 0) - throw usage(help_name); - - output << interface_version << endl; -} - // Name: heads // Arguments: // 1: branch name (optional, default branch is used if non-existant) @@ -81,11 +60,7 @@ // newline. Revision ids are printed in alphabetically sorted order. // Error conditions: If the branch does not exist, prints nothing. (There are // no heads.) -static void -automate_heads(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(heads) { if (args.size() > 1) throw usage(help_name); @@ -109,11 +84,7 @@ // newline. Revision ids are printed in alphabetically sorted order. // Error conditions: If any of the revisions do not exist, prints nothing to // stdout, prints an error message to stderr, and exits with status 1. -static void -automate_ancestors(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(ancestors) { if (args.size() == 0) throw usage(help_name); @@ -160,11 +131,7 @@ // newline. Revision ids are printed in alphabetically sorted order. // Error conditions: If any of the revisions do not exist, prints nothing to // stdout, prints an error message to stderr, and exits with status 1. -static void -automate_descendents(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(descendents) { if (args.size() == 0) throw usage(help_name); @@ -212,11 +179,7 @@ // newline. Revision ids are printed in alphabetically sorted order. // Error conditions: If any of the revisions do not exist, prints nothing to // stdout, prints an error message to stderr, and exits with status 1. -static void -automate_erase_ancestors(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(erase_ancestors) { set revs; for (vector::const_iterator i = args.begin(); i != args.end(); ++i) @@ -239,11 +202,7 @@ // Output format: A list of file names in alphabetically sorted order, // or a list of attributes if a file name provided. // Error conditions: If the file name has no attributes, prints nothing. -static void -automate_attributes(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(attributes) { if (args.size() > 1) throw usage(help_name); @@ -293,11 +252,7 @@ // newline. Revisions are printed in topologically sorted order. // Error conditions: If any of the revisions do not exist, prints nothing to // stdout, prints an error message to stderr, and exits with status 1. -static void -automate_toposort(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(toposort) { set revs; for (vector::const_iterator i = args.begin(); i != args.end(); ++i) @@ -329,11 +284,7 @@ // newline. Revisions are printed in topologically sorted order. // Error conditions: If any of the revisions do not exist, prints nothing to // stdout, prints an error message to stderr, and exits with status 1. -static void -automate_ancestry_difference(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(ancestry_difference) { if (args.size() == 0) throw usage(help_name); @@ -372,11 +323,7 @@ // Output format: A list of revision ids, in hexadecimal, each followed by a // newline. Revision ids are printed in alphabetically sorted order. // Error conditions: None. -static void -automate_leaves(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(leaves) { if (args.size() != 0) throw usage(help_name); @@ -404,11 +351,7 @@ // newline. Revision ids are printed in alphabetically sorted order. // Error conditions: If the revision does not exist, prints nothing to stdout, // prints an error message to stderr, and exits with status 1. -static void -automate_parents(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(parents) { if (args.size() != 1) throw usage(help_name); @@ -432,11 +375,7 @@ // newline. Revision ids are printed in alphabetically sorted order. // Error conditions: If the revision does not exist, prints nothing to stdout, // prints an error message to stderr, and exits with status 1. -static void -automate_children(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(children) { if (args.size() != 1) throw usage(help_name); @@ -470,11 +409,7 @@ // The output as a whole is alphabetically sorted; additionally, the parents // within each line are alphabetically sorted. // Error conditions: None. -static void -automate_graph(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(graph) { if (args.size() != 0) throw usage(help_name); @@ -517,11 +452,7 @@ // Output format: A list of revision ids, in hexadecimal, each followed by a // newline. Revision ids are printed in alphabetically sorted order. // Error conditions: None. -static void -automate_select(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(select) { if (args.size() != 1) throw usage(help_name); @@ -703,11 +634,7 @@ // Error conditions: If no workspace book keeping _MTN directory is found, // prints an error message to stderr, and exits with status 1. -static void -automate_inventory(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(inventory) { if (args.size() != 0) throw usage(help_name); @@ -826,137 +753,6 @@ } } -namespace -{ - namespace syms - { - symbol const key("key"); - symbol const signature("signature"); - symbol const name("name"); - symbol const value("value"); - symbol const trust("trust"); - - symbol const public_hash("public_hash"); - symbol const private_hash("private_hash"); - symbol const public_location("public_location"); - symbol const private_location("private_location"); - } -}; - -// Name: certs -// Arguments: -// 1: a revision id -// Added in: 1.0 -// Purpose: Prints all certificates associated with the given revision -// ID. Each certificate is contained in a basic IO stanza. For each -// certificate, the following values are provided: -// -// 'key' : a string indicating the key used to sign this certificate. -// 'signature': a string indicating the status of the signature. -// Possible values of this string are: -// 'ok' : the signature is correct -// 'bad' : the signature is invalid -// 'unknown' : signature was made with an unknown key -// 'name' : the name of this certificate -// 'value' : the value of this certificate -// 'trust' : is this certificate trusted by the defined trust metric -// Possible values of this string are: -// 'trusted' : this certificate is trusted -// 'untrusted' : this certificate is not trusted -// -// Output format: All stanzas are formatted by basic_io. Stanzas are -// seperated by a blank line. Values will be escaped, '\' -> '\\' and -// '"' -> '\"'. -// -// Error conditions: If a certificate is signed with an unknown public -// key, a warning message is printed to stderr. If the revision -// specified is unknown or invalid prints an error message to stderr -// and exits with status 1. -static void -automate_certs(vector args, - string const & help_name, - app_state & app, - ostream & output) -{ - if (args.size() != 1) - throw usage(help_name); - - vector certs; - - transaction_guard guard(app.db, false); - - revision_id rid(idx(args, 0)()); - N(app.db.revision_exists(rid), F("No such revision %s") % rid); - hexenc ident(rid.inner()); - - vector< revision > ts; - app.db.get_revision_certs(rid, ts); - for (size_t i = 0; i < ts.size(); ++i) - certs.push_back(idx(ts, i).inner()); - - { - set checked; - for (size_t i = 0; i < certs.size(); ++i) - { - if (checked.find(idx(certs, i).key) == checked.end() && - !app.db.public_key_exists(idx(certs, i).key)) - W(F("no public key '%s' found in database") - % idx(certs, i).key); - checked.insert(idx(certs, i).key); - } - } - - // Make the output deterministic; this is useful for the test suite, - // in particular. - sort(certs.begin(), certs.end()); - - basic_io::printer pr; - - for (size_t i = 0; i < certs.size(); ++i) - { - basic_io::stanza st; - cert_status status = check_cert(app, idx(certs, i)); - cert_value tv; - cert_name name = idx(certs, i).name(); - set signers; - - decode_base64(idx(certs, i).value, tv); - - rsa_keypair_id keyid = idx(certs, i).key(); - signers.insert(keyid); - - bool trusted = - app.lua.hook_get_revision_cert_trust(signers, ident, - name, tv); - - st.push_str_pair(syms::key, keyid()); - - string stat; - switch (status) - { - case cert_ok: - stat = "ok"; - break; - case cert_bad: - stat = "bad"; - break; - case cert_unknown: - stat = "unknown"; - break; - } - st.push_str_pair(syms::signature, stat); - - st.push_str_pair(syms::name, name()); - st.push_str_pair(syms::value, tv()); - st.push_str_pair(syms::trust, (trusted ? "trusted" : "untrusted")); - - pr.print_stanza(st); - } - output.write(pr.buf.data(), pr.buf.size()); - - guard.commit(); -} - // Name: get_revision // Arguments: // 1: a revision id (optional, determined from the workspace if @@ -1020,11 +816,7 @@ // the same type will be sorted by the filename they refer to. // Error conditions: If the revision specified is unknown or invalid // prints an error message to stderr and exits with status 1. -static void -automate_get_revision(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(get_revision) { if (args.size() > 1) throw usage(help_name); @@ -1068,11 +860,7 @@ // on. This is the value stored in _MTN/revision // Error conditions: If no workspace book keeping _MTN directory is found, // prints an error message to stderr, and exits with status 1. -static void -automate_get_base_revision_id(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(get_base_revision_id) { if (args.size() > 0) throw usage(help_name); @@ -1093,11 +881,7 @@ // files in the workspace. // Error conditions: If no workspace book keeping _MTN directory is found, // prints an error message to stderr, and exits with status 1. -static void -automate_get_current_revision_id(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(get_current_revision_id) { if (args.size() > 0) throw usage(help_name); @@ -1162,11 +946,7 @@ // // Error conditions: If the revision ID specified is unknown or // invalid prints an error message to stderr and exits with status 1. -static void -automate_get_manifest_of(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(get_manifest_of) { if (args.size() > 1) throw usage(help_name); @@ -1209,11 +989,7 @@ // // Error conditions: If the file id specified is unknown or invalid prints // an error message to stderr and exits with status 1. -static void -automate_get_file(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(get_file) { if (args.size() != 1) throw usage(help_name); @@ -1239,11 +1015,7 @@ // // Error conditions: If the revision id specified is unknown or // invalid prints an error message to stderr and exits with status 1. -static void -automate_packet_for_rdata(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(packet_for_rdata) { if (args.size() != 1) throw usage(help_name); @@ -1269,11 +1041,7 @@ // // Error conditions: If the revision id specified is unknown or // invalid prints an error message to stderr and exits with status 1. -static void -automate_packets_for_certs(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(packets_for_certs) { if (args.size() != 1) throw usage(help_name); @@ -1300,11 +1068,7 @@ // // Error conditions: If the file id specified is unknown or invalid // prints an error message to stderr and exits with status 1. -static void -automate_packet_for_fdata(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(packet_for_fdata) { if (args.size() != 1) throw usage(help_name); @@ -1331,11 +1095,7 @@ // // Error conditions: If any of the file ids specified are unknown or // invalid prints an error message to stderr and exits with status 1. -static void -automate_packet_for_fdelta(vector args, - string const & help_name, - app_state & app, - ostream & output) +AUTOMATE(packet_for_fdelta) { if (args.size() != 2) throw usage(help_name); @@ -1357,328 +1117,6 @@ pw.consume_file_delta(f_old_id, f_new_id, file_delta(del)); } -void -automate_command(utf8 cmd, vector args, - string const & root_cmd_name, - app_state & app, - ostream & output); - -// Name: stdio -// Arguments: none -// Added in: 1.0 -// Purpose: Allow multiple automate commands to be run from one instance -// of monotone. -// -// Input format: The input is a series of lines of the form -// 'l'':'[':'...]'e', with characters -// after the 'e' of one command, but before the 'l' of the next ignored. -// This space is reserved, and should not contain characters other -// than '\n'. -// Example: -// l6:leavese -// l7:parents40:0e3171212f34839c2e3263e7282cdeea22fc5378e -// -// Output format: :::: -// is a decimal number specifying which command -// this output is from. It is 0 for the first command, and increases -// by one each time. -// is 0 for success, 1 for a syntax error, and 2 for any -// other error. -// is 'l' if this is the last piece of output for this command, -// and 'm' if there is more output to come. -// is the number of bytes in the output. -// is the output of the command. -// Example: -// 0:0:l:205:0e3171212f34839c2e3263e7282cdeea22fc5378 -// 1f4ef73c3e056883c6a5ff66728dd764557db5e6 -// 2133c52680aa2492b18ed902bdef7e083464c0b8 -// 23501f8afd1f9ee037019765309b0f8428567f8a -// 2c295fcf5fe20301557b9b3a5b4d437b5ab8ec8c -// 1:0:l:41:7706a422ccad41621c958affa999b1a1dd644e79 -// -// Error conditions: Errors encountered by the commands run only set -// the error code in the output for that command. Malformed input -// results in exit with a non-zero return value and an error message. - -// We use our own stringbuf class so we can put in a callback on write. -// This lets us dump output at a set length, rather than waiting until -// we have all of the output. - -typedef basic_stringbuf, - allocator > char_stringbuf; -struct my_stringbuf : public char_stringbuf -{ -private: - streamsize written; - boost::function1 on_write; - streamsize last_call; - streamsize call_every; - bool clear; -public: - my_stringbuf() : char_stringbuf(), - written(0), - last_call(0), - call_every(constants::automate_stdio_size) - {} - virtual streamsize - xsputn(const char_stringbuf::char_type* __s, streamsize __n) - { - streamsize ret=char_stringbuf::xsputn(__s, __n); - written+=__n; - while(written>=last_call+call_every) - { - if(on_write) - on_write(call_every); - last_call+=call_every; - } - return ret; - } - virtual int sync() - { - int ret=char_stringbuf::sync(); - if(on_write) - on_write(-1); - last_call=written; - return ret; - } - void set_on_write(boost::function1 x) - { - on_write = x; - } -}; - -void print_some_output(int cmdnum, - int err, - bool last, - string const & text, - ostream & s, - int & pos, - int size) -{ - if(size==-1) - { - while(text.size()-pos > constants::automate_stdio_size) - { - s<= 0, F("read from client failed with error code: %d") % rv); - return rv; -} - -static void -automate_stdio(vector args, - string const & help_name, - app_state & app, - ostream & output) -{ - if (args.size() != 0) - throw usage(help_name); - int cmdnum = 0; - char c; - ssize_t n=1; - while(n)//while(!EOF) - { - string x; - utf8 cmd; - args.clear(); - bool first=true; - int toklen=0; - bool firstchar=true; - for(n=automate_stdio_read(0, &c, 1); c != 'l' && n; n=automate_stdio_read(0, &c, 1)) - ; - for(n=automate_stdio_read(0, &c, 1); c!='e' && n; n=automate_stdio_read(0, &c, 1)) - { - if(c<='9' && c>='0') - { - toklen=(toklen*10)+(c-'0'); - } - else if(c == ':') - { - char *tok=new char[toklen]; - int count=0; - while(count >::rdbuf(&sb); - try - { - err=0; - automate_command(cmd, args, help_name, app, s); - } - catch(usage &) - { - if(sb.str().size()) - s.flush(); - err=1; - commands::explain_usage(help_name, s); - } - catch(informative_failure & f) - { - if(sb.str().size()) - s.flush(); - err=2; - //Do this instead of printing f.what directly so the output - //will be split into properly-sized blocks automatically. - s< args, string const & help_name, - app_state & app, ostream & output) -{ - if (args.size() != 0) - throw usage(help_name); - vector dbkeys; - vector kskeys; - // public_hash, private_hash, public_location, private_location - map, hexenc, - vector, - vector > > items; - if (app.db.database_specified()) - { - transaction_guard guard(app.db, false); - app.db.get_key_ids("", dbkeys); - guard.commit(); - } - app.keys.get_key_ids("", kskeys); - - for (vector::iterator i = dbkeys.begin(); - i != dbkeys.end(); i++) - { - base64 pub_encoded; - hexenc hash_code; - - app.db.get_key(*i, pub_encoded); - key_hash_code(*i, pub_encoded, hash_code); - items[(*i)()].get<0>() = hash_code; - items[(*i)()].get<2>().push_back("database"); - } - - for (vector::iterator i = kskeys.begin(); - i != kskeys.end(); i++) - { - keypair kp; - hexenc privhash, pubhash; - app.keys.get_key_pair(*i, kp); - key_hash_code(*i, kp.pub, pubhash); - key_hash_code(*i, kp.priv, privhash); - items[(*i)()].get<0>() = pubhash; - items[(*i)()].get<1>() = privhash; - items[(*i)()].get<2>().push_back("keystore"); - items[(*i)()].get<3>().push_back("keystore"); - } - basic_io::printer prt; - for (map, hexenc, - vector, - vector > >::iterator - i = items.begin(); i != items.end(); ++i) - { - basic_io::stanza stz; - stz.push_str_pair(syms::name, i->first); - stz.push_hex_pair(syms::public_hash, i->second.get<0>()); - if (!i->second.get<1>()().empty()) - stz.push_hex_pair(syms::private_hash, i->second.get<1>()); - stz.push_str_multi(syms::public_location, i->second.get<2>()); - if (!i->second.get<3>().empty()) - stz.push_str_multi(syms::private_location, i->second.get<3>()); - prt.print_stanza(stz); - } - output.write(prt.buf.data(), prt.buf.size()); -} - // Name: common_ancestors // Arguments: // 1 or more revision ids @@ -1691,9 +1129,7 @@ // Error conditions: If any of the revisions do not exist, prints // nothing to stdout, prints an error message to stderr, and exits // with status 1. -static void -automate_common_ancestors(vector args, string const & help_name, - app_state & app, ostream & output) +AUTOMATE(common_ancestors) { if (args.size() == 0) throw usage(help_name); @@ -1745,71 +1181,6 @@ } -void -automate_command(utf8 cmd, vector args, - string const & root_cmd_name, - app_state & app, - ostream & output) -{ - if (cmd() == "interface_version") - automate_interface_version(args, root_cmd_name, app, output); - else if (cmd() == "heads") - automate_heads(args, root_cmd_name, app, output); - else if (cmd() == "ancestors") - automate_ancestors(args, root_cmd_name, app, output); - else if (cmd() == "descendents") - automate_descendents(args, root_cmd_name, app, output); - else if (cmd() == "erase_ancestors") - automate_erase_ancestors(args, root_cmd_name, app, output); - else if (cmd() == "toposort") - automate_toposort(args, root_cmd_name, app, output); - else if (cmd() == "ancestry_difference") - automate_ancestry_difference(args, root_cmd_name, app, output); - else if (cmd() == "leaves") - automate_leaves(args, root_cmd_name, app, output); - else if (cmd() == "parents") - automate_parents(args, root_cmd_name, app, output); - else if (cmd() == "children") - automate_children(args, root_cmd_name, app, output); - else if (cmd() == "graph") - automate_graph(args, root_cmd_name, app, output); - else if (cmd() == "select") - automate_select(args, root_cmd_name, app, output); - else if (cmd() == "inventory") - automate_inventory(args, root_cmd_name, app, output); - else if (cmd() == "attributes") - automate_attributes(args, root_cmd_name, app, output); - else if (cmd() == "stdio") - automate_stdio(args, root_cmd_name, app, output); - else if (cmd() == "certs") - automate_certs(args, root_cmd_name, app, output); - else if (cmd() == "get_revision") - automate_get_revision(args, root_cmd_name, app, output); - else if (cmd() == "get_base_revision_id") - automate_get_base_revision_id(args, root_cmd_name, app, output); - else if (cmd() == "get_current_revision_id") - automate_get_current_revision_id(args, root_cmd_name, app, output); - else if (cmd() == "get_manifest_of") - automate_get_manifest_of(args, root_cmd_name, app, output); - else if (cmd() == "get_file") - automate_get_file(args, root_cmd_name, app, output); - else if (cmd() == "keys") - automate_keys(args, root_cmd_name, app, output); - else if (cmd() == "packet_for_rdata") - automate_packet_for_rdata(args, root_cmd_name, app, output); - else if (cmd() == "packets_for_certs") - automate_packets_for_certs(args, root_cmd_name, app, output); - else if (cmd() == "packet_for_fdata") - automate_packet_for_fdata(args, root_cmd_name, app, output); - else if (cmd() == "packet_for_fdelta") - automate_packet_for_fdelta(args, root_cmd_name, app, output); - else if (cmd() == "common_ancestors") - automate_common_ancestors(args, root_cmd_name, app, output); - else - throw usage(root_cmd_name); -} - - // Local Variables: // mode: C++ // fill-column: 76 ============================================================ --- cmd.hh 7c227eab1cfe03fc9c73ca144270119bb0d69568 +++ cmd.hh b9f35eeccf525a4bc9dc9253a00989998e8bcb91 @@ -158,6 +158,37 @@ process(app, std::string(#realcommand), args); \ } +namespace automation { + struct automate + { + automate(std::string const & name); + virtual void run(std::vector args, + std::string const & help_name, + app_state & app, + std::ostream & output) const = 0; + virtual ~automate(); + }; +} + +#define AUTOMATE(NAME) \ +namespace automation { \ + struct auto_ ## NAME : public automate \ + { \ + auto_ ## NAME () : automate(#NAME) {} \ + void run(std::vector args, std::string const & help_name, \ + app_state & app, std::ostream & output) const; \ + virtual ~auto_ ## NAME() {} \ + }; \ + static auto_ ## NAME NAME ## _auto; \ +} \ +void automation::auto_ ## NAME :: run(std::vector args, \ + std::string const & help_name,\ + app_state & app, \ + std::ostream & output) const + + + + // Local Variables: // mode: C++ // fill-column: 76 ============================================================ --- cmd_automate.cc 4ef7d2e08406ddc3dcfa5adc589285f1a39dddbe +++ cmd_automate.cc eca8e2b625e400ec961c9a2c9772097dbff8351c @@ -7,14 +7,290 @@ // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. +#include #include +#include -#include "automate.hh" +#include + #include "cmd.hh" -using std::cout; +using std::map; +using std::ostream; +using std::string; using std::vector; +namespace automation { + // When this is split into multiple files, there will not be any + // guarantees about initialization order. So, use something we can + // initialize ourselves. + static map * automations; + automate::automate(string const &name) + { + static bool first(true); + if (first) + { + first = false; + automations = new map; + } + automations->insert(make_pair(name, this)); + } + automate::~automate() {} +} + +void +automate_command(utf8 cmd, vector args, + string const & root_cmd_name, + app_state & app, + ostream & output) +{ + map::const_iterator + i = automation::automations->find(cmd()); + if (i == automation::automations->end()) + throw usage(root_cmd_name); + else + i->second->run(args, root_cmd_name, app, output); +} + +static string const interface_version = "2.1"; + +// Name: interface_version +// Arguments: none +// Added in: 0.0 +// Purpose: Prints version of automation interface. Major number increments +// whenever a backwards incompatible change is made; minor number increments +// whenever any change is made (but is reset when major number increments). +// Output format: ".\n". Always matches +// "[0-9]+\.[0-9]+\n". +// Error conditions: None. +AUTOMATE(interface_version) +{ + if (args.size() != 0) + throw usage(help_name); + + output << interface_version << "\n"; +} + +void +automate_command(utf8 cmd, std::vector args, + std::string const & root_cmd_name, + app_state & app, + std::ostream & output); + +// Name: stdio +// Arguments: none +// Added in: 1.0 +// Purpose: Allow multiple automate commands to be run from one instance +// of monotone. +// +// Input format: The input is a series of lines of the form +// 'l'':'[':'...]'e', with characters +// after the 'e' of one command, but before the 'l' of the next ignored. +// This space is reserved, and should not contain characters other +// than '\n'. +// Example: +// l6:leavese +// l7:parents40:0e3171212f34839c2e3263e7282cdeea22fc5378e +// +// Output format: :::: +// is a decimal number specifying which command +// this output is from. It is 0 for the first command, and increases +// by one each time. +// is 0 for success, 1 for a syntax error, and 2 for any +// other error. +// is 'l' if this is the last piece of output for this command, +// and 'm' if there is more output to come. +// is the number of bytes in the output. +// is the output of the command. +// Example: +// 0:0:l:205:0e3171212f34839c2e3263e7282cdeea22fc5378 +// 1f4ef73c3e056883c6a5ff66728dd764557db5e6 +// 2133c52680aa2492b18ed902bdef7e083464c0b8 +// 23501f8afd1f9ee037019765309b0f8428567f8a +// 2c295fcf5fe20301557b9b3a5b4d437b5ab8ec8c +// 1:0:l:41:7706a422ccad41621c958affa999b1a1dd644e79 +// +// Error conditions: Errors encountered by the commands run only set +// the error code in the output for that command. Malformed input +// results in exit with a non-zero return value and an error message. + +// We use our own stringbuf class so we can put in a callback on write. +// This lets us dump output at a set length, rather than waiting until +// we have all of the output. + +typedef std::basic_stringbuf, + std::allocator > char_stringbuf; +struct my_stringbuf : public char_stringbuf +{ +private: + std::streamsize written; + boost::function1 on_write; + std::streamsize last_call; + std::streamsize call_every; + bool clear; +public: + my_stringbuf() : char_stringbuf(), + written(0), + last_call(0), + call_every(constants::automate_stdio_size) + {} + virtual std::streamsize + xsputn(const char_stringbuf::char_type* __s, std::streamsize __n) + { + std::streamsize ret=char_stringbuf::xsputn(__s, __n); + written+=__n; + while(written>=last_call+call_every) + { + if(on_write) + on_write(call_every); + last_call+=call_every; + } + return ret; + } + virtual int sync() + { + int ret=char_stringbuf::sync(); + if(on_write) + on_write(-1); + last_call=written; + return ret; + } + void set_on_write(boost::function1 x) + { + on_write = x; + } +}; + +void print_some_output(int cmdnum, + int err, + bool last, + string const & text, + ostream & s, + int & pos, + int size) +{ + if(size==-1) + { + while(text.size()-pos > constants::automate_stdio_size) + { + s<= 0, F("read from client failed with error code: %d") % rv); + return rv; +} + +AUTOMATE(stdio) +{ + if (args.size() != 0) + throw usage(help_name); + int cmdnum = 0; + char c; + ssize_t n=1; + while(n)//while(!EOF) + { + string x; + utf8 cmd; + args.clear(); + bool first=true; + int toklen=0; + bool firstchar=true; + for(n=automate_stdio_read(0, &c, 1); c != 'l' && n; n=automate_stdio_read(0, &c, 1)) + ; + for(n=automate_stdio_read(0, &c, 1); c!='e' && n; n=automate_stdio_read(0, &c, 1)) + { + if(c<='9' && c>='0') + { + toklen=(toklen*10)+(c-'0'); + } + else if(c == ':') + { + char *tok=new char[toklen]; + int count=0; + while(count >::rdbuf(&sb); + try + { + err=0; + automate_command(cmd, args, help_name, app, s); + } + catch(usage &) + { + if(sb.str().size()) + s.flush(); + err=1; + commands::explain_usage(help_name, s); + } + catch(informative_failure & f) + { + if(sb.str().size()) + s.flush(); + err=2; + //Do this instead of printing f.what directly so the output + //will be split into properly-sized blocks automatically. + s< #include +#include + +#include "basic_io.hh" #include "cert.hh" #include "charset.hh" #include "cmd.hh" @@ -481,6 +484,231 @@ ALIAS(ls, list) +namespace +{ + namespace syms + { + symbol const key("key"); + symbol const signature("signature"); + symbol const name("name"); + symbol const value("value"); + symbol const trust("trust"); + + symbol const public_hash("public_hash"); + symbol const private_hash("private_hash"); + symbol const public_location("public_location"); + symbol const private_location("private_location"); + } +}; + +// Name: keys +// Arguments: none +// Added in: 1.1 +// Purpose: Prints all keys in the keystore, and if a database is given +// also all keys in the database, in basic_io format. +// Output format: For each key, a basic_io stanza is printed. The items in +// the stanza are: +// name - the key identifier +// public_hash - the hash of the public half of the key +// private_hash - the hash of the private half of the key +// public_location - where the public half of the key is stored +// private_location - where the private half of the key is stored +// The *_location items may have multiple values, as shown below +// for public_location. +// If the private key does not exist, then the private_hash and +// private_location items will be absent. +// +// Sample output: +// name "address@hidden" +// public_hash [475055ec71ad48f5dfaf875b0fea597b5cbbee64] +// private_hash [7f76dae3f91bb48f80f1871856d9d519770b7f8a] +// public_location "database" "keystore" +// private_location "keystore" +// +// name "address@hidden" +// public_hash [de84b575d5e47254393eba49dce9dc4db98ed42d] +// public_location "database" +// +// name "address@hidden" +// public_hash [7b6ce0bd83240438e7a8c7c207d8654881b763f6] +// private_hash [bfc3263e3257087f531168850801ccefc668312d] +// public_location "keystore" +// private_location "keystore" +// +// Error conditions: None. +AUTOMATE(keys) +{ + if (args.size() != 0) + throw usage(help_name); + vector dbkeys; + vector kskeys; + // public_hash, private_hash, public_location, private_location + map, hexenc, + vector, + vector > > items; + if (app.db.database_specified()) + { + transaction_guard guard(app.db, false); + app.db.get_key_ids("", dbkeys); + guard.commit(); + } + app.keys.get_key_ids("", kskeys); + + for (vector::iterator i = dbkeys.begin(); + i != dbkeys.end(); i++) + { + base64 pub_encoded; + hexenc hash_code; + + app.db.get_key(*i, pub_encoded); + key_hash_code(*i, pub_encoded, hash_code); + items[(*i)()].get<0>() = hash_code; + items[(*i)()].get<2>().push_back("database"); + } + + for (vector::iterator i = kskeys.begin(); + i != kskeys.end(); i++) + { + keypair kp; + hexenc privhash, pubhash; + app.keys.get_key_pair(*i, kp); + key_hash_code(*i, kp.pub, pubhash); + key_hash_code(*i, kp.priv, privhash); + items[(*i)()].get<0>() = pubhash; + items[(*i)()].get<1>() = privhash; + items[(*i)()].get<2>().push_back("keystore"); + items[(*i)()].get<3>().push_back("keystore"); + } + basic_io::printer prt; + for (map, hexenc, + vector, + vector > >::iterator + i = items.begin(); i != items.end(); ++i) + { + basic_io::stanza stz; + stz.push_str_pair(syms::name, i->first); + stz.push_hex_pair(syms::public_hash, i->second.get<0>()); + if (!i->second.get<1>()().empty()) + stz.push_hex_pair(syms::private_hash, i->second.get<1>()); + stz.push_str_multi(syms::public_location, i->second.get<2>()); + if (!i->second.get<3>().empty()) + stz.push_str_multi(syms::private_location, i->second.get<3>()); + prt.print_stanza(stz); + } + output.write(prt.buf.data(), prt.buf.size()); +} + +// Name: certs +// Arguments: +// 1: a revision id +// Added in: 1.0 +// Purpose: Prints all certificates associated with the given revision +// ID. Each certificate is contained in a basic IO stanza. For each +// certificate, the following values are provided: +// +// 'key' : a string indicating the key used to sign this certificate. +// 'signature': a string indicating the status of the signature. +// Possible values of this string are: +// 'ok' : the signature is correct +// 'bad' : the signature is invalid +// 'unknown' : signature was made with an unknown key +// 'name' : the name of this certificate +// 'value' : the value of this certificate +// 'trust' : is this certificate trusted by the defined trust metric +// Possible values of this string are: +// 'trusted' : this certificate is trusted +// 'untrusted' : this certificate is not trusted +// +// Output format: All stanzas are formatted by basic_io. Stanzas are +// seperated by a blank line. Values will be escaped, '\' -> '\\' and +// '"' -> '\"'. +// +// Error conditions: If a certificate is signed with an unknown public +// key, a warning message is printed to stderr. If the revision +// specified is unknown or invalid prints an error message to stderr +// and exits with status 1. +AUTOMATE(certs) +{ + if (args.size() != 1) + throw usage(help_name); + + vector certs; + + transaction_guard guard(app.db, false); + + revision_id rid(idx(args, 0)()); + N(app.db.revision_exists(rid), F("No such revision %s") % rid); + hexenc ident(rid.inner()); + + vector< revision > ts; + app.db.get_revision_certs(rid, ts); + for (size_t i = 0; i < ts.size(); ++i) + certs.push_back(idx(ts, i).inner()); + + { + set checked; + for (size_t i = 0; i < certs.size(); ++i) + { + if (checked.find(idx(certs, i).key) == checked.end() && + !app.db.public_key_exists(idx(certs, i).key)) + W(F("no public key '%s' found in database") + % idx(certs, i).key); + checked.insert(idx(certs, i).key); + } + } + + // Make the output deterministic; this is useful for the test suite, + // in particular. + sort(certs.begin(), certs.end()); + + basic_io::printer pr; + + for (size_t i = 0; i < certs.size(); ++i) + { + basic_io::stanza st; + cert_status status = check_cert(app, idx(certs, i)); + cert_value tv; + cert_name name = idx(certs, i).name(); + set signers; + + decode_base64(idx(certs, i).value, tv); + + rsa_keypair_id keyid = idx(certs, i).key(); + signers.insert(keyid); + + bool trusted = + app.lua.hook_get_revision_cert_trust(signers, ident, + name, tv); + + st.push_str_pair(syms::key, keyid()); + + string stat; + switch (status) + { + case cert_ok: + stat = "ok"; + break; + case cert_bad: + stat = "bad"; + break; + case cert_unknown: + stat = "unknown"; + break; + } + st.push_str_pair(syms::signature, stat); + + st.push_str_pair(syms::name, name()); + st.push_str_pair(syms::value, tv()); + st.push_str_pair(syms::trust, (trusted ? "trusted" : "untrusted")); + + pr.print_stanza(st); + } + output.write(pr.buf.data(), pr.buf.size()); + + guard.commit(); +} + + // Local Variables: // mode: C++ // fill-column: 76