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