/*************************************************************************** * Copyright (C) 2013 by Terraneo Federico * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * As a special exception, if other files instantiate templates or use * * macros or inline functions from this file, or you compile this file * * and link it with other works to produce a work based on this file, * * this file does not by itself cause the resulting work to be covered * * by the GNU General Public License. However the source code for this * * file must still be made available in accordance with the GNU General * * Public License. This exception does not invalidate any other reasons * * why a work based on this file might be covered by the GNU General * * Public License. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see * ***************************************************************************/ #include "fat32.h" #include #include #include #include #include #include #include #include #include #include "filesystem/stringpart.h" #include "filesystem/ioctl.h" #include "util/unicode.h" using namespace std; namespace miosix { #ifdef WITH_FILESYSTEM /** * Translate between FATFS error codes and POSIX ones * \param ec FATS error code * \return POSIX error code */ static int translateError(int ec) { switch(ec) { case FR_OK: return 0; case FR_NO_FILE: case FR_NO_PATH: return -ENOENT; case FR_DENIED: return -ENOSPC; case FR_EXIST: return -EEXIST; case FR_WRITE_PROTECTED: return -EROFS; case FR_LOCKED: return -EBUSY; case FR_NOT_ENOUGH_CORE: return -ENOMEM; case FR_TOO_MANY_OPEN_FILES: return -ENFILE; default: return -EACCES; } } /** * Directory class for Fat32Fs */ class Fat32Directory : public DirectoryBase { public: /** * \param parent parent filesystem * \param mutex mutex to lock when accessing the fiesystem * \param currentInode inode value for '.' entry * \param parentInode inode value for '..' entry */ Fat32Directory(intrusive_ref_ptr parent, FastMutex& mutex, int currentInode, int parentInode) : DirectoryBase(parent), mutex(mutex), currentInode(currentInode), parentInode(parentInode), first(true), unfinished(false) { //Make sure a closedir of an uninitialized dir won't do any damage dir.fs=0; fi.lfname=lfn; fi.lfsize=sizeof(lfn); } /** * \return the underlying directory object */ DIR_ *directory() { return &dir; } /** * Also directories can be opened as files. In this case, this system call * allows to retrieve directory entries. * \param dp pointer to a memory buffer where one or more struct dirent * will be placed. dp must be four words aligned. * \param len memory buffer size. * \return the number of bytes read on success, or a negative number on * failure. */ virtual int getdents(void *dp, int len); /** * Destructor */ virtual ~Fat32Directory(); private: FastMutex& mutex; ///< Parent filesystem's mutex DIR_ dir; ///< Directory object FILINFO fi; ///< Information on a file int currentInode; ///< Inode of '.' int parentInode; ///< Inode of '..' bool first; ///< To display '.' and '..' entries bool unfinished; ///< True if fi contains unread data char lfn[(_MAX_LFN+1)*2]; ///< Long file name }; // // class Fat32Directory // int Fat32Directory::getdents(void *dp, int len) { if(len(dp); char *buffer=begin; char *end=buffer+len; Lock l(mutex); if(first) { first=false; addDefaultEntries(&buffer,currentInode,parentInode); } if(unfinished) { unfinished=false; char type=fi.fattrib & AM_DIR ? DT_DIR : DT_REG; StringPart name(fi.lfname); if(addEntry(&buffer,end,fi.inode,type,name)<0) return -EINVAL; } for(;;) { if(int res=translateError(f_readdir(&dir,&fi))) return res; if(fi.fname[0]=='\0') { addTerminatingEntry(&buffer,end); return buffer-begin; } char type=fi.fattrib & AM_DIR ? DT_DIR : DT_REG; StringPart name(fi.lfname); if(addEntry(&buffer,end,fi.inode,type,name)<0) { unfinished=true; return buffer-begin; } } } Fat32Directory::~Fat32Directory() { Lock l(mutex); f_closedir(&dir); } /** * Files of the Fat32Fs filesystem */ class Fat32File : public FileBase { public: /** * Constructor * \param parent the filesystem to which this file belongs */ Fat32File(intrusive_ref_ptr parent, FastMutex& mutex); /** * Write data to the file, if the file supports writing. * \param data the data to write * \param len the number of bytes to write * \return the number of written characters, or a negative number in case * of errors */ virtual ssize_t write(const void *data, size_t len); /** * Read data from the file, if the file supports reading. * \param data buffer to store read data * \param len the number of bytes to read * \return the number of read characters, or a negative number in case * of errors */ virtual ssize_t read(void *data, size_t len); /** * Move file pointer, if the file supports random-access. * \param pos offset to sum to the beginning of the file, current position * or end of file, depending on whence * \param whence SEEK_SET, SEEK_CUR or SEEK_END * \return the offset from the beginning of the file if the operation * completed, or a negative number in case of errors */ virtual off_t lseek(off_t pos, int whence); /** * Return file information. * \param pstat pointer to stat struct * \return 0 on success, or a negative number on failure */ virtual int fstat(struct stat *pstat) const; /** * Perform various operations on a file descriptor * \param cmd specifies the operation to perform * \param arg optional argument that some operation require * \return the exact return value depends on CMD, -1 is returned on error */ virtual int ioctl(int cmd, void *arg); /** * \return the FatFs FIL object */ FIL *fil() { return &file; } /** * \param inode file inode */ void setInode(int inode) { this->inode=inode; } /** * Destructor */ ~Fat32File(); private: FIL file; FastMutex& mutex; int inode; }; // // class Fat32File // Fat32File::Fat32File(intrusive_ref_ptr parent, FastMutex& mutex) : FileBase(parent), mutex(mutex), inode(0) {} ssize_t Fat32File::write(const void *data, size_t len) { Lock l(mutex); unsigned int bytesWritten; if(int res=translateError(f_write(&file,data,len,&bytesWritten))) return res; #ifdef SYNC_AFTER_WRITE if(f_sync(&file)!=FR_OK) return -EIO; #endif //SYNC_AFTER_WRITE return static_cast(bytesWritten); } ssize_t Fat32File::read(void *data, size_t len) { Lock l(mutex); unsigned int bytesRead; if(int res=translateError(f_read(&file,data,len,&bytesRead))) return res; return static_cast(bytesRead); } off_t Fat32File::lseek(off_t pos, int whence) { Lock l(mutex); off_t offset; switch(whence) { case SEEK_CUR: offset=static_cast(f_tell(&file))+pos; break; case SEEK_SET: offset=pos; break; case SEEK_END: offset=static_cast(f_size(&file))+pos; break; default: return -EINVAL; } //We don't support seek past EOF for Fat32 if(offset<0 || offset>static_cast(f_size(&file))) return -EOVERFLOW; if(int result=translateError( f_lseek(&file,static_cast(offset)))) return result; return offset; } int Fat32File::fstat(struct stat *pstat) const { memset(pstat,0,sizeof(struct stat)); pstat->st_dev=getParent()->getFsId(); pstat->st_ino=inode; pstat->st_mode=S_IFREG | 0755; //-rwxr-xr-x pstat->st_nlink=1; pstat->st_size=f_size(&file); pstat->st_blksize=512; pstat->st_blocks=(static_cast(f_size(&file))+511)/512; return 0; } int Fat32File::ioctl(int cmd, void *arg) { if(cmd!=IOCTL_SYNC) return -ENOTTY; Lock l(mutex); return translateError(f_sync(&file)); } Fat32File::~Fat32File() { Lock l(mutex); if(inode) f_close(&file); //TODO: what to do with error code? } // // class Fat32Fs // Fat32Fs::Fat32Fs(intrusive_ref_ptr disk) : mutex(FastMutex::RECURSIVE), failed(true) { filesystem.drv=disk; failed=f_mount(&filesystem,1,false)!=FR_OK; } int Fat32Fs::open(intrusive_ref_ptr& file, StringPart& name, int flags, int mode) { if(failed) return -ENOENT; flags++; //To convert from O_RDONLY, O_WRONLY, ... to _FREAD, _FWRITE, ... // Code path checklist: // Not existent | Regular file | Directory | // ok | ok | ok | _FREAD // ok | ok | ok | _FWRITE // ok | ok | ok | _FWRITE | _FCREAT struct stat st; bool statFailed=false; if(int result=lstat(name,&st)) { //If _FCREAT the file may not yet exist as we are asked to create it if((flags & (_FWRITE | _FCREAT)) != (_FWRITE | _FCREAT)) return result; else statFailed=true; } //Using if short circuit, as st is not initialized if statFailed if(statFailed || !S_ISDIR(st.st_mode)) { //About to open a file BYTE openflags=0; if(flags & _FREAD) openflags|=FA_READ; if(flags & _FWRITE) openflags|=FA_WRITE; if(flags & _FTRUNC) openflags|=FA_CREATE_ALWAYS;//Truncate else if(flags & _FCREAT) openflags|=FA_OPEN_ALWAYS;//If !exists create else openflags|=FA_OPEN_EXISTING;//If not exists fail intrusive_ref_ptr f(new Fat32File(shared_from_this(),mutex)); Lock l(mutex); if(int res=translateError(f_open(&filesystem,f->fil(),name.c_str(),openflags))) return res; if(statFailed) { //If we didn't stat before, stat now to get the inode if(int result=lstat(name,&st)) return result; } f->setInode(st.st_ino); //Can't open files larger than INT_MAX if(static_cast(f_size(f->fil()))<0) return -EOVERFLOW; #ifdef SYNC_AFTER_WRITE if(f_sync(f->fil())!=FR_OK) return -EFAULT; #endif //SYNC_AFTER_WRITE //If file opened for appending, seek to end of file if(flags & _FAPPEND) if(f_lseek(f->fil(),f_size(f->fil()))!=FR_OK) return -EFAULT; file=f; return 0; } else { //About to open a directory if(flags & (_FWRITE | _FAPPEND | _FCREAT | _FTRUNC)) return -EISDIR; int parentInode; if(name.empty()==false) { unsigned int lastSlash=name.findLastOf('/'); if(lastSlash!=string::npos) { StringPart parent(name,lastSlash); struct stat st2; if(int result=lstat(parent,&st2)) return result; parentInode=st2.st_ino; } else parentInode=1; //Asked to list subdir of root } else parentInode=parentFsMountpointInode; //Asked to list root dir intrusive_ref_ptr d( new Fat32Directory(shared_from_this(),mutex,st.st_ino,parentInode)); Lock l(mutex); if(int res=translateError(f_opendir(&filesystem,d->directory(),name.c_str()))) return res; file=d; return 0; } } int Fat32Fs::lstat(StringPart& name, struct stat *pstat) { if(failed) return -ENOENT; memset(pstat,0,sizeof(struct stat)); pstat->st_dev=filesystemId; pstat->st_nlink=1; pstat->st_blksize=512; Lock l(mutex); if(name.empty()) { //We are asked to stat the filesystem's root directory //By convention, we use 1 for root dir inode, see INODE() macro in ff.c pstat->st_ino=1; pstat->st_mode=S_IFDIR | 0755; //drwxr-xr-x return 0; } FILINFO info; info.lfname=0; //We're not interested in getting the lfname info.lfsize=0; if(int result=translateError(f_stat(&filesystem,name.c_str(),&info))) return result; pstat->st_ino=info.inode; pstat->st_mode=(info.fattrib & AM_DIR) ? S_IFDIR | 0755 //drwxr-xr-x : S_IFREG | 0755; //-rwxr-xr-x pstat->st_size=info.fsize; pstat->st_blocks=(info.fsize+511)/512; return 0; } int Fat32Fs::unlink(StringPart& name) { return unlinkRmdirHelper(name,false); } int Fat32Fs::rename(StringPart& oldName, StringPart& newName) { if(failed) return -ENOENT; Lock l(mutex); return translateError(f_rename(&filesystem,oldName.c_str(),newName.c_str())); } int Fat32Fs::mkdir(StringPart& name, int mode) { if(failed) return -ENOENT; Lock l(mutex); return translateError(f_mkdir(&filesystem,name.c_str())); } int Fat32Fs::rmdir(StringPart& name) { return unlinkRmdirHelper(name,true); } Fat32Fs::~Fat32Fs() { if(failed) return; f_mount(&filesystem,0,true); //TODO: what to do with error code? filesystem.drv->ioctl(IOCTL_SYNC,0); filesystem.drv.reset(); } int Fat32Fs::unlinkRmdirHelper(StringPart& name, bool delDir) { if(failed) return -ENOENT; Lock l(mutex); struct stat st; if(int result=lstat(name,&st)) return result; if(delDir) { if(!S_ISDIR(st.st_mode)) return -ENOTDIR; } else if(S_ISDIR(st.st_mode)) return -EISDIR; return translateError(f_unlink(&filesystem,name.c_str())); } #endif //WITH_FILESYSTEM } //namespace miosix