From fad7fee89bb2b2cb058d12e217ffaadbe27639cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Pinson?= Date: Wed, 16 Sep 2015 15:02:41 +0200 Subject: [PATCH] Add a lua interpreter to augtool This commit adds a -l/--lua flag to augtool which allows to execute Lua code instead of the native augsrun interpreter. Each API call is mapped into a correponding aug_* Lua command, as well as short * commands. --- .travis.yml | 1 + configure.ac | 2 + src/Makefile.am | 8 +- src/augtool.c | 499 ++++++++++++++++++++++++++++++++++++++++-- tests/Makefile.am | 3 +- tests/root/etc/passwd | 2 +- tests/test-lua.sh | 13 ++ tests/test.auglua | 93 ++++++++ 8 files changed, 594 insertions(+), 27 deletions(-) create mode 100755 tests/test-lua.sh create mode 100644 tests/test.auglua 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..b3a3a40c7 100644 --- a/configure.ac +++ b/configure.ac @@ -110,6 +110,8 @@ gl_INIT PKG_PROG_PKG_CONFIG PKG_CHECK_MODULES([LIBXML], [libxml-2.0]) +PKG_CHECK_MODULES([LIBLUA], [lua5.2]) + AC_CHECK_FUNCS([strerror_r fsync]) AC_OUTPUT(Makefile \ diff --git a/src/Makefile.am b/src/Makefile.am index 5616796ea..1738ef164 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,7 @@ 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_ @@ -35,13 +35,13 @@ endif libaugeas_la_LDFLAGS = $(AUGEAS_VERSION_SCRIPT) \ -version-info $(LIBAUGEAS_VERSION_INFO) -libaugeas_la_LIBADD = liblexer.la libfa.la $(LIB_SELINUX) $(LIBXML_LIBS) $(GNULIB) +libaugeas_la_LIBADD = liblexer.la libfa.la $(LIB_SELINUX) $(LIBXML_LIBS) $(LIBLUA_LIBS) $(GNULIB) augtool_SOURCES = augtool.c -augtool_LDADD = libaugeas.la $(READLINE_LIBS) $(LIBXML_LIBS) $(GNULIB) +augtool_LDADD = libaugeas.la $(READLINE_LIBS) $(LIBXML_LIBS) $(LIBLUA_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/augtool.c b/src/augtool.c index 7ab2c39ff..4383fc2ef 100644 --- a/src/augtool.c +++ b/src/augtool.c @@ -38,6 +38,10 @@ #include #include +#include +#include +#include + /* Global variables */ static augeas *aug = NULL; @@ -49,6 +53,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; @@ -56,6 +62,7 @@ bool interactive = false; char *history_file = NULL; #define AUGTOOL_PROMPT "augtool> " +#define AUGTOOL_LUA_PROMPT "augtool|lua> " /* * General utilities @@ -285,6 +292,7 @@ static void usage(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"); @@ -315,6 +323,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' }, @@ -326,7 +335,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; @@ -355,6 +364,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; @@ -450,6 +462,420 @@ static void install_signal_handlers(void) { sigaction(SIGINT, &sigint_action, NULL); } +static void lua_checkargs(lua_State *L, const char *name, int arity) { + int n = lua_gettop(L); + char *msg = NULL; + if (n != arity) { + strcat(msg, "Wrong number of arguments for '"); + strcat(msg, name); + strcat(msg, "'"); + lua_pushstring(L, msg); + lua_error(L); + } +} + +static int lua_pusherror(lua_State *L) { + lua_pushstring(L, aug_error_message(aug)); + lua_error(L); + return 1; +} + +static int lua_aug_get(lua_State *L) { + int r; + const char *path, *value; + + /* get number of arguments */ + lua_checkargs(L, "aug_get", 1); + + path = luaL_checkstring(L, 1); + // TODO: check string really + + r = aug_get(aug, path, &value); + if (r < 0) + return lua_pusherror(L); + lua_pushstring(L, value); + + /* return the number of results */ + return 1; +} + +static int lua_aug_label(lua_State *L) { + int r; + const char *path, *value; + + lua_checkargs(L, "aug_label", 1); + + path = luaL_checkstring(L, 1); + // TODO: check string really + + r = aug_label(aug, path, &value); + if (r < 0) + return lua_pusherror(L); + lua_pushstring(L, value); + + /* return the number of results */ + return 1; +} + +static int lua_aug_set(lua_State *L) { + int r; + const char *path, *value; + + lua_checkargs(L, "aug_set", 2); + + path = luaL_checkstring(L, 1); + // TODO: check string really + value = luaL_checkstring(L, 2); + + r = aug_set(aug, path, value); + if (r < 0) + return lua_pusherror(L); + + /* return the number of results */ + return 0; +} + +static int lua_aug_setm(lua_State *L) { + int r; + const char *base, *sub, *value; + + lua_checkargs(L, "aug_setm", 3); + + base = luaL_checkstring(L, 1); + // TODO: check string really + sub = luaL_checkstring(L, 2); + value = luaL_checkstring(L, 3); + + r = aug_setm(aug, base, sub, value); + if (r < 0) + return lua_pusherror(L); + + /* return the number of results */ + return 0; +} + +static int lua_aug_insert(lua_State *L) { + int r; + const char *path, *label; + int before; + + lua_checkargs(L, "aug_insert", 3); + + path = luaL_checkstring(L, 1); + // TODO: check string really + label = luaL_checkstring(L, 2); + before = lua_toboolean(L, 3); + + r = aug_insert(aug, path, label, before); + if (r < 0) + return lua_pusherror(L); + + /* return the number of results */ + return 0; +} + +static int lua_aug_rm(lua_State *L) { + int r; + const char *path; + + lua_checkargs(L, "aug_rm", 1); + + path = luaL_checkstring(L, 1); + // TODO: check string really + + r = aug_rm(aug, path); + if (r < 0) + return lua_pusherror(L); + + /* return the number of results */ + return 0; +} + +static int lua_aug_mv(lua_State *L) { + int r; + const char *path, *new_path; + + lua_checkargs(L, "aug_mv", 2); + + path = luaL_checkstring(L, 1); + // TODO: check string really + new_path = luaL_checkstring(L, 2); + + r = aug_mv(aug, path, new_path); + if (r < 0) + return lua_pusherror(L); + + /* return the number of results */ + return 0; +} + +static int lua_aug_cp(lua_State *L) { + int r; + const char *path, *new_path; + + lua_checkargs(L, "aug_cp", 2); + + path = luaL_checkstring(L, 1); + // TODO: check string really + new_path = luaL_checkstring(L, 2); + + r = aug_cp(aug, path, new_path); + if (r < 0) + return lua_pusherror(L); + + /* return the number of results */ + return 0; +} + +static int lua_aug_rename(lua_State *L) { + int r; + const char *path, *label; + + lua_checkargs(L, "aug_rename", 2); + + path = luaL_checkstring(L, 1); + // TODO: check string really + label = luaL_checkstring(L, 2); + + r = aug_rename(aug, path, label); + if (r < 0) + return lua_pusherror(L); + + /* return the number of results */ + return 0; +} + +static int lua_aug_matches(lua_State *L) { + int r; + const char *path; + + lua_checkargs(L, "aug_matches", 1); + + path = luaL_checkstring(L, 1); + // TODO: check string really + // TODO: a second arg is possible (value) + + r = aug_match(aug, path, NULL); + if (r < 0) + return lua_pusherror(L); + + lua_pushinteger(L, r); + /* return the number of results */ + return 1; +} + +static int lua_aug_match(lua_State *L) { + int r, i; + const char *path; + char **match = NULL; + + lua_checkargs(L, "aug_match", 1); + + path = luaL_checkstring(L, 1); + // TODO: check string really + // TODO: a second arg is possible (value) + + r = aug_match(aug, path, &match); + if (r < 0) + return lua_pusherror(L); + + lua_newtable(L); + for (i = 0; i < r; i++) { + lua_pushnumber(L, i+1); + lua_pushstring(L, match[i]); + lua_settable(L, -3); + free(match[i]); + } + free(match); + lua_pushinteger(L, r); + + /* return the number of results */ + return 2; +} + +static int lua_aug_defvar(lua_State *L) { + int r; + const char *name, *expr; + + lua_checkargs(L, "aug_defvar", 2); + + name = luaL_checkstring(L, 1); + // TODO: check string really + expr = luaL_checkstring(L, 2); + + r = aug_defvar(aug, name, expr); + if (r < 0) + return lua_pusherror(L); + + /* return the number of results */ + return 0; +} + +static int lua_aug_defnode(lua_State *L) { + int r; + const char *name, *expr, *value; + + lua_checkargs(L, "aug_defnode", 3); + + name = luaL_checkstring(L, 1); + // TODO: check string really + expr = luaL_checkstring(L, 2); + value = luaL_checkstring(L, 3); + + r = aug_defnode(aug, name, expr, value, NULL); + if (r < 0) + return lua_pusherror(L); + + /* return the number of results */ + return 0; +} + +static int lua_aug_save(lua_State *L) { + int r; + + lua_checkargs(L, "aug_save", 0); + + r = aug_save(aug); + if (r == -1) { + lua_pushstring(L, "saving failed (run 'errors' for details)"); + lua_error(L); + } else { + r = aug_match(aug, "/augeas/events/saved", NULL); + if (r > 0) + printf("Saved %d file(s)\n", r); + } + + /* return the number of results */ + return 0; +} + +static int lua_aug_load(lua_State *L) { + int r; + + lua_checkargs(L, "aug_load", 0); + + r = aug_load(aug); + if (r < 0) + return lua_pusherror(L); + + /* return the number of results */ + return 0; +} + +static int lua_aug_text_store(lua_State *L) { + int r; + const char *lens, *node, *path; + + lua_checkargs(L, "aug_text_store", 3); + + lens = luaL_checkstring(L, 1); + node = luaL_checkstring(L, 2); + path = luaL_checkstring(L, 3); + + r = aug_text_store(aug, lens, node, path); + if (r < 0) + return lua_pusherror(L); + + /* return the number of results */ + return 0; +} + +static int lua_aug_text_retrieve(lua_State *L) { + int r; + const char *lens, *node_in, *path, *node_out; + + lua_checkargs(L, "aug_text_retrieve", 4); + + lens = luaL_checkstring(L, 1); + node_in = luaL_checkstring(L, 2); + path = luaL_checkstring(L, 3); + node_out = luaL_checkstring(L, 4); + + r = aug_text_retrieve(aug, lens, node_in, path, node_out); + if (r < 0) + return lua_pusherror(L); + + /* return the number of results */ + return 0; +} + +static int lua_aug_transform(lua_State *L) { + int r; + const char *lens, *file; + bool excl; + + lua_checkargs(L, "aug_transform", 3); + + lens = luaL_checkstring(L, 1); + file = luaL_checkstring(L, 2); + excl = lua_toboolean(L, 3); + + r = aug_transform(aug, lens, file, excl); + if (r < 0) + return lua_pusherror(L); + + /* return the number of results */ + return 0; +} + +static void setup_lua(void) { + LS = luaL_newstate(); + luaL_openlibs(LS); + lua_register(LS, "aug_get", lua_aug_get); + lua_register(LS, "aug_label", lua_aug_label); + lua_register(LS, "aug_set", lua_aug_set); + lua_register(LS, "aug_setm", lua_aug_setm); + // lua_register(LS, "aug_span", lua_aug_span); + lua_register(LS, "aug_insert", lua_aug_insert); + lua_register(LS, "aug_rm", lua_aug_rm); + lua_register(LS, "aug_mv", lua_aug_mv); + lua_register(LS, "aug_cp", lua_aug_cp); + lua_register(LS, "aug_rename", lua_aug_rename); + lua_register(LS, "aug_matches", lua_aug_matches); + lua_register(LS, "aug_match", lua_aug_match); + lua_register(LS, "aug_defvar", lua_aug_defvar); + lua_register(LS, "aug_defnode", lua_aug_defnode); + lua_register(LS, "aug_save", lua_aug_save); + lua_register(LS, "aug_load", lua_aug_load); + lua_register(LS, "aug_text_store", lua_aug_text_store); + lua_register(LS, "aug_text_retrieve", lua_aug_text_retrieve); + // lua_register(LS, "aug_escape_name", lua_aug_escape_name); + lua_register(LS, "aug_transform", lua_aug_transform); + // lua_register(LS, "aug_print", lua_aug_print); + // lua_register(LS, "aug_to_xml", lua_aug_to_xml); + // lua_register(LS, "aug_srun", lua_aug_srun); + // lua_register(LS, "aug_errors", lua_aug_errors); + + // short names + lua_register(LS, "get", lua_aug_get); + lua_register(LS, "label", lua_aug_label); + lua_register(LS, "set", lua_aug_set); + lua_register(LS, "setm", lua_aug_setm); + // lua_register(LS, "span", lua_aug_span); + lua_register(LS, "insert", lua_aug_insert); + lua_register(LS, "ins", lua_aug_insert); // alias + lua_register(LS, "rm", lua_aug_rm); + lua_register(LS, "mv", lua_aug_mv); + lua_register(LS, "move", lua_aug_mv); // alias + lua_register(LS, "cp", lua_aug_cp); + lua_register(LS, "copy", lua_aug_cp); // alias + lua_register(LS, "rename", lua_aug_rename); + lua_register(LS, "matches", lua_aug_matches); + lua_register(LS, "match", lua_aug_match); + lua_register(LS, "defvar", lua_aug_defvar); + lua_register(LS, "defnode", lua_aug_defnode); + lua_register(LS, "save", lua_aug_save); + lua_register(LS, "load", lua_aug_load); + lua_register(LS, "text_store", lua_aug_text_store); + lua_register(LS, "text_retrieve", lua_aug_text_retrieve); + // lua_register(LS, "escape_name", lua_aug_escape_name); + lua_register(LS, "transform", lua_aug_transform); + // lua_register(LS, "print", lua_aug_print); + // lua_register(LS, "to_xml", lua_aug_to_xml); + // lua_register(LS, "srun", lua_aug_srun); + // lua_register(LS, "errors", lua_aug_errors); +} + static int main_loop(void) { char *line = NULL; int ret = 0; @@ -459,14 +885,27 @@ static int main_loop(void) { bool get_line = true; bool in_interactive = false; + if (use_lua) + setup_lua(); + 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_dofile(LS, inputfile)) { + printf("%s\n", lua_tostring(LS, -1)); + lua_close(LS); + return -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; + } } } @@ -481,7 +920,10 @@ static int main_loop(void) { while(1) { if (get_line) { - line = readline(AUGTOOL_PROMPT); + if (use_lua) + line = readline(AUGTOOL_LUA_PROMPT); + else + line = readline(AUGTOOL_PROMPT); } else { line = NULL; } @@ -529,25 +971,40 @@ static int main_loop(void) { } if (end_reached) { + if (use_lua) + lua_close(LS); if (echo_commands) printf("\n"); return ret; } - if (*line == '\0' || *line == '#') { - free(line); - continue; - } + if (use_lua) { + // FIXME: newlines don't work! + code = luaL_loadbuffer(LS, line, strlen(line), "line") || lua_pcall(LS, 0, 0, 0); + if (isatty(fileno(stdin))) + add_history(line); - code = run_command(line); - if (code == -2) { - free(line); - return ret; - } + if (code) { + fprintf(stderr, "%s\n", lua_tostring(LS, -1)); + lua_pop(LS, 1); /* pop error message from the stack */ + ret = -1; + } + } else { + if (*line == '\0' || *line == '#') { + free(line); + continue; + } - if (code < 0) { - ret = -1; - print_aug_error(); + code = run_command(line); + if (code == -2) { + free(line); + return ret; + } + + if (code < 0) { + ret = -1; + print_aug_error(); + } } if (line != inputline) diff --git a/tests/Makefile.am b/tests/Makefile.am index 86fb35eb3..812ebdfa2 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -255,7 +255,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..2c2f3f503 --- /dev/null +++ b/tests/test.auglua @@ -0,0 +1,93 @@ +--- 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_matches() +m = aug_matches("etc/passwd/*") +assert(m == 20) + +--- 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()