VFS: Implement utime()

This commit is contained in:
Roland Dobai 2018-10-25 11:53:52 +02:00
parent ada3bc3fb5
commit fb334baa7c
13 changed files with 342 additions and 2 deletions

View File

@ -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. */

View File

@ -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(&times->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;
}

View File

@ -19,6 +19,7 @@
#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"
@ -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");

View File

@ -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);

View File

@ -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();

View File

@ -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();

View File

@ -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)

View File

@ -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 <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_ */

21
components/newlib/utime.c Normal file
View File

@ -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 <utime.h>
#include "esp_vfs.h"
int utime(const char *path, const struct utimbuf *times)
{
return esp_vfs_utime(path, times);
}

View File

@ -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

View File

@ -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

View File

@ -19,6 +19,7 @@
#include <stddef.h>
#include <stdarg.h>
#include <unistd.h>
#include <utime.h>
#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);
/**@}*/
/**

View File

@ -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;
}