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