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_ll.h"
13 #include <string.h>
14 #include "lv_gc.h"
15
16 #if defined(LV_GC_INCLUDE)
17 #include LV_GC_INCLUDE
18 #endif /* LV_ENABLE_GC */
19
20 /*********************
21 * DEFINES
22 *********************/
23
24 /* "free" is used as a function pointer (in lv_fs_drv_t).
25 * We must make sure "free" was not defined to a platform specific
26 * free function, otherwise compilation would fail.
27 */
28 #ifdef free
29 #undef free
30 #endif
31
32 /**********************
33 * TYPEDEFS
34 **********************/
35
36 /**********************
37 * STATIC PROTOTYPES
38 **********************/
39 static const char * lv_fs_get_real_path(const char * path);
40
41 /**********************
42 * STATIC VARIABLES
43 **********************/
44
45 /**********************
46 * MACROS
47 **********************/
48
49 /**********************
50 * GLOBAL FUNCTIONS
51 **********************/
52
53 /**
54 * Initialize the File system interface
55 */
lv_fs_init(void)56 void lv_fs_init(void)
57 {
58 lv_ll_init(&LV_GC_ROOT(_lv_drv_ll), sizeof(lv_fs_drv_t));
59 }
60
61 /**
62 * Test if a drive is rady or not. If the `ready` function was not initialized `true` will be
63 * returned.
64 * @param letter letter of the drive
65 * @return true: drive is ready; false: drive is not ready
66 */
lv_fs_is_ready(char letter)67 bool lv_fs_is_ready(char letter)
68 {
69 lv_fs_drv_t * drv = lv_fs_get_drv(letter);
70
71 if(drv == NULL) return false; /*An unknown driver in not ready*/
72
73 if(drv->ready_cb == NULL) return true; /*Assume the driver is always ready if no handler provided*/
74
75 return drv->ready_cb(drv);
76 }
77
78 /**
79 * Open a file
80 * @param file_p pointer to a lv_fs_file_t variable
81 * @param path path to the file beginning with the driver letter (e.g. S:/folder/file.txt)
82 * @param mode read: FS_MODE_RD, write: FS_MODE_WR, both: FS_MODE_RD | FS_MODE_WR
83 * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
84 */
lv_fs_open(lv_fs_file_t * file_p,const char * path,lv_fs_mode_t mode)85 lv_fs_res_t lv_fs_open(lv_fs_file_t * file_p, const char * path, lv_fs_mode_t mode)
86 {
87 file_p->drv = NULL;
88 file_p->file_d = NULL;
89
90 if(path == NULL) return LV_FS_RES_INV_PARAM;
91
92 char letter = path[0];
93
94 file_p->drv = lv_fs_get_drv(letter);
95
96 if(file_p->drv == NULL) {
97 file_p->file_d = NULL;
98 return LV_FS_RES_NOT_EX;
99 }
100
101 if(file_p->drv->ready_cb != NULL) {
102 if(file_p->drv->ready_cb(file_p->drv) == false) {
103 file_p->drv = NULL;
104 file_p->file_d = NULL;
105 return LV_FS_RES_HW_ERR;
106 }
107 }
108
109 file_p->file_d = lv_mem_alloc(file_p->drv->file_size);
110 lv_mem_assert(file_p->file_d);
111 if(file_p->file_d == NULL) {
112 file_p->drv = NULL;
113 return LV_FS_RES_OUT_OF_MEM; /* Out of memory */
114 }
115
116 if(file_p->drv->open_cb == NULL) {
117 return LV_FS_RES_NOT_IMP;
118 }
119
120 const char * real_path = lv_fs_get_real_path(path);
121 lv_fs_res_t res = file_p->drv->open_cb(file_p->drv, file_p->file_d, real_path, mode);
122
123 if(res != LV_FS_RES_OK) {
124 lv_mem_free(file_p->file_d);
125 file_p->file_d = NULL;
126 file_p->drv = NULL;
127 }
128
129 return res;
130 }
131
132 /**
133 * Close an already opened file
134 * @param file_p pointer to a lv_fs_file_t variable
135 * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
136 */
lv_fs_close(lv_fs_file_t * file_p)137 lv_fs_res_t lv_fs_close(lv_fs_file_t * file_p)
138 {
139 if(file_p->drv == NULL) {
140 return LV_FS_RES_INV_PARAM;
141 }
142
143 if(file_p->drv->close_cb == NULL) {
144 return LV_FS_RES_NOT_IMP;
145 }
146
147 lv_fs_res_t res = file_p->drv->close_cb(file_p->drv, file_p->file_d);
148
149 lv_mem_free(file_p->file_d); /*Clean up*/
150 file_p->file_d = NULL;
151 file_p->drv = NULL;
152 file_p->file_d = NULL;
153
154 return res;
155 }
156
157 /**
158 * Delete a file
159 * @param path path of the file to delete
160 * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
161 */
lv_fs_remove(const char * path)162 lv_fs_res_t lv_fs_remove(const char * path)
163 {
164 if(path == NULL) return LV_FS_RES_INV_PARAM;
165 lv_fs_drv_t * drv = NULL;
166
167 char letter = path[0];
168
169 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_mem_assert(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 } else {
426 res = rddir_p->drv->dir_close_cb(rddir_p->drv, rddir_p->dir_d);
427 }
428
429 lv_mem_free(rddir_p->dir_d); /*Clean up*/
430 rddir_p->dir_d = NULL;
431 rddir_p->drv = NULL;
432 rddir_p->dir_d = NULL;
433
434 return res;
435 }
436
437 /**
438 * Get the free and total size of a driver in kB
439 * @param letter the driver letter
440 * @param total_p pointer to store the total size [kB]
441 * @param free_p pointer to store the free size_cb [kB]
442 * @return LV_FS_RES_OK or any error from lv_fs_res_t enum
443 */
lv_fs_free_space(char letter,uint32_t * total_p,uint32_t * free_p)444 lv_fs_res_t lv_fs_free_space(char letter, uint32_t * total_p, uint32_t * free_p)
445 {
446 lv_fs_drv_t * drv = lv_fs_get_drv(letter);
447
448 if(drv == NULL) {
449 return LV_FS_RES_INV_PARAM;
450 }
451
452 lv_fs_res_t res;
453
454 if(drv->free_space_cb == NULL) {
455 res = LV_FS_RES_NOT_IMP;
456 } else {
457 uint32_t total_tmp = 0;
458 uint32_t free_tmp = 0;
459 res = drv->free_space_cb(drv, &total_tmp, &free_tmp);
460
461 if(total_p != NULL) *total_p = total_tmp;
462 if(free_p != NULL) *free_p = free_tmp;
463 }
464
465 return res;
466 }
467
468 /**
469 * Initialize a file system driver with default values.
470 * It is used to surly have known values in the fields ant not memory junk.
471 * After it you can set the fields.
472 * @param drv pointer to driver variable to initialize
473 */
lv_fs_drv_init(lv_fs_drv_t * drv)474 void lv_fs_drv_init(lv_fs_drv_t * drv)
475 {
476 memset(drv, 0, sizeof(lv_fs_drv_t));
477 }
478
479 /**
480 * Add a new drive
481 * @param drv_p pointer to an lv_fs_drv_t structure which is inited with the
482 * corresponding function pointers. The data will be copied so the variable can be local.
483 */
lv_fs_drv_register(lv_fs_drv_t * drv_p)484 void lv_fs_drv_register(lv_fs_drv_t * drv_p)
485 {
486 /*Save the new driver*/
487 lv_fs_drv_t * new_drv;
488 new_drv = lv_ll_ins_head(&LV_GC_ROOT(_lv_drv_ll));
489 lv_mem_assert(new_drv);
490 if(new_drv == NULL) return;
491
492 memcpy(new_drv, drv_p, sizeof(lv_fs_drv_t));
493 }
494
495 /**
496 * Give a pointer to a driver from its letter
497 * @param letter the driver letter
498 * @return pointer to a driver or NULL if not found
499 */
lv_fs_get_drv(char letter)500 lv_fs_drv_t * lv_fs_get_drv(char letter)
501 {
502 lv_fs_drv_t * drv;
503
504 LV_LL_READ(LV_GC_ROOT(_lv_drv_ll), drv)
505 {
506 if(drv->letter == letter) {
507 return drv;
508 }
509 }
510
511 return NULL;
512 }
513 /**
514 * Fill a buffer with the letters of existing drivers
515 * @param buf buffer to store the letters ('\0' added after the last letter)
516 * @return the buffer
517 */
lv_fs_get_letters(char * buf)518 char * lv_fs_get_letters(char * buf)
519 {
520 lv_fs_drv_t * drv;
521 uint8_t i = 0;
522
523 LV_LL_READ(LV_GC_ROOT(_lv_drv_ll), drv)
524 {
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 uint16_t i;
542 for(i = strlen(fn); i > 0; i--) {
543 if(fn[i] == '.') {
544 return &fn[i + 1];
545 } else if(fn[i] == '/' || fn[i] == '\\') {
546 return ""; /*No extension if a '\' or '/' found*/
547 }
548 }
549
550 return ""; /*Empty string if no '.' in the file name. */
551 }
552
553 /**
554 * Step up one level
555 * @param path pointer to a file name
556 * @return the truncated file name
557 */
lv_fs_up(char * path)558 char * lv_fs_up(char * path)
559 {
560 uint16_t len = strlen(path);
561 if(len == 0) return path;
562
563 len--; /*Go before the trailing '\0'*/
564
565 /*Ignore trailing '/' or '\'*/
566 while(path[len] == '/' || path[len] == '\\') {
567 path[len] = '\0';
568 if(len > 0)
569 len--;
570 else
571 return path;
572 }
573
574 uint16_t i;
575 for(i = len; i > 0; i--) {
576 if(path[i] == '/' || path[i] == '\\') break;
577 }
578
579 if(i > 0) path[i] = '\0';
580
581 return path;
582 }
583
584 /**
585 * Get the last element of a path (e.g. U:/folder/file -> file)
586 * @param path a character sting with the path to search in
587 * @return pointer to the beginning of the last element in the path
588 */
lv_fs_get_last(const char * path)589 const char * lv_fs_get_last(const char * path)
590 {
591 uint16_t len = strlen(path);
592 if(len == 0) return path;
593
594 len--; /*Go before the trailing '\0'*/
595
596 /*Ignore trailing '/' or '\'*/
597 while(path[len] == '/' || path[len] == '\\') {
598 if(len > 0)
599 len--;
600 else
601 return path;
602 }
603
604 uint16_t i;
605 for(i = len; i > 0; i--) {
606 if(path[i] == '/' || path[i] == '\\') break;
607 }
608
609 /*No '/' or '\' in the path so return with path itself*/
610 if(i == 0) return path;
611
612 return &path[i + 1];
613 }
614 /**********************
615 * STATIC FUNCTIONS
616 **********************/
617
618 /**
619 * Leave the driver letters and / or \ letters from beginning of the path
620 * @param path path string (E.g. S:/folder/file.txt)
621 * @return pointer to the beginning of the real path (E.g. folder/file.txt)
622 */
lv_fs_get_real_path(const char * path)623 static const char * lv_fs_get_real_path(const char * path)
624 {
625 /* Example path: "S:/folder/file.txt"
626 * Leave the letter and the : / \ characters*/
627
628 path++; /*Ignore the driver letter*/
629
630 while(*path != '\0') {
631 if(*path == ':' || *path == '\\' || *path == '/') {
632 path++;
633 } else {
634 break;
635 }
636 }
637
638 return path;
639 }
640
641 #endif /*LV_USE_FILESYSTEM*/
642