genode/repos/libports/src/server/fuse_fs/directory.h

278 lines
5.9 KiB
C++

/*
* \brief File-system directory node
* \author Norman Feske
* \author Christian Helmuth
* \author Josef Soentgen
* \date 2013-11-11
*/
#ifndef _DIRECTORY_H_
#define _DIRECTORY_H_
/* Genode includes */
#include <os/path.h>
#include <util/string.h>
/* libc includes */
#include <sys/dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
/* local includes */
#include <file.h>
#include <mode_util.h>
#include <node.h>
#include <util.h>
#include <symlink.h>
#include <fuse.h>
#include <fuse_private.h>
namespace File_system {
class Directory;
}
class File_system::Directory : public Node
{
private:
typedef Genode::Path<MAX_PATH_LEN> Path;
struct fuse_file_info _file_info;
Path _path;
Allocator &_alloc;
/**
* Check if the given path points to a directory
*/
bool _is_dir(char const *path)
{
struct stat s;
if (Fuse::fuse()->op.getattr(path, &s) != 0 || ! S_ISDIR(s.st_mode))
return false;
return true;
}
void _open_path(char const *path, bool create)
{
int res;
int tries = 0;
do {
/* first try to open path */
res = Fuse::fuse()->op.opendir(path, &_file_info);
if (res == 0) {
break;
}
if (create && !tries) {
res = Fuse::fuse()->op.mkdir(path, 0755);
switch (res) {
case 0: break;
default:
PERR("could not create '%s'", path);
throw Lookup_failed();
}
tries++;
continue;
}
if (res < 0) {
int err = -res;
switch (err) {
case EACCES:
PERR("op.mkdir() permission denied");
throw Permission_denied();
case EEXIST:
throw Node_already_exists();
case EIO:
PERR("op.mkdir() I/O error occurred");
throw Lookup_failed();
case ENOENT:
throw Lookup_failed();
case ENOTDIR:
throw Lookup_failed();
case ENOSPC:
PERR("op.mkdir() error while expanding directory");
throw Lookup_failed();
case EROFS:
throw Permission_denied();
default:
PERR("op.mkdir() returned unexpected error code: %d", res);
throw Lookup_failed();
}
}
} while (true);
}
size_t _num_entries()
{
char buf[4096];
Genode::memset(buf, 0, sizeof (buf));
struct fuse_dirhandle dh = {
.filler = Fuse::fuse()->filler,
.buf = buf,
.size = sizeof (buf),
.offset = 0,
};
int res = Fuse::fuse()->op.readdir(_path.base(), &dh,
Fuse::fuse()->filler, 0,
&_file_info);
if (res != 0)
return 0;
return dh.offset / sizeof (struct dirent);
}
public:
Directory(Allocator &alloc, char const *path, bool create)
:
Node(path),
_path(path),
_alloc(alloc)
{
if (!create && !_is_dir(path))
throw Lookup_failed();
_open_path(path, create);
}
virtual ~Directory()
{
Fuse::fuse()->op.release(_path.base(), &_file_info);
}
Node *node(char const *path)
{
Path node_path(path, _path.base());
struct stat s;
int res = Fuse::fuse()->op.getattr(node_path.base(), &s);
if (res != 0)
throw Lookup_failed();
Node *node = 0;
if (S_ISDIR(s.st_mode))
node = new (&_alloc) Directory(_alloc, node_path.base(), false);
else if (S_ISREG(s.st_mode))
node = new (&_alloc) File(this, path, STAT_ONLY);
else if (S_ISLNK(s.st_mode))
node = new (&_alloc) Symlink(this, path, false);
else
throw Lookup_failed();
node->lock();
return node;
}
struct fuse_file_info *file_info() { return &_file_info; }
Status status()
{
struct stat s;
int res = Fuse::fuse()->op.getattr(_path.base(), &s);
if (res != 0)
return Status();
Status status;
status.inode = s.st_ino ? s.st_ino : 1;
status.size = _num_entries() * sizeof(Directory_entry);
status.mode = File_system::Status::MODE_DIRECTORY;
return status;
}
size_t read(char *dst, size_t len, seek_off_t seek_offset)
{
if (len < sizeof(Directory_entry)) {
PERR("read buffer too small for directory entry");
return 0;
}
if (seek_offset % sizeof(Directory_entry)) {
PERR("seek offset not aligned to sizeof(Directory_entry)");
return 0;
}
seek_off_t index = seek_offset / sizeof(Directory_entry);
char buf[4096];
Genode::memset(buf, 0, sizeof (buf));
struct dirent *de = (struct dirent *)buf;
struct fuse_dirhandle dh = {
.filler = Fuse::fuse()->filler,
.buf = buf,
.size = sizeof (buf),
.offset = 0,
};
int res = Fuse::fuse()->op.readdir(_path.base(), &dh,
Fuse::fuse()->filler, 0,
&_file_info);
if (res != 0)
return 0;
if (index > (seek_off_t)(dh.offset / sizeof (struct dirent)))
return 0;
struct dirent *dent = de + index;
if (!dent)
return 0;
Directory_entry *e = (Directory_entry *)(dst);
switch (dent->d_type) {
case DT_REG: e->type = Directory_entry::TYPE_FILE; break;
case DT_DIR: e->type = Directory_entry::TYPE_DIRECTORY; break;
case DT_LNK: e->type = Directory_entry::TYPE_SYMLINK; break;
/**
* There are FUSE file system implementations that do not fill-out
* d_type when calling readdir(). We mark these entries by setting
* their type to DT_UNKNOWN in our libfuse implementation. Afterwards
* we call getattr() on each entry that, hopefully, will yield proper
* results.
*/
case DT_UNKNOWN:
{
Genode::Path<4096> path(dent->d_name, _path.base());
struct stat sbuf;
res = Fuse::fuse()->op.getattr(path.base(), &sbuf);
if (res == 0) {
switch (IFTODT(sbuf.st_mode)) {
case DT_REG: e->type = Directory_entry::TYPE_FILE; break;
case DT_DIR: e->type = Directory_entry::TYPE_DIRECTORY; break;
}
/* break outer switch */
break;
}
}
default:
return 0;
}
strncpy(e->name, dent->d_name, sizeof(e->name));
return sizeof(Directory_entry);
}
size_t write(char const *src, size_t len, seek_off_t seek_offset)
{
/* writing to directory nodes is not supported */
return 0;
}
};
#endif /* _DIRECTORY_H_ */