mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
507e273e1d
mktime function uses tm_isdst member as an indicator whether the time stamp is expected to be in daylight saving time (1) or not (0). FAT filesystem uses local time as mtime, so no information about DST is available from the filesystem. According to mktime documentation, tm_isdst can be set to -1, in which case the C library will try to determine if DST was or wasn't in effect at that time, and will set UTC time accordingly. Note that the conversion from UTC to local time and then back to UTC (time_t -> localtime_r -> FAT timestamp -> mktime -> time_t) does not always recover the same UTC time. In particular, the local time in the hour before DST comes into effect can be interpreted as "before DST" or "after DST", which would correspond to different UTC values. In this case which option the C library chooses is undefined. Closes https://github.com/espressif/esp-idf/issues/9039 Originally reported in https://github.com/espressif/arduino-esp32/issues/6786
924 lines
30 KiB
C
924 lines
30 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <fcntl.h>
|
|
#include <sys/time.h>
|
|
#include <sys/unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
#include <utime.h>
|
|
#include "unity.h"
|
|
#include "esp_log.h"
|
|
#include "esp_system.h"
|
|
#include "esp_vfs.h"
|
|
#include "esp_vfs_fat.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "ff.h"
|
|
#include "test_fatfs_common.h"
|
|
#include "esp_rom_sys.h"
|
|
|
|
const char* fatfs_test_hello_str = "Hello, World!\n";
|
|
const char* fatfs_test_hello_str_utf = "世界,你好!\n";
|
|
|
|
void test_fatfs_create_file_with_text(const char* name, const char* text)
|
|
{
|
|
FILE* f = fopen(name, "wb");
|
|
TEST_ASSERT_NOT_NULL(f);
|
|
TEST_ASSERT_TRUE(fputs(text, f) != EOF);
|
|
TEST_ASSERT_EQUAL(0, fclose(f));
|
|
}
|
|
|
|
void test_fatfs_overwrite_append(const char* filename)
|
|
{
|
|
/* Create new file with 'aaaa' */
|
|
test_fatfs_create_file_with_text(filename, "aaaa");
|
|
|
|
/* Append 'bbbb' to file */
|
|
FILE *f_a = fopen(filename, "a");
|
|
TEST_ASSERT_NOT_NULL(f_a);
|
|
TEST_ASSERT_NOT_EQUAL(EOF, fputs("bbbb", f_a));
|
|
TEST_ASSERT_EQUAL(0, fclose(f_a));
|
|
|
|
/* Read back 8 bytes from file, verify it's 'aaaabbbb' */
|
|
char buf[10] = { 0 };
|
|
FILE *f_r = fopen(filename, "r");
|
|
TEST_ASSERT_NOT_NULL(f_r);
|
|
TEST_ASSERT_EQUAL(8, fread(buf, 1, 8, f_r));
|
|
TEST_ASSERT_EQUAL_STRING_LEN("aaaabbbb", buf, 8);
|
|
|
|
/* Be sure we're at end of file */
|
|
TEST_ASSERT_EQUAL(0, fread(buf, 1, 8, f_r));
|
|
|
|
TEST_ASSERT_EQUAL(0, fclose(f_r));
|
|
|
|
/* Overwrite file with 'cccc' */
|
|
test_fatfs_create_file_with_text(filename, "cccc");
|
|
|
|
/* Verify file now only contains 'cccc' */
|
|
f_r = fopen(filename, "r");
|
|
TEST_ASSERT_NOT_NULL(f_r);
|
|
bzero(buf, sizeof(buf));
|
|
TEST_ASSERT_EQUAL(4, fread(buf, 1, 8, f_r)); // trying to read 8 bytes, only expecting 4
|
|
TEST_ASSERT_EQUAL_STRING_LEN("cccc", buf, 4);
|
|
TEST_ASSERT_EQUAL(0, fclose(f_r));
|
|
}
|
|
|
|
void test_fatfs_read_file(const char* filename)
|
|
{
|
|
FILE* f = fopen(filename, "r");
|
|
TEST_ASSERT_NOT_NULL(f);
|
|
char buf[32] = { 0 };
|
|
int cb = fread(buf, 1, sizeof(buf), f);
|
|
TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str), cb);
|
|
TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str, buf));
|
|
TEST_ASSERT_EQUAL(0, fclose(f));
|
|
}
|
|
|
|
void test_fatfs_read_file_utf_8(const char* filename)
|
|
{
|
|
FILE* f = fopen(filename, "r");
|
|
TEST_ASSERT_NOT_NULL(f);
|
|
char buf[64] = { 0 }; //Doubled buffer size to allow for longer UTF-8 strings
|
|
int cb = fread(buf, 1, sizeof(buf), f);
|
|
TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str_utf), cb);
|
|
TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str_utf, buf));
|
|
TEST_ASSERT_EQUAL(0, fclose(f));
|
|
}
|
|
|
|
void test_fatfs_pread_file(const char* filename)
|
|
{
|
|
char buf[32] = { 0 };
|
|
const int fd = open(filename, O_RDONLY);
|
|
TEST_ASSERT_NOT_EQUAL(-1, fd);
|
|
|
|
int r = pread(fd, buf, sizeof(buf), 0); // it is a regular read() with offset==0
|
|
TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str, buf));
|
|
TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str), r);
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
r = pread(fd, buf, sizeof(buf), 1); // offset==1
|
|
TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str + 1, buf));
|
|
TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str) - 1, r);
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
r = pread(fd, buf, sizeof(buf), 5); // offset==5
|
|
TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str + 5, buf));
|
|
TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str) - 5, r);
|
|
|
|
// regular read() should work now because pread() should not affect the current position in file
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
r = read(fd, buf, sizeof(buf)); // note that this is read() and not pread()
|
|
TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str, buf));
|
|
TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str), r);
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
r = pread(fd, buf, sizeof(buf), 10); // offset==10
|
|
TEST_ASSERT_EQUAL(0, strcmp(fatfs_test_hello_str + 10, buf));
|
|
TEST_ASSERT_EQUAL(strlen(fatfs_test_hello_str) - 10, r);
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
r = pread(fd, buf, sizeof(buf), strlen(fatfs_test_hello_str) + 1); // offset to EOF
|
|
TEST_ASSERT_EQUAL(0, r);
|
|
|
|
TEST_ASSERT_EQUAL(0, close(fd));
|
|
}
|
|
|
|
static void test_pwrite(const char *filename, off_t offset, const char *msg)
|
|
{
|
|
const int fd = open(filename, O_WRONLY);
|
|
TEST_ASSERT_NOT_EQUAL(-1, fd);
|
|
|
|
const off_t current_pos = lseek(fd, 0, SEEK_END); // O_APPEND is not the same - jumps to the end only before write()
|
|
|
|
const int r = pwrite(fd, msg, strlen(msg), offset);
|
|
TEST_ASSERT_EQUAL(strlen(msg), r);
|
|
|
|
TEST_ASSERT_EQUAL(current_pos, lseek(fd, 0, SEEK_CUR)); // pwrite should not move the pointer
|
|
|
|
TEST_ASSERT_EQUAL(0, close(fd));
|
|
}
|
|
|
|
static void test_file_content(const char *filename, const char *msg)
|
|
{
|
|
char buf[32] = { 0 };
|
|
const int fd = open(filename, O_RDONLY);
|
|
TEST_ASSERT_NOT_EQUAL(-1, fd);
|
|
|
|
int r = read(fd, buf, sizeof(buf));
|
|
TEST_ASSERT_NOT_EQUAL(-1, r);
|
|
TEST_ASSERT_EQUAL(0, strcmp(msg, buf));
|
|
|
|
TEST_ASSERT_EQUAL(0, close(fd));
|
|
}
|
|
|
|
void test_fatfs_pwrite_file(const char *filename)
|
|
{
|
|
int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC);
|
|
TEST_ASSERT_NOT_EQUAL(-1, fd);
|
|
TEST_ASSERT_EQUAL(0, close(fd));
|
|
|
|
test_pwrite(filename, 0, "Hello");
|
|
test_file_content(filename, "Hello");
|
|
|
|
test_pwrite(filename, strlen("Hello"), ", world!");
|
|
test_file_content(filename, "Hello, world!");
|
|
test_pwrite(filename, strlen("Hello, "), "Dolly");
|
|
test_file_content(filename, "Hello, Dolly!");
|
|
}
|
|
|
|
void test_fatfs_open_max_files(const char* filename_prefix, size_t files_count)
|
|
{
|
|
FILE** files = calloc(files_count, sizeof(FILE*));
|
|
for (size_t i = 0; i < files_count; ++i) {
|
|
char name[32];
|
|
snprintf(name, sizeof(name), "%s_%d.txt", filename_prefix, i);
|
|
files[i] = fopen(name, "w");
|
|
TEST_ASSERT_NOT_NULL(files[i]);
|
|
}
|
|
/* close everything and clean up */
|
|
for (size_t i = 0; i < files_count; ++i) {
|
|
fclose(files[i]);
|
|
}
|
|
free(files);
|
|
}
|
|
|
|
void test_fatfs_lseek(const char* filename)
|
|
{
|
|
FILE* f = fopen(filename, "wb+");
|
|
TEST_ASSERT_NOT_NULL(f);
|
|
TEST_ASSERT_EQUAL(11, fprintf(f, "0123456789\n"));
|
|
TEST_ASSERT_EQUAL(0, fseek(f, -2, SEEK_CUR));
|
|
TEST_ASSERT_EQUAL('9', fgetc(f));
|
|
TEST_ASSERT_EQUAL(0, fseek(f, 3, SEEK_SET));
|
|
TEST_ASSERT_EQUAL('3', fgetc(f));
|
|
TEST_ASSERT_EQUAL(0, fseek(f, -3, SEEK_END));
|
|
TEST_ASSERT_EQUAL('8', fgetc(f));
|
|
TEST_ASSERT_EQUAL(0, fseek(f, 3, SEEK_END));
|
|
TEST_ASSERT_EQUAL(14, ftell(f));
|
|
TEST_ASSERT_EQUAL(4, fprintf(f, "abc\n"));
|
|
TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_END));
|
|
TEST_ASSERT_EQUAL(18, ftell(f));
|
|
TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_SET));
|
|
char buf[20];
|
|
|
|
TEST_ASSERT_EQUAL(18, fread(buf, 1, sizeof(buf), f));
|
|
const char ref_buf[] = "0123456789\n\0\0\0abc\n";
|
|
TEST_ASSERT_EQUAL_INT8_ARRAY(ref_buf, buf, sizeof(ref_buf) - 1);
|
|
TEST_ASSERT_EQUAL(0, fclose(f));
|
|
|
|
#ifdef CONFIG_FATFS_USE_FASTSEEK
|
|
f = fopen(filename, "rb+");
|
|
TEST_ASSERT_NOT_NULL(f);
|
|
TEST_ASSERT_EQUAL(0, fseek(f, 0, SEEK_END));
|
|
TEST_ASSERT_EQUAL(18, ftell(f));
|
|
TEST_ASSERT_EQUAL(0, fseek(f, -4, SEEK_CUR));
|
|
TEST_ASSERT_EQUAL(14, ftell(f));
|
|
TEST_ASSERT_EQUAL(0, fseek(f, -14, SEEK_CUR));
|
|
TEST_ASSERT_EQUAL(0, ftell(f));
|
|
|
|
TEST_ASSERT_EQUAL(18, fread(buf, 1, sizeof(buf), f));
|
|
TEST_ASSERT_EQUAL_INT8_ARRAY(ref_buf, buf, sizeof(ref_buf) - 1);
|
|
TEST_ASSERT_EQUAL(0, fclose(f));
|
|
#endif
|
|
|
|
}
|
|
|
|
void test_fatfs_truncate_file(const char* filename)
|
|
{
|
|
int read = 0;
|
|
int truncated_len = 0;
|
|
|
|
const char input[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
char output[sizeof(input)];
|
|
|
|
FILE* f = fopen(filename, "wb");
|
|
|
|
TEST_ASSERT_NOT_NULL(f);
|
|
TEST_ASSERT_EQUAL(strlen(input), fprintf(f, input));
|
|
|
|
TEST_ASSERT_EQUAL(0, fclose(f));
|
|
|
|
|
|
// Extending file beyond size is not supported
|
|
TEST_ASSERT_EQUAL(-1, truncate(filename, strlen(input) + 1));
|
|
TEST_ASSERT_EQUAL(errno, EPERM);
|
|
|
|
TEST_ASSERT_EQUAL(-1, truncate(filename, -1));
|
|
TEST_ASSERT_EQUAL(errno, EINVAL);
|
|
|
|
|
|
// Truncating should succeed
|
|
const char truncated_1[] = "ABCDEFGHIJ";
|
|
truncated_len = strlen(truncated_1);
|
|
|
|
TEST_ASSERT_EQUAL(0, truncate(filename, truncated_len));
|
|
|
|
f = fopen(filename, "rb");
|
|
TEST_ASSERT_NOT_NULL(f);
|
|
|
|
memset(output, 0, sizeof(output));
|
|
read = fread(output, 1, sizeof(output), f);
|
|
|
|
TEST_ASSERT_EQUAL(truncated_len, read);
|
|
TEST_ASSERT_EQUAL_STRING_LEN(truncated_1, output, truncated_len);
|
|
|
|
TEST_ASSERT_EQUAL(0, fclose(f));
|
|
|
|
|
|
// Once truncated, the new file size should be the basis
|
|
// whether truncation should succeed or not
|
|
TEST_ASSERT_EQUAL(-1, truncate(filename, truncated_len + 1));
|
|
TEST_ASSERT_EQUAL(EPERM, errno);
|
|
|
|
TEST_ASSERT_EQUAL(-1, truncate(filename, strlen(input)));
|
|
TEST_ASSERT_EQUAL(EPERM, errno);
|
|
|
|
TEST_ASSERT_EQUAL(-1, truncate(filename, strlen(input) + 1));
|
|
TEST_ASSERT_EQUAL(EPERM, errno);
|
|
|
|
TEST_ASSERT_EQUAL(-1, truncate(filename, -1));
|
|
TEST_ASSERT_EQUAL(EINVAL, errno);
|
|
|
|
|
|
// Truncating a truncated file should succeed
|
|
const char truncated_2[] = "ABCDE";
|
|
truncated_len = strlen(truncated_2);
|
|
|
|
TEST_ASSERT_EQUAL(0, truncate(filename, truncated_len));
|
|
|
|
f = fopen(filename, "rb");
|
|
TEST_ASSERT_NOT_NULL(f);
|
|
|
|
memset(output, 0, sizeof(output));
|
|
read = fread(output, 1, sizeof(output), f);
|
|
|
|
TEST_ASSERT_EQUAL(truncated_len, read);
|
|
TEST_ASSERT_EQUAL_STRING_LEN(truncated_2, output, truncated_len);
|
|
|
|
TEST_ASSERT_EQUAL(0, fclose(f));
|
|
}
|
|
|
|
void test_fatfs_ftruncate_file(const char* filename)
|
|
{
|
|
int truncated_len = 0;
|
|
|
|
const char input[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
char output[sizeof(input)];
|
|
|
|
int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC);
|
|
TEST_ASSERT_NOT_EQUAL(-1, fd);
|
|
|
|
TEST_ASSERT_EQUAL(strlen(input), write(fd, input, strlen(input)));
|
|
|
|
// Extending file beyond size is not supported
|
|
TEST_ASSERT_EQUAL(-1, ftruncate(fd, strlen(input) + 1));
|
|
TEST_ASSERT_EQUAL(errno, EPERM);
|
|
|
|
TEST_ASSERT_EQUAL(-1, ftruncate(fd, -1));
|
|
TEST_ASSERT_EQUAL(errno, EINVAL);
|
|
|
|
// Truncating should succeed
|
|
const char truncated_1[] = "ABCDEFGHIJ";
|
|
truncated_len = strlen(truncated_1);
|
|
TEST_ASSERT_EQUAL(0, ftruncate(fd, truncated_len));
|
|
TEST_ASSERT_EQUAL(0, close(fd));
|
|
|
|
// open file for reading and validate the content
|
|
fd = open(filename, O_RDONLY);
|
|
TEST_ASSERT_NOT_EQUAL(-1, fd);
|
|
|
|
memset(output, 0, sizeof(output));
|
|
|
|
TEST_ASSERT_EQUAL(truncated_len, read(fd, output, sizeof(output)));
|
|
|
|
TEST_ASSERT_EQUAL_STRING_LEN(truncated_1, output, truncated_len);
|
|
|
|
TEST_ASSERT_EQUAL(0, close(fd));
|
|
|
|
// further truncate the file
|
|
fd = open(filename, O_WRONLY);
|
|
TEST_ASSERT_NOT_EQUAL(-1, fd);
|
|
// Once truncated, the new file size should be the basis
|
|
// whether truncation should succeed or not
|
|
TEST_ASSERT_EQUAL(-1, ftruncate(fd, truncated_len + 1));
|
|
TEST_ASSERT_EQUAL(EPERM, errno);
|
|
|
|
TEST_ASSERT_EQUAL(-1, ftruncate(fd, strlen(input)));
|
|
TEST_ASSERT_EQUAL(EPERM, errno);
|
|
|
|
TEST_ASSERT_EQUAL(-1, ftruncate(fd, strlen(input) + 1));
|
|
TEST_ASSERT_EQUAL(EPERM, errno);
|
|
|
|
TEST_ASSERT_EQUAL(-1, ftruncate(fd, -1));
|
|
TEST_ASSERT_EQUAL(EINVAL, errno);
|
|
|
|
// Truncating a truncated file should succeed
|
|
const char truncated_2[] = "ABCDE";
|
|
truncated_len = strlen(truncated_2);
|
|
|
|
TEST_ASSERT_EQUAL(0, ftruncate(fd, truncated_len));
|
|
TEST_ASSERT_EQUAL(0, close(fd));
|
|
|
|
// open file for reading and validate the content
|
|
fd = open(filename, O_RDONLY);
|
|
TEST_ASSERT_NOT_EQUAL(-1, fd);
|
|
|
|
memset(output, 0, sizeof(output));
|
|
|
|
TEST_ASSERT_EQUAL(truncated_len, read(fd, output, sizeof(output)));
|
|
TEST_ASSERT_EQUAL_STRING_LEN(truncated_2, output, truncated_len);
|
|
|
|
TEST_ASSERT_EQUAL(0, close(fd));
|
|
}
|
|
|
|
void test_fatfs_stat(const char* filename, const char* root_dir)
|
|
{
|
|
struct tm tm = {
|
|
.tm_year = 2017 - 1900,
|
|
.tm_mon = 11,
|
|
.tm_mday = 8,
|
|
.tm_hour = 19,
|
|
.tm_min = 51,
|
|
.tm_sec = 10
|
|
};
|
|
time_t t = mktime(&tm);
|
|
printf("Setting time: %s", asctime(&tm));
|
|
struct timeval now = { .tv_sec = t };
|
|
settimeofday(&now, NULL);
|
|
|
|
test_fatfs_create_file_with_text(filename, "foo\n");
|
|
|
|
struct stat st;
|
|
TEST_ASSERT_EQUAL(0, stat(filename, &st));
|
|
time_t mtime = st.st_mtime;
|
|
struct tm mtm;
|
|
localtime_r(&mtime, &mtm);
|
|
printf("File time: %s", asctime(&mtm));
|
|
TEST_ASSERT(abs(mtime - t) < 2); // fatfs library stores time with 2 second precision
|
|
|
|
TEST_ASSERT(st.st_mode & S_IFREG);
|
|
TEST_ASSERT_FALSE(st.st_mode & S_IFDIR);
|
|
|
|
memset(&st, 0, sizeof(st));
|
|
TEST_ASSERT_EQUAL(0, stat(root_dir, &st));
|
|
TEST_ASSERT(st.st_mode & S_IFDIR);
|
|
TEST_ASSERT_FALSE(st.st_mode & S_IFREG);
|
|
}
|
|
|
|
void test_fatfs_mtime_dst(const char* filename, const char* root_dir)
|
|
{
|
|
struct timeval tv = { 1653638041, 0 };
|
|
settimeofday(&tv, NULL);
|
|
setenv("TZ", "MST7MDT,M3.2.0,M11.1.0", 1);
|
|
tzset();
|
|
|
|
struct tm tm;
|
|
time_t sys_time = tv.tv_sec;
|
|
localtime_r(&sys_time, &tm);
|
|
printf("Setting time: %s", asctime(&tm));
|
|
|
|
test_fatfs_create_file_with_text(filename, "foo\n");
|
|
|
|
struct stat st;
|
|
TEST_ASSERT_EQUAL(0, stat(filename, &st));
|
|
|
|
time_t mtime = st.st_mtime;
|
|
struct tm mtm;
|
|
localtime_r(&mtime, &mtm);
|
|
printf("File time: %s", asctime(&mtm));
|
|
|
|
TEST_ASSERT(llabs(mtime - sys_time) < 2); // fatfs library stores time with 2 second precision
|
|
|
|
unsetenv("TZ");
|
|
tzset();
|
|
}
|
|
|
|
void test_fatfs_utime(const char* filename, const char* root_dir)
|
|
{
|
|
struct stat achieved_stat;
|
|
struct tm desired_tm;
|
|
struct utimbuf desired_time = {
|
|
.actime = 0, // access time is not supported
|
|
.modtime = 0,
|
|
};
|
|
time_t false_now = 0;
|
|
memset(&desired_tm, 0, sizeof(struct tm));
|
|
|
|
{
|
|
// Setting up a false actual time - used when the file is created and for modification with the current time
|
|
desired_tm.tm_mon = 10 - 1;
|
|
desired_tm.tm_mday = 31;
|
|
desired_tm.tm_year = 2018 - 1900;
|
|
desired_tm.tm_hour = 10;
|
|
desired_tm.tm_min = 35;
|
|
desired_tm.tm_sec = 23;
|
|
|
|
false_now = mktime(&desired_tm);
|
|
|
|
struct timeval now = { .tv_sec = false_now };
|
|
settimeofday(&now, NULL);
|
|
}
|
|
test_fatfs_create_file_with_text(filename, "");
|
|
|
|
// 00:00:00. January 1st, 1980 - FATFS cannot handle earlier dates
|
|
desired_tm.tm_mon = 1 - 1;
|
|
desired_tm.tm_mday = 1;
|
|
desired_tm.tm_year = 1980 - 1900;
|
|
desired_tm.tm_hour = 0;
|
|
desired_tm.tm_min = 0;
|
|
desired_tm.tm_sec = 0;
|
|
printf("Testing mod. time: %s", asctime(&desired_tm));
|
|
desired_time.modtime = mktime(&desired_tm);
|
|
TEST_ASSERT_EQUAL(0, utime(filename, &desired_time));
|
|
TEST_ASSERT_EQUAL(0, stat(filename, &achieved_stat));
|
|
TEST_ASSERT_EQUAL_UINT32(desired_time.modtime, achieved_stat.st_mtime);
|
|
|
|
// current time
|
|
TEST_ASSERT_EQUAL(0, utime(filename, NULL));
|
|
TEST_ASSERT_EQUAL(0, stat(filename, &achieved_stat));
|
|
printf("Mod. time changed to (false actual time): %s", ctime(&achieved_stat.st_mtime));
|
|
TEST_ASSERT_NOT_EQUAL(desired_time.modtime, achieved_stat.st_mtime);
|
|
TEST_ASSERT(false_now - achieved_stat.st_mtime <= 2); // two seconds of tolerance are given
|
|
|
|
// 23:59:08. December 31st, 2037
|
|
desired_tm.tm_mon = 12 - 1;
|
|
desired_tm.tm_mday = 31;
|
|
desired_tm.tm_year = 2037 - 1900;
|
|
desired_tm.tm_hour = 23;
|
|
desired_tm.tm_min = 59;
|
|
desired_tm.tm_sec = 8;
|
|
printf("Testing mod. time: %s", asctime(&desired_tm));
|
|
desired_time.modtime = mktime(&desired_tm);
|
|
TEST_ASSERT_EQUAL(0, utime(filename, &desired_time));
|
|
TEST_ASSERT_EQUAL(0, stat(filename, &achieved_stat));
|
|
TEST_ASSERT_EQUAL_UINT32(desired_time.modtime, achieved_stat.st_mtime);
|
|
|
|
//WARNING: it has the Unix Millenium bug (Y2K38)
|
|
|
|
// 00:00:00. January 1st, 1970 - FATFS cannot handle years before 1980
|
|
desired_tm.tm_mon = 1 - 1;
|
|
desired_tm.tm_mday = 1;
|
|
desired_tm.tm_year = 1970 - 1900;
|
|
desired_tm.tm_hour = 0;
|
|
desired_tm.tm_min = 0;
|
|
desired_tm.tm_sec = 0;
|
|
printf("Testing mod. time: %s", asctime(&desired_tm));
|
|
desired_time.modtime = mktime(&desired_tm);
|
|
TEST_ASSERT_EQUAL(-1, utime(filename, &desired_time));
|
|
TEST_ASSERT_EQUAL(EINVAL, errno);
|
|
}
|
|
|
|
void test_fatfs_unlink(const char* filename)
|
|
{
|
|
test_fatfs_create_file_with_text(filename, "unlink\n");
|
|
|
|
TEST_ASSERT_EQUAL(0, unlink(filename));
|
|
|
|
TEST_ASSERT_NULL(fopen(filename, "r"));
|
|
}
|
|
|
|
void test_fatfs_link_rename(const char* filename_prefix)
|
|
{
|
|
char name_copy[64];
|
|
char name_dst[64];
|
|
char name_src[64];
|
|
snprintf(name_copy, sizeof(name_copy), "%s_cpy.txt", filename_prefix);
|
|
snprintf(name_dst, sizeof(name_dst), "%s_dst.txt", filename_prefix);
|
|
snprintf(name_src, sizeof(name_src), "%s_src.txt", filename_prefix);
|
|
|
|
unlink(name_copy);
|
|
unlink(name_dst);
|
|
unlink(name_src);
|
|
|
|
FILE* f = fopen(name_src, "w+");
|
|
TEST_ASSERT_NOT_NULL(f);
|
|
const char* str = "0123456789";
|
|
for (int i = 0; i < 4000; ++i) {
|
|
TEST_ASSERT_NOT_EQUAL(EOF, fputs(str, f));
|
|
}
|
|
TEST_ASSERT_EQUAL(0, fclose(f));
|
|
TEST_ASSERT_EQUAL(0, link(name_src, name_copy));
|
|
FILE* fcopy = fopen(name_copy, "r");
|
|
TEST_ASSERT_NOT_NULL(fcopy);
|
|
TEST_ASSERT_EQUAL(0, fseek(fcopy, 0, SEEK_END));
|
|
TEST_ASSERT_EQUAL(40000, ftell(fcopy));
|
|
TEST_ASSERT_EQUAL(0, fclose(fcopy));
|
|
TEST_ASSERT_EQUAL(0, rename(name_copy, name_dst));
|
|
TEST_ASSERT_NULL(fopen(name_copy, "r"));
|
|
FILE* fdst = fopen(name_dst, "r");
|
|
TEST_ASSERT_NOT_NULL(fdst);
|
|
TEST_ASSERT_EQUAL(0, fseek(fdst, 0, SEEK_END));
|
|
TEST_ASSERT_EQUAL(40000, ftell(fdst));
|
|
TEST_ASSERT_EQUAL(0, fclose(fdst));
|
|
}
|
|
|
|
void test_fatfs_mkdir_rmdir(const char* filename_prefix)
|
|
{
|
|
char name_dir1[64];
|
|
char name_dir2[64];
|
|
char name_dir2_file[64];
|
|
snprintf(name_dir1, sizeof(name_dir1), "%s1", filename_prefix);
|
|
snprintf(name_dir2, sizeof(name_dir2), "%s2", filename_prefix);
|
|
snprintf(name_dir2_file, sizeof(name_dir2_file), "%s2/1.txt", filename_prefix);
|
|
|
|
TEST_ASSERT_EQUAL(0, mkdir(name_dir1, 0755));
|
|
struct stat st;
|
|
TEST_ASSERT_EQUAL(0, stat(name_dir1, &st));
|
|
TEST_ASSERT_TRUE(st.st_mode & S_IFDIR);
|
|
TEST_ASSERT_FALSE(st.st_mode & S_IFREG);
|
|
TEST_ASSERT_EQUAL(0, rmdir(name_dir1));
|
|
TEST_ASSERT_EQUAL(-1, stat(name_dir1, &st));
|
|
|
|
TEST_ASSERT_EQUAL(0, mkdir(name_dir2, 0755));
|
|
test_fatfs_create_file_with_text(name_dir2_file, "foo\n");
|
|
TEST_ASSERT_EQUAL(0, stat(name_dir2, &st));
|
|
TEST_ASSERT_TRUE(st.st_mode & S_IFDIR);
|
|
TEST_ASSERT_FALSE(st.st_mode & S_IFREG);
|
|
TEST_ASSERT_EQUAL(0, stat(name_dir2_file, &st));
|
|
TEST_ASSERT_FALSE(st.st_mode & S_IFDIR);
|
|
TEST_ASSERT_TRUE(st.st_mode & S_IFREG);
|
|
TEST_ASSERT_EQUAL(-1, rmdir(name_dir2));
|
|
TEST_ASSERT_EQUAL(0, unlink(name_dir2_file));
|
|
TEST_ASSERT_EQUAL(0, rmdir(name_dir2));
|
|
}
|
|
|
|
void test_fatfs_can_opendir(const char* path)
|
|
{
|
|
char name_dir_file[64];
|
|
const char * file_name = "test_opd.txt";
|
|
snprintf(name_dir_file, sizeof(name_dir_file), "%s/%s", path, file_name);
|
|
unlink(name_dir_file);
|
|
test_fatfs_create_file_with_text(name_dir_file, "test_opendir\n");
|
|
DIR* dir = opendir(path);
|
|
TEST_ASSERT_NOT_NULL(dir);
|
|
bool found = false;
|
|
while (true) {
|
|
struct dirent* de = readdir(dir);
|
|
if (!de) {
|
|
break;
|
|
}
|
|
if (strcasecmp(de->d_name, file_name) == 0) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
TEST_ASSERT_TRUE(found);
|
|
TEST_ASSERT_EQUAL(0, closedir(dir));
|
|
unlink(name_dir_file);
|
|
}
|
|
|
|
void test_fatfs_opendir_readdir_rewinddir(const char* dir_prefix)
|
|
{
|
|
char name_dir_inner_file[64];
|
|
char name_dir_inner[64];
|
|
char name_dir_file3[64];
|
|
char name_dir_file2[64];
|
|
char name_dir_file1[64];
|
|
|
|
snprintf(name_dir_inner_file, sizeof(name_dir_inner_file), "%s/inner/3.txt", dir_prefix);
|
|
snprintf(name_dir_inner, sizeof(name_dir_inner), "%s/inner", dir_prefix);
|
|
snprintf(name_dir_file3, sizeof(name_dir_file2), "%s/boo.bin", dir_prefix);
|
|
snprintf(name_dir_file2, sizeof(name_dir_file2), "%s/2.txt", dir_prefix);
|
|
snprintf(name_dir_file1, sizeof(name_dir_file1), "%s/1.txt", dir_prefix);
|
|
|
|
unlink(name_dir_inner_file);
|
|
rmdir(name_dir_inner);
|
|
unlink(name_dir_file1);
|
|
unlink(name_dir_file2);
|
|
unlink(name_dir_file3);
|
|
rmdir(dir_prefix);
|
|
|
|
TEST_ASSERT_EQUAL(0, mkdir(dir_prefix, 0755));
|
|
test_fatfs_create_file_with_text(name_dir_file1, "1\n");
|
|
test_fatfs_create_file_with_text(name_dir_file2, "2\n");
|
|
test_fatfs_create_file_with_text(name_dir_file3, "\01\02\03");
|
|
TEST_ASSERT_EQUAL(0, mkdir(name_dir_inner, 0755));
|
|
test_fatfs_create_file_with_text(name_dir_inner_file, "3\n");
|
|
|
|
DIR* dir = opendir(dir_prefix);
|
|
TEST_ASSERT_NOT_NULL(dir);
|
|
int count = 0;
|
|
const char* names[4];
|
|
while(count < 4) {
|
|
struct dirent* de = readdir(dir);
|
|
if (!de) {
|
|
break;
|
|
}
|
|
printf("found '%s'\n", de->d_name);
|
|
if (strcasecmp(de->d_name, "1.txt") == 0) {
|
|
TEST_ASSERT_TRUE(de->d_type == DT_REG);
|
|
names[count] = "1.txt";
|
|
++count;
|
|
} else if (strcasecmp(de->d_name, "2.txt") == 0) {
|
|
TEST_ASSERT_TRUE(de->d_type == DT_REG);
|
|
names[count] = "2.txt";
|
|
++count;
|
|
} else if (strcasecmp(de->d_name, "inner") == 0) {
|
|
TEST_ASSERT_TRUE(de->d_type == DT_DIR);
|
|
names[count] = "inner";
|
|
++count;
|
|
} else if (strcasecmp(de->d_name, "boo.bin") == 0) {
|
|
TEST_ASSERT_TRUE(de->d_type == DT_REG);
|
|
names[count] = "boo.bin";
|
|
++count;
|
|
} else {
|
|
TEST_FAIL_MESSAGE("unexpected directory entry");
|
|
}
|
|
}
|
|
TEST_ASSERT_EQUAL(count, 4);
|
|
|
|
rewinddir(dir);
|
|
struct dirent* de = readdir(dir);
|
|
TEST_ASSERT_NOT_NULL(de);
|
|
TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[0]));
|
|
seekdir(dir, 3);
|
|
de = readdir(dir);
|
|
TEST_ASSERT_NOT_NULL(de);
|
|
TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[3]));
|
|
seekdir(dir, 1);
|
|
de = readdir(dir);
|
|
TEST_ASSERT_NOT_NULL(de);
|
|
TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[1]));
|
|
seekdir(dir, 2);
|
|
de = readdir(dir);
|
|
TEST_ASSERT_NOT_NULL(de);
|
|
TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[2]));
|
|
|
|
TEST_ASSERT_EQUAL(0, closedir(dir));
|
|
}
|
|
|
|
void test_fatfs_opendir_readdir_rewinddir_utf_8(const char* dir_prefix)
|
|
{
|
|
char name_dir_inner_file[64];
|
|
char name_dir_inner[64];
|
|
char name_dir_file3[64];
|
|
char name_dir_file2[64];
|
|
char name_dir_file1[64];
|
|
|
|
snprintf(name_dir_inner_file, sizeof(name_dir_inner_file), "%s/内部目录/内部文件.txt", dir_prefix);
|
|
snprintf(name_dir_inner, sizeof(name_dir_inner), "%s/内部目录", dir_prefix);
|
|
snprintf(name_dir_file3, sizeof(name_dir_file3), "%s/文件三.bin", dir_prefix);
|
|
snprintf(name_dir_file2, sizeof(name_dir_file2), "%s/文件二.txt", dir_prefix);
|
|
snprintf(name_dir_file1, sizeof(name_dir_file1), "%s/文件一.txt", dir_prefix);
|
|
|
|
unlink(name_dir_inner_file);
|
|
rmdir(name_dir_inner);
|
|
unlink(name_dir_file1);
|
|
unlink(name_dir_file2);
|
|
unlink(name_dir_file3);
|
|
rmdir(dir_prefix);
|
|
|
|
TEST_ASSERT_EQUAL(0, mkdir(dir_prefix, 0755));
|
|
test_fatfs_create_file_with_text(name_dir_file1, "一号\n");
|
|
test_fatfs_create_file_with_text(name_dir_file2, "二号\n");
|
|
test_fatfs_create_file_with_text(name_dir_file3, "\0一\0二\0三");
|
|
TEST_ASSERT_EQUAL(0, mkdir(name_dir_inner, 0755));
|
|
test_fatfs_create_file_with_text(name_dir_inner_file, "三号\n");
|
|
|
|
DIR* dir = opendir(dir_prefix);
|
|
TEST_ASSERT_NOT_NULL(dir);
|
|
int count = 0;
|
|
const char* names[4];
|
|
while(count < 4) {
|
|
struct dirent* de = readdir(dir);
|
|
if (!de) {
|
|
break;
|
|
}
|
|
printf("found '%s'\n", de->d_name);
|
|
if (strcasecmp(de->d_name, "文件一.txt") == 0) {
|
|
TEST_ASSERT_TRUE(de->d_type == DT_REG);
|
|
names[count] = "文件一.txt";
|
|
++count;
|
|
} else if (strcasecmp(de->d_name, "文件二.txt") == 0) {
|
|
TEST_ASSERT_TRUE(de->d_type == DT_REG);
|
|
names[count] = "文件二.txt";
|
|
++count;
|
|
} else if (strcasecmp(de->d_name, "内部目录") == 0) {
|
|
TEST_ASSERT_TRUE(de->d_type == DT_DIR);
|
|
names[count] = "内部目录";
|
|
++count;
|
|
} else if (strcasecmp(de->d_name, "文件三.bin") == 0) {
|
|
TEST_ASSERT_TRUE(de->d_type == DT_REG);
|
|
names[count] = "文件三.bin";
|
|
++count;
|
|
} else {
|
|
TEST_FAIL_MESSAGE("unexpected directory entry");
|
|
}
|
|
}
|
|
TEST_ASSERT_EQUAL(count, 4);
|
|
|
|
rewinddir(dir);
|
|
struct dirent* de = readdir(dir);
|
|
TEST_ASSERT_NOT_NULL(de);
|
|
TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[0]));
|
|
seekdir(dir, 3);
|
|
de = readdir(dir);
|
|
TEST_ASSERT_NOT_NULL(de);
|
|
TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[3]));
|
|
seekdir(dir, 1);
|
|
de = readdir(dir);
|
|
TEST_ASSERT_NOT_NULL(de);
|
|
TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[1]));
|
|
seekdir(dir, 2);
|
|
de = readdir(dir);
|
|
TEST_ASSERT_NOT_NULL(de);
|
|
TEST_ASSERT_EQUAL(0, strcasecmp(de->d_name, names[2]));
|
|
|
|
TEST_ASSERT_EQUAL(0, closedir(dir));
|
|
}
|
|
|
|
typedef struct {
|
|
const char* filename;
|
|
bool write;
|
|
size_t word_count;
|
|
int seed;
|
|
SemaphoreHandle_t done;
|
|
int result;
|
|
} read_write_test_arg_t;
|
|
|
|
#define READ_WRITE_TEST_ARG_INIT(name, seed_) \
|
|
{ \
|
|
.filename = name, \
|
|
.seed = seed_, \
|
|
.word_count = 8192, \
|
|
.write = true, \
|
|
.done = xSemaphoreCreateBinary() \
|
|
}
|
|
|
|
static void read_write_task(void* param)
|
|
{
|
|
read_write_test_arg_t* args = (read_write_test_arg_t*) param;
|
|
FILE* f = fopen(args->filename, args->write ? "wb" : "rb");
|
|
if (f == NULL) {
|
|
args->result = ESP_ERR_NOT_FOUND;
|
|
goto done;
|
|
}
|
|
|
|
srand(args->seed);
|
|
for (size_t i = 0; i < args->word_count; ++i) {
|
|
uint32_t val = rand();
|
|
if (args->write) {
|
|
int cnt = fwrite(&val, sizeof(val), 1, f);
|
|
if (cnt != 1) {
|
|
esp_rom_printf("E(w): i=%d, cnt=%d val=%d\n\n", i, cnt, val);
|
|
args->result = ESP_FAIL;
|
|
goto close;
|
|
}
|
|
} else {
|
|
uint32_t rval;
|
|
int cnt = fread(&rval, sizeof(rval), 1, f);
|
|
if (cnt != 1 || rval != val) {
|
|
esp_rom_printf("E(r): i=%d, cnt=%d rval=%d val=%d\n\n", i, cnt, rval, val);
|
|
args->result = ESP_FAIL;
|
|
goto close;
|
|
}
|
|
}
|
|
}
|
|
args->result = ESP_OK;
|
|
|
|
close:
|
|
fclose(f);
|
|
|
|
done:
|
|
xSemaphoreGive(args->done);
|
|
vTaskDelay(1);
|
|
vTaskDelete(NULL);
|
|
}
|
|
|
|
void test_fatfs_concurrent(const char* filename_prefix)
|
|
{
|
|
char names[4][64];
|
|
for (size_t i = 0; i < 4; ++i) {
|
|
snprintf(names[i], sizeof(names[i]), "%s%d", filename_prefix, i + 1);
|
|
unlink(names[i]);
|
|
}
|
|
|
|
read_write_test_arg_t args1 = READ_WRITE_TEST_ARG_INIT(names[0], 1);
|
|
read_write_test_arg_t args2 = READ_WRITE_TEST_ARG_INIT(names[1], 2);
|
|
|
|
printf("writing f1 and f2\n");
|
|
|
|
const int cpuid_0 = 0;
|
|
const int cpuid_1 = portNUM_PROCESSORS - 1;
|
|
const int stack_size = 4096;
|
|
xTaskCreatePinnedToCore(&read_write_task, "rw1", stack_size, &args1, 3, NULL, cpuid_0);
|
|
xTaskCreatePinnedToCore(&read_write_task, "rw2", stack_size, &args2, 3, NULL, cpuid_1);
|
|
|
|
xSemaphoreTake(args1.done, portMAX_DELAY);
|
|
printf("f1 done\n");
|
|
TEST_ASSERT_EQUAL(ESP_OK, args1.result);
|
|
xSemaphoreTake(args2.done, portMAX_DELAY);
|
|
printf("f2 done\n");
|
|
TEST_ASSERT_EQUAL(ESP_OK, args2.result);
|
|
|
|
args1.write = false;
|
|
args2.write = false;
|
|
read_write_test_arg_t args3 = READ_WRITE_TEST_ARG_INIT(names[2], 3);
|
|
read_write_test_arg_t args4 = READ_WRITE_TEST_ARG_INIT(names[3], 4);
|
|
|
|
printf("reading f1 and f2, writing f3 and f4\n");
|
|
|
|
xTaskCreatePinnedToCore(&read_write_task, "rw3", stack_size, &args3, 3, NULL, cpuid_1);
|
|
xTaskCreatePinnedToCore(&read_write_task, "rw4", stack_size, &args4, 3, NULL, cpuid_0);
|
|
xTaskCreatePinnedToCore(&read_write_task, "rw1", stack_size, &args1, 3, NULL, cpuid_0);
|
|
xTaskCreatePinnedToCore(&read_write_task, "rw2", stack_size, &args2, 3, NULL, cpuid_1);
|
|
|
|
xSemaphoreTake(args1.done, portMAX_DELAY);
|
|
printf("f1 done\n");
|
|
TEST_ASSERT_EQUAL(ESP_OK, args1.result);
|
|
xSemaphoreTake(args2.done, portMAX_DELAY);
|
|
printf("f2 done\n");
|
|
TEST_ASSERT_EQUAL(ESP_OK, args2.result);
|
|
xSemaphoreTake(args3.done, portMAX_DELAY);
|
|
printf("f3 done\n");
|
|
TEST_ASSERT_EQUAL(ESP_OK, args3.result);
|
|
xSemaphoreTake(args4.done, portMAX_DELAY);
|
|
printf("f4 done\n");
|
|
TEST_ASSERT_EQUAL(ESP_OK, args4.result);
|
|
|
|
vSemaphoreDelete(args1.done);
|
|
vSemaphoreDelete(args2.done);
|
|
vSemaphoreDelete(args3.done);
|
|
vSemaphoreDelete(args4.done);
|
|
}
|
|
|
|
void test_fatfs_rw_speed(const char* filename, void* buf, size_t buf_size, size_t file_size, bool is_write)
|
|
{
|
|
const size_t buf_count = file_size / buf_size;
|
|
|
|
FILE* f = fopen(filename, (is_write) ? "wb" : "rb");
|
|
TEST_ASSERT_NOT_NULL(f);
|
|
|
|
struct timeval tv_start;
|
|
gettimeofday(&tv_start, NULL);
|
|
for (size_t n = 0; n < buf_count; ++n) {
|
|
if (is_write) {
|
|
TEST_ASSERT_EQUAL(buf_size, write(fileno(f), buf, buf_size));
|
|
} else {
|
|
if (read(fileno(f), buf, buf_size) != buf_size) {
|
|
printf("reading at n=%d, eof=%d", n, feof(f));
|
|
TEST_FAIL();
|
|
}
|
|
}
|
|
}
|
|
|
|
struct timeval tv_end;
|
|
gettimeofday(&tv_end, NULL);
|
|
|
|
TEST_ASSERT_EQUAL(0, fclose(f));
|
|
|
|
float t_s = tv_end.tv_sec - tv_start.tv_sec + 1e-6f * (tv_end.tv_usec - tv_start.tv_usec);
|
|
printf("%s %d bytes (block size %d) in %.3fms (%.3f MB/s)\n",
|
|
(is_write)?"Wrote":"Read", file_size, buf_size, t_s * 1e3,
|
|
file_size / (1024.0f * 1024.0f * t_s));
|
|
}
|