PrevUpHomeNext

API VFS

primer::api::vfs helps to create a custom filesystem exposed to lua.

Overview

lua provides some core functionality to allow scripts to load modules from the filesystem, through three functions loadfile, dofile, and require. These aren't part of the package module, they are part of the base library, and the package search path is something that can be configured during the process of installing lua.

In an embedded lua application, it's likely that you'll want to sandbox this and prevent lua from directly accessing and loading things from the filesystem.

primer::api::vfs helps to simplify this. Instead of implementing three very similar functions, to use primer::api::vfs, you only implement one of them. You do this by creating a class satisfying the "VFS Provider" concept.

Then, you make your class derive from the primer::api::vfs class template using the CRTP style. This basically performs "dependency-injection", so that the generic implementations of dofile, loadfile, and require can access your load function and do the right thing, and brings in the boiler-plate which makes it a proper API feature. primer::api::vfs has no data members and no nontrivial intialization, but your "VFS Provider" might.

Concept: VFS Provider

A VFS Provider is expected to provide a member function load:

expected<void> load(lua_State *, const std::string & path);

which attempts to load the module corresponding to path onto the stack, using e.g. luaL_loadbuffer. It should then return "ok", i.e., no error. If it cannot, it should instead return an error message. For instance you might use the pre-formatted message primer::error::module_not_found.

Example Usage
// Model of vfs provider concept
struct my_files : primer::api::vfs<my_files> {
  using map_t = std::map<std::string, std::string>;
  map_t files_;

  explicit my_files(map_t m)
    : files_(std::move(m)) {}

  primer::expected<void> load(lua_State * L, const std::string & path) {
    auto it = files_.find(path);
    if (it != files_.end()) {
      const std::string & chunk = it->second;
      luaL_loadbuffer(L, chunk.c_str(), chunk.size(), path.c_str());

      return {};
    } else {
      return primer::error::module_not_found(path);
    }
  }
};

// Example api::base object
struct test_api : primer::api::base<test_api> {
  lua_raii L_;

  API_FEATURE(primer::api::sandboxed_basic_libraries, libs_);
  API_FEATURE(my_files, vfs_);
  API_FEATURE(primer::api::print_manager, print_man_);

  test_api()
    : L_()
    , vfs_{
        {{"foo", "return {}"},
         {"bar", "local function baz() return 5 end; return { baz = baz }"}}} {
    this->initialize_api(L_);
  }

  std::string save() {
    std::string result;
    this->persist(L_, result);
    return result;
  }

  void restore(const std::string & buffer) { this->unpersist(L_, buffer); }
};

UNIT_TEST(api_vfs) {
  std::string buffer;
  table_summary summary;

  const char * script =
    "assert(type(5) == 'number')                                           \n"
    "assert(type('foo') == 'string')                                       \n"
    "assert(type(loadfile 'foo') == 'function')                            \n"
    "assert(type(dofile 'foo') == 'table')                                 \n"
    "                                                                      \n"
    "local foo = require 'foo'                                             \n"
    "assert(type(foo) == 'table')                                          \n"
    "assert(type(bar) == 'nil')                                            \n"
    "local bar = require 'bar'                                             \n"
    "assert(5 == bar.baz())                                                \n"
    "assert(not pcall(require, 'baz'))                                     \n"
    "                                                                      \n"
    "assert(bar == require 'bar')                                          \n"
    "assert(bar ~= dofile 'bar')                                           \n"
    "                                                                      "
    "\n";

  {
    test_api a;

    lua_State * L = a.L_;

    TEST_LUA_OK(L, luaL_loadstring(L, script));
    TEST_LUA_OK(L, lua_pcall(L, 0, 0, 0));

    buffer = a.save();
    summary = get_global_table_summary(L);
  }

  {
    test_api a;

    lua_State * L = a.L_;

    TEST_LUA_OK(L, luaL_loadstring(L, script));
    TEST_LUA_OK(L, lua_pcall(L, 0, 0, 0));

    a.restore(buffer);

    auto summary2 = get_global_table_summary(L);
    // TEST(check_tables_match(summary, summary2),
    //     "expected global tables to match!");
    if (!check_tables_match(summary, summary2)) {
      std::cerr << "WARN: Global table mismatch!\n";
    }

    TEST_LUA_OK(L, luaL_loadstring(L, script));
    TEST_LUA_OK(L, lua_pcall(L, 0, 0, 0));
  }
}
Synopsis

TODO


PrevUpHomeNext