#include #include #include #include #include <9p.h> #include #include "common.h" #include "debug.h" #include "utils.h" #include "collection.h" #include "function.h" #include "array.h" #include "rule.h" #include "holemanager.h" #include "filehandle.h" #include "filepath.h" #include "fiddata.h" #include "file.h" #include "qidgenerator.h" #include "matcher.h" enum { DEBUG_MATCHER = true }; struct Matcher { Array *rules; QidGenerator *qidgen; }; Matcher *matcher_new(QidGenerator *qidgen) { Matcher *result; result = (Matcher *)emallocz_fs(sizeof(*result)); result->rules = array_new(); result->qidgen = qidgen; return result; } /** * @todo find a place to call free, something like atexit */ void matcher_free(Matcher *self) { if(self == nil) { return; } array_free_with(self->rules, (functionunary)rule_free); free(self); } void matcher_add(Matcher *self, Rule *rule) { assert_valid(self); assert_valid(rule); NOISE(DEBUG_MATCHER, "matcher_add adding rule"); array_add(self->rules, rule); } typedef struct MatcherFindData { char *path; Array *dirs; } MatcherFindData; static collection_do_ret matcher_find_each(void *p, void *arg) { collection_do_ret result = COLLECTION_DO_CONTINUE; Dir *d; Rule *rule = (Rule *)p; MatcherFindData *data = (MatcherFindData *)arg; assert_valid(rule); assert_valid(data); NOISE(DEBUG_MATCHER, "entering matcher_find_each with path: %s", data->path); d = rule_find(rule, data->path); if(d != nil) { NOISE(DEBUG_MATCHER, "matcher_find_each adding: 0x%uX", d); if(!qid_isdir(&(d->qid))) { if(array_size(data->dirs) > 0) { free(d); NOISE(DEBUG_MATCHER, "matcher_find_each file with same name as dir not added"); return COLLECTION_DO_CONTINUE; } NOISE(DEBUG_MATCHER, "matcher_find_each single file"); result = COLLECTION_DO_STOP; } array_add(data->dirs, d); } return result; } /** * @todo call to a unique Qid and Dir generation right from here. * - if Dir is a file * - generate a unique path based on qid.path * - save qid -> unique qid. * - hash by qid.path * - compare by type, dev and path. * * - if Dir is a directory (naive implementation) * - generate unique path based on all the qid.paths. * - save qids -> unique qid * - hash by qid.paths * - compare by number of qids, and type, dev and path. * * - if Dir is a directory * - generate unique path based on all the qid.paths. reset version to 0. * - save file path -> (qids, unique qid). * - hash by file path * - compare by intersect(current_qids, saved_qids), * - if intersect != nil, return unique qid and increment version if * intersect != current_qids or if any of the qid.vers changed. * - else remove the file path association and generate a new unique qid * * - need a structure that can look up data both ways. */ Dir *matcher_find(Matcher *self, char *path) { Dir *result = nil; MatcherFindData data = (MatcherFindData){path, nil}; assert_valid(self); assert_valid(path); data.dirs = array_new_size(1); NOISE(DEBUG_MATCHER, "entering matcher_find path: %s", path); array_do(self->rules, matcher_find_each, &data); if(array_size(data.dirs) > 0) { result = qidgenerator_get(self->qidgen, path, data.dirs); } array_free_with(data.dirs, free); NOISE(DEBUG_MATCHER, "leaving matcher_find result: 0x%uX", result); return result; } typedef struct MatcherOpenFileWriteData { char *result; Rule *readlayer; FidData *fiddata; int omode; int readfd; Dir *readdir; } MatcherOpenFileWriteData; /** * @todo should we use rule's create method or just use path and use file_copy */ static char *matcher_copy_on_write(Rule *rule, MatcherOpenFileWriteData *data) { bool result; int targetfd; assert(fd_isopen(data->readfd)); assert_valid(data->readdir); targetfd = rule_create(rule, fiddata_path(data->fiddata), OWRITE, data->readdir->mode & 0777); if(!fd_isopen(targetfd)) { NOISE(DEBUG_MATCHER, "unable to open file %s for writing", fiddata_path(data->fiddata)); return "unable to open file for writing"; } result = file_copy(data->readfd, targetfd); file_close(targetfd); NOISE(DEBUG_MATCHER, "matcher_copy_on_write file copy result: %d", result); return result ? nil : "failed to copy file"; } static collection_do_ret matcher_find_write_layer_each(void *p, void *arg) { Rule *rule = (Rule *)p; MatcherOpenFileWriteData *data = (MatcherOpenFileWriteData *)arg; assert_valid(rule); assert_valid(data); assert(fd_isopen(data->readfd)); assert_valid(data->readdir); assert_valid(data->readlayer); if(rule == data->readlayer) { NOISE(DEBUG_MATCHER, "matcher_find_write_layer_each read layer == write layer: %s", rule_name(rule)); data->result = nil; return COLLECTION_DO_STOP; } if(rule_contains(rule, fiddata_path(data->fiddata), OWRITE, data->readdir->mode & 0777)) { NOISE(DEBUG_MATCHER, "matcher_find_write_layer_each found write layer on: %s", rule->root); data->result = matcher_copy_on_write(rule, data); return COLLECTION_DO_STOP; } return COLLECTION_DO_CONTINUE; } static collection_do_ret matcher_find_read_layer_each(void *p, void *arg) { Rule *rule = (Rule *)p; Dir *satisfies; MatcherOpenFileWriteData *data = (MatcherOpenFileWriteData *)arg; assert_valid(rule); assert_valid(data); data->readfd = rule_satisfied_open_file(rule, fiddata_path(data->fiddata), OREAD, &satisfies); if(fd_isopen(data->readfd)) { data->readdir = rule_find(rule, fiddata_path(data->fiddata)); if(data->readdir == nil) { FATAL(DEBUG_MATCHER, "matcher_find_read_layer_each can open file but can't stat"); file_close(data->readfd); data->result = "dirstat failed"; } else { NOISE(DEBUG_MATCHER, "matcher_find_read_layer_each found read layer: %s", rule->root); data->readlayer = rule; data->result = nil; } return COLLECTION_DO_STOP; } else if(satisfies != nil) { WARNING(DEBUG_MATCHER, "matcher_find_read_layer_each has file %s but can't open", satisfies->name, strlen(satisfies->name)); return COLLECTION_DO_STOP; } return COLLECTION_DO_CONTINUE; } static char *matcher_open_file_prepare_write( Matcher *self, FidData *fiddata, int omode) { MatcherOpenFileWriteData data = (MatcherOpenFileWriteData) { "file does not exist", nil, fiddata, omode, INVALID_FD, nil}; assert(mode_includes_write(omode)); NOISE(DEBUG_MATCHER, "entering matcher_open_file_prepare_write"); array_do(self->rules, matcher_find_read_layer_each, &data); if(data.result == nil) { data.result = "no write layer found"; array_do(self->rules, matcher_find_write_layer_each, &data); free(data.readdir); file_close(data.readfd); } NOISE(DEBUG_MATCHER, "leaving matcher_open_file_prepare_write result: %s", data.result); return data.result; } typedef struct MatcherOpenFileData { FidData *fiddata; int omode; } MatcherOpenFileData; /** @TODO have to stop at first file with failed attempt */ static collection_do_ret matcher_open_file_each(void *p, void *arg) { int fd; Dir *satisfies; Rule *rule = (Rule *)p; MatcherOpenFileData *data = (MatcherOpenFileData *)arg; assert_valid(rule); assert_valid(data); NOISE(DEBUG_MATCHER, "matcher_open_file_each calling rule_satisfied_open_file mode: %d", data->omode); fd = rule_satisfied_open_file(rule, fiddata_path(data->fiddata), data->omode, &satisfies); if(fd_isopen(fd)) { NOISE(DEBUG_MATCHER, "matcher_open_file_each successfully opened file"); fiddata_set_handle(data->fiddata, (FileHandle*)singlefilehandle_open(fd)); return COLLECTION_DO_STOP; } else if(satisfies != nil) { WARNING(DEBUG_MATCHER, "matcher_open_file_each has file %s but can't open", satisfies->name, strlen(satisfies->name)); fiddata_clear_handle(data->fiddata); return COLLECTION_DO_STOP; } return COLLECTION_DO_CONTINUE; } char *matcher_open_file(Matcher *self, FidData *fiddata, int omode) { char *result = nil; MatcherOpenFileData data = (MatcherOpenFileData){fiddata, omode}; assert_valid(self); assert_valid(fiddata); NOISE(DEBUG_MATCHER, "matcher_open_file attempting to open file with mode 0x%uX", omode); if(mode_includes_write(omode)) { result = matcher_open_file_prepare_write(self, fiddata, omode); } if(result == nil) { array_do(self->rules, matcher_open_file_each, &data); if(!fiddata_isopen(fiddata)) { result = "unable to open file"; } } NOISE(DEBUG_MATCHER, "leaving matcher_open_file result: %s", result); return result; } typedef struct MatcherOpenDirData { MatcherOpenFileData; DirectoryHandle *handle; } MatcherOpenDirData; static collection_do_ret matcher_open_dir_each(void *p, void *arg) { int fd; Rule *rule = (Rule *)p; MatcherOpenDirData *data = (MatcherOpenDirData *)arg; assert_valid(rule); assert_valid(data); fd = rule_open_dir(rule, fiddata_path(data->fiddata), data->omode); if(fd_isopen(fd)) { directoryhandle_add(data->handle, rule, fd); } return COLLECTION_DO_CONTINUE; } char *matcher_open_dir(Matcher *self, FidData *fiddata, int omode) { MatcherOpenDirData data = (MatcherOpenDirData){(MatcherOpenFileData){fiddata, omode}, nil}; assert_valid(self); assert_valid(fiddata); NOISE(DEBUG_MATCHER, "entering matcher_open_dir path: %s mode: 0x%X", fiddata_path(fiddata), omode); data.handle = directoryhandle_new(fiddata_path(fiddata)); fiddata_set_handle(fiddata, (FileHandle*)data.handle); array_do(self->rules, matcher_open_dir_each, &data); if(directoryhandle_count(data.handle) == 0) { NOISE(DEBUG_MATCHER, "leaving matcher_open_dir with error: name not found"); return "name not found"; } NOISE(DEBUG_MATCHER, "leaving matcher_open_dir successfully with total dirs: %d", directoryhandle_count(data.handle)); return nil; } typedef struct MatcherCreateData { char *path; int omode; ulong perm; } MatcherCreateData; static bool matcher_create_detect(void *p, void *arg) { Rule *rule = (Rule *)p; MatcherCreateData *data = (MatcherCreateData *)arg; assert_valid(rule); assert_valid(data); return rule_contains(rule, data->path, data->omode, data->perm); } static char *matcher_create_set_handle( Matcher *, Rule *rule, FidData *fiddata, MatcherCreateData *data) { int fd; fd = rule_create(rule, data->path, data->omode, data->perm); if(!fd_isopen(fd)) { return last_error(); } fiddata_set_handle(fiddata, (FileHandle*)singlefilehandle_open(fd)); return nil; } char *matcher_create(Matcher *self, FidData *fiddata, char *path, int omode, ulong perm) { Rule *rule; MatcherCreateData data = (MatcherCreateData){path, omode, perm}; assert_valid(self); assert_valid(path); NOISE(DEBUG_MATCHER, "entering matcher_create on path: %s with mode: 0x%uX", path, omode); if(array_detect(self->rules, matcher_create_detect, (void **)&rule, &data)) { return matcher_create_set_handle(self, rule, fiddata, &data); } return "file failed to satisfy any rule"; } char *matcher_remove(Matcher *self, char *path) { Rule *rule; assert_valid(self); assert_valid(path); NOISE(DEBUG_MATCHER, "entering matcher_remove with path: %s", path); if(array_detect(self->rules, (predicatebinary)rule_exists, &rule, path)) { return rule_remove(rule, path) ? nil : last_error(); } return "file not found"; } enum { DEBUG_MATCHER_WSTAT = true }; typedef struct MatcherWStatData { Rule *source; Dir *sourcedir; bool isdir; char *path; Dir *d; } MatcherWStatData; static bool matcher_wstat_target_detect(void *p, void *arg) { bool result; Rule *rule = (Rule *)p; MatcherWStatData *data = (MatcherWStatData *)arg; assert_valid(rule); assert_valid(data); result = rule_contains(rule, data->path, 0, data->d->mode); if(result) { NOISE(DEBUG_MATCHER || DEBUG_MATCHER_WSTAT, "matcher_wstat_target_detect got rule root: %s name: %s path: %s", rule->root, rule->name, data->path); } return result; } static collection_do_ret matcher_wstat_source_each(void *p, void *arg) { Dir *d; Rule *rule = (Rule *)p; MatcherWStatData *data = (MatcherWStatData *)arg; assert_valid(rule); assert_valid(data); d = rule_find(rule, data->path); if(d != nil) { NOISE(DEBUG_MATCHER || DEBUG_MATCHER_WSTAT, "matcher_wstat_source_each got rule root: %s name: %s path: %s dir: %s", rule->root, rule->name, data->path, d->name); data->source = rule; data->isdir = perm_isdir(d->mode); data->sourcedir = d; } return (d == nil) ? COLLECTION_DO_CONTINUE : COLLECTION_DO_STOP; } #define MATCHER_WSTAT_DIR_MERGE(oldfield, newfield) \ if(newfield == -1) newfield = oldfield /** Merge non string and non qid fields from old into new */ static void matcher_wstat_dir_merge(Dir *old, Dir *new) { assert_valid(old); assert_valid(new); if (new->type == (ushort) -1) new->type = old->type; if (new->dev == (uint) -1) new->dev = old->dev; MATCHER_WSTAT_DIR_MERGE(old->mode, new->mode); MATCHER_WSTAT_DIR_MERGE(old->atime, new->atime); MATCHER_WSTAT_DIR_MERGE(old->mtime, new->mtime); MATCHER_WSTAT_DIR_MERGE(old->length, new->length); } #undef MATCHER_WSTAT_DIR_MERGE /** * @TODO deal specially with path that points to directory? * Currently, wstat just fails if we are renaming a directory that is read only. */ char *matcher_wstat(Matcher *self, char *path, Dir *d, HoleManager *holes) { MatcherWStatData data = (MatcherWStatData){nil, nil, false, path, d}; assert_valid(self); assert_valid(path); assert_valid(d); NOISE(DEBUG_MATCHER || DEBUG_MATCHER_WSTAT, "entering matcher_wstat with path: %s dir->name: %s mode: %uo", path, d->name, d->mode); array_do(self->rules, matcher_wstat_source_each, &data); if(data.source) { bool result; bool compatible; bool hastarget; Rule *target = nil; String *newpath = s_copy(path); assert(data.sourcedir); matcher_wstat_dir_merge(data.sourcedir, d); free(data.sourcedir); data.sourcedir = nil; if(strlen(d->name) > 0) { filepath_replace_last(&newpath, d->name); } data.path = s_to_c(newpath); NOISE(DEBUG_MATCHER|| DEBUG_MATCHER_WSTAT, "target path: %s", s_to_c(newpath)); compatible = rule_contains(data.source, s_to_c(newpath), 0, d->mode); hastarget = array_detect(self->rules, matcher_wstat_target_detect, &target, &data); if(!compatible && !hastarget && !data.isdir) { FATAL(DEBUG_MATCHER || DEBUG_MATCHER_WSTAT, "matcher_wstat cannot find new path to save file"); s_free(newpath); return "matcher_wstat cannot find new path to save file"; } if(data.isdir) { NOISE(DEBUG_MATCHER || DEBUG_MATCHER_WSTAT, "matcher_wstat dir"); result = rule_set_stat(data.source, path, d); } else if(compatible) { /* update info in qidgenerator */ NOISE(DEBUG_MATCHER || DEBUG_MATCHER_WSTAT, "matcher_wstat compatible"); result = rule_set_stat(data.source, path, d); if(!result) { if(hastarget && !rule_same_path(data.source, target)) { NOISE(DEBUG_MATCHER || DEBUG_MATCHER_WSTAT, "matcher_wstat wstat failed, fall back to copy" " target root: %s name: %s", target->root, target->name); result = rule_copy_file(data.source, path, target, s_to_c(newpath), d); } else { WARNING(DEBUG_MATCHER || DEBUG_MATCHER_WSTAT, "matcher_wstat wstat failed and source target have same path"); } } } else if(rule_same_path(data.source, target)) { NOISE(DEBUG_MATCHER || DEBUG_MATCHER_WSTAT, "matcher_wstat same path"); result = rule_set_stat(data.source, path, d); } else { NOISE(DEBUG_MATCHER || DEBUG_MATCHER_WSTAT, "matcher_wstat non compat"); result = rule_copy_file(data.source, path, target, s_to_c(newpath), d); } if(result) { NOISE(DEBUG_MATCHER || DEBUG_MATCHER_WSTAT, "matcher_wstat operation succeed"); } else { NOISE(DEBUG_MATCHER || DEBUG_MATCHER_WSTAT, "matcher_wstat operation failed"); } if(result && strcmp(path, s_to_c(newpath)) != 0) { NOISE(DEBUG_MATCHER || DEBUG_MATCHER_WSTAT, "matcher_wstat name differ, modifying hole db"); holemanager_add(holes, path); holemanager_remove(holes, s_to_c(newpath)); } s_free(newpath); return result ? nil : last_error(); } return "file not found"; }