Monday, April 17, 2017

VirtualBox VM autostart

The VirtualBox VM autostart feature first appeared in version 4.2.0. It's intended purpose is not only to automatically start selected virtual machines upon the host system startup, but to automatically and gracefully stop them upon the host system shutdown. But, of course, unless perhaps the VM is interactive, the stop part of the intent is fictional, specially on headless Solaris server systems. Thus for the stop part the feature acts like a placebo, at least for Solaris hosts.

I've been almost comfortable with headless startups for some time so I've never feel inclined to use the autostart, but now on version 5.1.18 I'm using VirtualBox frequently enough to justify taking some time to set it up and relax about manually starting (but not stopping!) VMs, now a tedious task.

There is a bare minimum documentation about this feature, as it's the case with many other features. In general, there are some mistakes and many omissions in the community provided documentation. Eventually it shall catch up but it would be wise not counting so much on it, anyway...

In a Solaris host environment the solution mechanism is elegant (yet unfortunately there some bugs requiring workarounds) as the autostart feature has been integrated/encapsulated into a SMF service:

# svcs virtualbox/autostart
STATE    STIME   FMRI
disabled 8:30:56 svc:/application/virtualbox/autostart:default


The essential configurable properties of this service are:

# svccfg -s virtualbox/autostart:default listprop config/*
config/config    astring     /etc/vbox/autostart.cfg
config/vboxgroup astring     staff


Looking at the corresponding service manifest:

# svcs -l virtualbox/autostart | grep manifest
manifest     /var/svc/.../virtualbox-autostart.xml


We find in its methods the main shell script file:

# grep exec= /var/svc/.../virtualbox-autostart.xml | sort -u
   exec='/opt/VirtualBox/smf-vboxautostart.sh %m'


By inspecting the main shell script we find all other possible properties:

# grep config/ /opt/VirtualBox/smf-vboxautostart.sh | sort -u
   VW_CONFIG=`... config/config ...`
   VW_LOGINTERVAL=`... config/loginterval ...`
   VW_LOGSIZE=`... config/logsize ...`
   VW_ROTATE=`... config/logrotate ...`
   VW_VBOXGROUP=`... config/vboxgroup ...`


Browsing the VirtualBox forums after querying Google I've found a discussion about the default value for config/vboxgroup and I share the vision that it should be set to vboxuser instead of staff because this group is already central and special to VirtualBox. So I set to adjust it as follows:

# svccfg -s virtualbox/autostart:default \
    setprop config/vboxgroup = astring:  \
    '("vboxuser")'

NOTE
If you enter the prompting mode by typing just the first part (that before the 1st backslash) of the above command, then when issuing the remaining part on the svcfg prompt, remember to omit the single quotes (they were needed only to avoid conflicts with the shell).
NOTE
BUG 1: Unless you apply one of my suggested workarounds that follow, the selected config/vboxgroup must be the primary group of each considered account. This may be due to a bug in logins(1M) -g.
Following, as close as possible, the naming convention adopted by VirtualBox, one shall create the necessary directory and file for the autostart service user authorization policy configuration. Other than the provided defaults, I prefer a different location and name for the configuration file:

# svccfg -s virtualbox/autostart:default    \
    setprop config/config = astring:        \
    '("/etc/VirtualBox/VBoxAutostart.conf")'

# svccfg -s virtualbox/autostart:default listprop config/*
config/config    astring  /etc/VirtualBox/VBoxAutostart.conf
config/vboxgroup astring  vboxuser

 
# mkdir -p /etc/VirtualBox

# cat /etc/VirtualBox/VBoxAutostart.conf
# The default policy: deny everyone starting a VM.
# Undocumented: you'd better place it at the top.

default_policy = deny

 
# Exceptions to the default policy.

# Prefer setting the startup delay on a per VM basis.
# The delay setting here is considered an override.
# Undocumented: you'd better follow this format.

john = {
    allow = true
#   startup_delay = 30
}


NOTE
BUG 2: Unless you apply another one of my suggested workarounds that follow, in the above user authorization policy configuration file, if the default_policy = deny, then you'll be required to list every user belonging to the selected config/vboxgroup. Failing to do so will probably cause the SMF service to enter maintenance mode because the main script will exit with a non-zero exit value.
Now, as the regular user (john in the next example), individually adjust the autostart of each VMs that's supposed to automatically start-up after the host system comes up and running:

john ~$ VBoxManage modifyvm Mkt-01 \
    --autostart-enabled on --autostart-delay 30

john ~$ VBoxManage showvminfo Mkt-01 | grep Auto 
Autostart Enabled: on
Autostart Delay: 30


In fact I have already induced the necessary workarounds for each bug. Now, for the suggested corrections to the aforementioned bugs in the autostart service I won't go too deep in how to ultimately, properly or ideally accomplish the task. I'll just indicate the central change that has to be done letting clear that by not following the proper procedures to "patch" the SMF service, the "fix" could be lost when applying future updates or when performing system upgrades. I wish I'll have time to dedicate a post on how to properly do that, but for now I suggest doing just the following adjustments in the main shell script file:

Locate the buggy lines for BUG 1, which are those that contain:
(there are 2, one for the start and other for the stop action)
(there is no line break as I cosmetically did below for readability)
for VW_USER in `logins -g $VW_VBOXGROUP
| cut -d' ' -f1`
and replace each of them with the following correction:
(don't break lines as I cosmetically did below just for readability)
(perhaps encapsulate part of it on a child script; see BUG 2 below)
for VW_USER in $(echo
`logins -g $VW_VBOXGROUP |cut -d' ' -f1`
`getent group $VW_VBOXGROUP |cut -d: -f4 |tr , ' '`
|xargs -n1 |sort -u
)
Locate the buggy lines for BUG 2, which are those that contain:
su - "$VW_USER" -c "/opt/VirtualBox/VBoxAutostart ...
and insert a flow control statement (and a few information gathering prerequisites) to allow branching to that line (the one listed above) only if $VM_USER is duly authorized to launch VMs as per an explicit or implicit rule(s) as declared in /etc/VirtualBox/VBoxAutostart.conf:
...

# Auxiliary scripts path
S_PATH="${0%/`basename $0`}"

# Auxiliary temporary cache files
F1="`mktemp`"
F2="`mktemp`"
trap 'rm -f -- "$F1" "$F2"' EXIT

# Tokenize and cache autostart configuration file
"$S_PATH"/smf-vboxautostart-parse.sh $VW_CONFIG "$F1"
 

# Cache AWK script for checking user allow rule
"$S_PATH"/smf-vboxautostart-allowance.sh "$F2"

# Get default policy
VW_POLICY=`
"$S_PATH"/smf-vboxautostart-policy.sh "$F1"`

# Get all users
for VW_USER in
\
  `"$S_PATH"/smf-vboxautostart-logins.sh "$VW_VBOXGROUP"`

do
    if                                          \
    (                                           \
      [[ `
"$S_PATH"/smf-vboxautostart-user.sh   \
       
"$F1" "$F2" "$VW_USER"` == "true" ]]    \
    )                                           \
    ||                                          \
    (                                           \
      [[ "$VW_POLICY" == "allow" ]]             \
      &&                                        \
      [[ `
"$S_PATH"/smf-vboxautostart-user.sh   \
       
"$F1" "$F2" "$VW_USER"` != "false" ]]   \
    )
    then
        su - "$VW_USER" ...
 
        VW_EXIT=$?
        ...
    fi
done


...
The auxiliary scripts that should be added (don't forget to adjust their permissions and ownership) next to /opt/VirtualBox/vboxautostart.sh are the following:

# ll smf-vboxautostart*
-rwxr-xr-x   1 root bin  ... smf-vboxautostart-original.sh
-rwxr-xr-x   1 root bin  ... smf-vboxautostart-parse.sh
-rwxr-xr-x   1 root bin  ... smf-vboxautostart.sh
-rwxr-xr-x   1 root bin  ... smf-vboxautostart-user.sh
-rwxr-xr-x   1 root bin  ... smf-vboxautostart-policy.sh
-rwxr-xr-x   1 root bin  ... smf-vboxautostart-allowance.sh
-rwxr-xr-x   1 root bin  ... smf-vboxautostart-logins.sh


And here's the contents of each of them:

# cat smf-vboxautostart-parse.sh
#!/bin/sh
#
# Tokenize and cache the configuration file.
# $1 should be the configuration file.
# $2 should be the output file name to use.
#

 

if [[ -n "$1" ]] && [[ -n "$2" ]]; then
    cat "$1" \

        |tr -d '[:blank:]' \
        |sed -e 's/#.*//' -e 's/{/{=/' -e 's/}/=}/' \
        |tr '=' '\n' \

        |sed -e '/^$/ d' >"$2"
fi


# cat smf-vboxautostart-allowance.sh
#!/bin/sh
#
# Report an user's allow rule value.
# $1 should be the output file name to use.
#


if [[ -n "$1" ]]; then

cat <<"EOF" >$1
BEGIN {
    policy = "unknown"
    users = 0
}


/^[A-Za-z0-9._-]+/ {
#
    if ( $0 == "default_policy" ) {
        getline
        if ( $0 == "allow" || $0 == "deny" ) {
            policy = $0
        }  else {
            print "Invalid policy!"
            exit 1
        }
    } else {
        if ( $0 == "allow" ) {
            getline
            allowance[u] = $0
        } else if ( $0 == "startup_delay" ) {
            getline
            startup_delay[u] = $0
        } else {
            # Got an user!
            u = users++
            user[u] = $0
        }
    }
}

END {
    for ( i in user ) {
        printf( "%s:%s\n", user[i], allowance[i] )
    }
}
EOF

fi


# cat smf-vboxautostart-policy.sh
#!/bin/sh
#
# Report the current VirtualBox Autostart policy.
# $1 should the tokenized configuration file.
#

 

if [[ -n "$1" ]]; then
    awk '/^default_policy$/ \

        {getline; if ($0=="allow"||$0=="deny") print $0}' \
        "$1"
fi


# cat smf-vboxautostart-logins.sh #!/bin/sh
#
# Report all logins belonging to config/vboxgroup SMF property.
# Both primary and secondary groups membership are considered.
# $1 should be the value of config/vboxgroup SMF property.
#


if [[ -n "$1" ]]; then
    echo `logins -g "$1" |cut -d' ' -f1` \
         `getent group "$1" |cut -d: -f4 |tr , ' '` \
         |xargs -n1 |sort -u
fi


# cat smf-vboxautostart-user.sh
#!/bin/sh
#
# Report an user's allow rule value.
# $1 should be the tokenized configuration file.
# $2 should be the auxiliary awk script.
# $3 should be the user to query.
#


if [[ -n "$1" ]] && [[ -n "$2" ]] && [[ -n "$3" ]]; then
    awk -f "$2"
"$1" |grep "$3:" |sed "s/$3://"
fi


As one can probably notice, BUG 2 fix turned out into a not so easy fix. A few auxiliary scripts were necessary to mitigate the buggy/failing logic of the /opt/VirtualBox/VBoxAutostart binary (at least up to version 5.1.18). Part of the difficulty was the lacking of documentation on the precise format of /etc/VirtualBox/VBoxAutostart.conf (as I chose to name it) configuration file, which forced some guessing on how to interpret the list of its tokens. But anyway, the result of this effort of mine seems reasonable after all if you consider the available documentation. As such I decided to share the ideas of this possible solution in the VirtualBox forum.

Now it's just a matter of enabling the service:

$ svcadm -v enable virtualbox/autostart
svc:/application/virtualbox/autostart:default enabled.
 
$ svcs -vL virtualbox/autostart
...
[ Apr 18 15:05:14 Enabled. ]
[ Apr 18 15:05:14 Executing start method 

                  (".../smf-vboxautostart.sh start"). ]
 

Oracle Corporation    SunOS 5.11    11.3    September 2015
Oracle VM VirtualBox Autostart 5.1.18
(C) 2017 Oracle Corporation
All rights reserved.

VirtualBox Autostart 5.1.18 r114002 solaris.amd64 ...
00:00:00.000519 main     Log opened 2017-04-18T18:05:14.601822000Z
00:00:00.000522 main     Build Type: release
00:00:00.000528 main     OS Product: SunOS
00:00:00.000530 main     OS Release: 5.11
00:00:00.000531 main     OS Version: 11.3
00:00:00.000667 main     DMI Product Name: ...
00:00:00.000724 main     DMI Product Version: 
00:00:00.002769 main     Host RAM: 8175MB ...
00:00:00.002777 main     Executable: /opt/VirtualBox/...
00:00:00.002779 main     Process ID: 2292
00:00:00.002780 main     Package type: SOLARIS_64BITS...
 

...

[ Apr 18 15:05:14 Method "start" exited with status 0. ]