#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include <String.h>
#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";
}
|