#include <u.h>
#include <libc.h>
#include <String.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include "common.h"
#include "debug.h"
#include "utils.h"
#include "collection.h"
#include "function.h"
#include "array.h"
#include "file.h"
#include "filepath.h"
#include "rule.h"
#include "baselayerrule.h"
#include "holemanager.h"
#include "filehandle.h"
#include "fiddata.h"
#include "qidgenerator.h"
#include "matcher.h"
#include "config.h"
#include "fs.h"
enum
{
DEBUG_FS = false,
DEBUG_SETTING = true
};
static void fid_dump(Fid *fid)
{
assert_valid(fid);
NOISE(DEBUG_FS,
"fid: %uld mode: %d file: 0x%uX uid: %s qid.path: %ulld aux: 0x%uX",
fid->fid, fid->omode, fid->file, fid->uid, fid->qid.path, fid->aux);
}
static int fid_isequal(Fid *first, Fid *second)
{
assert_valid(first);
assert_valid(second);
return first->fid == second->fid;
}
static void request_common_dump(Req *self)
{
assert_valid(self);
NOISE(DEBUG_FS, "tag: %uld aux: 0x%uX", self->tag, self->aux);
}
static void request_dump(char *name, Req *request)
{
assert_valid(request);
NOISE(DEBUG_FS, name);
request_common_dump(request);
fcall_dump(&(request->ifcall));
dir_dump(&(request->d));
}
struct Setting
{
char *mountpoint;
Matcher *matcher;
QidGenerator *qidgen;
HoleManager *holes;
};
static void setting_add_default_rules(Setting *self)
{
assert_valid(self);
NOISE(DEBUG_SETTING, "setting_add_default_rules adding default rule");
matcher_add(self->matcher, baselayerrule_new());
}
static bool setting_parse_rule_file(Setting *self, char *rulefile)
{
assert_valid(self);
if(rulefile == nil)
{
return false;
}
return config_parse(self->matcher, rulefile);
}
static bool setting_load_rules(Setting *self, char *rulefile)
{
assert_valid(self);
if(!setting_parse_rule_file(self, rulefile))
{
return false;
}
setting_add_default_rules(self);
return true;
}
static bool setting_init(Setting *self,
char *rulefile, char *mountpoint, char *holefile)
{
self->mountpoint = estrdup_fs(mountpoint);
self->qidgen = qidgenerator_new();
self->matcher = matcher_new(self->qidgen);
self->holes = holemanager_new(holefile);
return setting_load_rules(self, rulefile);
}
Setting *setting_new(char *rulefile, char *mountpoint, char *holefile)
{
Setting *result;
assert_valid(mountpoint);
result = (Setting *)emalloc_fs(sizeof(*result));
if(!setting_init(result, rulefile, mountpoint, holefile))
{
free(result);
result = nil;
}
return result;
}
#define SETTING_DEFAULT_FILESDIR "files"
#define SETTING_DEFAULT_HOLEFILE "holes"
#define SETTING_DEFAULT_RULEFILE "rules"
static bool setting_dump_default_rule(char *rulefile, char *defaultpath)
{
bool mkdirresult;
int fd;
char *filesdir;
assert_valid(rulefile);
if(file_exists(rulefile))
{
INFO(DEBUG_SETTING, "setting_dump_default_rule rulefile %s exists");
return true;
}
filesdir = filepath_append_cstr(defaultpath, SETTING_DEFAULT_FILESDIR);
INFO(DEBUG_SETTING,
"setting_dump_default_rule writing to rule: %s default path: %s",
filesdir, defaultpath);
mkdirresult = file_on_demand_mkdir(filesdir);
free(filesdir);
if(!mkdirresult)
{
return false;
}
fd = file_create(rulefile, OWRITE, 0777L);
if(!fd_isopen(fd))
{
return false;
}
fprint(fd, "%s/%s all<>\n", defaultpath, SETTING_DEFAULT_FILESDIR);
file_close(fd);
return true;
}
Setting *setting_default_path_new(
char *defaultpath, char *rulefile, char *mountpoint, char *holefile)
{
Setting *result = nil;
String *absolutedefaultpath;
assert_valid(defaultpath);
absolutedefaultpath = s_copy(defaultpath);
filepath_make_absolute(&absolutedefaultpath);
NOISE(DEBUG_SETTING, "setting_default_path_new default path: %s",
s_to_c(absolutedefaultpath));
if(file_on_demand_mkdir(s_to_c(absolutedefaultpath)))
{
holefile = (holefile == nil) ?
filepath_append_cstr(
s_to_c(absolutedefaultpath), SETTING_DEFAULT_HOLEFILE) :
estrdup_fs(holefile);
NOISE(DEBUG_SETTING, "setting_default_path_new default path: %s hole: %s",
s_to_c(absolutedefaultpath), holefile);
if(rulefile == nil)
{
rulefile = filepath_append_cstr(
s_to_c(absolutedefaultpath), SETTING_DEFAULT_RULEFILE);
NOISE(DEBUG_SETTING, "setting_default_path_new default path: %s rule: %s",
s_to_c(absolutedefaultpath), rulefile);
if(!setting_dump_default_rule(rulefile, s_to_c(absolutedefaultpath)))
{
ERROR(DEBUG_SETTING,
"setting_dump_default_rule unable to dump default rule to: %s",
rulefile);
free(holefile);
holefile = nil;
}
}
else
{
rulefile = estrdup_fs(rulefile);
}
if(rulefile != nil)
{
result = setting_new(rulefile, mountpoint, holefile);
free(rulefile);
}
free(holefile);
}
else
{
ERROR(DEBUG_SETTING, "setting_default_path_new unable to make dir: %s",
s_to_c(absolutedefaultpath));
}
s_free(absolutedefaultpath);
return result;
}
#undef SETTING_DEFAULT_FILESDIR
#undef SETTING_DEFAULT_HOLEFILE
#undef SETTING_DEFAULT_RULEFILE
static void setting_destroy(Setting *self)
{
assert_valid(self);
free(self->mountpoint);
matcher_free(self->matcher);
holemanager_free(self->holes);
qidgenerator_free(self->qidgen);
}
static void setting_free(Setting *self)
{
if(self == nil)
{
return;
}
setting_destroy(self);
free(self);
}
static Setting *fs_setting(Req *request)
{
assert_valid(request);
return (Setting *)(request->srv->aux);
}
static char *fs_root_directory(Req *request)
{
return fs_setting(request)->mountpoint;
}
static Matcher *fs_matcher(Req *request)
{
return fs_setting(request)->matcher;
}
HoleManager *fs_holes(Req *request)
{
return fs_setting(request)->holes;
}
QidGenerator *fs_qidgen(Req *request)
{
return fs_setting(request)->qidgen;
}
static void fs_attach(Req *request)
{
Dir *root;
assert_valid(request);
INFO(DEBUG_FS, "fs_attach uname: %s aname: %s",
request->ifcall.uname, request->ifcall.aname);
return_9p_error_if(
chdir(fs_root_directory(request)) == -1, "directory does not exist");
root = matcher_find(fs_matcher(request), "");
return_9p_error_if(root == nil, "dirstat failed");
request->ofcall.qid = root->qid;
free(root);
request->fid->qid = request->ofcall.qid;
request->fid->aux = fiddata_charpath_new("");
NOISE(DEBUG_FS, "leaving fs_attach");
return_9p_success();
}
//
/**
* @todo remember to distinguish between create for file/dir, and new file or
* truncating existing file. Also need to implement mkdir on demand.
*/
static void fs_create(Req *request)
{
char *result;
String *path;
Fcall *ifcall;
Fid *fid;
FidData *fiddata;
assert_valid(request);
assert_valid(request->fid);
fid = (Fid *)request->fid;
fiddata = (FidData *)(request->fid->aux);
assert_valid(fiddata);
ifcall = &(request->ifcall);
NOISE(DEBUG_FS, "entering (%s) fs_create fid: %uld omode: %d",
fs_root_directory(request), fid->fid, fid->omode);
return_9p_error_if(fiddata_isopen(fiddata), "file is already opened");
path = s_clone(fiddata_path_string(fiddata));
filepath_append(&path, ifcall->name);
result = matcher_create(fs_matcher(request),
fiddata, s_to_c(path), ifcall->mode, ifcall->perm);
if(result == nil)
{
holemanager_remove(fs_holes(request), s_to_c(path));
}
s_free(path);
NOISE(DEBUG_FS, "leaving fs_create");
return_9p_response(result);
}
static void fs_open_file(Req *request, FidData *fiddata)
{
assert_valid(request);
assert_valid(fiddata);
respond(request, matcher_open_file(
fs_matcher(request), fiddata, request->ifcall.mode));
}
static void fs_open_dir(Req *request, FidData *fiddata)
{
assert_valid(request);
assert_valid(fiddata);
respond(request, matcher_open_dir(
fs_matcher(request), fiddata, request->ifcall.mode));
}
static void fs_open(Req *request)
{
Fid *fid;
FidData *fiddata;
assert_valid(request);
assert_valid(request->fid);
fid = (Fid *)request->fid;
fiddata = (FidData *)request->fid->aux;
assert_valid(fiddata);
NOISE(DEBUG_FS, "entering (%s) fs_open fid: %uld omode: %d",
fs_root_directory(request), fid->fid, fid->omode);
return_9p_error_if(
holemanager_includes(fs_holes(request), fiddata_path(fiddata)),
"file not found");
return_9p_error_if(fiddata_isopen(fiddata), "file is already opened");
if(qid_isdir(&(request->fid->qid)))
{
NOISE(DEBUG_FS, "fs_open opening directory: %s",
fiddata_path(fiddata));
fs_open_dir(request, fiddata);
}
else
{
NOISE(DEBUG_FS, "fs_open opening file: %s",
fiddata_path(fiddata));
fs_open_file(request, fiddata);
}
NOISE(DEBUG_FS, "leaving fs_open");
}
static void fs_read_dir(Req *request, FidData *fiddata)
{
fiddata_read(fiddata, request, fs_holes(request));
}
static void fs_read_file(Req *request, FidData *fiddata)
{
fiddata_read(fiddata, request, fs_holes(request));
}
static void fs_read(Req *request)
{
Fid *fid;
FidData *fiddata;
assert_valid(request);
assert_valid(request->fid);
fid = (Fid *)request->fid;
fiddata = (FidData *)(request->fid->aux);
assert_valid(fiddata);
NOISE(DEBUG_FS, "entering (%s) fs_read fid: %uld omode: %d",
fs_root_directory(request), fid->fid, fid->omode);
return_9p_error_if(!fiddata_isopen(fiddata), "file does not exist");
if(qid_isdir(&(request->fid->qid)))
{
fs_read_dir(request, fiddata);
return;
}
fs_read_file(request, fiddata);
}
static void fs_write(Req *request)
{
FidData *fiddata;
assert_valid(request);
assert_valid(request->fid);
fiddata = (FidData *)(request->fid->aux);
assert_valid(fiddata);
NOISE(DEBUG_FS, "entering (%s) fs_write",
fs_root_directory(request));
return_9p_error_if(!fiddata_isopen(fiddata), "file does not exist");
return_9p_error_if(
qid_isdir(&(request->fid->qid)), "permission denied");
/** write will respond to 9p */
fiddata_write(fiddata, request);
}
static void fs_remove(Req *request)
{
char *result;
FidData *fiddata;
assert_valid(request);
assert_valid(request->fid);
fiddata = (FidData *)(request->fid->aux);
assert_valid(fiddata);
NOISE(DEBUG_FS, "entering (%s) fs_remove",
fs_root_directory(request));
return_9p_error_if(
holemanager_includes(fs_holes(request), fiddata_path(fiddata)),
"file not found");
result = matcher_remove(fs_matcher(request), fiddata_path(fiddata));
if(result == nil)
{
holemanager_add(fs_holes(request), fiddata_path(fiddata));
}
return_9p_response(result);
}
/**
* @todo how do we get the correct Dir structure and qid to return?
* - traverse through all the dirs and get the max time?
* - keep a table of assocation of path -> qids, then each time the total
* number of qids or any of the qid values differ, we increment the version
* field.
* - whenever a remove occurs, the association is marked as obsolete and new
* entries will be added during create.
*/
static void fs_stat(Req *request)
{
Dir *d;
FidData *fiddata;
assert_valid(request);
assert_valid(request->fid);
fiddata = (FidData *)(request->fid->aux);
assert_valid(fiddata);
NOISE(DEBUG_FS, "entering (%s) fs_stat",
fs_root_directory(request));
return_9p_error_if(
holemanager_includes(fs_holes(request), fiddata_path(fiddata)),
"file not found");
d = matcher_find(fs_matcher(request), fiddata_path(fiddata));
NOISE(DEBUG_FS, "fs_stat result of find: 0x%uX", d);
return_9p_error_if(d == nil, "directory is nil");
dir_dump(d);
dir_copy(d, &(request->d));
free(d);
NOISE(DEBUG_FS, "leaving fs_stat");
return_9p_success();
}
static void fs_wstat(Req *request)
{
FidData *fiddata;
assert_valid(request);
assert_valid(request->fid);
fiddata = (FidData *)(request->fid->aux);
assert_valid(fiddata);
NOISE(DEBUG_FS, "entering (%s) fs_wstat",
fs_root_directory(request));
return_9p_error_if(
holemanager_includes(fs_holes(request), fiddata_path(fiddata)),
"file not found");
return_9p_response(
matcher_wstat(fs_matcher(request),
fiddata_path(fiddata), &(request->d), fs_holes(request)));
}
static char *fs_walk_helper(Fid *fid, char *name, void *aux)
{
Dir *d = nil;
String *newpath;
Req *request = (Req *)aux;
assert_valid(fid);
assert_valid(name);
NOISE(DEBUG_FS, "fs_walk_helper name: %s", name);
if(strcmp(name, ".") == 0)
{
return nil;
}
newpath = s_clone(fiddata_path_string((FidData *)(fid->aux)));
if(strcmp(name, "..") == 0)
{
filepath_remove_last(newpath);
}
else
{
filepath_append(&newpath, name);
}
NOISE(DEBUG_FS,
"fs_walk_helper calling matcher_find on: %s", s_to_c(newpath));
if(!holemanager_includes(fs_holes(request), s_to_c(newpath)))
{
d = matcher_find(fs_matcher(request), s_to_c(newpath));
}
if(d == nil)
{
s_free(newpath);
return "name not found";
}
fid->qid = d->qid;
free(d);
qidgenerator_file_ref(fs_qidgen(request), s_to_c(newpath));
qidgenerator_file_deref(fs_qidgen(request),
fiddata_path((FidData *)(fid->aux)));
fiddata_set_path_string((FidData *)(fid->aux), newpath);
s_free(newpath);
return nil;
}
static char *fs_clone(Fid *old, Fid *new, void *aux)
{
Req *request = (Req *)aux;
assert_valid(old);
assert_valid(new);
new->aux = fiddata_copy((FidData *)(old->aux));
qidgenerator_file_ref(fs_qidgen(request),
fiddata_path((FidData *)new->aux));
return nil;
}
static void fs_walk(Req *request)
{
Fid *fid;
assert_valid(request);
fid = (Fid *)request->fid;
NOISE(DEBUG_FS, "entering (%s) fs_walk",
fs_root_directory(request));
if(fid != nil)
{
NOISE(DEBUG_FS, "fs_walk fid: %uld omode: %d",
fid->fid, fid->omode);
}
walkandclone(request, fs_walk_helper, fs_clone, request);
NOISE(DEBUG_FS, "leaving fs_walk");
}
static void fs_destroyfid(Fid *fid)
{
assert_valid(fid);
NOISE(DEBUG_FS, "entering fs_destroyfid fid: %uld omode: %d",
fid->fid, fid->omode);
if(fid->aux != nil)
{
NOISE(DEBUG_FS, "fs_destroyfid path: %s",
fiddata_path((FidData *)fid->aux));
}
fiddata_free((FidData *)fid->aux);
NOISE(DEBUG_FS, "leaving fs_destroyfid");
}
static void fs_end(Srv *srv)
{
assert_valid(srv);
setting_free((Setting *)(srv->aux));
}
/**
* @todo add extra layer or fix existing Srv struct to deal nil pointers
* w/o assert.
*/
Srv fs =
{
.attach = fs_attach,
.create = fs_create,
.open = fs_open,
.read = fs_read,
.write = fs_write,
.remove = fs_remove,
.stat = fs_stat,
.wstat = fs_wstat,
.walk = fs_walk,
.destroyfid = fs_destroyfid,
.end = fs_end
};
void fs_start(Setting *setting)
{
assert_valid(setting);
fs.aux = setting;
postmountsrv(&fs, nil, setting->mountpoint, MREPL | MCREATE);
}
void fs_native_9p_debug_enable(void)
{
++chatty9p;
}
|