1 | /* |
2 | * This file is part of the MicroPython 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 | |
27 | #include "py/mpconfig.h" |
28 | #if MICROPY_VFS && MICROPY_VFS_FAT |
29 | |
30 | #include <stdio.h> |
31 | |
32 | #include "py/runtime.h" |
33 | #include "py/stream.h" |
34 | #include "py/mperrno.h" |
35 | #include "lib/oofatfs/ff.h" |
36 | #include "extmod/vfs_fat.h" |
37 | |
38 | // this table converts from FRESULT to POSIX errno |
39 | const byte fresult_to_errno_table[20] = { |
40 | [FR_OK] = 0, |
41 | [FR_DISK_ERR] = MP_EIO, |
42 | [FR_INT_ERR] = MP_EIO, |
43 | [FR_NOT_READY] = MP_EBUSY, |
44 | [FR_NO_FILE] = MP_ENOENT, |
45 | [FR_NO_PATH] = MP_ENOENT, |
46 | [FR_INVALID_NAME] = MP_EINVAL, |
47 | [FR_DENIED] = MP_EACCES, |
48 | [FR_EXIST] = MP_EEXIST, |
49 | [FR_INVALID_OBJECT] = MP_EINVAL, |
50 | [FR_WRITE_PROTECTED] = MP_EROFS, |
51 | [FR_INVALID_DRIVE] = MP_ENODEV, |
52 | [FR_NOT_ENABLED] = MP_ENODEV, |
53 | [FR_NO_FILESYSTEM] = MP_ENODEV, |
54 | [FR_MKFS_ABORTED] = MP_EIO, |
55 | [FR_TIMEOUT] = MP_EIO, |
56 | [FR_LOCKED] = MP_EIO, |
57 | [FR_NOT_ENOUGH_CORE] = MP_ENOMEM, |
58 | [FR_TOO_MANY_OPEN_FILES] = MP_EMFILE, |
59 | [FR_INVALID_PARAMETER] = MP_EINVAL, |
60 | }; |
61 | |
62 | typedef struct _pyb_file_obj_t { |
63 | mp_obj_base_t base; |
64 | FIL fp; |
65 | } pyb_file_obj_t; |
66 | |
67 | STATIC void file_obj_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { |
68 | (void)kind; |
69 | mp_printf(print, "<io.%s %p>" , mp_obj_get_type_str(self_in), MP_OBJ_TO_PTR(self_in)); |
70 | } |
71 | |
72 | STATIC mp_uint_t file_obj_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) { |
73 | pyb_file_obj_t *self = MP_OBJ_TO_PTR(self_in); |
74 | UINT sz_out; |
75 | FRESULT res = f_read(&self->fp, buf, size, &sz_out); |
76 | if (res != FR_OK) { |
77 | *errcode = fresult_to_errno_table[res]; |
78 | return MP_STREAM_ERROR; |
79 | } |
80 | return sz_out; |
81 | } |
82 | |
83 | STATIC mp_uint_t file_obj_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) { |
84 | pyb_file_obj_t *self = MP_OBJ_TO_PTR(self_in); |
85 | UINT sz_out; |
86 | FRESULT res = f_write(&self->fp, buf, size, &sz_out); |
87 | if (res != FR_OK) { |
88 | *errcode = fresult_to_errno_table[res]; |
89 | return MP_STREAM_ERROR; |
90 | } |
91 | if (sz_out != size) { |
92 | // The FatFS documentation says that this means disk full. |
93 | *errcode = MP_ENOSPC; |
94 | return MP_STREAM_ERROR; |
95 | } |
96 | return sz_out; |
97 | } |
98 | |
99 | |
100 | STATIC mp_obj_t file_obj___exit__(size_t n_args, const mp_obj_t *args) { |
101 | (void)n_args; |
102 | return mp_stream_close(args[0]); |
103 | } |
104 | STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(file_obj___exit___obj, 4, 4, file_obj___exit__); |
105 | |
106 | STATIC mp_uint_t file_obj_ioctl(mp_obj_t o_in, mp_uint_t request, uintptr_t arg, int *errcode) { |
107 | pyb_file_obj_t *self = MP_OBJ_TO_PTR(o_in); |
108 | |
109 | if (request == MP_STREAM_SEEK) { |
110 | struct mp_stream_seek_t *s = (struct mp_stream_seek_t *)(uintptr_t)arg; |
111 | |
112 | switch (s->whence) { |
113 | case 0: // SEEK_SET |
114 | f_lseek(&self->fp, s->offset); |
115 | break; |
116 | |
117 | case 1: // SEEK_CUR |
118 | f_lseek(&self->fp, f_tell(&self->fp) + s->offset); |
119 | break; |
120 | |
121 | case 2: // SEEK_END |
122 | f_lseek(&self->fp, f_size(&self->fp) + s->offset); |
123 | break; |
124 | } |
125 | |
126 | s->offset = f_tell(&self->fp); |
127 | return 0; |
128 | |
129 | } else if (request == MP_STREAM_FLUSH) { |
130 | FRESULT res = f_sync(&self->fp); |
131 | if (res != FR_OK) { |
132 | *errcode = fresult_to_errno_table[res]; |
133 | return MP_STREAM_ERROR; |
134 | } |
135 | return 0; |
136 | |
137 | } else if (request == MP_STREAM_CLOSE) { |
138 | // if fs==NULL then the file is closed and in that case this method is a no-op |
139 | if (self->fp.obj.fs != NULL) { |
140 | FRESULT res = f_close(&self->fp); |
141 | if (res != FR_OK) { |
142 | *errcode = fresult_to_errno_table[res]; |
143 | return MP_STREAM_ERROR; |
144 | } |
145 | } |
146 | return 0; |
147 | |
148 | } else { |
149 | *errcode = MP_EINVAL; |
150 | return MP_STREAM_ERROR; |
151 | } |
152 | } |
153 | |
154 | // Note: encoding is ignored for now; it's also not a valid kwarg for CPython's FileIO, |
155 | // but by adding it here we can use one single mp_arg_t array for open() and FileIO's constructor |
156 | STATIC const mp_arg_t file_open_args[] = { |
157 | { MP_QSTR_file, MP_ARG_OBJ | MP_ARG_REQUIRED, {.u_rom_obj = MP_ROM_NONE} }, |
158 | { MP_QSTR_mode, MP_ARG_OBJ, {.u_obj = MP_OBJ_NEW_QSTR(MP_QSTR_r)} }, |
159 | { MP_QSTR_encoding, MP_ARG_OBJ | MP_ARG_KW_ONLY, {.u_rom_obj = MP_ROM_NONE} }, |
160 | }; |
161 | #define FILE_OPEN_NUM_ARGS MP_ARRAY_SIZE(file_open_args) |
162 | |
163 | STATIC mp_obj_t file_open(fs_user_mount_t *vfs, const mp_obj_type_t *type, mp_arg_val_t *args) { |
164 | int mode = 0; |
165 | const char *mode_s = mp_obj_str_get_str(args[1].u_obj); |
166 | // TODO make sure only one of r, w, x, a, and b, t are specified |
167 | while (*mode_s) { |
168 | switch (*mode_s++) { |
169 | case 'r': |
170 | mode |= FA_READ; |
171 | break; |
172 | case 'w': |
173 | mode |= FA_WRITE | FA_CREATE_ALWAYS; |
174 | break; |
175 | case 'x': |
176 | mode |= FA_WRITE | FA_CREATE_NEW; |
177 | break; |
178 | case 'a': |
179 | mode |= FA_WRITE | FA_OPEN_ALWAYS; |
180 | break; |
181 | case '+': |
182 | mode |= FA_READ | FA_WRITE; |
183 | break; |
184 | #if MICROPY_PY_IO_FILEIO |
185 | case 'b': |
186 | type = &mp_type_vfs_fat_fileio; |
187 | break; |
188 | #endif |
189 | case 't': |
190 | type = &mp_type_vfs_fat_textio; |
191 | break; |
192 | } |
193 | } |
194 | |
195 | pyb_file_obj_t *o = m_new_obj_with_finaliser(pyb_file_obj_t); |
196 | o->base.type = type; |
197 | |
198 | const char *fname = mp_obj_str_get_str(args[0].u_obj); |
199 | assert(vfs != NULL); |
200 | FRESULT res = f_open(&vfs->fatfs, &o->fp, fname, mode); |
201 | if (res != FR_OK) { |
202 | m_del_obj(pyb_file_obj_t, o); |
203 | mp_raise_OSError(fresult_to_errno_table[res]); |
204 | } |
205 | |
206 | // for 'a' mode, we must begin at the end of the file |
207 | if ((mode & FA_OPEN_ALWAYS) != 0) { |
208 | f_lseek(&o->fp, f_size(&o->fp)); |
209 | } |
210 | |
211 | return MP_OBJ_FROM_PTR(o); |
212 | } |
213 | |
214 | STATIC mp_obj_t file_obj_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { |
215 | mp_arg_val_t arg_vals[FILE_OPEN_NUM_ARGS]; |
216 | mp_arg_parse_all_kw_array(n_args, n_kw, args, FILE_OPEN_NUM_ARGS, file_open_args, arg_vals); |
217 | return file_open(NULL, type, arg_vals); |
218 | } |
219 | |
220 | // TODO gc hook to close the file if not already closed |
221 | |
222 | STATIC const mp_rom_map_elem_t vfs_fat_rawfile_locals_dict_table[] = { |
223 | { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, |
224 | { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, |
225 | { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, |
226 | { MP_ROM_QSTR(MP_QSTR_readlines), MP_ROM_PTR(&mp_stream_unbuffered_readlines_obj) }, |
227 | { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, |
228 | { MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) }, |
229 | { MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj) }, |
230 | { MP_ROM_QSTR(MP_QSTR_seek), MP_ROM_PTR(&mp_stream_seek_obj) }, |
231 | { MP_ROM_QSTR(MP_QSTR_tell), MP_ROM_PTR(&mp_stream_tell_obj) }, |
232 | { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_stream_close_obj) }, |
233 | { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj) }, |
234 | { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&file_obj___exit___obj) }, |
235 | }; |
236 | |
237 | STATIC MP_DEFINE_CONST_DICT(vfs_fat_rawfile_locals_dict, vfs_fat_rawfile_locals_dict_table); |
238 | |
239 | #if MICROPY_PY_IO_FILEIO |
240 | STATIC const mp_stream_p_t vfs_fat_fileio_stream_p = { |
241 | .read = file_obj_read, |
242 | .write = file_obj_write, |
243 | .ioctl = file_obj_ioctl, |
244 | }; |
245 | |
246 | const mp_obj_type_t mp_type_vfs_fat_fileio = { |
247 | { &mp_type_type }, |
248 | .name = MP_QSTR_FileIO, |
249 | .print = file_obj_print, |
250 | .make_new = file_obj_make_new, |
251 | .getiter = mp_identity_getiter, |
252 | .iternext = mp_stream_unbuffered_iter, |
253 | .protocol = &vfs_fat_fileio_stream_p, |
254 | .locals_dict = (mp_obj_dict_t *)&vfs_fat_rawfile_locals_dict, |
255 | }; |
256 | #endif |
257 | |
258 | STATIC const mp_stream_p_t vfs_fat_textio_stream_p = { |
259 | .read = file_obj_read, |
260 | .write = file_obj_write, |
261 | .ioctl = file_obj_ioctl, |
262 | .is_text = true, |
263 | }; |
264 | |
265 | const mp_obj_type_t mp_type_vfs_fat_textio = { |
266 | { &mp_type_type }, |
267 | .name = MP_QSTR_TextIOWrapper, |
268 | .print = file_obj_print, |
269 | .make_new = file_obj_make_new, |
270 | .getiter = mp_identity_getiter, |
271 | .iternext = mp_stream_unbuffered_iter, |
272 | .protocol = &vfs_fat_textio_stream_p, |
273 | .locals_dict = (mp_obj_dict_t *)&vfs_fat_rawfile_locals_dict, |
274 | }; |
275 | |
276 | // Factory function for I/O stream classes |
277 | STATIC mp_obj_t fatfs_builtin_open_self(mp_obj_t self_in, mp_obj_t path, mp_obj_t mode) { |
278 | // TODO: analyze buffering args and instantiate appropriate type |
279 | fs_user_mount_t *self = MP_OBJ_TO_PTR(self_in); |
280 | mp_arg_val_t arg_vals[FILE_OPEN_NUM_ARGS]; |
281 | arg_vals[0].u_obj = path; |
282 | arg_vals[1].u_obj = mode; |
283 | arg_vals[2].u_obj = mp_const_none; |
284 | return file_open(self, &mp_type_vfs_fat_textio, arg_vals); |
285 | } |
286 | MP_DEFINE_CONST_FUN_OBJ_3(fat_vfs_open_obj, fatfs_builtin_open_self); |
287 | |
288 | #endif // MICROPY_VFS && MICROPY_VFS_FAT |
289 | |