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

// -*- mode: C++; c-file-style: "gnu"; indent-tabs-mode: nil -*-
// 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 <boost/regex.hpp>

#include <set>
#include <map>
#include <fstream>

#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"
#include "paths.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");
}

// adapted from "programming in lua", section 24.2.3
// http://www.lua.org/pil/24.2.3.html
// output is from bottom (least accessible) to top (most accessible, where
// push and pop happen).
static std::string
dump_stack(lua_State * st)
{
  std::string out;
  int i;
  int top = lua_gettop(st);
  for (i = 1; i <= top; i++) {  /* repeat for each level */
    int t = lua_type(st, i);
    switch (t) {
    case LUA_TSTRING:  /* strings */
      out += (boost::format("`%s'") % std::string(lua_tostring(st, i), lua_strlen(st, i))).str();
      break;

    case LUA_TBOOLEAN:  /* booleans */
      out += (lua_toboolean(st, i) ? "true" : "false");
      break;

    case LUA_TNUMBER:  /* numbers */
      out += (boost::format("%g") % lua_tonumber(st, i)).str();
      break;

    default:  /* other values */
      out += (boost::format("%s") % lua_typename(st, t)).str();
      break;

    }
    out += "  ";  /* put a separator */
  }
  return out;
}

// 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);
  }

  void fail(std::string const & reason)
  {
    L(F("lua failure: %s; stack = %s\n") % reason % dump_stack(st));
    failed = true;
  }

  bool ok()
  {
    L(F("Lua::ok(): failed = %i") % failed);
    return !failed;
  }

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

  // getters

  Lua & get(int idx = LUA_GLOBALSINDEX)
  {
    if (failed) return *this;
    if (!lua_istable (st, idx))
      {
        fail("istable() in get");
        return *this;
      }
    if (lua_gettop (st) < 1)
      {
        fail("stack top > 0 in get");
        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))
      fail("isfunction() in get_fn");
    return *this;
  }

  Lua & get_tab(int idx = LUA_GLOBALSINDEX)
  {
    if (failed) return *this;
    get(idx);
    if (!lua_istable (st, -1))
      fail("istable() in get_tab");
    return *this;
  }

  Lua & get_str(int idx = LUA_GLOBALSINDEX)
  {
    if (failed) return *this;
    get(idx);
    if (!lua_isstring (st, -1))
      fail("isstring() in get_str");
    return *this;
  }

  Lua & get_num(int idx = LUA_GLOBALSINDEX)
  {
    if (failed) return *this;
    get(idx);
    if (!lua_isnumber (st, -1))
      fail("isnumber() in get_num");
    return *this;
  }

  Lua & get_bool(int idx = LUA_GLOBALSINDEX)
  {
    if (failed) return *this;
    get(idx);
    if (!lua_isboolean (st, -1))
      fail("isboolean() in get_bool");
    return *this;
  }

  // extractors

  Lua & extract_str(string & str)
  {
    if (failed) return *this;
    if (!lua_isstring (st, -1))
      {
        fail("isstring() in extract_str");
        return *this;
      }
    str = string(lua_tostring(st, -1), lua_strlen(st, -1));
    L(F("lua: extracted string = %s") % str);
    return *this;
  }

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

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


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


  // table iteration

  Lua & begin()
  {
    if (failed) return *this;
    if (!lua_istable(st, -1))
      {
        fail("istable() in begin");
        return *this;
      }
    I(lua_checkstack (st, 1));
    lua_pushnil(st);
    return *this;
  }

  bool next()
  {
    if (failed) return false;
    if (!lua_istable(st, -2))
      {
        fail("istable() in next");
        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)
      {
        fail("stack top is not >= count in pop");
        return *this;
      }
    lua_pop(st, count);
    return *this;
  }

  Lua & func(string const & fname)
  {
    L(F("loading lua hook %s") % 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_file_contents_for_lua(lua_State *L)
  {
    const char *path = lua_tostring(L, -1);
    N(path, F("%s called with an invalid parameter") % "guess_binary");

    std::ifstream file(path, ios_base::binary);
    if (!file)
      {
        lua_pushnil(L);
        return 1;
      }
    const int bufsize = 8192;
    char tmpbuf[bufsize];
    string buf;
    while (file.read(tmpbuf, sizeof tmpbuf))
      {
        I(file.gcount() <= static_cast<int>(sizeof tmpbuf));
        buf.assign(tmpbuf, file.gcount());
        if (guess_binary(buf))
          {
            lua_pushboolean(L, true);
            return 1;
          }
      }
    lua_pushboolean(L, false);
    return 1;
  }

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

    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("%s called with an invalid parameter") % "IncludeDir");

    fs::path locpath(pathstr, fs::native);
    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;
  }

  static int
  monotone_regex_search_for_lua(lua_State *L)
  {
    const char *re = lua_tostring(L, -2);
    const char *str = lua_tostring(L, -1);
    boost::cmatch what;

    lua_pushboolean(L, boost::regex_search(str, what, boost::regex(re)));
    return 1;
  }

  static int
  monotone_gettext_for_lua(lua_State *L)
  {
    const char *msgid = lua_tostring(L, -1);
    lua_pushstring(L, gettext(msgid));
    return 1;
  }
}


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

  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_file_contents", monotone_guess_binary_file_contents_for_lua);
  lua_register(st, "include", monotone_include_for_lua);
  lua_register(st, "includedir", monotone_includedir_for_lua);
  lua_register(st, "gettext", monotone_gettext_for_lua);

  // add regex functions:
  lua_newtable(st);
  lua_pushstring(st, "regex");
  lua_pushvalue(st, -2);
  lua_settable(st, LUA_GLOBALSINDEX);

  lua_pushstring(st, "search");
  lua_pushcfunction(st, monotone_regex_search_for_lua);
  lua_settable(st, -3);

  lua_pop(st, 1);
}

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(system_path & file)
{
  file = system_path(get_homedir()) / ".monotone/monotonerc";
}

void
lua_hooks::working_copy_rcfilename(bookkeeping_path & file)
{
  file = bookkeeping_root / "monotonerc";
}


void
lua_hooks::load_rcfile(utf8 const & rc)
{
  I(st);
  if (rc() != "-")
    {
      fs::path locpath(system_path(rc).as_external(), fs::native);
      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(system_path(i->native_directory_string()), 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(any_path const & rc, bool required)
{
  I(st);
  if (path_exists(rc))
    {
      L(F("opening rcfile '%s' ...\n") % rc);
      N(run_file(st, rc.as_external()),
        F("lua error while loading '%s'") % rc);
      L(F("'%s' is ok\n") % rc);
    }
  else
    {
      N(!required, F("rcfile '%s' does not exist") % rc);
      L(F("skipping nonexistent rcfile '%s'\n") % rc);
    }
}


// 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.as_external())
    .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.as_external())
    .push_str(right_path.as_external())
    .push_str(merged_path.as_external())
    .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.as_external())
    .push_str(left_path.as_external())
    .push_str(right_path.as_external())
    .push_str(merged_path.as_external())
    .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.as_external())
    .push_str(a.as_external())
    .push_str(b.as_external())
    .call(3,1)
    .extract_str(tmp)
    .ok();
  res = file_path_internal(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.as_external())
    .push_str(a.as_external())
    .push_str(b.as_external())
    .call(3,1)
    .extract_str(tmp)
    .ok();
  res = file_path_internal(tmp);
  return ok;
}

bool
lua_hooks::hook_external_diff(file_path const & path,
                              data const & data_old,
                              data const & data_new,
                              bool is_binary,
                              bool diff_args_provided,
                              std::string const & diff_args,
                              std::string const & oldrev,
                              std::string const & newrev)
{
  Lua ll(st);

  ll
    .func("external_diff")
    .push_str(path.as_external());

  if (oldrev.length() != 0)
    ll.push_str(data_old());
  else
    ll.push_nil();

  ll.push_str(data_new());

  ll.push_bool(is_binary);

  if (diff_args_provided)
    ll.push_str(diff_args);
  else
    ll.push_nil();

  ll.push_str(oldrev);
  ll.push_str(newrev);

  return ll.call(7,0).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 & branch,
                                           rsa_keypair_id const & identity)
{
  bool permitted = false, exec_ok = false;

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

  return exec_ok && permitted;
}

// Anonymous no-key version
bool
lua_hooks::hook_get_netsync_read_permitted(std::string const & branch)
{
  bool permitted = false, exec_ok = false;

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

  return exec_ok && permitted;
}

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

  exec_ok = Lua(st)
    .func("get_netsync_write_permitted")
    .push_str(identity())
    .call(1,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();

  L(F("calling attr_init_function for %s") % filename);
  ll.begin();
  while (ll.next())
    {
      L(F("  calling an attr_init_function for %s") % filename);
      ll.push_str(filename.as_external());
      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;
          L(F("  added attr %s = %s") % key % value);
        }
      else
        {