Damien George | 04b9147 | 2014-05-03 23:27:38 +0100 | [diff] [blame] | 1 | /* |
| 2 | * This file is part of the Micro Python project, http://micropython.org/ |
| 3 | * |
| 4 | * The MIT License (MIT) |
| 5 | * |
| 6 | * Copyright (c) 2013, 2014 Damien P. George |
| 7 | * |
| 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy |
| 9 | * of this software and associated documentation files (the "Software"), to deal |
| 10 | * in the Software without restriction, including without limitation the rights |
| 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 12 | * copies of the Software, and to permit persons to whom the Software is |
| 13 | * furnished to do so, subject to the following conditions: |
| 14 | * |
| 15 | * The above copyright notice and this permission notice shall be included in |
| 16 | * all copies or substantial portions of the Software. |
| 17 | * |
| 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| 24 | * THE SOFTWARE. |
| 25 | */ |
| 26 | |
Damien George | b92d3e1 | 2014-03-17 14:04:19 +0000 | [diff] [blame] | 27 | #include <stdio.h> |
Damien George | b7572ad | 2014-06-11 15:41:14 +0100 | [diff] [blame] | 28 | #include <errno.h> |
Damien George | b92d3e1 | 2014-03-17 14:04:19 +0000 | [diff] [blame] | 29 | |
Damien George | b92d3e1 | 2014-03-17 14:04:19 +0000 | [diff] [blame] | 30 | #include "mpconfig.h" |
Damien George | b7572ad | 2014-06-11 15:41:14 +0100 | [diff] [blame] | 31 | #include "nlr.h" |
| 32 | #include "misc.h" |
Damien George | b92d3e1 | 2014-03-17 14:04:19 +0000 | [diff] [blame] | 33 | #include "qstr.h" |
| 34 | #include "obj.h" |
Paul Sokolovsky | 1d4d9dd | 2014-04-03 20:32:54 +0300 | [diff] [blame] | 35 | #include "runtime.h" |
| 36 | #include "stream.h" |
Damien George | b92d3e1 | 2014-03-17 14:04:19 +0000 | [diff] [blame] | 37 | #include "file.h" |
| 38 | #include "ff.h" |
| 39 | |
Damien George | b7572ad | 2014-06-11 15:41:14 +0100 | [diff] [blame] | 40 | extern const mp_obj_type_t mp_type_fileio; |
| 41 | extern const mp_obj_type_t mp_type_textio; |
| 42 | |
| 43 | // this table converts from FRESULT to POSIX errno |
Damien George | 02bc882 | 2014-07-19 16:39:13 +0100 | [diff] [blame] | 44 | const byte fresult_to_errno_table[20] = { |
Damien George | b7572ad | 2014-06-11 15:41:14 +0100 | [diff] [blame] | 45 | [FR_OK] = 0, |
| 46 | [FR_DISK_ERR] = EIO, |
| 47 | [FR_INT_ERR] = EIO, |
| 48 | [FR_NOT_READY] = EBUSY, |
| 49 | [FR_NO_FILE] = ENOENT, |
| 50 | [FR_NO_PATH] = ENOENT, |
| 51 | [FR_INVALID_NAME] = EINVAL, |
| 52 | [FR_DENIED] = EACCES, |
| 53 | [FR_EXIST] = EEXIST, |
| 54 | [FR_INVALID_OBJECT] = EINVAL, |
| 55 | [FR_WRITE_PROTECTED] = EROFS, |
| 56 | [FR_INVALID_DRIVE] = ENODEV, |
| 57 | [FR_NOT_ENABLED] = ENODEV, |
| 58 | [FR_NO_FILESYSTEM] = ENODEV, |
| 59 | [FR_MKFS_ABORTED] = EIO, |
| 60 | [FR_TIMEOUT] = EIO, |
| 61 | [FR_LOCKED] = EIO, |
| 62 | [FR_NOT_ENOUGH_CORE] = ENOMEM, |
| 63 | [FR_TOO_MANY_OPEN_FILES] = EMFILE, |
| 64 | [FR_INVALID_PARAMETER] = EINVAL, |
| 65 | }; |
| 66 | |
Damien George | b92d3e1 | 2014-03-17 14:04:19 +0000 | [diff] [blame] | 67 | typedef struct _pyb_file_obj_t { |
| 68 | mp_obj_base_t base; |
| 69 | FIL fp; |
| 70 | } pyb_file_obj_t; |
| 71 | |
| 72 | void file_obj_print(void (*print)(void *env, const char *fmt, ...), void *env, mp_obj_t self_in, mp_print_kind_t kind) { |
Damien George | e79c669 | 2014-06-15 09:10:07 +0100 | [diff] [blame] | 73 | print(env, "<io.%s %p>", mp_obj_get_type_str(self_in), self_in); |
Damien George | b92d3e1 | 2014-03-17 14:04:19 +0000 | [diff] [blame] | 74 | } |
| 75 | |
Damien George | adf0f2a | 2014-07-27 22:38:58 +0100 | [diff] [blame] | 76 | STATIC mp_uint_t file_obj_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) { |
Damien George | b92d3e1 | 2014-03-17 14:04:19 +0000 | [diff] [blame] | 77 | pyb_file_obj_t *self = self_in; |
Paul Sokolovsky | 1d4d9dd | 2014-04-03 20:32:54 +0300 | [diff] [blame] | 78 | UINT sz_out; |
Damien George | b7572ad | 2014-06-11 15:41:14 +0100 | [diff] [blame] | 79 | FRESULT res = f_read(&self->fp, buf, size, &sz_out); |
| 80 | if (res != FR_OK) { |
| 81 | *errcode = fresult_to_errno_table[res]; |
Damien George | adf0f2a | 2014-07-27 22:38:58 +0100 | [diff] [blame] | 82 | return MP_STREAM_ERROR; |
Damien George | b7572ad | 2014-06-11 15:41:14 +0100 | [diff] [blame] | 83 | } |
Paul Sokolovsky | 1d4d9dd | 2014-04-03 20:32:54 +0300 | [diff] [blame] | 84 | return sz_out; |
Damien George | b92d3e1 | 2014-03-17 14:04:19 +0000 | [diff] [blame] | 85 | } |
| 86 | |
Damien George | adf0f2a | 2014-07-27 22:38:58 +0100 | [diff] [blame] | 87 | STATIC mp_uint_t file_obj_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) { |
Damien George | b92d3e1 | 2014-03-17 14:04:19 +0000 | [diff] [blame] | 88 | pyb_file_obj_t *self = self_in; |
Paul Sokolovsky | 1d4d9dd | 2014-04-03 20:32:54 +0300 | [diff] [blame] | 89 | UINT sz_out; |
Damien George | b7572ad | 2014-06-11 15:41:14 +0100 | [diff] [blame] | 90 | FRESULT res = f_write(&self->fp, buf, size, &sz_out); |
| 91 | if (res != FR_OK) { |
| 92 | *errcode = fresult_to_errno_table[res]; |
Damien George | adf0f2a | 2014-07-27 22:38:58 +0100 | [diff] [blame] | 93 | return MP_STREAM_ERROR; |
Damien George | b7572ad | 2014-06-11 15:41:14 +0100 | [diff] [blame] | 94 | } |
Paul Sokolovsky | 1d4d9dd | 2014-04-03 20:32:54 +0300 | [diff] [blame] | 95 | return sz_out; |
Damien George | b92d3e1 | 2014-03-17 14:04:19 +0000 | [diff] [blame] | 96 | } |
| 97 | |
Damien George | 02bc882 | 2014-07-19 16:39:13 +0100 | [diff] [blame] | 98 | STATIC mp_obj_t file_obj_flush(mp_obj_t self_in) { |
| 99 | pyb_file_obj_t *self = self_in; |
| 100 | f_sync(&self->fp); |
| 101 | return mp_const_none; |
| 102 | } |
| 103 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(file_obj_flush_obj, file_obj_flush); |
| 104 | |
Damien George | b92d3e1 | 2014-03-17 14:04:19 +0000 | [diff] [blame] | 105 | mp_obj_t file_obj_close(mp_obj_t self_in) { |
| 106 | pyb_file_obj_t *self = self_in; |
| 107 | f_close(&self->fp); |
| 108 | return mp_const_none; |
| 109 | } |
Paul Sokolovsky | 1d4d9dd | 2014-04-03 20:32:54 +0300 | [diff] [blame] | 110 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(file_obj_close_obj, file_obj_close); |
| 111 | |
| 112 | mp_obj_t file_obj___exit__(uint n_args, const mp_obj_t *args) { |
| 113 | return file_obj_close(args[0]); |
| 114 | } |
Damien George | b7572ad | 2014-06-11 15:41:14 +0100 | [diff] [blame] | 115 | STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(file_obj___exit___obj, 4, 4, file_obj___exit__); |
| 116 | |
| 117 | mp_obj_t file_obj_seek(uint n_args, const mp_obj_t *args) { |
| 118 | pyb_file_obj_t *self = args[0]; |
Damien George | 40f3c02 | 2014-07-03 13:25:24 +0100 | [diff] [blame] | 119 | mp_int_t offset = mp_obj_get_int(args[1]); |
| 120 | mp_int_t whence = 0; |
Damien George | b7572ad | 2014-06-11 15:41:14 +0100 | [diff] [blame] | 121 | if (n_args == 3) { |
| 122 | whence = mp_obj_get_int(args[2]); |
| 123 | } |
| 124 | |
| 125 | switch (whence) { |
| 126 | case 0: // SEEK_SET |
| 127 | f_lseek(&self->fp, offset); |
| 128 | break; |
| 129 | |
| 130 | case 1: // SEEK_CUR |
| 131 | if (offset != 0) { |
| 132 | goto error; |
| 133 | } |
| 134 | // no-operation |
| 135 | break; |
| 136 | |
| 137 | case 2: // SEEK_END |
| 138 | if (offset != 0) { |
| 139 | goto error; |
| 140 | } |
| 141 | f_lseek(&self->fp, f_size(&self->fp)); |
| 142 | break; |
| 143 | |
| 144 | default: |
| 145 | goto error; |
| 146 | } |
| 147 | |
| 148 | return mp_obj_new_int_from_uint(f_tell(&self->fp)); |
| 149 | |
| 150 | error: |
| 151 | // A bad whence is a ValueError, while offset!=0 is an io.UnsupportedOperation. |
| 152 | // But the latter inherits ValueError (as well as IOError), so we just raise ValueError. |
| 153 | nlr_raise(mp_obj_new_exception_msg(&mp_type_ValueError, "invalid whence and/or offset")); |
| 154 | } |
| 155 | STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(file_obj_seek_obj, 2, 3, file_obj_seek); |
| 156 | |
| 157 | mp_obj_t file_obj_tell(mp_obj_t self_in) { |
| 158 | pyb_file_obj_t *self = self_in; |
| 159 | return mp_obj_new_int_from_uint(f_tell(&self->fp)); |
| 160 | } |
| 161 | STATIC MP_DEFINE_CONST_FUN_OBJ_1(file_obj_tell_obj, file_obj_tell); |
| 162 | |
| 163 | STATIC mp_obj_t file_obj_make_new(mp_obj_t type, uint n_args, uint n_kw, const mp_obj_t *args) { |
| 164 | mp_arg_check_num(n_args, n_kw, 1, 2, false); |
| 165 | |
| 166 | const char *fname = mp_obj_str_get_str(args[0]); |
| 167 | |
| 168 | int mode = 0; |
| 169 | if (n_args == 1) { |
| 170 | mode = FA_READ; |
| 171 | } else { |
| 172 | const char *mode_s = mp_obj_str_get_str(args[1]); |
| 173 | // TODO make sure only one of r, w, x, a, and b, t are specified |
| 174 | while (*mode_s) { |
| 175 | switch (*mode_s++) { |
| 176 | case 'r': |
| 177 | mode |= FA_READ; |
| 178 | break; |
| 179 | case 'w': |
| 180 | mode |= FA_WRITE | FA_CREATE_ALWAYS; |
| 181 | break; |
| 182 | case 'x': |
| 183 | mode |= FA_WRITE | FA_CREATE_NEW; |
| 184 | break; |
| 185 | case 'a': |
| 186 | mode |= FA_WRITE | FA_OPEN_ALWAYS; |
| 187 | break; |
| 188 | case '+': |
| 189 | mode |= FA_READ | FA_WRITE; |
| 190 | break; |
| 191 | #if MICROPY_PY_IO_FILEIO |
| 192 | case 'b': |
| 193 | type = (mp_obj_t)&mp_type_fileio; |
| 194 | break; |
| 195 | #endif |
| 196 | case 't': |
| 197 | type = (mp_obj_t)&mp_type_textio; |
| 198 | break; |
| 199 | } |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | pyb_file_obj_t *o = m_new_obj_with_finaliser(pyb_file_obj_t); |
| 204 | o->base.type = type; |
| 205 | |
| 206 | FRESULT res = f_open(&o->fp, fname, mode); |
| 207 | if (res != FR_OK) { |
| 208 | m_del_obj(pyb_file_obj_t, o); |
Damien George | bb4c6f3 | 2014-07-31 10:49:14 +0100 | [diff] [blame] | 209 | nlr_raise(mp_obj_new_exception_arg1(&mp_type_OSError, MP_OBJ_NEW_SMALL_INT(fresult_to_errno_table[res]))); |
Damien George | b7572ad | 2014-06-11 15:41:14 +0100 | [diff] [blame] | 210 | } |
| 211 | |
Damien George | 8208967 | 2014-06-11 16:01:52 +0100 | [diff] [blame] | 212 | // for 'a' mode, we must begin at the end of the file |
| 213 | if ((mode & FA_OPEN_ALWAYS) != 0) { |
| 214 | f_lseek(&o->fp, f_size(&o->fp)); |
| 215 | } |
| 216 | |
Damien George | b7572ad | 2014-06-11 15:41:14 +0100 | [diff] [blame] | 217 | return o; |
| 218 | } |
Damien George | b92d3e1 | 2014-03-17 14:04:19 +0000 | [diff] [blame] | 219 | |
| 220 | // TODO gc hook to close the file if not already closed |
| 221 | |
Damien George | b7572ad | 2014-06-11 15:41:14 +0100 | [diff] [blame] | 222 | STATIC const mp_map_elem_t rawfile_locals_dict_table[] = { |
Paul Sokolovsky | 1d4d9dd | 2014-04-03 20:32:54 +0300 | [diff] [blame] | 223 | { MP_OBJ_NEW_QSTR(MP_QSTR_read), (mp_obj_t)&mp_stream_read_obj }, |
| 224 | { MP_OBJ_NEW_QSTR(MP_QSTR_readall), (mp_obj_t)&mp_stream_readall_obj }, |
| 225 | { MP_OBJ_NEW_QSTR(MP_QSTR_readline), (mp_obj_t)&mp_stream_unbuffered_readline_obj}, |
Damien George | d5f5b2f | 2014-05-03 22:01:32 +0100 | [diff] [blame] | 226 | { MP_OBJ_NEW_QSTR(MP_QSTR_readlines), (mp_obj_t)&mp_stream_unbuffered_readlines_obj}, |
Paul Sokolovsky | 1d4d9dd | 2014-04-03 20:32:54 +0300 | [diff] [blame] | 227 | { MP_OBJ_NEW_QSTR(MP_QSTR_write), (mp_obj_t)&mp_stream_write_obj }, |
Damien George | 02bc882 | 2014-07-19 16:39:13 +0100 | [diff] [blame] | 228 | { MP_OBJ_NEW_QSTR(MP_QSTR_flush), (mp_obj_t)&file_obj_flush_obj }, |
Damien George | 9b196cd | 2014-03-26 21:47:19 +0000 | [diff] [blame] | 229 | { MP_OBJ_NEW_QSTR(MP_QSTR_close), (mp_obj_t)&file_obj_close_obj }, |
Damien George | b7572ad | 2014-06-11 15:41:14 +0100 | [diff] [blame] | 230 | { MP_OBJ_NEW_QSTR(MP_QSTR_seek), (mp_obj_t)&file_obj_seek_obj }, |
| 231 | { MP_OBJ_NEW_QSTR(MP_QSTR_tell), (mp_obj_t)&file_obj_tell_obj }, |
Damien George | 12bab72 | 2014-04-05 20:35:48 +0100 | [diff] [blame] | 232 | { MP_OBJ_NEW_QSTR(MP_QSTR___del__), (mp_obj_t)&file_obj_close_obj }, |
Paul Sokolovsky | 1d4d9dd | 2014-04-03 20:32:54 +0300 | [diff] [blame] | 233 | { MP_OBJ_NEW_QSTR(MP_QSTR___enter__), (mp_obj_t)&mp_identity_obj }, |
| 234 | { MP_OBJ_NEW_QSTR(MP_QSTR___exit__), (mp_obj_t)&file_obj___exit___obj }, |
Damien George | b92d3e1 | 2014-03-17 14:04:19 +0000 | [diff] [blame] | 235 | }; |
| 236 | |
Damien George | b7572ad | 2014-06-11 15:41:14 +0100 | [diff] [blame] | 237 | STATIC MP_DEFINE_CONST_DICT(rawfile_locals_dict, rawfile_locals_dict_table); |
Damien George | 9b196cd | 2014-03-26 21:47:19 +0000 | [diff] [blame] | 238 | |
Damien George | b7572ad | 2014-06-11 15:41:14 +0100 | [diff] [blame] | 239 | #if MICROPY_PY_IO_FILEIO |
| 240 | STATIC const mp_stream_p_t fileio_stream_p = { |
| 241 | .read = file_obj_read, |
| 242 | .write = file_obj_write, |
Damien George | b7572ad | 2014-06-11 15:41:14 +0100 | [diff] [blame] | 243 | }; |
Damien George | 27e735f | 2014-04-05 23:02:23 +0100 | [diff] [blame] | 244 | |
Damien George | b7572ad | 2014-06-11 15:41:14 +0100 | [diff] [blame] | 245 | const mp_obj_type_t mp_type_fileio = { |
| 246 | { &mp_type_type }, |
| 247 | .name = MP_QSTR_FileIO, |
| 248 | .print = file_obj_print, |
| 249 | .make_new = file_obj_make_new, |
| 250 | .getiter = mp_identity, |
| 251 | .iternext = mp_stream_unbuffered_iter, |
| 252 | .stream_p = &fileio_stream_p, |
| 253 | .locals_dict = (mp_obj_t)&rawfile_locals_dict, |
| 254 | }; |
| 255 | #endif |
| 256 | |
| 257 | STATIC const mp_stream_p_t textio_stream_p = { |
| 258 | .read = file_obj_read, |
| 259 | .write = file_obj_write, |
Damien George | adf0f2a | 2014-07-27 22:38:58 +0100 | [diff] [blame] | 260 | .is_text = true, |
Damien George | 27e735f | 2014-04-05 23:02:23 +0100 | [diff] [blame] | 261 | }; |
| 262 | |
Paul Sokolovsky | 9e29666 | 2014-05-19 20:59:13 +0300 | [diff] [blame] | 263 | const mp_obj_type_t mp_type_textio = { |
Damien George | b92d3e1 | 2014-03-17 14:04:19 +0000 | [diff] [blame] | 264 | { &mp_type_type }, |
Damien George | b7572ad | 2014-06-11 15:41:14 +0100 | [diff] [blame] | 265 | .name = MP_QSTR_TextIOWrapper, |
Damien George | b92d3e1 | 2014-03-17 14:04:19 +0000 | [diff] [blame] | 266 | .print = file_obj_print, |
Damien George | b7572ad | 2014-06-11 15:41:14 +0100 | [diff] [blame] | 267 | .make_new = file_obj_make_new, |
Paul Sokolovsky | 1d4d9dd | 2014-04-03 20:32:54 +0300 | [diff] [blame] | 268 | .getiter = mp_identity, |
| 269 | .iternext = mp_stream_unbuffered_iter, |
Damien George | b7572ad | 2014-06-11 15:41:14 +0100 | [diff] [blame] | 270 | .stream_p = &textio_stream_p, |
| 271 | .locals_dict = (mp_obj_t)&rawfile_locals_dict, |
Damien George | b92d3e1 | 2014-03-17 14:04:19 +0000 | [diff] [blame] | 272 | }; |
| 273 | |
Paul Sokolovsky | 1d4d9dd | 2014-04-03 20:32:54 +0300 | [diff] [blame] | 274 | // Factory function for I/O stream classes |
| 275 | STATIC mp_obj_t pyb_io_open(uint n_args, const mp_obj_t *args) { |
| 276 | // TODO: analyze mode and buffering args and instantiate appropriate type |
Paul Sokolovsky | 9e29666 | 2014-05-19 20:59:13 +0300 | [diff] [blame] | 277 | return file_obj_make_new((mp_obj_t)&mp_type_textio, n_args, 0, args); |
Paul Sokolovsky | 1d4d9dd | 2014-04-03 20:32:54 +0300 | [diff] [blame] | 278 | } |
| 279 | |
| 280 | MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_builtin_open_obj, 1, 2, pyb_io_open); |