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

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



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


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.