/* 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. */
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()
.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);
_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;
+}
#include <sys/time.h>
#include <sys/unistd.h>
#include <errno.h>
+#include <utime.h>
#include "unity.h"
#include "esp_log.h"
#include "esp_system.h"
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");
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);
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();
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();
"syscall_table.c"
"syscalls.c"
"termios.c"
+ "utime.c"
"time.c")
set(COMPONENT_ADD_INCLUDEDIRS platform_include include)
--- /dev/null
+// 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 <sys/time.h>
+
+#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_ */
--- /dev/null
+// 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 <utime.h>
+#include "esp_vfs.h"
+
+int utime(const char *path, const struct utimbuf *times)
+{
+ return esp_vfs_utime(path, times);
+}
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];
.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);
#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
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
#include <stddef.h>
#include <stdarg.h>
#include <unistd.h>
+#include <utime.h>
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
#include "esp_err.h"
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);
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);
/**@}*/
/**
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;
+}