2020-06-03 18:40:05 -04:00
|
|
|
// Copyright (c) 2016 Paul Sokolovsky
|
|
|
|
// SPDX-FileCopyrightText: 2014 MicroPython & CircuitPython contributors (https://github.com/adafruit/circuitpython/graphs/contributors)
|
|
|
|
// SPDX-FileCopyrightText: Copyright (c) 2014 Damien P. George
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: MIT
|
2016-02-13 15:51:21 -05:00
|
|
|
|
|
|
|
#include "py/mpconfig.h"
|
|
|
|
#if MICROPY_VFS_FAT
|
|
|
|
|
2017-01-26 23:13:32 -05:00
|
|
|
#if !MICROPY_VFS
|
|
|
|
#error "with MICROPY_VFS_FAT enabled, must also enable MICROPY_VFS"
|
|
|
|
#endif
|
|
|
|
|
2016-05-29 11:23:59 -04:00
|
|
|
#include <string.h>
|
2018-03-27 22:28:18 -04:00
|
|
|
#include "py/objproperty.h"
|
2016-02-13 15:51:21 -05:00
|
|
|
#include "py/runtime.h"
|
2016-09-28 09:51:35 -04:00
|
|
|
#include "py/mperrno.h"
|
2016-06-01 12:00:28 -04:00
|
|
|
#include "lib/oofatfs/ff.h"
|
2017-01-26 23:04:17 -05:00
|
|
|
#include "extmod/vfs_fat.h"
|
2017-01-30 06:28:37 -05:00
|
|
|
#include "lib/timeutils/timeutils.h"
|
2019-03-13 18:17:07 -04:00
|
|
|
#include "supervisor/filesystem.h"
|
2018-08-08 21:24:49 -04:00
|
|
|
#include "supervisor/shared/translate.h"
|
|
|
|
|
2021-04-22 20:55:39 -04:00
|
|
|
#if FF_MAX_SS == FF_MIN_SS
|
|
|
|
#define SECSIZE(fs) (FF_MIN_SS)
|
2017-01-27 06:42:06 -05:00
|
|
|
#else
|
|
|
|
#define SECSIZE(fs) ((fs)->ssize)
|
|
|
|
#endif
|
|
|
|
|
2016-02-13 15:51:21 -05:00
|
|
|
#define mp_obj_fat_vfs_t fs_user_mount_t
|
|
|
|
|
2018-07-13 22:51:10 -04:00
|
|
|
mp_import_stat_t fat_vfs_import_stat(void *vfs_in, const char *path) {
|
2018-06-06 00:24:23 -04:00
|
|
|
fs_user_mount_t *vfs = vfs_in;
|
2018-02-23 01:24:57 -05:00
|
|
|
FILINFO fno;
|
|
|
|
assert(vfs != NULL);
|
|
|
|
FRESULT res = f_stat(&vfs->fatfs, path, &fno);
|
|
|
|
if (res == FR_OK) {
|
|
|
|
if ((fno.fattrib & AM_DIR) != 0) {
|
|
|
|
return MP_IMPORT_STAT_DIR;
|
|
|
|
} else {
|
|
|
|
return MP_IMPORT_STAT_FILE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return MP_IMPORT_STAT_NO_EXIST;
|
|
|
|
}
|
|
|
|
|
2019-01-14 18:14:40 -05:00
|
|
|
STATIC mp_obj_t fat_vfs_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) {
|
|
|
|
mp_arg_check_num(n_args, kw_args, 1, 1, false);
|
2017-01-26 23:13:32 -05:00
|
|
|
|
|
|
|
// create new object
|
|
|
|
fs_user_mount_t *vfs = m_new_obj(fs_user_mount_t);
|
2016-02-13 15:51:21 -05:00
|
|
|
vfs->base.type = type;
|
2017-01-26 23:13:32 -05:00
|
|
|
vfs->flags = FSUSER_FREE_OBJ;
|
|
|
|
vfs->fatfs.drv = vfs;
|
|
|
|
|
|
|
|
// load block protocol methods
|
|
|
|
mp_load_method(args[0], MP_QSTR_readblocks, vfs->readblocks);
|
|
|
|
mp_load_method_maybe(args[0], MP_QSTR_writeblocks, vfs->writeblocks);
|
|
|
|
mp_load_method_maybe(args[0], MP_QSTR_ioctl, vfs->u.ioctl);
|
|
|
|
if (vfs->u.ioctl[0] != MP_OBJ_NULL) {
|
|
|
|
// device supports new block protocol, so indicate it
|
|
|
|
vfs->flags |= FSUSER_HAVE_IOCTL;
|
|
|
|
} else {
|
|
|
|
// no ioctl method, so assume the device uses the old block protocol
|
|
|
|
mp_load_method_maybe(args[0], MP_QSTR_sync, vfs->u.old.sync);
|
|
|
|
mp_load_method(args[0], MP_QSTR_count, vfs->u.old.count);
|
|
|
|
}
|
|
|
|
|
2017-11-16 00:01:47 -05:00
|
|
|
// mount the block device so the VFS methods can be used
|
|
|
|
FRESULT res = f_mount(&vfs->fatfs);
|
|
|
|
if (res == FR_NO_FILESYSTEM) {
|
|
|
|
// don't error out if no filesystem, to let mkfs()/mount() create one if wanted
|
|
|
|
vfs->flags |= FSUSER_NO_FILESYSTEM;
|
|
|
|
} else if (res != FR_OK) {
|
|
|
|
mp_raise_OSError(fresult_to_errno_table[res]);
|
|
|
|
}
|
|
|
|
|
2016-02-14 09:21:27 -05:00
|
|
|
return MP_OBJ_FROM_PTR(vfs);
|
2016-02-13 15:51:21 -05:00
|
|
|
}
|
|
|
|
|
2019-03-13 18:17:07 -04:00
|
|
|
STATIC void verify_fs_writable(fs_user_mount_t *vfs) {
|
|
|
|
if (!filesystem_is_writable_by_python(vfs)) {
|
|
|
|
mp_raise_OSError(MP_EROFS);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-22 20:55:39 -04:00
|
|
|
#if FF_FS_REENTRANT
|
2017-11-16 00:01:47 -05:00
|
|
|
STATIC mp_obj_t fat_vfs_del(mp_obj_t self_in) {
|
|
|
|
mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(self_in);
|
|
|
|
// f_umount only needs to be called to release the sync object
|
|
|
|
f_umount(&self->fatfs);
|
|
|
|
return mp_const_none;
|
|
|
|
}
|
|
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_1(fat_vfs_del_obj, fat_vfs_del);
|
|
|
|
#endif
|
|
|
|
|
2016-02-13 15:51:21 -05:00
|
|
|
STATIC mp_obj_t fat_vfs_mkfs(mp_obj_t bdev_in) {
|
2017-01-26 23:13:32 -05:00
|
|
|
// create new object
|
2019-01-14 18:14:40 -05:00
|
|
|
fs_user_mount_t *vfs = MP_OBJ_TO_PTR(fat_vfs_make_new(&mp_fat_vfs_type, 1, &bdev_in, NULL));
|
2017-01-26 23:13:32 -05:00
|
|
|
|
|
|
|
// make the filesystem
|
2021-04-22 20:55:39 -04:00
|
|
|
uint8_t working_buf[FF_MAX_SS];
|
2017-01-26 23:13:32 -05:00
|
|
|
FRESULT res = f_mkfs(&vfs->fatfs, FM_FAT | FM_SFD, 0, working_buf, sizeof(working_buf));
|
2021-04-22 20:55:39 -04:00
|
|
|
if (res == FR_MKFS_ABORTED) { // Probably doesn't support FAT16
|
|
|
|
res = f_mkfs(&vfs->fatfs, FM_FAT32, 0, working_buf, sizeof(working_buf));
|
|
|
|
}
|
2017-01-26 23:13:32 -05:00
|
|
|
if (res != FR_OK) {
|
|
|
|
mp_raise_OSError(fresult_to_errno_table[res]);
|
|
|
|
}
|
|
|
|
|
2016-02-13 15:51:21 -05:00
|
|
|
return mp_const_none;
|
|
|
|
}
|
|
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_1(fat_vfs_mkfs_fun_obj, fat_vfs_mkfs);
|
|
|
|
STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(fat_vfs_mkfs_obj, MP_ROM_PTR(&fat_vfs_mkfs_fun_obj));
|
|
|
|
|
2018-02-23 01:17:32 -05:00
|
|
|
typedef struct _mp_vfs_fat_ilistdir_it_t {
|
|
|
|
mp_obj_base_t base;
|
|
|
|
mp_fun_1_t iternext;
|
|
|
|
bool is_str;
|
|
|
|
FF_DIR dir;
|
|
|
|
} mp_vfs_fat_ilistdir_it_t;
|
|
|
|
|
|
|
|
STATIC mp_obj_t mp_vfs_fat_ilistdir_it_iternext(mp_obj_t self_in) {
|
|
|
|
mp_vfs_fat_ilistdir_it_t *self = MP_OBJ_TO_PTR(self_in);
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
FILINFO fno;
|
|
|
|
FRESULT res = f_readdir(&self->dir, &fno);
|
|
|
|
char *fn = fno.fname;
|
|
|
|
if (res != FR_OK || fn[0] == 0) {
|
|
|
|
// stop on error or end of dir
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Note that FatFS already filters . and .., so we don't need to
|
|
|
|
|
2018-03-08 19:02:26 -05:00
|
|
|
// make 4-tuple with info about this entry
|
|
|
|
mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(4, NULL));
|
2018-02-23 01:17:32 -05:00
|
|
|
if (self->is_str) {
|
|
|
|
t->items[0] = mp_obj_new_str(fn, strlen(fn));
|
|
|
|
} else {
|
2021-03-15 09:57:36 -04:00
|
|
|
t->items[0] = mp_obj_new_bytes((const byte *)fn, strlen(fn));
|
2018-02-23 01:17:32 -05:00
|
|
|
}
|
|
|
|
if (fno.fattrib & AM_DIR) {
|
|
|
|
// dir
|
|
|
|
t->items[1] = MP_OBJ_NEW_SMALL_INT(MP_S_IFDIR);
|
|
|
|
} else {
|
|
|
|
// file
|
|
|
|
t->items[1] = MP_OBJ_NEW_SMALL_INT(MP_S_IFREG);
|
|
|
|
}
|
|
|
|
t->items[2] = MP_OBJ_NEW_SMALL_INT(0); // no inode number
|
2018-03-08 19:02:26 -05:00
|
|
|
t->items[3] = mp_obj_new_int_from_uint(fno.fsize);
|
2018-02-23 01:17:32 -05:00
|
|
|
|
|
|
|
return MP_OBJ_FROM_PTR(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
// ignore error because we may be closing a second time
|
|
|
|
f_closedir(&self->dir);
|
|
|
|
|
|
|
|
return MP_OBJ_STOP_ITERATION;
|
|
|
|
}
|
2016-02-13 15:51:21 -05:00
|
|
|
|
2017-05-05 09:32:44 -04:00
|
|
|
STATIC mp_obj_t fat_vfs_ilistdir_func(size_t n_args, const mp_obj_t *args) {
|
2016-06-01 12:00:28 -04:00
|
|
|
mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(args[0]);
|
2016-02-28 10:17:24 -05:00
|
|
|
bool is_str_type = true;
|
|
|
|
const char *path;
|
|
|
|
if (n_args == 2) {
|
|
|
|
if (mp_obj_get_type(args[1]) == &mp_type_bytes) {
|
|
|
|
is_str_type = false;
|
|
|
|
}
|
|
|
|
path = mp_obj_str_get_str(args[1]);
|
|
|
|
} else {
|
|
|
|
path = "";
|
|
|
|
}
|
|
|
|
|
2018-02-23 01:17:32 -05:00
|
|
|
// Create a new iterator object to list the dir
|
|
|
|
mp_vfs_fat_ilistdir_it_t *iter = m_new_obj(mp_vfs_fat_ilistdir_it_t);
|
|
|
|
iter->base.type = &mp_type_polymorph_iter;
|
|
|
|
iter->iternext = mp_vfs_fat_ilistdir_it_iternext;
|
|
|
|
iter->is_str = is_str_type;
|
|
|
|
FRESULT res = f_opendir(&self->fatfs, &iter->dir, path);
|
|
|
|
if (res != FR_OK) {
|
|
|
|
mp_raise_OSError(fresult_to_errno_table[res]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return MP_OBJ_FROM_PTR(iter);
|
2016-02-28 10:17:24 -05:00
|
|
|
}
|
2017-05-05 09:32:44 -04:00
|
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fat_vfs_ilistdir_obj, 1, 2, fat_vfs_ilistdir_func);
|
2016-02-28 10:17:24 -05:00
|
|
|
|
2016-06-01 12:00:28 -04:00
|
|
|
STATIC mp_obj_t fat_vfs_remove_internal(mp_obj_t vfs_in, mp_obj_t path_in, mp_int_t attr) {
|
|
|
|
mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(vfs_in);
|
2019-03-13 18:17:07 -04:00
|
|
|
verify_fs_writable(self);
|
2016-02-28 13:30:07 -05:00
|
|
|
const char *path = mp_obj_str_get_str(path_in);
|
2016-09-28 09:51:35 -04:00
|
|
|
|
|
|
|
FILINFO fno;
|
2016-06-01 12:00:28 -04:00
|
|
|
FRESULT res = f_stat(&self->fatfs, path, &fno);
|
2016-09-28 09:51:35 -04:00
|
|
|
|
|
|
|
if (res != FR_OK) {
|
|
|
|
mp_raise_OSError(fresult_to_errno_table[res]);
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if path is a file or directory
|
|
|
|
if ((fno.fattrib & AM_DIR) == attr) {
|
2016-06-01 12:00:28 -04:00
|
|
|
res = f_unlink(&self->fatfs, path);
|
2016-09-28 09:51:35 -04:00
|
|
|
|
|
|
|
if (res != FR_OK) {
|
|
|
|
mp_raise_OSError(fresult_to_errno_table[res]);
|
|
|
|
}
|
2016-05-27 16:28:00 -04:00
|
|
|
return mp_const_none;
|
|
|
|
} else {
|
2016-09-28 09:51:35 -04:00
|
|
|
mp_raise_OSError(attr ? MP_ENOTDIR : MP_EISDIR);
|
2016-02-28 13:30:07 -05:00
|
|
|
}
|
|
|
|
}
|
2016-09-28 09:51:35 -04:00
|
|
|
|
|
|
|
STATIC mp_obj_t fat_vfs_remove(mp_obj_t vfs_in, mp_obj_t path_in) {
|
2016-06-01 12:00:28 -04:00
|
|
|
return fat_vfs_remove_internal(vfs_in, path_in, 0); // 0 == file attribute
|
2016-09-28 09:51:35 -04:00
|
|
|
}
|
2016-02-28 13:30:07 -05:00
|
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_2(fat_vfs_remove_obj, fat_vfs_remove);
|
|
|
|
|
2016-07-15 20:46:42 -04:00
|
|
|
STATIC mp_obj_t fat_vfs_rmdir(mp_obj_t vfs_in, mp_obj_t path_in) {
|
2016-06-01 12:00:28 -04:00
|
|
|
return fat_vfs_remove_internal(vfs_in, path_in, AM_DIR);
|
2016-07-15 20:46:42 -04:00
|
|
|
}
|
|
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_2(fat_vfs_rmdir_obj, fat_vfs_rmdir);
|
|
|
|
|
2016-02-28 18:22:38 -05:00
|
|
|
STATIC mp_obj_t fat_vfs_rename(mp_obj_t vfs_in, mp_obj_t path_in, mp_obj_t path_out) {
|
2016-06-01 12:00:28 -04:00
|
|
|
mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(vfs_in);
|
2019-03-13 18:17:07 -04:00
|
|
|
verify_fs_writable(self);
|
2016-02-28 18:22:38 -05:00
|
|
|
const char *old_path = mp_obj_str_get_str(path_in);
|
|
|
|
const char *new_path = mp_obj_str_get_str(path_out);
|
2019-03-13 18:07:19 -04:00
|
|
|
|
|
|
|
// Check to see if we're moving a directory into itself. This occurs when we're moving a
|
|
|
|
// directory where the old path is a prefix of the new and the next character is a "/" and thus
|
|
|
|
// preserves the original directory name.
|
|
|
|
FILINFO fno;
|
|
|
|
FRESULT res = f_stat(&self->fatfs, old_path, &fno);
|
|
|
|
if (res != FR_OK) {
|
|
|
|
mp_raise_OSError(fresult_to_errno_table[res]);
|
|
|
|
}
|
|
|
|
if ((fno.fattrib & AM_DIR) != 0 &&
|
2021-03-15 09:57:36 -04:00
|
|
|
strlen(new_path) > strlen(old_path) &&
|
|
|
|
new_path[strlen(old_path)] == '/' &&
|
|
|
|
strncmp(old_path, new_path, strlen(old_path)) == 0) {
|
2019-03-13 18:07:19 -04:00
|
|
|
mp_raise_OSError(MP_EINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
res = f_rename(&self->fatfs, old_path, new_path);
|
2016-12-01 23:06:09 -05:00
|
|
|
if (res == FR_EXIST) {
|
|
|
|
// if new_path exists then try removing it (but only if it's a file)
|
2016-06-01 12:00:28 -04:00
|
|
|
fat_vfs_remove_internal(vfs_in, path_out, 0); // 0 == file attribute
|
2016-12-01 23:06:09 -05:00
|
|
|
// try to rename again
|
2016-06-01 12:00:28 -04:00
|
|
|
res = f_rename(&self->fatfs, old_path, new_path);
|
2016-12-01 23:06:09 -05:00
|
|
|
}
|
2016-05-27 16:28:00 -04:00
|
|
|
if (res == FR_OK) {
|
|
|
|
return mp_const_none;
|
|
|
|
} else {
|
2016-10-06 22:44:55 -04:00
|
|
|
mp_raise_OSError(fresult_to_errno_table[res]);
|
2016-02-28 18:22:38 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_3(fat_vfs_rename_obj, fat_vfs_rename);
|
|
|
|
|
2016-02-28 17:02:50 -05:00
|
|
|
STATIC mp_obj_t fat_vfs_mkdir(mp_obj_t vfs_in, mp_obj_t path_o) {
|
2016-06-01 12:00:28 -04:00
|
|
|
mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(vfs_in);
|
2019-03-13 18:17:07 -04:00
|
|
|
verify_fs_writable(self);
|
2016-02-28 17:02:50 -05:00
|
|
|
const char *path = mp_obj_str_get_str(path_o);
|
2016-06-01 12:00:28 -04:00
|
|
|
FRESULT res = f_mkdir(&self->fatfs, path);
|
2016-05-27 16:28:00 -04:00
|
|
|
if (res == FR_OK) {
|
|
|
|
return mp_const_none;
|
|
|
|
} else {
|
2016-10-06 22:44:55 -04:00
|
|
|
mp_raise_OSError(fresult_to_errno_table[res]);
|
2016-02-28 17:02:50 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_2(fat_vfs_mkdir_obj, fat_vfs_mkdir);
|
|
|
|
|
2016-05-29 11:17:00 -04:00
|
|
|
/// Change current directory.
|
2016-05-29 11:52:41 -04:00
|
|
|
STATIC mp_obj_t fat_vfs_chdir(mp_obj_t vfs_in, mp_obj_t path_in) {
|
2016-06-01 12:00:28 -04:00
|
|
|
mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(vfs_in);
|
2016-05-29 11:17:00 -04:00
|
|
|
const char *path;
|
|
|
|
path = mp_obj_str_get_str(path_in);
|
|
|
|
|
2016-06-01 12:00:28 -04:00
|
|
|
FRESULT res = f_chdir(&self->fatfs, path);
|
2016-05-29 11:17:00 -04:00
|
|
|
|
|
|
|
if (res != FR_OK) {
|
2016-10-06 22:44:55 -04:00
|
|
|
mp_raise_OSError(fresult_to_errno_table[res]);
|
2016-05-29 11:17:00 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return mp_const_none;
|
|
|
|
}
|
2016-05-29 11:52:41 -04:00
|
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_2(fat_vfs_chdir_obj, fat_vfs_chdir);
|
2016-05-29 11:17:00 -04:00
|
|
|
|
2016-05-29 11:23:59 -04:00
|
|
|
/// Get the current directory.
|
2016-05-29 11:52:41 -04:00
|
|
|
STATIC mp_obj_t fat_vfs_getcwd(mp_obj_t vfs_in) {
|
2016-06-01 12:00:28 -04:00
|
|
|
mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(vfs_in);
|
2016-05-29 11:23:59 -04:00
|
|
|
char buf[MICROPY_ALLOC_PATH_MAX + 1];
|
2017-01-26 23:13:32 -05:00
|
|
|
FRESULT res = f_getcwd(&self->fatfs, buf, sizeof(buf));
|
2016-05-29 11:23:59 -04:00
|
|
|
if (res != FR_OK) {
|
2016-10-06 22:44:55 -04:00
|
|
|
mp_raise_OSError(fresult_to_errno_table[res]);
|
2016-05-29 11:23:59 -04:00
|
|
|
}
|
2017-11-15 21:17:51 -05:00
|
|
|
return mp_obj_new_str(buf, strlen(buf));
|
2016-05-29 11:23:59 -04:00
|
|
|
}
|
2016-05-29 11:52:41 -04:00
|
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_1(fat_vfs_getcwd_obj, fat_vfs_getcwd);
|
2016-05-29 11:23:59 -04:00
|
|
|
|
2016-05-30 15:09:20 -04:00
|
|
|
/// \function stat(path)
|
|
|
|
/// Get the status of a file or directory.
|
|
|
|
STATIC mp_obj_t fat_vfs_stat(mp_obj_t vfs_in, mp_obj_t path_in) {
|
2016-06-01 12:00:28 -04:00
|
|
|
mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(vfs_in);
|
2016-05-30 15:09:20 -04:00
|
|
|
const char *path = mp_obj_str_get_str(path_in);
|
|
|
|
|
|
|
|
FILINFO fno;
|
2017-01-26 23:13:32 -05:00
|
|
|
if (path[0] == 0 || (path[0] == '/' && path[1] == 0)) {
|
2016-05-30 15:09:20 -04:00
|
|
|
// stat root directory
|
|
|
|
fno.fsize = 0;
|
2016-06-16 12:17:59 -04:00
|
|
|
fno.fdate = 0x2821; // Jan 1, 2000
|
2016-05-30 15:09:20 -04:00
|
|
|
fno.ftime = 0;
|
|
|
|
fno.fattrib = AM_DIR;
|
|
|
|
} else {
|
2017-01-26 23:13:32 -05:00
|
|
|
FRESULT res = f_stat(&self->fatfs, path, &fno);
|
2016-05-30 15:09:20 -04:00
|
|
|
if (res != FR_OK) {
|
2016-10-06 22:44:55 -04:00
|
|
|
mp_raise_OSError(fresult_to_errno_table[res]);
|
2016-05-30 15:09:20 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL));
|
|
|
|
mp_int_t mode = 0;
|
|
|
|
if (fno.fattrib & AM_DIR) {
|
2017-05-09 22:30:34 -04:00
|
|
|
mode |= MP_S_IFDIR;
|
2016-05-30 15:09:20 -04:00
|
|
|
} else {
|
2017-05-09 22:30:34 -04:00
|
|
|
mode |= MP_S_IFREG;
|
2016-05-30 15:09:20 -04:00
|
|
|
}
|
2021-03-15 09:57:36 -04:00
|
|
|
#if MICROPY_LONGINT_IMPL == MICROPY_LONGINT_IMPL_NONE
|
2020-02-14 16:31:31 -05:00
|
|
|
// On non-longint builds, the number of seconds since 1970 (epoch) is too
|
|
|
|
// large to fit in a smallint, so just return 31-DEC-1999 (0).
|
|
|
|
mp_obj_t seconds = MP_OBJ_NEW_SMALL_INT(946684800);
|
2021-03-15 09:57:36 -04:00
|
|
|
#else
|
2020-02-14 16:31:31 -05:00
|
|
|
mp_obj_t seconds = mp_obj_new_int_from_uint(
|
|
|
|
timeutils_seconds_since_epoch(
|
|
|
|
1980 + ((fno.fdate >> 9) & 0x7f),
|
|
|
|
(fno.fdate >> 5) & 0x0f,
|
|
|
|
fno.fdate & 0x1f,
|
|
|
|
(fno.ftime >> 11) & 0x1f,
|
|
|
|
(fno.ftime >> 5) & 0x3f,
|
|
|
|
2 * (fno.ftime & 0x1f)
|
|
|
|
));
|
2021-03-15 09:57:36 -04:00
|
|
|
#endif
|
2016-05-30 15:09:20 -04:00
|
|
|
t->items[0] = MP_OBJ_NEW_SMALL_INT(mode); // st_mode
|
|
|
|
t->items[1] = MP_OBJ_NEW_SMALL_INT(0); // st_ino
|
|
|
|
t->items[2] = MP_OBJ_NEW_SMALL_INT(0); // st_dev
|
|
|
|
t->items[3] = MP_OBJ_NEW_SMALL_INT(0); // st_nlink
|
|
|
|
t->items[4] = MP_OBJ_NEW_SMALL_INT(0); // st_uid
|
|
|
|
t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // st_gid
|
2017-08-21 06:47:22 -04:00
|
|
|
t->items[6] = mp_obj_new_int_from_uint(fno.fsize); // st_size
|
2020-02-14 16:31:31 -05:00
|
|
|
t->items[7] = seconds; // st_atime
|
|
|
|
t->items[8] = seconds; // st_mtime
|
|
|
|
t->items[9] = seconds; // st_ctime
|
2016-05-30 15:09:20 -04:00
|
|
|
|
|
|
|
return MP_OBJ_FROM_PTR(t);
|
|
|
|
}
|
|
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_2(fat_vfs_stat_obj, fat_vfs_stat);
|
|
|
|
|
2016-09-12 13:13:44 -04:00
|
|
|
// Get the status of a VFS.
|
|
|
|
STATIC mp_obj_t fat_vfs_statvfs(mp_obj_t vfs_in, mp_obj_t path_in) {
|
2016-06-01 12:00:28 -04:00
|
|
|
mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(vfs_in);
|
|
|
|
(void)path_in;
|
2016-09-12 13:13:44 -04:00
|
|
|
|
|
|
|
DWORD nclst;
|
2016-06-01 12:00:28 -04:00
|
|
|
FATFS *fatfs = &self->fatfs;
|
|
|
|
FRESULT res = f_getfree(fatfs, &nclst);
|
2016-09-12 13:13:44 -04:00
|
|
|
if (FR_OK != res) {
|
2016-10-06 22:44:55 -04:00
|
|
|
mp_raise_OSError(fresult_to_errno_table[res]);
|
2016-09-12 13:13:44 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL));
|
|
|
|
|
2017-01-27 06:42:06 -05:00
|
|
|
t->items[0] = MP_OBJ_NEW_SMALL_INT(fatfs->csize * SECSIZE(fatfs)); // f_bsize
|
2016-09-12 13:13:44 -04:00
|
|
|
t->items[1] = t->items[0]; // f_frsize
|
2017-03-28 21:53:35 -04:00
|
|
|
t->items[2] = MP_OBJ_NEW_SMALL_INT((fatfs->n_fatent - 2)); // f_blocks
|
2016-09-12 13:13:44 -04:00
|
|
|
t->items[3] = MP_OBJ_NEW_SMALL_INT(nclst); // f_bfree
|
|
|
|
t->items[4] = t->items[3]; // f_bavail
|
|
|
|
t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // f_files
|
|
|
|
t->items[6] = MP_OBJ_NEW_SMALL_INT(0); // f_ffree
|
|
|
|
t->items[7] = MP_OBJ_NEW_SMALL_INT(0); // f_favail
|
|
|
|
t->items[8] = MP_OBJ_NEW_SMALL_INT(0); // f_flags
|
2021-04-22 20:55:39 -04:00
|
|
|
t->items[9] = MP_OBJ_NEW_SMALL_INT(FF_MAX_LFN); // f_namemax
|
2016-09-12 13:13:44 -04:00
|
|
|
|
|
|
|
return MP_OBJ_FROM_PTR(t);
|
|
|
|
}
|
|
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_2(fat_vfs_statvfs_obj, fat_vfs_statvfs);
|
|
|
|
|
2017-01-26 23:13:32 -05:00
|
|
|
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);
|
|
|
|
|
|
|
|
// Read-only device indicated by writeblocks[0] == MP_OBJ_NULL.
|
|
|
|
// User can specify read-only device by:
|
|
|
|
// 1. readonly=True keyword argument
|
|
|
|
// 2. nonexistent writeblocks method (then writeblocks[0] == MP_OBJ_NULL already)
|
|
|
|
if (mp_obj_is_true(readonly)) {
|
|
|
|
self->writeblocks[0] = MP_OBJ_NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if we need to make the filesystem
|
2017-11-16 00:01:47 -05:00
|
|
|
FRESULT res = (self->flags & FSUSER_NO_FILESYSTEM) ? FR_NO_FILESYSTEM : FR_OK;
|
2017-01-26 23:13:32 -05:00
|
|
|
if (res == FR_NO_FILESYSTEM && mp_obj_is_true(mkfs)) {
|
2021-04-22 20:55:39 -04:00
|
|
|
uint8_t working_buf[FF_MAX_SS];
|
2017-01-26 23:13:32 -05:00
|
|
|
res = f_mkfs(&self->fatfs, FM_FAT | FM_SFD, 0, working_buf, sizeof(working_buf));
|
|
|
|
}
|
|
|
|
if (res != FR_OK) {
|
|
|
|
mp_raise_OSError(fresult_to_errno_table[res]);
|
|
|
|
}
|
2017-11-16 00:01:47 -05:00
|
|
|
self->flags &= ~FSUSER_NO_FILESYSTEM;
|
2017-01-26 23:13:32 -05:00
|
|
|
|
|
|
|
return mp_const_none;
|
|
|
|
}
|
|
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_3(vfs_fat_mount_obj, vfs_fat_mount);
|
|
|
|
|
|
|
|
STATIC mp_obj_t vfs_fat_umount(mp_obj_t self_in) {
|
2017-11-16 00:01:47 -05:00
|
|
|
(void)self_in;
|
|
|
|
// keep the FAT filesystem mounted internally so the VFS methods can still be used
|
2016-08-22 10:10:34 -04:00
|
|
|
return mp_const_none;
|
|
|
|
}
|
2017-01-26 23:13:32 -05:00
|
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_1(fat_vfs_umount_obj, vfs_fat_umount);
|
2016-08-22 10:10:34 -04:00
|
|
|
|
2018-03-27 22:28:18 -04:00
|
|
|
#if MICROPY_FATFS_USE_LABEL
|
|
|
|
STATIC mp_obj_t vfs_fat_getlabel(mp_obj_t self_in) {
|
|
|
|
fs_user_mount_t *self = MP_OBJ_TO_PTR(self_in);
|
|
|
|
char working_buf[12];
|
|
|
|
FRESULT res = f_getlabel(&self->fatfs, working_buf, NULL);
|
|
|
|
if (res != FR_OK) {
|
|
|
|
mp_raise_OSError(fresult_to_errno_table[res]);
|
|
|
|
}
|
2018-07-13 22:51:10 -04:00
|
|
|
return mp_obj_new_str(working_buf, strlen(working_buf));
|
2018-03-27 22:28:18 -04:00
|
|
|
}
|
|
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_1(fat_vfs_getlabel_obj, vfs_fat_getlabel);
|
|
|
|
|
2018-07-13 22:51:10 -04:00
|
|
|
STATIC mp_obj_t vfs_fat_setlabel(mp_obj_t self_in, mp_obj_t label_in) {
|
2018-03-27 22:28:18 -04:00
|
|
|
fs_user_mount_t *self = MP_OBJ_TO_PTR(self_in);
|
2019-03-13 18:17:07 -04:00
|
|
|
verify_fs_writable(self);
|
2018-03-27 22:28:18 -04:00
|
|
|
const char *label_str = mp_obj_str_get_str(label_in);
|
|
|
|
FRESULT res = f_setlabel(&self->fatfs, label_str);
|
|
|
|
if (res != FR_OK) {
|
2021-03-15 09:57:36 -04:00
|
|
|
if (res == FR_WRITE_PROTECTED) {
|
2018-08-08 21:24:49 -04:00
|
|
|
mp_raise_msg(&mp_type_OSError, translate("Read-only filesystem"));
|
2018-03-27 22:28:19 -04:00
|
|
|
}
|
2018-03-27 22:28:18 -04:00
|
|
|
mp_raise_OSError(fresult_to_errno_table[res]);
|
|
|
|
}
|
|
|
|
return mp_const_none;
|
|
|
|
}
|
|
|
|
STATIC MP_DEFINE_CONST_FUN_OBJ_2(fat_vfs_setlabel_obj, vfs_fat_setlabel);
|
|
|
|
STATIC const mp_obj_property_t fat_vfs_label_obj = {
|
|
|
|
.base.type = &mp_type_property,
|
|
|
|
.proxy = {(mp_obj_t)&fat_vfs_getlabel_obj,
|
|
|
|
(mp_obj_t)&fat_vfs_setlabel_obj,
|
|
|
|
(mp_obj_t)&mp_const_none_obj},
|
|
|
|
};
|
|
|
|
#endif
|
|
|
|
|
2016-02-13 15:51:21 -05:00
|
|
|
STATIC const mp_rom_map_elem_t fat_vfs_locals_dict_table[] = {
|
2021-04-22 20:55:39 -04:00
|
|
|
#if FF_FS_REENTRANT
|
2017-11-16 00:01:47 -05:00
|
|
|
{ MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&fat_vfs_del_obj) },
|
|
|
|
#endif
|
2016-02-13 15:51:21 -05:00
|
|
|
{ MP_ROM_QSTR(MP_QSTR_mkfs), MP_ROM_PTR(&fat_vfs_mkfs_obj) },
|
|
|
|
{ MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&fat_vfs_open_obj) },
|
2017-05-05 09:32:44 -04:00
|
|
|
{ MP_ROM_QSTR(MP_QSTR_ilistdir), MP_ROM_PTR(&fat_vfs_ilistdir_obj) },
|
2016-02-28 17:02:50 -05:00
|
|
|
{ MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&fat_vfs_mkdir_obj) },
|
2016-07-15 20:46:42 -04:00
|
|
|
{ MP_ROM_QSTR(MP_QSTR_rmdir), MP_ROM_PTR(&fat_vfs_rmdir_obj) },
|
2016-05-29 11:17:00 -04:00
|
|
|
{ MP_ROM_QSTR(MP_QSTR_chdir), MP_ROM_PTR(&fat_vfs_chdir_obj) },
|
2016-05-29 11:23:59 -04:00
|
|
|
{ MP_ROM_QSTR(MP_QSTR_getcwd), MP_ROM_PTR(&fat_vfs_getcwd_obj) },
|
2016-02-28 13:30:07 -05:00
|
|
|
{ MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&fat_vfs_remove_obj) },
|
2016-02-28 18:22:38 -05:00
|
|
|
{ MP_ROM_QSTR(MP_QSTR_rename), MP_ROM_PTR(&fat_vfs_rename_obj) },
|
2016-05-30 15:09:20 -04:00
|
|
|
{ MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&fat_vfs_stat_obj) },
|
2016-09-12 13:13:44 -04:00
|
|
|
{ MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&fat_vfs_statvfs_obj) },
|
2017-01-26 23:13:32 -05:00
|
|
|
{ MP_ROM_QSTR(MP_QSTR_mount), MP_ROM_PTR(&vfs_fat_mount_obj) },
|
2016-08-22 10:10:34 -04:00
|
|
|
{ MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&fat_vfs_umount_obj) },
|
2021-03-15 09:57:36 -04:00
|
|
|
#if MICROPY_FATFS_USE_LABEL
|
2018-03-27 22:28:18 -04:00
|
|
|
{ MP_ROM_QSTR(MP_QSTR_label), MP_ROM_PTR(&fat_vfs_label_obj) },
|
2021-03-15 09:57:36 -04:00
|
|
|
#endif
|
2016-02-13 15:51:21 -05:00
|
|
|
};
|
|
|
|
STATIC MP_DEFINE_CONST_DICT(fat_vfs_locals_dict, fat_vfs_locals_dict_table);
|
|
|
|
|
2018-06-06 00:24:23 -04:00
|
|
|
STATIC const mp_vfs_proto_t fat_vfs_proto = {
|
protocols: Allow them to be (optionally) type-safe
Protocols are nice, but there is no way for C code to verify whether
a type's "protocol" structure actually implements some particular
protocol. As a result, you can pass an object that implements the
"vfs" protocol to one that expects the "stream" protocol, and the
opposite of awesomeness ensues.
This patch adds an OPTIONAL (but enabled by default) protocol identifier
as the first member of any protocol structure. This identifier is
simply a unique QSTR chosen by the protocol designer and used by each
protocol implementer. When checking for protocol support, instead of
just checking whether the object's type has a non-NULL protocol field,
use `mp_proto_get` which implements the protocol check when possible.
The existing protocols are now named:
protocol_framebuf
protocol_i2c
protocol_pin
protocol_stream
protocol_spi
protocol_vfs
(most of these are unused in CP and are just inherited from MP; vfs and
stream are definitely used though)
I did not find any crashing examples, but here's one to give a flavor of what
is improved, using `micropython_coverage`. Before the change,
the vfs "ioctl" protocol is invoked, and the result is not intelligible
as json (but it could have resulted in a hard fault, potentially):
>>> import uos, ujson
>>> u = uos.VfsPosix('/tmp')
>>> ujson.load(u)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: syntax error in JSON
After the change, the vfs object is correctly detected as not supporting
the stream protocol:
>>> ujson.load(p)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OSError: stream operation not supported
2019-12-03 15:50:37 -05:00
|
|
|
MP_PROTO_IMPLEMENT(MP_QSTR_protocol_vfs)
|
2018-06-06 00:24:23 -04:00
|
|
|
.import_stat = fat_vfs_import_stat,
|
|
|
|
};
|
|
|
|
|
2016-02-13 15:51:21 -05:00
|
|
|
const mp_obj_type_t mp_fat_vfs_type = {
|
|
|
|
{ &mp_type_type },
|
|
|
|
.name = MP_QSTR_VfsFat,
|
|
|
|
.make_new = fat_vfs_make_new,
|
2018-06-06 00:24:23 -04:00
|
|
|
.protocol = &fat_vfs_proto,
|
2021-03-15 09:57:36 -04:00
|
|
|
.locals_dict = (mp_obj_dict_t *)&fat_vfs_locals_dict,
|
2018-06-06 00:24:23 -04:00
|
|
|
|
2016-02-13 15:51:21 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
#endif // MICROPY_VFS_FAT
|