[minfs] Enable out-of-space Minfs test on FVM-backed partition

Additionally, force "resize" test to exhaust all space
on disk after resizing.

ZX-3320 #comment In Progress
Test: This patch is a test

Change-Id: I721ddfe723741060519f94d4817f0a8ed1d8ffd7
diff --git a/system/utest/fs/test-minfs.cpp b/system/utest/fs/test-minfs.cpp
index 981ee6e..ac5b691 100644
--- a/system/utest/fs/test-minfs.cpp
+++ b/system/utest/fs/test-minfs.cpp
@@ -30,6 +30,14 @@
 
 namespace {
 
+// Using twice as many blocks and slices of half-size, we have just as much space, but we require
+// resizing to fill our filesystem.
+const test_disk_t kGrowableTestDisk = {
+    .block_count = TEST_BLOCK_COUNT_DEFAULT * 2,
+    .block_size = TEST_BLOCK_SIZE_DEFAULT,
+    .slice_size = TEST_FVM_SLICE_SIZE_DEFAULT / 2,
+};
+
 bool QueryInfo(fuchsia_io_FilesystemInfo* info) {
     BEGIN_HELPER;
     fbl::unique_fd fd(open(kMountPath, O_RDONLY | O_DIRECTORY));
@@ -186,11 +194,13 @@
     END_TEST;
 }
 
-bool GetUsedBlocks(uint32_t* used_blocks) {
+bool GetFreeBlocks(uint32_t* out_free_blocks) {
     BEGIN_HELPER;
     fuchsia_io_FilesystemInfo info;
     ASSERT_TRUE(QueryInfo(&info));
-    *used_blocks = static_cast<uint32_t>((info.total_bytes - info.used_bytes) / info.block_size);
+    uint64_t total_bytes = info.total_bytes + info.free_shared_pool_bytes;
+    uint64_t used_bytes = info.used_bytes;
+    *out_free_blocks = static_cast<uint32_t>((total_bytes - used_bytes) / info.block_size);
     END_HELPER;
 }
 
@@ -203,7 +213,7 @@
     uint32_t free_blocks;
 
     while (true) {
-        ASSERT_TRUE(GetUsedBlocks(&free_blocks));
+        ASSERT_TRUE(GetFreeBlocks(&free_blocks));
         if (free_blocks <= max_remaining_blocks) {
             break;
         }
@@ -290,7 +300,7 @@
     }
 
     // Make sure we now have only 1 block remaining.
-    ASSERT_TRUE(GetUsedBlocks(&free_blocks));
+    ASSERT_TRUE(GetFreeBlocks(&free_blocks));
     ASSERT_EQ(free_blocks, 1);
 
     // We should now have exactly 1 free block remaining. Attempt to write into the indirect
@@ -324,7 +334,7 @@
     ASSERT_TRUE(sml_fd);
 
     // Make sure we now have at least kMinfsDirect + 1 blocks remaining.
-    ASSERT_TRUE(GetUsedBlocks(&free_blocks));
+    ASSERT_TRUE(GetFreeBlocks(&free_blocks));
     ASSERT_GE(free_blocks, minfs::kMinfsDirect + 1);
 
     // We have some room now, so create a new directory.
@@ -361,7 +371,7 @@
     }
 
     // Ensure that there is now exactly one block remaining.
-    ASSERT_TRUE(GetUsedBlocks(&actual_blocks));
+    ASSERT_TRUE(GetFreeBlocks(&actual_blocks));
     ASSERT_EQ(free_blocks, actual_blocks);
 
     // Now, attempt to add one more file to the directory we created. Since it will need to
@@ -409,7 +419,7 @@
     }
 
     // Ensure that there is now exactly one block remaining.
-    ASSERT_TRUE(GetUsedBlocks(&actual_blocks));
+    ASSERT_TRUE(GetFreeBlocks(&actual_blocks));
     ASSERT_EQ(free_blocks, actual_blocks);
 
     // Now, attempt to rename one of our original files under the new directory.
@@ -440,7 +450,7 @@
     }
 
     uint32_t original_blocks;
-    ASSERT_TRUE(GetUsedBlocks(&original_blocks));
+    ASSERT_TRUE(GetFreeBlocks(&original_blocks));
 
     uint32_t fd_count = 100;
     fbl::unique_fd fds[fd_count];
@@ -473,7 +483,7 @@
 
     // Check that the number of Minfs free blocks has decreased.
     uint32_t current_blocks;
-    ASSERT_TRUE(GetUsedBlocks(&current_blocks));
+    ASSERT_TRUE(GetFreeBlocks(&current_blocks));
     ASSERT_LT(current_blocks, original_blocks);
 
     // Put the ramdisk to sleep and close all the fds. This will cause file purge to fail,
@@ -491,7 +501,7 @@
 
     // Writeback should have failed.
     // However, the in-memory state has been updated correctly.
-    ASSERT_TRUE(GetUsedBlocks(&current_blocks));
+    ASSERT_TRUE(GetFreeBlocks(&current_blocks));
     ASSERT_EQ(current_blocks, original_blocks);
 
     // Remount Minfs, which should cause leftover unlinked files to be removed.
@@ -499,7 +509,7 @@
     ASSERT_TRUE(check_remount());
 
     // Check that the block count has been reverted to the value before any files were added.
-    ASSERT_TRUE(GetUsedBlocks(&current_blocks));
+    ASSERT_TRUE(GetFreeBlocks(&current_blocks));
     ASSERT_EQ(current_blocks, original_blocks);
 
     END_TEST;
@@ -522,3 +532,8 @@
     RUN_TEST_MEDIUM(TestMetrics)
     RUN_TEST_MEDIUM(TestUnlinkFail)
 )
+
+// Running with an isolated FVM to avoid interactions with the other integration tests.
+FS_TEST_CASE(FsMinfsFullFvmTests, kGrowableTestDisk,
+    RUN_TEST_LARGE(TestFullOperations),
+FS_TEST_FVM, minfs, 1)
diff --git a/system/utest/fs/test-resize.cpp b/system/utest/fs/test-resize.cpp
index 72f641b..58c8c56 100644
--- a/system/utest/fs/test-resize.cpp
+++ b/system/utest/fs/test-resize.cpp
@@ -14,7 +14,9 @@
 
 #include <fbl/algorithm.h>
 #include <fbl/unique_ptr.h>
+#include <fuchsia/io/c/fidl.h>
 #include <fvm/fvm.h>
+#include <lib/fzl/fdio.h>
 #include <minfs/format.h>
 #include <unittest/unittest.h>
 #include <zircon/compiler.h>
@@ -25,35 +27,95 @@
 
 namespace {
 
-bool TestUseAllInodes(void) {
+bool QueryInfo(uint64_t* out_free_pool_size) {
+    BEGIN_HELPER;
+    fuchsia_io_FilesystemInfo info;
+    fbl::unique_fd fd(open(kMountPath, O_RDONLY | O_DIRECTORY));
+    ASSERT_TRUE(fd);
+    zx_status_t status;
+    fzl::FdioCaller caller(std::move(fd));
+    ASSERT_EQ(fuchsia_io_DirectoryAdminQueryFilesystem(caller.borrow_channel(), &status, &info),
+              ZX_OK);
+    // This should always be true, for all filesystems.
+    ASSERT_GT(info.total_bytes, info.used_bytes);
+    *out_free_pool_size = info.free_shared_pool_bytes;
+    END_HELPER;
+}
+
+bool EnsureCanGrow() {
+    BEGIN_HELPER;
+    uint64_t free_pool_size;
+    ASSERT_TRUE(QueryInfo(&free_pool_size));
+    // This tests expects to run with free FVM space.
+    ASSERT_GT(free_pool_size, 0);
+    END_HELPER;
+}
+
+bool EnsureCannotGrow() {
+    BEGIN_HELPER;
+    uint64_t free_pool_size;
+    ASSERT_TRUE(QueryInfo(&free_pool_size));
+    ASSERT_EQ(free_pool_size, 0);
+    END_HELPER;
+}
+
+const test_disk_t max_inode_disk = {
+    .block_count = 1LLU << 15,
+    .block_size = 1LLU << 9,
+    .slice_size = 1LLU << 20,
+};
+
+template <bool Remount>
+bool TestUseAllInodes() {
     BEGIN_TEST;
+    if (use_real_disk) {
+        fprintf(stderr, "Ramdisk required; skipping test\n");
+        return true;
+    }
     ASSERT_TRUE(test_info->supports_resize);
+    ASSERT_TRUE(EnsureCanGrow());
 
     // Create 100,000 inodes.
     // We expect that this will force enough inodes to cause the
     // filesystem structures to resize partway through.
     constexpr size_t kFilesPerDirectory = 100;
-    constexpr size_t kDirectoryCount = 1000;
-    for (size_t d = 0; d < kDirectoryCount; d++) {
+    size_t d = 0;
+    while (true) {
         if (d % 100 == 0) {
             printf("Creating directory (containing 100 files): %lu\n", d);
         }
         char dname[128];
         snprintf(dname, sizeof(dname), "::%lu", d);
-        ASSERT_EQ(mkdir(dname, 0666), 0);
+        if (mkdir(dname, 0666) < 0) {
+            ASSERT_EQ(errno, ENOSPC);
+            break;
+        }
+        bool stop = false;
         for (size_t f = 0; f < kFilesPerDirectory; f++) {
             char fname[128];
             snprintf(fname, sizeof(fname), "::%lu/%lu", d, f);
-            int fd = open(fname, O_CREAT | O_RDWR | O_EXCL);
-            ASSERT_GT(fd, 0);
-            ASSERT_EQ(close(fd), 0);
+            fbl::unique_fd fd(open(fname, O_CREAT | O_RDWR | O_EXCL));
+            if (!fd) {
+                ASSERT_EQ(errno, ENOSPC);
+                stop = true;
+                break;
+            }
         }
+        if (stop) {
+            break;
+        }
+        d++;
     }
 
-    printf("Unmounting, Re-mounting, verifying...\n");
-    ASSERT_TRUE(check_remount(), "Could not remount filesystem");
+    ASSERT_TRUE(EnsureCannotGrow());
 
-    for (size_t d = 0; d < kDirectoryCount; d++) {
+    if (Remount) {
+        printf("Unmounting, Re-mounting, verifying...\n");
+        ASSERT_TRUE(check_remount(), "Could not remount filesystem");
+    }
+
+    size_t directory_count = d;
+    for (size_t d = 0; d < directory_count; d++) {
         if (d % 100 == 0) {
             printf("Deleting directory (containing 100 files): %lu\n", d);
         }
@@ -70,49 +132,70 @@
     END_TEST;
 }
 
-const test_disk_t disk = {
+const test_disk_t max_data_disk = {
     .block_count = 1LLU << 17,
     .block_size = 1LLU << 9,
     .slice_size = 1LLU << 20,
 };
 
-bool TestUseAllData(void) {
+template <bool Remount>
+bool TestUseAllData() {
     BEGIN_TEST;
+    if (use_real_disk) {
+        fprintf(stderr, "Ramdisk required; skipping test\n");
+        return true;
+    }
     constexpr size_t kBufSize = (1 << 20);
-    constexpr size_t kFileBufCount = 19;
+    constexpr size_t kFileBufCount = 20;
     ASSERT_TRUE(test_info->supports_resize);
+    ASSERT_TRUE(EnsureCanGrow());
 
     uint64_t disk_size = test_disk_info.block_count * test_disk_info.block_size;
-    size_t metadata_size = fvm::MetadataSize(disk_size, disk.slice_size);
+    size_t metadata_size = fvm::MetadataSize(disk_size, max_data_disk.slice_size);
 
     ASSERT_GT(disk_size, metadata_size * 2);
     disk_size -= 2 * metadata_size;
 
-    ASSERT_GT(disk_size, minfs::kMinfsMinimumSlices * disk.slice_size);
-    disk_size -= minfs::kMinfsMinimumSlices * disk.slice_size;
+    ASSERT_GT(disk_size, minfs::kMinfsMinimumSlices * max_data_disk.slice_size);
+    disk_size -= minfs::kMinfsMinimumSlices * max_data_disk.slice_size;
 
-    size_t file_count = disk_size / kBufSize / kFileBufCount;
-    ASSERT_GT(file_count, 0);
-
-    fbl::AllocChecker ac;
-    fbl::unique_ptr<uint8_t[]> buf(new (&ac) uint8_t[kBufSize]);
-    ASSERT_TRUE(ac.check());
+    fbl::unique_ptr<uint8_t[]> buf(new uint8_t[kBufSize]);
     memset(buf.get(), 0, kBufSize);
 
-    for (size_t f = 0; f < file_count; f++) {
-        printf("Creating 19 MB file #%lu\n", f);
+    size_t f = 0;
+    while (true) {
+        printf("Creating 20 MB file #%lu\n", f);
         char fname[128];
         snprintf(fname, sizeof(fname), "::%lu", f);
-        int fd = open(fname, O_CREAT | O_RDWR | O_EXCL);
-        ASSERT_GT(fd, 0);
-        for (size_t i = 0; i < kFileBufCount; i++) {
-            ASSERT_EQ(write(fd, buf.get(), kBufSize), kBufSize);
+        fbl::unique_fd fd(open(fname, O_CREAT | O_RDWR | O_EXCL));
+        if (!fd) {
+            ASSERT_EQ(errno, ENOSPC);
+            break;
         }
-        ASSERT_EQ(close(fd), 0);
+        f++;
+        bool stop = false;
+        for (size_t i = 0; i < kFileBufCount; i++) {
+            ASSERT_EQ(ftruncate(fd.get(), kBufSize * kFileBufCount), 0);
+            ssize_t r = write(fd.get(), buf.get(), kBufSize);
+            if (r != kBufSize) {
+                ASSERT_EQ(errno, ENOSPC);
+                stop = true;
+                break;
+            }
+        }
+        if (stop) {
+            break;
+        }
     }
 
-    ASSERT_TRUE(check_remount(), "Could not remount filesystem");
+    ASSERT_TRUE(EnsureCannotGrow());
 
+    if (Remount) {
+        printf("Unmounting, Re-mounting, verifying...\n");
+        ASSERT_TRUE(check_remount(), "Could not remount filesystem");
+    }
+
+    size_t file_count = f;
     for (size_t f = 0; f < file_count; f++) {
         char fname[128];
         snprintf(fname, sizeof(fname), "::%lu", f);
@@ -125,10 +208,18 @@
 }  // namespace
 
 // Reformat the disk between tests to restore original size.
-RUN_FOR_ALL_FILESYSTEMS_TYPE(fs_resize_tests_inodes, disk, FS_TEST_FVM,
-    RUN_TEST_LARGE(TestUseAllInodes)
+RUN_FOR_ALL_FILESYSTEMS_TYPE(fs_resize_tests_inodes_remount, max_inode_disk, FS_TEST_FVM,
+    RUN_TEST_LARGE((TestUseAllInodes<true>))
 )
 
-RUN_FOR_ALL_FILESYSTEMS_TYPE(fs_resize_tests_data, disk, FS_TEST_FVM,
-    RUN_TEST_LARGE(TestUseAllData)
+RUN_FOR_ALL_FILESYSTEMS_TYPE(fs_resize_tests_inodes, max_inode_disk, FS_TEST_FVM,
+    RUN_TEST_LARGE((TestUseAllInodes<false>))
+)
+
+RUN_FOR_ALL_FILESYSTEMS_TYPE(fs_resize_tests_data_remount, max_data_disk, FS_TEST_FVM,
+    RUN_TEST_LARGE((TestUseAllData<true>))
+)
+
+RUN_FOR_ALL_FILESYSTEMS_TYPE(fs_resize_tests_data, max_data_disk, FS_TEST_FVM,
+    RUN_TEST_LARGE((TestUseAllData<false>))
 )