406 lines
13 KiB
C++
406 lines
13 KiB
C++
/***************************************************************************
|
|
* 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 <http://www.gnu.org/licenses/> *
|
|
***************************************************************************/
|
|
|
|
#include "devfs.h"
|
|
#include <string>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include "filesystem/stringpart.h"
|
|
|
|
using namespace std;
|
|
|
|
namespace miosix {
|
|
|
|
static const int _NOSEEK=0x20000; //Special flag used only here to disallow seek
|
|
|
|
static void fillStatHelper(struct stat* pstat, unsigned int st_ino,
|
|
short st_dev, mode_t mode)
|
|
{
|
|
memset(pstat,0,sizeof(struct stat));
|
|
pstat->st_dev=st_dev;
|
|
pstat->st_ino=st_ino;
|
|
pstat->st_mode=mode;
|
|
pstat->st_nlink=1;
|
|
pstat->st_blksize=0; //If zero means file buffer equals to BUFSIZ
|
|
}
|
|
|
|
/**
|
|
* This file type is for reading and writing from devices
|
|
*/
|
|
class DevFsFile : public FileBase
|
|
{
|
|
public:
|
|
/**
|
|
* Constructor
|
|
* \param fs pointer to DevFs
|
|
* \param dev the device to which this file refers
|
|
* \param flags file open flags (_FREAD, _FWRITE, ...)
|
|
*/
|
|
DevFsFile(intrusive_ref_ptr<FilesystemBase> fs,
|
|
intrusive_ref_ptr<Device> dev, int flags) : FileBase(fs),
|
|
dev(dev), seekPoint(0), flags(flags) {}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
/**
|
|
* Check whether the file refers to a terminal.
|
|
* \return 1 if it is a terminal, 0 if it is not, or a negative number in
|
|
* case of errors
|
|
*/
|
|
virtual int isatty() 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);
|
|
|
|
private:
|
|
intrusive_ref_ptr<Device> dev; ///< Device file
|
|
off_t seekPoint; ///< Seek point (note that off_t is 64bit)
|
|
int flags; ///< File open flags
|
|
};
|
|
|
|
ssize_t DevFsFile::write(const void *data, size_t len)
|
|
{
|
|
if((flags & _FWRITE)==0) return -EINVAL;
|
|
if(seekPoint+static_cast<off_t>(len)<0)
|
|
len=numeric_limits<off_t>::max()-seekPoint-len;
|
|
ssize_t result=dev->writeBlock(data,len,seekPoint);
|
|
if(result>0 && ((flags & _NOSEEK)==0)) seekPoint+=result;
|
|
return result;
|
|
}
|
|
|
|
ssize_t DevFsFile::read(void *data, size_t len)
|
|
{
|
|
if((flags & _FREAD)==0) return -EINVAL;
|
|
if(seekPoint+static_cast<off_t>(len)<0)
|
|
len=numeric_limits<off_t>::max()-seekPoint-len;
|
|
ssize_t result=dev->readBlock(data,len,seekPoint);
|
|
if(result>0 && ((flags & _NOSEEK)==0)) seekPoint+=result;
|
|
return result;
|
|
}
|
|
|
|
off_t DevFsFile::lseek(off_t pos, int whence)
|
|
{
|
|
if(flags & _NOSEEK) return -EBADF; //No seek support
|
|
|
|
off_t newSeekPoint=seekPoint;
|
|
switch(whence)
|
|
{
|
|
case SEEK_CUR:
|
|
newSeekPoint+=pos;
|
|
break;
|
|
case SEEK_SET:
|
|
newSeekPoint=pos;
|
|
break;
|
|
default:
|
|
return -EINVAL; //TODO: how to implement SEEK_END?
|
|
}
|
|
if(newSeekPoint<0) return -EOVERFLOW;
|
|
seekPoint=newSeekPoint;
|
|
return seekPoint;
|
|
}
|
|
|
|
int DevFsFile::fstat(struct stat *pstat) const
|
|
{
|
|
return dev->fstat(pstat);
|
|
}
|
|
|
|
int DevFsFile::isatty() const
|
|
{
|
|
return dev->isatty();
|
|
}
|
|
|
|
int DevFsFile::ioctl(int cmd, void *arg)
|
|
{
|
|
return dev->ioctl(cmd,arg);
|
|
}
|
|
|
|
//
|
|
// class Device
|
|
//
|
|
|
|
int Device::open(intrusive_ref_ptr<FileBase>& file,
|
|
intrusive_ref_ptr<FilesystemBase> fs, int flags, int mode)
|
|
{
|
|
flags++; //To convert from O_RDONLY, O_WRONLY, ... to _FREAD, _FWRITE, ...
|
|
file=intrusive_ref_ptr<FileBase>(
|
|
new DevFsFile(fs,shared_from_this(),flags | (seekable ? 0 : _NOSEEK)));
|
|
return 0;
|
|
}
|
|
|
|
int Device::fstat(struct stat* pstat) const
|
|
{
|
|
mode_t mode=(block ? S_IFBLK : S_IFCHR) | 0750;//brwxr-x--- | crwxr-x---
|
|
fillStatHelper(pstat,st_ino,st_dev,mode);
|
|
return 0;
|
|
}
|
|
|
|
int Device::isatty() const
|
|
{
|
|
return tty ? 1 : 0;
|
|
}
|
|
|
|
ssize_t Device::readBlock(void *buffer, size_t size, off_t where)
|
|
{
|
|
memset(buffer,0,size); //Act as /dev/zero
|
|
return size;
|
|
}
|
|
|
|
ssize_t Device::writeBlock(const void *buffer, size_t size, off_t where)
|
|
{
|
|
return size; //Act as /dev/null
|
|
}
|
|
|
|
void Device::IRQwrite(const char *str) {}
|
|
|
|
int Device::ioctl(int cmd, void *arg)
|
|
{
|
|
return -ENOTTY; //Means the operation does not apply to this descriptor
|
|
}
|
|
|
|
Device::~Device() {}
|
|
|
|
#ifdef WITH_DEVFS
|
|
|
|
/**
|
|
* Directory class for DevFs
|
|
*/
|
|
class DevFsDirectory : public DirectoryBase
|
|
{
|
|
public:
|
|
/**
|
|
* \param parent parent filesystem
|
|
* \param mutex mutex to lock when accessing the file map
|
|
* \param files file map
|
|
* \param currentInode inode of the directory we're listing
|
|
* \param parentInode inode of the parent directory
|
|
*/
|
|
DevFsDirectory(intrusive_ref_ptr<FilesystemBase> parent,
|
|
FastMutex& mutex,
|
|
map<StringPart,intrusive_ref_ptr<Device> >& files,
|
|
int currentInode, int parentInode)
|
|
: DirectoryBase(parent), mutex(mutex), files(files),
|
|
currentInode(currentInode), parentInode(parentInode),
|
|
first(true), last(false)
|
|
{
|
|
Lock<FastMutex> l(mutex);
|
|
if(files.empty()==false) currentItem=files.begin()->first.c_str();
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
|
|
private:
|
|
FastMutex& mutex; ///< Mutex of parent class
|
|
map<StringPart,intrusive_ref_ptr<Device> >& files; ///< Directory entries
|
|
string currentItem; ///< First unhandled item in directory
|
|
int currentInode,parentInode; ///< Inodes of . and ..
|
|
|
|
bool first; ///< True if first time getdents is called
|
|
bool last; ///< True if directory has ended
|
|
};
|
|
|
|
int DevFsDirectory::getdents(void *dp, int len)
|
|
{
|
|
if(len<minimumBufferSize) return -EINVAL;
|
|
if(last) return 0;
|
|
|
|
Lock<FastMutex> l(mutex);
|
|
char *begin=reinterpret_cast<char*>(dp);
|
|
char *buffer=begin;
|
|
char *end=buffer+len;
|
|
if(first)
|
|
{
|
|
first=false;
|
|
addDefaultEntries(&buffer,currentInode,parentInode);
|
|
}
|
|
if(currentItem.empty()==false)
|
|
{
|
|
map<StringPart,intrusive_ref_ptr<Device> >::iterator it;
|
|
it=files.find(StringPart(currentItem));
|
|
//Someone deleted the exact directory entry we had saved (unlikely)
|
|
if(it==files.end()) return -EBADF;
|
|
for(;it!=files.end();++it)
|
|
{
|
|
struct stat st;
|
|
it->second->fstat(&st);
|
|
if(addEntry(&buffer,end,st.st_ino,st.st_mode>>12,it->first)>0)
|
|
continue;
|
|
//Buffer finished
|
|
currentItem=it->first.c_str();
|
|
return buffer-begin;
|
|
}
|
|
}
|
|
addTerminatingEntry(&buffer,end);
|
|
last=true;
|
|
return buffer-begin;
|
|
}
|
|
|
|
//
|
|
// class DevFs
|
|
//
|
|
|
|
DevFs::DevFs() : mutex(FastMutex::RECURSIVE), inodeCount(rootDirInode+1)
|
|
{
|
|
addDevice("null",intrusive_ref_ptr<Device>(new Device(Device::STREAM)));
|
|
addDevice("zero",intrusive_ref_ptr<Device>(new Device(Device::STREAM)));
|
|
}
|
|
|
|
bool DevFs::addDevice(const char *name, intrusive_ref_ptr<Device> dev)
|
|
{
|
|
if(name==0 || name[0]=='\0') return false;
|
|
int len=strlen(name);
|
|
for(int i=0;i<len;i++) if(name[i]=='/') return false;
|
|
Lock<FastMutex> l(mutex);
|
|
bool result=files.insert(make_pair(StringPart(name),dev)).second;
|
|
//Assign inode to the file
|
|
if(result) dev->setFileInfo(atomicAddExchange(&inodeCount,1),filesystemId);
|
|
return result;
|
|
}
|
|
|
|
bool DevFs::remove(const char* name)
|
|
{
|
|
if(name==0 || name[0]=='\0') return false;
|
|
Lock<FastMutex> l(mutex);
|
|
map<StringPart,intrusive_ref_ptr<Device> >::iterator it;
|
|
it=files.find(StringPart(name));
|
|
if(it==files.end()) return false;
|
|
files.erase(StringPart(name));
|
|
return true;
|
|
}
|
|
|
|
int DevFs::open(intrusive_ref_ptr<FileBase>& file, StringPart& name,
|
|
int flags, int mode)
|
|
{
|
|
if(flags & (O_APPEND | O_EXCL)) return -EACCES;
|
|
Lock<FastMutex> l(mutex);
|
|
if(name.empty()) //Trying to open the root directory of the fs
|
|
{
|
|
if(flags & (O_WRONLY | O_RDWR)) return -EACCES;
|
|
file=intrusive_ref_ptr<FileBase>(
|
|
new DevFsDirectory(shared_from_this(),
|
|
mutex,files,rootDirInode,parentFsMountpointInode));
|
|
return 0;
|
|
}
|
|
map<StringPart,intrusive_ref_ptr<Device> >::iterator it=files.find(name);
|
|
if(it==files.end()) return -ENOENT;
|
|
return it->second->open(file,shared_from_this(),flags,mode);
|
|
}
|
|
|
|
int DevFs::lstat(StringPart& name, struct stat *pstat)
|
|
{
|
|
Lock<FastMutex> l(mutex);
|
|
if(name.empty())
|
|
{
|
|
fillStatHelper(pstat,rootDirInode,filesystemId,S_IFDIR | 0755);//drwxr-xr-x
|
|
return 0;
|
|
}
|
|
map<StringPart,intrusive_ref_ptr<Device> >::iterator it=files.find(name);
|
|
if(it==files.end()) return -ENOENT;
|
|
return it->second->fstat(pstat);
|
|
}
|
|
|
|
int DevFs::unlink(StringPart& name)
|
|
{
|
|
Lock<FastMutex> l(mutex);
|
|
if(files.erase(name)==1) return 0;
|
|
return -ENOENT;
|
|
}
|
|
|
|
int DevFs::rename(StringPart& oldName, StringPart& newName)
|
|
{
|
|
Lock<FastMutex> l(mutex);
|
|
map<StringPart,intrusive_ref_ptr<Device> >::iterator it=files.find(oldName);
|
|
if(it==files.end()) return -ENOENT;
|
|
for(unsigned int i=0;i<newName.length();i++)
|
|
if(newName[i]=='/')
|
|
return -EACCES; //DevFs does not support subdirectories
|
|
files.erase(newName); //If it exists
|
|
files.insert(make_pair(newName,it->second));
|
|
files.erase(it);
|
|
return 0;
|
|
}
|
|
|
|
int DevFs::mkdir(StringPart& name, int mode)
|
|
{
|
|
return -EACCES; // No directories support in DevFs yet
|
|
}
|
|
|
|
int DevFs::rmdir(StringPart& name)
|
|
{
|
|
return -EACCES; // No directories support in DevFs yet
|
|
}
|
|
|
|
#endif //WITH_DEVFS
|
|
|
|
} //namespace miosix
|