#include #include #include #include #include #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; }