Skip to content

moduos: Add os.utime() support to vfs layer and LFS2, FAT and posix filesystems. #9644

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/library/os.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ Filesystem access

Get the status of a file or directory.

.. function:: utime(path, times=None)

Set the access and modification times of a file or directory, where ``times``
is:

* ``(atime, mtime)``: a tuple of timestamps as integers; or
* ``None``: atime and mtime will be set to the current system time.

.. function:: statvfs(path)

Get the status of a fileystem.
Expand Down
1 change: 1 addition & 0 deletions extmod/moduos.c
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ STATIC const mp_rom_map_elem_t os_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&mp_vfs_stat_obj) },
{ MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&mp_vfs_statvfs_obj) },
{ MP_ROM_QSTR(MP_QSTR_unlink), MP_ROM_PTR(&mp_vfs_remove_obj) }, // unlink aliases to remove
{ MP_ROM_QSTR(MP_QSTR_utime), MP_ROM_PTR(&mp_vfs_utime_obj) },
#endif

// The following are MicroPython extensions.
Expand Down
18 changes: 18 additions & 0 deletions extmod/vfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "py/runtime.h"
#include "py/objstr.h"
#include "py/mperrno.h"
#include "py/mphal.h"
#include "extmod/vfs.h"

#if MICROPY_VFS
Expand Down Expand Up @@ -527,6 +528,23 @@ mp_obj_t mp_vfs_statvfs(mp_obj_t path_in) {
}
MP_DEFINE_CONST_FUN_OBJ_1(mp_vfs_statvfs_obj, mp_vfs_statvfs);

mp_obj_t mp_vfs_utime(size_t n_args, const mp_obj_t *args) {
mp_obj_t path_in = args[0];
mp_obj_t times_in = (n_args > 1) ? args[1] : mp_const_none;
// The qemu-arm port does not define mp_hal_time_ns().
#if !defined(__ARM_ARCH_ISA_ARM) && !defined(__ARM_ARCH_ISA_THUMB)
if (times_in == mp_const_none) {
// If times is not supplied or set to None, use current time
mp_obj_t t = mp_obj_new_int_from_ull(mp_hal_time_ns() / 1000000000ULL);
times_in = mp_obj_new_tuple(2, (mp_obj_t[2]) {t, t});
}
#endif
mp_obj_t args_out[2] = {MP_OBJ_NULL, times_in};
mp_vfs_mount_t *vfs = lookup_path(path_in, &args_out[0]);
return mp_vfs_proxy_call(vfs, MP_QSTR_utime, 2, args_out);
}
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_vfs_utime_obj, 1, 2, mp_vfs_utime);

// This is a C-level helper function for ports to use if needed.
int mp_vfs_mount_and_chdir_protected(mp_obj_t bdev, mp_obj_t mount_point) {
nlr_buf_t nlr;
Expand Down
2 changes: 2 additions & 0 deletions extmod/vfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ mp_obj_t mp_vfs_rename(mp_obj_t old_path_in, mp_obj_t new_path_in);
mp_obj_t mp_vfs_rmdir(mp_obj_t path_in);
mp_obj_t mp_vfs_stat(mp_obj_t path_in);
mp_obj_t mp_vfs_statvfs(mp_obj_t path_in);
mp_obj_t mp_vfs_utime(size_t n_args, const mp_obj_t *args);

int mp_vfs_mount_and_chdir_protected(mp_obj_t bdev, mp_obj_t mount_point);

Expand All @@ -117,5 +118,6 @@ MP_DECLARE_CONST_FUN_OBJ_2(mp_vfs_rename_obj);
MP_DECLARE_CONST_FUN_OBJ_1(mp_vfs_rmdir_obj);
MP_DECLARE_CONST_FUN_OBJ_1(mp_vfs_stat_obj);
MP_DECLARE_CONST_FUN_OBJ_1(mp_vfs_statvfs_obj);
MP_DECLARE_CONST_FUN_OBJ_VAR_BETWEEN(mp_vfs_utime_obj);

#endif // MICROPY_INCLUDED_EXTMOD_VFS_H
35 changes: 35 additions & 0 deletions extmod/vfs_fat.c
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,40 @@ STATIC mp_obj_t fat_vfs_statvfs(mp_obj_t vfs_in, mp_obj_t path_in) {
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(fat_vfs_statvfs_obj, fat_vfs_statvfs);

// Get the status of a file or directory.
STATIC mp_obj_t fat_vfs_utime(mp_obj_t vfs_in, mp_obj_t path_in, mp_obj_t times_in) {
mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(vfs_in);
const char *path = mp_obj_str_get_str(path_in);

if (path[0] == 0 || (path[0] == '/' && path[1] == 0)) {
mp_raise_OSError(MP_EPERM);
}
// times_in = (atime, mtime). Ignore atime and just set mtime
mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(times_in);
if (!mp_obj_is_type(times_in, &mp_type_tuple) || tuple->len != 2) {
mp_raise_OSError(MP_EINVAL);
}
mp_uint_t mtime = mp_obj_get_int(tuple->items[1]);
timeutils_struct_time_t tm;
timeutils_seconds_since_epoch_to_struct_time(mtime, &tm);

FILINFO fno;
fno.fdate = (
((uint16_t)(MAX(tm.tm_year, 1980) - 1980) & 0x7f) << 9 |
((uint16_t)tm.tm_mon & 0x0f) << 5 |
((uint16_t)tm.tm_mday & 0x1f));
fno.ftime = (
((uint16_t)tm.tm_hour & 0x1f) << 11 |
((uint16_t)tm.tm_min & 0x3f) << 5 |
((uint16_t)(tm.tm_sec / 2) & 0x1f));
FRESULT res = f_utime(&self->fatfs, path, &fno);
if (res != FR_OK) {
mp_raise_OSError(fresult_to_errno_table[res]);
}
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_3(fat_vfs_utime_obj, fat_vfs_utime);

STATIC mp_obj_t vfs_fat_mount(mp_obj_t self_in, mp_obj_t readonly, mp_obj_t mkfs) {
fs_user_mount_t *self = MP_OBJ_TO_PTR(self_in);

Expand Down Expand Up @@ -422,6 +456,7 @@ STATIC const mp_rom_map_elem_t fat_vfs_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_rename), MP_ROM_PTR(&fat_vfs_rename_obj) },
{ MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&fat_vfs_stat_obj) },
{ MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&fat_vfs_statvfs_obj) },
{ MP_ROM_QSTR(MP_QSTR_utime), MP_ROM_PTR(&fat_vfs_utime_obj) },
{ MP_ROM_QSTR(MP_QSTR_mount), MP_ROM_PTR(&vfs_fat_mount_obj) },
{ MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&fat_vfs_umount_obj) },
};
Expand Down
34 changes: 34 additions & 0 deletions extmod/vfs_lfsx.c
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,39 @@ STATIC mp_obj_t MP_VFS_LFSx(statvfs)(mp_obj_t self_in, mp_obj_t path_in) {
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(MP_VFS_LFSx(statvfs_obj), MP_VFS_LFSx(statvfs));

STATIC mp_obj_t MP_VFS_LFSx(utime)(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t times_in) {
#if LFS_BUILD_VERSION == 2
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
const char *path = MP_VFS_LFSx(make_path)(self, path_in);

if (self->enable_mtime) {
mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(times_in);
if (!mp_obj_is_type(times_in, &mp_type_tuple) || tuple->len != 2) {
mp_raise_OSError(MP_EINVAL);
}
// times_in = (atime, mtime). Ignore atime and just set mtime
uint64_t mtime = ((uint64_t)mp_obj_get_int(tuple->items[1])) * 1000000000ULL;
uint64_t ns = timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970(mtime);
// Store "ns" to "buf" in little-endian format (essentially htole64).
uint8_t mtime_buf[8];
for (size_t i = 0; i < 8; ++i) {
mtime_buf[i] = ns;
ns >>= 8;
}
int ret = lfs2_setattr(&self->lfs, path, LFS_ATTR_MTIME, &mtime_buf, sizeof(mtime_buf));
if (ret < 0) {
mp_raise_OSError(-ret);
}
} else {
mp_raise_OSError(MP_EPERM);
}
#else
mp_raise_OSError(MP_EPERM);
#endif
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_3(MP_VFS_LFSx(utime_obj), MP_VFS_LFSx(utime));

STATIC mp_obj_t MP_VFS_LFSx(mount)(mp_obj_t self_in, mp_obj_t readonly, mp_obj_t mkfs) {
MP_OBJ_VFS_LFSx *self = MP_OBJ_TO_PTR(self_in);
(void)mkfs;
Expand Down Expand Up @@ -480,6 +513,7 @@ STATIC const mp_rom_map_elem_t MP_VFS_LFSx(locals_dict_table)[] = {
{ MP_ROM_QSTR(MP_QSTR_rename), MP_ROM_PTR(&MP_VFS_LFSx(rename_obj)) },
{ MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&MP_VFS_LFSx(stat_obj)) },
{ MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&MP_VFS_LFSx(statvfs_obj)) },
{ MP_ROM_QSTR(MP_QSTR_utime), MP_ROM_PTR(&MP_VFS_LFSx(utime_obj)) },
{ MP_ROM_QSTR(MP_QSTR_mount), MP_ROM_PTR(&MP_VFS_LFSx(mount_obj)) },
{ MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&MP_VFS_LFSx(umount_obj)) },
};
Expand Down
22 changes: 22 additions & 0 deletions extmod/vfs_posix.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <utime.h>
#include <sys/time.h>
#include <dirent.h>
#ifdef _MSC_VER
#include <direct.h> // For mkdir etc.
Expand Down Expand Up @@ -332,6 +334,25 @@ STATIC mp_obj_t vfs_posix_stat(mp_obj_t self_in, mp_obj_t path_in) {
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(vfs_posix_stat_obj, vfs_posix_stat);

STATIC mp_obj_t vfs_posix_utime(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t times_in) {
mp_obj_vfs_posix_t *self = MP_OBJ_TO_PTR(self_in);
const char *path = vfs_posix_get_path_str(self, path_in);
// times_in = (atime, mtime).
mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR(times_in);
if (!mp_obj_is_type(times_in, &mp_type_tuple) || tuple->len != 2) {
mp_raise_OSError(MP_EINVAL);
}
// utime() is obsoleted for utimes() in posix.1-2008, but not supported on windows port.
struct utimbuf times = {
mp_obj_get_int(tuple->items[0]),
mp_obj_get_int(tuple->items[1])
};
int ret;
MP_HAL_RETRY_SYSCALL(ret, utime(path, &times), mp_raise_OSError(err));
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_3(vfs_posix_utime_obj, vfs_posix_utime);

#if MICROPY_PY_UOS_STATVFS

#ifdef __ANDROID__
Expand Down Expand Up @@ -390,6 +411,7 @@ STATIC const mp_rom_map_elem_t vfs_posix_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_rename), MP_ROM_PTR(&vfs_posix_rename_obj) },
{ MP_ROM_QSTR(MP_QSTR_rmdir), MP_ROM_PTR(&vfs_posix_rmdir_obj) },
{ MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&vfs_posix_stat_obj) },
{ MP_ROM_QSTR(MP_QSTR_utime), MP_ROM_PTR(&vfs_posix_utime_obj) },
#if MICROPY_PY_UOS_STATVFS
{ MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&vfs_posix_statvfs_obj) },
#endif
Expand Down
5 changes: 5 additions & 0 deletions ports/webassembly/mphalport.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ mp_uint_t mp_hal_ticks_cpu(void) {
return 0;
}

uint64_t mp_hal_time_ns(void) {
// Not currently implemented.
return 0;
}

extern int mp_interrupt_char;

int mp_hal_get_interrupt_char(void) {
Expand Down
4 changes: 4 additions & 0 deletions tests/extmod/vfs_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ def stat(self, path):
print(self.id, "stat", path)
return (self.id,)

def utime(self, path, times):
print(self.id, "utime", path, times)

def statvfs(self, path):
print(self.id, "statvfs", path)
return (self.id,)
Expand Down Expand Up @@ -134,6 +137,7 @@ def open(self, file, mode):
uos.rename("test_file", "test_file2")
uos.rmdir("test_dir")
print(uos.stat("test_file"))
uos.utime("test_file", (365 * 24 * 60 * 60, 2 * 365 * 24 * 60 * 60))
print(uos.statvfs("/test_mnt"))
open("test_file")
open("test_file", "wb")
Expand Down
1 change: 1 addition & 0 deletions tests/extmod/vfs_basic.py.exp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ OSError
1 rmdir test_dir
1 stat test_file
(1,)
1 utime test_file (31536000, 63072000)
1 statvfs /
(1,)
1 open test_file r
Expand Down
79 changes: 79 additions & 0 deletions tests/extmod/vfs_fat_utime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Test for VfsFat using a RAM device, mtime feature

try:
import utime, uos

utime.time
utime.sleep
uos.VfsFat
except (ImportError, AttributeError):
print("SKIP")
raise SystemExit


class RAMBlockDevice:
ERASE_BLOCK_SIZE = 512

def __init__(self, blocks):
self.data = bytearray(blocks * self.ERASE_BLOCK_SIZE)

def readblocks(self, block, buf):
addr = block * self.ERASE_BLOCK_SIZE
for i in range(len(buf)):
buf[i] = self.data[addr + i]

def writeblocks(self, block, buf):
addr = block * self.ERASE_BLOCK_SIZE
for i in range(len(buf)):
self.data[addr + i] = buf[i]

def ioctl(self, op, arg):
if op == 4: # block count
return len(self.data) // self.ERASE_BLOCK_SIZE
if op == 5: # block size
return self.ERASE_BLOCK_SIZE


def test(bdev, vfs_class):
print("test", vfs_class)

# Initial format of block device.
vfs_class.mkfs(bdev)

# construction
vfs = vfs_class(bdev)

# Create an empty file, should have a timestamp.
vfs.open("test1", "wt").close()

# Create an empty file, should have a timestamp.
vfs.open("test1", "wt").close()

# Stat the file before and after setting mtime with os.utime().
stat1 = vfs.stat("test1")
tm = utime.mktime((2010, 1, 1, 11, 49, 1, 0, 0))
vfs.utime("test1", (tm, tm))
stat2 = vfs.stat("test1")
# Note FAT time granularity only valid to two seconds
print(stat1[8] != 0, stat2[8] != stat1[8], abs(stat2[8] - tm) < 2)

# Check for invalid arguments: should all raise OSError.
try:
vfs.utime("test1", None)
except OSError:
print("OSError")
try:
vfs.utime("test1", (None, None))
except TypeError:
print("TypeError")
try:
vfs.utime("test1", (None, "Invalid"))
except TypeError:
print("TypeError")

# Unmount.
vfs.umount()


bdev = RAMBlockDevice(50)
test(bdev, uos.VfsFat)
5 changes: 5 additions & 0 deletions tests/extmod/vfs_fat_utime.py.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
test <class 'VfsFat'>
True True True
OSError
TypeError
TypeError
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy