blob: 36bdb5dbdee7ad278440e4def664338531ff12ba [file] [log] [blame]
Paul Sokolovskye9be6a32016-02-13 22:51:21 +02001/*
2 * This file is part of the MicroPython project, http://micropython.org/
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (c) 2014 Damien P. George
7 * Copyright (c) 2016 Paul Sokolovsky
8 *
9 * Permission is hereby granted, free of charge, to any person obtaining a copy
10 * of this software and associated documentation files (the "Software"), to deal
11 * in the Software without restriction, including without limitation the rights
12 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 * copies of the Software, and to permit persons to whom the Software is
14 * furnished to do so, subject to the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included in
17 * all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 * THE SOFTWARE.
26 */
27
28#include "py/mpconfig.h"
29#if MICROPY_VFS_FAT
30
Damien Georgef5f4cda2016-06-01 17:00:28 +010031#if !MICROPY_FATFS_OO
32#error "with MICROPY_VFS_FAT enabled, must also enable MICROPY_FATFS_OO"
33#endif
34
Paul Sokolovskycac6c972016-05-29 18:23:59 +030035#include <string.h>
Paul Sokolovskye9be6a32016-02-13 22:51:21 +020036#include "py/nlr.h"
37#include "py/runtime.h"
Alex Marchd02f3a52016-09-28 14:51:35 +010038#include "py/mperrno.h"
Damien Georgef5f4cda2016-06-01 17:00:28 +010039#include "lib/oofatfs/ff.h"
Paul Sokolovsky6b0c8822016-02-15 00:16:46 +020040#include "extmod/vfs_fat_file.h"
Damien Georgef9dc6442016-05-20 12:43:32 +010041#include "extmod/fsusermount.h"
Robert HHee009d72016-05-30 21:09:20 +020042#include "timeutils.h"
Paul Sokolovskye9be6a32016-02-13 22:51:21 +020043
44#define mp_obj_fat_vfs_t fs_user_mount_t
45
46STATIC mp_obj_t fat_vfs_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
47 mp_arg_check_num(n_args, n_kw, 2, 2, false);
48 mp_obj_fat_vfs_t *vfs = fatfs_mount_mkfs(n_args, args, (mp_map_t*)&mp_const_empty_map, false);
49 vfs->base.type = type;
Paul Sokolovsky1bb15ca2016-02-14 16:21:27 +020050 return MP_OBJ_FROM_PTR(vfs);
Paul Sokolovskye9be6a32016-02-13 22:51:21 +020051}
52
53STATIC mp_obj_t fat_vfs_mkfs(mp_obj_t bdev_in) {
54 mp_obj_t args[] = {bdev_in, MP_OBJ_NEW_QSTR(MP_QSTR_mkfs)};
55 fatfs_mount_mkfs(2, args, (mp_map_t*)&mp_const_empty_map, true);
56 return mp_const_none;
57}
58STATIC MP_DEFINE_CONST_FUN_OBJ_1(fat_vfs_mkfs_fun_obj, fat_vfs_mkfs);
59STATIC MP_DEFINE_CONST_STATICMETHOD_OBJ(fat_vfs_mkfs_obj, MP_ROM_PTR(&fat_vfs_mkfs_fun_obj));
60
Damien Georgef5f4cda2016-06-01 17:00:28 +010061STATIC MP_DEFINE_CONST_FUN_OBJ_KW(fat_vfs_open_obj, 2, fatfs_builtin_open_self);
Paul Sokolovskye9be6a32016-02-13 22:51:21 +020062
Paul Sokolovskycd6d1892016-02-28 17:17:24 +020063STATIC mp_obj_t fat_vfs_listdir_func(size_t n_args, const mp_obj_t *args) {
Damien Georgef5f4cda2016-06-01 17:00:28 +010064 mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(args[0]);
Paul Sokolovskycd6d1892016-02-28 17:17:24 +020065 bool is_str_type = true;
66 const char *path;
67 if (n_args == 2) {
68 if (mp_obj_get_type(args[1]) == &mp_type_bytes) {
69 is_str_type = false;
70 }
71 path = mp_obj_str_get_str(args[1]);
72 } else {
73 path = "";
74 }
75
Damien Georgef5f4cda2016-06-01 17:00:28 +010076 return fat_vfs_listdir2(self, path, is_str_type);
Paul Sokolovskycd6d1892016-02-28 17:17:24 +020077}
78STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fat_vfs_listdir_obj, 1, 2, fat_vfs_listdir_func);
79
Damien Georgef5f4cda2016-06-01 17:00:28 +010080STATIC mp_obj_t fat_vfs_remove_internal(mp_obj_t vfs_in, mp_obj_t path_in, mp_int_t attr) {
81 mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(vfs_in);
Paul Sokolovsky19749db2016-02-28 20:30:07 +020082 const char *path = mp_obj_str_get_str(path_in);
Alex Marchd02f3a52016-09-28 14:51:35 +010083
84 FILINFO fno;
Damien Georgef5f4cda2016-06-01 17:00:28 +010085 FRESULT res = f_stat(&self->fatfs, path, &fno);
Alex Marchd02f3a52016-09-28 14:51:35 +010086
87 if (res != FR_OK) {
Damien George620c4c32016-10-07 13:44:55 +110088 mp_raise_OSError(fresult_to_errno_table[res]);
Paul Sokolovsky19749db2016-02-28 20:30:07 +020089 }
Alex Marchd02f3a52016-09-28 14:51:35 +010090
91 // check if path is a file or directory
92 if ((fno.fattrib & AM_DIR) == attr) {
Damien Georgef5f4cda2016-06-01 17:00:28 +010093 res = f_unlink(&self->fatfs, path);
Alex Marchd02f3a52016-09-28 14:51:35 +010094
95 if (res != FR_OK) {
96 mp_raise_OSError(fresult_to_errno_table[res]);
97 }
98 return mp_const_none;
99 } else {
100 mp_raise_OSError(attr ? MP_ENOTDIR : MP_EISDIR);
101 }
102}
103
104STATIC mp_obj_t fat_vfs_remove(mp_obj_t vfs_in, mp_obj_t path_in) {
Damien Georgef5f4cda2016-06-01 17:00:28 +0100105 return fat_vfs_remove_internal(vfs_in, path_in, 0); // 0 == file attribute
Paul Sokolovsky19749db2016-02-28 20:30:07 +0200106}
107STATIC MP_DEFINE_CONST_FUN_OBJ_2(fat_vfs_remove_obj, fat_vfs_remove);
108
Paul Sokolovsky0a6f5992016-07-16 03:46:42 +0300109STATIC mp_obj_t fat_vfs_rmdir(mp_obj_t vfs_in, mp_obj_t path_in) {
Damien Georgef5f4cda2016-06-01 17:00:28 +0100110 return fat_vfs_remove_internal(vfs_in, path_in, AM_DIR);
Paul Sokolovsky0a6f5992016-07-16 03:46:42 +0300111}
112STATIC MP_DEFINE_CONST_FUN_OBJ_2(fat_vfs_rmdir_obj, fat_vfs_rmdir);
113
Paul Sokolovskye0821832016-02-29 01:22:38 +0200114STATIC mp_obj_t fat_vfs_rename(mp_obj_t vfs_in, mp_obj_t path_in, mp_obj_t path_out) {
Damien Georgef5f4cda2016-06-01 17:00:28 +0100115 mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(vfs_in);
Paul Sokolovskye0821832016-02-29 01:22:38 +0200116 const char *old_path = mp_obj_str_get_str(path_in);
117 const char *new_path = mp_obj_str_get_str(path_out);
Damien Georgef5f4cda2016-06-01 17:00:28 +0100118 FRESULT res = f_rename(&self->fatfs, old_path, new_path);
Damien Georgeb7df3e52016-12-02 15:06:09 +1100119 if (res == FR_EXIST) {
120 // if new_path exists then try removing it (but only if it's a file)
Damien Georgef5f4cda2016-06-01 17:00:28 +0100121 fat_vfs_remove_internal(vfs_in, path_out, 0); // 0 == file attribute
Damien Georgeb7df3e52016-12-02 15:06:09 +1100122 // try to rename again
Damien Georgef5f4cda2016-06-01 17:00:28 +0100123 res = f_rename(&self->fatfs, old_path, new_path);
Damien Georgeb7df3e52016-12-02 15:06:09 +1100124 }
Robert HH7c004e72016-05-27 22:28:00 +0200125 if (res == FR_OK) {
126 return mp_const_none;
127 } else {
Damien George620c4c32016-10-07 13:44:55 +1100128 mp_raise_OSError(fresult_to_errno_table[res]);
Paul Sokolovskye0821832016-02-29 01:22:38 +0200129 }
130
131}
132STATIC MP_DEFINE_CONST_FUN_OBJ_3(fat_vfs_rename_obj, fat_vfs_rename);
133
Paul Sokolovskybbe832a2016-02-29 00:02:50 +0200134STATIC mp_obj_t fat_vfs_mkdir(mp_obj_t vfs_in, mp_obj_t path_o) {
Damien Georgef5f4cda2016-06-01 17:00:28 +0100135 mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(vfs_in);
Paul Sokolovskybbe832a2016-02-29 00:02:50 +0200136 const char *path = mp_obj_str_get_str(path_o);
Damien Georgef5f4cda2016-06-01 17:00:28 +0100137 FRESULT res = f_mkdir(&self->fatfs, path);
Robert HH7c004e72016-05-27 22:28:00 +0200138 if (res == FR_OK) {
139 return mp_const_none;
140 } else {
Damien George620c4c32016-10-07 13:44:55 +1100141 mp_raise_OSError(fresult_to_errno_table[res]);
Paul Sokolovskybbe832a2016-02-29 00:02:50 +0200142 }
143}
144STATIC MP_DEFINE_CONST_FUN_OBJ_2(fat_vfs_mkdir_obj, fat_vfs_mkdir);
145
Paul Sokolovskyf12146c2016-05-29 18:17:00 +0300146/// Change current directory.
Paul Sokolovskyee5e3f62016-05-29 18:52:41 +0300147STATIC mp_obj_t fat_vfs_chdir(mp_obj_t vfs_in, mp_obj_t path_in) {
Damien Georgef5f4cda2016-06-01 17:00:28 +0100148 mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(vfs_in);
Paul Sokolovskyf12146c2016-05-29 18:17:00 +0300149 const char *path;
150 path = mp_obj_str_get_str(path_in);
151
Damien Georgef5f4cda2016-06-01 17:00:28 +0100152 FRESULT res = f_chdir(&self->fatfs, path);
Paul Sokolovskyf12146c2016-05-29 18:17:00 +0300153
154 if (res != FR_OK) {
Damien George620c4c32016-10-07 13:44:55 +1100155 mp_raise_OSError(fresult_to_errno_table[res]);
Paul Sokolovskyf12146c2016-05-29 18:17:00 +0300156 }
157
158 return mp_const_none;
159}
Paul Sokolovskyee5e3f62016-05-29 18:52:41 +0300160STATIC MP_DEFINE_CONST_FUN_OBJ_2(fat_vfs_chdir_obj, fat_vfs_chdir);
Paul Sokolovskyf12146c2016-05-29 18:17:00 +0300161
Paul Sokolovskycac6c972016-05-29 18:23:59 +0300162/// Get the current directory.
Paul Sokolovskyee5e3f62016-05-29 18:52:41 +0300163STATIC mp_obj_t fat_vfs_getcwd(mp_obj_t vfs_in) {
Damien Georgef5f4cda2016-06-01 17:00:28 +0100164 mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(vfs_in);
Paul Sokolovskycac6c972016-05-29 18:23:59 +0300165 char buf[MICROPY_ALLOC_PATH_MAX + 1];
Damien Georgef5f4cda2016-06-01 17:00:28 +0100166 memcpy(buf, self->str, self->len);
167 FRESULT res = f_getcwd(&self->fatfs, buf + self->len, sizeof(buf) - self->len);
Paul Sokolovskycac6c972016-05-29 18:23:59 +0300168 if (res != FR_OK) {
Damien George620c4c32016-10-07 13:44:55 +1100169 mp_raise_OSError(fresult_to_errno_table[res]);
Paul Sokolovskycac6c972016-05-29 18:23:59 +0300170 }
Damien Georgef5f4cda2016-06-01 17:00:28 +0100171 // remove trailing / if in root dir, because we prepended the mount point
172 size_t l = strlen(buf);
173 if (res == FR_OK && buf[l - 1] == '/') {
174 buf[l - 1] = 0;
175 }
Paul Sokolovskycac6c972016-05-29 18:23:59 +0300176 return mp_obj_new_str(buf, strlen(buf), false);
177}
Paul Sokolovskyee5e3f62016-05-29 18:52:41 +0300178STATIC MP_DEFINE_CONST_FUN_OBJ_1(fat_vfs_getcwd_obj, fat_vfs_getcwd);
Paul Sokolovskycac6c972016-05-29 18:23:59 +0300179
Robert HHee009d72016-05-30 21:09:20 +0200180// Checks for path equality, ignoring trailing slashes:
181// path_equal(/, /) -> true
182// second argument must be in canonical form (meaning no trailing slash, unless it's just /)
183STATIC bool path_equal(const char *path, const char *path_canonical) {
184 while (*path_canonical != '\0' && *path == *path_canonical) {
185 ++path;
186 ++path_canonical;
187 }
188 if (*path_canonical != '\0') {
189 return false;
190 }
191 while (*path == '/') {
192 ++path;
193 }
194 return *path == '\0';
195}
196
197/// \function stat(path)
198/// Get the status of a file or directory.
199STATIC mp_obj_t fat_vfs_stat(mp_obj_t vfs_in, mp_obj_t path_in) {
Damien Georgef5f4cda2016-06-01 17:00:28 +0100200 mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(vfs_in);
Robert HHee009d72016-05-30 21:09:20 +0200201 const char *path = mp_obj_str_get_str(path_in);
202
203 FILINFO fno;
Robert HHee009d72016-05-30 21:09:20 +0200204 FRESULT res;
205
206 if (path_equal(path, "/")) {
207 // stat root directory
208 fno.fsize = 0;
Robert HH23067a12016-06-16 18:17:59 +0200209 fno.fdate = 0x2821; // Jan 1, 2000
Robert HHee009d72016-05-30 21:09:20 +0200210 fno.ftime = 0;
211 fno.fattrib = AM_DIR;
212 } else {
213 res = FR_NO_PATH;
214 for (size_t i = 0; i < MP_ARRAY_SIZE(MP_STATE_PORT(fs_user_mount)); ++i) {
215 fs_user_mount_t *vfs = MP_STATE_PORT(fs_user_mount)[i];
216 if (vfs != NULL && path_equal(path, vfs->str)) {
217 // stat mounted device directory
218 fno.fsize = 0;
Robert HH23067a12016-06-16 18:17:59 +0200219 fno.fdate = 0x2821; // Jan 1, 2000
Robert HHee009d72016-05-30 21:09:20 +0200220 fno.ftime = 0;
221 fno.fattrib = AM_DIR;
222 res = FR_OK;
223 }
224 }
225 if (res == FR_NO_PATH) {
226 // stat normal file
Damien Georgef5f4cda2016-06-01 17:00:28 +0100227 res = f_stat(&self->fatfs, path, &fno);
Robert HHee009d72016-05-30 21:09:20 +0200228 }
229 if (res != FR_OK) {
Damien George620c4c32016-10-07 13:44:55 +1100230 mp_raise_OSError(fresult_to_errno_table[res]);
Robert HHee009d72016-05-30 21:09:20 +0200231 }
232 }
233
234 mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL));
235 mp_int_t mode = 0;
236 if (fno.fattrib & AM_DIR) {
237 mode |= 0x4000; // stat.S_IFDIR
238 } else {
239 mode |= 0x8000; // stat.S_IFREG
240 }
241 mp_int_t seconds = timeutils_seconds_since_2000(
242 1980 + ((fno.fdate >> 9) & 0x7f),
243 (fno.fdate >> 5) & 0x0f,
244 fno.fdate & 0x1f,
245 (fno.ftime >> 11) & 0x1f,
246 (fno.ftime >> 5) & 0x3f,
247 2 * (fno.ftime & 0x1f)
248 );
249 t->items[0] = MP_OBJ_NEW_SMALL_INT(mode); // st_mode
250 t->items[1] = MP_OBJ_NEW_SMALL_INT(0); // st_ino
251 t->items[2] = MP_OBJ_NEW_SMALL_INT(0); // st_dev
252 t->items[3] = MP_OBJ_NEW_SMALL_INT(0); // st_nlink
253 t->items[4] = MP_OBJ_NEW_SMALL_INT(0); // st_uid
254 t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // st_gid
255 t->items[6] = MP_OBJ_NEW_SMALL_INT(fno.fsize); // st_size
256 t->items[7] = MP_OBJ_NEW_SMALL_INT(seconds); // st_atime
257 t->items[8] = MP_OBJ_NEW_SMALL_INT(seconds); // st_mtime
258 t->items[9] = MP_OBJ_NEW_SMALL_INT(seconds); // st_ctime
259
260 return MP_OBJ_FROM_PTR(t);
261}
262STATIC MP_DEFINE_CONST_FUN_OBJ_2(fat_vfs_stat_obj, fat_vfs_stat);
263
Alex Marchdcf14c12016-09-12 18:13:44 +0100264// Get the status of a VFS.
265STATIC mp_obj_t fat_vfs_statvfs(mp_obj_t vfs_in, mp_obj_t path_in) {
Damien Georgef5f4cda2016-06-01 17:00:28 +0100266 mp_obj_fat_vfs_t *self = MP_OBJ_TO_PTR(vfs_in);
267 (void)path_in;
Alex Marchdcf14c12016-09-12 18:13:44 +0100268
Alex Marchdcf14c12016-09-12 18:13:44 +0100269 DWORD nclst;
Damien Georgef5f4cda2016-06-01 17:00:28 +0100270 FATFS *fatfs = &self->fatfs;
271 FRESULT res = f_getfree(fatfs, &nclst);
Alex Marchdcf14c12016-09-12 18:13:44 +0100272 if (FR_OK != res) {
Damien George620c4c32016-10-07 13:44:55 +1100273 mp_raise_OSError(fresult_to_errno_table[res]);
Alex Marchdcf14c12016-09-12 18:13:44 +0100274 }
275
276 mp_obj_tuple_t *t = MP_OBJ_TO_PTR(mp_obj_new_tuple(10, NULL));
277
278 t->items[0] = MP_OBJ_NEW_SMALL_INT(fatfs->csize * fatfs->ssize); // f_bsize
279 t->items[1] = t->items[0]; // f_frsize
280 t->items[2] = MP_OBJ_NEW_SMALL_INT((fatfs->n_fatent - 2) * fatfs->csize); // f_blocks
281 t->items[3] = MP_OBJ_NEW_SMALL_INT(nclst); // f_bfree
282 t->items[4] = t->items[3]; // f_bavail
283 t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // f_files
284 t->items[6] = MP_OBJ_NEW_SMALL_INT(0); // f_ffree
285 t->items[7] = MP_OBJ_NEW_SMALL_INT(0); // f_favail
286 t->items[8] = MP_OBJ_NEW_SMALL_INT(0); // f_flags
287 t->items[9] = MP_OBJ_NEW_SMALL_INT(_MAX_LFN); // f_namemax
288
289 return MP_OBJ_FROM_PTR(t);
290}
291STATIC MP_DEFINE_CONST_FUN_OBJ_2(fat_vfs_statvfs_obj, fat_vfs_statvfs);
292
Radomir Dopieralskid29ca282016-08-22 16:10:34 +0200293// Unmount the filesystem
294STATIC mp_obj_t fat_vfs_umount(mp_obj_t vfs_in) {
295 fatfs_umount(((fs_user_mount_t *)vfs_in)->readblocks[1]);
296 return mp_const_none;
297}
298STATIC MP_DEFINE_CONST_FUN_OBJ_1(fat_vfs_umount_obj, fat_vfs_umount);
299
Paul Sokolovskye9be6a32016-02-13 22:51:21 +0200300STATIC const mp_rom_map_elem_t fat_vfs_locals_dict_table[] = {
301 { MP_ROM_QSTR(MP_QSTR_mkfs), MP_ROM_PTR(&fat_vfs_mkfs_obj) },
302 { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&fat_vfs_open_obj) },
Paul Sokolovskycd6d1892016-02-28 17:17:24 +0200303 { MP_ROM_QSTR(MP_QSTR_listdir), MP_ROM_PTR(&fat_vfs_listdir_obj) },
Paul Sokolovskybbe832a2016-02-29 00:02:50 +0200304 { MP_ROM_QSTR(MP_QSTR_mkdir), MP_ROM_PTR(&fat_vfs_mkdir_obj) },
Paul Sokolovsky0a6f5992016-07-16 03:46:42 +0300305 { MP_ROM_QSTR(MP_QSTR_rmdir), MP_ROM_PTR(&fat_vfs_rmdir_obj) },
Paul Sokolovskyf12146c2016-05-29 18:17:00 +0300306 { MP_ROM_QSTR(MP_QSTR_chdir), MP_ROM_PTR(&fat_vfs_chdir_obj) },
Paul Sokolovskycac6c972016-05-29 18:23:59 +0300307 { MP_ROM_QSTR(MP_QSTR_getcwd), MP_ROM_PTR(&fat_vfs_getcwd_obj) },
Paul Sokolovsky19749db2016-02-28 20:30:07 +0200308 { MP_ROM_QSTR(MP_QSTR_remove), MP_ROM_PTR(&fat_vfs_remove_obj) },
Paul Sokolovskye0821832016-02-29 01:22:38 +0200309 { MP_ROM_QSTR(MP_QSTR_rename), MP_ROM_PTR(&fat_vfs_rename_obj) },
Robert HHee009d72016-05-30 21:09:20 +0200310 { MP_ROM_QSTR(MP_QSTR_stat), MP_ROM_PTR(&fat_vfs_stat_obj) },
Alex Marchdcf14c12016-09-12 18:13:44 +0100311 { MP_ROM_QSTR(MP_QSTR_statvfs), MP_ROM_PTR(&fat_vfs_statvfs_obj) },
Radomir Dopieralskid29ca282016-08-22 16:10:34 +0200312 { MP_ROM_QSTR(MP_QSTR_umount), MP_ROM_PTR(&fat_vfs_umount_obj) },
Paul Sokolovskye9be6a32016-02-13 22:51:21 +0200313};
314STATIC MP_DEFINE_CONST_DICT(fat_vfs_locals_dict, fat_vfs_locals_dict_table);
315
316const mp_obj_type_t mp_fat_vfs_type = {
317 { &mp_type_type },
318 .name = MP_QSTR_VfsFat,
319 .make_new = fat_vfs_make_new,
320 .locals_dict = (mp_obj_dict_t*)&fat_vfs_locals_dict,
321};
322
323#endif // MICROPY_VFS_FAT