VMware Tools power-on script fails because Elive exports a Bash-specific PS4
I ran into an issue with Elive running as a VMware Workstation guest using open-vm-tools.
On every VM startup, VMware displayed the following warning:
VMware Tools power-on script did not run successfully
open-vm-tools.service itself was running normally, so I investigated the VMware power scripts.
Executing:
/etc/vmware-tools/poweron-vm-default
resulted in many errors from:
/etc/vmware-tools/scripts/vmware/network
of the form:
Bad substitution
The VMware network script starts with:
#!/bin/sh -x
and on this Elive installation:
readlink -f /bin/sh
returns:
/usr/bin/dash
Finding the cause
After tracing the script execution, I found that the problem originates in:
/usr/lib/elive-tools/functions
Elive sets and exports PS4 using:
export PS4='+ [${FUNCNAME[0]:+${FUNCNAME[0]}} ${LINENO}] '
There are active PS4 assignments both in the general initialization section and inside el_make_environment().
For example, el_make_environment() contains:
if [[ -n "${SOURCE}" ]] ; then
export PS4='+ [$(basename ${SOURCE} 2>/dev/null ) ${FUNCNAME[0]:+${FUNCNAME[0]}} ${LINENO}] '
else
export PS4='+ [${FUNCNAME[0]:+${FUNCNAME[0]}} ${LINENO}] '
fi
The problem is not setting PS4 itself. The problem is exporting it.
${FUNCNAME[0]} is Bash-specific syntax. Because PS4 is exported, child processes inherit it.
VMware then executes its network script through /bin/sh -x, which on this Elive installation is dash.
With tracing enabled, dash attempts to expand the inherited PS4. It cannot interpret ${FUNCNAME[0]}, so every traced command can generate:
Bad substitution
The exported value could be seen directly with:
export -p | grep PS4
which originally returned:
export PS4='+ [${FUNCNAME[0]:+${FUNCNAME[0]}} ${LINENO}] '
Confirming that el_make_environment() exports it
I isolated el_make_environment() and checked PS4 before and after calling it.
Before:
typeset PS4='+ [${FUNCNAME[0]:+${FUNCNAME[0]}} ${LINENO}] '
PS4 not exported
After:
export PS4='+ [${FUNCNAME[0]:+${FUNCNAME[0]}} ${LINENO}] '
So el_make_environment() explicitly changes PS4 from a shell-local variable into an exported environment variable.
I also traced the loading of /usr/lib/elive-tools/functions, which showed another active assignment of the same form in the general initialization code.
Fix
I changed the active assignments in:
/usr/lib/elive-tools/functions
from:
export PS4='...'
to:
PS4='...'
For example:
PS4='+ [${FUNCNAME[0]:+${FUNCNAME[0]}} ${LINENO}] '
and inside el_make_environment():
if [[ -n "${SOURCE}" ]] ; then
PS4='+ [$(basename ${SOURCE} 2>/dev/null ) ${FUNCNAME[0]:+${FUNCNAME[0]}} ${LINENO}] '
else
PS4='+ [${FUNCNAME[0]:+${FUNCNAME[0]}} ${LINENO}] '
fi
This keeps Elive's custom debugging PS4 in the current shell, but prevents child processes from inheriting it.
After the change, a fresh interactive zsh reports:
typeset -p PS4
as:
typeset PS4='+ [${FUNCNAME[0]:+${FUNCNAME[0]}} ${LINENO}] '
while:
export -p | grep PS4
returns nothing.
In other words, the custom PS4 still exists, but it is no longer exported.
VMware test after the fix
I then tested the actual VMware power-on script again:
/etc/vmware-tools/poweron-vm-default
echo "VMware exit=$?"
It ran normally through the VMware #!/bin/sh -x network script.
There were no Bad substitution errors, and it completed with:
VMware exit=0
I then rebooted the VMware guest.
The reboot completed successfully and the original VMware warning:
VMware Tools power-on script did not run successfully
did not appear anymore.
So this fixed the problem in my VMware installation.
Upgrade-resistant workaround
Directly editing /usr/lib/elive-tools/functions is useful for proving the cause, but that file is package-managed and an Elive update could overwrite the modification.
A safer local workaround is therefore to leave the packaged Elive file unchanged and add the following to:
/root/.zshrc.local
# Elive uses a Bash-specific PS4.
# Keep PS4 local to zsh so /bin/sh (dash) children do not inherit it.
typeset +x PS4
/root/.zshrc loads the Elive functions and calls el_make_environment() before sourcing /root/.zshrc.local.
Therefore this local setting removes the export attribute from PS4 after Elive has initialized the shell.
Elive keeps its custom PS4 value for zsh debugging, while child programs no longer inherit it.
This should also survive upgrades of the elive-tools package.
Scope of the problem
I encountered and reproduced this specifically in an Elive VMware guest using open-vm-tools.
I have not observed the same visible problem on my bare-metal Elive installations.
However, I don't think the underlying issue is necessarily VMware-specific.
The underlying sequence is:
Elive zsh
|
+-- /usr/lib/elive-tools/functions
|
+-- exports Bash-specific PS4
|
+-- child process inherits PS4
|
+-- /bin/sh is dash
|
+-- script runs with -x
|
+-- dash expands PS4
|
+-- ${FUNCNAME[0]}
|
+-- Bad substitution
VMware exposes the problem because its open-vm-tools network script happens to use:
#!/bin/sh -x
but in principle another program could trigger the same issue if it launches a non-Bash /bin/sh with tracing enabled while inheriting this environment.
Suggested upstream fix
Unless there is a specific reason why the Elive debugging prompt needs to propagate into child shells, I think the proper upstream solution would be to stop exporting PS4 from /usr/lib/elive-tools/functions.
That is, use:
PS4='...'
instead of:
export PS4='...'
This preserves the useful Elive debugging prompt in the current shell while preventing Bash-specific PS4 syntax from leaking into dash or other child shells.
For VMware/open-vm-tools, this completely resolved the issue in my testing.