Plan 9 from Bell Labs’s /usr/web/sources/contrib/de0u/root/sys/src/cmd/divergefs/matcher.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.



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


Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.