Below is the file 'lua.cc' from this revision. You can also download the file.

// copyright (C) 2002, 2003 graydon hoare <graydon@pobox.com>
// all rights reserved.
// licensed to the public under the terms of the GNU GPL (>= 2)
// see the file COPYING for details

#include "config.h"

extern "C" {
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}

#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <signal.h>
#include <boost/lexical_cast.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>

#include <set>
#include <map>

#include "app_state.hh"
#include "file_io.hh"
#include "lua.hh"
#include "mkstemp.hh"
#include "sanity.hh"
#include "vocab.hh"
#include "platform.hh"
#include "transforms.hh"

// defined in {std,test}_hooks.lua, converted
#include "test_hooks.h"
#include "std_hooks.h"

using namespace std;
using boost::lexical_cast;

/*
static int panic_thrower(lua_State * st)
{
  throw oops("lua panic");
}
*/

// This Lua object represents a single imperative transaction with the lua
// interpreter. if it fails at any point, all further commands in the
// transaction are ignored. it cleans the lua stack up when it is
// destructed, so no need to pop values when you're done.

struct
Lua
{
  lua_State * st;
  bool failed;
  static std::set<string> missing_functions;

  Lua(lua_State * s) :
    st(s), failed(false)
  {}

  ~Lua()
  {
    lua_settop(st, 0);
  }

  bool ok()
  {
    return !failed;
  }

  void report_error()
  {
    I(lua_isstring(st, -1));
    string err = string(lua_tostring(st, -1), lua_strlen(st, -1));
    W(F("%s\n") % err);
    lua_pop(st, 1);
    failed = true;
  }

  // getters

  Lua & get(int idx = LUA_GLOBALSINDEX)
  {
    if (failed) return *this;
    if (!lua_istable (st, idx))
      {
        L(F("lua istable() failed\n"));
        failed = true;
        return *this;
      }
    if (lua_gettop (st) < 1)
      {
        L(F("lua stack top > 0 failed\n"));
        failed = true;
        return *this;
      }
    lua_gettable(st, idx);
    return *this;
  }

  Lua & get_fn(int idx = LUA_GLOBALSINDEX)
  {
    if (failed) return *this;
    get(idx);
    if (!lua_isfunction (st, -1))
      {
        L(F("lua isfunction() failed in get_fn\n"));
        failed = true;
      }
    return *this;
  }

  Lua & get_tab(int idx = LUA_GLOBALSINDEX)
  {
    if (failed) return *this;
    get(idx);
    if (!lua_istable (st, -1))
      {
        L(F("lua istable() failed in get_tab\n"));
        failed = true;
      }
    return *this;
  }

  Lua & get_str(int idx = LUA_GLOBALSINDEX)
  {
    if (failed) return *this;
    get(idx);
    if (!lua_isstring (st, -1))
      {
        L(F("lua isstring() failed in get_str\n"));
        failed = true;
      }
    return *this;
  }

  Lua & get_num(int idx = LUA_GLOBALSINDEX)
  {
    if (failed) return *this;
    get(idx);
    if (!lua_isnumber (st, -1))
      {
        L(F("lua isnumber() failed in get_num\n"));
        failed = true;
      }
    return *this;
  }

  Lua & get_bool(int idx = LUA_GLOBALSINDEX)
  {
    if (failed) return *this;
    get(idx);
    if (!lua_isboolean (st, -1))
      {
        L(F("lua isboolean() failed in get_bool\n"));
        failed = true;
      }
    return *this;
  }

  // extractors

  Lua & extract_str(string & str)
  {
    if (failed) return *this;
    if (!lua_isstring (st, -1))
      {
        L(F("lua isstring() failed in extract_str\n"));
        failed = true;
        return *this;
      }
    str = string(lua_tostring(st, -1), lua_strlen(st, -1));
    return *this;
  }

  Lua & extract_int(int & i)
  {
    if (failed) return *this;
    if (!lua_isnumber (st, -1))
      {
        L(F("lua isnumber() failed in extract_int\n"));
        failed = true;
        return *this;
      }
    i = static_cast<int>(lua_tonumber(st, -1));
    return *this;
  }

  Lua & extract_double(double & i)
  {
    if (failed) return *this;
    if (!lua_isnumber (st, -1))
      {
        L(F("lua isnumber() failed in extract_double\n"));
        failed = true;
        return *this;
      }
    i = lua_tonumber(st, -1);
    return *this;
  }


  Lua & extract_bool(bool & i)
  {
    if (failed) return *this;
    if (!lua_isboolean (st, -1))
      {
        L(F("lua isboolean() failed in extract_bool\n"));
        failed = true;
        return *this;
      }
    i = (lua_toboolean(st, -1) == 1);
    return *this;
  }


  // table iteration

  Lua & begin()
  {
    if (failed) return *this;
    if (!lua_istable(st, -1))
      {
        L(F("lua istable() failed in begin\n"));
        failed = true;
        return *this;
      }
    I(lua_checkstack (st, 1));
    lua_pushnil(st);
    return *this;
  }

  bool next()
  {
    if (failed) return false;
    if (!lua_istable(st, -2))
      {
        L(F("lua istable() failed in next\n"));
        failed = true;
        return false;
      }
    I(lua_checkstack (st, 1));
    if (lua_next(st, -2) != 0)
      {
        return true;
      }
    pop();
    return false;
  }

  // pushers

  Lua & push_str(string const & str)
  {
    if (failed) return *this;
    I(lua_checkstack (st, 1));
    lua_pushlstring(st, str.c_str(), str.size());
    return *this;
  }

  Lua & push_int(int num)
  {
    if (failed) return *this;
    I(lua_checkstack (st, 1));
    lua_pushnumber(st, num);
    return *this;
  }

  Lua & push_int(double num)
  {
    if (failed) return *this;
    I(lua_checkstack (st, 1));
    lua_pushnumber(st, num);
    return *this;
  }

  Lua & push_bool(bool b)
  {
    if (failed) return *this;
    I(lua_checkstack (st, 1));
    lua_pushboolean(st, b);
    return *this;
  }

  Lua & push_nil()
  {
    if (failed) return *this;
    I(lua_checkstack (st, 1));
    lua_pushnil(st);
    return *this;
  }

  Lua & push_table()
  {
    if (failed) return *this;
    I(lua_checkstack (st, 1));
    lua_newtable(st);
    return *this;
  }

  Lua & set_table(int idx = -3)
  {
    if (failed) return *this;
    I(lua_checkstack (st, 1));
    lua_settable(st, idx);
    return *this;
  }

  Lua & call(int in, int out)
  {
    if (failed) return *this;
    I(lua_checkstack (st, out));
    if (lua_pcall(st, in, out, 0) != 0)
      {
        report_error();
      }
    return *this;
  }

  Lua & pop(int count = 1)
  {
    if (failed) return *this;
    if (lua_gettop (st) < count)
      {
        L(F("lua stack top >= count failed\n"));
        failed = true;
        return *this;
      }
    lua_pop(st, count);
    return *this;
  }

  Lua & func(string const & fname)
  {
    if (!failed)
      {
        if (missing_functions.find(fname) != missing_functions.end())
          failed = true;
        else
          {
            push_str(fname);
            get_fn();
            if (failed)
              missing_functions.insert(fname);
          }
      }
    return *this;
  }

  Lua & loadstring(string const & str, string const & identity)
  {
    if (!failed)
      {
        if (luaL_loadbuffer(st, str.c_str(), str.size(), identity.c_str()))
          {
            report_error();
          }
      }
    return *this;
  }

  Lua & loadfile(string const & filename)
  {
    if (!failed)
      {
        if (luaL_loadfile(st, filename.c_str()))
          {
            report_error();
          }
      }
    return *this;
  }
};

std::set<string> Lua::missing_functions;




extern "C"
{
  static int
  monotone_mkstemp_for_lua(lua_State *L)
  {
    int fd = -1;
    FILE **pf = NULL;
    char const *filename = lua_tostring (L, -1);
    std::string dup(filename);

    fd = monotone_mkstemp(dup);

    if (fd == -1)
      return 0;

    // this magic constructs a lua object which the lua io library
    // will enjoy working with
    pf = static_cast<FILE **>(lua_newuserdata(L, sizeof(FILE *)));
    *pf = fdopen(fd, "r+");
    lua_pushstring(L, "FILE*");
    lua_rawget(L, LUA_REGISTRYINDEX);
    lua_setmetatable(L, -2);

    lua_pushstring(L, dup.c_str());

    if (*pf == NULL)
      {
        lua_pushnil(L);
        lua_pushfstring(L, "%s", strerror(errno));
        lua_pushnumber(L, errno);
        return 3;
      }
    else
      return 2;
  }

  static int
  monotone_existsonpath_for_lua(lua_State *L)
  {
    const char *exe = lua_tostring(L, -1);
    lua_pushnumber(L, existsonpath(exe));
    return 1;
  }

  static int
  monotone_is_executable_for_lua(lua_State *L)
  {
    const char *path = lua_tostring(L, -1);
    lua_pushboolean(L, is_executable(path));
    return 1;
  }

  static int
  monotone_make_executable_for_lua(lua_State *L)
  {
    const char *path = lua_tostring(L, -1);
    lua_pushnumber(L, make_executable(path));
    return 1;
  }

  static int
  monotone_spawn_for_lua(lua_State *L)
  {
    int n = lua_gettop(L);
    const char *path = lua_tostring(L, -n);
    char **argv = (char**)malloc((n+1)*sizeof(char*));
    int i;
    pid_t ret;
    if (argv==NULL)
      return 0;
    argv[0] = (char*)path;
    for (i=1; i<n; i++) argv[i] = (char*)lua_tostring(L, -(n - i));
    argv[i] = NULL;
    ret = process_spawn(argv);
    free(argv);
    lua_pushnumber(L, ret);
    return 1;
  }

  static int
  monotone_wait_for_lua(lua_State *L)
  {
    pid_t pid = (pid_t)lua_tonumber(L, -1);
    int res;
    int ret;
    ret = process_wait(pid, &res);
    lua_pushnumber(L, res);
    lua_pushnumber(L, ret);
    return 2;
  }

  static int
  monotone_kill_for_lua(lua_State *L)
  {
    int n = lua_gettop(L);
    pid_t pid = (pid_t)lua_tonumber(L, -2);
    int sig;
    if (n>1)
      sig = (int)lua_tonumber(L, -1);
    else
      sig = SIGTERM;
    lua_pushnumber(L, process_kill(pid, sig));
    return 1;
  }

  static int
  monotone_sleep_for_lua(lua_State *L)
  {
    int seconds = (int)lua_tonumber(L, -1);
    lua_pushnumber(L, process_sleep(seconds));
    return 1;
  }

  static int
  monotone_guess_binary_for_lua(lua_State *L)
  {
    const char *path = lua_tostring(L, -1);
    N(path, F("guess_binary called with an invalid parameter"));
    lua_pushboolean(L, guess_binary(std::string(path, lua_strlen(L, -1))));
    return 1;
  }

  static int
  monotone_include_for_lua(lua_State *L)
  {
    const char *path = lua_tostring(L, -1);
    N(path, F("Include called with an invalid parameter"));

    bool res =Lua(L)
    .loadfile(std::string(path, lua_strlen(L, -1)))
    .call(0,1)
    .ok();

    lua_pushboolean(L, res);
    return 1;
  }

  static int
  monotone_includedir_for_lua(lua_State *L)
  {
    const char *pathstr = lua_tostring(L, -1);
    N(pathstr, F("IncludeDir called with an invalid parameter"));

    fs::path locpath(pathstr);
    N(fs::exists(locpath), F("Directory '%s' does not exists") % pathstr);
    N(fs::is_directory(locpath), F("'%s' is not a directory") % pathstr);

    // directory, iterate over it, skipping subdirs, taking every filename,
    // sorting them and loading in sorted order
    fs::directory_iterator it(locpath);
    std::vector<fs::path> arr;
    while (it != fs::directory_iterator())
      {
        if (!fs::is_directory(*it))
          arr.push_back(*it);
        ++it;
      }
    std::sort(arr.begin(), arr.end());
    for (std::vector<fs::path>::iterator i= arr.begin(); i != arr.end(); ++i)
      {
        bool res =Lua(L)
        .loadfile(i->string())
        .call(0,1)
        .ok();
        N(res, F("lua error while loading rcfile '%s'") % i->string());
      }

    lua_pushboolean(L, true);
    return 1;
  }
}


lua_hooks::lua_hooks()
{
  st = lua_open ();
  I(st);

  // no atpanic support in 4.x
  // lua_atpanic (st, &panic_thrower);

  luaopen_base(st);
  luaopen_io(st);
  luaopen_string(st);
  luaopen_math(st);
  luaopen_table(st);
  luaopen_debug(st);

  // add monotone-specific functions
  lua_register(st, "mkstemp", monotone_mkstemp_for_lua);
  lua_register(st, "existsonpath", monotone_existsonpath_for_lua);
  lua_register(st, "is_executable", monotone_is_executable_for_lua);
  lua_register(st, "make_executable", monotone_make_executable_for_lua);
  lua_register(st, "spawn", monotone_spawn_for_lua);
  lua_register(st, "wait", monotone_wait_for_lua);
  lua_register(st, "kill", monotone_kill_for_lua);
  lua_register(st, "sleep", monotone_sleep_for_lua);
  lua_register(st, "guess_binary", monotone_guess_binary_for_lua);
  lua_register(st, "include", monotone_include_for_lua);
  lua_register(st, "includedir", monotone_includedir_for_lua);
}

lua_hooks::~lua_hooks()
{
  if (st)
    lua_close (st);
}

static bool
run_string(lua_State * st, string const &str, string const & identity)
{
  I(st);
  return
    Lua(st)
    .loadstring(str, identity)
    .call(0,1)
    .ok();
}

static bool
run_file(lua_State * st, string const &filename)
{
  I(st);
  return
    Lua(st)
    .loadfile(filename)
    .call(0,1)
    .ok();
}


#ifdef BUILD_UNIT_TESTS
void
lua_hooks::add_test_hooks()
{
  if (!run_string(st, test_hooks_constant, string("<test hooks>")))
    throw oops("lua error while setting up testing hooks");
}
#endif

void
lua_hooks::add_std_hooks()
{
  if (!run_string(st, std_hooks_constant, string("<std hooks>")))
    throw oops("lua error while setting up standard hooks");
}

void
lua_hooks::default_rcfilename(fs::path & file)
{
  file = mkpath(get_homedir()) / mkpath(".monotone/monotonerc");
}

void
lua_hooks::working_copy_rcfilename(fs::path & file)
{
  file = mkpath(book_keeping_dir) / mkpath("monotonerc");
}


void
lua_hooks::load_rcfile(utf8 const & rc)
{
  I(st);
  if (rc() != "-")
    {
      fs::path locpath(localized(rc));
      if (fs::exists(locpath) && fs::is_directory(locpath))
        {
          // directory, iterate over it, skipping subdirs, taking every filename,
          // sorting them and loading in sorted order
          fs::directory_iterator it(locpath);
          std::vector<fs::path> arr;
          while (it != fs::directory_iterator())
            {
              if (!fs::is_directory(*it))
                arr.push_back(*it);
              ++it;
            }
          std::sort(arr.begin(), arr.end());
          for (std::vector<fs::path>::iterator i= arr.begin(); i != arr.end(); ++i)
            {
              load_rcfile(*i, true);
            }
          return; // directory read, skip the rest ...
        }
    }
  data dat;
  L(F("opening rcfile '%s' ...\n") % rc);
  read_data_for_command_line(rc, dat);
  N(run_string(st, dat(), rc().c_str()),
    F("lua error while loading rcfile '%s'") % rc);
  L(F("'%s' is ok\n") % rc);
}

void
lua_hooks::load_rcfile(fs::path const & rc, bool required)
{
  I(st);
  if (fs::exists(rc))
    {
      L(F("opening rcfile '%s' ...\n") % rc.string());
      N(run_file(st, rc.string()),
        F("lua error while loading '%s'") % rc.string());
      L(F("'%s' is ok\n") % rc.string());
    }
  else
    {
      N(!required, F("rcfile '%s' does not exist") % rc.string());
      L(F("skipping nonexistent rcfile '%s'\n") % rc.string());
    }
}


// concrete hooks

// nb: if you're hooking lua to return your passphrase, you don't care if we
// keep a couple extra temporaries of your passphrase around.
bool
lua_hooks::hook_get_passphrase(rsa_keypair_id const & k, string & phrase)
{
  return Lua(st)
    .func("get_passphrase")
    .push_str(k())
    .call(1,1)
    .extract_str(phrase)
    .ok();
}

bool
lua_hooks::hook_persist_phrase_ok()
{
  bool persist_ok = false;
  bool executed_ok = Lua(st)
    .func("persist_phrase_ok")
    .call(0,1)
    .extract_bool(persist_ok)
    .ok();
  return executed_ok && persist_ok;
}

bool
lua_hooks::hook_expand_selector(std::string const & sel,
                                std::string & exp)
{
  return Lua(st)
    .func("expand_selector")
    .push_str(sel)
    .call(1,1)
    .extract_str(exp)
    .ok();
}

bool
lua_hooks::hook_expand_date(std::string const & sel,
                            std::string & exp)
{
	exp.clear();
  bool res= Lua(st)
    .func("expand_date")
    .push_str(sel)
    .call(1,1)
    .extract_str(exp)
    .ok();
	return res && exp.size();
}

bool
lua_hooks::hook_get_branch_key(cert_value const & branchname,
                               rsa_keypair_id & k)
{
  string key;
  bool ok = Lua(st)
    .func("get_branch_key")
    .push_str(branchname())
    .call(1,1)
    .extract_str(key)
    .ok();

  k = key;
  return ok;
}

bool
lua_hooks::hook_get_priv_key(rsa_keypair_id const & k,
                               base64< arc4<rsa_priv_key> > & priv_key )
{
  string key;
  bool ok = Lua(st)
    .func("get_priv_key")
    .push_str(k())
    .call(1,1)
    .extract_str(key)
    .ok();

  priv_key = key;
  return ok;
}

bool
lua_hooks::hook_get_author(cert_value const & branchname,
                           string & author)
{
  return Lua(st)
    .func("get_author")
    .push_str(branchname())
    .call(1,1)
    .extract_str(author)
    .ok();
}

bool
lua_hooks::hook_edit_comment(string const & commentary,
                             string const & user_log_message,
                             string & result)
{
  return Lua(st)
    .func("edit_comment")
    .push_str(commentary)
    .push_str(user_log_message)
    .call(2,1)
    .extract_str(result)
    .ok();
}

bool
lua_hooks::hook_ignore_file(file_path const & p)
{
  bool ignore_it = false;
  bool exec_ok = Lua(st)
    .func("ignore_file")
    .push_str(p())
    .call(1,1)
    .extract_bool(ignore_it)
    .ok();
  return exec_ok && ignore_it;
}

bool
lua_hooks::hook_ignore_branch(std::string const & branch)
{
  bool ignore_it = false;
  bool exec_ok = Lua(st)
    .func("ignore_branch")
    .push_str(branch)
    .call(1,1)
    .extract_bool(ignore_it)
    .ok();
  return exec_ok && ignore_it;
}

bool
lua_hooks::hook_non_blocking_rng_ok()
{
  bool ok = false;
  bool exec_ok = Lua(st)
    .func("non_blocking_rng_ok")
    .call(0,1)
    .extract_bool(ok)
    .ok();
  return exec_ok && ok;
}

static inline bool
shared_trust_function_body(Lua & ll,
                           std::set<rsa_keypair_id> const & signers,
                           hexenc<id> const & id,
                           cert_name const & name,
                           cert_value const & val)
{
  ll.push_table();

  int k = 0;
  for (set<rsa_keypair_id>::const_iterator v = signers.begin();
       v != signers.end(); ++v)
    {
      ll.push_int(k);
      ll.push_str((*v)());
      ll.set_table();
      ++k;
    }

  bool ok;
  bool exec_ok = ll
    .push_str(id())
    .push_str(name())
    .push_str(val())
    .call(4, 1)
    .extract_bool(ok)
    .ok();

  return exec_ok && ok;
}

bool
lua_hooks::hook_get_revision_cert_trust(std::set<rsa_keypair_id> const & signers,
                                       hexenc<id> const & id,
                                       cert_name const & name,
                                       cert_value const & val)
{
  Lua ll(st);
  ll.func("get_revision_cert_trust");
  return shared_trust_function_body(ll, signers, id, name, val);
}

bool
lua_hooks::hook_get_manifest_cert_trust(std::set<rsa_keypair_id> const & signers,
                                        hexenc<id> const & id,
                                        cert_name const & name,
                                        cert_value const & val)
{
  Lua ll(st);
  ll.func("get_manifest_cert_trust");
  return shared_trust_function_body(ll, signers, id, name, val);
}

bool
lua_hooks::hook_accept_testresult_change(map<rsa_keypair_id, bool> const & old_results,
                                         map<rsa_keypair_id, bool> const & new_results)
{
  Lua ll(st);
  ll
    .func("accept_testresult_change")
    .push_table();

  for (map<rsa_keypair_id, bool>::const_iterator i = old_results.begin();
       i != old_results.end(); ++i)
    {
      ll.push_str(i->first());
      ll.push_bool(i->second);
      ll.set_table();
    }

  ll.push_table();

  for (map<rsa_keypair_id, bool>::const_iterator i = new_results.begin();
       i != new_results.end(); ++i)
    {
      ll.push_str(i->first());
      ll.push_bool(i->second);
      ll.set_table();
    }

  bool ok;
  bool exec_ok = ll
    .call(2, 1)
    .extract_bool(ok)
    .ok();

  return exec_ok && ok;
}



bool
lua_hooks::hook_merge2(file_path const & left_path,
                       file_path const & right_path,
                       file_path const & merged_path,
                       data const & left,
                       data const & right,
                       data & result)
{
  string res;
  bool ok = Lua(st)
    .func("merge2")
    .push_str(left_path())
    .push_str(right_path())
    .push_str(merged_path())
    .push_str(left())
    .push_str(right())
    .call(5,1)
    .extract_str(res)
    .ok();
  result = res;
  return ok;
}

bool
lua_hooks::hook_merge3(file_path const & anc_path,
                       file_path const & left_path,
                       file_path const & right_path,
                       file_path const & merged_path,
                       data const & ancestor,
                       data const & left,
                       data const & right,
                       data & result)
{
  string res;
  bool ok = Lua(st)
    .func("merge3")
    .push_str(anc_path())
    .push_str(left_path())
    .push_str(right_path())
    .push_str(merged_path())
    .push_str(ancestor())
    .push_str(left())
    .push_str(right())
    .call(7,1)
    .extract_str(res)
    .ok();
  result = res;
  return ok;
}

bool
lua_hooks::hook_resolve_file_conflict(file_path const & anc,
                                      file_path const & a,
                                      file_path const & b,
                                      file_path & res)
{
  string tmp;
  bool ok = Lua(st)
    .func("resolve_file_conflict")
    .push_str(anc())
    .push_str(a())
    .push_str(b())
    .call(3,1)
    .extract_str(tmp)
    .ok();
  res = tmp;
  return ok;
}

bool
lua_hooks::hook_resolve_dir_conflict(file_path const & anc,
                                     file_path const & a,
                                     file_path const & b,
                                     file_path & res)
{
  string tmp;
  bool ok = Lua(st)
    .func("resolve_dir_conflict")
    .push_str(anc())
    .push_str(a())
    .push_str(b())
    .call(3,1)
    .extract_str(tmp)
    .ok();
  res = tmp;
  return ok;
}


bool
lua_hooks::hook_use_inodeprints()
{
  bool use = false, exec_ok = false;

  exec_ok = Lua(st)
    .func("use_inodeprints")
    .call(0, 1)
    .extract_bool(use)
    .ok();
  return use && exec_ok;
}

bool
lua_hooks::hook_get_netsync_read_permitted(std::string const & pattern,
                                           rsa_keypair_id const & identity)
{
  bool permitted = false, exec_ok = false;

  exec_ok = Lua(st)
    .func("get_netsync_read_permitted")
    .push_str(pattern)
    .push_str(identity())
    .call(2,1)
    .extract_bool(permitted)
    .ok();

  return exec_ok && permitted;
}

bool
lua_hooks::hook_get_netsync_anonymous_read_permitted(std::string const & pattern)
{
  bool permitted = false, exec_ok = false;

  exec_ok = Lua(st)
    .func("get_netsync_anonymous_read_permitted")
    .push_str(pattern)
    .call(1,1)
    .extract_bool(permitted)
    .ok();

  return exec_ok && permitted;
}

bool
lua_hooks::hook_get_netsync_write_permitted(std::string const & pattern,
                                            rsa_keypair_id const & identity)
{
  bool permitted = false, exec_ok = false;

  exec_ok = Lua(st)
    .func("get_netsync_write_permitted")
    .push_str(pattern)
    .push_str(identity())
    .call(2,1)
    .extract_bool(permitted)
    .ok();

  return exec_ok && permitted;
}

bool
lua_hooks::hook_init_attributes(file_path const & filename,
                                std::map<std::string, std::string> & attrs)
{
  Lua ll(st);

  ll
    .push_str("attr_init_functions")
    .get_tab()
    .push_nil();

  while (ll.next())
    {
      ll.push_str(filename());
      ll.call(1, 1);

      if (lua_isstring(st, -1))
        {
          string key, value;

          ll.extract_str(value);
          ll.pop();
          ll.extract_str(key);

          attrs[key] = value;
        }
      else
        ll.pop();
    }

  return ll.pop().ok();
}

bool
lua_hooks::hook_apply_attribute(string const & attr,
                                file_path const & filename,
                                string const & value)
{
  return Lua(st)
    .push_str("attr_functions")
    .get_tab()
    .push_str(attr)
    .get_fn(-2)
    .push_str(filename())
    .push_str(value)
    .call(2,0)
    .ok();
}


bool
lua_hooks::hook_get_system_linesep(string & linesep)
{
  return Lua(st)
    .func("get_system_linesep")
    .call(0,1)
    .extract_str(linesep)
    .ok();
}

bool
lua_hooks::hook_get_charset_conv(file_path const & p,
                                 std::string & db,
                                 std::string & ext)
{
  Lua ll(st);
  ll
    .func("get_charset_conv")
    .push_str(p())
    .call(1,1)
    .begin();

  ll.next();
  ll.extract_str(db).pop();

  ll.next();
  ll.extract_str(ext).pop();
  return ll.ok();
}

bool
lua_hooks::hook_get_linesep_conv(file_path const & p,
                                 std::string & db,
                                 std::string & ext)
{
  Lua ll(st);
  ll
    .func("get_linesep_conv")
    .push_str(p())
    .call(1,1)
    .begin();

  ll.next();
  ll.extract_str(db).pop();

  ll.next();
  ll.extract_str(ext).pop();
  return ll.ok();
}

bool
lua_hooks::hook_note_commit(revision_id const & new_id,
                            map<cert_name, cert_value> const & certs)
{
  Lua ll(st);
  ll
    .func("note_commit")
    .push_str(new_id.inner()());

  ll.push_table();

  for (map<cert_name, cert_value>::const_iterator i = certs.begin();
       i != certs.end(); ++i)
    {
      ll.push_str(i->first());
      ll.push_str(i->second());
      ll.set_table();
    }

  ll.call(2, 0);
  return ll.ok();
}

bool
lua_hooks::hook_note_netsync_revision_received(revision_id const & new_id,
                            set<pair<rsa_keypair_id,
                                     pair<cert_name,
                                          cert_value> > > const & certs)
{
  Lua ll(st);
  ll
    .func("note_netsync_revision_received")
    .push_str(new_id.inner()());

  ll.push_table();

  typedef set<pair<rsa_keypair_id, pair<cert_name, cert_value> > > cdat;

  int n=0;
  for (cdat::const_iterator i = certs.begin(); i != certs.end(); ++i)
    {
      ll.push_int(n++);
      ll.push_table();
      ll.push_str("key");
      ll.push_str(i->first());
      ll.set_table();
      ll.push_str("name");
      ll.push_str(i->second.first());
      ll.set_table();
      ll.push_str("value");
      ll.push_str(i->second.second());
      ll.set_table();
      ll.set_table();
    }

  ll.call(2, 0);
  return ll.ok();
}

bool
lua_hooks::hook_note_netsync_pubkey_received(rsa_keypair_id const & kid)
{
  Lua ll(st);
  ll