# The config parser, against hostile input. dryrun is the parser's only
# observable behaviour, so every case runs through it.

"$VMM" create cfg1 64M >/dev/null 2>&1

: > "$VMMDIR/cfg1/config"
exits 0 "minimal config parses" dryrun cfg1

cfg cfg1 <<EOF
CPUS=2 # two of them
EOF
refuses 'config:1' "inline comment is rejected by line" dryrun cfg1

cfg cfg1 <<EOF
MEMORY=4096
EOF
refuses 'unknown key' "a typo'd key is an error, not silence" dryrun cfg1

cfg cfg1 <<EOF
CPUS=1
CPUS=2
EOF
refuses 'duplicate key' "duplicate keys are an error" dryrun cfg1

cfg cfg1 <<EOF
IMAGE_FORMAT="qcow2,readonly=on"
EOF
refuses 'IMAGE_FORMAT must be' "IMAGE_FORMAT injection is refused" dryrun cfg1

cfg cfg1 <<EOF
GRAPHICS=bogus
EOF
refuses 'GRAPHICS must be' "an invalid GRAPHICS value is refused" dryrun cfg1

cfg cfg1 <<EOF
CPUS=abc
EOF
refuses 'CPUS must be' "non-numeric CPUS is refused" dryrun cfg1

cfg cfg1 <<EOF
HOSTFWD="70000:22"
EOF
refuses 'out of range' "out of range host port is refused" dryrun cfg1

cfg cfg1 <<EOF
HOSTFWD="0:22"
EOF
refuses 'out of range' "host port 0 is refused" dryrun cfg1

cfg cfg1 <<EOF
HOSTFWD="99999999999999999999:22"
EOF
refuses 'out of range' "a port too big for arithmetic is refused" dryrun cfg1

cfg cfg1 <<EOF
HOSTFWD=",,,"
EOF
refuses 'no usable entries' "a HOSTFWD of only separators is refused" dryrun cfg1

# Only the dotted quad binds what it looks like: inet_aton reads 127.1
# as 127.0.0.1 and 010.0.0.1 as octal for 8.0.0.1, and a host name is
# not an address at all.
cfg cfg1 <<EOF
HOSTFWD="localhost:2222:22"
EOF
refuses 'must be A.B.C.D' "a host name in HOSTFWD is refused" dryrun cfg1

cfg cfg1 <<EOF
HOSTFWD="127.0.0.256:2222:22"
EOF
refuses 'must be A.B.C.D' "an octet out of range is refused" dryrun cfg1

cfg cfg1 <<EOF
HOSTFWD="127.1:2222:22"
EOF
refuses 'must be A.B.C.D' "a short form address is refused" dryrun cfg1

cfg cfg1 <<EOF
HOSTFWD="010.0.0.1:2222:22"
EOF
refuses 'must be A.B.C.D' "an octal octet is refused" dryrun cfg1

cfg cfg1 <<EOF
HOSTFWD=":2222:22"
EOF
refuses 'must be A.B.C.D' "an entry with no address at all is refused" dryrun cfg1

# The address check runs inside the loop over entries and keeps its own
# scratch names, since the shell has no others to give it.
cfg cfg1 <<EOF
HOSTFWD="2222:22,localhost:8080:80"
EOF
refuses 'must be A.B.C.D' "a bad address in a later entry is refused" dryrun cfg1

# A forward the guest cannot receive is a config that lies, not a
# default. A restricted NIC is still a NIC.
cfg cfg1 <<EOF
NETWORK=no
HOSTFWD="2222:22"
EOF
refuses 'nothing to forward' "HOSTFWD without a NIC is refused" dryrun cfg1

cfg cfg1 <<EOF
NETWORK=hostonly
HOSTFWD="2222:22"
EOF
exits 0 "HOSTFWD with a restricted NIC is kept" dryrun cfg1

cfg cfg1 <<EOF
BOOT_ORDER=cc
EOF
refuses 'BOOT_ORDER must be' "a repeated BOOT_ORDER letter is refused" dryrun cfg1

# A boot list restricts nothing unless some device it names exists: with
# no bootindex anywhere, QEMU falls back and boots the disk the list
# left out.
cfg cfg1 <<EOF
BOOT_ORDER=d
EOF
refuses 'no device to boot' "a boot list of absent devices is refused" dryrun cfg1

cfg cfg1 <<EOF
BOOT_ORDER=n
NETWORK=no
EOF
refuses 'no device to boot' "netboot without a NIC is refused" dryrun cfg1

cfg cfg1 <<EOF
BOOT_ORDER=dc
EOF
exits 0 "a boot list that still names the disk is kept" dryrun cfg1

cfg cfg1 <<EOF
IMAGE="$WORK/shared.qcow2"
EOF
refuses 'unknown key: IMAGE' "the primary disk path cannot be configured" dryrun cfg1

cfg cfg1 <<EOF
CDROM="/tmp/install.iso
EOF
refuses 'unbalanced quote' "unbalanced quote is refused" dryrun cfg1

cfg cfg1 <<EOF
CPUS=$(printf '2\001')
EOF
refuses 'non-printable' "a non-printable byte is refused" dryrun cfg1

# Shell variables cannot carry NUL, so the parser must inspect the file
# before read(1) can silently remove it.
printf 'CPUS=2\nMEM=2\000\n' > "$VMMDIR/cfg1/config"
refuses 'config:2: NUL byte' "a NUL byte is refused on its own line" dryrun cfg1

# The parser is byte oriented whatever the caller's locale says, or a
# multibyte character passes for one printable character.
printf 'CDROM="/tmp/\303\251.iso"\n' > "$VMMDIR/cfg1/config"
out=$(env LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 "$VMM" dryrun cfg1 2>&1) && got=0 || got=$?
if [ "$got" != 0 ] && printf '%s' "$out" | grep -q 'non-printable'; then
	ok "a UTF-8 byte is refused under a UTF-8 locale"
else
	notok "a UTF-8 byte is refused under a UTF-8 locale" "exit $got: $out"
fi

# A final line with no trailing newline must still be seen.
printf 'CPUS=3' > "$VMMDIR/cfg1/config"
outputs "-smp '3'" "final line without a newline is parsed" dryrun cfg1

# CRLF must not leak into a value.
printf 'CPUS=4\r\n' > "$VMMDIR/cfg1/config"
outputs "-smp '4'" "CRLF line endings are handled" dryrun cfg1

# An unreadable config must say so, not blame a key it never got to read.
chmod 000 "$VMMDIR/cfg1/config"
refuses 'not readable' "an unreadable config names the real problem" dryrun cfg1
chmod 600 "$VMMDIR/cfg1/config"

# edit exists to re-read the config once the editor has been through it,
# which is the only reason to run one from here. EDITOR=true is exported
# by the harness, so what is left of edit is the check.
: > "$VMMDIR/cfg1/config"
exits 0 "edit re-validates the config it just wrote" edit cfg1
outputs 'config ok' "edit says so when the config parses" edit cfg1

cfg cfg1 <<EOF
MEMORY=4096
EOF
refuses 'unknown key' "edit refuses to leave a broken config unreported" edit cfg1
exits 2 "edit of an unknown VM exits 2" edit nosuchvm
