I'm executing an exec using nohup in the background using the PHP shell_exec function. It looks like it is waiting for the executed command to finish instead of returning immediately.
private function processSystemData(array $files, string $jar_name, bool $no_server, string $out_filename): void
{
$i = 1;
chdir('/home/' . Constants::ASSET_USER);
foreach ($files as $file) {
$filename = $file->getTempName();
$cmd = 'nohup ' . Constants::JAVA
. ' -jar ' . Constants::JAVA_APPS . "$jar_name.jar"
. ' -f=' . $filename;
$password = false;
if (in_array($jar_name, ['netstat-query', 'server-query', 'software-versions'])) {
$password = (new BNCPasswordDecryptor('asset'))->decryptedPassword();
if ($no_server)
$cmd .= ' --no-server';
$cmd .= ' -u=' . Constants::ASSET_USER;
$cmd .= ' -p=' . "\"" . $password . "\"";
}
$output_dir = '/home/' . Constants::ASSET_USER . '/system-data/';
$outpath = $output_dir . pathinfo($out_filename, PATHINFO_FILENAME) . "-$i."
. pathinfo($out_filename, PATHINFO_EXTENSION);
$cmd .= ' --output=' . $outpath;
$cmd .= ' 2>&1 &';
$safe_cmd = $cmd;
if ($password)
$safe_cmd = str_replace($password, '****', $safe_cmd);
log_message('debug', __METHOD__ . " Process $jar_name-$i started: " . $safe_cmd);
shell_exec($cmd);
log_message('debug', __METHOD__ . " Process $jar_name-$i ended");
++$i;
} // foreach
}
Here is the log:
INFO - 2024-05-13 10:10:01 --> CSRF token verified.
DEBUG - 2024-05-13 10:10:01 --> App\Controllers\AssetManager::processSystemData Process software-versions-1 started: nohup ~/jdk-21.0.2/bin/java -jar ~/software-versions.jar -f=/tmp/phpHZHUPF --no-server -u=info001sec -p="****" --output=/home/info001sec/system-data/software-versions-20240513_10_10_01-1.csv 2>&1 &
DEBUG - 2024-05-13 10:14:03 --> App\Controllers\AssetManager::processSystemData Process software-versions-1 ended
INFO - 2024-05-13 10:14:04 --> Session: Class initialized using 'CodeIgniter\Session\Handlers\FileHandler' driver.
What is causing it to not return immediately after the shell_exec() command? (It waited four minutes for the java code to execute)
I used the alternative PHP exec() command. That didn't make any difference.
First, let's come up with a minimal reproducible example:
shell_exec('sleep 10 &'); # pauses for 10 seconds
This causes PHP to sleep for 10 seconds - although the sleep
command has returned to the shell straight away, PHP isn't terminating the shell early.
As noted in the manual page for exec
but not currently for shell_exec
, this is apparently because PHP is waiting for the command to close its output stream. Redirecting output to /dev/null
returns immediately:
shell_exec('sleep 10 >/dev/null &'); # instant
But add in nohup
and it stops working:
shell_exec('nohup sleep 10 >/dev/null &'); # pauses for 10 seconds
The problem is we need to take care of stderr
as well as stdout
. The order of redirections matters here!
shell_exec('nohup sleep 10 2>&1 >/dev/null &'); # pauses for 10 seconds
shell_exec('nohup sleep 10 >/dev/null 2>&1 &'); # instant! :)
If you need the output or error stream, just redirect one or both to a real file:
shell_exec('nohup sleep 10 >/tmp/combined-output 2>&1 &'); # instant
shell_exec('nohup sleep 10 >/tmp/only-stdout 2>/dev/null &'); # instant
shell_exec('nohup sleep 10 >/dev/null 2>/tmp/only-stderr &'); # instant