Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rpm.spawn() Lua API #3241

Merged
merged 2 commits into from
Aug 27, 2024
Merged

Conversation

pmatilai
Copy link
Member

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: #3192

| `stdout`| path | Redirect standard output to path
| `stderr`| path | Redirect standard error to path

Returns the command exit status.
Copy link
Contributor

@hroncok hroncok Aug 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see this tested. (But the test framework is a bit hard to read for me.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I almost agreed 😅 but it is actually tested by this:

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is missing is the detailed description of the return code... (shared by rpm.execute())

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see:

> rpm.execute('true')
0,0
> rpm.execute('false')
nil	"exit code"	256,0

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A brief description of the return status added.

Copy link
Contributor

@dmnks dmnks Aug 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, this is strange:

> print(rpm.execute('ls blah'))
nil     No such file or directory       2.0
> print(rpm.spawn({'ls', 'blah'}))
ls: cannot access 'blah': No such file or directory
nil     exit code       2

Shouldn't the second invocation's error message be the same as that of the first one?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. The difference comes from executing posix_spawn_file_actions_init(), even if it's empty. And rpm.spawn() always initializes it to keep things simple, under the assumption that an empty set doesn't make any difference, right? 😄 Good spotting, thanks.

Copy link
Member Author

@pmatilai pmatilai Aug 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except ... nope 😄 For a moment there I thought I was losing my mind when I couldn't reproduce this in the test-suite, but those two are not equivalent at all. rpm.execute('ls blah') is trying to execute a binary ls blah with no arguments, and because there isn't such an executable on most systems, you get a "No such file or directory". Whereas the latter is succesfully executing ls and with a blah argument, and the "ls: cannot.." is from ls, like it says in the message. The spawn equivalent is print(rpm.spawn({'ls blah'}) and with that, you get the same result.

The rpm.spawn({'ls', 'blah'}) equivalent is rpm.execute('ls', 'blah') and that too has identical behavior.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, damn... Of course, rpm.execute() takes a list of arguments (much like similar spawn functions in e.g. Python), not a string. My bad 😅.

@pmatilai
Copy link
Member Author

Rebased to sort out the conflict.

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
@pmatilai
Copy link
Member Author

...and changed to only initialize file actions if actually there, it's not that much more code and somehow seems more right anyhow.

@dmnks
Copy link
Contributor

dmnks commented Aug 22, 2024

LGTM, just one question:

Why did the old execute() function not extract the exit code from the returned status int with the WEXITSTATUS macro? The new function does that, which looks correct, but I'm wondering what's different between those two.

@pmatilai
Copy link
Member Author

Good spotting again.

It's a bug I noticed while looking at this. In some early version I had it fixed, but then it got in the way and then I planned on merging the rpm.execute() implementation with this one so it'd be taken care of, but that had other complications, so ... I'll add that as a separate commit to this PR 😄

The status returned from waitpid() is not your exit code, it's encoded
and needs processing the W*() macros, like we do in the new rpm.spawn()
function.

These two should really be refactored to use common code, but leaving
that to later because one is in C and the other one in C++...
@pmatilai
Copy link
Member Author

Added one more commit to fix the rpm.execute() return code handling too.

It makes all these "these should be refactored to use common code" alarms in my head go wild but we don't want to do that just now, because one is in C and the other in C++ and it all just gets messy. So, we'll refactor in git master, after the dust settles a bit.

} else {
return pusherror(L, WEXITSTATUS(status), "exit code");
}
}

return pushresult(L, status);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, seems like this one should also be fixed to use WEXITSTATUS(status) instead 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That will only be reached when status is zero, so it doesn't make any difference.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could of course make the zero explicit to make it clearer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right, of course... nevermind then. We'll be refactoring this code anyway.

@dmnks dmnks merged commit b066411 into rpm-software-management:master Aug 27, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

posix.redirect2null deprecated without replacement
3 participants