1 /**
2  * @file lv_fs.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_fs.h"
10 
11 #include "../misc/lv_assert.h"
12 #include "lv_ll.h"
13 #include <string.h>
14 #include "lv_gc.h"
15 
16 /*********************
17  *      DEFINES
18  *********************/
19 
20 /**********************
21  *      TYPEDEFS
22  **********************/
23 
24 /**********************
25  *  STATIC PROTOTYPES
26  **********************/
27 static const char * lv_fs_get_real_path(const char * path);
28 
29 /**********************
30  *  STATIC VARIABLES
31  **********************/
32 
33 /**********************
34  *      MACROS
35  **********************/
36 
37 /**********************
38  *   GLOBAL FUNCTIONS
39  **********************/
40 
_lv_fs_init(void)41 void _lv_fs_init(void)
42 {
43     _lv_ll_init(&LV_GC_ROOT(_lv_fsdrv_ll), sizeof(lv_fs_drv_t *));
44 }
45 
lv_fs_is_ready(char letter)46 bool lv_fs_is_ready(char letter)
47 {
48     lv_fs_drv_t * drv = lv_fs_get_drv(letter);
49 
50     if(drv == NULL) return false; /*An unknown driver in not ready*/
51 
52     if(drv->ready_cb == NULL) return true; /*Assume the driver is always ready if no handler provided*/
53 
54     return drv->ready_cb(drv);
55 }
56 
lv_fs_open(lv_fs_file_t * file_p,const char * path,lv_fs_mode_t mode)57 lv_fs_res_t lv_fs_open(lv_fs_file_t * file_p, const char * path, lv_fs_mode_t mode)
58 {
59     if(path == NULL) {
60         LV_LOG_WARN("Can't open file: path is NULL");
61         return LV_FS_RES_INV_PARAM;
62     }
63 
64     char letter = path[0];
65     lv_fs_drv_t * drv = lv_fs_get_drv(letter);
66 
67     if(drv == NULL) {
68         LV_LOG_WARN("Can't open file (%s): unknown driver letter", path);
69         return LV_FS_RES_NOT_EX;
70     }
71 
72     if(drv->ready_cb) {
73         if(drv->ready_cb(drv) == false) {
74             LV_LOG_WARN("Can't open file (%s): driver not ready", path);
75             return LV_FS_RES_HW_ERR;
76         }
77     }
78 
79     if(drv->open_cb == NULL) {
80         LV_LOG_WARN("Can't open file (%s): open function not exists", path);
81         return LV_FS_RES_NOT_IMP;
82     }
83 
84     const char * real_path = lv_fs_get_real_path(path);
85     void * file_d = drv->open_cb(drv, real_path, mode);
86 
87     if(file_d == NULL || file_d == (void *)(-1)) {
88         return LV_FS_RES_UNKNOWN;
89     }
90 
91     file_p->drv = drv;
92     file_p->file_d = file_d;
93 
94     if(drv->cache_size) {
95         file_p->cache = lv_mem_alloc(sizeof(lv_fs_file_cache_t));
96         LV_ASSERT_MALLOC(file_p->cache);
97         lv_memset_00(file_p->cache, sizeof(lv_fs_file_cache_t));
98         file_p->cache->start = UINT32_MAX;  /*Set an invalid range by default*/
99         file_p->cache->end = UINT32_MAX - 1;
100     }
101 
102     return LV_FS_RES_OK;
103 }
104 
lv_fs_close(lv_fs_file_t * file_p)105 lv_fs_res_t lv_fs_close(lv_fs_file_t * file_p)
106 {
107     if(file_p->drv == NULL) {
108         return LV_FS_RES_INV_PARAM;
109     }
110 
111     if(file_p->drv->close_cb == NULL) {
112         return LV_FS_RES_NOT_IMP;
113     }
114 
115     lv_fs_res_t res = file_p->drv->close_cb(file_p->drv, file_p->file_d);
116 
117     if(file_p->drv->cache_size && file_p->cache) {
118         if(file_p->cache->buffer) {
119             lv_mem_free(file_p->cache->buffer);
120         }
121 
122         lv_mem_free(file_p->cache);
123     }
124 
125     file_p->file_d = NULL;
126     file_p->drv    = NULL;
127     file_p->cache  = NULL;
128 
129     return res;
130 }
131 
lv_fs_read_cached(lv_fs_file_t * file_p,char * buf,uint32_t btr,uint32_t * br)132 static lv_fs_res_t lv_fs_read_cached(lv_fs_file_t * file_p, char * buf, uint32_t btr, uint32_t * br)
133 {
134     lv_fs_res_t res = LV_FS_RES_OK;
135     uint32_t file_position = file_p->cache->file_position;
136     uint32_t start = file_p->cache->start;
137     uint32_t end = file_p->cache->end;
138     char * buffer = file_p->cache->buffer;
139     uint16_t buffer_size = file_p->drv->cache_size;
140 
141     if(start <= file_position && file_position < end) {
142         /* Data can be read from cache buffer */
143         uint16_t buffer_offset = file_position - start;
144         uint32_t buffer_remaining_length = LV_MIN((uint32_t)buffer_size - buffer_offset, (uint32_t)end - file_position);
145 
146         if(btr <= buffer_remaining_length) {
147             /*Data is in cache buffer, and buffer end not reached, no need to read from FS*/
148             lv_memcpy(buf, buffer + buffer_offset, btr);
149             *br = btr;
150         }
151         else {
152             /*First part of data is in cache buffer, but we need to read rest of data from FS*/
153             lv_memcpy(buf, buffer + buffer_offset, buffer_remaining_length);
154 
155             uint32_t bytes_read_to_buffer = 0;
156             if(btr > buffer_size) {
157                 /*If remaining data chuck is bigger than buffer size, then do not use cache, instead read it directly from FS*/
158                 res = file_p->drv->read_cb(file_p->drv, file_p->file_d, (void *)(buf + buffer_remaining_length),
159                                            btr - buffer_remaining_length, &bytes_read_to_buffer);
160             }
161             else {
162                 /*If remaining data chunk is smaller than buffer size, then read into cache buffer*/
163                 res = file_p->drv->read_cb(file_p->drv, file_p->file_d, (void *)buffer, buffer_size, &bytes_read_to_buffer);
164                 file_p->cache->start = file_p->cache->end;
165                 file_p->cache->end = file_p->cache->start + bytes_read_to_buffer;
166 
167                 uint16_t data_chunk_remaining = LV_MIN(btr - buffer_remaining_length, bytes_read_to_buffer);
168                 lv_memcpy(buf + buffer_remaining_length, buffer, data_chunk_remaining);
169             }
170             *br = LV_MIN(buffer_remaining_length + bytes_read_to_buffer, btr);
171         }
172     }
173     else {
174         /*Data is not in cache buffer*/
175         if(btr > buffer_size) {
176             /*If bigger data is requested, then do not use cache, instead read it directly*/
177             res = file_p->drv->read_cb(file_p->drv, file_p->file_d, (void *)buf, btr, br);
178         }
179         else {
180             /*If small data is requested, then read from FS into cache buffer*/
181             if(buffer == NULL) {
182                 file_p->cache->buffer = lv_mem_alloc(buffer_size);
183                 LV_ASSERT_MALLOC(file_p->cache->buffer);
184                 buffer = file_p->cache->buffer;
185             }
186 
187             uint32_t bytes_read_to_buffer = 0;
188             res = file_p->drv->read_cb(file_p->drv, file_p->file_d, (void *)buffer, buffer_size, &bytes_read_to_buffer);
189             file_p->cache->start = file_position;
190             file_p->cache->end = file_p->cache->start + bytes_read_to_buffer;
191 
192             *br = LV_MIN(btr, bytes_read_to_buffer);
193             lv_memcpy(buf, buffer, *br);
194 
195         }
196     }
197 
198     if(res == LV_FS_RES_OK) {
199         file_p->cache->file_position += *br;
200     }
201 
202     return res;
203 }
204 
lv_fs_read(lv_fs_file_t * file_p,void * buf,uint32_t btr,uint32_t * br)205 lv_fs_res_t lv_fs_read(lv_fs_file_t * file_p, void * buf, uint32_t btr, uint32_t * br)
206 {
207     if(br != NULL) *br = 0;
208     if(file_p->drv == NULL) return LV_FS_RES_INV_PARAM;
209     if(file_p->drv->read_cb == NULL) return LV_FS_RES_NOT_IMP;
210 
211     uint32_t br_tmp = 0;
212     lv_fs_res_t res;
213 
214     if(file_p->drv->cache_size) {
215         res = lv_fs_read_cached(file_p, (char *)buf, btr, &br_tmp);
216     }
217     else {
218         res = file_p->drv->read_cb(file_p->drv, file_p->file_d, buf, btr, &br_tmp);
219     }
220 
221     if(br != NULL) *br = br_tmp;
222 
223     return res;
224 }
225 
lv_fs_write(lv_fs_file_t * file_p,const void * buf,uint32_t btw,uint32_t * bw)226 lv_fs_res_t lv_fs_write(lv_fs_file_t * file_p, const void * buf, uint32_t btw, uint32_t * bw)
227 {
228     if(bw != NULL) *bw = 0;
229 
230     if(file_p->drv == NULL) {
231         return LV_FS_RES_INV_PARAM;
232     }
233 
234     if(file_p->drv->write_cb == NULL) {
235         return LV_FS_RES_NOT_IMP;
236     }
237 
238     uint32_t bw_tmp = 0;
239     lv_fs_res_t res = file_p->drv->write_cb(file_p->drv, file_p->file_d, buf, btw, &bw_tmp);
240     if(bw != NULL) *bw = bw_tmp;
241 
242     return res;
243 }
244 
lv_fs_seek(lv_fs_file_t * file_p,uint32_t pos,lv_fs_whence_t whence)245 lv_fs_res_t lv_fs_seek(lv_fs_file_t * file_p, uint32_t pos, lv_fs_whence_t whence)
246 {
247     if(file_p->drv == NULL) {
248         return LV_FS_RES_INV_PARAM;
249     }
250 
251     if(file_p->drv->seek_cb == NULL) {
252         return LV_FS_RES_NOT_IMP;
253     }
254 
255     lv_fs_res_t res = LV_FS_RES_OK;
256     if(file_p->drv->cache_size) {
257         switch(whence) {
258             case LV_FS_SEEK_SET: {
259                     file_p->cache->file_position = pos;
260 
261                     /*FS seek if new position is outside cache buffer*/
262                     if(file_p->cache->file_position < file_p->cache->start || file_p->cache->file_position > file_p->cache->end) {
263                         res = file_p->drv->seek_cb(file_p->drv, file_p->file_d, file_p->cache->file_position, LV_FS_SEEK_SET);
264                     }
265 
266                     break;
267                 }
268             case LV_FS_SEEK_CUR: {
269                     file_p->cache->file_position += pos;
270 
271                     /*FS seek if new position is outside cache buffer*/
272                     if(file_p->cache->file_position < file_p->cache->start || file_p->cache->file_position > file_p->cache->end) {
273                         res = file_p->drv->seek_cb(file_p->drv, file_p->file_d, file_p->cache->file_position, LV_FS_SEEK_SET);
274                     }
275 
276                     break;
277                 }
278             case LV_FS_SEEK_END: {
279                     /*Because we don't know the file size, we do a little trick: do a FS seek, then get new file position from FS*/
280                     res = file_p->drv->seek_cb(file_p->drv, file_p->file_d, pos, whence);
281                     if(res == LV_FS_RES_OK) {
282                         uint32_t tmp_position;
283                         res = file_p->drv->tell_cb(file_p->drv, file_p->file_d, &tmp_position);
284 
285                         if(res == LV_FS_RES_OK) {
286                             file_p->cache->file_position = tmp_position;
287                         }
288                     }
289                     break;
290                 }
291         }
292     }
293     else {
294         res = file_p->drv->seek_cb(file_p->drv, file_p->file_d, pos, whence);
295     }
296 
297     return res;
298 }
299 
lv_fs_tell(lv_fs_file_t * file_p,uint32_t * pos)300 lv_fs_res_t lv_fs_tell(lv_fs_file_t * file_p, uint32_t * pos)
301 {
302     if(file_p->drv == NULL) {
303         *pos = 0;
304         return LV_FS_RES_INV_PARAM;
305     }
306 
307     if(file_p->drv->tell_cb == NULL) {
308         *pos = 0;
309         return LV_FS_RES_NOT_IMP;
310     }
311 
312     lv_fs_res_t res;
313     if(file_p->drv->cache_size) {
314         *pos = file_p->cache->file_position;
315         res = LV_FS_RES_OK;
316     }
317     else {
318         res = file_p->drv->tell_cb(file_p->drv, file_p->file_d, pos);
319     }
320 
321     return res;
322 }
323 
lv_fs_dir_open(lv_fs_dir_t * rddir_p,const char * path)324 lv_fs_res_t lv_fs_dir_open(lv_fs_dir_t * rddir_p, const char * path)
325 {
326     if(path == NULL) return LV_FS_RES_INV_PARAM;
327 
328     char letter = path[0];
329     lv_fs_drv_t * drv = lv_fs_get_drv(letter);
330 
331     if(drv == NULL) {
332         return LV_FS_RES_NOT_EX;
333     }
334 
335     if(drv->ready_cb) {
336         if(drv->ready_cb(drv) == false) {
337             return LV_FS_RES_HW_ERR;
338         }
339     }
340 
341     if(drv->dir_open_cb == NULL) {
342         return LV_FS_RES_NOT_IMP;
343     }
344 
345     const char * real_path = lv_fs_get_real_path(path);
346     void * dir_d = drv->dir_open_cb(drv, real_path);
347 
348     if(dir_d == NULL || dir_d == (void *)(-1)) {
349         return LV_FS_RES_UNKNOWN;
350     }
351 
352     rddir_p->drv = drv;
353     rddir_p->dir_d = dir_d;
354 
355     return LV_FS_RES_OK;
356 }
357 
lv_fs_dir_read(lv_fs_dir_t * rddir_p,char * fn)358 lv_fs_res_t lv_fs_dir_read(lv_fs_dir_t * rddir_p, char * fn)
359 {
360     if(rddir_p->drv == NULL || rddir_p->dir_d == NULL) {
361         fn[0] = '\0';
362         return LV_FS_RES_INV_PARAM;
363     }
364 
365     if(rddir_p->drv->dir_read_cb == NULL) {
366         fn[0] = '\0';
367         return LV_FS_RES_NOT_IMP;
368     }
369 
370     lv_fs_res_t res = rddir_p->drv->dir_read_cb(rddir_p->drv, rddir_p->dir_d, fn);
371 
372     return res;
373 }
374 
lv_fs_dir_close(lv_fs_dir_t * rddir_p)375 lv_fs_res_t lv_fs_dir_close(lv_fs_dir_t * rddir_p)
376 {
377     if(rddir_p->drv == NULL || rddir_p->dir_d == NULL) {
378         return LV_FS_RES_INV_PARAM;
379     }
380 
381     if(rddir_p->drv->dir_close_cb == NULL) {
382         return LV_FS_RES_NOT_IMP;
383     }
384 
385     lv_fs_res_t res = rddir_p->drv->dir_close_cb(rddir_p->drv, rddir_p->dir_d);
386 
387     rddir_p->dir_d = NULL;
388     rddir_p->drv   = NULL;
389 
390     return res;
391 }
392 
lv_fs_drv_init(lv_fs_drv_t * drv)393 void lv_fs_drv_init(lv_fs_drv_t * drv)
394 {
395     lv_memset_00(drv, sizeof(lv_fs_drv_t));
396 }
397 
lv_fs_drv_register(lv_fs_drv_t * drv_p)398 void lv_fs_drv_register(lv_fs_drv_t * drv_p)
399 {
400     /*Save the new driver*/
401     lv_fs_drv_t ** new_drv;
402     new_drv = _lv_ll_ins_head(&LV_GC_ROOT(_lv_fsdrv_ll));
403     LV_ASSERT_MALLOC(new_drv);
404     if(new_drv == NULL) return;
405 
406     *new_drv = drv_p;
407 }
408 
lv_fs_get_drv(char letter)409 lv_fs_drv_t * lv_fs_get_drv(char letter)
410 {
411     lv_fs_drv_t ** drv;
412 
413     _LV_LL_READ(&LV_GC_ROOT(_lv_fsdrv_ll), drv) {
414         if((*drv)->letter == letter) {
415             return *drv;
416         }
417     }
418 
419     return NULL;
420 }
421 
lv_fs_get_letters(char * buf)422 char * lv_fs_get_letters(char * buf)
423 {
424     lv_fs_drv_t ** drv;
425     uint8_t i = 0;
426 
427     _LV_LL_READ(&LV_GC_ROOT(_lv_fsdrv_ll), drv) {
428         buf[i] = (*drv)->letter;
429         i++;
430     }
431 
432     buf[i] = '\0';
433 
434     return buf;
435 }
436 
lv_fs_get_ext(const char * fn)437 const char * lv_fs_get_ext(const char * fn)
438 {
439     size_t i;
440     for(i = strlen(fn); i > 0; i--) {
441         if(fn[i] == '.') {
442             return &fn[i + 1];
443         }
444         else if(fn[i] == '/' || fn[i] == '\\') {
445             return ""; /*No extension if a '\' or '/' found*/
446         }
447     }
448 
449     return ""; /*Empty string if no '.' in the file name.*/
450 }
451 
lv_fs_up(char * path)452 char * lv_fs_up(char * path)
453 {
454     size_t len = strlen(path);
455     if(len == 0) return path;
456 
457     len--; /*Go before the trailing '\0'*/
458 
459     /*Ignore trailing '/' or '\'*/
460     while(path[len] == '/' || path[len] == '\\') {
461         path[len] = '\0';
462         if(len > 0)
463             len--;
464         else
465             return path;
466     }
467 
468     size_t i;
469     for(i = len; i > 0; i--) {
470         if(path[i] == '/' || path[i] == '\\') break;
471     }
472 
473     if(i > 0) path[i] = '\0';
474 
475     return path;
476 }
477 
lv_fs_get_last(const char * path)478 const char * lv_fs_get_last(const char * path)
479 {
480     size_t len = strlen(path);
481     if(len == 0) return path;
482 
483     len--; /*Go before the trailing '\0'*/
484 
485     /*Ignore trailing '/' or '\'*/
486     while(path[len] == '/' || path[len] == '\\') {
487         if(len > 0)
488             len--;
489         else
490             return path;
491     }
492 
493     size_t i;
494     for(i = len; i > 0; i--) {
495         if(path[i] == '/' || path[i] == '\\') break;
496     }
497 
498     /*No '/' or '\' in the path so return with path itself*/
499     if(i == 0) return path;
500 
501     return &path[i + 1];
502 }
503 /**********************
504  *   STATIC FUNCTIONS
505  **********************/
506 
507 /**
508  * Skip the driver letter and the possible : after the letter
509  * @param path path string (E.g. S:/folder/file.txt)
510  * @return pointer to the beginning of the real path (E.g. /folder/file.txt)
511  */
lv_fs_get_real_path(const char * path)512 static const char * lv_fs_get_real_path(const char * path)
513 {
514     path++; /*Ignore the driver letter*/
515     if(*path == ':') path++;
516 
517     return path;
518 }
519