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(×->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