1 // Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include <string.h>
16 #include <stdlib.h>
17 #include <unistd.h>
18 #include <dirent.h>
19 #include <sys/errno.h>
20 #include <sys/fcntl.h>
21 #include <sys/lock.h>
22 #include "esp_vfs.h"
23 #include "esp_log.h"
24 #include "ff.h"
25 #include "diskio_impl.h"
26 
27 typedef struct {
28     char fat_drive[8];  /* FAT drive name */
29     char base_path[ESP_VFS_PATH_MAX];   /* base path in VFS where partition is registered */
30     size_t max_files;   /* max number of simultaneously open files; size of files[] array */
31     _lock_t lock;       /* guard for access to this structure */
32     FATFS fs;           /* fatfs library FS structure */
33     char tmp_path_buf[FILENAME_MAX+3];  /* temporary buffer used to prepend drive name to the path */
34     char tmp_path_buf2[FILENAME_MAX+3]; /* as above; used in functions which take two path arguments */
35     bool *o_append;  /* O_APPEND is stored here for each max_files entries (because O_APPEND is not compatible with FA_OPEN_APPEND) */
36     FIL files[0];   /* array with max_files entries; must be the final member of the structure */
37 } vfs_fat_ctx_t;
38 
39 typedef struct {
40     DIR dir;
41     long offset;
42     FF_DIR ffdir;
43     FILINFO filinfo;
44     struct dirent cur_dirent;
45 } vfs_fat_dir_t;
46 
47 /* Date and time storage formats in FAT */
48 typedef union {
49     struct {
50         uint16_t mday : 5;  /* Day of month, 1 - 31 */
51         uint16_t mon : 4;   /* Month, 1 - 12 */
52         uint16_t year : 7;  /* Year, counting from 1980. E.g. 37 for 2017 */
53     };
54     uint16_t as_int;
55 } fat_date_t;
56 
57 typedef union {
58     struct {
59         uint16_t sec : 5;   /* Seconds divided by 2. E.g. 21 for 42 seconds */
60         uint16_t min : 6;   /* Minutes, 0 - 59 */
61         uint16_t hour : 5;  /* Hour, 0 - 23 */
62     };
63     uint16_t as_int;
64 } fat_time_t;
65 
66 static const char* TAG = "vfs_fat";
67 
68 static ssize_t vfs_fat_write(void* p, int fd, const void * data, size_t size);
69 static off_t vfs_fat_lseek(void* p, int fd, off_t size, int mode);
70 static ssize_t vfs_fat_read(void* ctx, int fd, void * dst, size_t size);
71 static ssize_t vfs_fat_pread(void *ctx, int fd, void *dst, size_t size, off_t offset);
72 static ssize_t vfs_fat_pwrite(void *ctx, int fd, const void *src, size_t size, off_t offset);
73 static int vfs_fat_open(void* ctx, const char * path, int flags, int mode);
74 static int vfs_fat_close(void* ctx, int fd);
75 static int vfs_fat_fstat(void* ctx, int fd, struct stat * st);
76 static int vfs_fat_fsync(void* ctx, int fd);
77 #ifdef CONFIG_VFS_SUPPORT_DIR
78 static int vfs_fat_stat(void* ctx, const char * path, struct stat * st);
79 static int vfs_fat_link(void* ctx, const char* n1, const char* n2);
80 static int vfs_fat_unlink(void* ctx, const char *path);
81 static int vfs_fat_rename(void* ctx, const char *src, const char *dst);
82 static DIR* vfs_fat_opendir(void* ctx, const char* name);
83 static struct dirent* vfs_fat_readdir(void* ctx, DIR* pdir);
84 static int vfs_fat_readdir_r(void* ctx, DIR* pdir, struct dirent* entry, struct dirent** out_dirent);
85 static long vfs_fat_telldir(void* ctx, DIR* pdir);
86 static void vfs_fat_seekdir(void* ctx, DIR* pdir, long offset);
87 static int vfs_fat_closedir(void* ctx, DIR* pdir);
88 static int vfs_fat_mkdir(void* ctx, const char* name, mode_t mode);
89 static int vfs_fat_rmdir(void* ctx, const char* name);
90 static int vfs_fat_access(void* ctx, const char *path, int amode);
91 static int vfs_fat_truncate(void* ctx, const char *path, off_t length);
92 static int vfs_fat_utime(void* ctx, const char *path, const struct utimbuf *times);
93 #endif // CONFIG_VFS_SUPPORT_DIR
94 
95 static vfs_fat_ctx_t* s_fat_ctxs[FF_VOLUMES] = { NULL, NULL };
96 //backwards-compatibility with esp_vfs_fat_unregister()
97 static vfs_fat_ctx_t* s_fat_ctx = NULL;
98 
find_context_index_by_path(const char * base_path)99 static size_t find_context_index_by_path(const char* base_path)
100 {
101     for(size_t i=0; i<FF_VOLUMES; i++) {
102         if (s_fat_ctxs[i] && !strcmp(s_fat_ctxs[i]->base_path, base_path)) {
103             return i;
104         }
105     }
106     return FF_VOLUMES;
107 }
108 
find_unused_context_index(void)109 static size_t find_unused_context_index(void)
110 {
111     for(size_t i=0; i<FF_VOLUMES; i++) {
112         if (!s_fat_ctxs[i]) {
113             return i;
114         }
115     }
116     return FF_VOLUMES;
117 }
118 
esp_vfs_fat_register(const char * base_path,const char * fat_drive,size_t max_files,FATFS ** out_fs)119 esp_err_t esp_vfs_fat_register(const char* base_path, const char* fat_drive, size_t max_files, FATFS** out_fs)
120 {
121     size_t ctx = find_context_index_by_path(base_path);
122     if (ctx < FF_VOLUMES) {
123         return ESP_ERR_INVALID_STATE;
124     }
125 
126     ctx = find_unused_context_index();
127     if (ctx == FF_VOLUMES) {
128         return ESP_ERR_NO_MEM;
129     }
130 
131     const esp_vfs_t vfs = {
132         .flags = ESP_VFS_FLAG_CONTEXT_PTR,
133         .write_p = &vfs_fat_write,
134         .lseek_p = &vfs_fat_lseek,
135         .read_p = &vfs_fat_read,
136         .pread_p = &vfs_fat_pread,
137         .pwrite_p = &vfs_fat_pwrite,
138         .open_p = &vfs_fat_open,
139         .close_p = &vfs_fat_close,
140         .fstat_p = &vfs_fat_fstat,
141         .fsync_p = &vfs_fat_fsync,
142 #ifdef CONFIG_VFS_SUPPORT_DIR
143         .stat_p = &vfs_fat_stat,
144         .link_p = &vfs_fat_link,
145         .unlink_p = &vfs_fat_unlink,
146         .rename_p = &vfs_fat_rename,
147         .opendir_p = &vfs_fat_opendir,
148         .closedir_p = &vfs_fat_closedir,
149         .readdir_p = &vfs_fat_readdir,
150         .readdir_r_p = &vfs_fat_readdir_r,
151         .seekdir_p = &vfs_fat_seekdir,
152         .telldir_p = &vfs_fat_telldir,
153         .mkdir_p = &vfs_fat_mkdir,
154         .rmdir_p = &vfs_fat_rmdir,
155         .access_p = &vfs_fat_access,
156         .truncate_p = &vfs_fat_truncate,
157         .utime_p = &vfs_fat_utime,
158 #endif // CONFIG_VFS_SUPPORT_DIR
159     };
160     size_t ctx_size = sizeof(vfs_fat_ctx_t) + max_files * sizeof(FIL);
161     vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ff_memalloc(ctx_size);
162     if (fat_ctx == NULL) {
163         return ESP_ERR_NO_MEM;
164     }
165     memset(fat_ctx, 0, ctx_size);
166     fat_ctx->o_append = ff_memalloc(max_files * sizeof(bool));
167     if (fat_ctx->o_append == NULL) {
168         free(fat_ctx);
169         return ESP_ERR_NO_MEM;
170     }
171     memset(fat_ctx->o_append, 0, max_files * sizeof(bool));
172     fat_ctx->max_files = max_files;
173     strlcpy(fat_ctx->fat_drive, fat_drive, sizeof(fat_ctx->fat_drive) - 1);
174     strlcpy(fat_ctx->base_path, base_path, sizeof(fat_ctx->base_path) - 1);
175 
176     esp_err_t err = esp_vfs_register(base_path, &vfs, fat_ctx);
177     if (err != ESP_OK) {
178         free(fat_ctx->o_append);
179         free(fat_ctx);
180         return err;
181     }
182 
183     _lock_init(&fat_ctx->lock);
184     s_fat_ctxs[ctx] = fat_ctx;
185 
186     //compatibility
187     s_fat_ctx = fat_ctx;
188 
189     *out_fs = &fat_ctx->fs;
190 
191     return ESP_OK;
192 }
193 
esp_vfs_fat_unregister_path(const char * base_path)194 esp_err_t esp_vfs_fat_unregister_path(const char* base_path)
195 {
196     size_t ctx = find_context_index_by_path(base_path);
197     if (ctx == FF_VOLUMES) {
198         return ESP_ERR_INVALID_STATE;
199     }
200     vfs_fat_ctx_t* fat_ctx = s_fat_ctxs[ctx];
201     esp_err_t err = esp_vfs_unregister(fat_ctx->base_path);
202     if (err != ESP_OK) {
203         return err;
204     }
205     _lock_close(&fat_ctx->lock);
206     free(fat_ctx->o_append);
207     free(fat_ctx);
208     s_fat_ctxs[ctx] = NULL;
209     return ESP_OK;
210 }
211 
get_next_fd(vfs_fat_ctx_t * fat_ctx)212 static int get_next_fd(vfs_fat_ctx_t* fat_ctx)
213 {
214     for (size_t i = 0; i < fat_ctx->max_files; ++i) {
215         if (fat_ctx->files[i].obj.fs == NULL) {
216             return (int) i;
217         }
218     }
219     return -1;
220 }
221 
fat_mode_conv(int m)222 static int fat_mode_conv(int m)
223 {
224     int res = 0;
225     int acc_mode = m & O_ACCMODE;
226     if (acc_mode == O_RDONLY) {
227         res |= FA_READ;
228     } else if (acc_mode == O_WRONLY) {
229         res |= FA_WRITE;
230     } else if (acc_mode == O_RDWR) {
231         res |= FA_READ | FA_WRITE;
232     }
233     if ((m & O_CREAT) && (m & O_EXCL)) {
234         res |= FA_CREATE_NEW;
235     } else if ((m & O_CREAT) && (m & O_TRUNC)) {
236         res |= FA_CREATE_ALWAYS;
237     } else if (m & O_APPEND) {
238         res |= FA_OPEN_ALWAYS;
239     } else {
240         res |= FA_OPEN_EXISTING;
241     }
242     return res;
243 }
244 
fresult_to_errno(FRESULT fr)245 static int fresult_to_errno(FRESULT fr)
246 {
247     switch(fr) {
248         case FR_DISK_ERR:       return EIO;
249         case FR_INT_ERR:        return EIO;
250         case FR_NOT_READY:      return ENODEV;
251         case FR_NO_FILE:        return ENOENT;
252         case FR_NO_PATH:        return ENOENT;
253         case FR_INVALID_NAME:   return EINVAL;
254         case FR_DENIED:         return EACCES;
255         case FR_EXIST:          return EEXIST;
256         case FR_INVALID_OBJECT: return EBADF;
257         case FR_WRITE_PROTECTED: return EACCES;
258         case FR_INVALID_DRIVE:  return ENXIO;
259         case FR_NOT_ENABLED:    return ENODEV;
260         case FR_NO_FILESYSTEM:  return ENODEV;
261         case FR_MKFS_ABORTED:   return EINTR;
262         case FR_TIMEOUT:        return ETIMEDOUT;
263         case FR_LOCKED:         return EACCES;
264         case FR_NOT_ENOUGH_CORE: return ENOMEM;
265         case FR_TOO_MANY_OPEN_FILES: return ENFILE;
266         case FR_INVALID_PARAMETER: return EINVAL;
267         case FR_OK: return 0;
268     }
269     assert(0 && "unhandled FRESULT");
270     return ENOTSUP;
271 }
272 
file_cleanup(vfs_fat_ctx_t * ctx,int fd)273 static void file_cleanup(vfs_fat_ctx_t* ctx, int fd)
274 {
275     memset(&ctx->files[fd], 0, sizeof(FIL));
276 }
277 
278 /**
279  * @brief Prepend drive letters to path names
280  * This function returns new path path pointers, pointing to a temporary buffer
281  * inside ctx.
282  * @note Call this function with ctx->lock acquired. Paths are valid while the
283  *       lock is held.
284  * @param ctx vfs_fat_ctx_t context
285  * @param[inout] path as input, pointer to the path; as output, pointer to the new path
286  * @param[inout] path2 as input, pointer to the path; as output, pointer to the new path
287  */
prepend_drive_to_path(vfs_fat_ctx_t * ctx,const char ** path,const char ** path2)288 static void prepend_drive_to_path(vfs_fat_ctx_t * ctx, const char ** path, const char ** path2){
289     snprintf(ctx->tmp_path_buf, sizeof(ctx->tmp_path_buf), "%s%s", ctx->fat_drive, *path);
290     *path = ctx->tmp_path_buf;
291     if(path2){
292         snprintf(ctx->tmp_path_buf2, sizeof(ctx->tmp_path_buf2), "%s%s", ((vfs_fat_ctx_t*)ctx)->fat_drive, *path2);
293         *path2 = ctx->tmp_path_buf2;
294     }
295 }
296 
vfs_fat_open(void * ctx,const char * path,int flags,int mode)297 static int vfs_fat_open(void* ctx, const char * path, int flags, int mode)
298 {
299     ESP_LOGV(TAG, "%s: path=\"%s\", flags=%x, mode=%x", __func__, path, flags, mode);
300     vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
301     _lock_acquire(&fat_ctx->lock);
302     prepend_drive_to_path(fat_ctx, &path, NULL);
303     int fd = get_next_fd(fat_ctx);
304     if (fd < 0) {
305         _lock_release(&fat_ctx->lock);
306         ESP_LOGE(TAG, "open: no free file descriptors");
307         errno = ENFILE;
308         return -1;
309     }
310 
311     FRESULT res = f_open(&fat_ctx->files[fd], path, fat_mode_conv(flags));
312     if (res != FR_OK) {
313         file_cleanup(fat_ctx, fd);
314         _lock_release(&fat_ctx->lock);
315         ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
316         errno = fresult_to_errno(res);
317         return -1;
318     }
319 
320 #ifdef CONFIG_FATFS_USE_FASTSEEK
321     FIL* file = &fat_ctx->files[fd];
322     //fast-seek is only allowed in read mode, since file cannot be expanded
323     //to use it.
324     if(!(fat_mode_conv(flags) & (FA_WRITE))) {
325         DWORD *clmt_mem =  ff_memalloc(sizeof(DWORD) * CONFIG_FATFS_FAST_SEEK_BUFFER_SIZE);
326         if (clmt_mem == NULL) {
327             f_close(file);
328             file_cleanup(fat_ctx, fd);
329             _lock_release(&fat_ctx->lock);
330             ESP_LOGE(TAG, "open: Failed to pre-allocate CLMT buffer for fast-seek");
331             errno = ENOMEM;
332             return -1;
333         }
334 
335         file->cltbl = clmt_mem;
336         file->cltbl[0] = CONFIG_FATFS_FAST_SEEK_BUFFER_SIZE;
337         res = f_lseek(file, CREATE_LINKMAP);
338         ESP_LOGD(TAG, "%s: fast-seek has: %s",
339                 __func__,
340                 (res == FR_OK) ? "activated" : "failed");
341         if(res != FR_OK) {
342             ESP_LOGW(TAG, "%s: fast-seek not activated reason code: %d",
343                     __func__, res);
344             //If linkmap creation fails, fallback to the non fast seek.
345             ff_memfree(file->cltbl);
346             file->cltbl = NULL;
347         }
348     } else {
349         file->cltbl = NULL;
350     }
351 #endif
352 
353     // O_APPEND need to be stored because it is not compatible with FA_OPEN_APPEND:
354     //  - FA_OPEN_APPEND means to jump to the end of file only after open()
355     //  - O_APPEND means to jump to the end only before each write()
356     // Other VFS drivers handles O_APPEND well (to the best of my knowledge),
357     // therefore this flag is stored here (at this VFS level) in order to save
358     // memory.
359     fat_ctx->o_append[fd] = (flags & O_APPEND) == O_APPEND;
360     _lock_release(&fat_ctx->lock);
361     return fd;
362 }
363 
vfs_fat_write(void * ctx,int fd,const void * data,size_t size)364 static ssize_t vfs_fat_write(void* ctx, int fd, const void * data, size_t size)
365 {
366     vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
367     FIL* file = &fat_ctx->files[fd];
368     FRESULT res;
369     if (fat_ctx->o_append[fd]) {
370         if ((res = f_lseek(file, f_size(file))) != FR_OK) {
371             ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
372             errno = fresult_to_errno(res);
373             return -1;
374         }
375     }
376     unsigned written = 0;
377     res = f_write(file, data, size, &written);
378     if (res != FR_OK) {
379         ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
380         errno = fresult_to_errno(res);
381         if (written == 0) {
382             return -1;
383         }
384     }
385     return written;
386 }
387 
vfs_fat_read(void * ctx,int fd,void * dst,size_t size)388 static ssize_t vfs_fat_read(void* ctx, int fd, void * dst, size_t size)
389 {
390     vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
391     FIL* file = &fat_ctx->files[fd];
392     unsigned read = 0;
393     FRESULT res = f_read(file, dst, size, &read);
394     if (res != FR_OK) {
395         ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
396         errno = fresult_to_errno(res);
397         if (read == 0) {
398             return -1;
399         }
400     }
401     return read;
402 }
403 
vfs_fat_pread(void * ctx,int fd,void * dst,size_t size,off_t offset)404 static ssize_t vfs_fat_pread(void *ctx, int fd, void *dst, size_t size, off_t offset)
405 {
406     ssize_t ret = -1;
407     vfs_fat_ctx_t *fat_ctx = (vfs_fat_ctx_t *) ctx;
408     _lock_acquire(&fat_ctx->lock);
409     FIL *file = &fat_ctx->files[fd];
410     const off_t prev_pos = f_tell(file);
411 
412     FRESULT f_res = f_lseek(file, offset);
413 
414     if (f_res != FR_OK) {
415         ESP_LOGD(TAG, "%s: fresult=%d", __func__, f_res);
416         errno = fresult_to_errno(f_res);
417         goto pread_release;
418     }
419 
420     unsigned read = 0;
421     f_res = f_read(file, dst, size, &read);
422     if (f_res == FR_OK) {
423         ret = read;
424     } else {
425         ESP_LOGD(TAG, "%s: fresult=%d", __func__, f_res);
426         errno = fresult_to_errno(f_res);
427         // No return yet - need to restore previous position
428     }
429 
430     f_res = f_lseek(file, prev_pos);
431     if (f_res != FR_OK) {
432         ESP_LOGD(TAG, "%s: fresult=%d", __func__, f_res);
433         if (ret >= 0) {
434             errno = fresult_to_errno(f_res);
435         } // else f_read failed so errno shouldn't be overwritten
436         ret = -1; // in case the read was successful but the seek wasn't
437     }
438 
439 pread_release:
440     _lock_release(&fat_ctx->lock);
441     return ret;
442 }
443 
vfs_fat_pwrite(void * ctx,int fd,const void * src,size_t size,off_t offset)444 static ssize_t vfs_fat_pwrite(void *ctx, int fd, const void *src, size_t size, off_t offset)
445 {
446     ssize_t ret = -1;
447     vfs_fat_ctx_t *fat_ctx = (vfs_fat_ctx_t *) ctx;
448     _lock_acquire(&fat_ctx->lock);
449     FIL *file = &fat_ctx->files[fd];
450     const off_t prev_pos = f_tell(file);
451 
452     FRESULT f_res = f_lseek(file, offset);
453 
454     if (f_res != FR_OK) {
455         ESP_LOGD(TAG, "%s: fresult=%d", __func__, f_res);
456         errno = fresult_to_errno(f_res);
457         goto pwrite_release;
458     }
459 
460     unsigned wr = 0;
461     f_res = f_write(file, src, size, &wr);
462     if (f_res == FR_OK) {
463         ret = wr;
464     } else {
465         ESP_LOGD(TAG, "%s: fresult=%d", __func__, f_res);
466         errno = fresult_to_errno(f_res);
467         // No return yet - need to restore previous position
468     }
469 
470     f_res = f_lseek(file, prev_pos);
471     if (f_res != FR_OK) {
472         ESP_LOGD(TAG, "%s: fresult=%d", __func__, f_res);
473         if (ret >= 0) {
474             errno = fresult_to_errno(f_res);
475         } // else f_write failed so errno shouldn't be overwritten
476         ret = -1; // in case the write was successful but the seek wasn't
477     }
478 
479 pwrite_release:
480     _lock_release(&fat_ctx->lock);
481     return ret;
482 }
483 
vfs_fat_fsync(void * ctx,int fd)484 static int vfs_fat_fsync(void* ctx, int fd)
485 {
486     vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
487     _lock_acquire(&fat_ctx->lock);
488     FIL* file = &fat_ctx->files[fd];
489     FRESULT res = f_sync(file);
490     _lock_release(&fat_ctx->lock);
491     int rc = 0;
492     if (res != FR_OK) {
493         ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
494         errno = fresult_to_errno(res);
495         rc = -1;
496     }
497     return rc;
498 }
499 
vfs_fat_close(void * ctx,int fd)500 static int vfs_fat_close(void* ctx, int fd)
501 {
502     vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
503     _lock_acquire(&fat_ctx->lock);
504     FIL* file = &fat_ctx->files[fd];
505 
506 #ifdef CONFIG_FATFS_USE_FASTSEEK
507     ff_memfree(file->cltbl);
508     file->cltbl = NULL;
509 #endif
510 
511     FRESULT res = f_close(file);
512     file_cleanup(fat_ctx, fd);
513     _lock_release(&fat_ctx->lock);
514     int rc = 0;
515     if (res != FR_OK) {
516         ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
517         errno = fresult_to_errno(res);
518         rc = -1;
519     }
520     return rc;
521 }
522 
vfs_fat_lseek(void * ctx,int fd,off_t offset,int mode)523 static off_t vfs_fat_lseek(void* ctx, int fd, off_t offset, int mode)
524 {
525     vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
526     FIL* file = &fat_ctx->files[fd];
527     off_t new_pos;
528     if (mode == SEEK_SET) {
529         new_pos = offset;
530     } else if (mode == SEEK_CUR) {
531         off_t cur_pos = f_tell(file);
532         new_pos = cur_pos + offset;
533     } else if (mode == SEEK_END) {
534         off_t size = f_size(file);
535         new_pos = size + offset;
536     } else {
537         errno = EINVAL;
538         return -1;
539     }
540 
541     ESP_LOGD(TAG, "%s: offset=%ld, filesize:=%d", __func__, new_pos, f_size(file));
542     FRESULT res = f_lseek(file, new_pos);
543     if (res != FR_OK) {
544         ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
545         errno = fresult_to_errno(res);
546         return -1;
547     }
548     return new_pos;
549 }
550 
vfs_fat_fstat(void * ctx,int fd,struct stat * st)551 static int vfs_fat_fstat(void* ctx, int fd, struct stat * st)
552 {
553     vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
554     FIL* file = &fat_ctx->files[fd];
555     memset(st, 0, sizeof(*st));
556     st->st_size = f_size(file);
557     st->st_mode = S_IRWXU | S_IRWXG | S_IRWXO | S_IFREG;
558     st->st_mtime = 0;
559     st->st_atime = 0;
560     st->st_ctime = 0;
561     return 0;
562 }
563 
564 #ifdef CONFIG_VFS_SUPPORT_DIR
565 
get_stat_mode(bool is_dir)566 static inline mode_t get_stat_mode(bool is_dir)
567 {
568     return S_IRWXU | S_IRWXG | S_IRWXO |
569             ((is_dir) ? S_IFDIR : S_IFREG);
570 }
571 
vfs_fat_stat(void * ctx,const char * path,struct stat * st)572 static int vfs_fat_stat(void* ctx, const char * path, struct stat * st)
573 {
574     if (strcmp(path, "/") == 0) {
575         /* FatFS f_stat function does not work for the drive root.
576          * Just pretend that this is a directory.
577          */
578         memset(st, 0, sizeof(*st));
579         st->st_mode = get_stat_mode(true);
580         return 0;
581     }
582 
583     vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
584     _lock_acquire(&fat_ctx->lock);
585     prepend_drive_to_path(fat_ctx, &path, NULL);
586     FILINFO info;
587     FRESULT res = f_stat(path, &info);
588     _lock_release(&fat_ctx->lock);
589     if (res != FR_OK) {
590         ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
591         errno = fresult_to_errno(res);
592         return -1;
593     }
594 
595     memset(st, 0, sizeof(*st));
596     st->st_size = info.fsize;
597     st->st_mode = get_stat_mode((info.fattrib & AM_DIR) != 0);
598     fat_date_t fdate = { .as_int = info.fdate };
599     fat_time_t ftime = { .as_int = info.ftime };
600     struct tm tm = {
601         .tm_mday = fdate.mday,
602         .tm_mon = fdate.mon - 1,    /* unlike tm_mday, tm_mon is zero-based */
603         .tm_year = fdate.year + 80,
604         .tm_sec = ftime.sec * 2,
605         .tm_min = ftime.min,
606         .tm_hour = ftime.hour
607     };
608     st->st_mtime = mktime(&tm);
609     st->st_atime = 0;
610     st->st_ctime = 0;
611     return 0;
612 }
613 
vfs_fat_unlink(void * ctx,const char * path)614 static int vfs_fat_unlink(void* ctx, const char *path)
615 {
616     vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
617     _lock_acquire(&fat_ctx->lock);
618     prepend_drive_to_path(fat_ctx, &path, NULL);
619     FRESULT res = f_unlink(path);
620     _lock_release(&fat_ctx->lock);
621     if (res != FR_OK) {
622         ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
623         errno = fresult_to_errno(res);
624         return -1;
625     }
626     return 0;
627 }
628 
vfs_fat_link(void * ctx,const char * n1,const char * n2)629 static int vfs_fat_link(void* ctx, const char* n1, const char* n2)
630 {
631     vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
632     _lock_acquire(&fat_ctx->lock);
633     prepend_drive_to_path(fat_ctx, &n1, &n2);
634     const size_t copy_buf_size = fat_ctx->fs.csize;
635     FRESULT res;
636     FIL* pf1 = (FIL*) ff_memalloc(sizeof(FIL));
637     FIL* pf2 = (FIL*) ff_memalloc(sizeof(FIL));
638     void* buf = ff_memalloc(copy_buf_size);
639     if (buf == NULL || pf1 == NULL || pf2 == NULL) {
640         _lock_release(&fat_ctx->lock);
641         ESP_LOGD(TAG, "alloc failed, pf1=%p, pf2=%p, buf=%p", pf1, pf2, buf);
642         free(pf1);
643         free(pf2);
644         free(buf);
645         errno = ENOMEM;
646         return -1;
647     }
648     memset(pf1, 0, sizeof(*pf1));
649     memset(pf2, 0, sizeof(*pf2));
650     res = f_open(pf1, n1, FA_READ | FA_OPEN_EXISTING);
651     if (res != FR_OK) {
652         _lock_release(&fat_ctx->lock);
653         goto fail1;
654     }
655     res = f_open(pf2, n2, FA_WRITE | FA_CREATE_NEW);
656     _lock_release(&fat_ctx->lock);
657     if (res != FR_OK) {
658         goto fail2;
659     }
660     size_t size_left = f_size(pf1);
661     while (size_left > 0) {
662         size_t will_copy = (size_left < copy_buf_size) ? size_left : copy_buf_size;
663         size_t read;
664         res = f_read(pf1, buf, will_copy, &read);
665         if (res != FR_OK) {
666             goto fail3;
667         } else if (read != will_copy) {
668             res = FR_DISK_ERR;
669             goto fail3;
670         }
671         size_t written;
672         res = f_write(pf2, buf, will_copy, &written);
673         if (res != FR_OK) {
674             goto fail3;
675         } else if (written != will_copy) {
676             res = FR_DISK_ERR;
677             goto fail3;
678         }
679         size_left -= will_copy;
680     }
681 fail3:
682     f_close(pf2);
683 fail2:
684     f_close(pf1);
685 fail1:
686     free(buf);
687     free(pf2);
688     free(pf1);
689     if (res != FR_OK) {
690         ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
691         errno = fresult_to_errno(res);
692         return -1;
693     }
694     return 0;
695 }
696 
vfs_fat_rename(void * ctx,const char * src,const char * dst)697 static int vfs_fat_rename(void* ctx, const char *src, const char *dst)
698 {
699     vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
700     _lock_acquire(&fat_ctx->lock);
701     prepend_drive_to_path(fat_ctx, &src, &dst);
702     FRESULT res = f_rename(src, dst);
703     _lock_release(&fat_ctx->lock);
704     if (res != FR_OK) {
705         ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
706         errno = fresult_to_errno(res);
707         return -1;
708     }
709     return 0;
710 }
711 
vfs_fat_opendir(void * ctx,const char * name)712 static DIR* vfs_fat_opendir(void* ctx, const char* name)
713 {
714     vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
715     _lock_acquire(&fat_ctx->lock);
716     prepend_drive_to_path(fat_ctx, &name, NULL);
717     vfs_fat_dir_t* fat_dir = ff_memalloc(sizeof(vfs_fat_dir_t));
718     if (!fat_dir) {
719         _lock_release(&fat_ctx->lock);
720         errno = ENOMEM;
721         return NULL;
722     }
723     memset(fat_dir, 0, sizeof(*fat_dir));
724 
725     FRESULT res = f_opendir(&fat_dir->ffdir, name);
726     _lock_release(&fat_ctx->lock);
727     if (res != FR_OK) {
728         free(fat_dir);
729         ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
730         errno = fresult_to_errno(res);
731         return NULL;
732     }
733     return (DIR*) fat_dir;
734 }
735 
vfs_fat_closedir(void * ctx,DIR * pdir)736 static int vfs_fat_closedir(void* ctx, DIR* pdir)
737 {
738     assert(pdir);
739     vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir;
740     FRESULT res = f_closedir(&fat_dir->ffdir);
741     free(pdir);
742     if (res != FR_OK) {
743         ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
744         errno = fresult_to_errno(res);
745         return -1;
746     }
747     return 0;
748 }
749 
vfs_fat_readdir(void * ctx,DIR * pdir)750 static struct dirent* vfs_fat_readdir(void* ctx, DIR* pdir)
751 {
752     vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir;
753     struct dirent* out_dirent;
754     int err = vfs_fat_readdir_r(ctx, pdir, &fat_dir->cur_dirent, &out_dirent);
755     if (err != 0) {
756         errno = err;
757         return NULL;
758     }
759     return out_dirent;
760 }
761 
vfs_fat_readdir_r(void * ctx,DIR * pdir,struct dirent * entry,struct dirent ** out_dirent)762 static int vfs_fat_readdir_r(void* ctx, DIR* pdir,
763         struct dirent* entry, struct dirent** out_dirent)
764 {
765     assert(pdir);
766     vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir;
767     FRESULT res = f_readdir(&fat_dir->ffdir, &fat_dir->filinfo);
768     if (res != FR_OK) {
769         *out_dirent = NULL;
770         ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
771         return fresult_to_errno(res);
772     }
773     if (fat_dir->filinfo.fname[0] == 0) {
774         // end of directory
775         *out_dirent = NULL;
776         return 0;
777     }
778     entry->d_ino = 0;
779     if (fat_dir->filinfo.fattrib & AM_DIR) {
780         entry->d_type = DT_DIR;
781     } else {
782         entry->d_type = DT_REG;
783     }
784     strlcpy(entry->d_name, fat_dir->filinfo.fname,
785             sizeof(entry->d_name));
786     fat_dir->offset++;
787     *out_dirent = entry;
788     return 0;
789 }
790 
vfs_fat_telldir(void * ctx,DIR * pdir)791 static long vfs_fat_telldir(void* ctx, DIR* pdir)
792 {
793     assert(pdir);
794     vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir;
795     return fat_dir->offset;
796 }
797 
vfs_fat_seekdir(void * ctx,DIR * pdir,long offset)798 static void vfs_fat_seekdir(void* ctx, DIR* pdir, long offset)
799 {
800     assert(pdir);
801     vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir;
802     FRESULT res;
803     if (offset < fat_dir->offset) {
804         res = f_rewinddir(&fat_dir->ffdir);
805         if (res != FR_OK) {
806             ESP_LOGD(TAG, "%s: rewinddir fresult=%d", __func__, res);
807             errno = fresult_to_errno(res);
808             return;
809         }
810         fat_dir->offset = 0;
811     }
812     while (fat_dir->offset < offset) {
813         res = f_readdir(&fat_dir->ffdir, &fat_dir->filinfo);
814         if (res != FR_OK) {
815             ESP_LOGD(TAG, "%s: f_readdir fresult=%d", __func__, res);
816             errno = fresult_to_errno(res);
817             return;
818         }
819         fat_dir->offset++;
820     }
821 }
822 
vfs_fat_mkdir(void * ctx,const char * name,mode_t mode)823 static int vfs_fat_mkdir(void* ctx, const char* name, mode_t mode)
824 {
825     (void) mode;
826     vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
827     _lock_acquire(&fat_ctx->lock);
828     prepend_drive_to_path(fat_ctx, &name, NULL);
829     FRESULT res = f_mkdir(name);
830     _lock_release(&fat_ctx->lock);
831     if (res != FR_OK) {
832         ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
833         errno = fresult_to_errno(res);
834         return -1;
835     }
836     return 0;
837 }
838 
vfs_fat_rmdir(void * ctx,const char * name)839 static int vfs_fat_rmdir(void* ctx, const char* name)
840 {
841     vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
842     _lock_acquire(&fat_ctx->lock);
843     prepend_drive_to_path(fat_ctx, &name, NULL);
844     FRESULT res = f_unlink(name);
845     _lock_release(&fat_ctx->lock);
846     if (res != FR_OK) {
847         ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
848         errno = fresult_to_errno(res);
849         return -1;
850     }
851     return 0;
852 }
853 
vfs_fat_access(void * ctx,const char * path,int amode)854 static int vfs_fat_access(void* ctx, const char *path, int amode)
855 {
856     FILINFO info;
857     int ret = 0;
858     FRESULT res;
859 
860     vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
861 
862     _lock_acquire(&fat_ctx->lock);
863     prepend_drive_to_path(fat_ctx, &path, NULL);
864     res = f_stat(path, &info);
865     _lock_release(&fat_ctx->lock);
866 
867     if (res == FR_OK) {
868         if (((amode & W_OK) == W_OK) && ((info.fattrib & AM_RDO) == AM_RDO)) {
869             ret = -1;
870             errno = EACCES;
871         }
872         // There is no flag to test readable or executable: we assume that if
873         // it exists then it is readable and executable
874     } else {
875         ret = -1;
876         errno = ENOENT;
877     }
878 
879     return ret;
880 }
881 
vfs_fat_truncate(void * ctx,const char * path,off_t length)882 static int vfs_fat_truncate(void* ctx, const char *path, off_t length)
883 {
884     FRESULT res;
885     FIL* file = NULL;
886 
887     int ret = 0;
888 
889     vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx;
890 
891     if (length < 0) {
892         errno = EINVAL;
893         ret = -1;
894         goto out;
895     }
896 
897     _lock_acquire(&fat_ctx->lock);
898     prepend_drive_to_path(fat_ctx, &path, NULL);
899 
900     file = (FIL*) ff_memalloc(sizeof(FIL));
901     if (file == NULL) {
902         _lock_release(&fat_ctx->lock);
903         ESP_LOGD(TAG, "truncate alloc failed");
904         errno = ENOMEM;
905         ret = -1;
906         goto out;
907     }
908     memset(file, 0, sizeof(*file));
909 
910     res = f_open(file, path, FA_WRITE);
911 
912     if (res != FR_OK) {
913         _lock_release(&fat_ctx->lock);
914         ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
915         errno = fresult_to_errno(res);
916         ret = -1;
917         goto out;
918     }
919 
920     long sz = f_size(file);
921     if (sz < length) {
922         _lock_release(&fat_ctx->lock);
923         ESP_LOGD(TAG, "truncate does not support extending size");
924         errno = EPERM;
925         ret = -1;
926         goto close;
927     }
928 
929     res = f_lseek(file, length);
930     if (res != FR_OK) {
931         _lock_release(&fat_ctx->lock);
932         ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
933         errno = fresult_to_errno(res);
934         ret = -1;
935         goto close;
936     }
937 
938     res = f_truncate(file);
939     _lock_release(&fat_ctx->lock);
940 
941     if (res != FR_OK) {
942         ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
943         errno = fresult_to_errno(res);
944         ret = -1;
945     }
946 
947 close:
948     res = f_close(file);
949 
950     if (res != FR_OK) {
951         ESP_LOGE(TAG, "closing file opened for truncate failed");
952         // Overwrite previous errors, since not being able to close
953         // an opened file is a more critical issue.
954         errno = fresult_to_errno(res);
955         ret = -1;
956     }
957 
958 out:
959     free(file);
960     return ret;
961 }
962 
vfs_fat_utime(void * ctx,const char * path,const struct utimbuf * times)963 static int vfs_fat_utime(void *ctx, const char *path, const struct utimbuf *times)
964 {
965     FILINFO filinfo_time;
966 
967     {
968         struct tm tm_time;
969 
970         if (times) {
971             localtime_r(&times->modtime, &tm_time);
972         } else {
973             // use current time
974             struct timeval tv;
975             gettimeofday(&tv, NULL);
976             localtime_r(&tv.tv_sec, &tm_time);
977         }
978 
979         if (tm_time.tm_year < 80) {
980             // FATFS cannot handle years before 1980
981             errno = EINVAL;
982             return -1;
983         }
984 
985         fat_date_t fdate;
986         fat_time_t ftime;
987 
988         // this time transformation is esentially the reverse of the one in vfs_fat_stat()
989         fdate.mday = tm_time.tm_mday;
990         fdate.mon = tm_time.tm_mon + 1;     // January in fdate.mon is 1, and 0 in tm_time.tm_mon
991         fdate.year = tm_time.tm_year - 80;  // tm_time.tm_year=0 is 1900, tm_time.tm_year=0 is 1980
992         ftime.sec = tm_time.tm_sec / 2,     // ftime.sec counts seconds by 2
993         ftime.min = tm_time.tm_min;
994         ftime.hour = tm_time.tm_hour;
995 
996         filinfo_time.fdate = fdate.as_int;
997         filinfo_time.ftime = ftime.as_int;
998     }
999 
1000     vfs_fat_ctx_t *fat_ctx = (vfs_fat_ctx_t *) ctx;
1001     _lock_acquire(&fat_ctx->lock);
1002     prepend_drive_to_path(fat_ctx, &path, NULL);
1003     FRESULT res = f_utime(path, &filinfo_time);
1004     _lock_release(&fat_ctx->lock);
1005 
1006     if (res != FR_OK) {
1007         ESP_LOGD(TAG, "%s: fresult=%d", __func__, res);
1008         errno = fresult_to_errno(res);
1009         return -1;
1010     }
1011 
1012     return 0;
1013 }
1014 
1015 #endif // CONFIG_VFS_SUPPORT_DIR
1016