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