diff --git a/.travis.yml b/.travis.yml index 3df96934f..791488f40 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ addons: packages: - libxml2-dev - libreadline-dev + - liblua5.2-dev - valgrind install: - ./autogen.sh diff --git a/configure.ac b/configure.ac index 5654723c9..f37484300 100644 --- a/configure.ac +++ b/configure.ac @@ -65,6 +65,7 @@ if test x"$enable_debug" = x"yes"; then fi dnl Version info in libtool's notation +AC_SUBST([LIBAUGLUA_VERSION_INFO], [0:0:0]) AC_SUBST([LIBAUGEAS_VERSION_INFO], [20:0:20]) AC_SUBST([LIBFA_VERSION_INFO], [5:1:4]) @@ -110,12 +111,39 @@ gl_INIT PKG_PROG_PKG_CONFIG PKG_CHECK_MODULES([LIBXML], [libxml-2.0]) +LUA_SUBDIRS= +LUA_MAKEFILES= +m4_foreach_w([LUAPKG], [lua5.1 lua5.2 lua5.3 lua], [ + AC_MSG_CHECKING([for LUAPKG]) + PKG_CHECK_EXISTS(LUAPKG, [ + AC_MSG_RESULT([yes]) + LIBLUA_CFLAGS=$($PKG_CONFIG --cflags LUAPKG) + LIBLUA_LIBS=$($PKG_CONFIG --libs LUAPKG) + LUA_SUBDIRS="$LUA_SUBDIRS LUAPKG" + + LUA_SUBDIR=src/LUAPKG + AS_MKDIR_P([$LUA_SUBDIR]) + + LUA_MAKEFILE=$LUA_SUBDIR/Makefile + ln -fs ../Makefile.luamod $LUA_MAKEFILE.am + eval $AUTOMAKE $LUA_MAKEFILE + LUA_MAKEFILES="$LUA_MAKEFILES $LUA_MAKEFILE" + ], [AC_MSG_RESULT([no])]) +]) +if test -z "$LIBLUA_LIBS"; then + AC_MSG_ERROR([no Lua libraries found]) +fi +AC_SUBST([LIBLUA_CFLAGS]) +AC_SUBST([LIBLUA_LIBS]) +AC_SUBST([LUA_SUBDIRS]) + AC_CHECK_FUNCS([strerror_r fsync]) AC_OUTPUT(Makefile \ gnulib/lib/Makefile \ gnulib/tests/Makefile \ src/Makefile \ + $LUA_MAKEFILES \ man/Makefile \ tests/Makefile \ examples/Makefile \ diff --git a/src/Makefile.am b/src/Makefile.am index 5616796ea..87751d389 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,9 @@ +SUBDIRS = . $(LUA_SUBDIRS) + GNULIB= ../gnulib/lib/libgnu.la GNULIB_CFLAGS= -I $(top_builddir)/gnulib/lib -I $(top_srcdir)/gnulib/lib -AM_CFLAGS = @AUGEAS_CFLAGS@ @WARN_CFLAGS@ $(GNULIB_CFLAGS) $(LIBXML_CFLAGS) +AM_CFLAGS = @AUGEAS_CFLAGS@ @WARN_CFLAGS@ $(GNULIB_CFLAGS) $(LIBXML_CFLAGS) $(LIBLUA_CFLAGS) AM_YFLAGS=-d -p spec_ @@ -11,7 +13,7 @@ BUILT_SOURCES = datadir.h DISTCLEANFILES = datadir.h -lib_LTLIBRARIES = libfa.la libaugeas.la +lib_LTLIBRARIES = libfa.la libaugeas.la libauglua.la noinst_LTLIBRARIES = liblexer.la bin_PROGRAMS = augtool augparse @@ -26,9 +28,11 @@ libaugeas_la_SOURCES = augeas.h augeas.c augrun.c pathx.c \ info.c info.h errcode.c errcode.h jmt.h jmt.c if USE_VERSION_SCRIPT + AUGLUA_VERSION_SCRIPT = $(VERSION_SCRIPT_FLAGS)$(srcdir)/auglua_sym.version AUGEAS_VERSION_SCRIPT = $(VERSION_SCRIPT_FLAGS)$(srcdir)/augeas_sym.version FA_VERSION_SCRIPT = $(VERSION_SCRIPT_FLAGS)$(srcdir)/fa_sym.version else + AUGLUA_VERSION_SCRIPT = AUGEAS_VERSION_SCRIPT = FA_VERSION_SCRIPT = endif @@ -37,11 +41,16 @@ libaugeas_la_LDFLAGS = $(AUGEAS_VERSION_SCRIPT) \ -version-info $(LIBAUGEAS_VERSION_INFO) libaugeas_la_LIBADD = liblexer.la libfa.la $(LIB_SELINUX) $(LIBXML_LIBS) $(GNULIB) +libauglua_la_LDFLAGS = $(AUGLUA_VERSION_SCRIPT) \ + -version-info $(LIBAUGLUA_VERSION_INFO) +libauglua_la_SOURCES = augeas.h auglua.c auglua.h internal.h luamod.c luamod.h +libauglua_la_LIBADD = libaugeas.la $(LIBLUA_LIBS) + augtool_SOURCES = augtool.c -augtool_LDADD = libaugeas.la $(READLINE_LIBS) $(LIBXML_LIBS) $(GNULIB) +augtool_LDADD = libaugeas.la libauglua.la $(READLINE_LIBS) $(GNULIB) augparse_SOURCES = augparse.c -augparse_LDADD = libaugeas.la $(LIBXML_LIBS) $(GNULIB) +augparse_LDADD = libaugeas.la $(LIBXML_LIBS) $(LIBLUA_LIBS) $(GNULIB) libfa_la_SOURCES = fa.c fa.h hash.c hash.h memory.c memory.h ref.h ref.c libfa_la_LIBADD = $(LIB_SELINUX) $(GNULIB) diff --git a/src/Makefile.luamod b/src/Makefile.luamod new file mode 100644 index 000000000..d26b7aa4d --- /dev/null +++ b/src/Makefile.luamod @@ -0,0 +1,13 @@ +# Copyright (C) 2016 Kaarle Ritvanen + +AUTOMAKE_OPTIONS = subdir-objects + +LUA_PACKAGE = $(notdir $(CURDIR)) + +luadir = $(libdir)/lua/$(shell $(PKG_CONFIG) --variable=V $(LUA_PACKAGE)) +lua_LTLIBRARIES = augeas.la + +augeas_la_SOURCES = ../augeas.h ../luamod.c ../luamod.h +augeas_la_CFLAGS = $(AM_CFLAGS) $(LIBXML_CFLAGS) $(shell $(PKG_CONFIG) --cflags $(LUA_PACKAGE)) +augeas_la_LIBADD = ../libaugeas.la +augeas_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version -module -shared diff --git a/src/auglua.c b/src/auglua.c new file mode 100644 index 000000000..9118a615b --- /dev/null +++ b/src/auglua.c @@ -0,0 +1,139 @@ +/* + * auglua.c: lua integration for augtool + * + * Copyright (C) 2015 Raphaël Pinson + * Copyright (C) 2016 Kaarle Ritvanen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Raphaël Pinson + */ + +#include +#include "internal.h" +#include "augeas.h" +#include "luamod.h" +#include "auglua.h" +#include +#include +#include +#include + + +#define REG_KEY_MODULE "augeas-module" +#define REG_KEY_INSTANCE "augeas-instance" + +static void push_reg_value(lua_State *L, const char *key) { + lua_pushstring(L, key); + lua_gettable(L, LUA_REGISTRYINDEX); +} + +static int call_function(lua_State *L, const char *name) { + push_reg_value(L, REG_KEY_MODULE); + lua_pushstring(L, name); + lua_gettable(L, -2); + lua_insert(L, 1); + + push_reg_value(L, REG_KEY_INSTANCE); + lua_insert(L, 2); + + lua_call(L, lua_gettop(L) - 1, LUA_MULTRET); + return lua_gettop(L); +} + +static int call_bound_function(lua_State *L) { + int vals = call_function(L, lua_tostring(L, lua_upvalueindex(1))); + if (lua_isnil(L, 1) && lua_isstring(L, 2)) { + lua_pushvalue(L, 2); + lua_error(L); + } + return vals; +} + +static int bind_function(lua_State *L) { + lua_pushcclosure(L, call_bound_function, 1); + return 1; +} + +static int lua_aug_save(lua_State *L) { + int n; + + call_function(L, "save"); + + if (lua_isnil(L, 1)) { + lua_pushstring(L, "saving failed (run 'errors' for details)"); + lua_error(L); + } else { + lua_settop(L, 0); + lua_pushliteral(L, "/augeas/events/saved"); + call_function(L, "match"); + + if (!lua_isnil(L, 1)) { + n = (int) lua_tointeger(L, 2); + if (n > 0) + printf("Saved %d file(s)\n", n); + } + } + + return 0; +} + +struct lua_State *setup_lua(augeas *a) { + lua_State *L = luaL_newstate(); + luaL_openlibs(L); + + lua_pushliteral(L, REG_KEY_MODULE); + luaopen_augeas(L); + lua_settable(L, LUA_REGISTRYINDEX); + + lua_pushliteral(L, REG_KEY_INSTANCE); + luamod_push_augeas(L, a); + lua_settable(L, LUA_REGISTRYINDEX); + + static const luaL_Reg augfuncs[] = { + //{ "span", lua_aug_span }, + { "save", lua_aug_save }, + //{ "escape_name", lua_aug_escape_name }, + //{ "to_xml", lua_aug_to_xml }, + //{ "errors", lua_aug_errors }, + { NULL, NULL } + }; + + luaL_newlib(L, augfuncs); + + static const luaL_Reg meta[] = {{"__index", bind_function}, {NULL, NULL}}; + luaL_newlib(L, meta); + lua_setmetatable(L, -2); + + lua_setglobal(L, "aug"); + + return L; +} + +int aug_lua(lua_State *L, const char *text) { + int code = luaL_loadbuffer(L, text, strlen(text), "line") || lua_pcall(L, 0, 0, 0); + return code; +} + + +/* + * Local variables: + * indent-tabs-mode: nil + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ + diff --git a/src/auglua.h b/src/auglua.h new file mode 100644 index 000000000..0d0261069 --- /dev/null +++ b/src/auglua.h @@ -0,0 +1,48 @@ +/* + * auglua.h: Lua helper lib for Augeas + * + * Copyright (C) 2015 Raphaël Pinson + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Raphaël Pinson + */ +#include + +#ifndef AUGLUA_H_ +#define AUGLUA_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +struct lua_State *setup_lua(augeas *a); + +int aug_lua(lua_State *L, const char *text); + +#ifdef __cplusplus +} +#endif + +#endif + +/* + * Local variables: + * indent-tabs-mode: nil + * c-indent-level: 4 + * c-basic-offset: 4 + * tab-width: 4 + * End: + */ diff --git a/src/auglua_sym.version b/src/auglua_sym.version new file mode 100644 index 000000000..df97f89f5 --- /dev/null +++ b/src/auglua_sym.version @@ -0,0 +1,6 @@ +AUGLUA_0.0.0 { + global: + setup_lua; + aug_lua; + local: *; +}; diff --git a/src/augtool.c b/src/augtool.c index 0603c6de1..b33dd0f29 100644 --- a/src/augtool.c +++ b/src/augtool.c @@ -22,6 +22,7 @@ #include #include "augeas.h" +#include "auglua.h" #include "internal.h" #include "safe-alloc.h" @@ -39,6 +40,10 @@ #include #include +#include +#include +#include + /* Global variables */ static augeas *aug = NULL; @@ -50,6 +55,8 @@ char *transforms = NULL; size_t transformslen = 0; const char *inputfile = NULL; int echo_commands = 0; /* Gets also changed in main_loop */ +bool use_lua = false; +static lua_State *LS = NULL; bool print_version = false; bool auto_save = false; bool interactive = false; @@ -58,6 +65,8 @@ bool timing = false; char *history_file = NULL; #define AUGTOOL_PROMPT "augtool> " +#define AUGTOOL_LUA_PROMPT "augtool|lua> " +#define AUGTOOL_LUA_CONT_PROMPT "augtool|lua?> " /* * General utilities @@ -302,6 +311,7 @@ static void help(void) { " syntax, e.g. -t 'Fstab incl /etc/fstab.bak'\n"); fprintf(stderr, " -e, --echo echo commands when reading from a file\n"); fprintf(stderr, " -f, --file FILE read commands from FILE\n"); + fprintf(stderr, " -l, --lua use Lua interpreter instead of native Augeas\n"); fprintf(stderr, " -s, --autosave automatically save at the end of instructions\n"); fprintf(stderr, " -i, --interactive run an interactive shell after evaluating\n" " the commands in STDIN and FILE\n"); @@ -334,6 +344,7 @@ static void parse_opts(int argc, char **argv) { { "transform", 1, 0, 't' }, { "echo", 0, 0, 'e' }, { "file", 1, 0, 'f' }, + { "lua", 0, 0, 'l' }, { "autosave", 0, 0, 's' }, { "interactive", 0, 0, 'i' }, { "nostdinc", 0, 0, 'S' }, @@ -346,7 +357,7 @@ static void parse_opts(int argc, char **argv) { }; int idx; - while ((opt = getopt_long(argc, argv, "hnbcr:I:t:ef:siSLA", options, &idx)) != -1) { + while ((opt = getopt_long(argc, argv, "hnbcr:I:t:ef:lsiSLA", options, &idx)) != -1) { switch(opt) { case 'c': flags |= AUG_TYPE_CHECK; @@ -375,6 +386,9 @@ static void parse_opts(int argc, char **argv) { case 'f': inputfile = optarg; break; + case 'l': + use_lua = true; + break; case 's': auto_save = true; break; @@ -489,8 +503,19 @@ static void install_signal_handlers(void) { sigaction(SIGINT, &sigint_action, NULL); } -static int main_loop(void) { - char *line = NULL; + +static int ends_with(const char *str, const char *suffix) { + if (!str || !suffix) + return 0; + size_t lenstr = strlen(str); + size_t lensuffix = strlen(suffix); + if (lensuffix > lenstr) + return 0; + return strncmp(str + lenstr - lensuffix, suffix, lensuffix) == 0; +} + +static int main_loop(int argc, char **argv) { + char *line = NULL, *cur_line = NULL; int ret = 0; char inputline [128]; int code; @@ -499,13 +524,41 @@ static int main_loop(void) { bool in_interactive = false; if (inputfile) { - if (freopen(inputfile, "r", stdin) == NULL) { - char *msg = NULL; - if (asprintf(&msg, "Failed to open %s", inputfile) < 0) - perror("Failed to open input file"); - else - perror(msg); - return -1; + if (use_lua) { + if (luaL_loadfile(LS, inputfile)) { + printf("luaL_loadfile() failed: %s\n", lua_tostring(LS, -1)); + lua_close(LS); + return -1; + } + for (int i=0; i < argc; i++) + lua_pushstring(LS, argv[i]); + if (lua_pcall(LS, argc, 0, 0)) { + printf("lua_pcall() failed: %s\n", lua_tostring(LS, -1)); + lua_close(LS); + return -1; + } + if (auto_save) { + strncpy(inputline, "save()", sizeof(inputline)); + line = inputline; + code = luaL_loadbuffer(LS, line, strlen(line), "line") || lua_pcall(LS, 0, 0, 0); + + if (code) { + fprintf(stderr, "%s\n", lua_tostring(LS, -1)); + lua_pop(LS, 1); /* pop error message from the stack */ + ret = -1; + } + } + lua_close(LS); + return 0; + } else { + if (freopen(inputfile, "r", stdin) == NULL) { + char *msg = NULL; + if (asprintf(&msg, "Failed to open %s", inputfile) < 0) + perror("Failed to open input file"); + else + perror(msg); + return -1; + } } } @@ -520,7 +573,14 @@ static int main_loop(void) { while(1) { if (get_line) { - line = readline(AUGTOOL_PROMPT); + if (use_lua) { + if (cur_line == NULL) + line = readline(AUGTOOL_LUA_PROMPT); + else + line = readline(AUGTOOL_LUA_CONT_PROMPT); + } else { + line = readline(AUGTOOL_PROMPT); + } } else { line = NULL; } @@ -568,6 +628,8 @@ static int main_loop(void) { } if (end_reached) { + if (use_lua) + lua_close(LS); if (echo_commands) printf("\n"); return ret; @@ -578,15 +640,43 @@ static int main_loop(void) { continue; } - code = run_command(line, timing); - if (code == -2) { - free(line); - return ret; - } + if (use_lua) { + char *buf; + if (cur_line == NULL) { + buf = malloc(sizeof(char) * strlen(line)); + strcpy(buf, line); + } else { + sprintf(buf, "%s\n%s", cur_line, line); + } - if (code < 0) { - ret = -1; - print_aug_error(); + code = aug_lua(LS, buf); + if (isatty(fileno(stdin))) + add_history(line); + + if (code) { + const char *err = lua_tostring(LS, -1); + if (ends_with(err, " near ")) { + cur_line = malloc(sizeof(char) * strlen(buf)); + strcpy(cur_line, buf); + } else { + fprintf(stderr, "%s\n", err); + lua_pop(LS, 1); /* pop error message from the stack */ + ret = -1; + } + } else { + cur_line = NULL; + } + } else { + code = run_command(line, timing); + if (code == -2) { + free(line); + return ret; + } + + if (code < 0) { + ret = -1; + print_aug_error(); + } } if (line != inputline) @@ -607,9 +697,16 @@ static int run_args(int argc, char **argv) { strcat(line, argv[i]); strcat(line, " "); } - if (echo_commands) - printf("%s%s\n", AUGTOOL_PROMPT, line); - code = run_command(line, timing); + if (echo_commands) { + if (use_lua) + printf("%s%s\n", AUGTOOL_LUA_PROMPT, line); + else + printf("%s%s\n", AUGTOOL_PROMPT, line); + } + if (use_lua) + code = luaL_loadbuffer(LS, line, strlen(line), "line") || lua_pcall(LS, 0, 0, 0); + else + code = run_command(line, timing); free(line); if (code >= 0 && auto_save) if (echo_commands) @@ -677,17 +774,21 @@ int main(int argc, char **argv) { print_aug_error(); exit(EXIT_FAILURE); } + + if (use_lua) + LS = setup_lua(aug); + add_transforms(transforms, transformslen); if (print_version) { print_version_info(); return EXIT_SUCCESS; } readline_init(); - if (optind < argc) { + if (optind < argc && !use_lua) { // Accept one command from the command line r = run_args(argc - optind, argv+optind); } else { - r = main_loop(); + r = main_loop(argc - optind, argv+optind); } if (history_file != NULL) write_history(history_file); diff --git a/src/luamod.c b/src/luamod.c new file mode 100644 index 000000000..d4ce082d7 --- /dev/null +++ b/src/luamod.c @@ -0,0 +1,523 @@ +/* +--- Lua wrapper for Augeas library. +-- +-- In general, the functions map straight to the C library. See the +-- descriptions below for details. + */ + +/* + * Copyright (C) 2010-2013 Natanael Copa + * Copyright (C) 2013-2016 Kaarle Ritvanen + * Copyright (C) 2015 Raphaël Pinson + */ + +#include + +#include +#include + +#include +#include +#include + +#include "augeas.h" +#include "luamod.h" + +#define LIBNAME "augeas" +#define PAUG_META "augeas" + +#ifndef VERSION +#define VERSION "unknown" +#endif + +#define LUA_FILEHANDLE "FILE*" + +#if LUA_VERSION_NUM < 502 +# define luaL_newlib(L,l) (lua_newtable(L), luaL_register(L,NULL,l)) +# define luaL_setfuncs(L,l,n) (assert(n==0), luaL_register(L,NULL,l)) +#else +static int luaL_typerror (lua_State *L, int narg, const char *tname) +{ + const char *msg = lua_pushfstring(L, "%s expected, got %s", + tname, luaL_typename(L, narg)); + return luaL_argerror(L, narg, msg); +} +#endif + +struct aug_userdata { + augeas *aug; + int gc; +}; + +struct aug_flagmap { + const char *name; + int value; +}; + +struct aug_flagmap Taug_flagmap[] = { + { "none", AUG_NONE }, + { "save_backup", AUG_SAVE_BACKUP }, + { "save_newfile", AUG_SAVE_NEWFILE }, + { "typecheck", AUG_TYPE_CHECK }, + { "no_stdinc", AUG_NO_STDINC }, + { "save_noop", AUG_SAVE_NOOP }, + { "no_load", AUG_NO_LOAD }, + { "no_modl_autoload", AUG_NO_MODL_AUTOLOAD }, + { NULL, 0 } +}; + +static const char *get_opt_string_field(lua_State *L, int index, + const char *key, const char *def) +{ + const char *value; + lua_getfield(L, index, key); + value = luaL_optstring(L, -1, def); + lua_pop(L, 1); + return value; +} + +static int get_boolean_field(lua_State *L, int index, const char *key) +{ + int value; + lua_getfield(L, index, key); + value = lua_toboolean(L, -1); + lua_pop(L, 1); + return value; +} + +static int pusherror(lua_State *L, augeas *aug, const char *info) +{ + lua_pushnil(L); + if (info==NULL) + lua_pushstring(L, aug_error_message(aug)); + else + lua_pushfstring(L, "%s: %s", info, aug_error_message(aug)); + lua_pushinteger(L, aug_error(aug)); + return 3; +} + +static int pushresult(lua_State *L, int i, augeas *aug, const char *info) +{ + if (i < 0) + return pusherror(L, aug, info); + lua_pushinteger(L, i); + return 1; +} + +static augeas *Paug_checkarg(lua_State *L, int index) +{ + struct aug_userdata *ud; + luaL_checktype(L, index, LUA_TUSERDATA); + ud = (struct aug_userdata *) luaL_checkudata(L, index, PAUG_META); + if (ud == NULL) + luaL_typerror(L, index, PAUG_META); + return ud->aug; +} + +void luamod_push_augeas(lua_State *L, augeas *a) { + struct aug_userdata *ud = (struct aug_userdata *) lua_newuserdata(L, sizeof(struct aug_userdata)); + luaL_getmetatable(L, PAUG_META); + lua_setmetatable(L, -2); + ud->aug = a; + ud->gc = 0; +} + +static int Paug_init(lua_State *L) +{ + augeas *a; + struct aug_flagmap *f; + const char *root = NULL, *loadpath = NULL; + int flags = 0; + + if (lua_istable(L, 1)) { + root = get_opt_string_field(L, 1, "root", NULL); + loadpath = get_opt_string_field(L, 1, "loadpath", NULL); + for (f = Taug_flagmap; f->name != NULL; f++) + if (get_boolean_field(L, 1, f->name)) + flags |= f->value; + } else { + root = luaL_optstring(L, 1, NULL); + loadpath = luaL_optstring(L, 2, NULL); + flags = luaL_optinteger(L, 3, AUG_NONE); + } + + a = aug_init(root, loadpath, flags); + if (a == NULL) + luaL_error(L, "aug_init failed"); + + luamod_push_augeas(L, a); + ((struct aug_userdata *) lua_touserdata(L, -1))->gc = 1; + + return 1; +} + +static int Paug_defvar(lua_State *L) +{ + augeas *a = Paug_checkarg(L, 1); + const char *name = luaL_checkstring(L, 2); + const char *expr = luaL_checkstring(L, 3); + return pushresult(L, aug_defvar(a, name, expr), a, NULL); +} + +static int Paug_defnode(lua_State *L) +{ + augeas *a = Paug_checkarg(L, 1); + const char *name = luaL_checkstring(L, 2); + const char *expr = luaL_checkstring(L, 3); + const char *value = luaL_checkstring(L, 4); + return pushresult(L, aug_defnode(a, name, expr, value, NULL), a, NULL); +} + +static int Paug_close(lua_State *L) +{ + struct aug_userdata *ud; + luaL_checktype(L, 1, LUA_TUSERDATA); + ud = (struct aug_userdata *) luaL_checkudata(L, 1, PAUG_META); + + if (ud && ud->gc && ud->aug) { + aug_close(ud->aug); + ud->aug = NULL; + } + return 0; +} + +static int Paug_get(lua_State *L) +{ + augeas *a; + const char *path; + const char *value = NULL; + int r; + + a = Paug_checkarg(L, 1); + path = luaL_checkstring(L, 2); + r = aug_get(a, path, &value); + if (r < 0) + return pusherror(L, a, path); + lua_pushstring(L, value); + return 1; +} + +static int Paug_label(lua_State *L) +{ + const char *value; + augeas *a = Paug_checkarg(L, 1); + const char *path = luaL_checkstring(L, 2); + int r = aug_label(a, path, &value); + if (r < 0) + return pusherror(L, a, path); + lua_pushstring(L, value); + return 1; +} + +static int Paug_set(lua_State *L) +{ + augeas *a; + const char *path, *value; + + a = Paug_checkarg(L, 1); + path = luaL_checkstring(L, 2); + value = lua_isnil(L, 3) ? NULL : luaL_checkstring(L, 3); + return pushresult(L, aug_set(a, path, value), a, path); +} + +static int Paug_setm(lua_State *L) +{ + augeas *a = Paug_checkarg(L, 1); + const char *base = luaL_checkstring(L, 2); + const char *sub= luaL_checkstring(L, 3); + const char *value = lua_isnil(L, 4) ? NULL : luaL_checkstring(L, 4); + return pushresult(L, aug_setm(a, base, sub, value), a, NULL); +} + +static int Paug_insert(lua_State *L) +{ + augeas *a = Paug_checkarg(L, 1); + const char *path = luaL_checkstring(L, 2); + const char *label = luaL_checkstring(L, 3); + int before = lua_toboolean(L, 4); + return pushresult(L, aug_insert(a, path, label, before), a, path); +} + +static int Paug_rm(lua_State *L) +{ + augeas *a = Paug_checkarg(L, 1); + const char *path = luaL_checkstring(L, 2); + return pushresult(L, aug_rm(a, path), a, NULL); +} + +static int Paug_mv(lua_State *L) +{ + augeas *a = Paug_checkarg(L, 1); + const char *src = luaL_checkstring(L, 2); + const char *dst = luaL_checkstring(L, 3); + return pushresult(L, aug_mv(a, src, dst), a, NULL); +} + +static int Paug_cp(lua_State *L) +{ + augeas *a = Paug_checkarg(L, 1); + const char *src = luaL_checkstring(L, 2); + const char *dst = luaL_checkstring(L, 3); + return pushresult(L, aug_cp(a, src, dst), a, NULL); +} + +static int Paug_rename(lua_State *L) +{ + augeas *a = Paug_checkarg(L, 1); + const char *path = luaL_checkstring(L, 2); + const char *label = luaL_checkstring(L, 3); + return pushresult(L, aug_rename(a, path, label), a, path); +} + +static int Paug_clear(lua_State *L) +{ + lua_settop(L, 2); + lua_pushnil(L); + return Paug_set(L); +} + +static int Paug_clearm(lua_State *L) +{ + lua_settop(L, 3); + lua_pushnil(L); + return Paug_setm(L); +} + +static int Paug_touch(lua_State *L) +{ + augeas *a = Paug_checkarg(L, 1); + const char *path = luaL_checkstring(L, 2); + return aug_match(a, path, NULL) ? 0 : Paug_clear(L); +} + +static int Paug_matches(lua_State *L) +{ + augeas *a = Paug_checkarg(L, 1); + const char *path = luaL_checkstring(L, 2); + return pushresult(L, aug_match(a, path, NULL), a, path); +} + +static int Paug_match(lua_State *L) +{ + augeas *a = Paug_checkarg(L, 1); + const char *path = luaL_checkstring(L, 2); + char **match = NULL; + int i, n; + n = aug_match(a, path, &match); + if (n < 0) + return pusherror(L, a, path); + + lua_newtable(L); + for (i = 0; i < n; i++) { + lua_pushnumber(L, i+1); + lua_pushstring(L, match[i]); + lua_settable(L, -3); + free(match[i]); + } + free(match); + lua_pushinteger(L, n); + return 2; +} + +static int Paug_save(lua_State *L) +{ + augeas *a = Paug_checkarg(L, 1); + return pushresult(L, aug_save(a), a, NULL); +} + +static int Paug_load(lua_State *L) +{ + augeas *a = Paug_checkarg(L, 1); + return pushresult(L, aug_load(a), a, NULL); +} + +static int Paug_text_store(lua_State *L) { + augeas *a = Paug_checkarg(L, 1); + const char *lens = luaL_checkstring(L, 2); + const char *node = luaL_checkstring(L, 3); + const char *path = luaL_checkstring(L, 4); + return pushresult(L, aug_text_store(a, lens, node, path), a, NULL); +} + +static int Paug_text_retrieve(lua_State *L) { + augeas *a = Paug_checkarg(L, 1); + const char *lens = luaL_checkstring(L, 2); + const char *node_in = luaL_checkstring(L, 3); + const char *path = luaL_checkstring(L, 4); + const char *node_out = luaL_checkstring(L, 5); + return pushresult(L, aug_text_retrieve(a, lens, node_in, path, node_out), a, NULL); +} + +static int Paug_transform(lua_State *L) { + augeas *a = Paug_checkarg(L, 1); + const char *lens = luaL_checkstring(L, 2); + const char *file = luaL_checkstring(L, 3); + int excl = lua_toboolean(L, 4); + return pushresult(L, aug_transform(a, lens, file, excl), a, NULL); +} + +static int Paug_print(lua_State *L) +{ + augeas *a; + const char *path; + FILE *f = stdout; + a = Paug_checkarg(L, 1); + path = luaL_checkstring(L, 2); + if (lua_isuserdata(L, 3)) + f = *(FILE**) luaL_checkudata(L, 3, LUA_FILEHANDLE); + return pushresult(L, aug_print(a, f, path), a, path); +} + +static int Paug_error(lua_State *L) +{ + augeas *a = Paug_checkarg(L, 1); + lua_pushinteger(L, aug_error(a)); + return 1; +} + +static int Paug_error_message(lua_State *L) +{ + augeas *a = Paug_checkarg(L, 1); + lua_pushstring(L, aug_error_message(a)); + return 1; +} + +static int Paug_error_minor_message(lua_State *L) +{ + augeas *a = Paug_checkarg(L, 1); + lua_pushstring(L, aug_error_minor_message(a)); + return 1; +} + +static int Paug_error_details(lua_State *L) +{ + augeas *a = Paug_checkarg(L, 1); + lua_pushstring(L, aug_error_details(a)); + return 1; +} + +static const luaL_Reg Paug_methods[] = { +/* +--- Initializes the library. +-- +-- * `params` Table of initialization parameters; all optional. `root` path to +-- Augeas, `loadpath` to Augeas lenses, and string/boolean pairs for the +-- flags in the Taug_flagmap array. +-- * `[return]` augeas object, used as first parameter in subsequent function +-- calls +function init(params) + */ + {"init", Paug_init}, + {"defvar", Paug_defvar}, + {"defnode", Paug_defnode}, +/* +--- Closes the library. Optional; automatically called by garbage collector. +-- +-- * `augobj` Augeas object from init() +function close(augobj) + */ + {"close", Paug_close}, +/* +--- Gets the value for an Augeas path. +-- +-- * `augobj` Augeas object from init() +-- * `path` Augeas path +-- * `[return]` Value for path +function get(augobj, path) + */ + {"get", Paug_get}, +/* +--- Sets the value for an Augeas path. +-- +-- * `augobj` Augeas object from init() +-- * `path` Augeas path +-- * `value` Value for path +function set(augobj, path, value) + */ + {"label", Paug_label}, + {"set", Paug_set}, + {"setm", Paug_setm}, + {"insert", Paug_insert}, + {"ins", Paug_insert}, + {"rm", Paug_rm}, + {"mv", Paug_mv}, + {"move", Paug_mv}, + {"cp", Paug_cp}, + {"copy", Paug_cp}, + {"rename", Paug_rename}, + {"clear", Paug_clear}, + {"clearm", Paug_clearm}, + {"touch", Paug_touch}, + {"matches", Paug_matches}, +/* +--- Collects paths in the Augeas tree. +-- +-- * `augobj` Augeas object from init() +-- * `path` Source path to be matched +-- * `[return]` Array of matching paths +function match(augobj, path) + */ + {"match", Paug_match}, + {"save", Paug_save}, +/* +--- Loads the values for the Augeas tree. +-- +-- * `augobj` Augeas object from init() +function load(augobj) + */ + {"load", Paug_load}, + {"text_store", Paug_text_store}, + {"store", Paug_text_store}, + {"text_retrieve", Paug_text_retrieve}, + {"retrieve", Paug_text_retrieve}, + {"transform", Paug_transform}, +/* +--- Prints the value(s) for an Augeas path. +-- +-- * `augobj` Augeas object from init() +-- * `path` Augeas path +-- * `file_handle` *optional* open file for output; otherwise uses stdout +function print(augobj, path, file_handle) + */ + {"print", Paug_print}, + {"error", Paug_error}, + {"error_message", Paug_error_message}, + {"error_minor_message", Paug_error_minor_message}, + {"error_details", Paug_error_details}, + {NULL, NULL} +}; + +static const luaL_Reg Laug_meta_methods[] = { + {"__gc", Paug_close}, + {NULL, NULL} +}; + + +LUALIB_API int luaopen_augeas(lua_State *L) +{ + struct aug_flagmap *f = Taug_flagmap; + luaL_newlib(L, Paug_methods); + lua_pushliteral(L, "version"); + lua_pushliteral(L, VERSION); + lua_settable(L, -3); + + while (f->name != NULL) { + lua_pushstring(L, f->name); + lua_pushinteger(L, f->value); + lua_settable(L, -3); + f++; + } + + luaL_newmetatable(L, PAUG_META); + luaL_setfuncs(L, Laug_meta_methods, 0); + lua_pushliteral(L, "__index"); + lua_pushvalue(L, -3); + lua_rawset(L, -3); + lua_pushliteral(L, "__metatable"); + lua_pushvalue(L, -3); + lua_rawset(L, -3); + lua_pop(L, 1); + + return 1; +} + diff --git a/src/luamod.h b/src/luamod.h new file mode 100644 index 000000000..1f2b6a8e0 --- /dev/null +++ b/src/luamod.h @@ -0,0 +1,9 @@ +/* Copyright (C) 2016 Kaarle Ritvanen */ + +#include +#include + +#include "augeas.h" + +LUALIB_API int luaopen_augeas(lua_State *L); +void luamod_push_augeas(lua_State *L, augeas *a); diff --git a/tests/Makefile.am b/tests/Makefile.am index e0353cacd..04ca94274 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -257,7 +257,8 @@ check_SCRIPTS = \ test-put-mount.sh test-put-mount-augnew.sh test-put-mount-augsave.sh \ test-save-empty.sh test-bug-1.sh test-idempotent.sh test-preserve.sh \ test-events-saved.sh test-save-mode.sh test-unlink-error.sh \ - test-augtool-empty-line.sh test-augtool-modify-root.sh + test-augtool-empty-line.sh test-augtool-modify-root.sh \ + test-lua.sh EXTRA_DIST = \ test-augtool root lens-test-1 \ diff --git a/tests/root/etc/passwd b/tests/root/etc/passwd index 9cefbfe48..6e662777b 100644 --- a/tests/root/etc/passwd +++ b/tests/root/etc/passwd @@ -1,4 +1,4 @@ -root:x:0:0:root:/root:/bin/bash +root:x:0:0:root:/root:/bin/zsh bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin diff --git a/tests/test-lua.sh b/tests/test-lua.sh new file mode 100755 index 000000000..996addb6e --- /dev/null +++ b/tests/test-lua.sh @@ -0,0 +1,13 @@ +#! /bin/bash + +# Test the lua interpreter in augtool + +ROOT=$abs_top_srcdir/tests/root +LENSES=$abs_top_srcdir/lenses + +OUT=$(augtool --lua --nostdinc -I $LENSES -r $ROOT -f $abs_top_srcdir/tests/test.auglua 2>&1) + +echo "$OUT" + +[[ -z $OUT ]] || exit 1 + diff --git a/tests/test.auglua b/tests/test.auglua new file mode 100644 index 000000000..57ff31a28 --- /dev/null +++ b/tests/test.auglua @@ -0,0 +1,107 @@ +--- test aug.get() +assert(aug.get("etc/passwd/root/uid") == "0") + +--- test aug.label() +assert(aug.label("etc/passwd/sync") == "sync") + +--- test aug.set() +aug.set("/foo", "bar") +assert(aug.get("/foo") == "bar") + +--- test aug.setm() +aug.setm("etc/passwd/*", "shell", "zsh") +assert(aug.get("etc/passwd/sync/shell") == "zsh") + +--- test aug.span() +--- TODO + +--- test aug.insert() +aug.insert("etc/passwd/*[1]", "foo", true) +assert(aug.label("etc/passwd/*[1]") == "foo") + +--- test aug.rm() +aug.rm("etc/passwd/sync") +assert(aug.label("etc/passwd/sync") == nil) + +--- test aug.mv() +aug.mv("etc/passwd/root", "etc/passwd/qux") +assert(aug.label("etc/passwd/*[last()]") == "qux") +assert(aug.label("etc/passwd/root") == nil) + +--- test aug.cp() +aug.cp("etc/passwd/qux", "etc/passwd/root") +assert(aug.get("etc/passwd/root/uid") == aug.get("etc/passwd/qux/uid")) + +--- test aug.rename() +aug.rename("etc/passwd/qux", "bar") +assert(aug.get("etc/passwd/root/uid") == aug.get("etc/passwd/bar/uid")) + +--- test aug.clear() +aug.clear("etc/passwd/qux/uid") +assert(aug.get("etc/passwd/qux/uid") == nil) + +--- test aug.clearm() +aug.clearm("etc/passwd/*", "uid") +assert(aug.get("etc/passwd/root/uid") == nil) + +--- test aug.touch() +aug.touch("etc/passwd/flex/uid") +assert(aug.get("etc/passwd/flex/uid") == nil) +aug.touch("etc/passwd/root/gid") +assert(aug.get("etc/passwd/root/gid") == "0") + +--- test aug.matches() +m = aug.matches("etc/passwd/*") +assert(m == 22) + +--- test aug.match() +m = aug.match("etc/passwd/*") +c = 0 +for k, v in pairs(m) do c= c+1 end +assert(c > 0) +assert(m[1] == "/files/etc/passwd/foo") + +--- test aug.defvar() +aug.defvar("root", "etc/passwd/root") +assert(aug.label("$root") == "root") + +--- test aug.defnode() +aug.defnode("raphink", "etc/passwd/raphink/uid", "314") +assert(aug.get("$raphink") == "314") +--- TODO: support 3 arguments +--- aug.defnode("gid", "etc/passwd/raphink/gid") +--- assert(aug.get("$gid") == nil) + +--- test aug.save() +--- reload tree to be clean +aug.load() +aug.set("etc/passwd/root/shell", "/bin/zsh") +aug.save() + +--- test aug.load() +aug.load() + +--- test aug.text_store() +aug.set("/input", "key=value\n") +aug.text_store("Shellvars.lns", "/input", "/parsed") +assert(aug.get("/parsed/key") == "value") + +--- test aug.text_retrieve() +aug.set("/parsed/key", "newval") +aug.text_retrieve("Shellvars.lns", "/input", "/parsed", "/output") +assert(aug.get("/output") == "key=newval\n") + +--- test aug.escape_name() +--- test that? + +--- test aug.transform() +aug.transform("Json.lns", "/tmp/foo.json", false) +assert(aug.get("/augeas/load/Json/incl[1]") == "/tmp/foo.json") + +--- test aug.print() + +--- test aug.to_xml() + +--- test aug.srun() + +--- test aug.errors()