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