diff --git a/configure.ac b/configure.ac index a4dd61de0..69060b45c 100755 --- a/configure.ac +++ b/configure.ac @@ -56,6 +56,8 @@ AC_LANG_PUSH(C++) TORRENT_WITH_XMLRPC_C AC_LANG_POP(C++) +TORRENT_WITH_LUA + AC_DEFINE(HAVE_CONFIG_H, 1, true if config.h was included) AC_DEFINE(USER_AGENT, [std::string(PACKAGE "/" VERSION "/") + torrent::version()], Http user agent) diff --git a/rtorrent.lua b/rtorrent.lua new file mode 100644 index 000000000..bd4b2bd88 --- /dev/null +++ b/rtorrent.lua @@ -0,0 +1,36 @@ +-- "rtorrent" is a global variable set by the module loading call +-- Autocall +local mt = {} +function mt.__call (t, ...) + name = table.concat(rawget(t, "__namestack"), ".") + success, ret = pcall(rtorrent.call, name, ...) + if not success then error(name..": "..ret, 2) end + return ret +end +function mt.__index (t, key) + ns = rawget(t, "__namestack") or {} + table.insert(ns, key) + return setmetatable({__namestack=ns}, mt) +end +rtorrent["autocall"] = setmetatable({}, mt) + +-- autocall-config +local mt = {} +function mt.__call (t, ...) + name = table.concat(rawget(t, "__namestack"), ".") + success, ret = pcall(rtorrent.call, name, "", ...) + if not success then error(name..": "..ret, 2) end + return ret +end +function mt.__index (t, key) + ns = rawget(t, "__namestack") + if ns == nil then + if _G[key] ~= nil then return _G[key] end + ns = {} + end + table.insert(ns, key) + return setmetatable({__namestack=ns}, mt) +end +rtorrent["autocall_config"] = setmetatable({}, mt) + +return rtorrent diff --git a/scripts/ax_lua.m4 b/scripts/ax_lua.m4 new file mode 100644 index 000000000..ddbb9dd6d --- /dev/null +++ b/scripts/ax_lua.m4 @@ -0,0 +1,659 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_lua.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PROG_LUA[([MINIMUM-VERSION], [TOO-BIG-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])] +# AX_LUA_HEADERS[([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])] +# AX_LUA_LIBS[([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])] +# AX_LUA_READLINE[([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])] +# +# DESCRIPTION +# +# Detect a Lua interpreter, optionally specifying a minimum and maximum +# version number. Set up important Lua paths, such as the directories in +# which to install scripts and modules (shared libraries). +# +# Also detect Lua headers and libraries. The Lua version contained in the +# header is checked to match the Lua interpreter version exactly. When +# searching for Lua libraries, the version number is used as a suffix. +# This is done with the goal of supporting multiple Lua installs (5.1, +# 5.2, and 5.3 side-by-side). +# +# A note on compatibility with previous versions: This file has been +# mostly rewritten for serial 18. Most developers should be able to use +# these macros without needing to modify configure.ac. Care has been taken +# to preserve each macro's behavior, but there are some differences: +# +# 1) AX_WITH_LUA is deprecated; it now expands to the exact same thing as +# AX_PROG_LUA with no arguments. +# +# 2) AX_LUA_HEADERS now checks that the version number defined in lua.h +# matches the interpreter version. AX_LUA_HEADERS_VERSION is therefore +# unnecessary, so it is deprecated and does not expand to anything. +# +# 3) The configure flag --with-lua-suffix no longer exists; the user +# should instead specify the LUA precious variable on the command line. +# See the AX_PROG_LUA description for details. +# +# Please read the macro descriptions below for more information. +# +# This file was inspired by Andrew Dalke's and James Henstridge's +# python.m4 and Tom Payne's, Matthieu Moy's, and Reuben Thomas's ax_lua.m4 +# (serial 17). Basically, this file is a mash-up of those two files. I +# like to think it combines the best of the two! +# +# AX_PROG_LUA: Search for the Lua interpreter, and set up important Lua +# paths. Adds precious variable LUA, which may contain the path of the Lua +# interpreter. If LUA is blank, the user's path is searched for an +# suitable interpreter. +# +# If MINIMUM-VERSION is supplied, then only Lua interpreters with a +# version number greater or equal to MINIMUM-VERSION will be accepted. If +# TOO-BIG-VERSION is also supplied, then only Lua interpreters with a +# version number greater or equal to MINIMUM-VERSION and less than +# TOO-BIG-VERSION will be accepted. +# +# The Lua version number, LUA_VERSION, is found from the interpreter, and +# substituted. LUA_PLATFORM is also found, but not currently supported (no +# standard representation). +# +# Finally, the macro finds four paths: +# +# luadir Directory to install Lua scripts. +# pkgluadir $luadir/$PACKAGE +# luaexecdir Directory to install Lua modules. +# pkgluaexecdir $luaexecdir/$PACKAGE +# +# These paths are found based on $prefix, $exec_prefix, Lua's +# package.path, and package.cpath. The first path of package.path +# beginning with $prefix is selected as luadir. The first path of +# package.cpath beginning with $exec_prefix is used as luaexecdir. This +# should work on all reasonable Lua installations. If a path cannot be +# determined, a default path is used. Of course, the user can override +# these later when invoking make. +# +# luadir Default: $prefix/share/lua/$LUA_VERSION +# luaexecdir Default: $exec_prefix/lib/lua/$LUA_VERSION +# +# These directories can be used by Automake as install destinations. The +# variable name minus 'dir' needs to be used as a prefix to the +# appropriate Automake primary, e.g. lua_SCRIPS or luaexec_LIBRARIES. +# +# If an acceptable Lua interpreter is found, then ACTION-IF-FOUND is +# performed, otherwise ACTION-IF-NOT-FOUND is preformed. If ACTION-IF-NOT- +# FOUND is blank, then it will default to printing an error. To prevent +# the default behavior, give ':' as an action. +# +# AX_LUA_HEADERS: Search for Lua headers. Requires that AX_PROG_LUA be +# expanded before this macro. Adds precious variable LUA_INCLUDE, which +# may contain Lua specific include flags, e.g. -I/usr/include/lua5.1. If +# LUA_INCLUDE is blank, then this macro will attempt to find suitable +# flags. +# +# LUA_INCLUDE can be used by Automake to compile Lua modules or +# executables with embedded interpreters. The *_CPPFLAGS variables should +# be used for this purpose, e.g. myprog_CPPFLAGS = $(LUA_INCLUDE). +# +# This macro searches for the header lua.h (and others). The search is +# performed with a combination of CPPFLAGS, CPATH, etc, and LUA_INCLUDE. +# If the search is unsuccessful, then some common directories are tried. +# If the headers are then found, then LUA_INCLUDE is set accordingly. +# +# The paths automatically searched are: +# +# * /usr/include/luaX.Y +# * /usr/include/lua/X.Y +# * /usr/include/luaXY +# * /usr/local/include/luaX.Y +# * /usr/local/include/lua-X.Y +# * /usr/local/include/lua/X.Y +# * /usr/local/include/luaXY +# +# (Where X.Y is the Lua version number, e.g. 5.1.) +# +# The Lua version number found in the headers is always checked to match +# the Lua interpreter's version number. Lua headers with mismatched +# version numbers are not accepted. +# +# If headers are found, then ACTION-IF-FOUND is performed, otherwise +# ACTION-IF-NOT-FOUND is performed. If ACTION-IF-NOT-FOUND is blank, then +# it will default to printing an error. To prevent the default behavior, +# set the action to ':'. +# +# AX_LUA_LIBS: Search for Lua libraries. Requires that AX_PROG_LUA be +# expanded before this macro. Adds precious variable LUA_LIB, which may +# contain Lua specific linker flags, e.g. -llua5.1. If LUA_LIB is blank, +# then this macro will attempt to find suitable flags. +# +# LUA_LIB can be used by Automake to link Lua modules or executables with +# embedded interpreters. The *_LIBADD and *_LDADD variables should be used +# for this purpose, e.g. mymod_LIBADD = $(LUA_LIB). +# +# This macro searches for the Lua library. More technically, it searches +# for a library containing the function lua_load. The search is performed +# with a combination of LIBS, LIBRARY_PATH, and LUA_LIB. +# +# If the search determines that some linker flags are missing, then those +# flags will be added to LUA_LIB. +# +# If libraries are found, then ACTION-IF-FOUND is performed, otherwise +# ACTION-IF-NOT-FOUND is performed. If ACTION-IF-NOT-FOUND is blank, then +# it will default to printing an error. To prevent the default behavior, +# set the action to ':'. +# +# AX_LUA_READLINE: Search for readline headers and libraries. Requires the +# AX_LIB_READLINE macro, which is provided by ax_lib_readline.m4 from the +# Autoconf Archive. +# +# If a readline compatible library is found, then ACTION-IF-FOUND is +# performed, otherwise ACTION-IF-NOT-FOUND is performed. +# +# LICENSE +# +# Copyright (c) 2015 Reuben Thomas +# Copyright (c) 2014 Tim Perkins +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 42 + +dnl ========================================================================= +dnl AX_PROG_LUA([MINIMUM-VERSION], [TOO-BIG-VERSION], +dnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl ========================================================================= +AC_DEFUN([AX_PROG_LUA], +[ + dnl Check for required tools. + AC_REQUIRE([AC_PROG_GREP]) + AC_REQUIRE([AC_PROG_SED]) + + dnl Make LUA a precious variable. + AC_ARG_VAR([LUA], [The Lua interpreter, e.g. /usr/bin/lua5.1]) + + dnl Find a Lua interpreter. + m4_define_default([_AX_LUA_INTERPRETER_LIST], + [lua lua5.3 lua53 lua5.2 lua52 lua5.1 lua51 lua50]) + + m4_if([$1], [], + [ dnl No version check is needed. Find any Lua interpreter. + AS_IF([test "x$LUA" = 'x'], + [AC_PATH_PROGS([LUA], [_AX_LUA_INTERPRETER_LIST], [:])]) + ax_display_LUA='lua' + + AS_IF([test "x$LUA" != 'x:'], + [ dnl At least check if this is a Lua interpreter. + AC_MSG_CHECKING([if $LUA is a Lua interpreter]) + _AX_LUA_CHK_IS_INTRP([$LUA], + [AC_MSG_RESULT([yes])], + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([not a Lua interpreter]) + ]) + ]) + ], + [ dnl A version check is needed. + AS_IF([test "x$LUA" != 'x'], + [ dnl Check if this is a Lua interpreter. + AC_MSG_CHECKING([if $LUA is a Lua interpreter]) + _AX_LUA_CHK_IS_INTRP([$LUA], + [AC_MSG_RESULT([yes])], + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([not a Lua interpreter]) + ]) + dnl Check the version. + m4_if([$2], [], + [_ax_check_text="whether $LUA version >= $1"], + [_ax_check_text="whether $LUA version >= $1, < $2"]) + AC_MSG_CHECKING([$_ax_check_text]) + _AX_LUA_CHK_VER([$LUA], [$1], [$2], + [AC_MSG_RESULT([yes])], + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([version is out of range for specified LUA])]) + ax_display_LUA=$LUA + ], + [ dnl Try each interpreter until we find one that satisfies VERSION. + m4_if([$2], [], + [_ax_check_text="for a Lua interpreter with version >= $1"], + [_ax_check_text="for a Lua interpreter with version >= $1, < $2"]) + AC_CACHE_CHECK([$_ax_check_text], + [ax_cv_pathless_LUA], + [ for ax_cv_pathless_LUA in _AX_LUA_INTERPRETER_LIST none; do + test "x$ax_cv_pathless_LUA" = 'xnone' && break + _AX_LUA_CHK_IS_INTRP([$ax_cv_pathless_LUA], [], [continue]) + _AX_LUA_CHK_VER([$ax_cv_pathless_LUA], [$1], [$2], [break]) + done + ]) + dnl Set $LUA to the absolute path of $ax_cv_pathless_LUA. + AS_IF([test "x$ax_cv_pathless_LUA" = 'xnone'], + [LUA=':'], + [AC_PATH_PROG([LUA], [$ax_cv_pathless_LUA])]) + ax_display_LUA=$ax_cv_pathless_LUA + ]) + ]) + + AS_IF([test "x$LUA" = 'x:'], + [ dnl Run any user-specified action, or abort. + m4_default([$4], [AC_MSG_ERROR([cannot find suitable Lua interpreter])]) + ], + [ dnl Query Lua for its version number. + AC_CACHE_CHECK([for $ax_display_LUA version], + [ax_cv_lua_version], + [ dnl Get the interpreter version in X.Y format. This should work for + dnl interpreters version 5.0 and beyond. + ax_cv_lua_version=[`$LUA -e ' + -- return a version number in X.Y format + local _, _, ver = string.find(_VERSION, "^Lua (%d+%.%d+)") + print(ver)'`] + ]) + AS_IF([test "x$ax_cv_lua_version" = 'x'], + [AC_MSG_ERROR([invalid Lua version number])]) + AC_SUBST([LUA_VERSION], [$ax_cv_lua_version]) + AC_SUBST([LUA_SHORT_VERSION], [`echo "$LUA_VERSION" | $SED 's|\.||'`]) + + dnl The following check is not supported: + dnl At times (like when building shared libraries) you may want to know + dnl which OS platform Lua thinks this is. + AC_CACHE_CHECK([for $ax_display_LUA platform], + [ax_cv_lua_platform], + [ax_cv_lua_platform=[`$LUA -e 'print("unknown")'`]]) + AC_SUBST([LUA_PLATFORM], [$ax_cv_lua_platform]) + + dnl Use the values of $prefix and $exec_prefix for the corresponding + dnl values of LUA_PREFIX and LUA_EXEC_PREFIX. These are made distinct + dnl variables so they can be overridden if need be. However, the general + dnl consensus is that you shouldn't need this ability. + AC_SUBST([LUA_PREFIX], ['${prefix}']) + AC_SUBST([LUA_EXEC_PREFIX], ['${exec_prefix}']) + + dnl Lua provides no way to query the script directory, and instead + dnl provides LUA_PATH. However, we should be able to make a safe educated + dnl guess. If the built-in search path contains a directory which is + dnl prefixed by $prefix, then we can store scripts there. The first + dnl matching path will be used. + AC_CACHE_CHECK([for $ax_display_LUA script directory], + [ax_cv_lua_luadir], + [ AS_IF([test "x$prefix" = 'xNONE'], + [ax_lua_prefix=$ac_default_prefix], + [ax_lua_prefix=$prefix]) + + dnl Initialize to the default path. + ax_cv_lua_luadir="$LUA_PREFIX/share/lua/$LUA_VERSION" + + dnl Try to find a path with the prefix. + _AX_LUA_FND_PRFX_PTH([$LUA], [$ax_lua_prefix], [script]) + AS_IF([test "x$ax_lua_prefixed_path" != 'x'], + [ dnl Fix the prefix. + _ax_strip_prefix=`echo "$ax_lua_prefix" | $SED 's|.|.|g'` + ax_cv_lua_luadir=`echo "$ax_lua_prefixed_path" | \ + $SED "s|^$_ax_strip_prefix|$LUA_PREFIX|"` + ]) + ]) + AC_SUBST([luadir], [$ax_cv_lua_luadir]) + AC_SUBST([pkgluadir], [\${luadir}/$PACKAGE]) + + dnl Lua provides no way to query the module directory, and instead + dnl provides LUA_PATH. However, we should be able to make a safe educated + dnl guess. If the built-in search path contains a directory which is + dnl prefixed by $exec_prefix, then we can store modules there. The first + dnl matching path will be used. + AC_CACHE_CHECK([for $ax_display_LUA module directory], + [ax_cv_lua_luaexecdir], + [ AS_IF([test "x$exec_prefix" = 'xNONE'], + [ax_lua_exec_prefix=$ax_lua_prefix], + [ax_lua_exec_prefix=$exec_prefix]) + + dnl Initialize to the default path. + ax_cv_lua_luaexecdir="$LUA_EXEC_PREFIX/lib/lua/$LUA_VERSION" + + dnl Try to find a path with the prefix. + _AX_LUA_FND_PRFX_PTH([$LUA], + [$ax_lua_exec_prefix], [module]) + AS_IF([test "x$ax_lua_prefixed_path" != 'x'], + [ dnl Fix the prefix. + _ax_strip_prefix=`echo "$ax_lua_exec_prefix" | $SED 's|.|.|g'` + ax_cv_lua_luaexecdir=`echo "$ax_lua_prefixed_path" | \ + $SED "s|^$_ax_strip_prefix|$LUA_EXEC_PREFIX|"` + ]) + ]) + AC_SUBST([luaexecdir], [$ax_cv_lua_luaexecdir]) + AC_SUBST([pkgluaexecdir], [\${luaexecdir}/$PACKAGE]) + + dnl Run any user specified action. + $3 + ]) +]) + +dnl AX_WITH_LUA is now the same thing as AX_PROG_LUA. +AC_DEFUN([AX_WITH_LUA], +[ + AC_MSG_WARN([[$0 is deprecated, please use AX_PROG_LUA instead]]) + AX_PROG_LUA +]) + + +dnl ========================================================================= +dnl _AX_LUA_CHK_IS_INTRP(PROG, [ACTION-IF-TRUE], [ACTION-IF-FALSE]) +dnl ========================================================================= +AC_DEFUN([_AX_LUA_CHK_IS_INTRP], +[ + dnl A minimal Lua factorial to prove this is an interpreter. This should work + dnl for Lua interpreters version 5.0 and beyond. + _ax_lua_factorial=[`$1 2>/dev/null -e ' + -- a simple factorial + function fact (n) + if n == 0 then + return 1 + else + return n * fact(n-1) + end + end + print("fact(5) is " .. fact(5))'`] + AS_IF([test "$_ax_lua_factorial" = 'fact(5) is 120'], + [$2], [$3]) +]) + + +dnl ========================================================================= +dnl _AX_LUA_CHK_VER(PROG, MINIMUM-VERSION, [TOO-BIG-VERSION], +dnl [ACTION-IF-TRUE], [ACTION-IF-FALSE]) +dnl ========================================================================= +AC_DEFUN([_AX_LUA_CHK_VER], +[ + dnl Check that the Lua version is within the bounds. Only the major and minor + dnl version numbers are considered. This should work for Lua interpreters + dnl version 5.0 and beyond. + _ax_lua_good_version=[`$1 -e ' + -- a script to compare versions + function verstr2num(verstr) + local _, _, majorver, minorver = string.find(verstr, "^(%d+)%.(%d+)") + if majorver and minorver then + return tonumber(majorver) * 100 + tonumber(minorver) + end + end + local minver = verstr2num("$2") + local _, _, trimver = string.find(_VERSION, "^Lua (.*)") + local ver = verstr2num(trimver) + local maxver = verstr2num("$3") or 1e9 + if minver <= ver and ver < maxver then + print("yes") + else + print("no") + end'`] + AS_IF([test "x$_ax_lua_good_version" = "xyes"], + [$4], [$5]) +]) + + +dnl ========================================================================= +dnl _AX_LUA_FND_PRFX_PTH(PROG, PREFIX, SCRIPT-OR-MODULE-DIR) +dnl ========================================================================= +AC_DEFUN([_AX_LUA_FND_PRFX_PTH], +[ + dnl Get the script or module directory by querying the Lua interpreter, + dnl filtering on the given prefix, and selecting the shallowest path. If no + dnl path is found matching the prefix, the result will be an empty string. + dnl The third argument determines the type of search, it can be 'script' or + dnl 'module'. Supplying 'script' will perform the search with package.path + dnl and LUA_PATH, and supplying 'module' will search with package.cpath and + dnl LUA_CPATH. This is done for compatibility with Lua 5.0. + + ax_lua_prefixed_path=[`$1 -e ' + -- get the path based on search type + local searchtype = "$3" + local paths = "" + if searchtype == "script" then + paths = (package and package.path) or LUA_PATH + elseif searchtype == "module" then + paths = (package and package.cpath) or LUA_CPATH + end + -- search for the prefix + local prefix = "'$2'" + local minpath = "" + local mindepth = 1e9 + string.gsub(paths, "(@<:@^;@:>@+)", + function (path) + path = string.gsub(path, "%?.*$", "") + path = string.gsub(path, "/@<:@^/@:>@*$", "") + if string.find(path, prefix) then + local depth = string.len(string.gsub(path, "@<:@^/@:>@", "")) + if depth < mindepth then + minpath = path + mindepth = depth + end + end + end) + print(minpath)'`] +]) + + +dnl ========================================================================= +dnl AX_LUA_HEADERS([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl ========================================================================= +AC_DEFUN([AX_LUA_HEADERS], +[ + dnl Check for LUA_VERSION. + AC_MSG_CHECKING([if LUA_VERSION is defined]) + AS_IF([test "x$LUA_VERSION" != 'x'], + [AC_MSG_RESULT([yes])], + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([cannot check Lua headers without knowing LUA_VERSION]) + ]) + + dnl Make LUA_INCLUDE a precious variable. + AC_ARG_VAR([LUA_INCLUDE], [The Lua includes, e.g. -I/usr/include/lua5.1]) + + dnl Some default directories to search. + LUA_SHORT_VERSION=`echo "$LUA_VERSION" | $SED 's|\.||'` + m4_define_default([_AX_LUA_INCLUDE_LIST], + [ /usr/include/lua$LUA_VERSION \ + /usr/include/lua-$LUA_VERSION \ + /usr/include/lua/$LUA_VERSION \ + /usr/include/lua$LUA_SHORT_VERSION \ + /usr/local/include/lua$LUA_VERSION \ + /usr/local/include/lua-$LUA_VERSION \ + /usr/local/include/lua/$LUA_VERSION \ + /usr/local/include/lua$LUA_SHORT_VERSION \ + ]) + + dnl Try to find the headers. + _ax_lua_saved_cppflags=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $LUA_INCLUDE" + AC_CHECK_HEADERS([lua.h lualib.h lauxlib.h luaconf.h]) + CPPFLAGS=$_ax_lua_saved_cppflags + + dnl Try some other directories if LUA_INCLUDE was not set. + AS_IF([test "x$LUA_INCLUDE" = 'x' && + test "x$ac_cv_header_lua_h" != 'xyes'], + [ dnl Try some common include paths. + for _ax_include_path in _AX_LUA_INCLUDE_LIST; do + test ! -d "$_ax_include_path" && continue + + AC_MSG_CHECKING([for Lua headers in]) + AC_MSG_RESULT([$_ax_include_path]) + + AS_UNSET([ac_cv_header_lua_h]) + AS_UNSET([ac_cv_header_lualib_h]) + AS_UNSET([ac_cv_header_lauxlib_h]) + AS_UNSET([ac_cv_header_luaconf_h]) + + _ax_lua_saved_cppflags=$CPPFLAGS + CPPFLAGS="$CPPFLAGS -I$_ax_include_path" + AC_CHECK_HEADERS([lua.h lualib.h lauxlib.h luaconf.h]) + CPPFLAGS=$_ax_lua_saved_cppflags + + AS_IF([test "x$ac_cv_header_lua_h" = 'xyes'], + [ LUA_INCLUDE="-I$_ax_include_path" + break + ]) + done + ]) + + AS_IF([test "x$ac_cv_header_lua_h" = 'xyes'], + [ dnl Make a program to print LUA_VERSION defined in the header. + dnl TODO It would be really nice if we could do this without compiling a + dnl program, then it would work when cross compiling. But I'm not sure how + dnl to do this reliably. For now, assume versions match when cross compiling. + + AS_IF([test "x$cross_compiling" != 'xyes'], + [ AC_CACHE_CHECK([for Lua header version], + [ax_cv_lua_header_version], + [ _ax_lua_saved_cppflags=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $LUA_INCLUDE" + AC_COMPUTE_INT(ax_cv_lua_header_version_major,[LUA_VERSION_NUM/100],[AC_INCLUDES_DEFAULT +#include +],[ax_cv_lua_header_version_major=unknown]) + AC_COMPUTE_INT(ax_cv_lua_header_version_minor,[LUA_VERSION_NUM%100],[AC_INCLUDES_DEFAULT +#include +],[ax_cv_lua_header_version_minor=unknown]) + AS_IF([test "x$ax_cv_lua_header_version_major" = xunknown || test "x$ax_cv_lua_header_version_minor" = xunknown],[ + ax_cv_lua_header_version=unknown + ],[ + ax_cv_lua_header_version="$ax_cv_lua_header_version_major.$ax_cv_lua_header_version_minor" + ]) + CPPFLAGS=$_ax_lua_saved_cppflags + ]) + + dnl Compare this to the previously found LUA_VERSION. + AC_MSG_CHECKING([if Lua header version matches $LUA_VERSION]) + AS_IF([test "x$ax_cv_lua_header_version" = "x$LUA_VERSION"], + [ AC_MSG_RESULT([yes]) + ax_header_version_match='yes' + ], + [ AC_MSG_RESULT([no]) + ax_header_version_match='no' + ]) + ], + [ AC_MSG_WARN([cross compiling so assuming header version number matches]) + ax_header_version_match='yes' + ]) + ]) + + dnl Was LUA_INCLUDE specified? + AS_IF([test "x$ax_header_version_match" != 'xyes' && + test "x$LUA_INCLUDE" != 'x'], + [AC_MSG_ERROR([cannot find headers for specified LUA_INCLUDE])]) + + dnl Test the final result and run user code. + AS_IF([test "x$ax_header_version_match" = 'xyes'], [$1], + [m4_default([$2], [AC_MSG_ERROR([cannot find Lua includes])])]) +]) + +dnl AX_LUA_HEADERS_VERSION no longer exists, use AX_LUA_HEADERS. +AC_DEFUN([AX_LUA_HEADERS_VERSION], +[ + AC_MSG_WARN([[$0 is deprecated, please use AX_LUA_HEADERS instead]]) +]) + + +dnl ========================================================================= +dnl AX_LUA_LIBS([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl ========================================================================= +AC_DEFUN([AX_LUA_LIBS], +[ + dnl TODO Should this macro also check various -L flags? + + dnl Check for LUA_VERSION. + AC_MSG_CHECKING([if LUA_VERSION is defined]) + AS_IF([test "x$LUA_VERSION" != 'x'], + [AC_MSG_RESULT([yes])], + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([cannot check Lua libs without knowing LUA_VERSION]) + ]) + + dnl Make LUA_LIB a precious variable. + AC_ARG_VAR([LUA_LIB], [The Lua library, e.g. -llua5.1]) + + AS_IF([test "x$LUA_LIB" != 'x'], + [ dnl Check that LUA_LIBS works. + _ax_lua_saved_libs=$LIBS + LIBS="$LIBS $LUA_LIB" + AC_SEARCH_LIBS([lua_load], [], + [_ax_found_lua_libs='yes'], + [_ax_found_lua_libs='no']) + LIBS=$_ax_lua_saved_libs + + dnl Check the result. + AS_IF([test "x$_ax_found_lua_libs" != 'xyes'], + [AC_MSG_ERROR([cannot find libs for specified LUA_LIB])]) + ], + [ dnl First search for extra libs. + _ax_lua_extra_libs='' + + _ax_lua_saved_libs=$LIBS + LIBS="$LIBS $LUA_LIB" + AC_SEARCH_LIBS([exp], [m]) + AC_SEARCH_LIBS([dlopen], [dl]) + LIBS=$_ax_lua_saved_libs + + AS_IF([test "x$ac_cv_search_exp" != 'xno' && + test "x$ac_cv_search_exp" != 'xnone required'], + [_ax_lua_extra_libs="$_ax_lua_extra_libs $ac_cv_search_exp"]) + + AS_IF([test "x$ac_cv_search_dlopen" != 'xno' && + test "x$ac_cv_search_dlopen" != 'xnone required'], + [_ax_lua_extra_libs="$_ax_lua_extra_libs $ac_cv_search_dlopen"]) + + dnl Try to find the Lua libs. + _ax_lua_saved_libs=$LIBS + LIBS="$LIBS $LUA_LIB" + AC_SEARCH_LIBS([lua_load], + [ lua$LUA_VERSION \ + lua$LUA_SHORT_VERSION \ + lua-$LUA_VERSION \ + lua-$LUA_SHORT_VERSION \ + lua \ + ], + [_ax_found_lua_libs='yes'], + [_ax_found_lua_libs='no'], + [$_ax_lua_extra_libs]) + LIBS=$_ax_lua_saved_libs + + AS_IF([test "x$ac_cv_search_lua_load" != 'xno' && + test "x$ac_cv_search_lua_load" != 'xnone required'], + [LUA_LIB="$ac_cv_search_lua_load $_ax_lua_extra_libs"]) + ]) + + dnl Test the result and run user code. + AS_IF([test "x$_ax_found_lua_libs" = 'xyes'], [$1], + [m4_default([$2], [AC_MSG_ERROR([cannot find Lua libs])])]) +]) + + +dnl ========================================================================= +dnl AX_LUA_READLINE([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl ========================================================================= +AC_DEFUN([AX_LUA_READLINE], +[ + AX_LIB_READLINE + AS_IF([test "x$ac_cv_header_readline_readline_h" != 'x' && + test "x$ac_cv_header_readline_history_h" != 'x'], + [ LUA_LIBS_CFLAGS="-DLUA_USE_READLINE $LUA_LIBS_CFLAGS" + $1 + ], + [$2]) +]) diff --git a/scripts/checks.m4 b/scripts/checks.m4 index b9095cee1..956fecf87 100644 --- a/scripts/checks.m4 +++ b/scripts/checks.m4 @@ -427,6 +427,25 @@ AC_DEFUN([TORRENT_WITH_XMLRPC_C], [ ]) +AC_DEFUN([TORRENT_WITH_LUA], [ + AC_ARG_WITH(lua, + AS_HELP_STRING([--with-lua],[enable LUA support]), + [ + if test "$withval" = "no"; then + AC_MSG_RESULT(no) + else + AX_PROG_LUA + AX_LUA_LIBS + AX_LUA_HEADERS + AC_DEFINE(HAVE_LUA, 1, Use LUA.) + LIBS="$LIBS $LUA_LIB" + CXXFLAGS="$CXXFLAGS $LUA_INCLUDE" + fi + ],[ + AC_MSG_RESULT(ignored) + ]) +]) + AC_DEFUN([TORRENT_WITH_INOTIFY], [ AC_LANG_PUSH(C++) diff --git a/src/Makefile.am b/src/Makefile.am index c68251fa7..4ce428613 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -122,6 +122,9 @@ libsub_root_a_SOURCES = \ rpc/xmlrpc.h \ rpc/xmlrpc.cc \ \ + rpc/lua.h \ + rpc/lua.cc \ + \ ui/download.cc \ ui/download.h \ ui/download_list.cc \ diff --git a/src/command_local.cc b/src/command_local.cc index e26b9ebb7..078b86bbd 100644 --- a/src/command_local.cc +++ b/src/command_local.cc @@ -60,6 +60,7 @@ #include "utils/file_status_cache.h" #include "globals.h" +#include "rpc/lua.h" #include "control.h" #include "command_helpers.h" @@ -232,6 +233,7 @@ void initialize_command_local() { core::DownloadList* dList = control->core()->download_list(); core::DownloadStore* dStore = control->core()->download_store(); + rpc::LuaEngine* luaEngine = control->lua_engine(); torrent::ChunkManager* chunkManager = torrent::chunk_manager(); torrent::FileManager* fileManager = torrent::file_manager(); @@ -315,6 +317,12 @@ initialize_command_local() { CMD2_ANY_V ("session.save", std::bind(&core::DownloadList::session_save, dList)); +#ifdef HAVE_LUA + CMD2_ANY ("lua.execute", std::bind(&rpc::execute_lua, luaEngine, std::placeholders::_1, std::placeholders::_2, 0)); + CMD2_ANY ("lua.execute.str", std::bind(&rpc::execute_lua, luaEngine, std::placeholders::_1, std::placeholders::_2, rpc::LuaEngine::flag_string)); + CMD2_ANY ("lua.import", std::bind(&rpc::execute_lua, luaEngine, std::placeholders::_1, std::placeholders::_2, rpc::LuaEngine::flag_autocall_upvalue)); +#endif + #define CMD2_EXECUTE(key, flags) \ CMD2_ANY(key, std::bind(&rpc::ExecFile::execute_object, &rpc::execFile, std::placeholders::_2, flags)); diff --git a/src/control.cc b/src/control.cc index 60c9b5301..e6d964e2c 100644 --- a/src/control.cc +++ b/src/control.cc @@ -55,6 +55,7 @@ #include "rpc/command_scheduler.h" #include "rpc/parse_commands.h" #include "rpc/scgi.h" +#include "rpc/lua.h" #include "rpc/object_storage.h" #include "ui/root.h" @@ -68,6 +69,7 @@ Control::Control() : m_commandScheduler(new rpc::CommandScheduler()), m_objectStorage(new rpc::object_storage()), + m_luaEngine(new rpc::LuaEngine()), m_directory_events(new torrent::directory_events()), m_tick(0), @@ -99,6 +101,7 @@ Control::~Control() { delete m_directory_events; delete m_commandScheduler; delete m_objectStorage; + delete m_luaEngine; } void diff --git a/src/control.h b/src/control.h index 023ecffc2..b6d47d924 100644 --- a/src/control.h +++ b/src/control.h @@ -66,6 +66,7 @@ namespace rpc { class CommandScheduler; class XmlRpc; class object_storage; + class LuaEngine; } namespace torrent { @@ -101,6 +102,7 @@ class Control { rpc::CommandScheduler* command_scheduler() { return m_commandScheduler; } rpc::object_storage* object_storage() { return m_objectStorage; } + rpc::LuaEngine* lua_engine() { return m_luaEngine; } torrent::directory_events* directory_events() { return m_directory_events; } @@ -125,6 +127,7 @@ class Control { rpc::CommandScheduler* m_commandScheduler; rpc::object_storage* m_objectStorage; + rpc::LuaEngine* m_luaEngine; torrent::directory_events* m_directory_events; uint64_t m_tick; diff --git a/src/rpc/lua.cc b/src/rpc/lua.cc new file mode 100644 index 000000000..761214273 --- /dev/null +++ b/src/rpc/lua.cc @@ -0,0 +1,398 @@ +#include "config.h" + +#include +#include +#include +#include +#include + +#include "lua.h" +#ifdef HAVE_LUA +#include +#endif + +#include +#include +#include +#include + +#include "core/download.h" +#include "rpc/command.h" +#include "rpc/command_map.h" +#include "rpc/parse_commands.h" +#include "rpc/xmlrpc.h" + +namespace rpc { + +const int LuaEngine::flag_string; +const int LuaEngine::flag_autocall_upvalue; +const std::string LuaEngine::module_name = "rtorrent"; + +#ifdef HAVE_LUA + +LuaEngine::LuaEngine() { + m_luaState = luaL_newstate(); + luaL_openlibs(m_luaState); + set_package_preload(); +} + +LuaEngine::~LuaEngine() { lua_close(m_luaState); } + +void +LuaEngine::set_package_preload() { + auto l_state = m_luaState; + lua_getglobal(l_state, "package"); + lua_getfield(l_state, -1, "preload"); + lua_pushcfunction(l_state, LuaEngine::lua_init_module); + lua_setfield(l_state, -2, LuaEngine::module_name.c_str()); + lua_pop(l_state, 2); +} + +std::string +LuaEngine::search_lua_path(lua_State* l_state) { + // Get package.path + lua_getglobal(l_state, "package"); + lua_getfield(l_state, -1, "path"); + std::stringstream path_ss(lua_tostring(l_state, -1)); + lua_pop(l_state, 2); + + // Tokenize path using ';' as delimiter + std::string file_path; + // Use lambda here to ensure file closes as soon as it goes out of scope + auto file_exists = [](const std::string& path) -> bool { + std::ifstream file(path); + return file.is_open(); + }; + + while (getline(path_ss, file_path, ';')) { + size_t pos; + while ((pos = file_path.find('?')) != std::string::npos) { + file_path.replace(pos, 1, LuaEngine::module_name); + } + if (file_exists(file_path)) + return file_path; + } + return ""; +} + +int +LuaEngine::lua_rtorrent_call(lua_State* l_state) { + auto method = lua_tostring(l_state, 1); + lua_remove(l_state, 1); + torrent::Object object; + rpc::target_type target = rpc::make_target(); + ; + rpc::CommandMap::iterator itr = rpc::commands.find(std::string(method).c_str()); + if (itr == rpc::commands.end()) { + throw torrent::input_error("method not found: " + std::string(method)); + } + object = lua_callstack_to_object(l_state, itr->second.m_flags, &target); + try { + const auto& result = rpc::commands.call_command(itr, object, target); + object_to_lua(l_state, result); + return 1; + } catch (torrent::base_error& e) { + throw luaL_error(l_state, e.what()); + } +} + +int +LuaEngine::lua_init_module(lua_State* l_state) { + lua_createtable(l_state, 0, 1); + // Assign functions + lua_pushliteral(l_state, "call"); + lua_pushcfunction(l_state, LuaEngine::lua_rtorrent_call); + lua_settable(l_state, -3); + auto lua_file = search_lua_path(l_state); + // Should this throw if it fails to find the file? + if (!lua_file.empty()) { + // Pop table into global var for file to manipulate + lua_setglobal(l_state, "rtorrent"); + int status = luaL_loadfile(l_state, lua_file.c_str()); + if (status == LUA_OK) { + status = lua_pcall(l_state, 0, LUA_MULTRET, 0); + } + check_lua_status(l_state, status); + // Clean up global var + lua_pushnil(l_state); + lua_setglobal(l_state, "rtorrent"); + // Leave the original table in place + } + return 1; +} + +void +object_to_target(const torrent::Object& obj, int call_flags, rpc::target_type* target) { + if (!obj.is_string()) { + throw torrent::input_error("invalid parameters: target must be a string"); + } + std::string target_string = obj.as_string(); + bool require_index = (call_flags & (CommandMap::flag_tracker_target | CommandMap::flag_file_target)); + if (target_string.empty() && !require_index) { + return; + } + + // Length of SHA1 hash is 40 + if (target_string.size() < 40) { + throw torrent::input_error("invalid parameters: invalid target"); + } + + char type = 'd'; + std::string hash; + std::string index; + const auto& delim_pos = target_string.find_first_of(':', 40); + if (delim_pos == target_string.npos || delim_pos + 2 >= target_string.size()) { + if (require_index) { + throw torrent::input_error("invalid parameters: no index"); + } + hash = target_string; + } else { + hash = target_string.substr(0, delim_pos); + type = target_string[delim_pos + 1]; + index = target_string.substr(delim_pos + 2); + } + core::Download* download = xmlrpc.slot_find_download()(hash.c_str()); + + if (download == nullptr) + throw torrent::input_error("invalid parameters: info-hash not found"); + + try { + switch (type) { + case 'd': + *target = rpc::make_target(download); + break; + case 'f': + *target = rpc::make_target(command_base::target_file, xmlrpc.slot_find_file()(download, std::stoi(std::string(index)))); + break; + case 't': + *target = + rpc::make_target(command_base::target_tracker, xmlrpc.slot_find_tracker()(download, std::stoi(std::string(index)))); + break; + case 'p': { + if (index.size() < 40) { + throw torrent::input_error("Not a hash string."); + } + torrent::HashString hash; + torrent::hash_string_from_hex_c_str(index.c_str(), hash); + *target = rpc::make_target(command_base::target_peer, xmlrpc.slot_find_peer()(download, hash)); + break; + } + default: + throw torrent::input_error("invalid parameters: unexpected target type"); + } + } catch (const std::logic_error&) { + throw torrent::input_error("invalid parameters: invalid index"); + } +} + +void +object_to_lua(lua_State* l_state, torrent::Object const& object) { + // Converts an object to a single Lua stack object + switch (object.type()) { + case torrent::Object::TYPE_VALUE: + lua_pushnumber(l_state, static_cast(object.as_value())); + break; + case torrent::Object::TYPE_NONE: + lua_pushnil(l_state); + break; + case torrent::Object::TYPE_STRING: + lua_pushstring(l_state, object.as_string().c_str()); + break; + case torrent::Object::TYPE_LIST: { + int index = 1; + auto object_list = object.as_list(); + + lua_createtable(l_state, static_cast(object_list.size()), 0); + int table_idx = lua_gettop(l_state); + for (const auto& itr : object_list) { + object_to_lua(l_state, itr); + lua_rawseti(l_state, table_idx, lua_Integer(index++)); + } + break; + } + case torrent::Object::TYPE_MAP: { + auto object_map = object.as_map(); + lua_createtable(l_state, 0, static_cast(object_map.size())); + int table_idx = lua_gettop(l_state); + for (const auto& itr : object_map) { + object_to_lua(l_state, itr.second); + lua_pushlstring(l_state, itr.first.c_str(), itr.first.size()); + lua_rawset(l_state, table_idx); + } + break; + } + default: + lua_pushnumber(l_state, lua_Number(object.type())); + break; + } +} + +torrent::Object +lua_to_object(lua_State* l_state) { + switch (lua_type(l_state, -1)) { + case LUA_TNUMBER: + return torrent::Object(lua_tonumber(l_state, -1)); + case LUA_TSTRING: + return torrent::Object(lua_tostring(l_state, -1)); + case LUA_TBOOLEAN: + return torrent::Object(lua_toboolean(l_state, -1)); + case LUA_TTABLE: { + lua_pushnil(l_state); + int status = lua_next(l_state, -2); + if (status == 0) + return torrent::Object::create_map(); + // If the table starts at 1, assume it's an array + if (lua_isnumber(l_state, -2) && (lua_tonumber(l_state, -2) == 1)) { + torrent::Object list = torrent::Object::create_list(); + list.insert_back(lua_to_object(l_state)); + lua_pop(l_state, 1); + while (lua_next(l_state, -2) != 0) { + list.insert_back(lua_to_object(l_state)); + lua_pop(l_state, 1); + } + return list; + } else { + torrent::Object map = torrent::Object::create_map(); + // Reset the stack and start from the first element + lua_pop(l_state, 2); + lua_pushnil(l_state); + while (lua_next(l_state, -2) != 0) { + std::string key; + // Bencode dictionaries require string keys, but naively calling + // lua_tostring would auto-convert any numbers to strings in-place, + // which would throw off lua_next + switch (lua_type(l_state, -2)) { + case LUA_TNUMBER: + key = std::to_string(lua_tonumber(l_state, -2)); + break; + case LUA_TSTRING: + key = lua_tostring(l_state, -2); + break; + } + map.insert_key(key, lua_to_object(l_state)); + lua_pop(l_state, 1); + } + return map; + } + return torrent::Object(lua_tostring(l_state, -1)); + } + default: + std::string result = luaL_tolstring(l_state, -1, NULL); + lua_pop(l_state, 1); + return torrent::Object(result); + } +} + +torrent::Object +lua_callstack_to_object(lua_State* l_state, int command_flags, rpc::target_type* target) { + torrent::Object object; + if (lua_gettop(l_state) == 0) { + return torrent::Object(); + } + + if (!lua_isstring(l_state, 1)) { + throw torrent::input_error("invalid parameters: target must be a string"); + } + object_to_target(torrent::Object(lua_tostring(l_state, 1)), command_flags, target); + + // start from the second argument since the first is the target + lua_remove(l_state, 1); + + if (lua_gettop(l_state) == 0) { + return torrent::Object(); + } else { + torrent::Object result = torrent::Object::create_list(); + torrent::Object::list_type& list_ref = result.as_list(); + while (lua_gettop(l_state) != 0) { + list_ref.insert(list_ref.begin(), lua_to_object(l_state)); + lua_remove(l_state, -1); + } + return result; + } + + return object; +} + +int +lua_rtorrent_call(lua_State* l_state) { + torrent::Object object; + rpc::target_type target = rpc::make_target(); + auto method = lua_tostring(l_state, 1); + lua_remove(l_state, 1); + + rpc::CommandMap::iterator itr = rpc::commands.find(method); + if (itr == rpc::commands.end()) { + throw torrent::input_error("method not found: " + std::string(method)); + } + object = lua_callstack_to_object(l_state, itr->second.m_flags, &target); + try { + const auto& result = rpc::commands.call_command(itr, object, target); + object_to_lua(l_state, result); + return 1; + } catch (torrent::base_error& e) { + throw luaL_error(l_state, e.what()); + } +} + +void +check_lua_status(lua_State* l_state, int status) { + if (status != LUA_OK) { + throw torrent::input_error(lua_tostring(l_state, -1)); + } +} + +torrent::Object +execute_lua(LuaEngine* engine, rpc::target_type target_type, torrent::Object const& raw_args, int flags) { + size_t lua_argc = 1; // Target is always present, even if empty + lua_State* l_state = engine->state(); + std::string target_string; + + switch (target_type.first) { + case (command_base::target_download): + core::Download* dl_target = (core::Download*)target_type.second; + torrent::HashString infohash = dl_target->info()->hash(); + target_string = rak::transform_hex_str(infohash); + break; + } + if (raw_args.is_list()) { + const torrent::Object::list_type& args = raw_args.as_list(); + if (flags & LuaEngine::flag_string) { + check_lua_status(l_state, luaL_loadstring(l_state, args.begin()->as_string().c_str())); + } else { + check_lua_status(l_state, luaL_loadfile(l_state, args.begin()->as_string().c_str())); + } + object_to_lua(l_state, target_string); + for (torrent::Object::list_const_iterator itr = std::next(args.begin()), last = args.end(); itr != last; itr++) { + object_to_lua(l_state, *itr); + } + lua_argc += args.size() - 1; + } else { + const torrent::Object::string_type& target = raw_args.as_string(); + if (flags & LuaEngine::flag_string) { + check_lua_status(l_state, luaL_loadstring(l_state, target.c_str())); + } else { + check_lua_status(l_state, luaL_loadfile(l_state, target.c_str())); + } + object_to_lua(l_state, target_string); + } + if (flags & LuaEngine::flag_autocall_upvalue) { + lua_getglobal(l_state, "rtorrent"); + lua_getfield(l_state, -1, "autocall_config"); + lua_remove(l_state, -2); + lua_setupvalue(l_state, -2, 1); + } + check_lua_status(l_state, lua_pcall(l_state, lua_argc, LUA_MULTRET, 0)); + return lua_to_object(l_state); +} + +#else +torrent::Object +execute_lua(LuaEngine* engine, torrent::Object const& rawArgs, int flags) { + throw torrent::input_error("Lua support not enabled"); + return torrent::Object(); +} +void +LuaEngine::LuaEngine() {} +LuaEngine::~LuaEngine() {} +#endif +} // namespace rpc diff --git a/src/rpc/lua.h b/src/rpc/lua.h new file mode 100644 index 000000000..2b13c7f76 --- /dev/null +++ b/src/rpc/lua.h @@ -0,0 +1,48 @@ +#ifndef RTORRENT_LUA_H +#define RTORRENT_LUA_H + +#include "rpc/command.h" +#include + +#ifdef HAVE_LUA +#include "lua.h" +#include +#include +#endif + +namespace rpc { + +class LuaEngine { +public: + static const int flag_string = 0x1; + static const int flag_autocall_upvalue = 0x2; + static const std::string module_name; + + LuaEngine(); + ~LuaEngine(); +#ifdef HAVE_LUA + // lua_CFunctions + static int lua_init_module(lua_State* l_state); + static int lua_rtorrent_call(lua_State* l_state); + void set_package_preload(); + lua_State* state() { return m_luaState; } + +private: + static std::string search_lua_path(lua_State* l_state); + lua_State* m_luaState; +#endif +}; +torrent::Object execute_lua(LuaEngine* engine, rpc::target_type target, const torrent::Object& raw_args, int flags); + +#ifdef HAVE_LUA +int rtorrent_call(lua_State* l_state); +void init_rtorrent_module(lua_State* l_state); +void object_to_lua(lua_State* l_state, torrent::Object const& object); +void check_lua_status(lua_State* l_state, int status); +torrent::Object lua_to_object(lua_State* l_state); +torrent::Object lua_callstack_to_object(lua_State* l_state, int commandFlags, rpc::target_type* target); +#endif + +} // namespace rpc + +#endif