Commit 3be9e215 authored by Nikos Skalkotos's avatar Nikos Skalkotos
Browse files

Merge branch 'feature-windows-legacy' into develop

The main goal of this patch set is to add support for Windows XP/Server
2003 Images.  In the process, it also adds:

 * The ability for a simple debug shell under KVM, so a developer or the
   administrator can access the helper VM directly for simpler debugging,
 * Robust helper shutdown and mounting/un-mounting of NTFS, fixing the
   remote possibility of data loss.

Conflicts:
	docs/interface.rst
	snf-image-helper/common.sh.in
	snf-image-helper/tasks/50AssignHostname.in
	snf-image-host/kvm-common.sh
parents 51f32515 37773398
......@@ -24,7 +24,7 @@ for more information.
Copyright and license
=====================
Copyright (C) 2011-2014 GRNET S.A.
Copyright (C) 2011-2015 GRNET S.A. and individual contributors.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......
......@@ -177,6 +177,23 @@ All image formats properties
one. See :ref:`Overwriting Configuration Tasks<overwriting-configuration-tasks>`
for more info.
* **OFFLINE_NTFSRESIZE**
When deploying a Windows Image, perform an offline NTFS resize, instead
of setting up the Unattend.xml file so SYSPREP executes a custom DISKPART
script to perform an online resize during the first boot. Note NTFS is
left dirty and will be checked automatically on first boot when performing
an offline NTFS resize. Define the *OFFLINE_NTFSRESIZE_NOCHECK* property
to a non-empty value to disable this behavior (this is dangerous).
For more information on "answer files" please refer to
:ref:`windows-deployment`.
* **OFFLINE_NTFSRESIZE_NOCHECK**
Set this property to a non-empty value to skip the NTFS check performed by
Windows upon the first boot when performing an offline NTFS resize (see the
*OFFLINE_NTFSRESIZE* property). Skipping the initial filesystem check is
dangerous, as it may lead to bugs of the offline NTFS resize procedure going
undetected.
* **PASSWD_HASHING_METHOD=md5|sha1|blowfish|sha256|sha512**
This property can be used on Unix instances to specify the method to be used
to hash the users password. By default this is determined by the type of the
......
# This is the official list of contributors for copyright purposes.
GRNET S.A.
Vangelis Koukis <vkoukis@gmail.com>
......@@ -6,9 +6,10 @@ export commondir networkingdir scriptsdir tasksdir
SUBDIRS = tasks networking
dist_doc_DATA = COPYING AUTHORS
dist_doc_DATA = COPYING AUTHORS CONTRIBUTORS
dist_bin_SCRIPTS = snf-image-helper
dist_scripts_SCRIPTS= hashpwd.py inject-files.py decode-properties.py disklabel.py
dist_scripts_SCRIPTS= hashpwd.py inject-files.py decode-properties.py \
disklabel.py handle-ini-file.py
dist_common_DATA = common.sh unattend.xml
edit = sed \
......
# Copyright (C) 2011-2015 GRNET S.A.
# Copyright (C) 2011-2015 GRNET S.A. and individual contributors
# Copyright (C) 2007, 2008, 2009 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
......@@ -42,6 +42,9 @@ HIVEXREGEDIT=hivexregedit
BTRFS=btrfs
XFS_GROWFS=xfs_growfs
BASE64=base64
NTFSINFO=ntfsinfo
NTFSRESIZE=ntfsresize
NTFSFIX=ntfsfix
CLEANUP=( )
ERRORS=( )
......@@ -81,6 +84,23 @@ send_monitor_message_xen() {
echo "$@" | socat "STDIO" "UDP-DATAGRAM:${BROADCAST}:${MONITOR_PORT},broadcast"
}
start_debug_shell_kvm() {
local dev="/dev/ttyS3"
echo "Starting a debug shell on \`$dev'"
echo "See Ganeti logs for /dev/pts/X device to use on host."
bash <$dev >$dev 2>&1
echo "Returned from debug shell, resuming..."
}
start_debug_shell_xen() {
echo "Cannot start a debug shell on Xen. Feature not yet supported."
}
start_debug_shell() {
start_debug_shell_${HYPERVISOR}
}
prepare_helper() {
local cmdline item key val hypervisor domid
......@@ -174,6 +194,12 @@ report_task_end() {
}
system_poweroff() {
# Sync everything to disk and sleep for a second before powering off.
# Do this to ensure data has hit the disk, since we are about to
# kill the helper VM with a sysrq call.
sync
sleep 1
while [ 1 ]; do
# Credits to psomas@grnet.gr for this ...
echo o > /proc/sysrq-trigger
......@@ -492,17 +518,31 @@ get_last_free_sector() {
fi
}
get_unattend() {
local target exists
get_sysprepinf() {
local target
target="$1"
# Workaround to search for $target/Unattend.xml in an case insensitive way.
exists=$(find "$target"/ -maxdepth 1 -iregex ".*/\(auto\)?unattend\.xml" )
if [ $(wc -l <<< "$exists") -gt 1 ]; then
log_error "Found multiple Unattend.xml files in the image:" $exists
# Return position of old-style (XP / Server 2003) answer file,
# C:\SYSPREP\SYSPREP.INF, if found.
sysprepinf="$target"/sysprep/sysprep.inf
if [ ! -f "$sysprepinf" ]; then
sysprepinf=""
fi
echo "$sysprepinf"
}
get_unattend() {
local target
target="$1"
echo "$exists"
# TODO: Take into account the precise answer file search order defined in
# https://technet.microsoft.com/en-us/library/cc749415(v=ws.10).aspx
for unattend in "$target/unattend.xml" "$target/autounattend.xml"; do
if [ -f "$unattend" ]; then
echo "$unattend"
break
fi
done
}
bsd2linux() {
......@@ -862,8 +902,12 @@ task_cleanup() {
report_task_end
else
report_error
# If in debug mode, drop to a shell and let a developer figure it out
if grep -q snf_image_debug_helper /proc/cmdline; then
echo "Failed. In debug mode, dropping to a shell."
start_debug_shell
fi
fi
cleanup
}
......
#!/usr/bin/env python
#
# Copyright (C) 2015 GRNET S.A. and individual contributors.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
"""Get or set a configuration variable inside an INI file.
Could have used Python's ConfigParser for this,
but it breaks with sloppily-formatted Windows SYSPREP.INF
answer files.
"""
import re
import sys
import os.path
import argparse
def main():
DESCRIPTION = "Get or set a configuration variable in a INI file"
parser = argparse.ArgumentParser(description=DESCRIPTION)
parser.add_argument("path", metavar="FILE",
help="Path to INI file")
parser.add_argument("action", metavar="ACTION",
help="Action to perform: Must be `get' or `set'.")
parser.add_argument("section", metavar="SECTION",
help="The section containing the option. If the"
" section does not exist, it will be created.")
parser.add_argument("key", metavar="KEY",
help="The name of the variable. If it does not"
" exist it, will be added to the specified"
" section.")
parser.add_argument("value", metavar="VALUE", nargs="?",
help="The value of the variable to set.",
default="")
args = parser.parse_args()
if args.action != "get" and args.action != "set":
raise ValueError("ACTION must be `get' or `set'")
if args.action == "set" and args.value == "":
raise ValueError("VALUE is required if ACTION is `set'")
update = (args.action == "set")
section = args.section
key = args.key
value = args.value
section_re = re.compile("\s*\[\s*%s\s*\]\s*" % section, re.I)
section_start_re = re.compile("\s*\[.*", re.I)
key_re = re.compile("\s*(%s)\s*=(.*)$" % key, re.I)
section_found = False
key_found = False
with open(args.path, "r") as f:
lines = f.readlines()
with open(args.path if update else "/dev/null", "w") as f:
for line in lines:
if section_re.search(line):
# Found matching section
section_found = True
elif section_found and key_re.search(line):
# Found key inside matching section.
# Preserve case of key and write the value.
key_found = True
key, val = key_re.search(line).groups()
if not update:
print val.strip()
return 0
f.write("%s=%s\n" % (key, value))
continue
elif (not key_found and section_found and
section_start_re.match(line)):
# Found a new section after the matching section, but without
# finding the key. This means that this is a new key.
f.write("%s=%s\n" % (key, value))
f.write(line)
if section_found and not key_found:
f.write("%s=%s\n" % (key, value))
if not section_found:
f.write("[%s]\n" % section)
f.write("%s=%s\n" % (key, value))
return 0
if __name__ == "__main__":
sys.exit(main())
#! /bin/bash
# Copyright (C) 2011 GRNET S.A.
# Copyright (C) 2011, 2015 GRNET S.A. and individual contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -84,6 +84,25 @@ if [[ "$ptype" == ext[234] ]]; then
fi
elif [[ "$ptype" == "freebsd-ufs" ]]; then
$GROWFS_UFS -y "$device"
elif [[ "$ptype" == "ntfs" ]]; then
# For NTFS, only perform an offline resize if forced
if [ -n "$SNF_IMAGE_PROPERTY_OFFLINE_NTFSRESIZE" ]; then
# Be extra safe: Refuse to work on Image with a dirty NTFS
if ! $NTFSINFO -m "$device"; then
log_error "NTFS is dirty, refusing to continue"
fi
$NTFSRESIZE -f "$device"
if [ -n "$SNF_IMAGE_PROPERTY_OFFLINE_NTFSRESIZE_NOCHECK" ]; then
warn "Clearing dirty bit after NTFS resize"
warn "Please run CHKDSK manually after boot"
$NTFSFIX -d "$device"
else
warn "NTFS filesystem set to dirty after offline resize"
warn "Disable with OFFLINE_NTFSRESIZE_NOCHECK (dangerous)"
fi
else
warn "Not performing offline file system resize for NTFS unless forced"
fi
else
warn "Don't know how to resize unmounted partition \`$id' with file system \`$ptype'."
fi
......
#! /bin/bash
# Copyright (C) 2011 GRNET S.A.
# Copyright (C) 2011, 2015 GRNET S.A. and individual contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -63,6 +63,31 @@ if [[ "$SNF_IMAGE_PROPERTY_OSFAMILY" == *bsd ]]; then
# See mount_all() for a reason why we do this
$MOUNT -o remount,rw "$SNF_IMAGE_TARGET"
elif [ "$SNF_IMAGE_PROPERTY_OSFAMILY" = "windows" ]; then
# Special case for Windows:
# Perform a case-insensitive, all-lowercase mount with 'lowntfs-3g'
# to avoid problems with different case between installations,
# e.g., C:\WINDOWS vs. C:\Windows, and simplify path lookups.
#
NTFS_OPTS="ignore_case,windows_names,rw"
# Be extra safe: Warn when attempting to work on a dirty NTFS.
# In the future, it would be best to reject the Image outright.
if ! $NTFSINFO -m "$rootdev"; then
warn "Failed to get NTFS info, fs may be dirty (scheduled for CHKDSK)."
warn "Please fix your Image. Future versions will refuse to continue."
fi
if ! $MOUNT -t lowntfs-3g -o "$NTFS_OPTS,norecover" "$rootdev" \
"$SNF_IMAGE_TARGET"; then
warn "Failed to mount NTFS, fs may be unclean and need recovery."
warn "Please fix your Image. Future versions will refuse to continue."
# It would be best to fail here. NTFS-3G based recovery
# is based on wiping off the NTFS journal instead of replaying it,
# and may lead to data corruption.
warn "Attempting to mount NTFS again (allowing unclean filesystem)"
$MOUNT -t lowntfs-3g -o "$NTFS_OPTS" "$rootdev" "$SNF_IMAGE_TARGET"
fi
else
$MOUNT -o rw "$rootdev" "$SNF_IMAGE_TARGET"
fi
......
#! /bin/bash
# Copyright (C) 2011 GRNET S.A.
# Copyright (C) 2011, 2015 GRNET S.A. and individual contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -69,6 +69,10 @@ else
fi
fi
# FIXME: There is no guarantee the answer file is actually named
# C:\unattend.xml. These may have to be modified to use the actual
# name of the answer file, as returned by get_unattend().
echo "del /Q /F C:\unattend.xml" > \
"$target/Windows/Setup/Scripts/SetupComplete.cmd"
......@@ -92,6 +96,44 @@ echo "del /Q /F C:\Windows\SnfScripts\ChangeAdminPassword.cmd" >> \
echo "rmdir C:\Windows\SnfScripts" >> \
"$target/Windows/Setup/Scripts/SetupComplete.cmd"
# If using an old-style (XP / Server 2003) SYSPREP.INF answer file,
# ensure C:\SnfScripts\SetupComplete.cmd is executed via CmdLines.txt
# which must be installed in the InstalledfilesPath from SYSPREP.INF.
sysprepinf=$(get_sysprepinf "$target")
if [ -n "$sysprepinf" ]; then
h=@scriptsdir@/handle-ini-file.py
installfilespath=$($h "$sysprepinf" get Unattended InstallFilesPath)
if [ -z "$installfilespath" ]; then
# Set InstallFilesPath to C:\SYSPREP\i386 explicitly, if missing
$h "$sysprepinf" set Unattended InstallFilesPath 'C:\SYSPREP\i386'
installfilespath=$($h "$sysprepinf" get Unattended InstallFilesPath)
fi
if [ -z "$installfilespath" ]; then
log_error "Failed to get value of InstallFilesPath in SYSPREP.INF"
fi
installfilespath=$(tr [A-Z] [a-z] <<< "$installfilespath")
if [[ ! "$installfilespath" == 'c:\sysprep\'* ]]; then
log_error "InstallFilesPath from SYSPREP.INF not under C:\\SYSPREP\\"
fi
installfilespath=${installfilespath#'c:\sysprep\'}
installfilespath=$(echo "$installfilespath"|sed 's/\\/\//g')
# Ensure final location for InstallFilesPath is still under $target
oemdir="$target"/sysprep/"$installfilespath"/'$OEM$'
if ! readlink -f "$oemdir"|grep -q "^$target"; then
log_error "Invalid value for InstallFilesPath in SYSPREP.INF"
fi
mkdir -p "$oemdir"
cmdlinestxt="$oemdir"/CmdLines.txt
if [ ! -f "$cmdlinestxt" ]; then
echo "[Commands]" >"$cmdlinestxt"
fi
echo "\"C:\\Windows\\SnfScripts\\ChangeAdminPassword.cmd\"" >>"$cmdlinestxt"
echo "\"C:\\Windows\\Setup\\Scripts\\SetupComplete.cmd\"" >>"$cmdlinestxt"
fi
exit 0
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
#! /bin/bash
# Copyright (C) 2011 GRNET S.A.
# Copyright (C) 2011, 2015 GRNET S.A. and individual contributors.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -25,11 +25,15 @@
### END TASK INFO
#
# This task will change the value of `fDenyTSConnection' registry key located
# This task will change the value of `fDenyTSConnections' registry key located
# under `HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\'
# to "true". This will disable RDP connections. The key will change back to
# "false" during the specialize pass of the Windows setup.
# to "true". This will disable RDP connections while the machine is being
# set up by SYSPREP upon first boot.
#
# When Setup is complete, the task ensures the value of `fDenyTSConnections'
# is not set unconditionally to False, but is re-set to its original value
# instead, thus preserving Image-specific policy of whether RDP is enabled
# or not.
set -e
. "@commondir@/common.sh"
......@@ -61,12 +65,16 @@ fi
# Pad the value with zeros
current=$(printf "%03d" "$current")
#The current '\SYSTEM\CurrentContolSet\Control\Terminal Server' values
# The current '\SYSTEM\CurrentContolSet\Control\Terminal Server' values
values=$($HIVEXGET "$hive" "ControlSet${current}\Control\Terminal Server")
# Remove fDenyTSConnections if present
# Get current value of fDenyTSConnections, if one exists
curval=$(grep ^'"fDenyTSConnections"=' <<< "$values"|cut -f2 -d:)
# Then remove it from the list of values, if present
values=$(grep -v ^'"fDenyTSConnections"=' <<< "$values")
# and readd it with a value of 1
regfile=$(mktemp)
add_cleanup rm "$regfile"
......@@ -81,6 +89,16 @@ EOF
$HIVEXREGEDIT --merge "$hive" "$regfile"
# Ensure the value of fDenyTSConnections is re-set
# to its original Image-specific state after Setup has completed.
# TODO: Remove any snf-image specific <RunSynchronousCommand>
# segments from unattend.xml, since this task is now self-standing.
curval=$(printf "%d" "$curval")
echo "reg add \"HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server\" /f /v fDenyTSConnections /t REG_DWORD /d $curval" >> \
"$SNF_IMAGE_TARGET/Windows/Setup/Scripts/SetupComplete.cmd"
exit 0
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :
#! /bin/bash
# Copyright (C) 2011 GRNET S.A.
# Copyright (C) 2011, 2015 GRNET S.A. and individual contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -55,14 +55,29 @@ windows_hostname() {
namespace="urn:schemas-microsoft-com:unattend"
# Windows XP / Server 2003:
# Set the hostname inside a SYSPREP.INF answer file, if found
sysprepinf=$(get_sysprepinf "$target")
# Windows Vista / Server 2008 and later:
# Use Unattend.xml or similar
unattend=$(get_unattend "$target")
if [ -z "$unattend" ]; then
log_error "Unattend.xml is missing."
if [ -z "$sysprepinf" -a -z "$unattend" ]; then
log_error "No C:\SYSPREP\SYSPREP.INF or Unattend.xml found in Image."
fi
if [ -n "$sysprepinf" ]; then
@scriptsdir@/handle-ini-file.py \
"$sysprepinf" set UserData ComputerName "$hostname"
fi
if [ -n "$unattend" ]; then
"$XMLSTARLET" ed -N x=$namespace -u "/x:unattend/x:settings/x:component/x:ComputerName" -v "$hostname" "$unattend" > "$tmp_unattend"
cat "$tmp_unattend" > "$unattend"
fi
"$XMLSTARLET" ed -N x=$namespace -u "/x:unattend/x:settings/x:component/x:ComputerName" -v "$hostname" "$unattend" > "$tmp_unattend"
cat "$tmp_unattend" > "$unattend"
echo done
}
......
#! /bin/bash
# Copyright (C) 2011 GRNET S.A.
# Copyright (C) 2011, 2015 GRNET S.A. and individual contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -90,7 +90,7 @@ windows_password() {
for usr in $SNF_IMAGE_PROPERTY_USERS; do
echo -n "Installing new password for user \`$usr'..."
echo "net user $usr $password /ACTIVE:YES /LOGONPASSWORDCHG:NO /EXPIRES:NEVER /PASSWORDREQ:YES" >> \
echo "net user $usr $password /ACTIVE:YES /EXPIRES:NEVER /PASSWORDREQ:YES" >> \
"$target/Windows/SnfScripts/ChangeAdminPassword.cmd"
echo done
done
......
#! /bin/bash
# Copyright (C) 2011 GRNET S.A.
# Copyright (C) 2011, 2015 GRNET S.A. and individual contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -57,6 +57,18 @@ ptype=$(cut -d: -f5 <<< "$partition")
device="${SNF_IMAGE_DEV}${id}"
if [ "$ptype" = "ntfs" -a "$SNF_IMAGE_PROPERTY_OSFAMILY" = "windows" ]; then
if [ -n "$SNF_IMAGE_PROPERTY_OFFLINE_NTFSRESIZE" ]; then
warn "Task ${PROGNAME:2} will not run. Offline NTFS resize requested."
exit 0
fi
# Windows XP / Server 2003
# No online resize is supported, emit a warning
sysprepinf=$(get_sysprepinf "$target")
if [ -n "$sysprepinf" ]; then
warn "Found SYSPREP.INF. Cannot perform online resize on XP / Server 2003."
warn "Set the OFFLINE_NTFSRESIZE property to request an offline resize."
fi
# Write a diskpart script to %SystemDrive%\Windows\SnfScripts. Sysprep will
# try to execute this script during the specialize pass.
cat > "$SNF_IMAGE_TARGET/Windows/SnfScripts/ExtendFilesystem" <<EOF
......
#! /bin/bash
# Copyright (C) 2011 GRNET S.A.
# Copyright (C) 2011, 2015 GRNET S.A. and individual contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -40,12 +40,17 @@ fi
umount_all "$SNF_IMAGE_TARGET"
if [ "$SNF_IMAGE_PROPERTY_OSFAMILY" = "windows" ]; then
# Sleep for a second after umounting Windows file systems just to be on
# the safe side. The NTFS driver is over fuse and umount with fuse is not
# synchronous. Since the helper VM gets killed using a sysrq call, there
# is a 0.0000000001% possibility that the altered data are not written back
# to the disk when the OS dies, unless you wait a little bit.
sleep 1
# Ensure any NTFS-3G processes are gone after umounting NTFS.
# Otherwise, we run the risk of continuing while data may still be
# written to the underlying block device.
# The NTFS driver is over fuse and umount with fuse is not synchronous.
echo "Waiting until no NTFS-3G mount processes remain..."
while pgrep -f 'mount.*ntfs' >/dev/null; do
echo "NTFS-3G mount processes still remain:"
pgrep -f 'mount.*ntfs'
echo "Sleeping for 1s..."
sleep 1
done
fi
exit 0
......
......@@ -6,9 +6,9 @@ dist_tasks_SCRIPTS = \
30MountImage \
40AddSwap \
40DeleteSSHKeys \
40DisableRemoteDesktopConnections \
40InstallUnattend \
40SELinuxAutorelabel \
41DisableRemoteDesktopConnections \
50AssignHostname \
50ChangePassword \
50ConfigureNetwork \
......
# This is the official list of contributors for copyright purposes.
GRNET S.A.
Vangelis Koukis <vkoukis@gmail.com>
......@@ -32,6 +32,20 @@
# HELPER_MEMORY: Virtual RAM size in megabytes to be given to the helper VM.
# HELPER_MEMORY="512"
# HELPER_DEBUG: When enabled, the helper VM will drop to a root shell
# whenever a task fails. This allows the administrator or a developer
# to examine its internal state for debugging purposes.
# To access the shell, use a program like 'minicom' to connect to /dev/pts/X on
# the host, where /dev/pts/X is the name of the device reported in the Ganeti
# OS installation logs for helper's 3rd serial port, e.g.,
# "char device redirected to /dev/pts/9 (label serial3)".
# This feature is KVM-specific for the time being.
# For HELPER_DEBUG to be useful, you also need to set HELPER_SOFT_TIMEOUT
# to a much higher value.
# WARNING: DO NOT ENABLE THIS FEATURE IN PRODUCTION. Every failure to deploy
# an Image will cause the helper VM to hang.
# HELPER_DEBUG="no"
# MULTISTRAP_CONFIG: Configuration file to be used with multistrap to create
# the rootfs of the helper image.
# MULTISTRAP_CONFIG="@MULTISTRAP_CONFIG@"
......
# Copyright (C) 2013-2015 GRNET S.A.
# Copyright (C) 2013-2015 GRNET S.A. and individual contributors