// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include #include "esp_vfs.h" #include "esp_log.h" #include "ff.h" #include "diskio.h" typedef struct { char fat_drive[8]; char base_path[ESP_VFS_PATH_MAX]; size_t max_files; FATFS fs; FIL files[0]; _lock_t lock; } vfs_fat_ctx_t; typedef struct { DIR dir; long offset; FF_DIR ffdir; FILINFO filinfo; struct dirent cur_dirent; } vfs_fat_dir_t; static const char* TAG = "vfs_fat"; static size_t vfs_fat_write(void* p, int fd, const void * data, size_t size); static off_t vfs_fat_lseek(void* p, int fd, off_t size, int mode); static ssize_t vfs_fat_read(void* ctx, int fd, void * dst, size_t size); static int vfs_fat_open(void* ctx, const char * path, int flags, int mode); static int vfs_fat_close(void* ctx, int fd); static int vfs_fat_fstat(void* ctx, int fd, struct stat * st); static int vfs_fat_stat(void* ctx, const char * path, struct stat * st); static int vfs_fat_link(void* ctx, const char* n1, const char* n2); static int vfs_fat_unlink(void* ctx, const char *path); static int vfs_fat_rename(void* ctx, const char *src, const char *dst); static DIR* vfs_fat_opendir(void* ctx, const char* name); static struct dirent* vfs_fat_readdir(void* ctx, DIR* pdir); static int vfs_fat_readdir_r(void* ctx, DIR* pdir, struct dirent* entry, struct dirent** out_dirent); static long vfs_fat_telldir(void* ctx, DIR* pdir); static void vfs_fat_seekdir(void* ctx, DIR* pdir, long offset); static int vfs_fat_closedir(void* ctx, DIR* pdir); static int vfs_fat_mkdir(void* ctx, const char* name, mode_t mode); static int vfs_fat_rmdir(void* ctx, const char* name); static vfs_fat_ctx_t* s_fat_ctxs[_VOLUMES] = { NULL, NULL }; //backwards-compatibility with esp_vfs_fat_unregister() static vfs_fat_ctx_t* s_fat_ctx = NULL; static size_t find_context_index_by_path(const char* base_path) { for(size_t i=0; i<_VOLUMES; i++) { if (s_fat_ctxs[i] && !strcmp(s_fat_ctxs[i]->base_path, base_path)) { return i; } } return _VOLUMES; } static size_t find_unused_context_index() { for(size_t i=0; i<_VOLUMES; i++) { if (!s_fat_ctxs[i]) { return i; } } return _VOLUMES; } esp_err_t esp_vfs_fat_register(const char* base_path, const char* fat_drive, size_t max_files, FATFS** out_fs) { size_t ctx = find_context_index_by_path(base_path); if (ctx < _VOLUMES) { return ESP_ERR_INVALID_STATE; } ctx = find_unused_context_index(); if (ctx == _VOLUMES) { return ESP_ERR_NO_MEM; } const esp_vfs_t vfs = { .flags = ESP_VFS_FLAG_CONTEXT_PTR, .write_p = &vfs_fat_write, .lseek_p = &vfs_fat_lseek, .read_p = &vfs_fat_read, .open_p = &vfs_fat_open, .close_p = &vfs_fat_close, .fstat_p = &vfs_fat_fstat, .stat_p = &vfs_fat_stat, .link_p = &vfs_fat_link, .unlink_p = &vfs_fat_unlink, .rename_p = &vfs_fat_rename, .opendir_p = &vfs_fat_opendir, .closedir_p = &vfs_fat_closedir, .readdir_p = &vfs_fat_readdir, .readdir_r_p = &vfs_fat_readdir_r, .seekdir_p = &vfs_fat_seekdir, .telldir_p = &vfs_fat_telldir, .mkdir_p = &vfs_fat_mkdir, .rmdir_p = &vfs_fat_rmdir }; size_t ctx_size = sizeof(vfs_fat_ctx_t) + max_files * sizeof(FIL); vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) calloc(1, ctx_size); if (fat_ctx == NULL) { return ESP_ERR_NO_MEM; } fat_ctx->max_files = max_files; strlcpy(fat_ctx->fat_drive, fat_drive, sizeof(fat_ctx->fat_drive) - 1); strlcpy(fat_ctx->base_path, base_path, sizeof(fat_ctx->base_path) - 1); esp_err_t err = esp_vfs_register(base_path, &vfs, fat_ctx); if (err != ESP_OK) { free(fat_ctx); return err; } _lock_init(&fat_ctx->lock); s_fat_ctxs[ctx] = fat_ctx; //compatibility s_fat_ctx = fat_ctx; *out_fs = &fat_ctx->fs; return ESP_OK; } esp_err_t esp_vfs_fat_unregister_ctx(const char* base_path) { size_t ctx = find_context_index_by_path(base_path); if (ctx == _VOLUMES) { return ESP_ERR_INVALID_STATE; } vfs_fat_ctx_t* fat_ctx = s_fat_ctxs[ctx]; esp_err_t err = esp_vfs_unregister(fat_ctx->base_path); if (err != ESP_OK) { return err; } _lock_close(&fat_ctx->lock); free(fat_ctx); s_fat_ctxs[ctx] = NULL; return ESP_OK; } esp_err_t esp_vfs_fat_unregister() { if (s_fat_ctx == NULL) { return ESP_ERR_INVALID_STATE; } esp_err_t err = esp_vfs_fat_unregister_ctx(s_fat_ctx->base_path); if (err != ESP_OK) { return err; } s_fat_ctx = NULL; return ESP_OK; } static int get_next_fd(vfs_fat_ctx_t* fat_ctx) { for (size_t i = 0; i < fat_ctx->max_files; ++i) { if (fat_ctx->files[i].obj.fs == NULL) { return (int) i; } } return -1; } static int fat_mode_conv(int m) { int res = 0; int acc_mode = m & O_ACCMODE; if (acc_mode == O_RDONLY) { res |= FA_READ; } else if (acc_mode == O_WRONLY) { res |= FA_WRITE; } else if (acc_mode == O_RDWR) { res |= FA_READ | FA_WRITE; } if ((m & O_CREAT) && (m & O_EXCL)) { res |= FA_CREATE_NEW; } else if (m & O_CREAT) { res |= FA_CREATE_ALWAYS; } else if (m & O_APPEND) { res |= FA_OPEN_ALWAYS; } else { res |= FA_OPEN_EXISTING; } return res; } static int fresult_to_errno(FRESULT fr) { switch(fr) { case FR_DISK_ERR: return EIO; case FR_INT_ERR: assert(0 && "fatfs internal error"); return EIO; case FR_NOT_READY: return ENODEV; case FR_NO_FILE: return ENOENT; case FR_NO_PATH: return ENOENT; case FR_INVALID_NAME: return EINVAL; case FR_DENIED: return EACCES; case FR_EXIST: return EEXIST; case FR_INVALID_OBJECT: return EBADF; case FR_WRITE_PROTECTED: return EACCES; case FR_INVALID_DRIVE: return ENXIO; case FR_NOT_ENABLED: return ENODEV; case FR_NO_FILESYSTEM: return ENODEV; case FR_MKFS_ABORTED: return EINTR; case FR_TIMEOUT: return ETIMEDOUT; case FR_LOCKED: return EACCES; case FR_NOT_ENOUGH_CORE: return ENOMEM; case FR_TOO_MANY_OPEN_FILES: return ENFILE; case FR_INVALID_PARAMETER: return EINVAL; case FR_OK: return 0; } assert(0 && "unhandled FRESULT"); return ENOTSUP; } static void file_cleanup(vfs_fat_ctx_t* ctx, int fd) { memset(&ctx->files[fd], 0, sizeof(FIL)); } static void prepend_drive_to_path(void * ctx, const char * path, const char * path2){ static char buf[FILENAME_MAX+3]; static char buf2[FILENAME_MAX+3]; sprintf(buf, "%s%s", ((vfs_fat_ctx_t*)ctx)->fat_drive, path); path = (const char *)buf; if(path2){ sprintf(buf2, "%s%s", ((vfs_fat_ctx_t*)ctx)->fat_drive, path2); path2 = (const char *)buf; } } static int vfs_fat_open(void* ctx, const char * path, int flags, int mode) { prepend_drive_to_path(ctx, path, NULL); ESP_LOGV(TAG, "%s: path=\"%s\", flags=%x, mode=%x", __func__, path, flags, mode); vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; _lock_acquire(&fat_ctx->lock); int fd = get_next_fd(fat_ctx); if (fd < 0) { ESP_LOGE(TAG, "open: no free file descriptors"); errno = ENFILE; fd = -1; goto out; } FRESULT res = f_open(&fat_ctx->files[fd], path, fat_mode_conv(flags)); if (res != FR_OK) { ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); file_cleanup(fat_ctx, fd); errno = fresult_to_errno(res); fd = -1; goto out; } out: _lock_release(&fat_ctx->lock); return fd; } static size_t vfs_fat_write(void* ctx, int fd, const void * data, size_t size) { vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; FIL* file = &fat_ctx->files[fd]; unsigned written = 0; FRESULT res = f_write(file, data, size, &written); if (res != FR_OK) { ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); errno = fresult_to_errno(res); if (written == 0) { return -1; } } return written; } static ssize_t vfs_fat_read(void* ctx, int fd, void * dst, size_t size) { vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; FIL* file = &fat_ctx->files[fd]; unsigned read = 0; FRESULT res = f_read(file, dst, size, &read); if (res != FR_OK) { ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); errno = fresult_to_errno(res); if (read == 0) { return -1; } } return read; } static int vfs_fat_close(void* ctx, int fd) { vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; _lock_acquire(&fat_ctx->lock); FIL* file = &fat_ctx->files[fd]; FRESULT res = f_close(file); file_cleanup(fat_ctx, fd); int rc = 0; if (res != FR_OK) { ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); errno = fresult_to_errno(res); rc = -1; } _lock_release(&fat_ctx->lock); return rc; } static off_t vfs_fat_lseek(void* ctx, int fd, off_t offset, int mode) { vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; FIL* file = &fat_ctx->files[fd]; off_t new_pos; if (mode == SEEK_SET) { new_pos = offset; } else if (mode == SEEK_CUR) { off_t cur_pos = f_tell(file); new_pos = cur_pos + offset; } else if (mode == SEEK_END) { off_t size = f_size(file); new_pos = size + offset; } else { errno = EINVAL; return -1; } FRESULT res = f_lseek(file, new_pos); if (res != FR_OK) { ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); errno = fresult_to_errno(res); return -1; } return new_pos; } static int vfs_fat_fstat(void* ctx, int fd, struct stat * st) { vfs_fat_ctx_t* fat_ctx = (vfs_fat_ctx_t*) ctx; FIL* file = &fat_ctx->files[fd]; st->st_size = f_size(file); st->st_mode = S_IRWXU | S_IRWXG | S_IRWXO | S_IFREG; return 0; } static int vfs_fat_stat(void* ctx, const char * path, struct stat * st) { prepend_drive_to_path(ctx, path, NULL); FILINFO info; FRESULT res = f_stat(path, &info); if (res != FR_OK) { ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); errno = fresult_to_errno(res); return -1; } st->st_size = info.fsize; st->st_mode = S_IRWXU | S_IRWXG | S_IRWXO | ((info.fattrib & AM_DIR) ? S_IFDIR : S_IFREG); struct tm tm; uint16_t fdate = info.fdate; tm.tm_mday = fdate & 0x1f; fdate >>= 5; tm.tm_mon = (fdate & 0xf) - 1; fdate >>=4; tm.tm_year = fdate + 80; uint16_t ftime = info.ftime; tm.tm_sec = (ftime & 0x1f) * 2; ftime >>= 5; tm.tm_min = (ftime & 0x3f); ftime >>= 6; tm.tm_hour = (ftime & 0x1f); st->st_mtime = mktime(&tm); return 0; } static int vfs_fat_unlink(void* ctx, const char *path) { prepend_drive_to_path(ctx, path, NULL); FRESULT res = f_unlink(path); if (res != FR_OK) { ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); errno = fresult_to_errno(res); return -1; } return 0; } static int vfs_fat_link(void* ctx, const char* n1, const char* n2) { prepend_drive_to_path(ctx, n1, n2); const size_t copy_buf_size = 4096; void* buf = malloc(copy_buf_size); if (buf == NULL) { errno = ENOMEM; return -1; } FIL f1; FRESULT res = f_open(&f1, n1, FA_READ | FA_OPEN_EXISTING); if (res != FR_OK) { goto fail1; } FIL f2; res = f_open(&f2, n2, FA_WRITE | FA_CREATE_NEW); if (res != FR_OK) { goto fail2; } size_t size_left = f_size(&f1); while (size_left > 0) { size_t will_copy = (size_left < copy_buf_size) ? size_left : copy_buf_size; size_t read; res = f_read(&f1, buf, will_copy, &read); if (res != FR_OK) { goto fail3; } else if (read != will_copy) { res = FR_DISK_ERR; goto fail3; } size_t written; res = f_write(&f2, buf, will_copy, &written); if (res != FR_OK) { goto fail3; } else if (written != will_copy) { res = FR_DISK_ERR; goto fail3; } size_left -= will_copy; } fail3: f_close(&f2); fail2: f_close(&f1); fail1: free(buf); if (res != FR_OK) { ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); errno = fresult_to_errno(res); return -1; } return 0; } static int vfs_fat_rename(void* ctx, const char *src, const char *dst) { prepend_drive_to_path(ctx, src, dst); FRESULT res = f_rename(src, dst); if (res != FR_OK) { ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); errno = fresult_to_errno(res); return -1; } return 0; } static DIR* vfs_fat_opendir(void* ctx, const char* name) { prepend_drive_to_path(ctx, name, NULL); vfs_fat_dir_t* fat_dir = calloc(1, sizeof(vfs_fat_dir_t)); if (!fat_dir) { errno = ENOMEM; return NULL; } FRESULT res = f_opendir(&fat_dir->ffdir, name); if (res != FR_OK) { free(fat_dir); ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); errno = fresult_to_errno(res); return NULL; } return (DIR*) fat_dir; } static int vfs_fat_closedir(void* ctx, DIR* pdir) { assert(pdir); vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir; FRESULT res = f_closedir(&fat_dir->ffdir); free(pdir); if (res != FR_OK) { ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); errno = fresult_to_errno(res); return -1; } return 0; } static struct dirent* vfs_fat_readdir(void* ctx, DIR* pdir) { vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir; struct dirent* out_dirent; int err = vfs_fat_readdir_r(ctx, pdir, &fat_dir->cur_dirent, &out_dirent); if (err != 0) { errno = err; return NULL; } return out_dirent; } static int vfs_fat_readdir_r(void* ctx, DIR* pdir, struct dirent* entry, struct dirent** out_dirent) { assert(pdir); vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir; FRESULT res = f_readdir(&fat_dir->ffdir, &fat_dir->filinfo); if (res != FR_OK) { ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); return fresult_to_errno(res); } if (fat_dir->filinfo.fname[0] == 0) { // end of directory *out_dirent = NULL; return 0; } entry->d_ino = 0; if (fat_dir->filinfo.fattrib & AM_DIR) { entry->d_type = DT_DIR; } else { entry->d_type = DT_REG; } strlcpy(entry->d_name, fat_dir->filinfo.fname, sizeof(entry->d_name)); fat_dir->offset++; *out_dirent = entry; return 0; } static long vfs_fat_telldir(void* ctx, DIR* pdir) { assert(pdir); vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir; return fat_dir->offset; } static void vfs_fat_seekdir(void* ctx, DIR* pdir, long offset) { assert(pdir); vfs_fat_dir_t* fat_dir = (vfs_fat_dir_t*) pdir; FRESULT res; if (offset < fat_dir->offset) { res = f_rewinddir(&fat_dir->ffdir); if (res != FR_OK) { ESP_LOGD(TAG, "%s: rewinddir fresult=%d", __func__, res); errno = fresult_to_errno(res); return; } fat_dir->offset = 0; } while (fat_dir->offset < offset) { res = f_readdir(&fat_dir->ffdir, &fat_dir->filinfo); if (res != FR_OK) { ESP_LOGD(TAG, "%s: f_readdir fresult=%d", __func__, res); errno = fresult_to_errno(res); return; } fat_dir->offset++; } } static int vfs_fat_mkdir(void* ctx, const char* name, mode_t mode) { (void) mode; prepend_drive_to_path(ctx, name, NULL); FRESULT res = f_mkdir(name); if (res != FR_OK) { ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); errno = fresult_to_errno(res); return -1; } return 0; } static int vfs_fat_rmdir(void* ctx, const char* name) { prepend_drive_to_path(ctx, name, NULL); FRESULT res = f_unlink(name); if (res != FR_OK) { ESP_LOGD(TAG, "%s: fresult=%d", __func__, res); errno = fresult_to_errno(res); return -1; } return 0; }