-
Notifications
You must be signed in to change notification settings - Fork 641
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
Fix proc_*
usages for running shell commands not correctly returning the child process status code due to quirky proc_*
behaviours
#3745
Conversation
…ss status code due to quirky proc_* behaviours
To be honest this is one reason why random and custom “run shell command” functions make no sense in the plugin scope. |
proc_*
usages for running shell commands not correctly returning the child process status code due to quirky proc_*
behaviours
…process status code due to quirky proc_* behaviours
…s code due to quirky proc_* behaviours
…s code due to quirky proc_* behaviours
…de due to quirky proc_* behaviours
Ready for review. @fichtner I don't know enough of the design philosophy of this codebase to say one way or the other, but this at least fixes the existing problem. Longer term, I'd tentatively suggest to provide a library on base opnsense code to let plugins run commands safely and with less shenanigans. But it's out of scope for this fix I'd say. |
Any suggestion for improvements? |
First consolidate all spots into one library function, then discuss what requirement was needed to reimplement. It may have been the stdin pipe use? |
I was unable to reproduce this on OPNsense:
However, when running the same PHP code on Linux this was reliably reproducable:
🤷♂️ |
@fichtner I've tried to implement your suggestion in #3749. Would appreciate a review. @luispabon Thanks for submitting this PR. I've implemented other enhancements and incoorporated your fix into #3749. I'll close this PR now. |
Brilliant, thank you 👍 |
@luispabon, upon further testing I've noticed that the change may cause the
@luispabon EDIT: Sigh. This causes other issues. Currently investigating... |
@fraenki I haven't been able to reproduce acme.sh getting stuck, but the above doesn't seem to hurt it either, I was able to create a cert from scratch. What other issues does it cause? |
@luispabon I see |
No, not at all, see for yourself: recording.webmI've run this a few times to make sure. |
@luispabon Thanks! This is really odd. Using my initial non-blocking code I've had one occurence of PHP memory exhaustion and high CPU usage. I did extensive testing and the following code change seems to fix the stuck processes for me:
@luispabon Would you please once more test if this code still works for you, especially when using gcloud? |
With the following code: public static function run_shell_command($proc_cmd, $proc_env = array())
{
$proc_desc = array( // descriptor array for proc_open()
0 => array("pipe", "r"), // stdin
1 => array("pipe", "w"), // stdout
2 => array("pipe", "w") // stderr
);
$proc_pipes = array();
$proc = proc_open($proc_cmd, $proc_desc, $proc_pipes, null, $proc_env);
// Make sure the resource could be setup properly
if (is_resource($proc)) {
// This workaround ensures that the accurate return code
// is reliably returned.
fclose($proc_pipes[0]);
stream_set_blocking($proc_pipes[1], false);
stream_set_blocking($proc_pipes[2], false);
while (!feof($proc_pipes[1]) || !feof($proc_pipes[2])) {
$stdout = fread($proc_pipes[1], 1024);
$stderr = fread($proc_pipes[2], 1024);
usleep(50000);
}
fclose($proc_pipes[1]);
fclose($proc_pipes[2]);
// Get exit code
$result = proc_close($proc);
log_error(sprintf("AcmeClient: The shell command returned exit code '%d': '%s'", $result, $proc_cmd));
return($result);
} else {
log_error(sprintf("AcmeClient: Unable to prepare shell command '%s'", $proc_cmd));
return(-999);
}
} Still works fine and no issues with lingering processes: |
@luispabon Thanks again. So I'll commit this code and let others test it in the next release candidate. |
…e#3745 This deals with the following issues that occured while performing extensive testing: - acme.sh processes got stuck forever - PHP memory exhaustion - PHP processes with high CPU usage
…e#3745 This deals with the following issues that occured while performing extensive testing: - acme.sh processes got stuck forever - PHP memory exhaustion - PHP processes with high CPU usage
Great stuff, thank you @fraenki |
…e#3745 This deals with the following issues that occured while performing extensive testing: - acme.sh processes got stuck forever - PHP memory exhaustion - PHP processes with high CPU usage
Using
proc_*
instead ofshell_exec
does give an extra degree of control over the execution of commands, but also introduces quirky behaviour (present for nearly 20 years) whereby if the pipes aren't read before we close them, the return code given byproc_close
is incorrect.This fixes a number of issues on the AcmeClient where commands would apparently fail with return code
120
when in fact they actually succeeded.This actually fixes #1915 and #2710
The better solution would be to use one of the more established libraries (eg
symfony/process
) but it seems that introducing dependencies is a no-no given the project structure.I've noticed that all of the
proc_
usages dotted around the code are pretty much copy paste.Synthetic test: running a command that fails with before / after the fix:
Similar test, but with a succeeding command
Here's an example of the anomalous behaviour while running a google cloud DNS cert update
And this is after the patches above:
Again, when running acme.sh:
And after the fix: