From b7572ad11b31d4e357139e877a0815ebd6ae515e Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 11 Jun 2014 15:41:14 +0100 Subject: [PATCH] stmhal, file: Implement a,x,+ open modes, seek and tell. Also now returns correct POSIX errno when an IO operation fails. Addresses issues #516 and #676. --- stmhal/file.c | 217 +++++++++++++++++++++++++++++++++--------- stmhal/mpconfigport.h | 2 + stmhal/qstrdefsport.h | 4 + 3 files changed, 176 insertions(+), 47 deletions(-) diff --git a/stmhal/file.c b/stmhal/file.c index 3fea956530..fbaff879f4 100644 --- a/stmhal/file.c +++ b/stmhal/file.c @@ -25,9 +25,11 @@ */ #include +#include -#include "misc.h" #include "mpconfig.h" +#include "nlr.h" +#include "misc.h" #include "qstr.h" #include "obj.h" #include "runtime.h" @@ -35,6 +37,33 @@ #include "file.h" #include "ff.h" +extern const mp_obj_type_t mp_type_fileio; +extern const mp_obj_type_t mp_type_textio; + +// this table converts from FRESULT to POSIX errno +STATIC const byte fresult_to_errno_table[] = { + [FR_OK] = 0, + [FR_DISK_ERR] = EIO, + [FR_INT_ERR] = EIO, + [FR_NOT_READY] = EBUSY, + [FR_NO_FILE] = ENOENT, + [FR_NO_PATH] = ENOENT, + [FR_INVALID_NAME] = EINVAL, + [FR_DENIED] = EACCES, + [FR_EXIST] = EEXIST, + [FR_INVALID_OBJECT] = EINVAL, + [FR_WRITE_PROTECTED] = EROFS, + [FR_INVALID_DRIVE] = ENODEV, + [FR_NOT_ENABLED] = ENODEV, + [FR_NO_FILESYSTEM] = ENODEV, + [FR_MKFS_ABORTED] = EIO, + [FR_TIMEOUT] = EIO, + [FR_LOCKED] = EIO, + [FR_NOT_ENOUGH_CORE] = ENOMEM, + [FR_TOO_MANY_OPEN_FILES] = EMFILE, + [FR_INVALID_PARAMETER] = EINVAL, +}; + typedef struct _pyb_file_obj_t { mp_obj_base_t base; FIL fp; @@ -44,17 +73,25 @@ void file_obj_print(void (*print)(void *env, const char *fmt, ...), void *env, m printf("", mp_obj_get_type_str(self_in), self_in); } -STATIC machine_int_t file_read(mp_obj_t self_in, void *buf, machine_uint_t size, int *errcode) { +STATIC machine_int_t file_obj_read(mp_obj_t self_in, void *buf, machine_uint_t size, int *errcode) { pyb_file_obj_t *self = self_in; UINT sz_out; - *errcode = f_read(&self->fp, buf, size, &sz_out); + FRESULT res = f_read(&self->fp, buf, size, &sz_out); + if (res != FR_OK) { + *errcode = fresult_to_errno_table[res]; + return -1; + } return sz_out; } -STATIC machine_int_t file_write(mp_obj_t self_in, const void *buf, machine_uint_t size, int *errcode) { +STATIC machine_int_t file_obj_write(mp_obj_t self_in, const void *buf, machine_uint_t size, int *errcode) { pyb_file_obj_t *self = self_in; UINT sz_out; - *errcode = f_write(&self->fp, buf, size, &sz_out); + FRESULT res = f_write(&self->fp, buf, size, &sz_out); + if (res != FR_OK) { + *errcode = fresult_to_errno_table[res]; + return -1; + } return sz_out; } @@ -63,78 +100,164 @@ mp_obj_t file_obj_close(mp_obj_t self_in) { f_close(&self->fp); return mp_const_none; } - STATIC MP_DEFINE_CONST_FUN_OBJ_1(file_obj_close_obj, file_obj_close); mp_obj_t file_obj___exit__(uint n_args, const mp_obj_t *args) { return file_obj_close(args[0]); } -static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(file_obj___exit___obj, 4, 4, file_obj___exit__); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(file_obj___exit___obj, 4, 4, file_obj___exit__); + +mp_obj_t file_obj_seek(uint n_args, const mp_obj_t *args) { + pyb_file_obj_t *self = args[0]; + machine_int_t offset = mp_obj_get_int(args[1]); + machine_int_t whence = 0; + if (n_args == 3) { + whence = mp_obj_get_int(args[2]); + } + + switch (whence) { + case 0: // SEEK_SET + f_lseek(&self->fp, offset); + break; + + case 1: // SEEK_CUR + if (offset != 0) { + goto error; + } + // no-operation + break; + + case 2: // SEEK_END + if (offset != 0) { + goto error; + } + f_lseek(&self->fp, f_size(&self->fp)); + break; + + default: + goto error; + } + + return mp_obj_new_int_from_uint(f_tell(&self->fp)); + +error: + // A bad whence is a ValueError, while offset!=0 is an io.UnsupportedOperation. + // But the latter inherits ValueError (as well as IOError), so we just raise ValueError. + nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "invalid whence and/or offset")); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(file_obj_seek_obj, 2, 3, file_obj_seek); + +mp_obj_t file_obj_tell(mp_obj_t self_in) { + pyb_file_obj_t *self = self_in; + return mp_obj_new_int_from_uint(f_tell(&self->fp)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(file_obj_tell_obj, file_obj_tell); + +STATIC mp_obj_t file_obj_make_new(mp_obj_t type, uint n_args, uint n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 1, 2, false); + + const char *fname = mp_obj_str_get_str(args[0]); + + int mode = 0; + if (n_args == 1) { + mode = FA_READ; + } else { + const char *mode_s = mp_obj_str_get_str(args[1]); + // TODO make sure only one of r, w, x, a, and b, t are specified + while (*mode_s) { + switch (*mode_s++) { + case 'r': + mode |= FA_READ; + break; + case 'w': + mode |= FA_WRITE | FA_CREATE_ALWAYS; + break; + case 'x': + mode |= FA_WRITE | FA_CREATE_NEW; + break; + case 'a': + mode |= FA_WRITE | FA_OPEN_ALWAYS; + break; + case '+': + mode |= FA_READ | FA_WRITE; + break; + #if MICROPY_PY_IO_FILEIO + case 'b': + type = (mp_obj_t)&mp_type_fileio; + break; + #endif + case 't': + type = (mp_obj_t)&mp_type_textio; + break; + } + } + } + + pyb_file_obj_t *o = m_new_obj_with_finaliser(pyb_file_obj_t); + o->base.type = type; + + FRESULT res = f_open(&o->fp, fname, mode); + if (res != FR_OK) { + m_del_obj(pyb_file_obj_t, o); + nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT((machine_int_t)fresult_to_errno_table[res]))); + } + + return o; +} // TODO gc hook to close the file if not already closed -STATIC const mp_map_elem_t file_locals_dict_table[] = { +STATIC const mp_map_elem_t rawfile_locals_dict_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR_read), (mp_obj_t)&mp_stream_read_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_readall), (mp_obj_t)&mp_stream_readall_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_readline), (mp_obj_t)&mp_stream_unbuffered_readline_obj}, { MP_OBJ_NEW_QSTR(MP_QSTR_readlines), (mp_obj_t)&mp_stream_unbuffered_readlines_obj}, { MP_OBJ_NEW_QSTR(MP_QSTR_write), (mp_obj_t)&mp_stream_write_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_close), (mp_obj_t)&file_obj_close_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_seek), (mp_obj_t)&file_obj_seek_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_tell), (mp_obj_t)&file_obj_tell_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR___del__), (mp_obj_t)&file_obj_close_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR___enter__), (mp_obj_t)&mp_identity_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR___exit__), (mp_obj_t)&file_obj___exit___obj }, }; -STATIC MP_DEFINE_CONST_DICT(file_locals_dict, file_locals_dict_table); +STATIC MP_DEFINE_CONST_DICT(rawfile_locals_dict, rawfile_locals_dict_table); -STATIC mp_obj_t file_obj_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args); +#if MICROPY_PY_IO_FILEIO +STATIC const mp_stream_p_t fileio_stream_p = { + .read = file_obj_read, + .write = file_obj_write, + .is_bytes = true, +}; -STATIC const mp_stream_p_t file_obj_stream_p = { - .read = file_read, - .write = file_write, +const mp_obj_type_t mp_type_fileio = { + { &mp_type_type }, + .name = MP_QSTR_FileIO, + .print = file_obj_print, + .make_new = file_obj_make_new, + .getiter = mp_identity, + .iternext = mp_stream_unbuffered_iter, + .stream_p = &fileio_stream_p, + .locals_dict = (mp_obj_t)&rawfile_locals_dict, +}; +#endif + +STATIC const mp_stream_p_t textio_stream_p = { + .read = file_obj_read, + .write = file_obj_write, }; const mp_obj_type_t mp_type_textio = { { &mp_type_type }, - .name = MP_QSTR_FileIO, - .make_new = file_obj_make_new, + .name = MP_QSTR_TextIOWrapper, .print = file_obj_print, + .make_new = file_obj_make_new, .getiter = mp_identity, .iternext = mp_stream_unbuffered_iter, - .stream_p = &file_obj_stream_p, - .locals_dict = (mp_obj_t)&file_locals_dict, + .stream_p = &textio_stream_p, + .locals_dict = (mp_obj_t)&rawfile_locals_dict, }; -STATIC mp_obj_t file_obj_make_new(mp_obj_t type_in, uint n_args, uint n_kw, const mp_obj_t *args) { - mp_arg_check_num(n_args, n_kw, 1, 2, false); - const char *filename = mp_obj_str_get_str(args[0]); - const char *mode = "r"; - if (n_args > 1) { - mode = mp_obj_str_get_str(args[1]); - } - pyb_file_obj_t *self = m_new_obj_with_finaliser(pyb_file_obj_t); - self->base.type = &mp_type_textio; - if (mode[0] == 'r') { - // open for reading - FRESULT res = f_open(&self->fp, filename, FA_READ); - if (res != FR_OK) { - printf("FileNotFoundError: [Errno 2] No such file or directory: '%s'\n", filename); - return mp_const_none; - } - } else if (mode[0] == 'w') { - // open for writing, truncate the file first - FRESULT res = f_open(&self->fp, filename, FA_WRITE | FA_CREATE_ALWAYS); - if (res != FR_OK) { - printf("?FileError: could not create file: '%s'\n", filename); - return mp_const_none; - } - } else { - printf("ValueError: invalid mode: '%s'\n", mode); - return mp_const_none; - } - return self; -} - // Factory function for I/O stream classes STATIC mp_obj_t pyb_io_open(uint n_args, const mp_obj_t *args) { // TODO: analyze mode and buffering args and instantiate appropriate type diff --git a/stmhal/mpconfigport.h b/stmhal/mpconfigport.h index 5b99ffca55..28cd90bb01 100644 --- a/stmhal/mpconfigport.h +++ b/stmhal/mpconfigport.h @@ -48,6 +48,8 @@ #define MICROPY_PY_SYS_EXIT (1) #define MICROPY_PY_SYS_STDFILES (1) #define MICROPY_PY_CMATH (1) +#define MICROPY_PY_IO (1) +#define MICROPY_PY_IO_FILEIO (1) // extra built in names to add to the global namespace extern const struct _mp_obj_fun_native_t mp_builtin_help_obj; diff --git a/stmhal/qstrdefsport.h b/stmhal/qstrdefsport.h index d822a542f0..19a5d7c863 100644 --- a/stmhal/qstrdefsport.h +++ b/stmhal/qstrdefsport.h @@ -66,6 +66,10 @@ Q(0:/) Q(0:/lib) Q(millis) +// for file class +Q(seek) +Q(tell) + // for RTC class Q(RTC) Q(info)