]> git.proxmox.com Git - ceph.git/blame - ceph/src/tools/ceph_monstore_tool.cc
import ceph 16.2.7
[ceph.git] / ceph / src / tools / ceph_monstore_tool.cc
CommitLineData
7c673cae
FG
1// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2// vim: ts=8 sw=2 smarttab
3/*
4* Ceph - scalable distributed file system
5*
6* Copyright (C) 2012 Inktank, Inc.
7*
8* This is free software; you can redistribute it and/or
9* modify it under the terms of the GNU Lesser General Public
10* License version 2.1, as published by the Free Software
11* Foundation. See file COPYING.
12*/
13#include <boost/program_options/variables_map.hpp>
14#include <boost/program_options/parsers.hpp>
15#include <boost/scope_exit.hpp>
16
17#include <stdlib.h>
18#include <string>
19
20#include "common/Formatter.h"
21#include "common/errno.h"
22
23#include "auth/KeyRing.h"
24#include "auth/cephx/CephxKeyServer.h"
25#include "global/global_init.h"
26#include "include/stringify.h"
3efd9988 27#include "mgr/mgr_commands.h"
7c673cae
FG
28#include "mon/AuthMonitor.h"
29#include "mon/MonitorDBStore.h"
30#include "mon/Paxos.h"
31#include "mon/MonMap.h"
b32b8144
FG
32#include "mds/FSMap.h"
33#include "mon/MgrMap.h"
7c673cae
FG
34#include "osd/OSDMap.h"
35#include "crush/CrushCompiler.h"
a8e16298 36#include "mon/CreatingPGs.h"
7c673cae
FG
37
38namespace po = boost::program_options;
7c673cae
FG
39
40class TraceIter {
41 int fd;
42 unsigned idx;
43 MonitorDBStore::TransactionRef t;
44public:
45 explicit TraceIter(string fname) : fd(-1), idx(-1) {
f67539c2 46 fd = ::open(fname.c_str(), O_RDONLY|O_BINARY);
7c673cae
FG
47 t.reset(new MonitorDBStore::Transaction);
48 }
49 bool valid() {
50 return fd != -1;
51 }
52 MonitorDBStore::TransactionRef cur() {
11fdf7f2 53 ceph_assert(valid());
7c673cae
FG
54 return t;
55 }
56 unsigned num() { return idx; }
57 void next() {
58 ++idx;
59 bufferlist bl;
60 int r = bl.read_fd(fd, 6);
61 if (r < 0) {
62 std::cerr << "Got error: " << cpp_strerror(r) << " on read_fd"
63 << std::endl;
64 ::close(fd);
65 fd = -1;
66 return;
67 } else if ((unsigned)r < 6) {
68 std::cerr << "short read" << std::endl;
69 ::close(fd);
70 fd = -1;
71 return;
72 }
11fdf7f2 73 auto bliter = bl.cbegin();
7c673cae 74 uint8_t ver, ver2;
11fdf7f2
TL
75 decode(ver, bliter);
76 decode(ver2, bliter);
7c673cae 77 uint32_t len;
11fdf7f2 78 decode(len, bliter);
7c673cae
FG
79 r = bl.read_fd(fd, len);
80 if (r < 0) {
81 std::cerr << "Got error: " << cpp_strerror(r) << " on read_fd"
82 << std::endl;
83 ::close(fd);
84 fd = -1;
85 return;
86 } else if ((unsigned)r < len) {
87 std::cerr << "short read" << std::endl;
88 ::close(fd);
89 fd = -1;
90 return;
91 }
11fdf7f2 92 bliter = bl.cbegin();
7c673cae
FG
93 t.reset(new MonitorDBStore::Transaction);
94 t->decode(bliter);
95 }
96 void init() {
97 next();
98 }
99 ~TraceIter() {
100 if (fd != -1) {
101 ::close(fd);
102 fd = -1;
103 }
104 }
105};
106
107
108int parse_cmd_args(
109 po::options_description *desc, /// < visible options description
110 po::options_description *hidden_desc, /// < hidden options description
111 po::positional_options_description *positional, /// < positional args
112 vector<string> &cmd_args, /// < arguments to be parsed
113 po::variables_map *vm /// > post-parsing variable map
114 )
115{
116 // desc_all will aggregate all visible and hidden options for parsing.
117 //
118 // From boost's program_options point of view, there is absolutely no
119 // distinction between 'desc' and 'hidden_desc'. This is a distinction
120 // that is only useful to us: 'desc' is whatever we are willing to show
121 // on 'usage()', whereas 'hidden_desc' refers to parameters we wish to
122 // take advantage of but do not wish to show on 'usage()'.
123 //
124 // For example, consider that program_options matches positional arguments
125 // (specified via 'positional') against the paramenters defined on a
126 // given 'po::options_description' class. This is performed below,
127 // supplying both the description and the positional arguments to the
128 // parser. However, we do not want the parameters that are mapped to
129 // positional arguments to be shown on usage, as that makes for ugly and
130 // confusing usage messages. Therefore we dissociate the options'
131 // description that is to be used as an aid to the user from those options
132 // that are nothing but useful for internal purposes (i.e., mapping options
133 // to positional arguments). We still need to aggregate them before parsing
134 // and that's what 'desc_all' is all about.
135 //
136
11fdf7f2 137 ceph_assert(desc != NULL);
7c673cae
FG
138
139 po::options_description desc_all;
140 desc_all.add(*desc);
141 if (hidden_desc != NULL)
142 desc_all.add(*hidden_desc);
143
144 try {
145 po::command_line_parser parser = po::command_line_parser(cmd_args).
146 options(desc_all);
147
148 if (positional) {
149 parser = parser.positional(*positional);
150 }
151
152 po::parsed_options parsed = parser.run();
153 po::store(parsed, *vm);
154 po::notify(*vm);
155 } catch (po::error &e) {
156 std::cerr << "error: " << e.what() << std::endl;
157 return -EINVAL;
158 }
159 return 0;
160}
161
162
163/**
164 * usage: ceph-monstore-tool <store-path> <command> [options]
165 *
166 * commands:
167 *
168 * store-copy < --out arg >
169 * dump-keys
170 * compact
171 * getmonmap < --out arg [ --version arg ] >
172 * getosdmap < --out arg [ --version arg ] >
173 * dump-paxos <--dump-start VER> <--dump-end VER>
174 * dump-trace < --trace-file arg >
175 * replay-trace
176 * random-gen
177 * rewrite-crush
7c673cae
FG
178 *
179 * wanted syntax:
180 *
181 * ceph-monstore-tool PATH CMD [options]
182 *
183 * ceph-monstore-tool PATH store-copy <PATH2 | -o PATH2>
184 * ceph-monstore-tool PATH dump-keys
185 * ceph-monstore-tool PATH compact
186 * ceph-monstore-tool PATH get monmap [VER]
187 * ceph-monstore-tool PATH get osdmap [VER]
188 * ceph-monstore-tool PATH dump-paxos STARTVER ENDVER
189 *
190 *
191 */
192void usage(const char *n, po::options_description &d)
193{
194 std::cerr <<
195 "usage: " << n << " <store-path> <cmd> [args|options]\n"
196 << "\n"
197 << "Commands:\n"
198 << " store-copy PATH copies store to PATH\n"
199 << " compact compacts the store\n"
200 << " get monmap [-- options] get monmap (version VER if specified)\n"
201 << " (default: last committed)\n"
202 << " get osdmap [-- options] get osdmap (version VER if specified)\n"
203 << " (default: last committed)\n"
204 << " get mdsmap [-- options] get mdsmap (version VER if specified)\n"
205 << " (default: last committed)\n"
b32b8144
FG
206 << " get mgr [-- options] get mgr map (version VER if specified)\n"
207 << " (default: last committed)\n"
7c673cae
FG
208 << " get crushmap [-- options] get crushmap (version VER if specified)\n"
209 << " (default: last committed)\n"
210 << " show-versions [-- options] show the first&last committed version of map\n"
211 << " (show-versions -- --help for more info)\n"
212 << " dump-keys dumps store keys to FILE\n"
213 << " (default: stdout)\n"
214 << " dump-paxos [-- options] dump paxos transactions\n"
215 << " (dump-paxos -- --help for more info)\n"
216 << " dump-trace FILE [-- options] dump contents of trace file FILE\n"
217 << " (dump-trace -- --help for more info)\n"
218 << " replay-trace FILE [-- options] replay trace from FILE\n"
219 << " (replay-trace -- --help for more info)\n"
220 << " random-gen [-- options] add randomly generated ops to the store\n"
221 << " (random-gen -- --help for more info)\n"
222 << " rewrite-crush [-- options] add a rewrite commit to the store\n"
223 << " (rewrite-crush -- --help for more info)\n"
7c673cae
FG
224 << " rebuild rebuild store\n"
225 << " (rebuild -- --help for more info)\n"
226 << std::endl;
227 std::cerr << d << std::endl;
228 std::cerr
229 << "\nPlease Note:\n"
230 << "* Ceph-specific options should be in the format --option-name=VAL\n"
231 << " (specifically, do not forget the '='!!)\n"
232 << "* Command-specific options need to be passed after a '--'\n"
233 << " e.g., 'get monmap -- --version 10 --out /tmp/foo'"
234 << std::endl;
235}
236
237int update_osdmap(MonitorDBStore& store, version_t ver, bool copy,
11fdf7f2 238 std::shared_ptr<CrushWrapper> crush,
7c673cae
FG
239 MonitorDBStore::Transaction* t) {
240 const string prefix("osdmap");
241
242 // full
243 bufferlist bl;
244 int r = 0;
245 r = store.get(prefix, store.combine_strings("full", ver), bl);
246 if (r) {
247 std::cerr << "Error getting full map: " << cpp_strerror(r) << std::endl;
248 return r;
249 }
250 OSDMap osdmap;
251 osdmap.decode(bl);
252 osdmap.crush = crush;
253 if (copy) {
254 osdmap.inc_epoch();
255 }
256 bl.clear();
257 // be consistent with OSDMonitor::update_from_paxos()
258 osdmap.encode(bl, CEPH_FEATURES_ALL|CEPH_FEATURE_RESERVED);
259 t->put(prefix, store.combine_strings("full", osdmap.get_epoch()), bl);
260
261 // incremental
262 OSDMap::Incremental inc;
263 if (copy) {
264 inc.epoch = osdmap.get_epoch();
265 inc.fsid = osdmap.get_fsid();
266 } else {
267 bl.clear();
268 r = store.get(prefix, ver, bl);
269 if (r) {
270 std::cerr << "Error getting inc map: " << cpp_strerror(r) << std::endl;
271 return r;
272 }
273 OSDMap::Incremental inc(bl);
274 if (inc.crush.length()) {
275 inc.crush.clear();
276 crush->encode(inc.crush, CEPH_FEATURES_SUPPORTED_DEFAULT);
277 }
278 if (inc.fullmap.length()) {
279 OSDMap fullmap;
280 fullmap.decode(inc.fullmap);
281 fullmap.crush = crush;
282 inc.fullmap.clear();
283 fullmap.encode(inc.fullmap);
284 }
285 }
11fdf7f2 286 ceph_assert(osdmap.have_crc());
7c673cae
FG
287 inc.full_crc = osdmap.get_crc();
288 bl.clear();
289 // be consistent with OSDMonitor::update_from_paxos()
290 inc.encode(bl, CEPH_FEATURES_ALL|CEPH_FEATURE_RESERVED);
291 t->put(prefix, inc.epoch, bl);
292 return 0;
293}
294
295int rewrite_transaction(MonitorDBStore& store, int version,
296 const string& crush_file,
297 MonitorDBStore::Transaction* t) {
298 const string prefix("osdmap");
299
300 // calc the known-good epoch
301 version_t last_committed = store.get(prefix, "last_committed");
302 version_t good_version = 0;
303 if (version <= 0) {
304 if (last_committed >= (unsigned)-version) {
305 good_version = last_committed + version;
306 } else {
307 std::cerr << "osdmap-version is less than: -" << last_committed << std::endl;
308 return EINVAL;
309 }
310 } else {
311 good_version = version;
312 }
313 if (good_version >= last_committed) {
314 std::cout << "good epoch is greater or equal to the last committed one: "
315 << good_version << " >= " << last_committed << std::endl;
316 return 0;
317 }
318
319 // load/extract the crush map
320 int r = 0;
11fdf7f2 321 std::shared_ptr<CrushWrapper> crush(new CrushWrapper);
7c673cae
FG
322 if (crush_file.empty()) {
323 bufferlist bl;
324 r = store.get(prefix, store.combine_strings("full", good_version), bl);
325 if (r) {
326 std::cerr << "Error getting map: " << cpp_strerror(r) << std::endl;
327 return r;
328 }
329 OSDMap osdmap;
330 osdmap.decode(bl);
331 crush = osdmap.crush;
332 } else {
333 string err;
334 bufferlist bl;
335 r = bl.read_file(crush_file.c_str(), &err);
336 if (r) {
337 std::cerr << err << ": " << cpp_strerror(r) << std::endl;
338 return r;
339 }
11fdf7f2 340 auto p = bl.cbegin();
7c673cae
FG
341 crush->decode(p);
342 }
343
344 // prepare a transaction to rewrite the epochs
345 // (good_version, last_committed]
346 // with the good crush map.
347 // XXX: may need to break this into several paxos versions?
11fdf7f2 348 ceph_assert(good_version < last_committed);
7c673cae
FG
349 for (version_t v = good_version + 1; v <= last_committed; v++) {
350 cout << "rewriting epoch #" << v << "/" << last_committed << std::endl;
351 r = update_osdmap(store, v, false, crush, t);
352 if (r)
353 return r;
354 }
355
356 // add a new osdmap epoch to store, so monitors will update their current osdmap
357 // in addition to the ones stored in epochs.
358 //
359 // This is needed due to the way the monitor updates from paxos and the
360 // facilities we are leveraging to push this update to the rest of the
361 // quorum.
362 //
363 // In a nutshell, we are generating a good version of the osdmap, with a
364 // proper crush, and building a transaction that will replace the bad
365 // osdmaps with good osdmaps. But this transaction needs to be applied on
366 // all nodes, so that the monitors will have good osdmaps to share with
367 // clients. We thus leverage Paxos, specifically the recovery mechanism, by
368 // creating a pending value that will be committed once the monitors form an
369 // initial quorum after being brought back to life.
370 //
371 // However, the way the monitor works has the paxos services, including the
372 // OSDMonitor, updating their state from disk *prior* to the recovery phase
373 // begins (so they have an up to date state in memory). This means the
374 // OSDMonitor will see the old, broken map, before the new paxos version is
375 // applied to disk, and the old version is cached. Even though we have the
376 // good map now, and we share the good map with clients, we will still be
377 // working on the old broken map. Instead of mucking around the monitor to
378 // make this work, we instead opt for adding the same osdmap but with a
379 // newer version, so that the OSDMonitor picks up on it when it updates from
380 // paxos after the proposal has been committed. This is not elegant, but
381 // avoids further unpleasantness that would arise from kludging around the
382 // current behavior. Also, has the added benefit of making sure the clients
383 // get an updated version of the map (because last_committed+1 >
384 // last_committed) :)
385 //
386 cout << "adding a new epoch #" << last_committed+1 << std::endl;
387 r = update_osdmap(store, last_committed++, true, crush, t);
388 if (r)
389 return r;
390 t->put(prefix, store.combine_strings("full", "latest"), last_committed);
391 t->put(prefix, "last_committed", last_committed);
392 return 0;
393}
394
395/**
396 * create a new paxos version which carries a proposal to rewrite all epochs
397 * of incremental and full map of "osdmap" after a faulty crush map is injected.
398 * so the leader will trigger a recovery and propagate this fix to its peons,
399 * after the proposal is accepted, and the transaction in it is applied. all
400 * monitors will rewrite the bad crush map with the good one, and have a new
401 * osdmap epoch with the good crush map in it.
402 */
403int rewrite_crush(const char* progname,
404 vector<string>& subcmds,
405 MonitorDBStore& store) {
406 po::options_description op_desc("Allowed 'rewrite-crush' options");
407 int version = -1;
408 string crush_file;
409 op_desc.add_options()
410 ("help,h", "produce this help message")
411 ("crush", po::value<string>(&crush_file),
412 ("path to the crush map file "
413 "(default: will instead extract it from the known-good osdmap)"))
414 ("good-epoch", po::value<int>(&version),
415 "known-good epoch of osdmap, if a negative number '-N' is given, the "
416 "$last_committed-N is used instead (default: -1). "
417 "Please note, -1 is not necessarily a good epoch, because there are "
418 "good chance that we have more epochs slipped into the monstore after "
419 "the one where the crushmap is firstly injected.")
420 ;
421 po::variables_map op_vm;
422 int r = parse_cmd_args(&op_desc, NULL, NULL, subcmds, &op_vm);
423 if (r) {
424 return -r;
425 }
426 if (op_vm.count("help")) {
427 usage(progname, op_desc);
428 return 0;
429 }
430
431 MonitorDBStore::Transaction rewrite_txn;
432 r = rewrite_transaction(store, version, crush_file, &rewrite_txn);
433 if (r) {
434 return r;
435 }
436
437 // store the transaction into store as a proposal
438 const string prefix("paxos");
439 version_t pending_v = store.get(prefix, "last_committed") + 1;
440 auto t(std::make_shared<MonitorDBStore::Transaction>());
441 bufferlist bl;
442 rewrite_txn.encode(bl);
443 cout << "adding pending commit " << pending_v
444 << " " << bl.length() << " bytes" << std::endl;
445 t->put(prefix, pending_v, bl);
446 t->put(prefix, "pending_v", pending_v);
447 // a large enough yet unique proposal number will probably do the trick
448 version_t pending_pn = (store.get(prefix, "accepted_pn") / 100 + 4) * 100 + 1;
449 t->put(prefix, "pending_pn", pending_pn);
450 store.apply_transaction(t);
451 return 0;
452}
453
7c673cae
FG
454static int update_auth(MonitorDBStore& st, const string& keyring_path)
455{
456 // import all keyrings stored in the keyring file
457 KeyRing keyring;
458 int r = keyring.load(g_ceph_context, keyring_path);
459 if (r < 0) {
460 cerr << "unable to load admin keyring: " << keyring_path << std::endl;
461 return r;
462 }
463
464 bufferlist bl;
465 __u8 v = 1;
11fdf7f2 466 encode(v, bl);
7c673cae
FG
467
468 for (const auto& k : keyring.get_keys()) {
469 KeyServerData::Incremental auth_inc;
470 auth_inc.name = k.first;
471 auth_inc.auth = k.second;
472 if (auth_inc.auth.caps.empty()) {
473 cerr << "no caps granted to: " << auth_inc.name << std::endl;
474 return -EINVAL;
475 }
9f95a23c
TL
476 map<string,string> caps;
477 std::transform(begin(auth_inc.auth.caps), end(auth_inc.auth.caps),
478 inserter(caps, end(caps)),
479 [](auto& cap) {
480 string c;
481 auto p = cap.second.cbegin();
482 decode(c, p);
483 return make_pair(cap.first, c);
484 });
485 cout << "adding auth for '"
486 << auth_inc.name << "': " << auth_inc.auth
487 << " with caps(" << caps << ")" << std::endl;
7c673cae
FG
488 auth_inc.op = KeyServerData::AUTH_INC_ADD;
489
490 AuthMonitor::Incremental inc;
491 inc.inc_type = AuthMonitor::AUTH_DATA;
11fdf7f2 492 encode(auth_inc, inc.auth_data);
7c673cae 493 inc.auth_type = CEPH_AUTH_CEPHX;
7c673cae
FG
494 inc.encode(bl, CEPH_FEATURES_ALL);
495 }
496
a4b75251
TL
497 // prime rotating secrets
498 {
499 KeyServer ks(g_ceph_context, nullptr);
500 KeyServerData::Incremental auth_inc;
501 auth_inc.op = KeyServerData::AUTH_INC_SET_ROTATING;
502 bool r = ks.prepare_rotating_update(auth_inc.rotating_bl);
503 ceph_assert(r);
504 AuthMonitor::Incremental inc;
505 inc.inc_type = AuthMonitor::AUTH_DATA;
506 encode(auth_inc, inc.auth_data);
507 inc.auth_type = CEPH_AUTH_CEPHX;
508 inc.encode(bl, CEPH_FEATURES_ALL);
509 }
510
7c673cae
FG
511 const string prefix("auth");
512 auto last_committed = st.get(prefix, "last_committed") + 1;
513 auto t = make_shared<MonitorDBStore::Transaction>();
514 t->put(prefix, last_committed, bl);
515 t->put(prefix, "last_committed", last_committed);
516 auto first_committed = st.get(prefix, "first_committed");
517 if (!first_committed) {
518 t->put(prefix, "first_committed", last_committed);
519 }
520 st.apply_transaction(t);
521 return 0;
522}
523
92f5a8d4
TL
524static int update_mkfs(MonitorDBStore& st,
525 const string& monmap_path,
526 const vector<string>& mon_ids)
7c673cae
FG
527{
528 MonMap monmap;
11fdf7f2
TL
529 if (!monmap_path.empty()) {
530 cout << __func__ << " pulling initial monmap from " << monmap_path << std::endl;
531 bufferlist bl;
532 string err;
533 int r = bl.read_file(monmap_path.c_str(), &err);
534 if (r < 0) {
535 cerr << "failed to read monmap from " << monmap_path << ": "
536 << cpp_strerror(r) << std::endl;
537 return r;
538 }
539 monmap.decode(bl);
540 } else {
541 cout << __func__ << " generating seed initial monmap" << std::endl;
542 int r = monmap.build_initial(g_ceph_context, true, cerr);
543 if (r) {
544 cerr << "no initial monitors" << std::endl;
545 return -EINVAL;
546 }
92f5a8d4
TL
547 vector<string> new_names;
548 if (!mon_ids.empty()) {
549 if (mon_ids.size() != monmap.size()) {
550 cerr << "Please pass the same number of <mon-ids> to name the hosts "
551 << "listed in 'mon_host'. "
552 << mon_ids.size() << " mon-id(s) specified, "
553 << "while you have " << monmap.size() << " mon hosts." << std::endl;
554 return -EINVAL;
555 }
556 new_names = mon_ids;
557 } else {
558 for (unsigned rank = 0; rank < monmap.size(); rank++) {
559 string new_name{"a"};
560 new_name[0] += rank;
561 new_names.push_back(std::move(new_name));
562 }
563 }
564 for (unsigned rank = 0; rank < monmap.size(); rank++) {
565 auto name = monmap.get_name(rank);
566 if (name.compare(0, 7, "noname-") == 0) {
567 monmap.rename(name, new_names[rank]);
568 }
569 }
7c673cae 570 }
11fdf7f2 571 monmap.print(cout);
7c673cae
FG
572 bufferlist bl;
573 monmap.encode(bl, CEPH_FEATURES_ALL);
574 monmap.set_epoch(0);
575 auto t = make_shared<MonitorDBStore::Transaction>();
576 t->put("mkfs", "monmap", bl);
577 st.apply_transaction(t);
578 return 0;
579}
580
581static int update_monitor(MonitorDBStore& st)
582{
583 const string prefix("monitor");
584 // a stripped-down Monitor::mkfs()
585 bufferlist bl;
586 bl.append(CEPH_MON_ONDISK_MAGIC "\n");
587 auto t = make_shared<MonitorDBStore::Transaction>();
588 t->put(prefix, "magic", bl);
589 st.apply_transaction(t);
590 return 0;
591}
592
a8e16298
TL
593// rebuild
594// - creating_pgs
595static int update_creating_pgs(MonitorDBStore& st)
596{
597 bufferlist bl;
598 auto last_osdmap_epoch = st.get("osdmap", "last_committed");
599 int r = st.get("osdmap", st.combine_strings("full", last_osdmap_epoch), bl);
600 if (r < 0) {
9f95a23c 601 cerr << "unable to load osdmap e" << last_osdmap_epoch << std::endl;
a8e16298
TL
602 return r;
603 }
604
605 OSDMap osdmap;
606 osdmap.decode(bl);
607 creating_pgs_t creating;
608 for (auto& i : osdmap.get_pools()) {
609 creating.created_pools.insert(i.first);
610 }
611 creating.last_scan_epoch = last_osdmap_epoch;
612
613 bufferlist newbl;
9f95a23c 614 encode(creating, newbl, CEPH_FEATURES_ALL);
a8e16298
TL
615
616 auto t = make_shared<MonitorDBStore::Transaction>();
617 t->put("osd_pg_creating", "creating", newbl);
618 st.apply_transaction(t);
619 return 0;
620}
621
b32b8144
FG
622// rebuild
623// - mgr
624// - mgr_command_desc
3efd9988
FG
625static int update_mgrmap(MonitorDBStore& st)
626{
627 auto t = make_shared<MonitorDBStore::Transaction>();
628
629 {
630 MgrMap map;
631 // mgr expects epoch > 1
632 map.epoch++;
633 auto initial_modules =
11fdf7f2 634 get_str_vec(g_ceph_context->_conf.get_val<string>("mgr_initial_modules"));
3efd9988
FG
635 copy(begin(initial_modules),
636 end(initial_modules),
637 inserter(map.modules, end(map.modules)));
638 bufferlist bl;
639 map.encode(bl, CEPH_FEATURES_ALL);
640 t->put("mgr", map.epoch, bl);
641 t->put("mgr", "last_committed", map.epoch);
642 }
643 {
644 auto mgr_command_descs = mgr_commands;
645 for (auto& c : mgr_command_descs) {
646 c.set_flag(MonCommand::FLAG_MGR);
647 }
648 bufferlist bl;
11fdf7f2 649 encode(mgr_command_descs, bl);
92f5a8d4 650 t->put("mgr_command_descs", "", bl);
3efd9988
FG
651 }
652 return st.apply_transaction(t);
653}
654
7c673cae
FG
655static int update_paxos(MonitorDBStore& st)
656{
522d829b
TL
657 const string prefix("paxos");
658 // a large enough version greater than the maximum possible `last_committed`
659 // that could be replied by the peons when the leader is collecting paxos
660 // transactions during recovery
661 constexpr version_t first_committed = 0x42;
662 constexpr version_t last_committed = first_committed;
663 for (version_t v = first_committed; v < last_committed + 1; v++) {
664 auto t = make_shared<MonitorDBStore::Transaction>();
665 if (v == first_committed) {
666 t->put(prefix, "first_committed", v);
667 }
668 bufferlist proposal;
669 MonitorDBStore::Transaction empty_txn;
670 empty_txn.encode(proposal);
671 t->put(prefix, v, proposal);
672 t->put(prefix, "last_committed", v);
673 st.apply_transaction(t);
674 }
7c673cae
FG
675 // build a pending paxos proposal from all non-permanent k/v pairs. once the
676 // proposal is committed, it will gets applied. on the sync provider side, it
677 // will be a no-op, but on its peers, the paxos commit will help to build up
678 // the necessary epochs.
679 bufferlist pending_proposal;
680 {
681 MonitorDBStore::Transaction t;
682 vector<string> prefixes = {"auth", "osdmap",
a8e16298 683 "mgr", "mgr_command_desc"};
7c673cae
FG
684 for (const auto& prefix : prefixes) {
685 for (auto i = st.get_iterator(prefix); i->valid(); i->next()) {
686 auto key = i->raw_key();
687 auto val = i->value();
688 t.put(key.first, key.second, val);
689 }
690 }
691 t.encode(pending_proposal);
692 }
522d829b 693 auto pending_v = last_committed + 1;
7c673cae 694 auto t = make_shared<MonitorDBStore::Transaction>();
7c673cae
FG
695 t->put(prefix, pending_v, pending_proposal);
696 t->put(prefix, "pending_v", pending_v);
697 t->put(prefix, "pending_pn", 400);
698 st.apply_transaction(t);
699 return 0;
700}
701
7c673cae
FG
702int rebuild_monstore(const char* progname,
703 vector<string>& subcmds,
704 MonitorDBStore& st)
705{
706 po::options_description op_desc("Allowed 'rebuild' options");
707 string keyring_path;
11fdf7f2 708 string monmap_path;
92f5a8d4 709 vector<string> mon_ids;
7c673cae
FG
710 op_desc.add_options()
711 ("keyring", po::value<string>(&keyring_path),
11fdf7f2
TL
712 "path to the client.admin key")
713 ("monmap", po::value<string>(&monmap_path),
92f5a8d4
TL
714 "path to the initial monmap")
715 ("mon-ids", po::value<vector<string>>(&mon_ids)->multitoken(),
716 "mon ids, use 'a', 'b', ... if not specified");
717 po::positional_options_description pos_desc;
718 pos_desc.add("mon-ids", -1);
7c673cae 719 po::variables_map op_vm;
92f5a8d4 720 int r = parse_cmd_args(&op_desc, nullptr, &pos_desc, subcmds, &op_vm);
7c673cae
FG
721 if (r) {
722 return -r;
723 }
724 if (op_vm.count("help")) {
725 usage(progname, op_desc);
726 return 0;
727 }
728 if (!keyring_path.empty())
729 update_auth(st, keyring_path);
a8e16298 730 if ((r = update_creating_pgs(st))) {
7c673cae
FG
731 return r;
732 }
b32b8144
FG
733 if ((r = update_mgrmap(st))) {
734 return r;
735 }
7c673cae
FG
736 if ((r = update_paxos(st))) {
737 return r;
738 }
92f5a8d4 739 if ((r = update_mkfs(st, monmap_path, mon_ids))) {
7c673cae
FG
740 return r;
741 }
742 if ((r = update_monitor(st))) {
743 return r;
744 }
745 return 0;
746}
747
748int main(int argc, char **argv) {
749 int err = 0;
750 po::options_description desc("Allowed options");
751 string store_path, cmd;
752 vector<string> subcmds;
753 desc.add_options()
754 ("help,h", "produce help message")
755 ;
756
757 /* Dear Future Developer:
758 *
759 * for further improvement, should you need to pass specific options to
760 * a command (e.g., get osdmap VER --hex), you can expand the current
761 * format by creating additional 'po::option_description' and passing
762 * 'subcmds' to 'po::command_line_parser', much like what is currently
763 * done by default. However, beware: in order to differentiate a
764 * command-specific option from the generic/global options, you will need
765 * to pass '--' in the command line (so that the first parser, the one
766 * below, assumes it has reached the end of all options); e.g.,
767 * 'get osdmap VER -- --hex'. Not pretty; far from intuitive; it was as
768 * far as I got with this library. Improvements on this format will be
769 * left as an excercise for the reader. -Joao
770 */
771 po::options_description positional_desc("Positional argument options");
772 positional_desc.add_options()
773 ("store-path", po::value<string>(&store_path),
774 "path to monitor's store")
775 ("command", po::value<string>(&cmd),
776 "Command")
777 ("subcmd", po::value<vector<string> >(&subcmds),
778 "Command arguments/Sub-Commands")
779 ;
780 po::positional_options_description positional;
781 positional.add("store-path", 1);
782 positional.add("command", 1);
783 positional.add("subcmd", -1);
784
785 po::options_description all_desc("All options");
786 all_desc.add(desc).add(positional_desc);
787
788 vector<string> ceph_option_strings;
789 po::variables_map vm;
790 try {
791 po::parsed_options parsed =
792 po::command_line_parser(argc, argv).
793 options(all_desc).
794 positional(positional).
795 allow_unregistered().run();
796
797 po::store(
798 parsed,
799 vm);
800 po::notify(vm);
801
802 // Specifying po::include_positional would have our positional arguments
803 // being collected (thus being part of ceph_option_strings and eventually
804 // passed on to global_init() below).
805 // Instead we specify po::exclude_positional, which has the upside of
806 // completely avoid this, but the downside of having to specify ceph
807 // options as --VAR=VAL (note the '='); otherwise we will capture the
808 // positional 'VAL' as belonging to us, never being collected.
809 ceph_option_strings = po::collect_unrecognized(parsed.options,
810 po::exclude_positional);
811
812 } catch(po::error &e) {
813 std::cerr << "error: " << e.what() << std::endl;
814 return 1;
815 }
816
817 // parse command structure before calling global_init() and friends.
818
819 if (vm.empty() || vm.count("help") ||
820 store_path.empty() || cmd.empty() ||
821 *cmd.begin() == '-') {
822 usage(argv[0], desc);
823 return 1;
824 }
825
11fdf7f2 826 vector<const char *> ceph_options;
7c673cae
FG
827 ceph_options.reserve(ceph_option_strings.size());
828 for (vector<string>::iterator i = ceph_option_strings.begin();
829 i != ceph_option_strings.end();
830 ++i) {
831 ceph_options.push_back(i->c_str());
832 }
833
834 auto cct = global_init(
11fdf7f2
TL
835 NULL, ceph_options, CEPH_ENTITY_TYPE_MON,
836 CODE_ENVIRONMENT_UTILITY,
837 CINIT_FLAG_NO_MON_CONFIG);
7c673cae 838 common_init_finish(g_ceph_context);
11fdf7f2 839 cct->_conf.apply_changes(nullptr);
7c673cae
FG
840
841 // this is where we'll write *whatever*, on a per-command basis.
842 // not all commands require some place to write their things.
843 MonitorDBStore st(store_path);
844 if (store_path.size()) {
845 stringstream ss;
846 int r = st.open(ss);
847 if (r < 0) {
848 std::cerr << ss.str() << std::endl;
849 return EINVAL;
850 }
851 }
852
853 if (cmd == "dump-keys") {
854 KeyValueDB::WholeSpaceIterator iter = st.get_iterator();
855 while (iter->valid()) {
856 pair<string,string> key(iter->raw_key());
857 cout << key.first << " / " << key.second << std::endl;
858 iter->next();
859 }
860 } else if (cmd == "compact") {
861 st.compact();
862 } else if (cmd == "get") {
863 unsigned v = 0;
864 string outpath;
7c673cae
FG
865 string map_type;
866 // visible options for this command
867 po::options_description op_desc("Allowed 'get' options");
868 op_desc.add_options()
869 ("help,h", "produce this help message")
870 ("out,o", po::value<string>(&outpath),
871 "output file (default: stdout)")
872 ("version,v", po::value<unsigned>(&v),
873 "map version to obtain")
9f95a23c 874 ("readable,r", "print the map information in human readable format")
7c673cae
FG
875 ;
876 // this is going to be a positional argument; we don't want to show
877 // it as an option during --help, but we do want to have it captured
878 // when parsing.
879 po::options_description hidden_op_desc("Hidden 'get' options");
880 hidden_op_desc.add_options()
881 ("map-type", po::value<string>(&map_type),
882 "map-type")
883 ;
884 po::positional_options_description op_positional;
885 op_positional.add("map-type", 1);
886
887 po::variables_map op_vm;
888 int r = parse_cmd_args(&op_desc, &hidden_op_desc, &op_positional,
889 subcmds, &op_vm);
890 if (r < 0) {
891 err = -r;
892 goto done;
893 }
894
895 if (op_vm.count("help") || map_type.empty()) {
896 usage(argv[0], op_desc);
897 err = 0;
898 goto done;
899 }
900
901 if (v == 0) {
902 if (map_type == "crushmap") {
903 v = st.get("osdmap", "last_committed");
904 } else {
905 v = st.get(map_type, "last_committed");
906 }
907 }
908
909 int fd = STDOUT_FILENO;
910 if (!outpath.empty()){
f67539c2 911 fd = ::open(outpath.c_str(), O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0666);
7c673cae
FG
912 if (fd < 0) {
913 std::cerr << "error opening output file: "
914 << cpp_strerror(errno) << std::endl;
915 err = EINVAL;
916 goto done;
917 }
918 }
919
920 BOOST_SCOPE_EXIT((&r) (&fd) (&outpath)) {
921 ::close(fd);
922 if (r < 0 && fd != STDOUT_FILENO) {
923 ::remove(outpath.c_str());
924 }
925 } BOOST_SCOPE_EXIT_END
926
927 bufferlist bl;
928 r = 0;
929 if (map_type == "osdmap") {
930 r = st.get(map_type, st.combine_strings("full", v), bl);
931 } else if (map_type == "crushmap") {
932 bufferlist tmp;
933 r = st.get("osdmap", st.combine_strings("full", v), tmp);
934 if (r >= 0) {
935 OSDMap osdmap;
936 osdmap.decode(tmp);
937 osdmap.crush->encode(bl, CEPH_FEATURES_SUPPORTED_DEFAULT);
938 }
939 } else {
940 r = st.get(map_type, v, bl);
941 }
942 if (r < 0) {
943 std::cerr << "Error getting map: " << cpp_strerror(r) << std::endl;
944 err = EINVAL;
945 goto done;
946 }
947
9f95a23c 948 if (op_vm.count("readable")) {
7c673cae
FG
949 stringstream ss;
950 bufferlist out;
b32b8144
FG
951 try {
952 if (map_type == "monmap") {
953 MonMap monmap;
954 monmap.decode(bl);
955 monmap.print(ss);
956 } else if (map_type == "osdmap") {
957 OSDMap osdmap;
958 osdmap.decode(bl);
959 osdmap.print(ss);
960 } else if (map_type == "mdsmap") {
961 FSMap fs_map;
962 fs_map.decode(bl);
963 fs_map.print(ss);
964 } else if (map_type == "mgr") {
965 MgrMap mgr_map;
11fdf7f2 966 auto p = bl.cbegin();
b32b8144
FG
967 mgr_map.decode(p);
968 JSONFormatter f;
969 f.dump_object("mgrmap", mgr_map);
970 f.flush(ss);
971 } else if (map_type == "crushmap") {
972 CrushWrapper cw;
11fdf7f2 973 auto it = bl.cbegin();
b32b8144
FG
974 cw.decode(it);
975 CrushCompiler cc(cw, std::cerr, 0);
976 cc.decompile(ss);
977 } else {
978 std::cerr << "This type of readable map does not exist: " << map_type
979 << std::endl << "You can only specify[osdmap|monmap|mdsmap"
980 "|crushmap|mgr]" << std::endl;
981 }
982 } catch (const buffer::error &err) {
983 std::cerr << "Could not decode for human readable output (you may still"
f67539c2 984 " use non-readable mode). Detail: " << err.what() << std::endl;
7c673cae 985 }
b32b8144 986
7c673cae
FG
987 out.append(ss);
988 out.write_fd(fd);
989 } else {
990 bl.write_fd(fd);
991 }
992
993 if (!outpath.empty()) {
994 std::cout << "wrote " << map_type
995 << " version " << v << " to " << outpath
996 << std::endl;
997 }
998 } else if (cmd == "show-versions") {
999 string map_type; //map type:osdmap,monmap...
1000 // visible options for this command
1001 po::options_description op_desc("Allowed 'show-versions' options");
1002 op_desc.add_options()
1003 ("help,h", "produce this help message")
1004 ("map-type", po::value<string>(&map_type), "map_type");
1005
1006 po::positional_options_description op_positional;
1007 op_positional.add("map-type", 1);
1008
1009 po::variables_map op_vm;
1010 int r = parse_cmd_args(&op_desc, NULL, &op_positional,
1011 subcmds, &op_vm);
1012 if (r < 0) {
1013 err = -r;
1014 goto done;
1015 }
1016
1017 if (op_vm.count("help") || map_type.empty()) {
1018 usage(argv[0], op_desc);
1019 err = 0;
1020 goto done;
1021 }
1022
1023 unsigned int v_first = 0;
1024 unsigned int v_last = 0;
1025 v_first = st.get(map_type, "first_committed");
1026 v_last = st.get(map_type, "last_committed");
1027
1028 std::cout << "first committed:\t" << v_first << "\n"
1029 << "last committed:\t" << v_last << std::endl;
1030 } else if (cmd == "dump-paxos") {
1031 unsigned dstart = 0;
1032 unsigned dstop = ~0;
1033 po::options_description op_desc("Allowed 'dump-paxos' options");
1034 op_desc.add_options()
1035 ("help,h", "produce this help message")
1036 ("start,s", po::value<unsigned>(&dstart),
1037 "starting version (default: 0)")
1038 ("end,e", po::value<unsigned>(&dstop),
1039 "finish version (default: ~0)")
1040 ;
1041
1042 po::variables_map op_vm;
1043 int r = parse_cmd_args(&op_desc, NULL, NULL,
1044 subcmds, &op_vm);
1045 if (r < 0) {
1046 err = -r;
1047 goto done;
1048 }
1049
1050 if (op_vm.count("help")) {
1051 usage(argv[0], op_desc);
1052 err = 0;
1053 goto done;
1054 }
1055
1056 if (dstart > dstop) {
1057 std::cerr << "error: 'start' version (value: " << dstart << ") "
1058 << " is greater than 'end' version (value: " << dstop << ")"
1059 << std::endl;
1060 err = EINVAL;
1061 goto done;
1062 }
1063
1064 version_t v = dstart;
1065 for (; v <= dstop; ++v) {
1066 bufferlist bl;
1067 st.get("paxos", v, bl);
1068 if (bl.length() == 0)
1069 break;
1070 cout << "\n--- " << v << " ---" << std::endl;
1071 auto tx(std::make_shared<MonitorDBStore::Transaction>());
1072 Paxos::decode_append_transaction(tx, bl);
1073 JSONFormatter f(true);
1074 tx->dump(&f);
1075 f.flush(cout);
1076 }
1077
1078 std::cout << "dumped " << v << " paxos versions" << std::endl;
1079
1080 } else if (cmd == "dump-trace") {
1081 unsigned dstart = 0;
1082 unsigned dstop = ~0;
1083 string outpath;
1084
1085 // visible options for this command
1086 po::options_description op_desc("Allowed 'dump-trace' options");
1087 op_desc.add_options()
1088 ("help,h", "produce this help message")
1089 ("start,s", po::value<unsigned>(&dstart),
1090 "starting version (default: 0)")
1091 ("end,e", po::value<unsigned>(&dstop),
1092 "finish version (default: ~0)")
1093 ;
1094 // this is going to be a positional argument; we don't want to show
1095 // it as an option during --help, but we do want to have it captured
1096 // when parsing.
1097 po::options_description hidden_op_desc("Hidden 'dump-trace' options");
1098 hidden_op_desc.add_options()
1099 ("out,o", po::value<string>(&outpath),
1100 "file to write the dump to")
1101 ;
1102 po::positional_options_description op_positional;
1103 op_positional.add("out", 1);
1104
1105 po::variables_map op_vm;
1106 int r = parse_cmd_args(&op_desc, &hidden_op_desc, &op_positional,
1107 subcmds, &op_vm);
1108 if (r < 0) {
1109 err = -r;
1110 goto done;
1111 }
1112
1113 if (op_vm.count("help")) {
1114 usage(argv[0], op_desc);
1115 err = 0;
1116 goto done;
1117 }
1118
1119 if (outpath.empty()) {
1120 usage(argv[0], op_desc);
1121 err = EINVAL;
1122 goto done;
1123 }
1124
1125 if (dstart > dstop) {
1126 std::cerr << "error: 'start' version (value: " << dstart << ") "
1127 << " is greater than 'stop' version (value: " << dstop << ")"
1128 << std::endl;
1129 err = EINVAL;
1130 goto done;
1131 }
1132
1133 TraceIter iter(outpath.c_str());
1134 iter.init();
1135 while (true) {
1136 if (!iter.valid())
1137 break;
1138 if (iter.num() >= dstop) {
1139 break;
1140 }
1141 if (iter.num() >= dstart) {
1142 JSONFormatter f(true);
1143 iter.cur()->dump(&f, false);
1144 f.flush(std::cout);
1145 std::cout << std::endl;
1146 }
1147 iter.next();
1148 }
1149 std::cerr << "Read up to transaction " << iter.num() << std::endl;
1150 } else if (cmd == "replay-trace") {
1151 string inpath;
1152 unsigned num_replays = 1;
1153 // visible options for this command
1154 po::options_description op_desc("Allowed 'replay-trace' options");
1155 op_desc.add_options()
1156 ("help,h", "produce this help message")
1157 ("num-replays,n", po::value<unsigned>(&num_replays),
1158 "finish version (default: 1)")
1159 ;
1160 // this is going to be a positional argument; we don't want to show
1161 // it as an option during --help, but we do want to have it captured
1162 // when parsing.
1163 po::options_description hidden_op_desc("Hidden 'replay-trace' options");
1164 hidden_op_desc.add_options()
1165 ("in,i", po::value<string>(&inpath),
1166 "file to write the dump to")
1167 ;
1168 po::positional_options_description op_positional;
1169 op_positional.add("in", 1);
1170
1171 // op_desc_all will aggregate all visible and hidden options for parsing.
1172 // when we call 'usage()' we just pass 'op_desc', as that's the description
1173 // holding the visible options.
1174 po::options_description op_desc_all;
1175 op_desc_all.add(op_desc).add(hidden_op_desc);
1176
1177 po::variables_map op_vm;
1178 try {
1179 po::parsed_options op_parsed = po::command_line_parser(subcmds).
1180 options(op_desc_all).positional(op_positional).run();
1181 po::store(op_parsed, op_vm);
1182 po::notify(op_vm);
1183 } catch (po::error &e) {
1184 std::cerr << "error: " << e.what() << std::endl;
1185 err = EINVAL;
1186 goto done;
1187 }
1188
1189 if (op_vm.count("help")) {
1190 usage(argv[0], op_desc);
1191 err = 0;
1192 goto done;
1193 }
1194
1195 if (inpath.empty()) {
1196 usage(argv[0], op_desc);
1197 err = EINVAL;
1198 goto done;
1199 }
1200
1201 unsigned num = 0;
1202 for (unsigned i = 0; i < num_replays; ++i) {
1203 TraceIter iter(inpath.c_str());
1204 iter.init();
1205 while (true) {
1206 if (!iter.valid())
1207 break;
1208 std::cerr << "Replaying trans num " << num << std::endl;
1209 st.apply_transaction(iter.cur());
1210 iter.next();
1211 ++num;
1212 }
1213 std::cerr << "Read up to transaction " << iter.num() << std::endl;
1214 }
1215 } else if (cmd == "random-gen") {
1216 unsigned tsize = 200;
1217 unsigned tvalsize = 1024;
1218 unsigned ntrans = 100;
1219 po::options_description op_desc("Allowed 'random-gen' options");
1220 op_desc.add_options()
1221 ("help,h", "produce this help message")
1222 ("num-keys,k", po::value<unsigned>(&tsize),
1223 "keys to write in each transaction (default: 200)")
1224 ("size,s", po::value<unsigned>(&tvalsize),
1225 "size (in bytes) of the value to write in each key (default: 1024)")
1226 ("ntrans,n", po::value<unsigned>(&ntrans),
1227 "number of transactions to run (default: 100)")
1228 ;
1229
1230 po::variables_map op_vm;
1231 try {
1232 po::parsed_options op_parsed = po::command_line_parser(subcmds).
1233 options(op_desc).run();
1234 po::store(op_parsed, op_vm);
1235 po::notify(op_vm);
1236 } catch (po::error &e) {
1237 std::cerr << "error: " << e.what() << std::endl;
1238 err = EINVAL;
1239 goto done;
1240 }
1241
1242 if (op_vm.count("help")) {
1243 usage(argv[0], op_desc);
1244 err = 0;
1245 goto done;
1246 }
1247
1248 unsigned num = 0;
1249 for (unsigned i = 0; i < ntrans; ++i) {
1250 std::cerr << "Applying trans " << i << std::endl;
1251 auto t(std::make_shared<MonitorDBStore::Transaction>());
1252 string prefix;
1253 prefix.push_back((i%26)+'a');
1254 for (unsigned j = 0; j < tsize; ++j) {
1255 stringstream os;
1256 os << num;
1257 bufferlist bl;
1258 for (unsigned k = 0; k < tvalsize; ++k) bl.append(rand());
1259 t->put(prefix, os.str(), bl);
1260 ++num;
1261 }
1262 t->compact_prefix(prefix);
1263 st.apply_transaction(t);
1264 }
1265 } else if (cmd == "store-copy") {
1266 if (subcmds.size() < 1 || subcmds[0].empty()) {
1267 usage(argv[0], desc);
1268 err = EINVAL;
1269 goto done;
1270 }
1271
1272 string out_path = subcmds[0];
1273
1274 MonitorDBStore out_store(out_path);
1275 {
1276 stringstream ss;
1277 int r = out_store.create_and_open(ss);
1278 if (r < 0) {
1279 std::cerr << ss.str() << std::endl;
1280 goto done;
1281 }
1282 }
1283
1284
1285 KeyValueDB::WholeSpaceIterator it = st.get_iterator();
1286 uint64_t total_keys = 0;
1287 uint64_t total_size = 0;
1288 uint64_t total_tx = 0;
1289
1290 do {
1291 uint64_t num_keys = 0;
1292
1293 auto tx(std::make_shared<MonitorDBStore::Transaction>());
1294
1295 while (it->valid() && num_keys < 128) {
1296 pair<string,string> k = it->raw_key();
1297 bufferlist v = it->value();
1298 tx->put(k.first, k.second, v);
1299
1300 num_keys ++;
1301 total_tx ++;
1302 total_size += v.length();
1303
1304 it->next();
1305 }
1306
1307 total_keys += num_keys;
1308
1309 if (!tx->empty())
1310 out_store.apply_transaction(tx);
1311
1312 std::cout << "copied " << total_keys << " keys so far ("
1adf2230 1313 << stringify(byte_u_t(total_size)) << ")" << std::endl;
7c673cae
FG
1314
1315 } while (it->valid());
1316 out_store.close();
1317 std::cout << "summary: copied " << total_keys << " keys, using "
1318 << total_tx << " transactions, totalling "
1adf2230 1319 << stringify(byte_u_t(total_size)) << std::endl;
7c673cae
FG
1320 std::cout << "from '" << store_path << "' to '" << out_path << "'"
1321 << std::endl;
1322 } else if (cmd == "rewrite-crush") {
1323 err = rewrite_crush(argv[0], subcmds, st);
7c673cae
FG
1324 } else if (cmd == "rebuild") {
1325 err = rebuild_monstore(argv[0], subcmds, st);
1326 } else {
1327 std::cerr << "Unrecognized command: " << cmd << std::endl;
1328 usage(argv[0], desc);
1329 goto done;
1330 }
1331
1332 done:
1333 st.close();
1334 return err;
1335}