| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include <fbl/algorithm.h> |
| #include <fbl/unique_fd.h> |
| #include <zircon/syscalls.h> |
| |
| #include "filesystems.h" |
| #include "misc.h" |
| |
| #define MB (1 << 20) |
| #define PRINT_SIZE (MB * 100) |
| |
| namespace { |
| |
| enum MountType { |
| DoRemount, |
| DontRemount, |
| }; |
| |
| // Test writing as much as we can to a file until we run |
| // out of space |
| template <MountType mt> |
| bool test_maxfile(void) { |
| BEGIN_TEST; |
| |
| if (!test_info->can_be_mounted && mt == DoRemount) { |
| fprintf(stderr, "Filesystem cannot be mounted; cannot remount\n"); |
| return true; |
| } |
| |
| // TODO(ZX-1735): We avoid making files that consume more than half |
| // of physical memory. When we can page out files, this restriction |
| // should be removed. |
| const size_t physmem = zx_system_get_physmem(); |
| const size_t max_cap = physmem / 2; |
| |
| fbl::unique_fd fd(open("::bigfile", O_CREAT | O_RDWR, 0644)); |
| ASSERT_TRUE(fd); |
| char data_a[8192]; |
| char data_b[8192]; |
| char data_c[8192]; |
| memset(data_a, 0xaa, sizeof(data_a)); |
| memset(data_b, 0xbb, sizeof(data_b)); |
| memset(data_c, 0xcc, sizeof(data_c)); |
| size_t sz = 0; |
| ssize_t r; |
| |
| auto rotate = [&](const char* data) { |
| if (data == data_a) { |
| return data_b; |
| } else if (data == data_b) { |
| return data_c; |
| } else { |
| return data_a; |
| } |
| }; |
| |
| const char* data = data_a; |
| for (;;) { |
| if (sz >= max_cap) { |
| fprintf(stderr, "Approaching physical memory capacity: %zd bytes\n", sz); |
| r = 0; |
| break; |
| } |
| |
| if ((r = write(fd.get(), data, sizeof(data_a))) < 0) { |
| fprintf(stderr, "bigfile received error: %s\n", strerror(errno)); |
| if ((errno == EFBIG) || (errno == ENOSPC)) { |
| // Either the file should be too big (EFBIG) or the file should |
| // consume the whole volume (ENOSPC). |
| fprintf(stderr, "(This was an expected error)\n"); |
| r = 0; |
| } |
| break; |
| } |
| if ((sz + r) % PRINT_SIZE < (sz % PRINT_SIZE)) { |
| fprintf(stderr, "wrote %lu MB\n", (sz + r) / MB); |
| } |
| sz += r; |
| ASSERT_EQ(r, sizeof(data_a)); |
| |
| // Rotate which data buffer we use |
| data = rotate(data); |
| } |
| ASSERT_EQ(r, 0, "Saw an unexpected error from write"); |
| fprintf(stderr, "wrote %lu bytes\n", sz); |
| |
| struct stat buf; |
| ASSERT_EQ(fstat(fd.get(), &buf), 0, "Couldn't stat max file"); |
| ASSERT_EQ(buf.st_size, static_cast<ssize_t>(sz), "Unexpected max file size"); |
| |
| // Try closing, re-opening, and verifying the file |
| ASSERT_EQ(close(fd.release()), 0); |
| if (mt == DoRemount) { |
| ASSERT_TRUE(check_remount(), "Could not remount filesystem"); |
| } |
| fd.reset(open("::bigfile", O_RDWR, 0644)); |
| ASSERT_TRUE(fd); |
| ASSERT_EQ(fstat(fd.get(), &buf), 0, "Couldn't stat max file"); |
| ASSERT_EQ(buf.st_size, static_cast<ssize_t>(sz), "Unexpected max file size"); |
| char readbuf[8192]; |
| size_t bytes_read = 0; |
| data = data_a; |
| while (bytes_read < sz) { |
| r = read(fd.get(), readbuf, sizeof(readbuf)); |
| ASSERT_EQ(r, static_cast<ssize_t>(fbl::min(sz - bytes_read, sizeof(readbuf)))); |
| ASSERT_EQ(memcmp(readbuf, data, r), 0, "File failed to verify"); |
| data = rotate(data); |
| bytes_read += r; |
| } |
| |
| ASSERT_EQ(bytes_read, sz); |
| |
| ASSERT_EQ(unlink("::bigfile"), 0); |
| ASSERT_EQ(close(fd.release()), 0); |
| END_TEST; |
| } |
| |
| // Test writing to two files, in alternation, until we run out |
| // of space. For trivial (sequential) block allocation policies, |
| // this will create two large files with non-contiguous block |
| // allocations. |
| template <MountType mt> |
| bool TestZippedMaxfiles(void) { |
| BEGIN_TEST; |
| |
| if (!test_info->can_be_mounted && mt == DoRemount) { |
| fprintf(stderr, "Filesystem cannot be mounted; cannot remount\n"); |
| return true; |
| } |
| |
| // TODO(ZX-1735): We avoid making files that consume more than half |
| // of physical memory. When we can page out files, this restriction |
| // should be removed. |
| const size_t physmem = zx_system_get_physmem(); |
| const size_t max_cap = physmem / 4; |
| |
| fbl::unique_fd fda(open("::bigfile-A", O_CREAT | O_RDWR, 0644)); |
| fbl::unique_fd fdb(open("::bigfile-B", O_CREAT | O_RDWR, 0644)); |
| ASSERT_TRUE(fda); |
| ASSERT_TRUE(fdb); |
| char data_a[8192]; |
| char data_b[8192]; |
| memset(data_a, 0xaa, sizeof(data_a)); |
| memset(data_b, 0xbb, sizeof(data_b)); |
| size_t sz_a = 0; |
| size_t sz_b = 0; |
| ssize_t r; |
| |
| size_t* sz = &sz_a; |
| int fd = fda.get(); |
| const char* data = data_a; |
| for (;;) { |
| if (*sz >= max_cap) { |
| fprintf(stderr, "Approaching physical memory capacity: %zd bytes\n", *sz); |
| r = 0; |
| break; |
| } |
| |
| if ((r = write(fd, data, sizeof(data_a))) <= 0) { |
| fprintf(stderr, "bigfile received error: %s\n", strerror(errno)); |
| // Either the file should be too big (EFBIG) or the file should |
| // consume the whole volume (ENOSPC). |
| ASSERT_TRUE(errno == EFBIG || errno == ENOSPC); |
| fprintf(stderr, "(This was an expected error)\n"); |
| break; |
| } |
| if ((*sz + r) % PRINT_SIZE < (*sz % PRINT_SIZE)) { |
| fprintf(stderr, "wrote %lu MB\n", (*sz + r) / MB); |
| } |
| *sz += r; |
| ASSERT_EQ(r, sizeof(data_a)); |
| |
| fd = (fd == fda.get()) ? fdb.get() : fda.get(); |
| data = (data == data_a) ? data_b : data_a; |
| sz = (sz == &sz_a) ? &sz_b : &sz_a; |
| } |
| fprintf(stderr, "wrote %lu bytes (to A)\n", sz_a); |
| fprintf(stderr, "wrote %lu bytes (to B)\n", sz_b); |
| |
| struct stat buf; |
| ASSERT_EQ(fstat(fda.get(), &buf), 0, "Couldn't stat max file"); |
| ASSERT_EQ(buf.st_size, static_cast<ssize_t>(sz_a), "Unexpected max file size"); |
| ASSERT_EQ(fstat(fdb.get(), &buf), 0, "Couldn't stat max file"); |
| ASSERT_EQ(buf.st_size, static_cast<ssize_t>(sz_b), "Unexpected max file size"); |
| |
| // Try closing, re-opening, and verifying the file |
| ASSERT_EQ(close(fda.release()), 0); |
| ASSERT_EQ(close(fdb.release()), 0); |
| if (mt == DoRemount) { |
| ASSERT_TRUE(check_remount(), "Could not remount filesystem"); |
| } |
| fda.reset(open("::bigfile-A", O_RDWR, 0644)); |
| fdb.reset(open("::bigfile-B", O_RDWR, 0644)); |
| ASSERT_TRUE(fda); |
| ASSERT_TRUE(fdb); |
| |
| char readbuf[8192]; |
| size_t bytes_read_a = 0; |
| size_t bytes_read_b = 0; |
| |
| fd = fda.get(); |
| data = data_a; |
| sz = &sz_a; |
| size_t* bytes_read = &bytes_read_a; |
| while (*bytes_read < *sz) { |
| r = read(fd, readbuf, sizeof(readbuf)); |
| ASSERT_EQ(r, static_cast<ssize_t>(fbl::min(*sz - *bytes_read, sizeof(readbuf)))); |
| ASSERT_EQ(memcmp(readbuf, data, r), 0, "File failed to verify"); |
| *bytes_read += r; |
| |
| fd = (fd == fda.get()) ? fdb.get() : fda.get(); |
| data = (data == data_a) ? data_b : data_a; |
| sz = (sz == &sz_a) ? &sz_b : &sz_a; |
| bytes_read = (bytes_read == &bytes_read_a) ? &bytes_read_b : &bytes_read_a; |
| } |
| |
| ASSERT_EQ(bytes_read_a, sz_a); |
| ASSERT_EQ(bytes_read_b, sz_b); |
| |
| ASSERT_EQ(unlink("::bigfile-A"), 0); |
| ASSERT_EQ(unlink("::bigfile-B"), 0); |
| ASSERT_EQ(close(fda.release()), 0); |
| ASSERT_EQ(close(fdb.release()), 0); |
| |
| END_TEST; |
| } |
| |
| // Disk must be at least this large to exceed the maximum transaction limit during delayed data |
| // allocation on non-FVM-backed Minfs partitions. |
| const test_disk_t disk = { |
| .block_count = 1LLU << 20, |
| .block_size = 1LLU << 9, |
| .slice_size = 1LLU << 23, |
| }; |
| |
| } // namespace |
| |
| RUN_FOR_ALL_FILESYSTEMS_SIZE(maxfile_tests, disk, |
| RUN_TEST_LARGE((test_maxfile<DontRemount>)) |
| RUN_TEST_LARGE((test_maxfile<DoRemount>)) |
| RUN_TEST_LARGE((TestZippedMaxfiles<DontRemount>)) |
| RUN_TEST_LARGE((TestZippedMaxfiles<DoRemount>))) |