mirror of
https://github.com/google/pebble.git
synced 2025-03-19 18:41:21 +00:00
1002 lines
31 KiB
C
1002 lines
31 KiB
C
|
/*
|
||
|
* 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 <string.h>
|
||
|
#include <stdlib.h>
|
||
|
|
||
|
#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);
|
||
|
}
|
||
|
}
|