1// Copyright 2016 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <stdio.h> 6#include <stdlib.h> 7#include <string.h> 8#include <unistd.h> 9 10#include <minfs/format.h> 11#include <minfs/fsck.h> 12#include "minfs-private.h" 13 14// #define DEBUG_PRINTF 15#ifdef DEBUG_PRINTF 16#define xprintf(args...) fprintf(stderr, args) 17#else 18#define xprintf(args...) 19#endif 20 21namespace minfs { 22 23class MinfsChecker { 24public: 25 MinfsChecker(); 26 zx_status_t Init(fbl::unique_ptr<Bcache> bc, const minfs_info_t* info); 27 void CheckReserved(); 28 zx_status_t CheckInode(ino_t ino, ino_t parent, bool dot_or_dotdot); 29 zx_status_t CheckForUnusedBlocks() const; 30 zx_status_t CheckForUnusedInodes() const; 31 zx_status_t CheckLinkCounts() const; 32 zx_status_t CheckAllocatedCounts() const; 33 34 // "Set once"-style flag to identify if anything nonconforming 35 // was found in the underlying filesystem -- even if it was fixed. 36 bool conforming_; 37 38private: 39 DISALLOW_COPY_ASSIGN_AND_MOVE(MinfsChecker); 40 41 zx_status_t GetInode(minfs_inode_t* inode, ino_t ino); 42 43 // Returns the nth block within an inode, relative to the start of the 44 // file. Returns the "next_n" which might contain a bno. This "next_n" 45 // is for performance reasons -- it allows fsck to avoid repeatedly checking 46 // the same indirect / doubly indirect blocks with all internal 47 // bno unallocated. 48 zx_status_t GetInodeNthBno(minfs_inode_t* inode, blk_t n, blk_t* next_n, 49 blk_t* bno_out); 50 zx_status_t CheckDirectory(minfs_inode_t* inode, ino_t ino, 51 ino_t parent, uint32_t flags); 52 const char* CheckDataBlock(blk_t bno); 53 zx_status_t CheckFile(minfs_inode_t* inode, ino_t ino); 54 55 fbl::unique_ptr<Minfs> fs_; 56 RawBitmap checked_inodes_; 57 RawBitmap checked_blocks_; 58 59 uint32_t alloc_inodes_; 60 uint32_t alloc_blocks_; 61 fbl::Array<int32_t> links_; 62 63 blk_t cached_doubly_indirect_; 64 blk_t cached_indirect_; 65 uint8_t doubly_indirect_cache_[kMinfsBlockSize]; 66 uint8_t indirect_cache_[kMinfsBlockSize]; 67}; 68 69zx_status_t MinfsChecker::GetInode(minfs_inode_t* inode, ino_t ino) { 70 if (ino >= fs_->Info().inode_count) { 71 FS_TRACE_ERROR("check: ino %u out of range (>=%u)\n", 72 ino, fs_->Info().inode_count); 73 return ZX_ERR_OUT_OF_RANGE; 74 } 75 76 fs_->inodes_->Load(ino, inode); 77 if ((inode->magic != kMinfsMagicFile) && (inode->magic != kMinfsMagicDir)) { 78 FS_TRACE_ERROR("check: ino %u has bad magic %#x\n", ino, inode->magic); 79 return ZX_ERR_IO_DATA_INTEGRITY; 80 } 81 return ZX_OK; 82} 83 84#define CD_DUMP 1 85#define CD_RECURSE 2 86 87zx_status_t MinfsChecker::GetInodeNthBno(minfs_inode_t* inode, blk_t n, 88 blk_t* next_n, blk_t* bno_out) { 89 // The default value for the "next n". It's easier to set it here anyway, 90 // since we proceed to modify n in the code below. 91 *next_n = n + 1; 92 if (n < kMinfsDirect) { 93 *bno_out = inode->dnum[n]; 94 return ZX_OK; 95 } 96 97 n -= kMinfsDirect; 98 uint32_t i = n / kMinfsDirectPerIndirect; // indirect index 99 uint32_t j = n % kMinfsDirectPerIndirect; // direct index 100 101 if (i < kMinfsIndirect) { 102 blk_t ibno; 103 if ((ibno = inode->inum[i]) == 0) { 104 *bno_out = 0; 105 *next_n = kMinfsDirect + (i + 1) * kMinfsDirectPerIndirect; 106 return ZX_OK; 107 } 108 109 if (cached_indirect_ != ibno) { 110 zx_status_t status; 111 if ((status = fs_->ReadDat(ibno, indirect_cache_)) != ZX_OK) { 112 return status; 113 } 114 cached_indirect_ = ibno; 115 } 116 117 uint32_t* ientry = reinterpret_cast<uint32_t*>(indirect_cache_); 118 *bno_out = ientry[j]; 119 return ZX_OK; 120 } 121 122 n -= kMinfsIndirect * kMinfsDirectPerIndirect; 123 i = n / (kMinfsDirectPerDindirect); // doubly indirect index 124 n -= (i * kMinfsDirectPerDindirect); 125 j = n / kMinfsDirectPerIndirect; // indirect index 126 uint32_t k = n % kMinfsDirectPerIndirect; // direct index 127 128 if (i < kMinfsDoublyIndirect) { 129 blk_t dibno; 130 if ((dibno = inode->dinum[i]) == 0) { 131 *bno_out = 0; 132 *next_n = kMinfsDirect + kMinfsIndirect * kMinfsDirectPerIndirect + 133 (i + 1) * kMinfsDirectPerDindirect; 134 return ZX_OK; 135 } 136 137 if (cached_doubly_indirect_ != dibno) { 138 zx_status_t status; 139 if ((status = fs_->ReadDat(dibno, doubly_indirect_cache_)) != ZX_OK) { 140 return status; 141 } 142 cached_doubly_indirect_ = dibno; 143 } 144 145 uint32_t* dientry = reinterpret_cast<uint32_t*>(doubly_indirect_cache_); 146 blk_t ibno; 147 if ((ibno = dientry[j]) == 0) { 148 *bno_out = 0; 149 *next_n = kMinfsDirect + kMinfsIndirect * kMinfsDirectPerIndirect + 150 (i * kMinfsDirectPerDindirect) + (j + 1) * kMinfsDirectPerIndirect; 151 return ZX_OK; 152 } 153 154 if (cached_indirect_ != ibno) { 155 zx_status_t status; 156 if ((status = fs_->ReadDat(ibno, indirect_cache_)) != ZX_OK) { 157 return status; 158 } 159 cached_indirect_ = ibno; 160 } 161 162 uint32_t* ientry = reinterpret_cast<uint32_t*>(indirect_cache_); 163 *bno_out = ientry[k]; 164 return ZX_OK; 165 } 166 167 return ZX_ERR_OUT_OF_RANGE; 168} 169 170zx_status_t MinfsChecker::CheckDirectory(minfs_inode_t* inode, ino_t ino, 171 ino_t parent, uint32_t flags) { 172 unsigned eno = 0; 173 bool dot = false; 174 bool dotdot = false; 175 uint32_t dirent_count = 0; 176 177 zx_status_t status; 178 fbl::RefPtr<VnodeMinfs> vn; 179 if ((status = VnodeMinfs::Recreate(fs_.get(), ino, &vn)) != ZX_OK) { 180 return status; 181 } 182 183 size_t off = 0; 184 while (true) { 185 uint32_t data[MINFS_DIRENT_SIZE]; 186 size_t actual; 187 status = vn->ReadInternal(data, MINFS_DIRENT_SIZE, off, &actual); 188 if (status != ZX_OK || actual != MINFS_DIRENT_SIZE) { 189 FS_TRACE_ERROR("check: ino#%u: Could not read de[%u] at %zd\n", eno, ino, off); 190 if (inode->dirent_count >= 2 && inode->dirent_count == eno - 1) { 191 // So we couldn't read the last direntry, for whatever reason, but our 192 // inode says that we shouldn't have been able to read it anyway. 193 FS_TRACE_ERROR("check: de count (%u) > inode_dirent_count (%u)\n", eno, 194 inode->dirent_count); 195 } 196 return status != ZX_OK ? status : ZX_ERR_IO; 197 } 198 minfs_dirent_t* de = reinterpret_cast<minfs_dirent_t*>(data); 199 uint32_t rlen = static_cast<uint32_t>(MinfsReclen(de, off)); 200 uint32_t dlen = DirentSize(de->namelen); 201 bool is_last = de->reclen & kMinfsReclenLast; 202 if (!is_last && ((rlen < MINFS_DIRENT_SIZE) || (dlen > rlen) || 203 (dlen > kMinfsMaxDirentSize) || (rlen & 3))) { 204 FS_TRACE_ERROR("check: ino#%u: de[%u]: bad dirent reclen (%u)\n", ino, eno, rlen); 205 return ZX_ERR_IO_DATA_INTEGRITY; 206 } 207 if (de->ino == 0) { 208 if (flags & CD_DUMP) { 209 xprintf("ino#%u: de[%u]: <empty> reclen=%u\n", ino, eno, rlen); 210 } 211 } else { 212 // Re-read the dirent to acquire the full name 213 uint32_t record_full[DirentSize(NAME_MAX)]; 214 status = vn->ReadInternal(record_full, DirentSize(de->namelen), off, &actual); 215 if (status != ZX_OK || actual != DirentSize(de->namelen)) { 216 FS_TRACE_ERROR("check: Error reading dirent of size: %u\n", DirentSize(de->namelen)); 217 return ZX_ERR_IO; 218 } 219 de = reinterpret_cast<minfs_dirent_t*>(record_full); 220 bool dot_or_dotdot = false; 221 222 if ((de->namelen == 0) || (de->namelen > (rlen - MINFS_DIRENT_SIZE))) { 223 FS_TRACE_ERROR("check: ino#%u: de[%u]: invalid namelen %u\n", ino, eno, de->namelen); 224 return ZX_ERR_IO_DATA_INTEGRITY; 225 } 226 if ((de->namelen == 1) && (de->name[0] == '.')) { 227 if (dot) { 228 FS_TRACE_ERROR("check: ino#%u: multiple '.' entries\n", ino); 229 } 230 dot_or_dotdot = true; 231 dot = true; 232 if (de->ino != ino) { 233 FS_TRACE_ERROR("check: ino#%u: de[%u]: '.' ino=%u (not self!)\n", ino, eno, de->ino); 234 } 235 } 236 if ((de->namelen == 2) && (de->name[0] == '.') && (de->name[1] == '.')) { 237 if (dotdot) { 238 FS_TRACE_ERROR("check: ino#%u: multiple '..' entries\n", ino); 239 } 240 dot_or_dotdot = true; 241 dotdot = true; 242 if (de->ino != parent) { 243 FS_TRACE_ERROR("check: ino#%u: de[%u]: '..' ino=%u (not parent!)\n", ino, eno, de->ino); 244 } 245 } 246 //TODO: check for cycles (non-dot/dotdot dir ref already in checked bitmap) 247 if (flags & CD_DUMP) { 248 xprintf("ino#%u: de[%u]: ino=%u type=%u '%.*s' %s\n", ino, eno, de->ino, de->type, 249 de->namelen, de->name, is_last ? "[last]" : ""); 250 } 251 252 if (flags & CD_RECURSE) { 253 if ((status = CheckInode(de->ino, ino, dot_or_dotdot)) < 0) { 254 return status; 255 } 256 } 257 dirent_count++; 258 } 259 if (is_last) { 260 break; 261 } else { 262 off += rlen; 263 } 264 eno++; 265 } 266 if (dirent_count != inode->dirent_count) { 267 FS_TRACE_ERROR("check: ino#%u: dirent_count of %u != %u (actual)\n", 268 ino, inode->dirent_count, dirent_count); 269 } 270 if (dot == false) { 271 FS_TRACE_ERROR("check: ino#%u: directory missing '.'\n", ino); 272 } 273 if (dotdot == false) { 274 FS_TRACE_ERROR("check: ino#%u: directory missing '..'\n", ino); 275 } 276 return ZX_OK; 277} 278 279const char* MinfsChecker::CheckDataBlock(blk_t bno) { 280 if (bno == 0) { 281 return "reserved bno"; 282 } 283 if (bno >= fs_->Info().block_count) { 284 return "out of range"; 285 } 286 if (!fs_->block_allocator_->map_.Get(bno, bno + 1)) { 287 return "not allocated"; 288 } 289 if (checked_blocks_.Get(bno, bno + 1)) { 290 return "double-allocated"; 291 } 292 checked_blocks_.Set(bno, bno + 1); 293 alloc_blocks_++; 294 return nullptr; 295} 296 297zx_status_t MinfsChecker::CheckFile(minfs_inode_t* inode, ino_t ino) { 298 xprintf("Direct blocks: \n"); 299 for (unsigned n = 0; n < kMinfsDirect; n++) { 300 xprintf(" %d,", inode->dnum[n]); 301 } 302 xprintf(" ...\n"); 303 304 uint32_t block_count = 0; 305 306 // count and sanity-check indirect blocks 307 for (unsigned n = 0; n < kMinfsIndirect; n++) { 308 if (inode->inum[n]) { 309 const char* msg; 310 if ((msg = CheckDataBlock(inode->inum[n])) != nullptr) { 311 FS_TRACE_WARN("check: ino#%u: indirect block %u(@%u): %s\n", 312 ino, n, inode->inum[n], msg); 313 conforming_ = false; 314 } 315 block_count++; 316 } 317 } 318 319 // count and sanity-check doubly indirect blocks 320 for (unsigned n = 0; n < kMinfsDoublyIndirect; n++) { 321 if (inode->dinum[n]) { 322 const char* msg; 323 if ((msg = CheckDataBlock(inode->dinum[n])) != nullptr) { 324 FS_TRACE_WARN("check: ino#%u: doubly indirect block %u(@%u): %s\n", 325 ino, n, inode->dinum[n], msg); 326 conforming_ = false; 327 } 328 block_count++; 329 330 char data[kMinfsBlockSize]; 331 zx_status_t status; 332 if ((status = fs_->ReadDat(inode->dinum[n], data)) != ZX_OK) { 333 return status; 334 } 335 uint32_t* entry = reinterpret_cast<uint32_t*>(data); 336 337 for (unsigned m = 0; m < kMinfsDirectPerIndirect; m++) { 338 if (entry[m]) { 339 if ((msg = CheckDataBlock(entry[m])) != nullptr) { 340 FS_TRACE_WARN("check: ino#%u: indirect block (in dind) %u(@%u): %s\n", 341 ino, m, entry[m], msg); 342 conforming_ = false; 343 } 344 block_count++; 345 } 346 } 347 } 348 } 349 350 // count and sanity-check data blocks 351 352 // The next block which would be allocated if we expand the file size 353 // by a single block. 354 unsigned next_blk = 0; 355 cached_doubly_indirect_ = 0; 356 cached_indirect_ = 0; 357 358 blk_t n = 0; 359 while (true) { 360 zx_status_t status; 361 blk_t bno; 362 blk_t next_n; 363 if ((status = GetInodeNthBno(inode, n, &next_n, &bno)) < 0) { 364 if (status == ZX_ERR_OUT_OF_RANGE) { 365 break; 366 } else { 367 return status; 368 } 369 } 370 assert(next_n > n); 371 if (bno) { 372 next_blk = n + 1; 373 block_count++; 374 const char* msg; 375 if ((msg = CheckDataBlock(bno)) != nullptr) { 376 FS_TRACE_WARN("check: ino#%u: block %u(@%u): %s\n", ino, n, bno, msg); 377 conforming_ = false; 378 } 379 } 380 n = next_n; 381 } 382 if (next_blk) { 383 unsigned max_blocks = fbl::round_up(inode->size, kMinfsBlockSize) / kMinfsBlockSize; 384 if (next_blk > max_blocks) { 385 FS_TRACE_WARN("check: ino#%u: filesize too small\n", ino); 386 conforming_ = false; 387 } 388 } 389 if (block_count != inode->block_count) { 390 FS_TRACE_WARN("check: ino#%u: block count %u, actual blocks %u\n", 391 ino, inode->block_count, block_count); 392 conforming_ = false; 393 } 394 return ZX_OK; 395} 396 397void MinfsChecker::CheckReserved() { 398 // Check reserved inode '0'. 399 if (fs_->inodes_->inode_allocator_->map_.Get(0, 1)) { 400 checked_inodes_.Set(0, 1); 401 alloc_inodes_++; 402 } else { 403 FS_TRACE_WARN("check: reserved inode#0: not marked in-use\n"); 404 conforming_ = false; 405 } 406 407 // Check reserved data block '0'. 408 if (fs_->block_allocator_->map_.Get(0, 1)) { 409 checked_blocks_.Set(0, 1); 410 alloc_blocks_++; 411 } else { 412 FS_TRACE_WARN("check: reserved block#0: not marked in-use\n"); 413 conforming_ = false; 414 } 415} 416 417zx_status_t MinfsChecker::CheckInode(ino_t ino, ino_t parent, bool dot_or_dotdot) { 418 minfs_inode_t inode; 419 zx_status_t status; 420 421 if ((status = GetInode(&inode, ino)) < 0) { 422 FS_TRACE_ERROR("check: ino#%u: not readable\n", ino); 423 return status; 424 } 425 426 bool prev_checked = checked_inodes_.Get(ino, ino + 1); 427 428 if (inode.magic == kMinfsMagicDir && prev_checked && !dot_or_dotdot) { 429 FS_TRACE_ERROR("check: ino#%u: Multiple hard links to directory (excluding '.' and '..') found\n", ino); 430 return ZX_ERR_BAD_STATE; 431 } 432 433 links_[ino - 1] += 1; 434 435 if (prev_checked) { 436 // we've been here before 437 return ZX_OK; 438 } 439 440 links_[ino - 1] -= inode.link_count; 441 checked_inodes_.Set(ino, ino + 1); 442 alloc_inodes_++; 443 444 if (!fs_->inodes_->inode_allocator_->map_.Get(ino, ino + 1)) { 445 FS_TRACE_WARN("check: ino#%u: not marked in-use\n", ino); 446 conforming_ = false; 447 } 448 449 if (inode.magic == kMinfsMagicDir) { 450 xprintf("ino#%u: DIR blks=%u links=%u\n", ino, inode.block_count, inode.link_count); 451 if ((status = CheckFile(&inode, ino)) < 0) { 452 return status; 453 } 454 if ((status = CheckDirectory(&inode, ino, parent, CD_DUMP)) < 0) { 455 return status; 456 } 457 if ((status = CheckDirectory(&inode, ino, parent, CD_RECURSE)) < 0) { 458 return status; 459 } 460 } else { 461 xprintf("ino#%u: FILE blks=%u links=%u size=%u\n", ino, inode.block_count, inode.link_count, 462 inode.size); 463 if ((status = CheckFile(&inode, ino)) < 0) { 464 return status; 465 } 466 } 467 return ZX_OK; 468} 469 470zx_status_t MinfsChecker::CheckForUnusedBlocks() const { 471 unsigned missing = 0; 472 473 for (unsigned n = 0; n < fs_->Info().block_count; n++) { 474 if (fs_->block_allocator_->map_.Get(n, n + 1)) { 475 if (!checked_blocks_.Get(n, n + 1)) { 476 missing++; 477 } 478 } 479 } 480 if (missing) { 481 FS_TRACE_ERROR("check: %u allocated block%s not in use\n", 482 missing, missing > 1 ? "s" : ""); 483 return ZX_ERR_BAD_STATE; 484 } 485 return ZX_OK; 486} 487 488zx_status_t MinfsChecker::CheckForUnusedInodes() const { 489 unsigned missing = 0; 490 for (unsigned n = 0; n < fs_->Info().inode_count; n++) { 491 if (fs_->inodes_->inode_allocator_->map_.Get(n, n + 1)) { 492 if (!checked_inodes_.Get(n, n + 1)) { 493 missing++; 494 } 495 } 496 } 497 if (missing) { 498 FS_TRACE_ERROR("check: %u allocated inode%s not in use\n", 499 missing, missing > 1 ? "s" : ""); 500 return ZX_ERR_BAD_STATE; 501 } 502 return ZX_OK; 503} 504 505zx_status_t MinfsChecker::CheckLinkCounts() const { 506 unsigned error = 0; 507 for (uint32_t n = 0; n < fs_->Info().inode_count; n++) { 508 if (links_[n] != 0) { 509 error += 1; 510 FS_TRACE_ERROR("check: inode#%u has incorrect link count %u\n", n + 1, links_[n]); 511 return ZX_ERR_BAD_STATE; 512 } 513 } 514 if (error) { 515 FS_TRACE_ERROR("check: %u inode%s with incorrect link count\n", 516 error, error > 1 ? "s" : ""); 517 return ZX_ERR_BAD_STATE; 518 } 519 return ZX_OK; 520} 521 522zx_status_t MinfsChecker::CheckAllocatedCounts() const { 523 zx_status_t status = ZX_OK; 524 if (alloc_blocks_ != fs_->Info().alloc_block_count) { 525 FS_TRACE_ERROR("check: incorrect allocated block count %u (should be %u)\n", 526 fs_->Info().alloc_block_count, alloc_blocks_); 527 status = ZX_ERR_BAD_STATE; 528 } 529 530 if (alloc_inodes_ != fs_->Info().alloc_inode_count) { 531 FS_TRACE_ERROR("check: incorrect allocated inode count %u (should be %u)\n", 532 fs_->Info().alloc_inode_count, alloc_inodes_); 533 status = ZX_ERR_BAD_STATE; 534 } 535 536 return status; 537} 538 539MinfsChecker::MinfsChecker() 540 : conforming_(true), fs_(nullptr), alloc_inodes_(0), alloc_blocks_(0), links_() {}; 541 542zx_status_t MinfsChecker::Init(fbl::unique_ptr<Bcache> bc, const minfs_info_t* info) { 543 links_.reset(new int32_t[info->inode_count]{0}, info->inode_count); 544 links_[0] = -1; 545 546 cached_doubly_indirect_ = 0; 547 cached_indirect_ = 0; 548 549 zx_status_t status; 550 if ((status = checked_inodes_.Reset(info->inode_count)) != ZX_OK) { 551 FS_TRACE_ERROR("MinfsChecker::Init Failed to reset checked inodes: %d\n", status); 552 return status; 553 } 554 if ((status = checked_blocks_.Reset(info->block_count)) != ZX_OK) { 555 FS_TRACE_ERROR("MinfsChecker::Init Failed to reset checked blocks: %d\n", status); 556 return status; 557 } 558 fbl::unique_ptr<Minfs> fs; 559 if ((status = Minfs::Create(fbl::move(bc), info, &fs)) != ZX_OK) { 560 FS_TRACE_ERROR("MinfsChecker::Create Failed to Create Minfs: %d\n", status); 561 return status; 562 } 563 fs_ = fbl::move(fs); 564 565 return ZX_OK; 566} 567 568zx_status_t minfs_check(fbl::unique_ptr<Bcache> bc) { 569 zx_status_t status; 570 571 char data[kMinfsBlockSize]; 572 if (bc->Readblk(0, data) < 0) { 573 FS_TRACE_ERROR("minfs: could not read info block\n"); 574 return ZX_ERR_IO; 575 } 576 const minfs_info_t* info = reinterpret_cast<const minfs_info_t*>(data); 577 minfs_dump_info(info); 578 if ((status = minfs_check_info(info, bc.get())) != ZX_OK) { 579 FS_TRACE_ERROR("minfs_check: check_info failure: %d\n", status); 580 return status; 581 } 582 583 MinfsChecker chk; 584 if ((status = chk.Init(fbl::move(bc), info)) != ZX_OK) { 585 FS_TRACE_ERROR("minfs_check: Init failure: %d\n", status); 586 return status; 587 } 588 589 chk.CheckReserved(); 590 591 //TODO: check root not a directory 592 if ((status = chk.CheckInode(1, 1, 0)) != ZX_OK) { 593 FS_TRACE_ERROR("minfs_check: CheckInode failure: %d\n", status); 594 return status; 595 } 596 597 zx_status_t r; 598 599 // Save an error if it occurs, but check for subsequent errors 600 // anyway. 601 r = chk.CheckForUnusedBlocks(); 602 status |= (status != ZX_OK) ? 0 : r; 603 r = chk.CheckForUnusedInodes(); 604 status |= (status != ZX_OK) ? 0 : r; 605 r = chk.CheckLinkCounts(); 606 status |= (status != ZX_OK) ? 0 : r; 607 r = chk.CheckAllocatedCounts(); 608 status |= (status != ZX_OK) ? 0 : r; 609 610 //TODO: check allocated inodes that were abandoned 611 //TODO: check allocated blocks that were not accounted for 612 //TODO: check unallocated inodes where magic != 0 613 status |= (status != ZX_OK) ? 0 : (chk.conforming_ ? ZX_OK : ZX_ERR_BAD_STATE); 614 615 return status; 616} 617 618} // namespace minfs 619