/* * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include "drivers/flash.h" #include "flash_region/flash_region.h" #include "services/normal/filesystem/pfs.h" #include "services/normal/filesystem/flash_translation.h" #include "system/logging.h" #include "system/passert.h" #include "util/math.h" #include "util/size.h" #include "clar.h" #include "fake_spi_flash.h" #include "fake_rtc.h" #include "stubs_analytics.h" #include "stubs_logging.h" #include "stubs_mutex.h" #include "stubs_passert.h" #include "stubs_pbl_malloc.h" #include "stubs_pebble_tasks.h" #include "stubs_print.h" #include "stubs_prompt.h" #include "stubs_serial.h" #include "stubs_sleep.h" #include "stubs_task_watchdog.h" #define PFS_SECTOR_SIZE 4096 // a - 4K file full of 1's static const char *const TEST_FILE_A_NAME = "a"; static const size_t TEST_FILE_A_SIZE = 4096; static char s_test_file_a[TEST_FILE_A_SIZE]; // b - 0K file for appending static const char *const TEST_FILE_B_NAME = "b"; static const size_t TEST_FILE_B_SIZE = 0; static const size_t TEST_FILE_B_APPEND_SIZE = 8000; static char s_test_file_b[TEST_FILE_B_APPEND_SIZE]; // c - space to perform non-append writes static const char *const TEST_FILE_C_NAME = "c"; static const size_t TEST_FILE_C_SIZE = 9001; // it's over 9000! static char s_test_file_c[TEST_FILE_C_SIZE]; static uint32_t num_pages(void) { return pfs_get_size() / PFS_SECTOR_SIZE; } void test_pfs__initialize(void) { fake_spi_flash_init(0, 0x1000000); pfs_init(false); pfs_format(true /* write erase headers */); // should have: this should be baked into an image. Perhaps with a test run // that captures a gold flash image? int fd; memset(s_test_file_a, 0, sizeof(s_test_file_a)); fd = pfs_open(TEST_FILE_A_NAME, OP_FLAG_WRITE, FILE_TYPE_STATIC, sizeof(s_test_file_a)); pfs_write(fd, (uint8_t *)s_test_file_a, sizeof(s_test_file_a)); pfs_close(fd); memset(s_test_file_b, 0, sizeof(s_test_file_b)); fd = pfs_open(TEST_FILE_B_NAME, OP_FLAG_WRITE, FILE_TYPE_STATIC, sizeof(s_test_file_b)); pfs_close(fd); fd = pfs_open(TEST_FILE_C_NAME, OP_FLAG_WRITE, FILE_TYPE_STATIC, sizeof (s_test_file_c)); for (unsigned int i = 0; i < ARRAY_LENGTH(s_test_file_c); ++i) { s_test_file_c[i] = 'c'; } pfs_write(fd, (uint8_t *)s_test_file_c, sizeof(s_test_file_c)); pfs_close(fd); } void test_pfs__cleanup(void) { fake_spi_flash_cleanup(); } void test_pfs__create(void) { char hello[] = { 'h', 'e', 'l', 'l', 'o' }; int fd_z = pfs_open("z", OP_FLAG_WRITE, FILE_TYPE_STATIC, sizeof(hello)); cl_assert(fd_z >= 0); int bytes_written = pfs_write(fd_z, (uint8_t *)hello, sizeof(hello)); cl_assert(bytes_written == sizeof(hello)); pfs_close(fd_z); } extern void test_force_garbage_collection(uint16_t start_page); extern uint16_t test_get_file_start_page(int fd); void test_pfs__garbage_collection(void) { char file_small[10]; uint16_t start_page = 0; // create a sectors worth of files for (int i = 0; i < 16; i++) { snprintf(file_small, sizeof(file_small), "file%d", i); uint8_t buf[PFS_SECTOR_SIZE * 2]; memset(&buf[0], i, sizeof(buf)); int fd = pfs_open(file_small, OP_FLAG_WRITE, FILE_TYPE_STATIC, sizeof(buf)); printf("fd=%d\n", fd); cl_assert(fd >= 0); cl_assert_equal_i(pfs_write(fd, &buf[0], sizeof(buf)), sizeof(buf)); if (i == 0) { start_page = test_get_file_start_page(fd); } if ((i % 2) != 0) { pfs_close_and_remove(fd); } else { pfs_close(fd); } } // force garbage collection test_force_garbage_collection(start_page); // now make sure the files are still there! for (int i = 0; i < 16; i+=2) { snprintf(file_small, sizeof(file_small), "file%d", i); uint8_t buf[PFS_SECTOR_SIZE * 2]; uint8_t bufcmp[PFS_SECTOR_SIZE * 2]; memset(&bufcmp[0], i, sizeof(bufcmp)); int fd = pfs_open(file_small, OP_FLAG_READ, FILE_TYPE_STATIC, sizeof(buf)); cl_assert(fd >= 0); cl_assert_equal_i(pfs_read(fd, &buf[0], sizeof(buf)), sizeof(buf)); cl_assert(memcmp(buf, bufcmp, sizeof(buf)) == 0); cl_assert_equal_i(pfs_close(fd), S_SUCCESS); } } void test_pfs__garbage_collection_when_full(void) { char file_name[10]; int num = 0; int fd; while (1) { snprintf(file_name, sizeof(file_name), "file%d", num++); fd = pfs_open(file_name, OP_FLAG_WRITE, FILE_TYPE_STATIC, sizeof(file_name)); cl_assert((fd >= 0) || (fd == E_OUT_OF_STORAGE)); if (fd == E_OUT_OF_STORAGE) { break; } cl_assert_equal_i(pfs_close(fd), S_SUCCESS); } // the file system is full, lets delete a file snprintf(file_name, sizeof(file_name), "file%d", num / 2); fd = pfs_open(file_name, OP_FLAG_READ, 0, 0); cl_assert(fd >= 0); uint16_t target_start_page = test_get_file_start_page(fd); pfs_close_and_remove(fd); // lets force garbage collection on every sector test_force_garbage_collection(target_start_page); // now lets try to create a file fd = pfs_open(file_name, OP_FLAG_WRITE, FILE_TYPE_STATIC, sizeof(file_name)); cl_assert_equal_i(target_start_page, test_get_file_start_page(fd)); cl_assert(fd >= 0); cl_assert_equal_i(pfs_close(fd), S_SUCCESS); } void test_pfs__open(void) { int fd = pfs_open("dne", OP_FLAG_READ, 0, 0); cl_assert(fd == E_DOES_NOT_EXIST); fd = pfs_open("dne", OP_FLAG_OVERWRITE, FILE_TYPE_STATIC, 0); cl_assert(fd == E_DOES_NOT_EXIST); char name[] = {'a'}; int fds[100]; // arbitrarily large # of FDs int i; for (i = 0; i < (sizeof(fds) - 1); i++) { fds[i] = pfs_open(name, OP_FLAG_WRITE, FILE_TYPE_STATIC, 10); name[0]++; if (fds[i] < 0) { break; } } cl_assert(fds[i] == E_OUT_OF_RESOURCES); for (int j = 0; j < i; j++) { cl_assert(pfs_close(fds[j]) == S_SUCCESS); } fd = pfs_open("newfile", OP_FLAG_WRITE, FILE_TYPE_STATIC, 10); cl_assert(fd >= 0); pfs_close(fd); int fd2 = pfs_open("newfile", OP_FLAG_READ, 0, 0); cl_assert((fd2 >= 0) && (fd == fd2)); // should have a cache hit pfs_close(fd2); fd = pfs_open("toobig", OP_FLAG_WRITE, FILE_TYPE_STATIC, 256 * 1024 * 1024); cl_assert(fd == E_OUT_OF_STORAGE); fd = pfs_open("toobig", OP_FLAG_READ, 0, 0); cl_assert(fd == E_DOES_NOT_EXIST); fd = pfs_open(NULL, OP_FLAG_READ, 0, 0); cl_assert(fd == E_INVALID_ARGUMENT); fd = pfs_open("newfile2", OP_FLAG_WRITE, FILE_TYPE_STATIC, 8000); cl_assert(fd >= 0); cl_assert(pfs_close(fd) == S_SUCCESS); } void test_pfs__page_lookup_cache(void) { // create fragmentation in the filesystem char file_small[10]; char buf_small[50]; for (int i = 0; i < num_pages(); i++) { snprintf(file_small, sizeof(file_small), "file%d", i); snprintf(buf_small, sizeof(buf_small), "This is small buf_small %d!", i); int len = strlen(buf_small); int fd = pfs_open(file_small, OP_FLAG_WRITE, FILE_TYPE_STATIC, len); cl_assert(fd >= 0); cl_assert(pfs_write(fd, (uint8_t *)buf_small, len) == len); cl_assert(pfs_close(fd) == S_SUCCESS); // delete every few files and a bunch of pages near the end if (((i & 0x1) == 0) || ((i > ((num_pages() * 7) / 10)) && (i < ((num_pages() * 8) / 10)))) { cl_assert(pfs_remove(file_small) == S_SUCCESS); } } // We limit this number because we would overflow our uint8_t. const int num_regions = MIN(UINT8_MAX, (num_pages() * 5) / 10); int fd = pfs_open("page_lookup", OP_FLAG_WRITE, FILE_TYPE_STATIC, PFS_SECTOR_SIZE * num_regions); cl_assert(fd >= 0); char buf[PFS_SECTOR_SIZE]; for (int i = 0; i < num_regions; i++) { memset(buf, 0xff - i, sizeof(buf)); cl_assert_equal_i(pfs_write(fd, (uint8_t *)&buf[0], sizeof(buf)), sizeof(buf)); } pfs_close(fd); fd = pfs_open("page_lookup", OP_FLAG_READ | OP_FLAG_USE_PAGE_CACHE, FILE_TYPE_STATIC, 0); cl_assert(fd >= 0); for (int i = 0; i < num_regions; i++) { cl_assert_equal_i(pfs_seek(fd, i * sizeof(buf), FSeekSet), i * sizeof(buf)); uint8_t read_byte; for (int j = 0; j < 16; j++) { cl_assert_equal_i(pfs_read(fd, &read_byte, sizeof(read_byte)), sizeof(read_byte)); cl_assert_equal_i(read_byte, 0xff - i); } } pfs_close(fd); } void test_pfs__write(void) { int rv = pfs_write(-1, NULL, 0); cl_assert(rv == E_INVALID_ARGUMENT); rv = pfs_write(1000000, NULL, 0); cl_assert(rv == E_INVALID_ARGUMENT); rv = pfs_write(0, NULL, 0); cl_assert(rv == E_INVALID_ARGUMENT); uint8_t buf[10]; int fd = pfs_open("newfile", OP_FLAG_WRITE | OP_FLAG_READ, FILE_TYPE_STATIC, sizeof(buf)); for (int i = 0; i < sizeof(buf); i++) { buf[i] = i; } rv = pfs_write(fd, NULL, sizeof(buf)); cl_assert(rv == E_INVALID_ARGUMENT); rv = pfs_write(fd, buf, sizeof(buf) / 2); int off = sizeof(buf) / 2; rv = pfs_write(fd, &buf[off], sizeof(buf) - off); cl_assert(rv == (sizeof(buf) - off)); rv = pfs_seek(fd, 0, FSeekSet); cl_assert(rv == 0); memset(buf, 0x00, sizeof(buf)); uint8_t bigbuf[11]; rv = pfs_write(fd, bigbuf, sizeof(bigbuf)); cl_assert(rv == E_RANGE); rv = pfs_read(fd, buf, sizeof(buf)); cl_assert(rv == sizeof(buf)); for (int i = 0; i < sizeof(buf); i++) { cl_assert(buf[i] == i); } pfs_close(fd); fd = pfs_open("newfile", OP_FLAG_READ, 0, 0); rv = pfs_write(fd, buf, sizeof(buf)); cl_assert(rv == E_INVALID_ARGUMENT); } void test_pfs__overwrite(void) { const char *file = "testfile"; const char *string = "original file!"; const char *overwrite_string = "overwrite file!"; int rv; int fd = pfs_open(file, OP_FLAG_WRITE, FILE_TYPE_STATIC, strlen(string)); cl_assert(fd >= 0); rv = pfs_write(fd, (uint8_t *)string, strlen(string)); cl_assert_equal_i(rv, strlen(string)); int tmp_fd = pfs_open(file, OP_FLAG_OVERWRITE, FILE_TYPE_STATIC, strlen(overwrite_string)); cl_assert(fd >= 0); pfs_init(false); // simulate a reboot uint8_t read_buf[strlen(string)]; fd = pfs_open(file, OP_FLAG_READ, 0, 0); cl_assert(fd >= 0); rv = pfs_read(fd, &read_buf[0], strlen(string)); cl_assert_equal_i(rv, strlen(string)); cl_assert(memcmp(string, read_buf, strlen(string)) == 0); pfs_close(fd); tmp_fd = pfs_open(file, OP_FLAG_OVERWRITE, FILE_TYPE_STATIC, strlen(overwrite_string)); cl_assert(tmp_fd >= 0); rv = pfs_write(tmp_fd, (uint8_t *)overwrite_string, strlen(overwrite_string)); cl_assert_equal_i(rv, strlen(overwrite_string)); cl_assert_equal_i(pfs_close(tmp_fd), S_SUCCESS); uint8_t new_buf[strlen(overwrite_string)]; fd = pfs_open(file, OP_FLAG_READ, 0, 0); cl_assert(fd >= 0); rv = pfs_read(fd, &new_buf[0], strlen(overwrite_string)); cl_assert_equal_i(rv, strlen(overwrite_string)); cl_assert(memcmp(overwrite_string, new_buf, strlen(overwrite_string)) == 0); pfs_close(fd); } void test_pfs__seek(void) { int len = 10; int fd = pfs_open("newfile", OP_FLAG_WRITE, FILE_TYPE_STATIC, len); cl_assert(fd >= 0); int rv = pfs_seek(fd, len, FSeekSet); cl_assert(rv == len); rv = pfs_seek(fd, 0, FSeekCur); cl_assert(rv == len); rv = pfs_seek(fd, -5, FSeekCur); cl_assert(rv == (len - 5)); rv = pfs_seek(fd, -6, FSeekCur); cl_assert(rv == E_RANGE); rv = pfs_seek(fd, 0, FSeekSet); cl_assert(rv == 0); rv = pfs_seek(fd, len + 1, FSeekCur); cl_assert(rv == E_RANGE); rv = pfs_seek(fd, len + 1, FSeekSet); cl_assert(rv == E_RANGE); rv = pfs_seek(fd, -1, FSeekSet); cl_assert(rv == E_RANGE); } void test_pfs__read(void) { const int rd_len = 10; int rv = pfs_read(-1, NULL, rd_len); cl_assert(rv == E_INVALID_ARGUMENT); rv = pfs_read(0, NULL, rd_len); cl_assert(rv == E_INVALID_ARGUMENT); int fd = pfs_open("newfile", OP_FLAG_WRITE, FILE_TYPE_STATIC, rd_len); cl_assert(fd >= 0); uint8_t buf[rd_len]; rv = pfs_read(fd, buf, rd_len); cl_assert(rv == E_INVALID_ARGUMENT); pfs_close(fd); fd = pfs_open("newfile", OP_FLAG_READ, 0, 0); rv = pfs_read(fd, buf, rd_len); cl_assert(rv == rd_len); rv = pfs_read(fd, buf, 1); cl_assert(rv == E_RANGE); rv = pfs_seek(fd, 0, FSeekSet); cl_assert(rv == 0); rv = pfs_read(fd, NULL, rd_len); cl_assert(rv == E_INVALID_ARGUMENT); rv = pfs_read(fd, buf, rd_len + 1); cl_assert(rv == E_RANGE); } void test_pfs__close(void) { // shouldn't be able to close fds that are not open int rv = pfs_close(-1); cl_assert(rv == E_INVALID_ARGUMENT); rv = pfs_close(0); cl_assert(rv == E_INVALID_ARGUMENT); rv = pfs_close(1000000); cl_assert(rv == E_INVALID_ARGUMENT); int fd = pfs_open("file", OP_FLAG_WRITE, FILE_TYPE_STATIC, 10); cl_assert(fd >= 0); rv = pfs_close(fd); cl_assert(rv == S_SUCCESS); rv = pfs_close(fd); // should not be able to double close an fd cl_assert(rv == E_INVALID_ARGUMENT); } void test_pfs__remove(void) { cl_assert(pfs_remove(NULL) == E_INVALID_ARGUMENT); char *fname = "abc"; int fd = pfs_open(fname, OP_FLAG_WRITE, FILE_TYPE_STATIC, 10 * 1024); cl_assert(fd >= 0); cl_assert(pfs_close(fd) == S_SUCCESS); cl_assert(pfs_remove(fname) == S_SUCCESS); cl_assert(pfs_remove(fname) == E_DOES_NOT_EXIST); cl_assert(pfs_open(fname, OP_FLAG_READ, 0, 0) == E_DOES_NOT_EXIST); } #define NUM_ENTRIES 3 void test_pfs__close_and_remove(void) { cl_assert(pfs_close_and_remove(-1) == E_INVALID_ARGUMENT); // there shouldn't be any fd open at this point cl_assert(pfs_close_and_remove(2) == E_INVALID_ARGUMENT); int fd[NUM_ENTRIES]; char *fname[NUM_ENTRIES] = {"a", "b", "c" }; // create several files for (int i = 0; i < NUM_ENTRIES; i++) { fd[i] = pfs_open(fname[i], OP_FLAG_WRITE, FILE_TYPE_STATIC, 3 * 4096); cl_assert(fd[i] >= 0); } // test close and remove for (int i = (NUM_ENTRIES - 1); i >= 0; i--) { cl_assert_equal_i(pfs_close_and_remove(fd[i]), S_SUCCESS); } // now make sure none of the files exist for (int i = 0; i < NUM_ENTRIES; i++) { int temp_fd = pfs_open(fname[i], OP_FLAG_READ, 0, 0); cl_assert(temp_fd == E_DOES_NOT_EXIST); } } void test_pfs__discontiguous_page_test(void) { pfs_format(false /* write erase headers */); // start with an empty flash pfs_init(false); char file_small[10], file_large[10]; char buf[50]; for (int i = 0; i < num_pages(); i++) { snprintf(file_small, sizeof(file_small), "file%d", i); snprintf(buf, sizeof(buf), "This is small buf %d!", i); int len = strlen(buf); int fd = pfs_open(file_small, OP_FLAG_WRITE, FILE_TYPE_STATIC, len); cl_assert(fd >= 0); cl_assert(pfs_write(fd, (uint8_t *)buf, len) == len); cl_assert(pfs_close(fd) == S_SUCCESS); if ((i % 2) == 0) { cl_assert(pfs_remove(file_small) == S_SUCCESS); } } // now write two large files that are interleaved between sectors // was negative int bytes_available = (num_pages() / 2) * 4000; int large_file_size = bytes_available / 2; printf("Space Avail = %d\n", bytes_available); uint8_t *bigbuf = (uint8_t *)calloc(large_file_size, 1); uint16_t curr = 0; for (int i = 0; i < 2; i++) { snprintf(file_large, sizeof(file_large), "large%d", i); for (int i = 0; i < large_file_size / 4; i++) { *(((uint32_t *)((uint8_t *)bigbuf)) + i) = curr | (curr << 16); curr++; } int fd = pfs_open(file_large, OP_FLAG_WRITE, FILE_TYPE_STATIC, large_file_size); printf("the fd is: %d\n", fd); cl_assert(fd >= 0); cl_assert(pfs_write(fd, bigbuf, large_file_size) == large_file_size); cl_assert(pfs_close(fd) == S_SUCCESS); } free(bigbuf); // now read back the large files curr = 0; for (int i = 0; i < 2; i++) { snprintf(file_large, sizeof(file_large), "large%d", i); int fd = pfs_open(file_large, OP_FLAG_READ, 0, 0); cl_assert(fd >= 0); size_t sz = pfs_get_file_size(fd); cl_assert(sz == large_file_size); uint8_t *b = calloc(sz, 1); cl_assert(pfs_read(fd, b, sz) == sz); for (int j = 0; j < large_file_size / 4; j++) { uint32_t *val = ((uint32_t *)((uint8_t *)b)) + j; cl_assert(*val == (curr | (curr << 16))); curr++; } cl_assert(pfs_close(fd) == S_SUCCESS); free(b); } } void test_pfs__file_span_regions(void) { pfs_format(false /* write erase headers */); // start with an empty flash pfs_init(false); char name[128]; snprintf(name, sizeof(name), "bigfile"); //Fill up entire memory section, subtract 32768 for header space. int fd = pfs_open(name, OP_FLAG_WRITE, FILE_TYPE_STATIC, pfs_get_size() - (num_pages()*128)); printf("%d\n", pfs_get_size() - 1024); cl_assert(fd >= 0); cl_assert(pfs_close(fd) == S_SUCCESS); cl_assert(pfs_remove(name) == S_SUCCESS); } void test_pfs__active_regions(void) { pfs_format(false); cl_assert(!pfs_active_in_region(0, pfs_get_size())); // erase every page and make sure pfs is active pfs_format(true); cl_assert(pfs_active_in_region(0, pfs_get_size())); // write something on every page and make sure pfs is active char file_name[10]; for (int i = 0; i < num_pages(); i++) { snprintf(file_name, sizeof(file_name), "file%d", i); int fd = pfs_open(file_name, OP_FLAG_WRITE, FILE_TYPE_STATIC, 10); cl_assert(fd >= 0); cl_assert(pfs_close(fd) == S_SUCCESS); } cl_assert(pfs_active_in_region(0, pfs_get_size())); // delete every page and make sure pfs is active for (int i = 0; i < num_pages(); i++) { snprintf(file_name, sizeof(file_name), "file%d", i); cl_assert(pfs_remove(file_name) == S_SUCCESS); } cl_assert(pfs_active_in_region(0, pfs_get_size())); // continuation page on region and make sure pfs is active pfs_format(true); int fd = pfs_open("testfile", OP_FLAG_WRITE, FILE_TYPE_STATIC, 68000); cl_assert(fd >= 0); cl_assert(pfs_close(fd) == S_SUCCESS); cl_assert(pfs_active_in_region(32000, 68000)); } int run_full_flash_region_test(void) { char name[128]; // assumes # pages is a multiple of 2 int st_val = 0; const int f_size = (((PFS_SECTOR_SIZE * 2) - 400) / sizeof(st_val)) * sizeof(st_val); int num_vals = f_size / sizeof(st_val); int idx = 0; int fd, rv; while (true) { snprintf(name, sizeof(name), "file%d", idx); if ((fd = pfs_open(name, OP_FLAG_WRITE, FILE_TYPE_STATIC, f_size)) < 0) { break; } for (int i = 0; i < num_vals; i++) { st_val = st_val + i; rv = pfs_write(fd, (uint8_t *)&st_val, sizeof(st_val)); cl_assert_equal_i(rv, sizeof(st_val)); } pfs_close(fd); idx++; } cl_assert_equal_i((status_t)fd, E_OUT_OF_STORAGE); // read back files to make sure they are all correct st_val = 0; for (int i = 0; i < idx; i++) { snprintf(name, sizeof(name), "file%d", i); if ((fd = pfs_open(name, OP_FLAG_READ, FILE_TYPE_STATIC, f_size)) < 0) { break; } for (int j = 0; j < num_vals; j++) { st_val = st_val + j; int out_val; rv = pfs_read(fd, (uint8_t *)&out_val, sizeof(out_val)); cl_assert_equal_i(rv, sizeof(st_val)); cl_assert_equal_i(out_val, st_val); } pfs_close(fd); } return (idx); } void test_pfs__out_of_space(void) { pfs_format(false /* write erase headers */); // start with an empty flash pfs_init(false); int num_iters = 30; for (int i = 0; i < num_iters; i++) { int files_written = run_full_flash_region_test(); if ((i % 2) == 0) { pfs_init(true); // simulate a reboot } // delete all files for (int i = (files_written - 1); i >= 0; i--) { char name[128]; snprintf(name, sizeof(name), "file%d", i); cl_assert_equal_i(pfs_remove(name), S_SUCCESS); } } } void test_pfs__active_in_region(void) { cl_assert(pfs_active_in_region(0, pfs_get_size())); } void test_pfs__get_size(void) { cl_assert(pfs_get_size() == (ftl_get_size() - SECTOR_SIZE_BYTES)); } void test_pfs__migration(void) { // reset the flash fake_spi_flash_cleanup(); fake_spi_flash_init(0, 0x1000000); extern void ftl_force_version(int version_idx); pfs_init(true); ftl_force_version(1); // simulate a migration by leaving leaving files in various states // in the first region. Then try to add another region and confirm // none of the files have been corrupted char file_small[10]; char buf[50]; const int erase_count = 3; for (int num_erases = 0; num_erases < erase_count; num_erases++) { for (int i = 0; i < num_pages(); i++) { snprintf(file_small, sizeof(file_small), "file%d", i); snprintf(buf, sizeof(buf), "This is small buf %d!", i); int len = strlen(buf); int fd = pfs_open(file_small, OP_FLAG_WRITE, FILE_TYPE_STATIC, len); cl_assert(fd >= 0); cl_assert(pfs_write(fd, (uint8_t *)buf, len) == len); cl_assert(pfs_close(fd) == S_SUCCESS); if (num_erases != (erase_count - 1)) { cl_assert(pfs_remove(file_small) == S_SUCCESS); } } } int original_page_count = num_pages(); ftl_populate_region_list(); // make sure something was added PBL_LOG(LOG_LEVEL_DEBUG, "original pages %u, num pages: %u", original_page_count, num_pages()); cl_assert(original_page_count < num_pages()); for (int i = 0; i < original_page_count; i++) { snprintf(file_small, sizeof(file_small), "file%d", i); snprintf(buf, sizeof(buf), "This is small buf %d!", i); int len = strlen(buf); char rbuf[len]; int fd = pfs_open(file_small, OP_FLAG_READ, FILE_TYPE_STATIC, 0); cl_assert(fd >= 0); cl_assert_equal_i(pfs_read(fd, (uint8_t *)rbuf, len), len); cl_assert(memcmp(rbuf, buf, len) == 0); cl_assert(fd >= 0); cl_assert(pfs_close(fd) == S_SUCCESS); } } static uint32_t s_watch_file_callback_called_count = 0; static void prv_file_changed_callback(void *data) { s_watch_file_callback_called_count++; } void test_pfs__watch_file_callbacks(void) { const char* file_name = "newfile"; PFSCallbackHandle cb_handle = pfs_watch_file(file_name, prv_file_changed_callback, FILE_CHANGED_EVENT_ALL, NULL); // Callback should get invoked if we close with write access s_watch_file_callback_called_count = 0; int fd = pfs_open(file_name, OP_FLAG_WRITE, FILE_TYPE_STATIC, 10); cl_assert(fd >= 0); pfs_close(fd); cl_assert(s_watch_file_callback_called_count == 1); // Callback should not get invoked if we close with read access s_watch_file_callback_called_count = 0; fd = pfs_open(file_name, OP_FLAG_READ, 0, 0); cl_assert(fd >= 0); pfs_close(fd); cl_assert(s_watch_file_callback_called_count == 0); // Callback should get invoked if we remove the file s_watch_file_callback_called_count = 0; pfs_remove(file_name); cl_assert(s_watch_file_callback_called_count == 1); pfs_unwatch_file(cb_handle); // Callback should not get invoked anymore s_watch_file_callback_called_count = 0; fd = pfs_open(file_name, OP_FLAG_WRITE, FILE_TYPE_STATIC, 10); cl_assert(fd >= 0); pfs_close(fd); cl_assert(s_watch_file_callback_called_count == 0); } extern status_t test_scan_for_last_written(void); void test_pfs__last_written_page(void) { pfs_format(true); pfs_init(false); // we just formatted so we shouldn't have a last written page cl_assert(test_scan_for_last_written() < 0); // set up an environment that forces some garbage collection for (int i = 0; i < num_pages(); i++) { char name[10]; snprintf(name, sizeof(name), "test%d", i); int fd = pfs_open(name, OP_FLAG_WRITE, FILE_TYPE_STATIC, 10); cl_assert(fd >= 0); if (((i % 2) == 0) || i > ((num_pages() * 2) / 10)) { cl_assert(pfs_close_and_remove(fd) >= 0); } else { pfs_close(fd); } cl_assert(test_scan_for_last_written() >= 0); } uint32_t size = ((num_pages() * 8) / 10) * PFS_SECTOR_SIZE; int fd = pfs_open("test", OP_FLAG_WRITE, FILE_TYPE_STATIC, size); cl_assert(fd >= 0); cl_assert(test_scan_for_last_written() >= 0); } extern void test_force_reboot_during_garbage_collection(uint16_t start_page); extern void pfs_reset_all_state(void); void test_pfs__reboot_during_gc(void) { pfs_format(true); pfs_reset_all_state(); pfs_init(false); const int pages_to_write = 16; char file_small[10]; uint16_t start_page; for (int i = 0; i < pages_to_write; i++) { snprintf(file_small, sizeof(file_small), "file%d", i); uint8_t buf[PFS_SECTOR_SIZE * 2]; memset(&buf[0], i, sizeof(buf)); int fd = pfs_open(file_small, OP_FLAG_WRITE, FILE_TYPE_STATIC, sizeof(buf)); cl_assert(fd >= 0); cl_assert_equal_i(pfs_write(fd, &buf[0], sizeof(buf)), sizeof(buf)); if (i == 0) { start_page = test_get_file_start_page(fd); } if ((i % 2) != 0) { pfs_close_and_remove(fd); } else { pfs_close(fd); } } // force partial garbage collection test_force_reboot_during_garbage_collection(start_page); // reset our state variables, there should be no files found pfs_reset_all_state(); for (int i = 0; i < pages_to_write; i++) { snprintf(file_small, sizeof(file_small), "file%d", i); int fd = pfs_open(file_small, OP_FLAG_READ, FILE_TYPE_STATIC, 0); cl_assert(fd < 0); } // simulate a reboot, all files should now appear because the GC completes pfs_init(false); // now make sure the files are still there! for (int i = 0; i < pages_to_write; i+=2) { snprintf(file_small, sizeof(file_small), "file%d", i); uint8_t buf[PFS_SECTOR_SIZE * 2]; uint8_t bufcmp[PFS_SECTOR_SIZE * 2]; memset(&bufcmp[0], i, sizeof(bufcmp)); int fd = pfs_open(file_small, OP_FLAG_READ, FILE_TYPE_STATIC, 0); cl_assert(fd >= 0); cl_assert_equal_i(pfs_read(fd, &buf[0], sizeof(buf)), sizeof(buf)); cl_assert(memcmp(buf, bufcmp, sizeof(buf)) == 0); cl_assert_equal_i(pfs_close(fd), S_SUCCESS); } } static bool prv_filename_filter_a_prefix_cb(const char *name) { return (strncmp(name, "a_", 2) == 0); } static bool prv_find_name(ListNode *node, void *data) { PFSFileListEntry *entry = (PFSFileListEntry *)node; return (strcmp(entry->name, (char *)data) == 0); } void test_pfs__file_list(void) { pfs_format(true); pfs_init(false); // Create some files int fd; fd = pfs_open("a_test_0", OP_FLAG_WRITE, FILE_TYPE_STATIC, 10); cl_assert(fd >= 0); fd = pfs_open("a_test_1", OP_FLAG_WRITE, FILE_TYPE_STATIC, 10); cl_assert(fd >= 0); fd = pfs_open("b_test_0", OP_FLAG_WRITE, FILE_TYPE_STATIC, 10); cl_assert(fd >= 0); fd = pfs_open("b_test_1", OP_FLAG_WRITE, FILE_TYPE_STATIC, 10); cl_assert(fd >= 0); // Get a directory listing with no filtering PFSFileListEntry *dir_list; dir_list = pfs_create_file_list(NULL); // Should have 4 entries in it cl_assert_equal_i(list_count(&dir_list->list_node), 4); cl_assert(list_find(&dir_list->list_node, prv_find_name, "a_test_0")); cl_assert(list_find(&dir_list->list_node, prv_find_name, "a_test_1")); cl_assert(list_find(&dir_list->list_node, prv_find_name, "b_test_0")); cl_assert(list_find(&dir_list->list_node, prv_find_name, "b_test_1")); pfs_delete_file_list(dir_list); // Do another search using a filter dir_list = pfs_create_file_list(prv_filename_filter_a_prefix_cb); // Should have 2 entries in it cl_assert_equal_i(list_count(&dir_list->list_node), 2); cl_assert(list_find(&dir_list->list_node, prv_find_name, "a_test_0")); cl_assert(list_find(&dir_list->list_node, prv_find_name, "a_test_1")); pfs_delete_file_list(dir_list); } static bool prv_file_exists(char *name) { int fd = pfs_open(name, OP_FLAG_READ, FILE_TYPE_STATIC, 0); if (fd < 0) { return false; } pfs_close(fd); return true; } extern void test_override_last_written_page(uint16_t start_page); extern void test_force_recalc_of_gc_region(void); // PBL-20973 // // On boot, we scan pfs for the last page which was written. We then scan for a garbage collection // sector (requirement is that no files exist in the entire sector) & use the last page as a // starting point for where we will create initialize new files. // // There is a perfect storm of events which can lead to corruption on reboot. The sequence is as follows: // 1) The last written file is deleted right before a reboot // 2) No other files exist in the same sector as the one where the last file was deleted // // Upon reboot, a file could be created in this region & then later deleted when a garbage // collection was needed. In practice, I think this is most likely to happen after issuing a command like // 'factory reset fast' which we rely on heavily for automated testing void test_pfs__start_page_collides_with_gc_page(void) { pfs_format(true); const uint8_t pages_per_sector = SECTOR_SIZE_BYTES / PFS_SECTOR_SIZE; const uint8_t start_page_offset = pages_per_sector / 2; test_override_last_written_page(start_page_offset); test_force_recalc_of_gc_region(); pfs_init(false); int expected_remaing_files = pages_per_sector - 1; // scatter files across two sectors for (int i = 0; i < (pages_per_sector + start_page_offset); i++) { char filename[20]; sprintf(filename, "test%d", i + start_page_offset); int fd = pfs_open(filename, OP_FLAG_WRITE, FILE_TYPE_STATIC, 10); cl_assert(fd >= 0); pfs_close(fd); // delete some files in the region so a garbage collection will do something if (i >= expected_remaing_files) { pfs_remove(filename); } } test_force_garbage_collection(pages_per_sector); for (int i = 0; i < expected_remaing_files; i++) { char filename[20]; sprintf(filename, "test%d", i + start_page_offset); int fd = pfs_open(filename, OP_FLAG_READ, FILE_TYPE_STATIC, 10); cl_assert(fd >= 0); pfs_close(fd); } } void test_pfs__remove_files(void) { pfs_format(true); pfs_init(false); // Create some files int fd; fd = pfs_open("a_test_0", OP_FLAG_WRITE, FILE_TYPE_STATIC, 10); cl_assert(fd >= 0); pfs_close(fd); fd = pfs_open("a_test_1", OP_FLAG_WRITE, FILE_TYPE_STATIC, 10); cl_assert(fd >= 0); pfs_close(fd); fd = pfs_open("b_test_0", OP_FLAG_WRITE, FILE_TYPE_STATIC, 10); cl_assert(fd >= 0); pfs_close(fd); fd = pfs_open("b_test_1", OP_FLAG_WRITE, FILE_TYPE_STATIC, 10); cl_assert(fd >= 0); pfs_close(fd); // Should have 4 entries in pfs cl_assert_equal_b(prv_file_exists("a_test_0"), true); cl_assert_equal_b(prv_file_exists("a_test_1"), true); cl_assert_equal_b(prv_file_exists("b_test_0"), true); cl_assert_equal_b(prv_file_exists("b_test_1"), true); pfs_remove_files(prv_filename_filter_a_prefix_cb); // Should have only files starting with b_ cl_assert_equal_b(prv_file_exists("a_test_0"), false); cl_assert_equal_b(prv_file_exists("a_test_1"), false); cl_assert_equal_b(prv_file_exists("b_test_0"), true); cl_assert_equal_b(prv_file_exists("b_test_1"), true); } void test_pfs__doesnt_give_out_fd_zero(void) { for (int i = 5; i > 0; --i) { char filename[20]; sprintf(filename, "test%d", i); int fd = pfs_open(filename, OP_FLAG_WRITE, FILE_TYPE_STATIC, 10); cl_assert(fd > 0); } }