diff --git a/components/fatfs/src/ffconf.h b/components/fatfs/src/ffconf.h index 1b1cf8c85c..bf0e1a8e94 100644 --- a/components/fatfs/src/ffconf.h +++ b/components/fatfs/src/ffconf.h @@ -52,7 +52,7 @@ /* This option switches f_expand function. (0:Disable or 1:Enable) */ -#define FF_USE_CHMOD 0 +#define FF_USE_CHMOD 1 /* This option switches attribute manipulation functions, f_chmod() and f_utime(). / (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */ diff --git a/components/fatfs/src/vfs_fat.c b/components/fatfs/src/vfs_fat.c index 6d7019a1e2..9f139f6eb4 100644 --- a/components/fatfs/src/vfs_fat.c +++ b/components/fatfs/src/vfs_fat.c @@ -86,6 +86,7 @@ static int vfs_fat_mkdir(void* ctx, const char* name, mode_t mode); static int vfs_fat_rmdir(void* ctx, const char* name); static int vfs_fat_access(void* ctx, const char *path, int amode); static int vfs_fat_truncate(void* ctx, const char *path, off_t length); +static int vfs_fat_utime(void* ctx, const char *path, const struct utimbuf *times); static vfs_fat_ctx_t* s_fat_ctxs[FF_VOLUMES] = { NULL, NULL }; //backwards-compatibility with esp_vfs_fat_unregister() @@ -146,6 +147,7 @@ esp_err_t esp_vfs_fat_register(const char* base_path, const char* fat_drive, siz .rmdir_p = &vfs_fat_rmdir, .access_p = &vfs_fat_access, .truncate_p = &vfs_fat_truncate, + .utime_p = &vfs_fat_utime, }; size_t ctx_size = sizeof(vfs_fat_ctx_t) + max_files * sizeof(FIL); vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) calloc(1, ctx_size); @@ -824,3 +826,55 @@ out: _lock_release(&fat_ctx->lock); return ret; } + +static int vfs_fat_utime(void *ctx, const char *path, const struct utimbuf *times) +{ + FILINFO filinfo_time; + + { + struct tm tm_time; + + if (times) { + localtime_r(×->modtime, &tm_time); + } else { + // use current time + struct timeval tv; + gettimeofday(&tv, NULL); + localtime_r(&tv.tv_sec, &tm_time); + } + + if (tm_time.tm_year < 80) { + // FATFS cannot handle years before 1980 + errno = EINVAL; + return -1; + } + + fat_date_t fdate; + fat_time_t ftime; + + // this time transformation is esentially the reverse of the one in vfs_fat_stat() + fdate.mday = tm_time.tm_mday; + fdate.mon = tm_time.tm_mon + 1; // January in fdate.mon is 1, and 0 in tm_time.tm_mon + fdate.year = tm_time.tm_year - 80; // tm_time.tm_year=0 is 1900, tm_time.tm_year=0 is 1980 + ftime.sec = tm_time.tm_sec / 2, // ftime.sec counts seconds by 2 + ftime.min = tm_time.tm_min; + ftime.hour = tm_time.tm_hour; + + filinfo_time.fdate = fdate.as_int; + filinfo_time.ftime = ftime.as_int; + } + + vfs_fat_ctx_t *fat_ctx = (vfs_fat_ctx_t *) ctx; + _lock_acquire(&fat_ctx->lock); + prepend_drive_to_path(fat_ctx, &path, NULL); + FRESULT res = f_utime(path, &filinfo_time); + _lock_release(&fat_ctx->lock); + + if (res != FR_OK) { + ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); + errno = fresult_to_errno(res); + return -1; + } + + return 0; +} diff --git a/components/fatfs/test/test_fatfs_common.c b/components/fatfs/test/test_fatfs_common.c index 0e84794157..4eeb4aad40 100644 --- a/components/fatfs/test/test_fatfs_common.c +++ b/components/fatfs/test/test_fatfs_common.c @@ -19,6 +19,7 @@ #include #include #include +#include #include "unity.h" #include "esp_log.h" #include "esp_system.h" @@ -246,6 +247,81 @@ void test_fatfs_stat(const char* filename, const char* root_dir) TEST_ASSERT_FALSE(st.st_mode & S_IFREG); } +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"); diff --git a/components/fatfs/test/test_fatfs_common.h b/components/fatfs/test/test_fatfs_common.h index e5511ec434..ba330b6b6b 100644 --- a/components/fatfs/test/test_fatfs_common.h +++ b/components/fatfs/test/test_fatfs_common.h @@ -49,6 +49,8 @@ void test_fatfs_truncate_file(const char* path); void test_fatfs_stat(const char* filename, const char* root_dir); +void test_fatfs_utime(const char* filename, const char* root_dir); + void test_fatfs_unlink(const char* filename); void test_fatfs_link_rename(const char* filename_prefix); diff --git a/components/fatfs/test/test_fatfs_sdmmc.c b/components/fatfs/test/test_fatfs_sdmmc.c index 74ef2207b7..e1a6cfb3bd 100644 --- a/components/fatfs/test/test_fatfs_sdmmc.c +++ b/components/fatfs/test/test_fatfs_sdmmc.c @@ -116,6 +116,13 @@ TEST_CASE("(SD) stat returns correct values", "[fatfs][test_env=UT_T1_SDMODE]") test_teardown(); } +TEST_CASE("(SD) utime sets modification time", "[fatfs][test_env=UT_T1_SDMODE]") +{ + test_setup(); + test_fatfs_utime("/sdcard/utime.txt", "/sdcard"); + test_teardown(); +} + TEST_CASE("(SD) unlink removes a file", "[fatfs][test_env=UT_T1_SDMODE]") { test_setup(); diff --git a/components/fatfs/test/test_fatfs_spiflash.c b/components/fatfs/test/test_fatfs_spiflash.c index 9d07249480..6c33559395 100644 --- a/components/fatfs/test/test_fatfs_spiflash.c +++ b/components/fatfs/test/test_fatfs_spiflash.c @@ -110,6 +110,13 @@ TEST_CASE("(WL) stat returns correct values", "[fatfs][wear_levelling]") test_teardown(); } +TEST_CASE("(WL) utime sets modification time", "[fatfs][wear_levelling]") +{ + test_setup(); + test_fatfs_utime("/spiflash/utime.txt", "/spiflash"); + test_teardown(); +} + TEST_CASE("(WL) unlink removes a file", "[fatfs][wear_levelling]") { test_setup(); diff --git a/components/newlib/CMakeLists.txt b/components/newlib/CMakeLists.txt index a58cddeb71..a7b29c6548 100644 --- a/components/newlib/CMakeLists.txt +++ b/components/newlib/CMakeLists.txt @@ -6,6 +6,7 @@ set(COMPONENT_SRCS "locks.c" "syscall_table.c" "syscalls.c" "termios.c" + "utime.c" "time.c") set(COMPONENT_ADD_INCLUDEDIRS platform_include include) diff --git a/components/newlib/platform_include/sys/utime.h b/components/newlib/platform_include/sys/utime.h new file mode 100644 index 0000000000..3251d3ce43 --- /dev/null +++ b/components/newlib/platform_include/sys/utime.h @@ -0,0 +1,35 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// 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. + +#ifndef _UTIME_H_ +#define _UTIME_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct utimbuf { + time_t actime; // access time + time_t modtime; // modification time +}; + +int utime(const char *path, const struct utimbuf *times); + +#ifdef __cplusplus +}; +#endif + +#endif /* _UTIME_H_ */ diff --git a/components/newlib/utime.c b/components/newlib/utime.c new file mode 100644 index 0000000000..c838fd45ab --- /dev/null +++ b/components/newlib/utime.c @@ -0,0 +1,21 @@ +// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// +// 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 "esp_vfs.h" + +int utime(const char *path, const struct utimbuf *times) +{ + return esp_vfs_utime(path, times); +} diff --git a/components/spiffs/esp_spiffs.c b/components/spiffs/esp_spiffs.c index 853986e499..06cfe44c4c 100644 --- a/components/spiffs/esp_spiffs.c +++ b/components/spiffs/esp_spiffs.c @@ -71,6 +71,7 @@ static int vfs_spiffs_mkdir(void* ctx, const char* name, mode_t mode); static int vfs_spiffs_rmdir(void* ctx, const char* name); static void vfs_spiffs_update_mtime(spiffs *fs, spiffs_file f); static time_t vfs_spiffs_get_mtime(const spiffs_stat* s); +static int vfs_spiffs_utime(void *ctx, const char *path, const struct utimbuf *times); static esp_spiffs_t * _efs[CONFIG_SPIFFS_MAX_PARTITIONS]; @@ -347,7 +348,12 @@ esp_err_t esp_vfs_spiffs_register(const esp_vfs_spiffs_conf_t * conf) .seekdir_p = &vfs_spiffs_seekdir, .telldir_p = &vfs_spiffs_telldir, .mkdir_p = &vfs_spiffs_mkdir, - .rmdir_p = &vfs_spiffs_rmdir + .rmdir_p = &vfs_spiffs_rmdir, +#ifdef CONFIG_SPIFFS_USE_MTIME + .utime_p = &vfs_spiffs_utime, +#else + .utime_p = NULL, +#endif // CONFIG_SPIFFS_USE_MTIME }; esp_err_t err = esp_spiffs_init(conf); @@ -744,3 +750,49 @@ static time_t vfs_spiffs_get_mtime(const spiffs_stat* s) #endif return t; } + +#ifdef CONFIG_SPIFFS_USE_MTIME +static int vfs_spiffs_update_mtime_value(spiffs *fs, const char *path, time_t t) +{ + int ret = SPIFFS_OK; + spiffs_stat s; + if (CONFIG_SPIFFS_META_LENGTH > sizeof(t)) { + ret = SPIFFS_stat(fs, path, &s); + } + if (ret == SPIFFS_OK) { + memcpy(s.meta, &t, sizeof(t)); + ret = SPIFFS_update_meta(fs, path, s.meta); + } + if (ret != SPIFFS_OK) { + ESP_LOGW(TAG, "Failed to update mtime (%d)", ret); + } + return ret; +} +#endif //CONFIG_SPIFFS_USE_MTIME + +#ifdef CONFIG_SPIFFS_USE_MTIME +static int vfs_spiffs_utime(void *ctx, const char *path, const struct utimbuf *times) +{ + assert(path); + + esp_spiffs_t *efs = (esp_spiffs_t *) ctx; + time_t t; + + if (times) { + t = times->modtime; + } else { + // use current time + t = time(NULL); + } + + int ret = vfs_spiffs_update_mtime_value(efs->fs, path, t); + + if (ret != SPIFFS_OK) { + errno = spiffs_res_to_errno(SPIFFS_errno(efs->fs)); + SPIFFS_clearerr(efs->fs); + return -1; + } + + return 0; +} +#endif //CONFIG_SPIFFS_USE_MTIME diff --git a/components/spiffs/test/test_spiffs.c b/components/spiffs/test/test_spiffs.c index cb2164ac1f..0a70cbba7c 100644 --- a/components/spiffs/test/test_spiffs.c +++ b/components/spiffs/test/test_spiffs.c @@ -649,4 +649,69 @@ TEST_CASE("mtime is updated when file is opened", "[spiffs]") test_teardown(); } + +TEST_CASE("utime() works well", "[spiffs]") +{ + const char filename[] = "/spiffs/utime.txt"; + 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)); + + test_setup(); + { + // 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_spiffs_create_file_with_text(filename, ""); + + // 00:00:00. January 1st, 1900 + desired_tm.tm_mon = 1 - 1; + desired_tm.tm_mday = 1; + desired_tm.tm_year = 0; + 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); + + // 23:59:08. December 31st, 2145 + desired_tm.tm_mon = 12 - 1; + desired_tm.tm_mday = 31; + desired_tm.tm_year = 2145 - 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); + + // 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 + + test_teardown(); +} #endif // CONFIG_SPIFFS_USE_MTIME diff --git a/components/vfs/include/esp_vfs.h b/components/vfs/include/esp_vfs.h index d7467d227f..e54a3e9835 100644 --- a/components/vfs/include/esp_vfs.h +++ b/components/vfs/include/esp_vfs.h @@ -19,6 +19,7 @@ #include #include #include +#include #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "esp_err.h" @@ -180,6 +181,10 @@ typedef struct int (*truncate_p)(void* ctx, const char *path, off_t length); int (*truncate)(const char *path, off_t length); }; + union { + int (*utime_p)(void* ctx, const char *path, const struct utimbuf *times); + int (*utime)(const char *path, const struct utimbuf *times); + }; #ifdef CONFIG_SUPPORT_TERMIOS union { int (*tcsetattr_p)(void *ctx, int fd, int optional_actions, const struct termios *p); @@ -330,6 +335,7 @@ int esp_vfs_stat(struct _reent *r, const char * path, struct stat * st); int esp_vfs_link(struct _reent *r, const char* n1, const char* n2); int esp_vfs_unlink(struct _reent *r, const char *path); int esp_vfs_rename(struct _reent *r, const char *src, const char *dst); +int esp_vfs_utime(const char *path, const struct utimbuf *times); /**@}*/ /** diff --git a/components/vfs/vfs.c b/components/vfs/vfs.c index e8b99eff2c..65718ca90f 100644 --- a/components/vfs/vfs.c +++ b/components/vfs/vfs.c @@ -1094,3 +1094,17 @@ int tcsendbreak(int fd, int duration) return ret; } #endif // CONFIG_SUPPORT_TERMIOS + +int esp_vfs_utime(const char *path, const struct utimbuf *times) +{ + int ret; + const vfs_entry_t* vfs = get_vfs_for_path(path); + struct _reent* r = __getreent(); + if (vfs == NULL) { + __errno_r(r) = ENOENT; + return -1; + } + const char* path_within_vfs = translate_path(vfs, path); + CHECK_AND_CALL(ret, r, vfs, utime, path_within_vfs, times); + return ret; +}