/*************************************************************************** * 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 "file_access.h" #include #include #include #include "console/console_device.h" #include "mountpointfs/mountpointfs.h" #include "fat32/fat32.h" #include "kernel/logging.h" #ifdef WITH_PROCESSES #include "kernel/process.h" #endif //WITH_PROCESSES using namespace std; #ifdef WITH_FILESYSTEM namespace miosix { /* * A note on the use of strings in this file. This file uses three string * types: C string, C++ std::string and StringPart which is an efficent * in-place substring of either a C or C++ string. * * The functions which are meant to be used by clients of the filesystem * API take file names as C strings. This is becase that's the signature * of the POSIX calls, such as fopen(), stat(), umount(), ... It is * worth noticing that these C strings are const. However, resolving paths * requires a writable scratchpad string to be able to remove * useless path components, such as "/./", go backwards when a "/../" is * found, and follow symbolic links. To this end, all these functions * make a copy of the passed string into a temporary C++ string that * will be deallocated as soon as the function returns. * Resolving a path, however, requires to scan all its path components * one by one, checking if the path up to that point is a symbolic link * or a mountpoint of a filesystem. * For example, resolving "/home/test/file" requires to make three * substrings, "/home", "/home/test" and "/home/test/file". Of course, * one can simply use the substr() member function of the std::string * class. However, this means making lots of tiny memory allocations on * the heap, increasing the RAM footprint to resolve a path. To this end, * the StringPart class was introduced, to create fast in-place substring * of a string. */ // // class FileDescriptorTable // FileDescriptorTable::FileDescriptorTable() : mutex(FastMutex::RECURSIVE), cwd("/") { FilesystemManager::instance().addFileDescriptorTable(this); files[0]=files[1]=files[2]=intrusive_ref_ptr( new TerminalDevice(DefaultConsole::instance().get())); } FileDescriptorTable::FileDescriptorTable(const FileDescriptorTable& rhs) : mutex(FastMutex::RECURSIVE), cwd(rhs.cwd) { //No need to lock the mutex since we are in a constructor and there can't //be pointers to this in other threads yet for(int i=0;ifiles[i]=atomic_load(&rhs.files[i]); FilesystemManager::instance().addFileDescriptorTable(this); } FileDescriptorTable& FileDescriptorTable::operator=( const FileDescriptorTable& rhs) { Lock l(mutex); for(int i=0;ifiles[i],atomic_load(&rhs.files[i])); return *this; } int FileDescriptorTable::open(const char* name, int flags, int mode) { if(name==0 || name[0]=='\0') return -EFAULT; Lock l(mutex); for(int i=3;iopen(files[i],sp,flags,mode); if(result==0) return i; //The file descriptor else return result; //The error code } return -ENFILE; } int FileDescriptorTable::close(int fd) { //No need to lock the mutex when deleting if(fd<0 || fd>=MAX_OPEN_FILES) return -EBADF; intrusive_ref_ptr toClose; toClose=atomic_exchange(files+fd,intrusive_ref_ptr()); if(!toClose) return -EBADF; //File entry was not open return 0; } void FileDescriptorTable::closeAll() { for(int i=0;i()); } int FileDescriptorTable::getcwd(char *buf, size_t len) { if(buf==0 || len<2) return -EINVAL; //We don't support the buf==0 extension Lock l(mutex); struct stat st; if(stat(".",&st) || !S_ISDIR(st.st_mode)) return -ENOENT; if(cwd.length()>len) return -ERANGE; strncpy(buf,cwd.c_str(),len); if(cwd.length()>1) buf[cwd.length()-1]='\0'; //Erase last '/' in cwd return 0; } int FileDescriptorTable::chdir(const char* name) { if(name==0 || name[0]=='\0') return -EFAULT; size_t len=strlen(name); if(name[len-1]!='/') len++; //Reserve room for trailing slash Lock l(mutex); if(name[0]!='/') len+=cwd.length(); if(len>PATH_MAX) return -ENAMETOOLONG; string newCwd; newCwd.reserve(len); if(name[0]=='/') newCwd=name; else { newCwd=cwd; newCwd+=name; } ResolvedPath openData=FilesystemManager::instance().resolvePath(newCwd); if(openData.result<0) return openData.result; struct stat st; StringPart sp(newCwd,string::npos,openData.off); if(int result=openData.fs->lstat(sp,&st)) return result; if(!S_ISDIR(st.st_mode)) return -ENOTDIR; //NOTE: put after resolvePath() as it strips trailing / //Also put after lstat() as it fails if path has a trailing slash newCwd+='/'; cwd=newCwd; return 0; } int FileDescriptorTable::mkdir(const char *name, int mode) { if(name==0 || name[0]=='\0') return -EFAULT; string path=absolutePath(name); if(path.empty()) return -ENAMETOOLONG; ResolvedPath openData=FilesystemManager::instance().resolvePath(path,true); if(openData.result<0) return openData.result; StringPart sp(path,string::npos,openData.off); return openData.fs->mkdir(sp,mode); } int FileDescriptorTable::rmdir(const char *name) { if(name==0 || name[0]=='\0') return -EFAULT; string path=absolutePath(name); if(path.empty()) return -ENAMETOOLONG; ResolvedPath openData=FilesystemManager::instance().resolvePath(path,true); if(openData.result<0) return openData.result; StringPart sp(path,string::npos,openData.off); return openData.fs->rmdir(sp); } int FileDescriptorTable::unlink(const char *name) { if(name==0 || name[0]=='\0') return -EFAULT; string path=absolutePath(name); if(path.empty()) return -ENAMETOOLONG; return FilesystemManager::instance().unlinkHelper(path); } int FileDescriptorTable::rename(const char *oldName, const char *newName) { if(oldName==0 || oldName[0]=='\0') return -EFAULT; if(newName==0 || newName[0]=='\0') return -EFAULT; string oldPath=absolutePath(oldName); string newPath=absolutePath(newName); if(oldPath.empty() || newPath.empty()) return -ENAMETOOLONG; return FilesystemManager::instance().renameHelper(oldPath,newPath); } int FileDescriptorTable::statImpl(const char* name, struct stat* pstat, bool f) { if(name==0 || name[0]=='\0' || pstat==0) return -EFAULT; string path=absolutePath(name); if(path.empty()) return -ENAMETOOLONG; return FilesystemManager::instance().statHelper(path,pstat,f); } FileDescriptorTable::~FileDescriptorTable() { FilesystemManager::instance().removeFileDescriptorTable(this); //There's no need to lock the mutex and explicitly close files eventually //left open, because if there are other threads accessing this while we are //being deleted we have bigger problems anyway } string FileDescriptorTable::absolutePath(const char* path) { size_t len=strlen(path); if(len>PATH_MAX) return ""; if(path[0]=='/') return path; Lock l(mutex); if(len+cwd.length()>PATH_MAX) return ""; return cwd+path; } /** * This class implements the path resolution logic */ class PathResolution { public: /** * Constructor * \param fs map of all mounted filesystems */ PathResolution(const map >& fs) : filesystems(fs) {} /** * The main purpose of this class, resolve a path * \param path inout parameter with the path to resolve. The resolved path * will be modified in-place in this string. The path must be absolute and * start with a "/". The caller is responsible for that. * \param followLastSymlink if true, follow last symlink * \return a resolved path */ ResolvedPath resolvePath(string& path, bool followLastSymlink); private: /** * Handle a /../ in a path * \param path path string * \param slash path[slash] is the / character after the .. * \return 0 on success, a negative number on error */ int upPathComponent(string& path, size_t slash); /** * Handle a normal path component in a path, i.e, a path component * that is neither //, /./ or /../ * \param path path string * \param followIfSymlink if true, follow symbolic links * \return 0 on success, or a negative number on error */ int normalPathComponent(string& path, bool followIfSymlink); /** * Follow a symbolic link * \param path path string. The relative path into the current filesystem * must be a symbolic link (verified by the caller). * \return 0 on success, a negative number on failure */ int followSymlink(string& path); /** * Find to which filesystem this path belongs * \param path path string. * \return 0 on success, a negative number on failure */ int recursiveFindFs(string& path); /// Mounted filesystems const map >& filesystems; /// Pointer to root filesystem intrusive_ref_ptr root; /// Current filesystem while looking up path intrusive_ref_ptr fs; /// True if current filesystem supports symlinks bool syms; /// path[index] is first unhandled char size_t index; /// path.substr(indexIntoFs) is the relative path to current filesystem size_t indexIntoFs; /// How many components does the relative path have in current fs int depthIntoFs; /// How many symlinks we've found so far int linksFollowed; /// Maximum number of symbolic links to follow (to avoid endless loops) static const int maxLinkToFollow=2; }; ResolvedPath PathResolution::resolvePath(string& path, bool followLastSymlink) { map >::const_iterator it; it=filesystems.find(StringPart("/")); if(it==filesystems.end()) return ResolvedPath(-ENOENT); //should not happen root=fs=it->second; syms=fs->supportsSymlinks(); index=1; //Skip leading / indexIntoFs=1; //NOTE: caller must ensure path[0]=='/' depthIntoFs=1; linksFollowed=0; for(;;) { size_t slash=path.find_first_of('/',index); //cout<path.length() ? followLastSymlink : true; int result=normalPathComponent(path,follow); if(result<0) return ResolvedPath(result); } //Last component if(index>=path.length()) { //Remove trailing / size_t last=path.length()-1; if(path[last]=='/') { path.erase(last,1); //This may happen if the last path component is a fs if(indexIntoFs>path.length()) indexIntoFs=path.length(); } return ResolvedPath(fs,indexIntoFs); } } } int PathResolution::upPathComponent(string& path, size_t slash) { if(index<=1) return -ENOENT; //root dir has no parent size_t removeStart=path.find_last_of('/',index-2); if(removeStart==string::npos) return -ENOENT; //should not happen path.erase(removeStart,slash-removeStart); index=removeStart+1; //This may happen when merging a path like "/dir/.." if(path.empty()) path='/'; //This may happen if the new last path component is a fs, e.g. "/dev/null/.." if(indexIntoFs>path.length()) indexIntoFs=path.length(); if(--depthIntoFs>0) return 0; //Depth went to zero, escape current filesystem return recursiveFindFs(path); } int PathResolution::normalPathComponent(string& path, bool followIfSymlink) { map >::const_iterator it; it=filesystems.find(StringPart(path,index-1)); if(it!=filesystems.end()) { //Jumped to a new filesystem. Not stat-ing the path as we're //relying on mount not allowing to mount a filesystem on anything //but a directory. fs=it->second; syms=fs->supportsSymlinks(); indexIntoFs=index>path.length() ? index-1 : index; depthIntoFs=1; return 0; } depthIntoFs++; if(syms && followIfSymlink) { struct stat st; { StringPart sp(path,index-1,indexIntoFs); if(int res=fs->lstat(sp,&st)<0) return res; } if(S_ISLNK(st.st_mode)) return followSymlink(path); else if(index<=path.length() && !S_ISDIR(st.st_mode)) return -ENOTDIR; } return 0; } int PathResolution::followSymlink(string& path) { if(++linksFollowed>=maxLinkToFollow) return -ELOOP; string target; { StringPart sp(path,index-1,indexIntoFs); if(int res=fs->readlink(sp,target)<0) return res; } if(target.empty()) return -ENOENT; //Should not happen if(target[0]=='/') { //Symlink is absolute size_t newPathLen=target.length()+path.length()-index+1; if(newPathLen>PATH_MAX) return -ENAMETOOLONG; string newPath; newPath.reserve(newPathLen); newPath=target; if(index<=path.length()) newPath.insert(newPath.length(),path,index-1,string::npos); path.swap(newPath); fs=root; syms=root->supportsSymlinks(); index=1; indexIntoFs=1; depthIntoFs=1; } else { //Symlink is relative size_t removeStart=path.find_last_of('/',index-2); size_t newPathLen=path.length()-(index-removeStart-2)+target.length(); if(newPathLen>PATH_MAX) return -ENAMETOOLONG; string newPath; newPath.reserve(newPathLen); newPath.insert(0,path,0,removeStart+1); newPath+=target; if(index<=path.length()) newPath.insert(newPath.length(),path,index-1,string::npos); path.swap(newPath); index=removeStart+1; depthIntoFs--; } return 0; } int PathResolution::recursiveFindFs(string& path) { depthIntoFs=1; size_t backIndex=index; for(;;) { backIndex=path.find_last_of('/',backIndex-1); if(backIndex==string::npos) return -ENOENT; //should not happpen if(backIndex==0) { fs=root; indexIntoFs=1; break; } map >::const_iterator it; it=filesystems.find(StringPart(path,backIndex)); if(it!=filesystems.end()) { fs=it->second; indexIntoFs=backIndex+1; break; } depthIntoFs++; } syms=fs->supportsSymlinks(); return 0; } // // class FilesystemManager // FilesystemManager& FilesystemManager::instance() { static FilesystemManager instance; return instance; } int FilesystemManager::kmount(const char* path, intrusive_ref_ptr fs) { if(path==0 || path[0]=='\0' || fs==0) return -EFAULT; Lock l(mutex); size_t len=strlen(path); if(len>PATH_MAX) return -ENAMETOOLONG; string temp(path); if(!(temp=="/" && filesystems.empty())) //Skip check when mounting / { struct stat st; if(int result=statHelper(temp,&st,false)) return result; if(!S_ISDIR(st.st_mode)) return -ENOTDIR; string parent=temp+"/.."; if(int result=statHelper(parent,&st,false)) return result; fs->setParentFsMountpointInode(st.st_ino); } if(filesystems.insert(make_pair(StringPart(temp),fs)).second==false) return -EBUSY; //Means already mounted else return 0; } int FilesystemManager::umount(const char* path, bool force) { typedef typename map >::iterator fsIt; if(path==0 || path[0]=='\0') return -ENOENT; size_t len=strlen(path); if(len>PATH_MAX) return -ENAMETOOLONG; Lock l(mutex); //A reader-writer lock would be better fsIt it=filesystems.find(StringPart(path)); if(it==filesystems.end()) return -EINVAL; //This finds all the filesystems that have to be recursively umounted //to umount the required filesystem. For example, if /path and /path/path2 //are filesystems, umounting /path must umount also /path/path2 vector fsToUmount; for(fsIt it2=filesystems.begin();it2!=filesystems.end();++it2) if(it2->first.startsWith(it->first)) fsToUmount.push_back(it2); //Now look into all file descriptor tables if there are open files in the //filesystems to umount. If there are, return busy. This is an heavy //operation given the way the filesystem data structure is organized, but //it has been done like this to minimize the size of an entry in the file //descriptor table (4 bytes), and because umount happens infrequently. //Note that since we are locking the same mutex used by resolvePath(), //other threads can't open new files concurrently while we check #ifdef WITH_PROCESSES list::iterator it3; for(it3=fileTables.begin();it3!=fileTables.end();++it3) { for(int i=0;i file=(*it3)->getFile(i); if(!file) continue; vector::iterator it4; for(it4=fsToUmount.begin();it4!=fsToUmount.end();++it4) { if(file->getParent()!=(*it4)->second) continue; if(force==false) return -EBUSY; (*it3)->close(i); //If forced umount, close the file } } } #else //WITH_PROCESSES for(int i=0;i file=getFileDescriptorTable().getFile(i); if(!file) continue; vector::iterator it4; for(it4=fsToUmount.begin();it4!=fsToUmount.end();++it4) { if(file->getParent()!=(*it4)->second) continue; if(force==false) return -EBUSY; getFileDescriptorTable().close(i);//If forced umount, close the file } } #endif //WITH_PROCESSES //Now there should be no more files belonging to the filesystems to umount, //but check if it is really so, as there is a possible race condition //which is the read/close,umount where one thread performs a read (or write) //operation on a file descriptor, it gets preempted and another thread does //a close on that descriptor and an umount of the filesystem. Also, this may //happen in case of a forced umount. In such a case there is no entry in //the descriptor table (as close was called) but the operation is still //ongoing. vector::iterator it5; const int maxRetry=3; //Retry up to three times for(int i=0;isecond->areAllFilesClosed()) continue; if(force==false) return -EBUSY; failed=true; break; } if(!failed) break; if(i==maxRetry-1) return -EBUSY; //Failed to umount even if forced Thread::sleep(1000); //Wait to see if the filesystem operation completes } //It is now safe to umount all filesystems for(it5=fsToUmount.begin();it5!=fsToUmount.end();++it5) filesystems.erase(*it5); return 0; } void FilesystemManager::umountAll() { Lock l(mutex); #ifdef WITH_PROCESSES list::iterator it; for(it=fileTables.begin();it!=fileTables.end();++it) (*it)->closeAll(); #else //WITH_PROCESSES getFileDescriptorTable().closeAll(); #endif //WITH_PROCESSES filesystems.clear(); } ResolvedPath FilesystemManager::resolvePath(string& path, bool followLastSymlink) { //see man path_resolution. This code supports arbitrarily mounted //filesystems, symbolic links resolution, but no hardlinks to directories if(path.length()>PATH_MAX) return ResolvedPath(-ENAMETOOLONG); if(path.empty() || path[0]!='/') return ResolvedPath(-ENOENT); Lock l(mutex); PathResolution pr(filesystems); return pr.resolvePath(path,followLastSymlink); } int FilesystemManager::unlinkHelper(string& path) { //Do everything while keeping the mutex locked to prevent someone to //concurrently mount a filesystem on the directory we're unlinking Lock l(mutex); ResolvedPath openData=resolvePath(path,true); if(openData.result<0) return openData.result; //After resolvePath() so path is in canonical form and symlinks are followed if(filesystems.find(StringPart(path))!=filesystems.end()) return -EBUSY; StringPart sp(path,string::npos,openData.off); return openData.fs->unlink(sp); } int FilesystemManager::statHelper(string& path, struct stat *pstat, bool f) { ResolvedPath openData=resolvePath(path,f); if(openData.result<0) return openData.result; StringPart sp(path,string::npos,openData.off); return openData.fs->lstat(sp,pstat); } int FilesystemManager::renameHelper(string& oldPath, string& newPath) { //Do everything while keeping the mutex locked to prevent someone to //concurrently mount a filesystem on the directory we're renaming Lock l(mutex); ResolvedPath oldOpenData=resolvePath(oldPath,true); if(oldOpenData.result<0) return oldOpenData.result; ResolvedPath newOpenData=resolvePath(newPath,true); if(newOpenData.result<0) return newOpenData.result; if(oldOpenData.fs!=newOpenData.fs) return -EXDEV; //Can't rename across fs //After resolvePath() so path is in canonical form and symlinks are followed if(filesystems.find(StringPart(oldPath))!=filesystems.end()) return -EBUSY; if(filesystems.find(StringPart(newPath))!=filesystems.end()) return -EBUSY; StringPart oldSp(oldPath,string::npos,oldOpenData.off); StringPart newSp(newPath,string::npos,newOpenData.off); //Can't rename a directory into a subdirectory of itself if(newSp.startsWith(oldSp)) return -EINVAL; return oldOpenData.fs->rename(oldSp,newSp); } short int FilesystemManager::getFilesystemId() { return atomicAddExchange(&devCount,1); } int FilesystemManager::devCount=1; #ifdef WITH_DEVFS intrusive_ref_ptr //return value is a pointer to DevFs #else //WITH_DEVFS void //return value is void #endif //WITH_DEVFS basicFilesystemSetup(intrusive_ref_ptr dev) { bootlog("Mounting MountpointFs as / ... "); FilesystemManager& fsm=FilesystemManager::instance(); intrusive_ref_ptr rootFs(new MountpointFs); bootlog(fsm.kmount("/",rootFs)==0 ? "Ok\n" : "Failed\n"); #ifdef WITH_DEVFS bootlog("Mounting DevFs as /dev ... "); StringPart sp("dev"); int r1=rootFs->mkdir(sp,0755); intrusive_ref_ptr devfs(new DevFs); int r2=fsm.kmount("/dev",devfs); bool devFsOk=(r1==0 && r2==0); bootlog(devFsOk ? "Ok\n" : "Failed\n"); if(!devFsOk) return devfs; fsm.setDevFs(devfs); #endif //WITH_DEVFS bootlog("Mounting Fat32Fs as /sd ... "); bool fat32failed=false; intrusive_ref_ptr disk; #ifdef WITH_DEVFS if(dev) devfs->addDevice("sda",dev); StringPart sda("sda"); if(devfs->open(disk,sda,O_RDWR,0)<0) fat32failed=true; #else //WITH_DEVFS if(dev && dev->open(disk,intrusive_ref_ptr(0),O_RDWR,0)<0) fat32failed=true; #endif //WITH_DEVFS intrusive_ref_ptr fat32; if(fat32failed==false) { fat32=new Fat32Fs(disk); if(fat32->mountFailed()) fat32failed=true; } if(fat32failed==false) { StringPart sd("sd"); fat32failed=rootFs->mkdir(sd,0755)!=0; fat32failed=fsm.kmount("/sd",fat32)!=0; } bootlog(fat32failed==0 ? "Ok\n" : "Failed\n"); #ifdef WITH_DEVFS return devfs; #endif //WITH_DEVFS } FileDescriptorTable& getFileDescriptorTable() { #ifdef WITH_PROCESSES //Something like return Thread::getCurrentThread()->getProcess()->getFileTable(); #else //WITH_PROCESSES static FileDescriptorTable fileTable; ///< The only file table return fileTable; #endif //WITH_PROCESSES } } //namespace miosix #endif //WITH_FILESYSTEM