// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace purefs::fs::drivers { namespace { int translate_error(int error) { switch (error) { case FR_OK: return 0; case FR_NO_FILE: case FR_NO_PATH: case FR_INVALID_NAME: return -ENOENT; case FR_DENIED: return -EACCES; case FR_EXIST: return -EEXIST; case FR_INVALID_OBJECT: return -EBADF; case FR_WRITE_PROTECTED: return -EROFS; case FR_INVALID_DRIVE: case FR_NOT_ENABLED: case FR_NO_FILESYSTEM: return -ENODEV; case FR_NOT_ENOUGH_CORE: return -ENOMEM; case FR_TOO_MANY_OPEN_FILES: return -EMFILE; case FR_INVALID_PARAMETER: return -EINVAL; case FR_LOCKED: case FR_TIMEOUT: case FR_MKFS_ABORTED: case FR_DISK_ERR: case FR_INT_ERR: case FR_NOT_READY: return -EIO; } return -EIO; } uint8_t translate_flags(unsigned flags) { uint8_t fat_mode = 0; switch (flags & O_ACCMODE) { case O_RDONLY: fat_mode |= FA_READ; break; case O_WRONLY: fat_mode |= FA_WRITE; break; case O_RDWR: fat_mode |= (FA_READ | FA_WRITE); break; } if (flags & O_APPEND) fat_mode |= FA_OPEN_APPEND; if (flags & O_CREAT) fat_mode |= FA_OPEN_ALWAYS; if (flags & O_TRUNC) fat_mode |= FA_CREATE_ALWAYS; return fat_mode; } auto translate_fat_attrib_to_mode(BYTE fattrib, bool ro) { decltype(static_cast(nullptr)->st_mode) mode = S_IRUSR | S_IRGRP | S_IROTH; if (fattrib & AM_DIR) { mode |= (S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH); } else { mode |= S_IFREG; } if ((fattrib & AM_RDO) == 0 && !ro) { mode |= (S_IWUSR | S_IWGRP | S_IWOTH); } if (fattrib & AM_HID) {} if (fattrib & AM_SYS) {} if (fattrib & AM_ARC) {} return mode; } auto translate_mode_to_attrib(decltype(static_cast(nullptr)->st_mode) mode) { BYTE attr{0}; if ((mode & (S_IWGRP | S_IWUSR | S_IWOTH)) == 0) attr |= AM_RDO; return attr; } void translate_filinfo_to_stat(const FILINFO &fs, const FIL *fil, bool ro, struct stat &st) { if (fil) { st.st_dev = fil->obj.id; st.st_ino = fil->obj.sclust; } else { st.st_dev = 0; st.st_ino = 0; } st.st_mode = translate_fat_attrib_to_mode(fs.fattrib, ro); st.st_nlink = 1; st.st_uid = 0; st.st_gid = 0; st.st_rdev = 0; st.st_size = fs.fsize; #if FF_MAX_SS != FF_MIN_SS st.st_blksize = fatfs->ssize; #else st.st_blksize = FF_MIN_SS; #endif st.st_blocks = fs.fsize / st.st_blksize; st.st_atime = 0; st.st_mtime = 0; st.st_ctime = 0; } int internal_stat_rootdir(const char *translated_path, bool ro, struct stat *entry) { FATFS *fs; DWORD bfree; FRESULT res = f_getfree(translated_path, &bfree, &fs); if (res != FR_OK) { return -EIO; } entry->st_dev = 0; entry->st_ino = 0; entry->st_mode = (S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH | S_IRUSR | S_IRGRP | S_IROTH); if (!ro) { entry->st_mode |= S_IWUSR | S_IWGRP | S_IWOTH; } entry->st_nlink = 1; entry->st_uid = 0; entry->st_gid = 0; entry->st_rdev = 0; entry->st_size = 0; #if FF_MAX_SS != FF_MIN_SS entry->st_blksize = fatfs->ssize; #else entry->st_blksize = FF_MIN_SS; #endif entry->st_blocks = fs->fsize / entry->st_blksize; entry->st_atime = 0; entry->st_mtime = 0; entry->st_ctime = 0; return 0; } } // namespace auto filesystem_vfat::mount_prealloc(std::shared_ptr diskh, std::string_view path, unsigned flags) -> fsmount { return std::make_shared(diskh, path, flags, shared_from_this()); } auto filesystem_vfat::mount(fsmount mnt, const void *data) noexcept -> int { auto disk = mnt->disk(); if (!disk) { return -EIO; } auto ret = ffat::internal::append_volume(disk); if (ret < 0) { LOG_ERROR("Unable to attach volume to ff layer with errno %i", ret); return ret; } auto vmnt = std::dynamic_pointer_cast(mnt); if (!vmnt) { LOG_ERROR("Non VFAT mount point"); return -EIO; } vmnt->ff_drive(ret); ret = f_mount(vmnt->fatfs(), vmnt->ff_drive(), 1); ret = translate_error(ret); if (!ret) { filesystem_operations::mount(mnt, data); } return ret; } auto filesystem_vfat::filesystem_register_completed() const noexcept -> int { const auto dmgr = disk_mngr(); if (dmgr) { ffat::internal::reset_volumes(dmgr); return 0; } else { return -EIO; } } auto filesystem_vfat::umount(fsmount mnt) noexcept -> int { auto vmnt = std::dynamic_pointer_cast(mnt); if (!vmnt) { LOG_ERROR("Non VFAT mount point"); return -EIO; } const auto lun = vmnt->ff_lun(); if (lun < 0) { LOG_ERROR("Wrong ff_lun"); return -ENXIO; } auto disk = mnt->disk(); if (!disk) { return -EIO; } int ret = f_unmount(vmnt->ff_drive()); ret = translate_error(ret); if (!ret) { ret = ffat::internal::remove_volume(disk); filesystem_operations::umount(mnt); } return ret; } auto filesystem_vfat::stat_vfs(fsmount mnt, std::string_view, struct statvfs &stat) const noexcept -> int { auto vmnt = std::dynamic_pointer_cast(mnt); if (!vmnt) { LOG_ERROR("Non VFAT mount point"); return -EIO; } DWORD xfree; FATFS *fatfs; const auto res = f_getfree(vmnt->ff_drive(), &xfree, &fatfs); if (res != FR_OK) { return -EIO; } /* * _MIN_SS holds the sector size. It is one of the configuration * constants used by the FS module */ #if FF_MAX_SS != FF_MIN_SS stat.f_frsize = fatfs->csize * fatfs->ssize; #else stat.f_frsize = fatfs->csize * FF_MIN_SS; #endif stat.f_bsize = stat.f_frsize; stat.f_blocks = (fatfs->n_fatent - 2); stat.f_bfree = xfree; stat.f_bavail = xfree; stat.f_flag = vmnt->flags(); stat.f_files = 0; stat.f_ffree = 0; stat.f_favail = 0; stat.f_fsid = 0; stat.f_namemax = PATH_MAX; return translate_error(res); } auto filesystem_vfat::open(fsmount mnt, std::string_view path, int flags, int mode) noexcept -> fsfile { auto vmnt = std::dynamic_pointer_cast(mnt); if (!vmnt) { LOG_ERROR("Non VFAT mount point"); return nullptr; } const auto fspath = vmnt->native_path(path); const auto fsflags = translate_flags(flags); auto fileo = std::make_shared(mnt, fspath, flags); auto ret = f_open(fileo->ff_filp(), fspath.c_str(), fsflags); fileo->error(translate_error(ret)); return fileo; } auto filesystem_vfat::close(fsfile zfile) noexcept -> int { auto vfile = std::dynamic_pointer_cast(zfile); if (!vfile) { LOG_ERROR("Non fat filesystem pointer"); return -EBADF; } const auto fres = f_close(vfile->ff_filp()); return translate_error(fres); } auto filesystem_vfat::write(fsfile zfile, const char *ptr, size_t len) noexcept -> ssize_t { auto vfile = std::dynamic_pointer_cast(zfile); if (!vfile) { LOG_ERROR("Non fat filesystem pointer"); return -EBADF; } UINT bytes_written; const auto fres = f_write(vfile->ff_filp(), ptr, len, &bytes_written); return (fres == FR_OK) ? (bytes_written) : translate_error(fres); } auto filesystem_vfat::read(fsfile zfile, char *ptr, size_t len) noexcept -> ssize_t { auto vfile = std::dynamic_pointer_cast(zfile); if (!vfile) { LOG_ERROR("Non fat filesystem pointer"); return -EBADF; } UINT bytes_read; const auto fres = f_read(vfile->ff_filp(), ptr, len, &bytes_read); return (fres == FR_OK) ? (bytes_read) : translate_error(fres); } auto filesystem_vfat::seek(fsfile zfile, off_t pos, int dir) noexcept -> off_t { auto vfile = std::dynamic_pointer_cast(zfile); if (!vfile) { LOG_ERROR("Non fat filesystem pointer"); return -EBADF; } off_t cpos{0}; auto fp = vfile->ff_filp(); if (f_error(fp) != FR_OK) { return translate_error(f_error(fp)); } if (dir == SEEK_CUR) { cpos = f_tell(fp); } else if (dir == SEEK_SET) { cpos = 0; } else if (dir == SEEK_END) { cpos = f_size(fp); } else { return -EINVAL; } const auto newpos = cpos + pos; if (newpos < 0) { return -ENXIO; } FRESULT fres{FR_OK}; if (f_tell(fp) != static_cast(newpos)) { fres = f_lseek(fp, newpos); } return (fres == FR_OK) ? (f_tell(fp)) : translate_error(fres); } auto filesystem_vfat::fstat(fsfile zfile, struct stat &st) noexcept -> int { auto vfile = std::dynamic_pointer_cast(zfile); if (!vfile) { LOG_ERROR("Non fat filesystem pointer"); return -EBADF; } FILINFO finfo; const auto fres = f_stat(vfile->open_path().c_str(), &finfo); if (fres == FR_OK) { const auto vmnt = vfile->mntpoint(); if (vmnt) { translate_filinfo_to_stat(finfo, vfile->ff_filp(), vmnt->is_ro(), st); } else { return -EIO; } } return translate_error(fres); } auto filesystem_vfat::stat(fsmount mnt, std::string_view file, struct stat &st) noexcept -> int { auto vmnt = std::dynamic_pointer_cast(mnt); if (!vmnt) { LOG_ERROR("Non VFAT mount point"); return -EBADF; } FILINFO finfo; const auto fspath = vmnt->native_path(file); static constexpr auto slash_pos = 2U; static constexpr auto root_size = 3U; static constexpr auto empty_root_size = 2U; if ((fspath[slash_pos] == '/' && fspath.size() == root_size) || fspath.size() == empty_root_size) { return internal_stat_rootdir(fspath.c_str(), vmnt->is_ro(), &st); } else { const int fres = f_stat(fspath.c_str(), &finfo); if (fres == FR_OK) { translate_filinfo_to_stat(finfo, nullptr, vmnt->is_ro(), st); } return translate_error(fres); } } auto filesystem_vfat::unlink(fsmount mnt, std::string_view name) noexcept -> int { auto vmnt = std::dynamic_pointer_cast(mnt); if (!vmnt) { LOG_ERROR("Non VFAT mount point"); return -ENXIO; } const auto fspath = vmnt->native_path(name); const auto fret = f_unlink(fspath.c_str()); return translate_error(fret); } auto filesystem_vfat::rmdir(fsmount mnt, std::string_view name) noexcept -> int { auto vmnt = std::dynamic_pointer_cast(mnt); if (!vmnt) { LOG_ERROR("Non VFAT mount point"); return -ENXIO; } const auto fspath = vmnt->native_path(name); const auto fret = f_rmdir(fspath.c_str()); return translate_error(fret); } auto filesystem_vfat::rename(fsmount mnt, std::string_view oldname, std::string_view newname) noexcept -> int { auto vmnt = std::dynamic_pointer_cast(mnt); if (!vmnt) { LOG_ERROR("Non VFAT mount point"); return -ENXIO; } const auto fsold = vmnt->native_path(oldname); const auto fsnew = vmnt->native_path(newname); const auto fret = f_rename(fsold.c_str(), fsnew.c_str()); return translate_error(fret); } auto filesystem_vfat::mkdir(fsmount mnt, std::string_view path, int mode) noexcept -> int { auto vmnt = std::dynamic_pointer_cast(mnt); if (!vmnt) { LOG_ERROR("Non VFAT mount point"); return -ENXIO; } const auto fspath = vmnt->native_path(path); const auto fret = f_mkdir(fspath.c_str()); return translate_error(fret); } auto filesystem_vfat::diropen(fsmount mnt, std::string_view path) noexcept -> fsdir { auto vmnt = std::dynamic_pointer_cast(mnt); if (!vmnt) { LOG_ERROR("Non VFAT mount point"); return nullptr; } const auto fspath = vmnt->native_path(path); const auto dirp = std::make_shared(mnt, 0); const auto fret = f_opendir(dirp->ff_dirp(), fspath.c_str()); dirp->error(translate_error(fret)); return dirp; } auto filesystem_vfat::dirreset(fsdir dirstate) noexcept -> int { auto dirp = std::dynamic_pointer_cast(dirstate); if (!dirp) { LOG_ERROR("Non VFAT directory handle"); return -ENXIO; } const auto ferr = f_rewinddir(dirp->ff_dirp()); return translate_error(ferr); } auto filesystem_vfat::dirnext(fsdir dirstate, std::string &filename, struct stat &filestat) -> int { auto dirp = std::dynamic_pointer_cast(dirstate); if (!dirp) { LOG_ERROR("Non VFAT directory handle"); return -ENXIO; } FILINFO ffinfo; const auto ferr = f_readdir(dirp->ff_dirp(), &ffinfo); if (ferr == FR_OK) { if (ffinfo.fname[0] == '\0') { return -ENODATA; } else { const auto vmnt = dirp->mntpoint(); if (vmnt) { translate_filinfo_to_stat(ffinfo, nullptr, vmnt->is_ro(), filestat); filename = ffinfo.fname; } else { return -EIO; } } } return translate_error(ferr); } auto filesystem_vfat::dirclose(fsdir dirstate) noexcept -> int { auto dirp = std::dynamic_pointer_cast(dirstate); if (!dirp) { LOG_ERROR("Not a vfat directory handle"); return -ENXIO; } const auto ferr = f_closedir(dirp->ff_dirp()); return translate_error(ferr); } auto filesystem_vfat::ftruncate(fsfile zfile, off_t len) noexcept -> int { auto vfile = std::dynamic_pointer_cast(zfile); if (!vfile) { LOG_ERROR("Non fat filesystem pointer"); return -EBADF; } int fres = f_lseek(vfile->ff_filp(), len); if (fres != FR_OK) { return translate_error(fres); } fres = f_truncate(vfile->ff_filp()); return translate_error(fres); } auto filesystem_vfat::fsync(fsfile zfile) noexcept -> int { auto vfile = std::dynamic_pointer_cast(zfile); if (!vfile) { LOG_ERROR("Non fat filesystem pointer"); return -EBADF; } const int fres = f_sync(vfile->ff_filp()); return translate_error(fres); } auto filesystem_vfat::isatty(fsfile zfile) noexcept -> int { // NOTE: Handle fvat is always not a tty return 0; } auto filesystem_vfat::chmod(fsmount mnt, std::string_view path, mode_t mode) noexcept -> int { auto vmnt = std::dynamic_pointer_cast(mnt); if (!vmnt) { LOG_ERROR("Non VFAT mount point"); return -ENXIO; } const auto fspath = vmnt->native_path(path); const auto fret = f_chmod(fspath.c_str(), translate_mode_to_attrib(mode), translate_mode_to_attrib(mode)); return translate_error(fret); } auto filesystem_vfat::fchmod(fsfile zfile, mode_t mode) noexcept -> int { auto vfile = std::dynamic_pointer_cast(zfile); if (!vfile) { LOG_ERROR("Non fat filesystem pointer"); return -EBADF; } const auto fspath = vfile->open_path(); const auto fret = f_chmod(fspath.c_str(), translate_mode_to_attrib(mode), translate_mode_to_attrib(mode)); return translate_error(fret); } } // namespace purefs::fs::drivers