]> git.proxmox.com Git - ceph.git/blob - ceph/src/mds/events/EMetaBlob.h
d555627a3c638419a672263337683b30e4844407
[ceph.git] / ceph / src / mds / events / EMetaBlob.h
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) 2004-2006 Sage Weil <sage@newdream.net>
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 */
14
15 #ifndef CEPH_MDS_EMETABLOB_H
16 #define CEPH_MDS_EMETABLOB_H
17
18 #include <string_view>
19
20 #include "../CInode.h"
21 #include "../CDir.h"
22 #include "../CDentry.h"
23 #include "../LogSegment.h"
24
25 #include "include/interval_set.h"
26 #include "common/strescape.h"
27
28 class MDSRank;
29 class MDLog;
30 class LogSegment;
31 struct MDPeerUpdate;
32
33 /*
34 * a bunch of metadata in the journal
35 */
36
37 /* notes:
38 *
39 * - make sure you adjust the inode.version for any modified inode you
40 * journal. CDir and CDentry maintain a projected_version, but CInode
41 * doesn't, since the journaled inode usually has to be modified
42 * manually anyway (to delay the change in the MDS's cache until after
43 * it is journaled).
44 *
45 */
46
47
48 class EMetaBlob {
49
50 public:
51 /* fullbit - a regular dentry + inode
52 *
53 * We encode this one a bit weirdly, just because (also, it's marginally faster
54 * on multiple encodes, which I think can happen):
55 * Encode a bufferlist on struct creation with all data members, without a struct_v.
56 * When encode is called, encode struct_v and then append the bufferlist.
57 * Decode straight into the appropriate variables.
58 *
59 * So, if you add members, encode them in the constructor and then change
60 * the struct_v in the encode function!
61 */
62 struct fullbit {
63 static const int STATE_DIRTY = (1<<0);
64 static const int STATE_DIRTYPARENT = (1<<1);
65 static const int STATE_DIRTYPOOL = (1<<2);
66 static const int STATE_NEED_SNAPFLUSH = (1<<3);
67 static const int STATE_EPHEMERAL_RANDOM = (1<<4);
68 std::string dn; // dentry
69 std::string alternate_name;
70 snapid_t dnfirst, dnlast;
71 version_t dnv{0};
72 CInode::inode_const_ptr inode; // if it's not XXX should not be part of mempool; wait for std::pmr to simplify
73 CInode::xattr_map_const_ptr xattrs;
74 fragtree_t dirfragtree;
75 std::string symlink;
76 snapid_t oldest_snap;
77 bufferlist snapbl;
78 __u8 state{0};
79 CInode::old_inode_map_const_ptr old_inodes; // XXX should not be part of mempool; wait for std::pmr to simplify
80
81 fullbit(std::string_view d, std::string_view an, snapid_t df, snapid_t dl,
82 version_t v, const CInode::inode_const_ptr& i, const fragtree_t &dft,
83 const CInode::xattr_map_const_ptr& xa, std::string_view sym,
84 snapid_t os, const bufferlist &sbl, __u8 st,
85 const CInode::old_inode_map_const_ptr& oi) :
86 dn(d), alternate_name(an), dnfirst(df), dnlast(dl), dnv(v), inode(i), xattrs(xa),
87 oldest_snap(os), state(st), old_inodes(oi)
88 {
89 if (i->is_symlink())
90 symlink = sym;
91 if (i->is_dir())
92 dirfragtree = dft;
93 snapbl = sbl;
94 }
95 explicit fullbit(bufferlist::const_iterator &p) {
96 decode(p);
97 }
98 fullbit() = default;
99 fullbit(const fullbit&) = delete;
100 ~fullbit() {}
101 fullbit& operator=(const fullbit&) = delete;
102
103 void encode(bufferlist& bl, uint64_t features) const;
104 void decode(bufferlist::const_iterator &bl);
105 void dump(Formatter *f) const;
106 static void generate_test_instances(std::list<EMetaBlob::fullbit*>& ls);
107
108 void update_inode(MDSRank *mds, CInode *in);
109 bool is_dirty() const { return (state & STATE_DIRTY); }
110 bool is_dirty_parent() const { return (state & STATE_DIRTYPARENT); }
111 bool is_dirty_pool() const { return (state & STATE_DIRTYPOOL); }
112 bool need_snapflush() const { return (state & STATE_NEED_SNAPFLUSH); }
113 bool is_export_ephemeral_random() const { return (state & STATE_EPHEMERAL_RANDOM); }
114
115 void print(ostream& out) const {
116 out << " fullbit dn " << dn << " [" << dnfirst << "," << dnlast << "] dnv " << dnv
117 << " inode " << inode->ino
118 << " state=" << state;
119 if (!alternate_name.empty()) {
120 out << " altn " << binstrprint(alternate_name, 8);
121 }
122 out << std::endl;
123 }
124 string state_string() const {
125 string state_string;
126 bool marked_already = false;
127 if (is_dirty()) {
128 state_string.append("dirty");
129 marked_already = true;
130 }
131 if (is_dirty_parent()) {
132 state_string.append(marked_already ? "+dirty_parent" : "dirty_parent");
133 if (is_dirty_pool())
134 state_string.append("+dirty_pool");
135 }
136 return state_string;
137 }
138 };
139 WRITE_CLASS_ENCODER_FEATURES(fullbit)
140
141 /* remotebit - a dentry + remote inode link (i.e. just an ino)
142 */
143 struct remotebit {
144 std::string dn;
145 std::string alternate_name;
146 snapid_t dnfirst = 0, dnlast = 0;
147 version_t dnv = 0;
148 inodeno_t ino = 0;
149 unsigned char d_type = '\0';
150 bool dirty = false;
151
152 remotebit(std::string_view d, std::string_view an, snapid_t df, snapid_t dl, version_t v, inodeno_t i, unsigned char dt, bool dr) :
153 dn(d), alternate_name(an), dnfirst(df), dnlast(dl), dnv(v), ino(i), d_type(dt), dirty(dr) { }
154 explicit remotebit(bufferlist::const_iterator &p) { decode(p); }
155 remotebit() = default;
156
157 void encode(bufferlist& bl) const;
158 void decode(bufferlist::const_iterator &bl);
159 void print(ostream& out) const {
160 out << " remotebit dn " << dn << " [" << dnfirst << "," << dnlast << "] dnv " << dnv
161 << " ino " << ino
162 << " dirty=" << dirty;
163 if (!alternate_name.empty()) {
164 out << " altn " << binstrprint(alternate_name, 8);
165 }
166 out << std::endl;
167 }
168 void dump(Formatter *f) const;
169 static void generate_test_instances(std::list<remotebit*>& ls);
170 };
171 WRITE_CLASS_ENCODER(remotebit)
172
173 /*
174 * nullbit - a null dentry
175 */
176 struct nullbit {
177 std::string dn;
178 snapid_t dnfirst, dnlast;
179 version_t dnv;
180 bool dirty;
181
182 nullbit(std::string_view d, snapid_t df, snapid_t dl, version_t v, bool dr) :
183 dn(d), dnfirst(df), dnlast(dl), dnv(v), dirty(dr) { }
184 explicit nullbit(bufferlist::const_iterator &p) { decode(p); }
185 nullbit(): dnfirst(0), dnlast(0), dnv(0), dirty(false) {}
186
187 void encode(bufferlist& bl) const;
188 void decode(bufferlist::const_iterator &bl);
189 void dump(Formatter *f) const;
190 static void generate_test_instances(std::list<nullbit*>& ls);
191 void print(ostream& out) const {
192 out << " nullbit dn " << dn << " [" << dnfirst << "," << dnlast << "] dnv " << dnv
193 << " dirty=" << dirty << std::endl;
194 }
195 };
196 WRITE_CLASS_ENCODER(nullbit)
197
198
199 /* dirlump - contains metadata for any dir we have contents for.
200 */
201 public:
202 struct dirlump {
203 static const int STATE_COMPLETE = (1<<1);
204 static const int STATE_DIRTY = (1<<2); // dirty due to THIS journal item, that is!
205 static const int STATE_NEW = (1<<3); // new directory
206 static const int STATE_IMPORTING = (1<<4); // importing directory
207 static const int STATE_DIRTYDFT = (1<<5); // dirty dirfragtree
208
209 //version_t dirv;
210 CDir::fnode_const_ptr fnode;
211 __u32 state;
212 __u32 nfull, nremote, nnull;
213
214 private:
215 mutable bufferlist dnbl;
216 mutable bool dn_decoded;
217 mutable list<fullbit> dfull;
218 mutable vector<remotebit> dremote;
219 mutable vector<nullbit> dnull;
220
221 public:
222 dirlump() : state(0), nfull(0), nremote(0), nnull(0), dn_decoded(true) { }
223 dirlump(const dirlump&) = delete;
224 dirlump& operator=(const dirlump&) = delete;
225
226 bool is_complete() const { return state & STATE_COMPLETE; }
227 void mark_complete() { state |= STATE_COMPLETE; }
228 bool is_dirty() const { return state & STATE_DIRTY; }
229 void mark_dirty() { state |= STATE_DIRTY; }
230 bool is_new() const { return state & STATE_NEW; }
231 void mark_new() { state |= STATE_NEW; }
232 bool is_importing() { return state & STATE_IMPORTING; }
233 void mark_importing() { state |= STATE_IMPORTING; }
234 bool is_dirty_dft() { return state & STATE_DIRTYDFT; }
235 void mark_dirty_dft() { state |= STATE_DIRTYDFT; }
236
237 const list<fullbit> &get_dfull() const { return dfull; }
238 list<fullbit> &_get_dfull() { return dfull; }
239 const vector<remotebit> &get_dremote() const { return dremote; }
240 const vector<nullbit> &get_dnull() const { return dnull; }
241
242 template< class... Args>
243 void add_dfull(Args&&... args) {
244 dfull.emplace_back(std::forward<Args>(args)...);
245 }
246 template< class... Args>
247 void add_dremote(Args&&... args) {
248 dremote.emplace_back(std::forward<Args>(args)...);
249 }
250 template< class... Args>
251 void add_dnull(Args&&... args) {
252 dnull.emplace_back(std::forward<Args>(args)...);
253 }
254
255 void print(dirfrag_t dirfrag, ostream& out) const {
256 out << "dirlump " << dirfrag << " v " << fnode->version
257 << " state " << state
258 << " num " << nfull << "/" << nremote << "/" << nnull
259 << std::endl;
260 _decode_bits();
261 for (const auto& p : dfull)
262 p.print(out);
263 for (const auto& p : dremote)
264 p.print(out);
265 for (const auto& p : dnull)
266 p.print(out);
267 }
268
269 string state_string() const {
270 string state_string;
271 bool marked_already = false;
272 if (is_complete()) {
273 state_string.append("complete");
274 marked_already = true;
275 }
276 if (is_dirty()) {
277 state_string.append(marked_already ? "+dirty" : "dirty");
278 marked_already = true;
279 }
280 if (is_new()) {
281 state_string.append(marked_already ? "+new" : "new");
282 }
283 return state_string;
284 }
285
286 // if this changes, update the versioning in encode for it!
287 void _encode_bits(uint64_t features) const {
288 using ceph::encode;
289 if (!dn_decoded) return;
290 encode(dfull, dnbl, features);
291 encode(dremote, dnbl);
292 encode(dnull, dnbl);
293 }
294 void _decode_bits() const {
295 using ceph::decode;
296 if (dn_decoded) return;
297 auto p = dnbl.cbegin();
298 decode(dfull, p);
299 decode(dremote, p);
300 decode(dnull, p);
301 dn_decoded = true;
302 }
303
304 void encode(bufferlist& bl, uint64_t features) const;
305 void decode(bufferlist::const_iterator &bl);
306 void dump(Formatter *f) const;
307 static void generate_test_instances(std::list<dirlump*>& ls);
308 };
309 WRITE_CLASS_ENCODER_FEATURES(dirlump)
310
311 // my lumps. preserve the order we added them in a list.
312 vector<dirfrag_t> lump_order;
313 map<dirfrag_t, dirlump> lump_map;
314 list<fullbit> roots;
315 public:
316 vector<pair<__u8,version_t> > table_tids; // tableclient transactions
317
318 inodeno_t opened_ino;
319 public:
320 inodeno_t renamed_dirino;
321 vector<frag_t> renamed_dir_frags;
322 private:
323
324 // ino (pre)allocation. may involve both inotable AND session state.
325 version_t inotablev, sessionmapv;
326 inodeno_t allocated_ino; // inotable
327 interval_set<inodeno_t> preallocated_inos; // inotable + session
328 inodeno_t used_preallocated_ino; // session
329 entity_name_t client_name; // session
330
331 // inodes i've truncated
332 vector<inodeno_t> truncate_start; // start truncate
333 map<inodeno_t, LogSegment::seq_t> truncate_finish; // finished truncate (started in segment blah)
334
335 public:
336 vector<inodeno_t> destroyed_inodes;
337 private:
338
339 // idempotent op(s)
340 vector<pair<metareqid_t,uint64_t> > client_reqs;
341 vector<pair<metareqid_t,uint64_t> > client_flushes;
342
343 public:
344 void encode(bufferlist& bl, uint64_t features) const;
345 void decode(bufferlist::const_iterator& bl);
346 void get_inodes(std::set<inodeno_t> &inodes) const;
347 void get_paths(std::vector<std::string> &paths) const;
348 void get_dentries(std::map<dirfrag_t, std::set<std::string> > &dentries) const;
349 entity_name_t get_client_name() const {return client_name;}
350
351 void dump(Formatter *f) const;
352 static void generate_test_instances(std::list<EMetaBlob*>& ls);
353 // soft stateadd
354 uint64_t last_subtree_map;
355 uint64_t event_seq;
356
357 // for replay, in certain cases
358 //LogSegment *_segment;
359
360 EMetaBlob() : opened_ino(0), renamed_dirino(0),
361 inotablev(0), sessionmapv(0), allocated_ino(0),
362 last_subtree_map(0), event_seq(0)
363 {}
364 EMetaBlob(const EMetaBlob&) = delete;
365 ~EMetaBlob() { }
366 EMetaBlob& operator=(const EMetaBlob&) = delete;
367
368 void print(ostream& out) {
369 for (const auto &p : lump_order)
370 lump_map[p].print(p, out);
371 }
372
373 void add_client_req(metareqid_t r, uint64_t tid=0) {
374 client_reqs.push_back(pair<metareqid_t,uint64_t>(r, tid));
375 }
376 void add_client_flush(metareqid_t r, uint64_t tid=0) {
377 client_flushes.push_back(pair<metareqid_t,uint64_t>(r, tid));
378 }
379
380 void add_table_transaction(int table, version_t tid) {
381 table_tids.push_back(pair<__u8, version_t>(table, tid));
382 }
383
384 void add_opened_ino(inodeno_t ino) {
385 ceph_assert(!opened_ino);
386 opened_ino = ino;
387 }
388
389 void set_ino_alloc(inodeno_t alloc,
390 inodeno_t used_prealloc,
391 interval_set<inodeno_t>& prealloc,
392 entity_name_t client,
393 version_t sv, version_t iv) {
394 allocated_ino = alloc;
395 used_preallocated_ino = used_prealloc;
396 preallocated_inos = prealloc;
397 client_name = client;
398 sessionmapv = sv;
399 inotablev = iv;
400 }
401
402 void add_truncate_start(inodeno_t ino) {
403 truncate_start.push_back(ino);
404 }
405 void add_truncate_finish(inodeno_t ino, uint64_t segoff) {
406 truncate_finish[ino] = segoff;
407 }
408
409 bool rewrite_truncate_finish(MDSRank const *mds, std::map<uint64_t, uint64_t> const &old_to_new);
410
411 void add_destroyed_inode(inodeno_t ino) {
412 destroyed_inodes.push_back(ino);
413 }
414
415 void add_null_dentry(CDentry *dn, bool dirty) {
416 add_null_dentry(add_dir(dn->get_dir(), false), dn, dirty);
417 }
418 void add_null_dentry(dirlump& lump, CDentry *dn, bool dirty) {
419 // add the dir
420 lump.nnull++;
421 lump.add_dnull(dn->get_name(), dn->first, dn->last,
422 dn->get_projected_version(), dirty);
423 }
424
425 void add_remote_dentry(CDentry *dn, bool dirty) {
426 add_remote_dentry(add_dir(dn->get_dir(), false), dn, dirty, 0, 0);
427 }
428 void add_remote_dentry(CDentry *dn, bool dirty, inodeno_t rino, int rdt) {
429 add_remote_dentry(add_dir(dn->get_dir(), false), dn, dirty, rino, rdt);
430 }
431 void add_remote_dentry(dirlump& lump, CDentry *dn, bool dirty,
432 inodeno_t rino=0, unsigned char rdt=0) {
433 if (!rino) {
434 rino = dn->get_projected_linkage()->get_remote_ino();
435 rdt = dn->get_projected_linkage()->get_remote_d_type();
436 }
437 lump.nremote++;
438 lump.add_dremote(dn->get_name(), dn->get_alternate_name(), dn->first, dn->last,
439 dn->get_projected_version(), rino, rdt, dirty);
440 }
441
442 // return remote pointer to to-be-journaled inode
443 void add_primary_dentry(CDentry *dn, CInode *in, bool dirty,
444 bool dirty_parent=false, bool dirty_pool=false,
445 bool need_snapflush=false) {
446 __u8 state = 0;
447 if (dirty) state |= fullbit::STATE_DIRTY;
448 if (dirty_parent) state |= fullbit::STATE_DIRTYPARENT;
449 if (dirty_pool) state |= fullbit::STATE_DIRTYPOOL;
450 if (need_snapflush) state |= fullbit::STATE_NEED_SNAPFLUSH;
451 add_primary_dentry(add_dir(dn->get_dir(), false), dn, in, state);
452 }
453 void add_primary_dentry(dirlump& lump, CDentry *dn, CInode *in, __u8 state) {
454 if (!in)
455 in = dn->get_projected_linkage()->get_inode();
456
457 if (in->is_ephemeral_rand()) {
458 state |= fullbit::STATE_EPHEMERAL_RANDOM;
459 }
460
461 const auto& pi = in->get_projected_inode();
462 ceph_assert(pi->version > 0);
463
464 if ((state & fullbit::STATE_DIRTY) && pi->is_backtrace_updated())
465 state |= fullbit::STATE_DIRTYPARENT;
466
467 bufferlist snapbl;
468 const sr_t *sr = in->get_projected_srnode();
469 if (sr)
470 sr->encode(snapbl);
471
472 lump.nfull++;
473 lump.add_dfull(dn->get_name(), dn->get_alternate_name(), dn->first, dn->last, dn->get_projected_version(),
474 pi, in->dirfragtree, in->get_projected_xattrs(), in->symlink,
475 in->oldest_snap, snapbl, state, in->get_old_inodes());
476
477 // make note of where this inode was last journaled
478 in->last_journaled = event_seq;
479 //cout << "journaling " << in->inode.ino << " at " << my_offset << std::endl;
480 }
481
482 // convenience: primary or remote? figure it out.
483 void add_dentry(CDentry *dn, bool dirty) {
484 dirlump& lump = add_dir(dn->get_dir(), false);
485 add_dentry(lump, dn, dirty, false, false);
486 }
487 void add_import_dentry(CDentry *dn) {
488 bool dirty_parent = false;
489 bool dirty_pool = false;
490 if (dn->get_linkage()->is_primary()) {
491 dirty_parent = dn->get_linkage()->get_inode()->is_dirty_parent();
492 dirty_pool = dn->get_linkage()->get_inode()->is_dirty_pool();
493 }
494 dirlump& lump = add_dir(dn->get_dir(), false);
495 add_dentry(lump, dn, dn->is_dirty(), dirty_parent, dirty_pool);
496 }
497 void add_dentry(dirlump& lump, CDentry *dn, bool dirty, bool dirty_parent, bool dirty_pool) {
498 // primary or remote
499 if (dn->get_projected_linkage()->is_remote()) {
500 add_remote_dentry(dn, dirty);
501 return;
502 } else if (dn->get_projected_linkage()->is_null()) {
503 add_null_dentry(dn, dirty);
504 return;
505 }
506 ceph_assert(dn->get_projected_linkage()->is_primary());
507 add_primary_dentry(dn, 0, dirty, dirty_parent, dirty_pool);
508 }
509
510 void add_root(bool dirty, CInode *in) {
511 in->last_journaled = event_seq;
512 //cout << "journaling " << in->inode.ino << " at " << my_offset << std::endl;
513
514 const auto& pi = in->get_projected_inode();
515 const auto& px = in->get_projected_xattrs();
516 const auto& pdft = in->dirfragtree;
517
518 bufferlist snapbl;
519 const sr_t *sr = in->get_projected_srnode();
520 if (sr)
521 sr->encode(snapbl);
522
523 for (auto p = roots.begin(); p != roots.end(); ++p) {
524 if (p->inode->ino == in->ino()) {
525 roots.erase(p);
526 break;
527 }
528 }
529
530 string empty;
531 roots.emplace_back(empty, "", in->first, in->last, 0, pi, pdft, px, in->symlink,
532 in->oldest_snap, snapbl, (dirty ? fullbit::STATE_DIRTY : 0),
533 in->get_old_inodes());
534 }
535
536 dirlump& add_dir(CDir *dir, bool dirty, bool complete=false) {
537 return add_dir(dir->dirfrag(), dir->get_projected_fnode(),
538 dirty, complete);
539 }
540 dirlump& add_new_dir(CDir *dir) {
541 return add_dir(dir->dirfrag(), dir->get_projected_fnode(),
542 true, true, true); // dirty AND complete AND new
543 }
544 dirlump& add_import_dir(CDir *dir) {
545 // dirty=false would be okay in some cases
546 return add_dir(dir->dirfrag(), dir->get_projected_fnode(),
547 dir->is_dirty(), dir->is_complete(), false, true, dir->is_dirty_dft());
548 }
549 dirlump& add_fragmented_dir(CDir *dir, bool dirty, bool dirtydft) {
550 return add_dir(dir->dirfrag(), dir->get_projected_fnode(),
551 dirty, false, false, false, dirtydft);
552 }
553 dirlump& add_dir(dirfrag_t df, const CDir::fnode_const_ptr& pf, bool dirty,
554 bool complete=false, bool isnew=false,
555 bool importing=false, bool dirty_dft=false) {
556 if (lump_map.count(df) == 0)
557 lump_order.push_back(df);
558
559 dirlump& l = lump_map[df];
560 l.fnode = pf;
561 if (complete) l.mark_complete();
562 if (dirty) l.mark_dirty();
563 if (isnew) l.mark_new();
564 if (importing) l.mark_importing();
565 if (dirty_dft) l.mark_dirty_dft();
566 return l;
567 }
568
569 static const int TO_AUTH_SUBTREE_ROOT = 0; // default.
570 static const int TO_ROOT = 1;
571
572 void add_dir_context(CDir *dir, int mode = TO_AUTH_SUBTREE_ROOT);
573
574 bool empty() {
575 return roots.empty() && lump_order.empty() && table_tids.empty() &&
576 truncate_start.empty() && truncate_finish.empty() &&
577 destroyed_inodes.empty() && client_reqs.empty() &&
578 opened_ino == 0 && inotablev == 0 && sessionmapv == 0;
579 }
580
581 void print(ostream& out) const {
582 out << "[metablob";
583 if (!lump_order.empty())
584 out << " " << lump_order.front() << ", " << lump_map.size() << " dirs";
585 if (!table_tids.empty())
586 out << " table_tids=" << table_tids;
587 if (allocated_ino || preallocated_inos.size()) {
588 if (allocated_ino)
589 out << " alloc_ino=" << allocated_ino;
590 if (preallocated_inos.size())
591 out << " prealloc_ino=" << preallocated_inos;
592 if (used_preallocated_ino)
593 out << " used_prealloc_ino=" << used_preallocated_ino;
594 out << " v" << inotablev;
595 }
596 out << "]";
597 }
598
599 void update_segment(LogSegment *ls);
600 void replay(MDSRank *mds, LogSegment *ls, MDPeerUpdate *su=NULL);
601 };
602 WRITE_CLASS_ENCODER_FEATURES(EMetaBlob)
603 WRITE_CLASS_ENCODER_FEATURES(EMetaBlob::fullbit)
604 WRITE_CLASS_ENCODER(EMetaBlob::remotebit)
605 WRITE_CLASS_ENCODER(EMetaBlob::nullbit)
606 WRITE_CLASS_ENCODER_FEATURES(EMetaBlob::dirlump)
607
608 inline ostream& operator<<(ostream& out, const EMetaBlob& t) {
609 t.print(out);
610 return out;
611 }
612
613 #endif