Skip to content

Commit

Permalink
Add rpm.spawn() Lua API
Browse files Browse the repository at this point in the history
Turns out real-world usage needs more control than rpm.execute()
delivers. This could be crammed into rpm.execute() but it'd be forward
incompatible in a somewhat non-obvious way, we might just as well
add a separate function for it.

Supports redirecting stdin, stdout and stderr to a given path, support
for file descriptors, other actions and spawn attributes can be added
later.

Fixes: rpm-software-management#3192
  • Loading branch information
pmatilai committed Aug 16, 2024
1 parent e03674a commit 52938a9
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 7 deletions.
31 changes: 27 additions & 4 deletions docs/manual/lua.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ by optional number of arguments to pass to the command.
rpm.execute('ls', '-l', '/')
```

For better control over the process execution and output, see rpm.spawn().

#### expand(arg)

Perform rpm macro expansion on argument string.
Expand Down Expand Up @@ -337,7 +339,28 @@ end
```

This function is deprecated and scheduled for removal in 6.0,
use `rpm.execute()` instead.
use `rpm.spawn()` or `rpm.execute()` instead.

#### spawn({command} [, {actions}])

Spawn, aka execute, an external program. (rpm >= 4.20)

`{command}` is a table consisting of the command and its arguments.
An optional second table can be used to pass various actions related
to the command execution, currently supported are:

| Action | Argument(s) | Description
|---------|----------------------
| `stdin` | path | Redirect standard input to path
| `stdout`| path | Redirect standard output to path
| `stderr`| path | Redirect standard error to path

Returns the command exit status: zero on success, or a tuplet
of (nil, message, code) on failure.

```
rpm.spawn({'systemctl', 'restart', 'httpd'}, {stderr='/dev/null'})
```

#### undefine(name)

Expand Down Expand Up @@ -477,7 +500,7 @@ end
Execute a program. This may only be performed after posix.fork().

This function is deprecated and scheduled for removal in 6.0,
use `rpm.execute()` instead.
use `rpm.spawn()` or `rpm.execute()` instead.

#### files([path])

Expand All @@ -495,7 +518,7 @@ end
Fork a new process.

This function is deprecated and scheduled for removal in 6.0,
use `rpm.execute()` instead.
use `rpm.spawn()` or `rpm.execute()` instead.

```
pid = posix.fork()
Expand Down Expand Up @@ -774,7 +797,7 @@ end
```

This function is deprecated and scheduled for removal in 6.0,
use `rpm.execute()` instead.
use `rpm.spawn()` or `rpm.execute()` instead.

#### setenv(name, value [, overwrite])

Expand Down
2 changes: 1 addition & 1 deletion rpmio/lposix.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ extern int _rpmlua_have_forked;

#define check_deprecated() \
fprintf(stderr, \
"warning: posix.%s(): .fork(), .exec(), .wait() and .redirect2null() are deprecated, use rpm.execute() instead\n", __func__+1)
"warning: posix.%s(): .fork(), .exec(), .wait() and .redirect2null() are deprecated, use rpm.spawn() or rpm.execute() instead\n", __func__+1)

static const char *filetype(mode_t m)
{
Expand Down
76 changes: 76 additions & 0 deletions rpmio/rpmlua.c
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,81 @@ static int rpm_exit(lua_State *L)
exit(luaL_optinteger(L, 1, EXIT_SUCCESS));
}

static int do_spawnop(lua_State *L, posix_spawn_file_actions_t *fap)
{
const char *key = luaL_checkstring(L, -2);
const char *val = luaL_checkstring(L, -1);
int rc = 0;

if (rstreq(key, "stdin")) {
rc = posix_spawn_file_actions_addopen(fap, STDIN_FILENO,
val, O_RDONLY, 0644);
} else if (rstreq(key, "stdout")) {
rc = posix_spawn_file_actions_addopen(fap, STDOUT_FILENO,
val, O_WRONLY|O_APPEND|O_CREAT, 0644);
} else if (rstreq(key, "stderr")) {
rc = posix_spawn_file_actions_addopen(fap, STDERR_FILENO,
val, O_WRONLY|O_APPEND|O_CREAT, 0644);
} else {
luaL_error(L, "invalid spawn directive: %s", key);
}

return rc;
}

static int rpm_spawn(lua_State *L)
{
int rc = 0, status;
pid_t pid;
posix_spawn_file_actions_t fa;
luaL_checktype(L, 1, LUA_TTABLE);
int argc = luaL_len(L, 1);

if (argc == 0)
luaL_error(L, "command not supplied");

if ((rc = posix_spawn_file_actions_init(&fa)))
return pusherror(L, rc, "spawn file action init");

if (lua_istable(L, 2)) {
lua_pushnil(L);
while (!rc && lua_next(L, 2) != 0) {
rc = do_spawnop(L, &fa);
lua_pop(L, 1);
};
if (rc)
return pusherror(L, rc, NULL);
}

ARGV_t argv = NULL;
for (int i = 0; i < argc; i++) {
lua_rawgeti(L, 1, i + 1);
argvAdd(&argv, luaL_checkstring(L, -1));
lua_pop(L, 1);
}

rpmSetCloseOnExec();

status = posix_spawnp(&pid, argv[0], &fa, NULL, argv, environ);

argvFree(argv);
posix_spawn_file_actions_destroy(&fa);

if (status != 0)
return pusherror(L, status, NULL);
if (waitpid(pid, &status, 0) == -1)
return pusherror(L, errno, NULL);
if (status != 0) {
if (WIFSIGNALED(status)) {
return pusherror(L, WTERMSIG(status), "exit signal");
} else {
return pusherror(L, WEXITSTATUS(status), "exit code");
}
}

return pushresult(L, WEXITSTATUS(status));
}

static int rpm_execute(lua_State *L)
{
const char *file = luaL_checkstring(L, 1);
Expand Down Expand Up @@ -1255,6 +1330,7 @@ static const luaL_Reg rpmlib[] = {
{"unregister", rpm_unregister},
{"call", rpm_call},
{"interactive", rpm_interactive},
{"spawn", rpm_spawn},
{"execute", rpm_execute},
{"redirect2null", rpm_redirect2null},
{"vercmp", rpm_vercmp},
Expand Down
75 changes: 73 additions & 2 deletions tests/rpmmacro.at
Original file line number Diff line number Diff line change
Expand Up @@ -1467,12 +1467,83 @@ runroot_other rpmlua -e 'pid = posix.fork(); if pid == 0 then a,b,c=rpm.redirect
[0],
[nil Bad file descriptor 9.0
],
[warning: posix.fork(): .fork(), .exec(), .wait() and .redirect2null() are deprecated, use rpm.execute() instead
warning: posix.wait(): .fork(), .exec(), .wait() and .redirect2null() are deprecated, use rpm.execute() instead
[warning: posix.fork(): .fork(), .exec(), .wait() and .redirect2null() are deprecated, use rpm.spawn() or rpm.execute() instead
warning: posix.wait(): .fork(), .exec(), .wait() and .redirect2null() are deprecated, use rpm.spawn() or rpm.execute() instead
warning: runaway fork() in Lua script]
)
RPMTEST_CLEANUP

AT_SETUP([lua rpm spawn])
AT_KEYWORDS([macros lua])
RPMDB_INIT
RPMTEST_CHECK([
runroot_other rpmlua -e "rpm.spawn({'echo', '1', '2', '3'})"
],
[0],
[1 2 3
],
[])

RPMTEST_CHECK([
runroot_other rpmlua \
-e "rpm.spawn({'echo', '1', '2', '3'}, {stdout='/dev/null'})"
],
[0],
[],
[])

RPMTEST_CHECK([
runroot_other rpmlua \
-e "rpm.spawn({'ls', '/notthere'})"
],
[0],
[],
[ls: cannot access '/notthere': No such file or directory
])

RPMTEST_CHECK([
runroot_other rpmlua \
-e "rpm.spawn({'ls', '/notthere'}, {stderr='/dev/null'})"
],
[0],
[],
[])

RPMTEST_CHECK([
runroot_other rpmlua \
-e "rpm.spawn({'ls', '/notthere'}, {garbage='bbb'})"
],
[255],
[],
[[error: lua script failed: [string "<execute>"]:1: invalid spawn directive: garbage
]])

RPMTEST_CHECK([
runroot_other rpmlua -e "rpm.spawn('echo', '1', '2', '3')"
],
[255],
[],
[[error: lua script failed: [string "<execute>"]:1: bad argument #1 to 'spawn' (table expected, got string)
]])

RPMTEST_CHECK([
runroot_other rpmlua -e "print(rpm.spawn({'cat'}, {stdin='aaa'}))"
],
[0],
[nil No such file or directory 2.0
],
[])

RPMTEST_CHECK([
echo 1 2 3 > ${RPMTEST}/aaa
runroot_other rpmlua -e "rpm.spawn({'cat'}, {stdin='aaa'})"
],
[0],
[1 2 3
],
[])
RPMTEST_CLEANUP

AT_SETUP([rpmlua hooks])
AT_KEYWORDS([lua])

Expand Down

0 comments on commit 52938a9

Please sign in to comment.