From nobody Wed Oct 06 07:13:38 2021 X-Original-To: dev-commits-src-branches@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id CF47B12D9912; Wed, 6 Oct 2021 07:13:38 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4HPQfV5P0yz3mJR; Wed, 6 Oct 2021 07:13:38 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 999D01BE48; Wed, 6 Oct 2021 07:13:38 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.16.1/8.16.1) with ESMTP id 1967DcVj038297; Wed, 6 Oct 2021 07:13:38 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.16.1/8.16.1/Submit) id 1967DcrP038296; Wed, 6 Oct 2021 07:13:38 GMT (envelope-from git) Date: Wed, 6 Oct 2021 07:13:38 GMT Message-Id: <202110060713.1967DcrP038296@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org From: Kyle Evans Subject: git: 44175ec8ce4d - stable/13 - jail(3lua): add a jail.list() method List-Id: Commits to the stable branches of the FreeBSD src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-branches List-Help: List-Post: List-Subscribe: List-Unsubscribe: Sender: owner-dev-commits-src-branches@freebsd.org X-BeenThere: dev-commits-src-branches@freebsd.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: kevans X-Git-Repository: src X-Git-Refname: refs/heads/stable/13 X-Git-Reftype: branch X-Git-Commit: 44175ec8ce4dd3ce76b3bd08eeb51b12edb7d5e3 Auto-Submitted: auto-generated X-ThisMailContainsUnwantedMimeParts: N The branch stable/13 has been updated by kevans: URL: https://cgit.FreeBSD.org/src/commit/?id=44175ec8ce4dd3ce76b3bd08eeb51b12edb7d5e3 commit 44175ec8ce4dd3ce76b3bd08eeb51b12edb7d5e3 Author: Kyle Evans AuthorDate: 2020-10-13 02:11:14 +0000 Commit: Kyle Evans CommitDate: 2021-10-06 07:13:23 +0000 jail(3lua): add a jail.list() method This is implemented as an iterator, reusing parts of the earlier logic to populate jailparams from a passed in table. The user may request any number of parameters to pull in while we're searching, but we'll force jid and name to appear at a minimum. (cherry picked from commit 6a7647eccd3ef35189c63a61b0ec8865fd559839) --- lib/flua/libjail/jail.3lua | 47 ++++++ lib/flua/libjail/lua_jail.c | 322 +++++++++++++++++++++++++++++++++++----- share/examples/flua/libjail.lua | 55 ++++++- 3 files changed, 385 insertions(+), 39 deletions(-) diff --git a/lib/flua/libjail/jail.3lua b/lib/flua/libjail/jail.3lua index fb54b94844f5..aa1e0ec49616 100644 --- a/lib/flua/libjail/jail.3lua +++ b/lib/flua/libjail/jail.3lua @@ -32,6 +32,7 @@ .Sh NAME .Nm getid , .Nm getname , +.Nm list , .Nm allparams , .Nm getparams , .Nm setparams , @@ -50,6 +51,7 @@ local jail = require('jail') .It Dv jid, err = jail.getid(name) .It Dv name, err = jail.getname(jid) .It Dv params, err = jail.allparams() +.It Dv iter, jail_obj = jail.list([params]) .It Dv jid, res = jail.getparams(jid|name, params [, flags ] ) .It Dv jid, err = jail.setparams(jid|name, params, flags ) .It Dv jail.CREATE @@ -79,6 +81,21 @@ is the name of a jail or a jid in the form of a string. Get the name of a jail as a string for the given .Fa jid .Pq an integer . +.It Dv iter, jail_obj = jail.list([params]) +Returns an iterator over running jails on the system. +.Dv params +is a list of parameters to fetch for each jail as we iterate. +.Dv jid +and +.Dv name +will always be returned, and may be omitted from +.Dv params . +Additionally, +.Dv params +may be omitted or an empty table, but not nil. +.Pp +See +.Sx EXAMPLES . .It Dv params, err = jail.allparams() Get a list of all supported parameter names .Pq as strings . @@ -167,6 +184,10 @@ function returns a jail identifier integer and a table of jail parameters with parameter name strings as keys and strings for values on success, or .Dv nil and an error message string if an error occurred. +.Pp +The +.Fn list +function returns an iterator over the list of running jails. .Sh EXAMPLES Set the hostname of jail .Dq foo @@ -193,6 +214,32 @@ if not jid then end print(res["host.hostname"]) .Ed +.Pp +Iterate over jails on the system: +.Bd -literal -offset indent +local jail = require('jail') + +-- Recommended: just loop over it +for jparams in jail.list() do + print(jparams["jid"] .. " = " .. jparams["name"]) +end + +-- Request path and hostname, too +for jparams in jail.list({"path", "host.hostname"}) do + print(jparams["host.hostname"] .. " mounted at " .. jparams["path"]) +end + +-- Raw iteration protocol +local iter, jail_obj = jail.list() + +-- Request the first params +local jparams = jail_obj:next() +while jparams do + print(jparams["jid"] .. " = " .. jparams["name"]) + -- Subsequent calls may return nil + jparams = jail_obj:next() +end +.Ed .Sh SEE ALSO .Xr jail 2 , .Xr jail 3 , diff --git a/lib/flua/libjail/lua_jail.c b/lib/flua/libjail/lua_jail.c index b66c60b43bc8..7bb0e13cceea 100644 --- a/lib/flua/libjail/lua_jail.c +++ b/lib/flua/libjail/lua_jail.c @@ -2,6 +2,7 @@ * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020, Ryan Moeller + * Copyright (c) 2020, Kyle Evans * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -34,6 +35,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include @@ -41,8 +43,222 @@ __FBSDID("$FreeBSD$"); #include #include +#define JAIL_METATABLE "jail iterator metatable" + +/* + * Taken from RhodiumToad's lspawn implementation, let static analyzers make + * better decisions about the behavior after we raise an error. + */ +#if defined(LUA_VERSION_NUM) && defined(LUA_API) +LUA_API int (lua_error) (lua_State *L) __dead2; +#endif +#if defined(LUA_ERRFILE) && defined(LUALIB_API) +LUALIB_API int (luaL_argerror) (lua_State *L, int arg, const char *extramsg) __dead2; +LUALIB_API int (luaL_typeerror) (lua_State *L, int arg, const char *tname) __dead2; +LUALIB_API int (luaL_error) (lua_State *L, const char *fmt, ...) __dead2; +#endif + int luaopen_jail(lua_State *); +typedef bool (*getparam_filter)(const char *, void *); + +static void getparam_table(lua_State *L, int paramindex, + struct jailparam *params, size_t paramoff, size_t *params_countp, + getparam_filter keyfilt, void *udata); + +struct l_jail_iter { + struct jailparam *params; + size_t params_count; + int jid; +}; + +static bool +l_jail_filter(const char *param_name, void *data __unused) +{ + + /* + * Allowing lastjid will mess up our iteration over all jails on the + * system, as this is a special paramter that indicates where the search + * starts from. We'll always add jid and name, so just silently remove + * these. + */ + return (strcmp(param_name, "lastjid") != 0 && + strcmp(param_name, "jid") != 0 && + strcmp(param_name, "name") != 0); +} + +static int +l_jail_iter_next(lua_State *L) +{ + struct l_jail_iter *iter, **iterp; + struct jailparam *jp; + int serrno; + + iterp = (struct l_jail_iter **)luaL_checkudata(L, 1, JAIL_METATABLE); + iter = *iterp; + luaL_argcheck(L, iter != NULL, 1, "closed jail iterator"); + + jp = iter->params; + /* Populate lastjid; we must keep it in params[0] for our sake. */ + if (jailparam_import_raw(&jp[0], &iter->jid, sizeof(iter->jid))) { + jailparam_free(jp, iter->params_count); + free(jp); + free(iter); + *iterp = NULL; + return (luaL_error(L, "jailparam_import_raw: %s", jail_errmsg)); + } + + /* The list of requested params was populated back in l_list(). */ + iter->jid = jailparam_get(jp, iter->params_count, 0); + if (iter->jid == -1) { + /* + * We probably got an ENOENT to signify the end of the jail + * listing, but just in case we didn't; stash it off and start + * cleaning up. We'll handle non-ENOENT errors later. + */ + serrno = errno; + jailparam_free(jp, iter->params_count); + free(iter->params); + free(iter); + *iterp = NULL; + if (serrno != ENOENT) + return (luaL_error(L, "jailparam_get: %s", + strerror(serrno))); + return (0); + } + + /* + * Finally, we'll fill in the return table with whatever parameters the + * user requested, in addition to the ones we forced with exception to + * lastjid. + */ + lua_newtable(L); + for (size_t i = 0; i < iter->params_count; ++i) { + char *value; + + jp = &iter->params[i]; + if (strcmp(jp->jp_name, "lastjid") == 0) + continue; + value = jailparam_export(jp); + lua_pushstring(L, value); + lua_setfield(L, -2, jp->jp_name); + free(value); + } + + return (1); +} + +static int +l_jail_iter_close(lua_State *L) +{ + struct l_jail_iter *iter, **iterp; + + /* + * Since we're using this as the __gc method as well, there's a good + * chance that it's already been cleaned up by iterating to the end of + * the list. + */ + iterp = (struct l_jail_iter **)lua_touserdata(L, 1); + iter = *iterp; + if (iter == NULL) + return (0); + + jailparam_free(iter->params, iter->params_count); + free(iter->params); + free(iter); + *iterp = NULL; + return (0); +} + +static int +l_list(lua_State *L) +{ + struct l_jail_iter *iter; + int nargs; + + nargs = lua_gettop(L); + if (nargs >= 1) + luaL_checktype(L, 1, LUA_TTABLE); + + iter = malloc(sizeof(*iter)); + if (iter == NULL) + return (luaL_error(L, "malloc: %s", strerror(errno))); + + /* + * lastjid, jid, name + length of the table. This may be too much if + * we have duplicated one of those fixed parameters. + */ + iter->params_count = 3 + (nargs != 0 ? lua_rawlen(L, 1) : 0); + iter->params = malloc(iter->params_count * sizeof(*iter->params)); + if (iter->params == NULL) { + free(iter); + return (luaL_error(L, "malloc params: %s", strerror(errno))); + } + + /* The :next() method will populate lastjid before jail_getparam(). */ + if (jailparam_init(&iter->params[0], "lastjid") == -1) { + free(iter->params); + free(iter); + return (luaL_error(L, "jailparam_init: %s", jail_errmsg)); + } + /* These two will get populated by jail_getparam(). */ + if (jailparam_init(&iter->params[1], "jid") == -1) { + jailparam_free(iter->params, 1); + free(iter->params); + free(iter); + return (luaL_error(L, "jailparam_init: %s", + jail_errmsg)); + } + if (jailparam_init(&iter->params[2], "name") == -1) { + jailparam_free(iter->params, 2); + free(iter->params); + free(iter); + return (luaL_error(L, "jailparam_init: %s", + jail_errmsg)); + } + + /* + * We only need to process additional arguments if we were given any. + * That is, we don't descend into getparam_table if we're passed nothing + * or an empty table. + */ + iter->jid = 0; + if (iter->params_count != 3) + getparam_table(L, 1, iter->params, 2, &iter->params_count, + l_jail_filter, NULL); + + /* + * Part of the iterator magic. We give it an iterator function with a + * metatable defining next() and close() that can be used for manual + * iteration. iter->jid is how we track which jail we last iterated, to + * be supplied as "lastjid". + */ + lua_pushcfunction(L, l_jail_iter_next); + *(struct l_jail_iter **)lua_newuserdata(L, + sizeof(struct l_jail_iter **)) = iter; + luaL_getmetatable(L, JAIL_METATABLE); + lua_setmetatable(L, -2); + return (2); +} + +static void +register_jail_metatable(lua_State *L) +{ + luaL_newmetatable(L, JAIL_METATABLE); + lua_newtable(L); + lua_pushcfunction(L, l_jail_iter_next); + lua_setfield(L, -2, "next"); + lua_pushcfunction(L, l_jail_iter_close); + lua_setfield(L, -2, "close"); + + lua_setfield(L, -2, "__index"); + + lua_pushcfunction(L, l_jail_iter_close); + lua_setfield(L, -2, "__gc"); + + lua_pop(L, 1); +} + static int l_getid(lua_State *L) { @@ -100,12 +316,71 @@ l_allparams(lua_State *L) return (1); } +static void +getparam_table(lua_State *L, int paramindex, struct jailparam *params, + size_t params_off, size_t *params_countp, getparam_filter keyfilt, + void *udata) +{ + size_t params_count; + int skipped; + + params_count = *params_countp; + skipped = 0; + for (size_t i = 1 + params_off; i < params_count; ++i) { + const char *param_name; + + lua_rawgeti(L, -1, i - params_off); + param_name = lua_tostring(L, -1); + if (param_name == NULL) { + jailparam_free(params, i - skipped); + free(params); + luaL_argerror(L, paramindex, + "param names must be strings"); + } + lua_pop(L, 1); + if (keyfilt != NULL && !keyfilt(param_name, udata)) { + ++skipped; + continue; + } + if (jailparam_init(¶ms[i - skipped], param_name) == -1) { + jailparam_free(params, i - skipped); + free(params); + luaL_error(L, "jailparam_init: %s", jail_errmsg); + } + } + *params_countp -= skipped; +} + +struct getparams_filter_args { + int filter_type; +}; + +static bool +l_getparams_filter(const char *param_name, void *udata) +{ + struct getparams_filter_args *gpa; + + gpa = udata; + + /* Skip name or jid, whichever was given. */ + if (gpa->filter_type == LUA_TSTRING) { + if (strcmp(param_name, "name") == 0) + return (false); + } else /* type == LUA_TNUMBER */ { + if (strcmp(param_name, "jid") == 0) + return (false); + } + + return (true); +} + static int l_getparams(lua_State *L) { const char *name; struct jailparam *params; - size_t params_count, skipped; + size_t params_count; + struct getparams_filter_args gpa; int flags, jid, type; type = lua_type(L, 1); @@ -154,40 +429,8 @@ l_getparams(lua_State *L) /* * Set the remaining param names being requested. */ - - skipped = 0; - for (size_t i = 1; i < params_count; ++i) { - const char *param_name; - - lua_rawgeti(L, -1, i); - param_name = lua_tostring(L, -1); - if (param_name == NULL) { - jailparam_free(params, i - skipped); - free(params); - return (luaL_argerror(L, 2, - "param names must be strings")); - } - lua_pop(L, 1); - /* Skip name or jid, whichever was given. */ - if (type == LUA_TSTRING) { - if (strcmp(param_name, "name") == 0) { - ++skipped; - continue; - } - } else /* type == LUA_TNUMBER */ { - if (strcmp(param_name, "jid") == 0) { - ++skipped; - continue; - } - } - if (jailparam_init(¶ms[i - skipped], param_name) == -1) { - jailparam_free(params, i - skipped); - free(params); - return (luaL_error(L, "jailparam_init: %s", - jail_errmsg)); - } - } - params_count -= skipped; + gpa.filter_type = type; + getparam_table(L, 2, params, 0, ¶ms_count, l_getparams_filter, &gpa); /* * Get the values and convert to a table. @@ -366,6 +609,13 @@ static const struct luaL_Reg l_jail[] = { * or nil, error (string) on error */ {"setparams", l_setparams}, + /** Get a list of jail parameters for running jails on the system. + * @param params optional list of parameter names (table of + * strings) + * @return iterator (function), jail_obj (object) with next and + * close methods + */ + {"list", l_list}, {NULL, NULL} }; @@ -385,5 +635,7 @@ luaopen_jail(lua_State *L) lua_pushinteger(L, JAIL_DYING); lua_setfield(L, -2, "DYING"); + register_jail_metatable(L); + return (1); } diff --git a/share/examples/flua/libjail.lua b/share/examples/flua/libjail.lua index 3ff878460d2f..1761f5c86b24 100644 --- a/share/examples/flua/libjail.lua +++ b/share/examples/flua/libjail.lua @@ -35,10 +35,23 @@ ucl = require("ucl") name = "demo" --- Create a persistent jail named "demo" with all other parameters default. -jid, err = jail.setparams(name, {persist = "true"}, jail.CREATE) -if not jid then - error(err) +local has_demo = false + +-- Make sure we don't have a demo jail to start with; "jid" and "name" are +-- always present. +for jparams in jail.list() do + if jparams["name"] == name then + has_demo = true + break + end +end + +if not has_demo then + -- Create a persistent jail named "demo" with all other parameters default. + jid, err = jail.setparams(name, {persist = "true"}, jail.CREATE) + if not jid then + error(err) + end end -- Get a list of all known jail parameter names. @@ -53,8 +66,42 @@ end -- Display the jail's parameters as a pretty-printed JSON object. print(ucl.to_json(res)) +-- Confirm that we still have it for now. +has_demo = false +for jparams in jail.list() do + if jparams["name"] == name then + has_demo = true + break + end +end + +if not has_demo then + print("demo does not exist") +end + -- Update the "persist" parameter to "false" to remove the jail. jid, err = jail.setparams(name, {persist = "false"}, jail.UPDATE) if not jid then error(err) end + +-- Verify that the jail is no longer on the system. +local is_persistent = false +has_demo = false +for jparams in jail.list({"persist"}) do + if jparams["name"] == name then + has_demo = true + jid = jparams["jid"] + is_persistent = jparams["persist"] ~= "false" + end +end + +-- In fact, it does remain until this process ends -- c'est la vie. +if has_demo then + io.write("demo still exists, jid " .. jid .. ", ") + if is_persistent then + io.write("persistent\n") + else + io.write("not persistent\n") + end +end