1 /**
2 * @file lv_fs.c
3 *
4 */
5
6 /*********************
7 * INCLUDES
8 *********************/
9 #include "lv_fs.h"
10 #if LV_USE_FILESYSTEM
11
12 #include "../lv_misc/lv_debug.h"
13 #include "lv_ll.h"
14 #include <string.h>
15 #include "lv_gc.h"
16
17 #if defined(LV_GC_INCLUDE)
18 #include LV_GC_INCLUDE
19 #endif /* LV_ENABLE_GC */
20
21 /*********************
22 * DEFINES
23 *********************/
24
25 /* "free" is used as a function pointer (in lv_fs_drv_t).
26 * We must make sure "free" was not defined to a platform specific
27 * free function, otherwise compilation would fail.
28 */
29 #ifdef free
30 #undef free
31 #endif
32
33 /**********************
34 * TYPEDEFS
35 **********************/
36
37 /**********************
38 * STATIC PROTOTYPES
39 **********************/
40 static const char * lv_fs_get_real_path(const char * path);
41
42 /**********************
43 * STATIC VARIABLES
44 **********************/
45
46 /**********************
47 * MACROS
48 **********************/
49
50 /**********************
51 * GLOBAL FUNCTIONS
52 **********************/
53
54 /**
55 * Initialize the File system interface
56 */
_lv_fs_init(void)57 void _lv_fs_init(void)
58 {
59 _lv_ll_init(&LV_GC_ROOT(_lv_drv_ll), sizeof(lv_fs_drv_t));
60 }
61
62 /**
63 * Test if a drive is ready or not. If the `ready` function was not initialized `true` will be
64 * returned.
65 * @param letter letter of the drive
66 * @return true: drive is ready; false: drive is not ready
67 */
lv_fs_is_ready(char letter)68 bool lv_fs_is_ready(char letter)
69 {
70 lv_fs_drv_t * drv = lv_fs_get_drv(letter);
71
72 if(drv == NULL) return false; /*An unknown driver in not ready*/
73
74 if(drv->ready_cb == NULL) return true; /*Assume the driver is always ready if no handler provided*/
75
76 return drv->ready_cb(drv);
77 }
78
79 /**
80 * Open a file
81 * @param file_p pointer to a lv_fs_file_t variable
82 * @param path path to the file beginning with the driver letter (e.g. S:/folder/file.txt)
83 * @param mode read: FS_MODE_RD, write: FS_MODE_WR, both: FS_MODE_RD | FS_MODE_WR
84 * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
85 */
lv_fs_open(lv_fs_file_t * file_p,const char * path,lv_fs_mode_t mode)86 lv_fs_res_t lv_fs_open(lv_fs_file_t * file_p, const char * path, lv_fs_mode_t mode)
87 {
88 file_p->drv = NULL;
89 file_p->file_d = NULL;
90
91 if(path == NULL) return LV_FS_RES_INV_PARAM;
92
93 char letter = path[0];
94
95 file_p->drv = lv_fs_get_drv(letter);
96
97 if(file_p->drv == NULL) {
98 file_p->file_d = NULL;
99 return LV_FS_RES_NOT_EX;
100 }
101
102 if(file_p->drv->ready_cb != NULL) {
103 if(file_p->drv->ready_cb(file_p->drv) == false) {
104 file_p->drv = NULL;
105 file_p->file_d = NULL;
106 return LV_FS_RES_HW_ERR;
107 }
108 }
109
110 file_p->file_d = lv_mem_alloc(file_p->drv->file_size);
111 LV_ASSERT_MEM(file_p->file_d);
112 if(file_p->file_d == NULL) {
113 file_p->drv = NULL;
114 return LV_FS_RES_OUT_OF_MEM; /* Out of memory */
115 }
116
117 if(file_p->drv->open_cb == NULL) {
118 return LV_FS_RES_NOT_IMP;
119 }
120
121 const char * real_path = lv_fs_get_real_path(path);
122 lv_fs_res_t res = file_p->drv->open_cb(file_p->drv, file_p->file_d, real_path, mode);
123
124 if(res != LV_FS_RES_OK) {
125 lv_mem_free(file_p->file_d);
126 file_p->file_d = NULL;
127 file_p->drv = NULL;
128 }
129
130 return res;
131 }
132
133 /**
134 * Close an already opened file
135 * @param file_p pointer to a lv_fs_file_t variable
136 * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
137 */
lv_fs_close(lv_fs_file_t * file_p)138 lv_fs_res_t lv_fs_close(lv_fs_file_t * file_p)
139 {
140 if(file_p->drv == NULL) {
141 return LV_FS_RES_INV_PARAM;
142 }
143
144 if(file_p->drv->close_cb == NULL) {
145 return LV_FS_RES_NOT_IMP;
146 }
147
148 lv_fs_res_t res = file_p->drv->close_cb(file_p->drv, file_p->file_d);
149
150 lv_mem_free(file_p->file_d); /*Clean up*/
151 file_p->file_d = NULL;
152 file_p->drv = NULL;
153 file_p->file_d = NULL;
154
155 return res;
156 }
157
158 /**
159 * Delete a file
160 * @param path path of the file to delete
161 * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
162 */
lv_fs_remove(const char * path)163 lv_fs_res_t lv_fs_remove(const char * path)
164 {
165 if(path == NULL) return LV_FS_RES_INV_PARAM;
166
167 char letter = path[0];
168
169 lv_fs_drv_t * drv = lv_fs_get_drv(letter);
170 if(drv == NULL) return LV_FS_RES_NOT_EX;
171 if(drv->ready_cb != NULL) {
172 if(drv->ready_cb(drv) == false) return LV_FS_RES_HW_ERR;
173 }
174
175 if(drv->remove_cb == NULL) return LV_FS_RES_NOT_IMP;
176
177 const char * real_path = lv_fs_get_real_path(path);
178 lv_fs_res_t res = drv->remove_cb(drv, real_path);
179
180 return res;
181 }
182
183 /**
184 * Read from a file
185 * @param file_p pointer to a lv_fs_file_t variable
186 * @param buf pointer to a buffer where the read bytes are stored
187 * @param btr Bytes To Read
188 * @param br the number of real read bytes (Bytes Read). NULL if unused.
189 * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
190 */
lv_fs_read(lv_fs_file_t * file_p,void * buf,uint32_t btr,uint32_t * br)191 lv_fs_res_t lv_fs_read(lv_fs_file_t * file_p, void * buf, uint32_t btr, uint32_t * br)
192 {
193 if(br != NULL) *br = 0;
194 if(file_p->drv == NULL) return LV_FS_RES_INV_PARAM;
195 if(file_p->drv->read_cb == NULL) return LV_FS_RES_NOT_IMP;
196
197 uint32_t br_tmp = 0;
198 lv_fs_res_t res = file_p->drv->read_cb(file_p->drv, file_p->file_d, buf, btr, &br_tmp);
199 if(br != NULL) *br = br_tmp;
200
201 return res;
202 }
203
204 /**
205 * Write into a file
206 * @param file_p pointer to a lv_fs_file_t variable
207 * @param buf pointer to a buffer with the bytes to write
208 * @param btr Bytes To Write
209 * @param br the number of real written bytes (Bytes Written). NULL if unused.
210 * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
211 */
lv_fs_write(lv_fs_file_t * file_p,const void * buf,uint32_t btw,uint32_t * bw)212 lv_fs_res_t lv_fs_write(lv_fs_file_t * file_p, const void * buf, uint32_t btw, uint32_t * bw)
213 {
214 if(bw != NULL) *bw = 0;
215
216 if(file_p->drv == NULL) {
217 return LV_FS_RES_INV_PARAM;
218 }
219
220 if(file_p->drv->write_cb == NULL) {
221 return LV_FS_RES_NOT_IMP;
222 }
223
224 uint32_t bw_tmp = 0;
225 lv_fs_res_t res = file_p->drv->write_cb(file_p->drv, file_p->file_d, buf, btw, &bw_tmp);
226 if(bw != NULL) *bw = bw_tmp;
227
228 return res;
229 }
230
231 /**
232 * Set the position of the 'cursor' (read write pointer) in a file
233 * @param file_p pointer to a lv_fs_file_t variable
234 * @param pos the new position expressed in bytes index (0: start of file)
235 * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
236 */
lv_fs_seek(lv_fs_file_t * file_p,uint32_t pos)237 lv_fs_res_t lv_fs_seek(lv_fs_file_t * file_p, uint32_t pos)
238 {
239 if(file_p->drv == NULL) {
240 return LV_FS_RES_INV_PARAM;
241 }
242
243 if(file_p->drv->seek_cb == NULL) {
244 return LV_FS_RES_NOT_IMP;
245 }
246
247 lv_fs_res_t res = file_p->drv->seek_cb(file_p->drv, file_p->file_d, pos);
248
249 return res;
250 }
251
252 /**
253 * Give the position of the read write pointer
254 * @param file_p pointer to a lv_fs_file_t variable
255 * @param pos_p pointer to store the position of the read write pointer
256 * @return LV_FS_RES_OK or any error from 'fs_res_t'
257 */
lv_fs_tell(lv_fs_file_t * file_p,uint32_t * pos)258 lv_fs_res_t lv_fs_tell(lv_fs_file_t * file_p, uint32_t * pos)
259 {
260 if(file_p->drv == NULL) {
261 pos = 0;
262 return LV_FS_RES_INV_PARAM;
263 }
264
265 if(file_p->drv->tell_cb == NULL) {
266 pos = 0;
267 return LV_FS_RES_NOT_IMP;
268 }
269
270 lv_fs_res_t res = file_p->drv->tell_cb(file_p->drv, file_p->file_d, pos);
271
272 return res;
273 }
274
275 /**
276 * Truncate the file size to the current position of the read write pointer
277 * @param file_p pointer to an 'ufs_file_t' variable. (opened with lv_fs_open )
278 * @return LV_FS_RES_OK: no error, the file is read
279 * any error from lv_fs_res_t enum
280 */
lv_fs_trunc(lv_fs_file_t * file_p)281 lv_fs_res_t lv_fs_trunc(lv_fs_file_t * file_p)
282 {
283 if(file_p->drv == NULL) {
284 return LV_FS_RES_INV_PARAM;
285 }
286
287 if(file_p->drv->tell_cb == NULL) {
288 return LV_FS_RES_NOT_IMP;
289 }
290
291 lv_fs_res_t res = file_p->drv->trunc_cb(file_p->drv, file_p->file_d);
292
293 return res;
294 }
295 /**
296 * Give the size of a file bytes
297 * @param file_p pointer to a lv_fs_file_t variable
298 * @param size pointer to a variable to store the size
299 * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
300 */
lv_fs_size(lv_fs_file_t * file_p,uint32_t * size)301 lv_fs_res_t lv_fs_size(lv_fs_file_t * file_p, uint32_t * size)
302 {
303 if(file_p->drv == NULL) {
304 return LV_FS_RES_INV_PARAM;
305 }
306
307 if(file_p->drv->size_cb == NULL) return LV_FS_RES_NOT_IMP;
308
309 if(size == NULL) return LV_FS_RES_INV_PARAM;
310
311 lv_fs_res_t res = file_p->drv->size_cb(file_p->drv, file_p->file_d, size);
312
313 return res;
314 }
315
316 /**
317 * Rename a file
318 * @param oldname path to the file
319 * @param newname path with the new name
320 * @return LV_FS_RES_OK or any error from 'fs_res_t'
321 */
lv_fs_rename(const char * oldname,const char * newname)322 lv_fs_res_t lv_fs_rename(const char * oldname, const char * newname)
323 {
324 if(!oldname || !newname) return LV_FS_RES_INV_PARAM;
325
326 char letter = oldname[0];
327
328 lv_fs_drv_t * drv = lv_fs_get_drv(letter);
329
330 if(!drv) {
331 return LV_FS_RES_NOT_EX;
332 }
333
334 if(drv->ready_cb != NULL) {
335 if(drv->ready_cb(drv) == false) {
336 return LV_FS_RES_HW_ERR;
337 }
338 }
339
340 if(drv->rename_cb == NULL) return LV_FS_RES_NOT_IMP;
341
342 const char * old_real = lv_fs_get_real_path(oldname);
343 const char * new_real = lv_fs_get_real_path(newname);
344
345 lv_fs_res_t res = drv->rename_cb(drv, old_real, new_real);
346
347 return res;
348 }
349
350 /**
351 * Initialize a 'fs_read_dir_t' variable for directory reading
352 * @param rddir_p pointer to a 'fs_read_dir_t' variable
353 * @param path path to a directory
354 * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
355 */
lv_fs_dir_open(lv_fs_dir_t * rddir_p,const char * path)356 lv_fs_res_t lv_fs_dir_open(lv_fs_dir_t * rddir_p, const char * path)
357 {
358 if(path == NULL) return LV_FS_RES_INV_PARAM;
359
360 char letter = path[0];
361
362 rddir_p->drv = lv_fs_get_drv(letter);
363
364 if(rddir_p->drv == NULL) {
365 rddir_p->dir_d = NULL;
366 return LV_FS_RES_NOT_EX;
367 }
368
369 rddir_p->dir_d = lv_mem_alloc(rddir_p->drv->rddir_size);
370 LV_ASSERT_MEM(rddir_p->dir_d);
371 if(rddir_p->dir_d == NULL) {
372 rddir_p->dir_d = NULL;
373 return LV_FS_RES_OUT_OF_MEM; /* Out of memory */
374 }
375
376 if(rddir_p->drv->dir_open_cb == NULL) {
377 return LV_FS_RES_NOT_IMP;
378 }
379
380 const char * real_path = lv_fs_get_real_path(path);
381
382 lv_fs_res_t res = rddir_p->drv->dir_open_cb(rddir_p->drv, rddir_p->dir_d, real_path);
383
384 return res;
385 }
386
387 /**
388 * Read the next filename form a directory.
389 * The name of the directories will begin with '/'
390 * @param rddir_p pointer to an initialized 'fs_read_dir_t' variable
391 * @param fn pointer to a buffer to store the filename
392 * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
393 */
lv_fs_dir_read(lv_fs_dir_t * rddir_p,char * fn)394 lv_fs_res_t lv_fs_dir_read(lv_fs_dir_t * rddir_p, char * fn)
395 {
396 if(rddir_p->drv == NULL || rddir_p->dir_d == NULL) {
397 fn[0] = '\0';
398 return LV_FS_RES_INV_PARAM;
399 }
400
401 if(rddir_p->drv->dir_read_cb == NULL) {
402 return LV_FS_RES_NOT_IMP;
403 }
404
405 lv_fs_res_t res = rddir_p->drv->dir_read_cb(rddir_p->drv, rddir_p->dir_d, fn);
406
407 return res;
408 }
409
410 /**
411 * Close the directory reading
412 * @param rddir_p pointer to an initialized 'fs_read_dir_t' variable
413 * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
414 */
lv_fs_dir_close(lv_fs_dir_t * rddir_p)415 lv_fs_res_t lv_fs_dir_close(lv_fs_dir_t * rddir_p)
416 {
417 if(rddir_p->drv == NULL || rddir_p->dir_d == NULL) {
418 return LV_FS_RES_INV_PARAM;
419 }
420
421 lv_fs_res_t res;
422
423 if(rddir_p->drv->dir_close_cb == NULL) {
424 res = LV_FS_RES_NOT_IMP;
425 }
426 else {
427 res = rddir_p->drv->dir_close_cb(rddir_p->drv, rddir_p->dir_d);
428 }
429
430 lv_mem_free(rddir_p->dir_d); /*Clean up*/
431 rddir_p->dir_d = NULL;
432 rddir_p->drv = NULL;
433 rddir_p->dir_d = NULL;
434
435 return res;
436 }
437
438 /**
439 * Get the free and total size of a driver in kB
440 * @param letter the driver letter
441 * @param total_p pointer to store the total size [kB]
442 * @param free_p pointer to store the free size_cb [kB]
443 * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
444 */
lv_fs_free_space(char letter,uint32_t * total_p,uint32_t * free_p)445 lv_fs_res_t lv_fs_free_space(char letter, uint32_t * total_p, uint32_t * free_p)
446 {
447 lv_fs_drv_t * drv = lv_fs_get_drv(letter);
448
449 if(drv == NULL) {
450 return LV_FS_RES_INV_PARAM;
451 }
452
453 lv_fs_res_t res;
454
455 if(drv->free_space_cb == NULL) {
456 res = LV_FS_RES_NOT_IMP;
457 }
458 else {
459 uint32_t total_tmp = 0;
460 uint32_t free_tmp = 0;
461 res = drv->free_space_cb(drv, &total_tmp, &free_tmp);
462
463 if(total_p != NULL) *total_p = total_tmp;
464 if(free_p != NULL) *free_p = free_tmp;
465 }
466
467 return res;
468 }
469
470 /**
471 * Initialize a file system driver with default values.
472 * It is used to surly have known values in the fields ant not memory junk.
473 * After it you can set the fields.
474 * @param drv pointer to driver variable to initialize
475 */
lv_fs_drv_init(lv_fs_drv_t * drv)476 void lv_fs_drv_init(lv_fs_drv_t * drv)
477 {
478 _lv_memset_00(drv, sizeof(lv_fs_drv_t));
479 }
480
481 /**
482 * Add a new drive
483 * @param drv_p pointer to an lv_fs_drv_t structure which is inited with the
484 * corresponding function pointers. The data will be copied so the variable can be local.
485 */
lv_fs_drv_register(lv_fs_drv_t * drv_p)486 void lv_fs_drv_register(lv_fs_drv_t * drv_p)
487 {
488 /*Save the new driver*/
489 lv_fs_drv_t * new_drv;
490 new_drv = _lv_ll_ins_head(&LV_GC_ROOT(_lv_drv_ll));
491 LV_ASSERT_MEM(new_drv);
492 if(new_drv == NULL) return;
493
494 _lv_memcpy(new_drv, drv_p, sizeof(lv_fs_drv_t));
495 }
496
497 /**
498 * Give a pointer to a driver from its letter
499 * @param letter the driver letter
500 * @return pointer to a driver or NULL if not found
501 */
lv_fs_get_drv(char letter)502 lv_fs_drv_t * lv_fs_get_drv(char letter)
503 {
504 lv_fs_drv_t * drv;
505
506 _LV_LL_READ(LV_GC_ROOT(_lv_drv_ll), drv) {
507 if(drv->letter == letter) {
508 return drv;
509 }
510 }
511
512 return NULL;
513 }
514 /**
515 * Fill a buffer with the letters of existing drivers
516 * @param buf buffer to store the letters ('\0' added after the last letter)
517 * @return the buffer
518 */
lv_fs_get_letters(char * buf)519 char * lv_fs_get_letters(char * buf)
520 {
521 lv_fs_drv_t * drv;
522 uint8_t i = 0;
523
524 _LV_LL_READ(LV_GC_ROOT(_lv_drv_ll), drv) {
525 buf[i] = drv->letter;
526 i++;
527 }
528
529 buf[i] = '\0';
530
531 return buf;
532 }
533
534 /**
535 * Return with the extension of the filename
536 * @param fn string with a filename
537 * @return pointer to the beginning extension or empty string if no extension
538 */
lv_fs_get_ext(const char * fn)539 const char * lv_fs_get_ext(const char * fn)
540 {
541 size_t i;
542 for(i = strlen(fn); i > 0; i--) {
543 if(fn[i] == '.') {
544 return &fn[i + 1];
545 }
546 else if(fn[i] == '/' || fn[i] == '\\') {
547 return ""; /*No extension if a '\' or '/' found*/
548 }
549 }
550
551 return ""; /*Empty string if no '.' in the file name. */
552 }
553
554 /**
555 * Step up one level
556 * @param path pointer to a file name
557 * @return the truncated file name
558 */
lv_fs_up(char * path)559 char * lv_fs_up(char * path)
560 {
561 size_t len = strlen(path);
562 if(len == 0) return path;
563
564 len--; /*Go before the trailing '\0'*/
565
566 /*Ignore trailing '/' or '\'*/
567 while(path[len] == '/' || path[len] == '\\') {
568 path[len] = '\0';
569 if(len > 0)
570 len--;
571 else
572 return path;
573 }
574
575 size_t i;
576 for(i = len; i > 0; i--) {
577 if(path[i] == '/' || path[i] == '\\') break;
578 }
579
580 if(i > 0) path[i] = '\0';
581
582 return path;
583 }
584
585 /**
586 * Get the last element of a path (e.g. U:/folder/file -> file)
587 * @param path a character sting with the path to search in
588 * @return pointer to the beginning of the last element in the path
589 */
lv_fs_get_last(const char * path)590 const char * lv_fs_get_last(const char * path)
591 {
592 size_t len = strlen(path);
593 if(len == 0) return path;
594
595 len--; /*Go before the trailing '\0'*/
596
597 /*Ignore trailing '/' or '\'*/
598 while(path[len] == '/' || path[len] == '\\') {
599 if(len > 0)
600 len--;
601 else
602 return path;
603 }
604
605 size_t i;
606 for(i = len; i > 0; i--) {
607 if(path[i] == '/' || path[i] == '\\') break;
608 }
609
610 /*No '/' or '\' in the path so return with path itself*/
611 if(i == 0) return path;
612
613 return &path[i + 1];
614 }
615 /**********************
616 * STATIC FUNCTIONS
617 **********************/
618
619 /**
620 * Leave the driver letters and / or \ letters from beginning of the path
621 * @param path path string (E.g. S:/folder/file.txt)
622 * @return pointer to the beginning of the real path (E.g. folder/file.txt)
623 */
lv_fs_get_real_path(const char * path)624 static const char * lv_fs_get_real_path(const char * path)
625 {
626 /* Example path: "S:/folder/file.txt"
627 * Leave the letter and the : / \ characters*/
628
629 path++; /*Ignore the driver letter*/
630
631 while(*path != '\0') {
632 if(*path == ':' || *path == '\\' || *path == '/') {
633 path++;
634 }
635 else {
636 break;
637 }
638 }
639
640 return path;
641 }
642
643 #endif /*LV_USE_FILESYSTEM*/
644