* Copyright 2009, Raghuram Nagireddy <raghuram87@gmail.com>.
* Distributed under the terms of the MIT License.
*/
#define FUSE_USE_VERSION 27
#include <fuse/fuse.h>
#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>
#include <unistd.h>
#include "fssh.h"
#include "driver_settings.h"
#include "external_commands.h"
#include "fd.h"
#include "fssh_dirent.h"
#include "fssh_errno.h"
#include "fssh_errors.h"
#include "fssh_fcntl.h"
#include "fssh_fs_info.h"
#include "fssh_module.h"
#include "fssh_node_monitor.h"
#include "fssh_stat.h"
#include "fssh_string.h"
#include "fssh_type_constants.h"
#include "module.h"
#include "syscalls.h"
#include "vfs.h"
extern fssh_module_info *modules[];
extern fssh_file_system_module_info gRootFileSystem;
namespace FSShell {
const char* kMountPoint = "/myfs";
static mode_t sUmask = 0022;
#define PRINTD(x) if (gIsDebug) fprintf(stderr, x)
bool gIsDebug = false;
static fssh_status_t
init_kernel()
{
fssh_status_t error;
error = module_init(NULL);
if (error != FSSH_B_OK) {
fprintf(stderr, "module_init() failed: %s\n", fssh_strerror(error));
return error;
}
error = driver_settings_init();
if (error != FSSH_B_OK) {
fprintf(stderr, "initializing driver settings failed: %s\n",
fssh_strerror(error));
return error;
}
register_builtin_module(&gRootFileSystem.info);
for (int i = 0; modules[i]; i++)
register_builtin_module(modules[i]);
error = vfs_init(NULL);
if (error != FSSH_B_OK) {
fprintf(stderr, "initializing VFS failed: %s\n", fssh_strerror(error));
return error;
}
gKernelIOContext = (io_context*)vfs_new_io_context(NULL);
if (!gKernelIOContext) {
fprintf(stderr, "creating IO context failed!\n");
return FSSH_B_NO_MEMORY;
}
fssh_dev_t rootDev = _kern_mount("/", NULL, "rootfs", 0, NULL, 0);
if (rootDev < 0) {
fprintf(stderr, "mounting rootfs failed: %s\n", fssh_strerror(rootDev));
return rootDev;
}
error = _kern_setcwd(-1, "/");
if (error != FSSH_B_OK) {
fprintf(stderr, "setting cwd failed: %s\n", fssh_strerror(error));
return error;
}
error = _kern_create_dir(-1, kMountPoint, 0775);
if (error != FSSH_B_OK) {
fprintf(stderr, "creating mount point failed: %s\n",
fssh_strerror(error));
return error;
}
return FSSH_B_OK;
}
static void
fromFsshStatToStat(struct fssh_stat* f_stbuf, struct stat* stbuf)
{
stbuf->st_dev = f_stbuf->fssh_st_dev;
stbuf->st_ino = f_stbuf->fssh_st_ino;
stbuf->st_mode = f_stbuf->fssh_st_mode;
stbuf->st_nlink = f_stbuf->fssh_st_nlink;
stbuf->st_uid = f_stbuf->fssh_st_uid;
stbuf->st_gid = f_stbuf->fssh_st_gid;
stbuf->st_rdev = f_stbuf->fssh_st_rdev;
stbuf->st_size = f_stbuf->fssh_st_size;
stbuf->st_blksize = f_stbuf->fssh_st_blksize;
stbuf->st_blocks = f_stbuf->fssh_st_blocks;
stbuf->st_atime = f_stbuf->fssh_st_atime;
stbuf->st_mtime = f_stbuf->fssh_st_mtime;
stbuf->st_ctime = f_stbuf->fssh_st_ctime;
}
#define _ERR(x) (-1 * fssh_to_host_error(x))
int
fuse_getattr(const char* path, struct stat* stbuf)
{
PRINTD("##getattr\n");
struct fssh_stat f_stbuf;
fssh_status_t status = _kern_read_stat(-1, path, false, &f_stbuf,
sizeof(f_stbuf));
fromFsshStatToStat(&f_stbuf, stbuf);
if (gIsDebug)
printf("GETATTR returned: %d\n", status);
return _ERR(status);
}
static int
fuse_access(const char* path, int mask)
{
PRINTD("##access\n");
return _ERR(_kern_access(path, mask));
}
static int
fuse_readlink(const char* path, char* buffer, size_t size)
{
PRINTD("##readlink\n");
fssh_size_t n_size = size - 1;
fssh_status_t st = _kern_read_link(-1, path, buffer, &n_size);
if (st >= FSSH_B_OK)
buffer[n_size] = '\0';
return _ERR(st);
}
static int
fuse_readdir(const char* path, void* buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info* fi)
{
PRINTD("##readdir\n");
int dfp = _kern_open_dir(-1, path);
if (dfp < FSSH_B_OK)
return _ERR(dfp);
fssh_ssize_t entriesRead = 0;
struct fssh_stat f_st;
struct stat st;
char buffer[sizeof(fssh_dirent) + FSSH_B_FILE_NAME_LENGTH];
fssh_dirent* dirEntry = (fssh_dirent*)buffer;
while ((entriesRead = _kern_read_dir(dfp, dirEntry,
sizeof(buffer), 1)) == 1) {
fssh_memset(&st, 0, sizeof(st));
fssh_memset(&f_st, 0, sizeof(f_st));
fssh_status_t status = _kern_read_stat(dfp, dirEntry->d_name,
false, &f_st, sizeof(f_st));
if (status >= FSSH_B_OK) {
fromFsshStatToStat(&f_st, &st);
if (filler(buf, dirEntry->d_name, &st, 0))
break;
}
}
_kern_close(dfp);
return 0;
}
static int
fuse_mknod(const char* path, mode_t mode, dev_t rdev)
{
PRINTD("##mknod\n");
if (S_ISREG(mode)) {
int fd = _kern_open(-1, path,
FSSH_O_CREAT | FSSH_O_EXCL | FSSH_O_WRONLY, mode);
if (fd >= FSSH_B_OK)
return _ERR(_kern_close(fd));
return _ERR(fd);
} else if (S_ISFIFO(mode))
return _ERR(FSSH_EINVAL);
else
return _ERR(FSSH_EINVAL);
}
static int
fuse_mkdir(const char* path, mode_t mode)
{
PRINTD("##mkdir\n");
return _ERR(_kern_create_dir(-1, path, mode));
}
static int
fuse_symlink(const char* from, const char* to)
{
PRINTD("##symlink\n");
return _ERR(_kern_create_symlink(-1, to, from,
FSSH_S_IRWXU | FSSH_S_IRWXG | FSSH_S_IRWXO));
}
static int
fuse_unlink(const char* path)
{
PRINTD("##unlink\n");
return _ERR(_kern_unlink(-1, path));
}
static int
fuse_rmdir(const char* path)
{
PRINTD("##rmdir\n");
return _ERR(_kern_remove_dir(-1, path));
}
static int
fuse_rename(const char* from, const char* to)
{
PRINTD("##rename\n");
return _ERR(_kern_rename(-1, from, -1, to));
}
static int
fuse_link(const char* from, const char* to)
{
PRINTD("##link\n");
return _ERR(_kern_create_link(to, from));
}
static int
fuse_chmod(const char* path, mode_t mode)
{
PRINTD("##chmod\n");
fssh_struct_stat st;
st.fssh_st_mode = mode;
return _ERR(_kern_write_stat(-1, path, false, &st, sizeof(st),
FSSH_B_STAT_MODE));
}
static int
fuse_chown(const char* path, uid_t uid, gid_t gid)
{
PRINTD("##chown\n");
fssh_struct_stat st;
st.fssh_st_uid = uid;
st.fssh_st_gid = gid;
return _ERR(_kern_write_stat(-1, path, false, &st, sizeof(st),
FSSH_B_STAT_UID|FSSH_B_STAT_GID));
}
static int
fuse_open(const char* path, struct fuse_file_info* fi)
{
PRINTD("##open\n");
int fd = _kern_open(-1, path, fi->flags,
(FSSH_S_IRWXU | FSSH_S_IRWXG | FSSH_S_IRWXO) & ~sUmask);
_kern_close(fd);
if (fd < FSSH_B_OK)
return _ERR(fd);
else
return 0;
}
static int
fuse_read(const char* path, char* buf, size_t size, off_t offset,
struct fuse_file_info* fi)
{
PRINTD("##read\n");
int fd = _kern_open(-1, path, FSSH_O_RDONLY,
(FSSH_S_IRWXU | FSSH_S_IRWXG | FSSH_S_IRWXO) & ~sUmask);
if (fd < FSSH_B_OK)
return _ERR(fd);
int res = _kern_read(fd, offset, buf, size);
_kern_close(fd);
if (res < FSSH_B_OK)
res = _ERR(res);
return res;
}
static int
fuse_write(const char* path, const char* buf, size_t size, off_t offset,
struct fuse_file_info* fi)
{
PRINTD("##write\n");
int fd = _kern_open(-1, path, FSSH_O_WRONLY,
(FSSH_S_IRWXU | FSSH_S_IRWXG | FSSH_S_IRWXO) & ~sUmask);
if (fd < FSSH_B_OK)
return _ERR(fd);
int res = _kern_write(fd, offset, buf, size);
_kern_close(fd);
if (res < FSSH_B_OK)
res = _ERR(res);
return res;
}
static int
fuse_truncate(const char *path, off_t off)
{
PRINTD("##truncate\n");
struct fssh_stat st;
st.fssh_st_size = off;
fssh_status_t res = _kern_write_stat(-1, path, true, &st, sizeof(st), FSSH_B_STAT_SIZE);
if (res < FSSH_B_OK)
return _ERR(res);
return 0;
}
static void
fuse_destroy(void* priv_data)
{
_kern_sync();
}
static fssh_dev_t
get_volume_id()
{
struct fssh_stat st;
fssh_status_t error = _kern_read_stat(-1, kMountPoint, false, &st,
sizeof(st));
if (error != FSSH_B_OK)
return error;
return st.fssh_st_dev;
}
static int
fuse_statfs(const char *path __attribute__((unused)),
struct statvfs *sfs)
{
PRINTD("##statfs\n");
fssh_dev_t volumeID = get_volume_id();
if (volumeID < 0)
return _ERR(volumeID);
fssh_fs_info info;
fssh_status_t status = _kern_read_fs_info(volumeID, &info);
if (status != FSSH_B_OK)
return _ERR(status);
sfs->f_bsize = sfs->f_frsize = info.block_size;
sfs->f_blocks = info.total_blocks;
sfs->f_bavail = sfs->f_bfree = info.free_blocks;
return 0;
}
struct fuse_operations gFUSEOperations;
static void
initialiseFuseOps(struct fuse_operations* fuseOps)
{
fuseOps->getattr = fuse_getattr;
fuseOps->access = fuse_access;
fuseOps->readlink = fuse_readlink;
fuseOps->readdir = fuse_readdir;
fuseOps->mknod = fuse_mknod;
fuseOps->mkdir = fuse_mkdir;
fuseOps->symlink = fuse_symlink;
fuseOps->unlink = fuse_unlink;
fuseOps->rmdir = fuse_rmdir;
fuseOps->rename = fuse_rename;
fuseOps->link = fuse_link;
fuseOps->chmod = fuse_chmod;
fuseOps->chown = fuse_chown;
fuseOps->truncate = fuse_truncate;
fuseOps->utimens = NULL;
fuseOps->open = fuse_open;
fuseOps->read = fuse_read;
fuseOps->write = fuse_write;
fuseOps->statfs = fuse_statfs;
fuseOps->release = NULL;
fuseOps->fsync = NULL;
fuseOps->destroy = fuse_destroy;
}
static int
mount_volume(const char* device, const char* mntPoint, const char* fsName)
{
fssh_dev_t fsDev = _kern_mount(kMountPoint, device, fsName, 0, NULL, 0);
if (fsDev < 0) {
fprintf(stderr, "Error: Mounting FS failed: %s\n",
fssh_strerror(fsDev));
return 1;
}
if (!gIsDebug) {
bool isErr = false;
fssh_dev_t volumeID = get_volume_id();
if (volumeID < 0)
isErr = true;
fssh_fs_info info;
if (!isErr) {
fssh_status_t status = _kern_read_fs_info(volumeID, &info);
if (status != FSSH_B_OK)
isErr = true;
}
syslog(LOG_INFO, "Mounted %s (%s) to %s",
device,
isErr ? "unknown" : info.volume_name,
mntPoint);
}
return 0;
}
static int
unmount_volume(const char* device, const char* mntPoint)
{
_kern_setcwd(-1, "/");
fssh_status_t error = _kern_unmount(kMountPoint, 0);
if (error != FSSH_B_OK) {
if (gIsDebug)
fprintf(stderr, "Error: Unmounting FS failed: %s\n",
fssh_strerror(error));
else
syslog(LOG_INFO, "Error: Unmounting FS failed: %s",
fssh_strerror(error));
return 1;
}
if (!gIsDebug)
syslog(LOG_INFO, "UnMounted %s from %s", device, mntPoint);
return 0;
}
static int
fssh_fuse_session(const char* device, const char* mntPoint, const char* fsName,
struct fuse_args& fuseArgs)
{
int ret;
ret = mount_volume(device, mntPoint, fsName);
if (ret != 0)
return ret;
if (getuid() == 0 && geteuid() == 0 && getgid() == 0 && getegid() == 0) {
char* fuseOptions = NULL;
char* fsNameOption = NULL;
if (fuse_opt_add_opt(&fuseOptions, "allow_other") < 0
|| asprintf(&fsNameOption, "fsname=%s", device) < 0
|| fuse_opt_add_opt(&fuseOptions, fsNameOption) < 0) {
unmount_volume(device, mntPoint);
return 1;
}
struct stat sbuf;
if ((stat(device, &sbuf) == 0) && S_ISBLK(sbuf.st_mode)) {
int blkSize = 512;
fssh_dev_t volumeID = get_volume_id();
if (volumeID >= 0) {
fssh_fs_info info;
if (_kern_read_fs_info(volumeID, &info) == FSSH_B_OK)
blkSize = info.block_size;
}
char* blkSizeOption = NULL;
if (fuse_opt_add_opt(&fuseOptions, "blkdev") < 0
|| asprintf(&blkSizeOption, "blksize=%i", blkSize) < 0
|| fuse_opt_add_opt(&fuseOptions, blkSizeOption) < 0) {
unmount_volume(device, mntPoint);
return 1;
}
}
if (fuse_opt_add_arg(&fuseArgs, "-o") < 0
|| fuse_opt_add_arg(&fuseArgs, fuseOptions) < 0) {
unmount_volume(device, mntPoint);
return 1;
}
}
if (fuse_opt_add_arg(&fuseArgs, "-s") < 0) {
unmount_volume(device, mntPoint);
return 1;
}
initialiseFuseOps(&gFUSEOperations);
int res = fuse_main(fuseArgs.argc, fuseArgs.argv, &gFUSEOperations, NULL);
ret = unmount_volume(device, mntPoint);
if (ret != 0)
return ret;
return res;
}
}
using namespace FSShell;
static void
print_usage_and_exit(const char* binName)
{
fprintf(stderr,"Usage: %s [-d] <device> <mount point>\n", binName);
exit(1);
}
struct FsConfig {
const char* device;
const char* mntPoint;
};
enum {
KEY_DEBUG,
KEY_HELP
};
static int
process_options(void* data, const char* arg, int key, struct fuse_args* outArgs)
{
struct FsConfig* config = (FsConfig*) data;
switch (key) {
case FUSE_OPT_KEY_NONOPT:
if (!config->device) {
config->device = arg;
return 0;
} else if (!config->mntPoint)
config->mntPoint = arg;
else
print_usage_and_exit(outArgs->argv[0]);
break;
case KEY_DEBUG:
gIsDebug = true;
break;
case KEY_HELP:
print_usage_and_exit(outArgs->argv[0]);
}
return 1;
}
int
main(int argc, char* argv[])
{
struct fuse_args fuseArgs = FUSE_ARGS_INIT(argc, argv);
struct FsConfig config;
memset(&config, 0, sizeof(config));
const struct fuse_opt fsOptions[] = {
FUSE_OPT_KEY("uhelper=", FUSE_OPT_KEY_DISCARD),
FUSE_OPT_KEY("-d", KEY_DEBUG),
FUSE_OPT_KEY("-h", KEY_HELP),
FUSE_OPT_KEY("--help", KEY_HELP),
FUSE_OPT_END
};
if (fuse_opt_parse(&fuseArgs, &config, fsOptions, process_options) < 0)
return 1;
if (!config.mntPoint)
print_usage_and_exit(fuseArgs.argv[0]);
if (!modules[0]) {
fprintf(stderr, "Error: Couldn't find FS module!\n");
return 1;
}
fssh_status_t error = init_kernel();
if (error != FSSH_B_OK) {
fprintf(stderr, "Error: Initializing kernel failed: %s\n",
fssh_strerror(error));
return error;
}
const char* fsName = modules[0]->name;
return fssh_fuse_session(config.device, config.mntPoint, fsName, fuseArgs);
}