diff --git a/docs/library/os.rst b/docs/library/os.rst index 19652ee2bc5ef..1b50ac54f4d39 100644 --- a/docs/library/os.rst +++ b/docs/library/os.rst @@ -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. diff --git a/extmod/moduos.c b/extmod/moduos.c index 87a611148d1cf..2016830fac9e4 100644 --- a/extmod/moduos.c +++ b/extmod/moduos.c @@ -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. diff --git a/extmod/vfs.c b/extmod/vfs.c index af63ceb37eb19..4ae4d99360d23 100644 --- a/extmod/vfs.c +++ b/extmod/vfs.c @@ -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 @@ -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; diff --git a/extmod/vfs.h b/extmod/vfs.h index f577d3e337c87..0f61d17fd6139 100644 --- a/extmod/vfs.h +++ b/extmod/vfs.h @@ -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); @@ -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 diff --git a/extmod/vfs_fat.c b/extmod/vfs_fat.c index efb6bf7e9815d..5c5f3a16586f0 100644 --- a/extmod/vfs_fat.c +++ b/extmod/vfs_fat.c @@ -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); @@ -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) }, }; diff --git a/extmod/vfs_lfsx.c b/extmod/vfs_lfsx.c index de1f421977fd9..92e3d0efb184d 100644 --- a/extmod/vfs_lfsx.c +++ b/extmod/vfs_lfsx.c @@ -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; @@ -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)) }, }; diff --git a/extmod/vfs_posix.c b/extmod/vfs_posix.c index 9b856e1f01cc6..e9acf403ffaa5 100644 --- a/extmod/vfs_posix.c +++ b/extmod/vfs_posix.c @@ -42,6 +42,8 @@ #include #include #include +#include +#include #include #ifdef _MSC_VER #include // For mkdir etc. @@ -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, ×), 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__ @@ -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 diff --git a/ports/webassembly/mphalport.c b/ports/webassembly/mphalport.c index c938a63928824..3d083275cf02e 100644 --- a/ports/webassembly/mphalport.c +++ b/ports/webassembly/mphalport.c @@ -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) { diff --git a/tests/extmod/vfs_basic.py b/tests/extmod/vfs_basic.py index 9a9ef2ca61be3..aebfeea6799e7 100644 --- a/tests/extmod/vfs_basic.py +++ b/tests/extmod/vfs_basic.py @@ -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,) @@ -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") diff --git a/tests/extmod/vfs_basic.py.exp b/tests/extmod/vfs_basic.py.exp index 536bb4c805d30..3acef68704061 100644 --- a/tests/extmod/vfs_basic.py.exp +++ b/tests/extmod/vfs_basic.py.exp @@ -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 diff --git a/tests/extmod/vfs_fat_utime.py b/tests/extmod/vfs_fat_utime.py new file mode 100644 index 0000000000000..4d9cd4e8c6649 --- /dev/null +++ b/tests/extmod/vfs_fat_utime.py @@ -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) diff --git a/tests/extmod/vfs_fat_utime.py.exp b/tests/extmod/vfs_fat_utime.py.exp new file mode 100644 index 0000000000000..f153fd89bb283 --- /dev/null +++ b/tests/extmod/vfs_fat_utime.py.exp @@ -0,0 +1,5 @@ +test +True True True +OSError +TypeError +TypeError diff --git a/tests/extmod/vfs_lfs_utime.py b/tests/extmod/vfs_lfs_utime.py new file mode 100644 index 0000000000000..5ce4800089e39 --- /dev/null +++ b/tests/extmod/vfs_lfs_utime.py @@ -0,0 +1,92 @@ +# Test for VfsLfs using a RAM device, mtime feature + +try: + import utime, uos + + utime.time + utime.sleep + uos.VfsLfs2 +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + + +class RAMBlockDevice: + ERASE_BLOCK_SIZE = 1024 + + def __init__(self, blocks): + self.data = bytearray(blocks * self.ERASE_BLOCK_SIZE) + + def readblocks(self, block, buf, off): + addr = block * self.ERASE_BLOCK_SIZE + off + for i in range(len(buf)): + buf[i] = self.data[addr + i] + + def writeblocks(self, block, buf, off): + addr = block * self.ERASE_BLOCK_SIZE + off + 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 + if op == 6: # erase block + return 0 + + +def test(bdev, vfs_class): + print("test", vfs_class) + + # Initial format of block device. + vfs_class.mkfs(bdev) + + # construction + print("mtime=True") + vfs = vfs_class(bdev, mtime=True) + + # 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") + print(stat1[8] != 0, stat2[8] != stat1[8], stat2[8] == tm) + + # 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() + + # Check that remounting with mtime=False raises errors on utime(). + print("mtime=False") + vfs = vfs_class(bdev, mtime=False) + print(vfs.stat("test1") == stat2) + + # Should fail with OSError(EPERM) + try: + vfs.utime("test1", (2000, 2000)) + except OSError: + print("OSError") + + print(vfs.stat("test1") == stat2) + vfs.umount() + + +bdev = RAMBlockDevice(30) +test(bdev, uos.VfsLfs2) diff --git a/tests/extmod/vfs_lfs_utime.py.exp b/tests/extmod/vfs_lfs_utime.py.exp new file mode 100644 index 0000000000000..fd92e01c4e3a5 --- /dev/null +++ b/tests/extmod/vfs_lfs_utime.py.exp @@ -0,0 +1,10 @@ +test +mtime=True +True True True +OSError +TypeError +TypeError +mtime=False +True +OSError +True diff --git a/tests/extmod/vfs_posix_utime.py b/tests/extmod/vfs_posix_utime.py new file mode 100644 index 0000000000000..7d61c2ed63b22 --- /dev/null +++ b/tests/extmod/vfs_posix_utime.py @@ -0,0 +1,58 @@ +# Test for VfsPosix + +try: + import uos + import utime + + uos.VfsPosix +except (ImportError, AttributeError): + print("SKIP") + raise SystemExit + +# We need a directory for testing that doesn't already exist. +# Skip the test if it does exist. +temp_dir = "micropy_test_dir" +try: + uos.stat(temp_dir) + print("SKIP") + raise SystemExit +except OSError: + pass + +# mkdir +uos.mkdir(temp_dir) + +test1 = temp_dir + "/test1" + +# Create an empty file, should have a timestamp. +open(test1, "wt").close() + +# Stat the file before and after setting mtime with os.utime(). +stat1 = uos.stat(test1) +tm = utime.mktime((2010, 1, 1, 11, 49, 1, 0, 0)) +uos.utime(test1, (tm, tm)) +stat2 = uos.stat(test1) +print(stat1[8] != 0, stat2[8] != stat1[8], stat2[8] == tm) + +# Check that uos.utime(f) and uos.utime(f, None) set mtime to current time +current_time = utime.time() +uos.utime(test1) +stat3 = uos.stat(test1) +print(stat3[8] - current_time < 100) +uos.utime(test1, (0, 0)) +uos.utime(test1, None) +stat4 = uos.stat(test1) +print(stat4[8] - current_time < 100) + +# Check for invalid arguments: should raise TypeError. +try: + uos.utime(test1, (None, None)) +except TypeError: + print("TypeError") +try: + uos.utime(test1, (None, "Invalid")) +except TypeError: + print("TypeError") + +uos.remove(test1) +uos.rmdir(temp_dir) diff --git a/tests/extmod/vfs_posix_utime.py.exp b/tests/extmod/vfs_posix_utime.py.exp new file mode 100644 index 0000000000000..f0bd19034cfa2 --- /dev/null +++ b/tests/extmod/vfs_posix_utime.py.exp @@ -0,0 +1,5 @@ +True True True +True +True +TypeError +TypeError 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