Statistics
| Branch: | Tag: | Revision:

root / image_creator / os_type / windows.py @ c6b84dc4

History | View | Annotate | Download (22.5 kB)

1 121f3bc0 Nikos Skalkotos
# -*- coding: utf-8 -*-
2 121f3bc0 Nikos Skalkotos
#
3 ae48a082 Nikos Skalkotos
# Copyright 2012 GRNET S.A. All rights reserved.
4 ae48a082 Nikos Skalkotos
#
5 ae48a082 Nikos Skalkotos
# Redistribution and use in source and binary forms, with or
6 ae48a082 Nikos Skalkotos
# without modification, are permitted provided that the following
7 ae48a082 Nikos Skalkotos
# conditions are met:
8 ae48a082 Nikos Skalkotos
#
9 ae48a082 Nikos Skalkotos
#   1. Redistributions of source code must retain the above
10 ae48a082 Nikos Skalkotos
#      copyright notice, this list of conditions and the following
11 ae48a082 Nikos Skalkotos
#      disclaimer.
12 ae48a082 Nikos Skalkotos
#
13 ae48a082 Nikos Skalkotos
#   2. Redistributions in binary form must reproduce the above
14 ae48a082 Nikos Skalkotos
#      copyright notice, this list of conditions and the following
15 ae48a082 Nikos Skalkotos
#      disclaimer in the documentation and/or other materials
16 ae48a082 Nikos Skalkotos
#      provided with the distribution.
17 ae48a082 Nikos Skalkotos
#
18 ae48a082 Nikos Skalkotos
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
19 ae48a082 Nikos Skalkotos
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 ae48a082 Nikos Skalkotos
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 ae48a082 Nikos Skalkotos
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
22 ae48a082 Nikos Skalkotos
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 ae48a082 Nikos Skalkotos
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 ae48a082 Nikos Skalkotos
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
25 ae48a082 Nikos Skalkotos
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 ae48a082 Nikos Skalkotos
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 ae48a082 Nikos Skalkotos
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 ae48a082 Nikos Skalkotos
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 ae48a082 Nikos Skalkotos
# POSSIBILITY OF SUCH DAMAGE.
30 ae48a082 Nikos Skalkotos
#
31 ae48a082 Nikos Skalkotos
# The views and conclusions contained in the software and
32 ae48a082 Nikos Skalkotos
# documentation are those of the authors and should not be
33 ae48a082 Nikos Skalkotos
# interpreted as representing official policies, either expressed
34 ae48a082 Nikos Skalkotos
# or implied, of GRNET S.A.
35 ae48a082 Nikos Skalkotos
36 121f3bc0 Nikos Skalkotos
"""This module hosts OS-specific code common for the various Microsoft
37 121f3bc0 Nikos Skalkotos
Windows OSs."""
38 121f3bc0 Nikos Skalkotos
39 55133880 Nikos Skalkotos
from image_creator.os_type import OSBase, sysprep
40 c50c5ae7 Nikos Skalkotos
from image_creator.util import FatalError, check_guestfs_version, get_command
41 aa2062ba Nikos Skalkotos
42 76b200cf Nikos Skalkotos
import hivex
43 76b200cf Nikos Skalkotos
import tempfile
44 76b200cf Nikos Skalkotos
import os
45 c50c5ae7 Nikos Skalkotos
import time
46 c50c5ae7 Nikos Skalkotos
import random
47 0db17fcf Nikos Skalkotos
import string
48 12c97404 Nikos Skalkotos
import subprocess
49 28d354ce Nikos Skalkotos
import struct
50 c50c5ae7 Nikos Skalkotos
51 c50c5ae7 Nikos Skalkotos
kvm = get_command('kvm')
52 76b200cf Nikos Skalkotos
53 0db17fcf Nikos Skalkotos
BOOT_TIMEOUT = 300
54 0db17fcf Nikos Skalkotos
55 8c574358 Nikos Skalkotos
56 aa2062ba Nikos Skalkotos
class Windows(OSBase):
57 88f83027 Nikos Skalkotos
    """OS class for Windows"""
58 670ea7e3 Nikos Skalkotos
    def __init__(self, image, **kargs):
59 670ea7e3 Nikos Skalkotos
        super(Windows, self).__init__(image, **kargs)
60 670ea7e3 Nikos Skalkotos
61 670ea7e3 Nikos Skalkotos
        device = self.g.part_to_dev(self.root)
62 670ea7e3 Nikos Skalkotos
63 670ea7e3 Nikos Skalkotos
        self.last_part_num = self.g.part_list(device)[-1]['part_num']
64 670ea7e3 Nikos Skalkotos
        self.last_drive = None
65 670ea7e3 Nikos Skalkotos
        self.system_drive = None
66 670ea7e3 Nikos Skalkotos
67 670ea7e3 Nikos Skalkotos
        for drive, partition in self.g.inspect_get_drive_mappings(self.root):
68 670ea7e3 Nikos Skalkotos
            if partition == "%s%d" % (device, self.last_part_num):
69 670ea7e3 Nikos Skalkotos
                self.last_drive = drive
70 670ea7e3 Nikos Skalkotos
            if partition == self.root:
71 670ea7e3 Nikos Skalkotos
                self.system_drive = drive
72 670ea7e3 Nikos Skalkotos
73 670ea7e3 Nikos Skalkotos
        assert self.system_drive
74 76b200cf Nikos Skalkotos
75 12c97404 Nikos Skalkotos
    def needed_sysprep_params(self):
76 12c97404 Nikos Skalkotos
        """Returns a list of needed sysprep parameters. Each element in the
77 12c97404 Nikos Skalkotos
        list is a SysprepParam object.
78 12c97404 Nikos Skalkotos
        """
79 12c97404 Nikos Skalkotos
        password = self.SysprepParam(
80 12c97404 Nikos Skalkotos
            'password', 'Image Administrator Password', 20, lambda x: True)
81 12c97404 Nikos Skalkotos
82 12c97404 Nikos Skalkotos
        return [password]
83 12c97404 Nikos Skalkotos
84 173022fb Nikos Skalkotos
    @sysprep('Disabling IPv6 privacy extensions')
85 173022fb Nikos Skalkotos
    def disable_ipv6_privacy_extensions(self):
86 12c97404 Nikos Skalkotos
        """Disable IPv6 privacy extensions"""
87 12c97404 Nikos Skalkotos
88 0db17fcf Nikos Skalkotos
        self._guest_exec('netsh interface ipv6 set global '
89 0db17fcf Nikos Skalkotos
                         'randomizeidentifiers=disabled store=persistent')
90 12c97404 Nikos Skalkotos
91 32e21ab7 Nikos Skalkotos
    @sysprep('Disabling Teredo interface')
92 32e21ab7 Nikos Skalkotos
    def disable_teredo(self):
93 32e21ab7 Nikos Skalkotos
        """Disable Teredo interface"""
94 32e21ab7 Nikos Skalkotos
95 32e21ab7 Nikos Skalkotos
        self._guest_exec('netsh interface teredo set state disabled')
96 32e21ab7 Nikos Skalkotos
97 32e21ab7 Nikos Skalkotos
    @sysprep('Disabling ISATAP Adapters')
98 32e21ab7 Nikos Skalkotos
    def disable_isatap(self):
99 32e21ab7 Nikos Skalkotos
        """Disable ISATAP Adapters"""
100 32e21ab7 Nikos Skalkotos
101 32e21ab7 Nikos Skalkotos
        self._guest_exec('netsh interface isa set state disabled')
102 32e21ab7 Nikos Skalkotos
103 32e21ab7 Nikos Skalkotos
    @sysprep('Enabling ping responses')
104 32e21ab7 Nikos Skalkotos
    def enable_pings(self):
105 32e21ab7 Nikos Skalkotos
        """Enable ping responces"""
106 32e21ab7 Nikos Skalkotos
107 32e21ab7 Nikos Skalkotos
        self._guest_exec('netsh firewall set icmpsetting 8')
108 32e21ab7 Nikos Skalkotos
109 38cf4599 Nikos Skalkotos
    @sysprep('Disabling hibernation support')
110 38cf4599 Nikos Skalkotos
    def disable_hibernation(self):
111 38cf4599 Nikos Skalkotos
        """Disable hibernation support and remove the hibernation file"""
112 38cf4599 Nikos Skalkotos
113 38cf4599 Nikos Skalkotos
        self._guest_exec(r'powercfg.exe /hibernate off')
114 38cf4599 Nikos Skalkotos
115 32e21ab7 Nikos Skalkotos
    @sysprep('Setting the system clock to UTC')
116 32e21ab7 Nikos Skalkotos
    def utc(self):
117 32e21ab7 Nikos Skalkotos
        """Set the hardware clock to UTC"""
118 32e21ab7 Nikos Skalkotos
119 32e21ab7 Nikos Skalkotos
        path = r'HKLM\SYSTEM\CurrentControlSet\Control\TimeZoneInformation'
120 32e21ab7 Nikos Skalkotos
        self._guest_exec(
121 32e21ab7 Nikos Skalkotos
            r'REG ADD %s /v RealTimeIsUniversal /t REG_DWORD /d 1 /f' % path)
122 32e21ab7 Nikos Skalkotos
123 9703f342 Nikos Skalkotos
    @sysprep('Clearing the event logs')
124 9703f342 Nikos Skalkotos
    def clear_logs(self):
125 9703f342 Nikos Skalkotos
        """Clear all the event logs"""
126 9703f342 Nikos Skalkotos
127 9703f342 Nikos Skalkotos
        self._guest_exec(
128 9703f342 Nikos Skalkotos
            r"cmd /q /c for /f %l in ('wevtutil el') do wevtutil cl %l")
129 9703f342 Nikos Skalkotos
130 173022fb Nikos Skalkotos
    @sysprep('Executing sysprep on the image (may take more that 10 minutes)')
131 173022fb Nikos Skalkotos
    def microsoft_sysprep(self):
132 32e21ab7 Nikos Skalkotos
        """Run the Microsoft System Preparation Tool. This will remove
133 32e21ab7 Nikos Skalkotos
        system-specific data and will make the image ready to be deployed.
134 32e21ab7 Nikos Skalkotos
        After this no other task may run.
135 12c97404 Nikos Skalkotos
        """
136 12c97404 Nikos Skalkotos
137 0db17fcf Nikos Skalkotos
        self._guest_exec(r'C:\Windows\system32\sysprep\sysprep '
138 0db17fcf Nikos Skalkotos
                         r'/quiet /generalize /oobe /shutdown')
139 12c97404 Nikos Skalkotos
        self.syspreped = True
140 c50c5ae7 Nikos Skalkotos
141 670ea7e3 Nikos Skalkotos
    @sysprep('Shrinking the last filesystem')
142 670ea7e3 Nikos Skalkotos
    def shrink(self):
143 670ea7e3 Nikos Skalkotos
        """Shrink the last filesystem. Make sure the filesystem is defragged"""
144 e773df24 Nikos Skalkotos
145 e773df24 Nikos Skalkotos
        # Query for the maximum number of reclaimable bytes
146 e773df24 Nikos Skalkotos
        cmd = (
147 e773df24 Nikos Skalkotos
            r'cmd /Q /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
148 e773df24 Nikos Skalkotos
            r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
149 e773df24 Nikos Skalkotos
            'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
150 e773df24 Nikos Skalkotos
            r'ECHO SHRINK QUERYMAX >> %SCRIPT% & ' +
151 e773df24 Nikos Skalkotos
            r'ECHO EXIT >> %SCRIPT% & ' +
152 e773df24 Nikos Skalkotos
            r'DISKPART /S %SCRIPT% & ' +
153 e773df24 Nikos Skalkotos
            r'IF ERRORLEVEL 1 EXIT /B 1 & ' +
154 e773df24 Nikos Skalkotos
            r'DEL /Q %SCRIPT%"')
155 e773df24 Nikos Skalkotos
156 e773df24 Nikos Skalkotos
        stdout, stderr, rc = self._guest_exec(cmd)
157 e773df24 Nikos Skalkotos
158 e773df24 Nikos Skalkotos
        querymax = None
159 e773df24 Nikos Skalkotos
        for line in stdout.splitlines():
160 e773df24 Nikos Skalkotos
            # diskpart will return something like this:
161 e773df24 Nikos Skalkotos
            #
162 e773df24 Nikos Skalkotos
            #   The maximum number of reclaimable bytes is: xxxx MB
163 e773df24 Nikos Skalkotos
            #
164 e773df24 Nikos Skalkotos
            if line.find('reclaimable') >= 0:
165 e773df24 Nikos Skalkotos
                querymax = line.split(':')[1].split()[0].strip()
166 e773df24 Nikos Skalkotos
                assert querymax.isdigit(), \
167 e773df24 Nikos Skalkotos
                    "Number of reclaimable bytes not a number"
168 e773df24 Nikos Skalkotos
169 e773df24 Nikos Skalkotos
        if querymax is None:
170 e773df24 Nikos Skalkotos
            FatalError("Error in shrinking! "
171 e773df24 Nikos Skalkotos
                       "Couldn't find the max number of reclaimable bytes!")
172 e773df24 Nikos Skalkotos
173 e773df24 Nikos Skalkotos
        querymax = int(querymax)
174 e773df24 Nikos Skalkotos
        # From ntfsresize:
175 e773df24 Nikos Skalkotos
        # Practically the smallest shrunken size generally is at around
176 e773df24 Nikos Skalkotos
        # "used space" + (20-200 MB). Please also take into account that
177 e773df24 Nikos Skalkotos
        # Windows might need about 50-100 MB free space left to boot safely.
178 e773df24 Nikos Skalkotos
        # I'll give 100MB extra space just to be sure
179 e773df24 Nikos Skalkotos
        querymax -= 100
180 e773df24 Nikos Skalkotos
181 e773df24 Nikos Skalkotos
        if querymax < 0:
182 e773df24 Nikos Skalkotos
            self.out.warn("Not enought available space to shrink the image!")
183 e773df24 Nikos Skalkotos
            return
184 e773df24 Nikos Skalkotos
185 e773df24 Nikos Skalkotos
        cmd = (
186 e773df24 Nikos Skalkotos
            r'cmd /Q /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
187 e773df24 Nikos Skalkotos
            r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
188 e773df24 Nikos Skalkotos
            'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num +
189 e773df24 Nikos Skalkotos
            'ECHO SHRINK DESIRED=%d >> %%SCRIPT%% & ' % querymax +
190 e773df24 Nikos Skalkotos
            r'ECHO EXIT >> %SCRIPT% & ' +
191 e773df24 Nikos Skalkotos
            r'DISKPART /S %SCRIPT% & ' +
192 e773df24 Nikos Skalkotos
            r'IF ERRORLEVEL 1 EXIT /B 1 & ' +
193 e773df24 Nikos Skalkotos
            r'DEL /Q %SCRIPT%"')
194 e773df24 Nikos Skalkotos
195 e773df24 Nikos Skalkotos
        stdout, stderr, rc = self._guest_exec(cmd)
196 e773df24 Nikos Skalkotos
197 e773df24 Nikos Skalkotos
        for line in stdout.splitlines():
198 e773df24 Nikos Skalkotos
            if line.find('shrunk') >= 0:
199 e773df24 Nikos Skalkotos
                self.out.output(line)
200 670ea7e3 Nikos Skalkotos
201 55133880 Nikos Skalkotos
    def do_sysprep(self):
202 55133880 Nikos Skalkotos
        """Prepare system for image creation."""
203 55133880 Nikos Skalkotos
204 55133880 Nikos Skalkotos
        if getattr(self, 'syspreped', False):
205 55133880 Nikos Skalkotos
            raise FatalError("Image is already syspreped!")
206 55133880 Nikos Skalkotos
207 12c97404 Nikos Skalkotos
        txt = "System preparation parameter: `%s' is needed but missing!"
208 12c97404 Nikos Skalkotos
        for param in self.needed_sysprep_params():
209 12c97404 Nikos Skalkotos
            if param[0] not in self.sysprep_params:
210 12c97404 Nikos Skalkotos
                raise FatalError(txt % param[0])
211 12c97404 Nikos Skalkotos
212 55133880 Nikos Skalkotos
        self.mount(readonly=False)
213 55133880 Nikos Skalkotos
        try:
214 55133880 Nikos Skalkotos
            disabled_uac = self._update_uac_remote_setting(1)
215 0db17fcf Nikos Skalkotos
            token = self._enable_os_monitor()
216 010f6b30 Nikos Skalkotos
217 010f6b30 Nikos Skalkotos
            # disable the firewalls
218 010f6b30 Nikos Skalkotos
            firewall_states = self._update_firewalls(0, 0, 0)
219 010f6b30 Nikos Skalkotos
220 010f6b30 Nikos Skalkotos
            # Delete the pagefile. It will be recreated when the system boots
221 010f6b30 Nikos Skalkotos
            systemroot = self.g.inspect_get_windows_systemroot(self.root)
222 010f6b30 Nikos Skalkotos
            pagefile = "%s/pagefile.sys" % systemroot
223 010f6b30 Nikos Skalkotos
            self.g.rm_rf(self.g.case_sensitive_path(pagefile))
224 010f6b30 Nikos Skalkotos
225 55133880 Nikos Skalkotos
        finally:
226 55133880 Nikos Skalkotos
            self.umount()
227 55133880 Nikos Skalkotos
228 55133880 Nikos Skalkotos
        self.out.output("Shutting down helper VM ...", False)
229 55133880 Nikos Skalkotos
        self.g.sync()
230 55133880 Nikos Skalkotos
        # guestfs_shutdown which is the prefered way to shutdown the backend
231 55133880 Nikos Skalkotos
        # process was introduced in version 1.19.16
232 55133880 Nikos Skalkotos
        if check_guestfs_version(self.g, 1, 19, 16) >= 0:
233 55133880 Nikos Skalkotos
            ret = self.g.shutdown()
234 55133880 Nikos Skalkotos
        else:
235 55133880 Nikos Skalkotos
            ret = self.g.kill_subprocess()
236 55133880 Nikos Skalkotos
237 55133880 Nikos Skalkotos
        self.out.success('done')
238 0db17fcf Nikos Skalkotos
239 0db17fcf Nikos Skalkotos
        vm = None
240 0db17fcf Nikos Skalkotos
        monitor = None
241 55133880 Nikos Skalkotos
        try:
242 c50c5ae7 Nikos Skalkotos
            self.out.output("Starting windows VM ...", False)
243 0db17fcf Nikos Skalkotos
            monitorfd, monitor = tempfile.mkstemp()
244 0db17fcf Nikos Skalkotos
            os.close(monitorfd)
245 0db17fcf Nikos Skalkotos
            vm, display = self._create_vm(monitor)
246 0db17fcf Nikos Skalkotos
            self.out.success("started (console on vnc display: %d)." % display)
247 0db17fcf Nikos Skalkotos
248 0db17fcf Nikos Skalkotos
            self.out.output("Waiting for OS to boot ...", False)
249 0db17fcf Nikos Skalkotos
            if not self._wait_on_file(monitor, token):
250 0db17fcf Nikos Skalkotos
                raise FatalError("Windows booting timed out.")
251 0db17fcf Nikos Skalkotos
            else:
252 c6b84dc4 Nikos Skalkotos
                time.sleep(10)  # Just to be sure everything is up
253 0db17fcf Nikos Skalkotos
                self.out.success('done')
254 0db17fcf Nikos Skalkotos
255 0db17fcf Nikos Skalkotos
            self.out.output("Disabling automatic logon ...", False)
256 0db17fcf Nikos Skalkotos
            self._disable_autologon()
257 c50c5ae7 Nikos Skalkotos
            self.out.success('done')
258 12c97404 Nikos Skalkotos
259 0db17fcf Nikos Skalkotos
            self.out.output('Preparing system from image creation:')
260 0db17fcf Nikos Skalkotos
261 12c97404 Nikos Skalkotos
            tasks = self.list_syspreps()
262 12c97404 Nikos Skalkotos
            enabled = filter(lambda x: x.enabled, tasks)
263 12c97404 Nikos Skalkotos
            size = len(enabled)
264 12c97404 Nikos Skalkotos
265 670ea7e3 Nikos Skalkotos
            # Make sure shrink runs in the end, before ms sysprep
266 670ea7e3 Nikos Skalkotos
            enabled = filter(lambda x: self.sysprep_info(x).name != 'shrink',
267 670ea7e3 Nikos Skalkotos
                             enabled)
268 670ea7e3 Nikos Skalkotos
269 670ea7e3 Nikos Skalkotos
            shrink_enabled = False
270 670ea7e3 Nikos Skalkotos
            if len(enabled) != size:
271 670ea7e3 Nikos Skalkotos
                enabled.append(self.shrink)
272 670ea7e3 Nikos Skalkotos
                shrink_enabled = True
273 670ea7e3 Nikos Skalkotos
274 12c97404 Nikos Skalkotos
            # Make sure the ms sysprep is the last task to run if it is enabled
275 12c97404 Nikos Skalkotos
            enabled = filter(
276 670ea7e3 Nikos Skalkotos
                lambda x: self.sysprep_info(x).name != 'microsoft-sysprep',
277 670ea7e3 Nikos Skalkotos
                enabled)
278 12c97404 Nikos Skalkotos
279 12c97404 Nikos Skalkotos
            ms_sysprep_enabled = False
280 12c97404 Nikos Skalkotos
            if len(enabled) != size:
281 32e21ab7 Nikos Skalkotos
                enabled.append(self.microsoft_sysprep)
282 12c97404 Nikos Skalkotos
                ms_sysprep_enabled = True
283 12c97404 Nikos Skalkotos
284 12c97404 Nikos Skalkotos
            cnt = 0
285 12c97404 Nikos Skalkotos
            for task in enabled:
286 12c97404 Nikos Skalkotos
                cnt += 1
287 12c97404 Nikos Skalkotos
                self.out.output(('(%d/%d)' % (cnt, size)).ljust(7), False)
288 12c97404 Nikos Skalkotos
                task()
289 12c97404 Nikos Skalkotos
                setattr(task.im_func, 'executed', True)
290 12c97404 Nikos Skalkotos
291 010f6b30 Nikos Skalkotos
            self.out.output("Sending shut down command ...", False)
292 12c97404 Nikos Skalkotos
            if not ms_sysprep_enabled:
293 12c97404 Nikos Skalkotos
                self._shutdown()
294 0db17fcf Nikos Skalkotos
            self.out.success("done")
295 12c97404 Nikos Skalkotos
296 010f6b30 Nikos Skalkotos
            self.out.output("Waiting for windows to shut down ...", False)
297 c50c5ae7 Nikos Skalkotos
            vm.wait()
298 010f6b30 Nikos Skalkotos
            self.out.success("done")
299 55133880 Nikos Skalkotos
        finally:
300 0db17fcf Nikos Skalkotos
            if monitor is not None:
301 0db17fcf Nikos Skalkotos
                os.unlink(monitor)
302 0db17fcf Nikos Skalkotos
303 0db17fcf Nikos Skalkotos
            if vm is not None:
304 0db17fcf Nikos Skalkotos
                self._destroy_vm(vm)
305 12c97404 Nikos Skalkotos
306 55133880 Nikos Skalkotos
            self.out.output("Relaunching helper VM (may take a while) ...",
307 55133880 Nikos Skalkotos
                            False)
308 55133880 Nikos Skalkotos
            self.g.launch()
309 55133880 Nikos Skalkotos
            self.out.success('done')
310 55133880 Nikos Skalkotos
311 010f6b30 Nikos Skalkotos
            self.mount(readonly=False)
312 010f6b30 Nikos Skalkotos
            try:
313 010f6b30 Nikos Skalkotos
                if disabled_uac:
314 010f6b30 Nikos Skalkotos
                    self._update_uac_remote_setting(0)
315 010f6b30 Nikos Skalkotos
316 010f6b30 Nikos Skalkotos
                self._update_firewalls(*firewall_states)
317 010f6b30 Nikos Skalkotos
            finally:
318 010f6b30 Nikos Skalkotos
                self.umount()
319 55133880 Nikos Skalkotos
320 0db17fcf Nikos Skalkotos
    def _create_vm(self, monitor):
321 0db17fcf Nikos Skalkotos
        """Create a VM with the image attached as the disk
322 0db17fcf Nikos Skalkotos

323 0db17fcf Nikos Skalkotos
            monitor: a file to be used to monitor when the OS is up
324 0db17fcf Nikos Skalkotos
        """
325 0db17fcf Nikos Skalkotos
326 0db17fcf Nikos Skalkotos
        def random_mac():
327 0db17fcf Nikos Skalkotos
            mac = [0x00, 0x16, 0x3e,
328 0db17fcf Nikos Skalkotos
                   random.randint(0x00, 0x7f),
329 0db17fcf Nikos Skalkotos
                   random.randint(0x00, 0xff),
330 0db17fcf Nikos Skalkotos
                   random.randint(0x00, 0xff)]
331 0db17fcf Nikos Skalkotos
332 0db17fcf Nikos Skalkotos
            return ':'.join(map(lambda x: "%02x" % x, mac))
333 0db17fcf Nikos Skalkotos
334 0db17fcf Nikos Skalkotos
        # Use ganeti's VNC port range for a random vnc port
335 0db17fcf Nikos Skalkotos
        vnc_port = random.randint(11000, 14999)
336 0db17fcf Nikos Skalkotos
        display = vnc_port - 5900
337 0db17fcf Nikos Skalkotos
338 670ea7e3 Nikos Skalkotos
        vm = kvm(
339 670ea7e3 Nikos Skalkotos
            '-smp', '1', '-m', '1024', '-drive',
340 670ea7e3 Nikos Skalkotos
            'file=%s,format=raw,cache=unsafe,if=virtio' % self.image.device,
341 670ea7e3 Nikos Skalkotos
            '-netdev', 'type=user,hostfwd=tcp::445-:445,id=netdev0',
342 670ea7e3 Nikos Skalkotos
            '-device', 'virtio-net-pci,mac=%s,netdev=netdev0' % random_mac(),
343 670ea7e3 Nikos Skalkotos
            '-vnc', ':%d' % display, '-serial', 'file:%s' % monitor, _bg=True)
344 0db17fcf Nikos Skalkotos
345 0db17fcf Nikos Skalkotos
        return vm, display
346 0db17fcf Nikos Skalkotos
347 0db17fcf Nikos Skalkotos
    def _destroy_vm(self, vm):
348 0db17fcf Nikos Skalkotos
        """Destroy a VM previously created by _create_vm"""
349 0db17fcf Nikos Skalkotos
        if vm.process.alive:
350 0db17fcf Nikos Skalkotos
            vm.terminate()
351 0db17fcf Nikos Skalkotos
352 12c97404 Nikos Skalkotos
    def _shutdown(self):
353 12c97404 Nikos Skalkotos
        """Shuts down the windows VM"""
354 0db17fcf Nikos Skalkotos
        self._guest_exec(r'shutdown /s /t 5')
355 12c97404 Nikos Skalkotos
356 0db17fcf Nikos Skalkotos
    def _wait_on_file(self, fname, msg):
357 0db17fcf Nikos Skalkotos
        """Wait until a message appears on a file"""
358 12c97404 Nikos Skalkotos
359 0db17fcf Nikos Skalkotos
        for i in range(BOOT_TIMEOUT):
360 0db17fcf Nikos Skalkotos
            time.sleep(1)
361 0db17fcf Nikos Skalkotos
            with open(fname) as f:
362 0db17fcf Nikos Skalkotos
                for line in f:
363 0db17fcf Nikos Skalkotos
                    if line.startswith(msg):
364 0db17fcf Nikos Skalkotos
                        return True
365 0db17fcf Nikos Skalkotos
        return False
366 12c97404 Nikos Skalkotos
367 0db17fcf Nikos Skalkotos
    def _disable_autologon(self):
368 0db17fcf Nikos Skalkotos
        """Disable automatic logon on the windows image"""
369 0db17fcf Nikos Skalkotos
370 0db17fcf Nikos Skalkotos
        winlogon = \
371 0db17fcf Nikos Skalkotos
            r'"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"'
372 0db17fcf Nikos Skalkotos
373 0db17fcf Nikos Skalkotos
        self._guest_exec('REG DELETE %s /v DefaultUserName /f' % winlogon)
374 0db17fcf Nikos Skalkotos
        self._guest_exec('REG DELETE %s /v DefaultPassword /f' % winlogon)
375 0db17fcf Nikos Skalkotos
        self._guest_exec('REG DELETE %s /v AutoAdminLogon /f' % winlogon)
376 55133880 Nikos Skalkotos
377 55133880 Nikos Skalkotos
    def _registry_file_path(self, regfile):
378 55133880 Nikos Skalkotos
        """Retrieves the case sensitive path to a registry file"""
379 55133880 Nikos Skalkotos
380 55133880 Nikos Skalkotos
        systemroot = self.g.inspect_get_windows_systemroot(self.root)
381 55133880 Nikos Skalkotos
        path = "%s/system32/config/%s" % (systemroot, regfile)
382 55133880 Nikos Skalkotos
        try:
383 55133880 Nikos Skalkotos
            path = self.g.case_sensitive_path(path)
384 55133880 Nikos Skalkotos
        except RuntimeError as e:
385 55133880 Nikos Skalkotos
            raise FatalError("Unable to retrieve registry file: %s. Reason: %s"
386 55133880 Nikos Skalkotos
                             % (regfile, str(e)))
387 55133880 Nikos Skalkotos
        return path
388 55133880 Nikos Skalkotos
389 0db17fcf Nikos Skalkotos
    def _enable_os_monitor(self):
390 0db17fcf Nikos Skalkotos
        """Add a script in the registry that will send a random string to the
391 0db17fcf Nikos Skalkotos
        first serial port when the windows image finishes booting.
392 0db17fcf Nikos Skalkotos
        """
393 0db17fcf Nikos Skalkotos
394 0db17fcf Nikos Skalkotos
        token = "".join(random.choice(string.ascii_letters) for x in range(16))
395 0db17fcf Nikos Skalkotos
396 0db17fcf Nikos Skalkotos
        path = self._registry_file_path('SOFTWARE')
397 0db17fcf Nikos Skalkotos
        softwarefd, software = tempfile.mkstemp()
398 0db17fcf Nikos Skalkotos
        try:
399 0db17fcf Nikos Skalkotos
            os.close(softwarefd)
400 0db17fcf Nikos Skalkotos
            self.g.download(path, software)
401 0db17fcf Nikos Skalkotos
402 0db17fcf Nikos Skalkotos
            h = hivex.Hivex(software, write=True)
403 0db17fcf Nikos Skalkotos
404 0db17fcf Nikos Skalkotos
            # Enable automatic logon.
405 0db17fcf Nikos Skalkotos
            # This is needed because we need to execute a script that we add in
406 0db17fcf Nikos Skalkotos
            # the RunOnce registry entry and those programs only get executed
407 0db17fcf Nikos Skalkotos
            # when a user logs on. There is a RunServicesOnce registry entry
408 0db17fcf Nikos Skalkotos
            # whose keys get executed in the background when the logon dialog
409 0db17fcf Nikos Skalkotos
            # box first appears, but they seem to only work with services and
410 0db17fcf Nikos Skalkotos
            # not arbitrary command line expressions :-(
411 0db17fcf Nikos Skalkotos
            #
412 0db17fcf Nikos Skalkotos
            # Instructions on how to turn on automatic logon in Windows can be
413 0db17fcf Nikos Skalkotos
            # found here: http://support.microsoft.com/kb/324737
414 0db17fcf Nikos Skalkotos
            #
415 0db17fcf Nikos Skalkotos
            # Warning: Registry change will not work if the โ€œLogon Bannerโ€ is
416 0db17fcf Nikos Skalkotos
            # defined on the server either by a Group Policy object (GPO) or by
417 0db17fcf Nikos Skalkotos
            # a local policy.
418 0db17fcf Nikos Skalkotos
419 0db17fcf Nikos Skalkotos
            winlogon = h.root()
420 0db17fcf Nikos Skalkotos
            for child in ('Microsoft', 'Windows NT', 'CurrentVersion',
421 0db17fcf Nikos Skalkotos
                          'Winlogon'):
422 0db17fcf Nikos Skalkotos
                winlogon = h.node_get_child(winlogon, child)
423 0db17fcf Nikos Skalkotos
424 0db17fcf Nikos Skalkotos
            h.node_set_value(
425 0db17fcf Nikos Skalkotos
                winlogon,
426 0db17fcf Nikos Skalkotos
                {'key': 'DefaultUserName', 't': 1,
427 0db17fcf Nikos Skalkotos
                 'value': "Administrator".encode('utf-16le')})
428 0db17fcf Nikos Skalkotos
            h.node_set_value(
429 0db17fcf Nikos Skalkotos
                winlogon,
430 0db17fcf Nikos Skalkotos
                {'key': 'DefaultPassword', 't': 1,
431 0db17fcf Nikos Skalkotos
                 'value':  self.sysprep_params['password'].encode('utf-16le')})
432 0db17fcf Nikos Skalkotos
            h.node_set_value(
433 0db17fcf Nikos Skalkotos
                winlogon,
434 0db17fcf Nikos Skalkotos
                {'key': 'AutoAdminLogon', 't': 1,
435 0db17fcf Nikos Skalkotos
                 'value': "1".encode('utf-16le')})
436 0db17fcf Nikos Skalkotos
437 0db17fcf Nikos Skalkotos
            key = h.root()
438 0db17fcf Nikos Skalkotos
            for child in ('Microsoft', 'Windows', 'CurrentVersion'):
439 0db17fcf Nikos Skalkotos
                key = h.node_get_child(key, child)
440 0db17fcf Nikos Skalkotos
441 0db17fcf Nikos Skalkotos
            runonce = h.node_get_child(key, "RunOnce")
442 0db17fcf Nikos Skalkotos
            if runonce is None:
443 0db17fcf Nikos Skalkotos
                runonce = h.node_add_child(key, "RunOnce")
444 0db17fcf Nikos Skalkotos
445 0db17fcf Nikos Skalkotos
            value = (
446 0db17fcf Nikos Skalkotos
                r'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe '
447 0db17fcf Nikos Skalkotos
                r'-ExecutionPolicy RemoteSigned '
448 0db17fcf Nikos Skalkotos
                r'"&{$port=new-Object System.IO.Ports.SerialPort COM1,9600,'
449 0db17fcf Nikos Skalkotos
                r'None,8,one;$port.open();$port.WriteLine(\"' + token + r'\");'
450 0db17fcf Nikos Skalkotos
                r'$port.Close()}"').encode('utf-16le')
451 0db17fcf Nikos Skalkotos
452 0db17fcf Nikos Skalkotos
            h.node_set_value(runonce,
453 0db17fcf Nikos Skalkotos
                             {'key': "BootMonitor", 't': 1, 'value': value})
454 0db17fcf Nikos Skalkotos
455 28d354ce Nikos Skalkotos
            value = (
456 28d354ce Nikos Skalkotos
                r'REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion'
457 28d354ce Nikos Skalkotos
                r'\policies\system /v LocalAccountTokenFilterPolicy'
458 28d354ce Nikos Skalkotos
                r' /t REG_DWORD /d 1 /f').encode('utf-16le')
459 28d354ce Nikos Skalkotos
460 28d354ce Nikos Skalkotos
            h.node_set_value(runonce,
461 28d354ce Nikos Skalkotos
                             {'key': "UpdateRegistry", 't': 1, 'value': value})
462 28d354ce Nikos Skalkotos
463 0db17fcf Nikos Skalkotos
            h.commit(None)
464 0db17fcf Nikos Skalkotos
465 0db17fcf Nikos Skalkotos
            self.g.upload(software, path)
466 0db17fcf Nikos Skalkotos
        finally:
467 0db17fcf Nikos Skalkotos
            os.unlink(software)
468 0db17fcf Nikos Skalkotos
469 0db17fcf Nikos Skalkotos
        return token
470 0db17fcf Nikos Skalkotos
471 010f6b30 Nikos Skalkotos
    def _update_firewalls(self, domain, public, standard):
472 010f6b30 Nikos Skalkotos
        """Enables or disables the firewall for the Domain, the Public and the
473 010f6b30 Nikos Skalkotos
        Standard profile. Returns a triplete with the old values.
474 010f6b30 Nikos Skalkotos

475 010f6b30 Nikos Skalkotos
        1 will enable a firewall and 0 will disable it
476 010f6b30 Nikos Skalkotos
        """
477 010f6b30 Nikos Skalkotos
478 010f6b30 Nikos Skalkotos
        if domain not in (0, 1):
479 010f6b30 Nikos Skalkotos
            raise ValueError("Valid values for domain parameter are 0 and 1")
480 010f6b30 Nikos Skalkotos
481 010f6b30 Nikos Skalkotos
        if public not in (0, 1):
482 010f6b30 Nikos Skalkotos
            raise ValueError("Valid values for public parameter are 0 and 1")
483 010f6b30 Nikos Skalkotos
484 010f6b30 Nikos Skalkotos
        if standard not in (0, 1):
485 010f6b30 Nikos Skalkotos
            raise ValueError("Valid values for standard parameter are 0 and 1")
486 010f6b30 Nikos Skalkotos
487 010f6b30 Nikos Skalkotos
        path = self._registry_file_path("SYSTEM")
488 010f6b30 Nikos Skalkotos
        systemfd, system = tempfile.mkstemp()
489 010f6b30 Nikos Skalkotos
        try:
490 010f6b30 Nikos Skalkotos
            os.close(systemfd)
491 010f6b30 Nikos Skalkotos
            self.g.download(path, system)
492 010f6b30 Nikos Skalkotos
493 010f6b30 Nikos Skalkotos
            h = hivex.Hivex(system, write=True)
494 010f6b30 Nikos Skalkotos
495 010f6b30 Nikos Skalkotos
            select = h.node_get_child(h.root(), 'Select')
496 010f6b30 Nikos Skalkotos
            current_value = h.node_get_value(select, 'Current')
497 010f6b30 Nikos Skalkotos
498 010f6b30 Nikos Skalkotos
            # expecting a little endian dword
499 010f6b30 Nikos Skalkotos
            assert h.value_type(current_value)[1] == 4
500 010f6b30 Nikos Skalkotos
            current = "%03d" % h.value_dword(current_value)
501 010f6b30 Nikos Skalkotos
502 010f6b30 Nikos Skalkotos
            firewall_policy = h.root()
503 010f6b30 Nikos Skalkotos
            for child in ('ControlSet%s' % current, 'services', 'SharedAccess',
504 010f6b30 Nikos Skalkotos
                          'Parameters', 'FirewallPolicy'):
505 010f6b30 Nikos Skalkotos
                firewall_policy = h.node_get_child(firewall_policy, child)
506 010f6b30 Nikos Skalkotos
507 010f6b30 Nikos Skalkotos
            old_values = []
508 010f6b30 Nikos Skalkotos
            new_values = [domain, public, standard]
509 010f6b30 Nikos Skalkotos
            for profile in ('Domain', 'Public', 'Standard'):
510 010f6b30 Nikos Skalkotos
                node = h.node_get_child(firewall_policy, '%sProfile' % profile)
511 010f6b30 Nikos Skalkotos
512 010f6b30 Nikos Skalkotos
                old_value = h.node_get_value(node, 'EnableFirewall')
513 010f6b30 Nikos Skalkotos
514 010f6b30 Nikos Skalkotos
                # expecting a little endian dword
515 010f6b30 Nikos Skalkotos
                assert h.value_type(old_value)[1] == 4
516 010f6b30 Nikos Skalkotos
                old_values.append(h.value_dword(old_value))
517 010f6b30 Nikos Skalkotos
518 28d354ce Nikos Skalkotos
                h.node_set_value(
519 28d354ce Nikos Skalkotos
                    node, {'key': 'EnableFirewall', 't': 4L,
520 28d354ce Nikos Skalkotos
                           'value': struct.pack("<I", new_values.pop(0))})
521 010f6b30 Nikos Skalkotos
522 010f6b30 Nikos Skalkotos
            h.commit(None)
523 010f6b30 Nikos Skalkotos
            self.g.upload(system, path)
524 010f6b30 Nikos Skalkotos
525 010f6b30 Nikos Skalkotos
        finally:
526 010f6b30 Nikos Skalkotos
            os.unlink(system)
527 010f6b30 Nikos Skalkotos
528 010f6b30 Nikos Skalkotos
        return old_values
529 010f6b30 Nikos Skalkotos
530 55133880 Nikos Skalkotos
    def _update_uac_remote_setting(self, value):
531 55133880 Nikos Skalkotos
        """Updates the registry key value:
532 55133880 Nikos Skalkotos
        [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies
533 55133880 Nikos Skalkotos
        \System]"LocalAccountTokenFilterPolicy"
534 55133880 Nikos Skalkotos

535 55133880 Nikos Skalkotos
        value = 1 will disable the UAC remote restrictions
536 55133880 Nikos Skalkotos
        value = 0 will enable the UAC remote restrictions
537 55133880 Nikos Skalkotos

538 55133880 Nikos Skalkotos
        For more info see here: http://support.microsoft.com/kb/951016
539 55133880 Nikos Skalkotos

540 55133880 Nikos Skalkotos
        Returns:
541 55133880 Nikos Skalkotos
            True if the key is changed
542 55133880 Nikos Skalkotos
            False if the key is unchanged
543 55133880 Nikos Skalkotos
        """
544 55133880 Nikos Skalkotos
545 55133880 Nikos Skalkotos
        if value not in (0, 1):
546 55133880 Nikos Skalkotos
            raise ValueError("Valid values for value parameter are 0 and 1")
547 55133880 Nikos Skalkotos
548 55133880 Nikos Skalkotos
        path = self._registry_file_path('SOFTWARE')
549 55133880 Nikos Skalkotos
        softwarefd, software = tempfile.mkstemp()
550 55133880 Nikos Skalkotos
        try:
551 55133880 Nikos Skalkotos
            os.close(softwarefd)
552 55133880 Nikos Skalkotos
            self.g.download(path, software)
553 55133880 Nikos Skalkotos
554 55133880 Nikos Skalkotos
            h = hivex.Hivex(software, write=True)
555 55133880 Nikos Skalkotos
556 55133880 Nikos Skalkotos
            key = h.root()
557 55133880 Nikos Skalkotos
            for child in ('Microsoft', 'Windows', 'CurrentVersion', 'Policies',
558 55133880 Nikos Skalkotos
                          'System'):
559 55133880 Nikos Skalkotos
                key = h.node_get_child(key, child)
560 55133880 Nikos Skalkotos
561 55133880 Nikos Skalkotos
            policy = None
562 55133880 Nikos Skalkotos
            for val in h.node_values(key):
563 55133880 Nikos Skalkotos
                if h.value_key(val) == "LocalAccountTokenFilterPolicy":
564 55133880 Nikos Skalkotos
                    policy = val
565 55133880 Nikos Skalkotos
566 55133880 Nikos Skalkotos
            if policy is not None:
567 55133880 Nikos Skalkotos
                dword = h.value_dword(policy)
568 55133880 Nikos Skalkotos
                if dword == value:
569 55133880 Nikos Skalkotos
                    return False
570 55133880 Nikos Skalkotos
            elif value == 0:
571 55133880 Nikos Skalkotos
                return False
572 55133880 Nikos Skalkotos
573 28d354ce Nikos Skalkotos
            new_value = {'key': "LocalAccountTokenFilterPolicy", 't': 4L,
574 28d354ce Nikos Skalkotos
                         'value': struct.pack("<I", value)}
575 55133880 Nikos Skalkotos
576 55133880 Nikos Skalkotos
            h.node_set_value(key, new_value)
577 55133880 Nikos Skalkotos
            h.commit(None)
578 55133880 Nikos Skalkotos
579 55133880 Nikos Skalkotos
            self.g.upload(software, path)
580 55133880 Nikos Skalkotos
581 55133880 Nikos Skalkotos
        finally:
582 55133880 Nikos Skalkotos
            os.unlink(software)
583 55133880 Nikos Skalkotos
584 55133880 Nikos Skalkotos
        return True
585 55133880 Nikos Skalkotos
586 b8c0848c Nikos Skalkotos
    def _do_collect_metadata(self):
587 b8c0848c Nikos Skalkotos
        """Collect metadata about the OS"""
588 b8c0848c Nikos Skalkotos
        super(Windows, self)._do_collect_metadata()
589 76b200cf Nikos Skalkotos
        self.meta["USERS"] = " ".join(self._get_users())
590 76b200cf Nikos Skalkotos
591 76b200cf Nikos Skalkotos
    def _get_users(self):
592 121f3bc0 Nikos Skalkotos
        """Returns a list of users found in the images"""
593 55133880 Nikos Skalkotos
        path = self._registry_file_path('SAM')
594 76b200cf Nikos Skalkotos
        samfd, sam = tempfile.mkstemp()
595 76b200cf Nikos Skalkotos
        try:
596 55133880 Nikos Skalkotos
            os.close(samfd)
597 76b200cf Nikos Skalkotos
            self.g.download(path, sam)
598 76b200cf Nikos Skalkotos
599 76b200cf Nikos Skalkotos
            h = hivex.Hivex(sam)
600 76b200cf Nikos Skalkotos
601 76b200cf Nikos Skalkotos
            key = h.root()
602 76b200cf Nikos Skalkotos
            # Navigate to /SAM/Domains/Account/Users/Names
603 76b200cf Nikos Skalkotos
            for child in ('SAM', 'Domains', 'Account', 'Users', 'Names'):
604 76b200cf Nikos Skalkotos
                key = h.node_get_child(key, child)
605 76b200cf Nikos Skalkotos
606 76b200cf Nikos Skalkotos
            users = [h.node_name(x) for x in h.node_children(key)]
607 76b200cf Nikos Skalkotos
608 76b200cf Nikos Skalkotos
        finally:
609 76b200cf Nikos Skalkotos
            os.unlink(sam)
610 76b200cf Nikos Skalkotos
611 76b200cf Nikos Skalkotos
        # Filter out the guest account
612 76b200cf Nikos Skalkotos
        return filter(lambda x: x != "Guest", users)
613 aa2062ba Nikos Skalkotos
614 0db17fcf Nikos Skalkotos
    def _guest_exec(self, command, fatal=True):
615 0db17fcf Nikos Skalkotos
        """Execute a command on a windows VM"""
616 0db17fcf Nikos Skalkotos
617 12c97404 Nikos Skalkotos
        user = "Administrator%" + self.sysprep_params['password']
618 12c97404 Nikos Skalkotos
        addr = 'localhost'
619 12c97404 Nikos Skalkotos
        runas = '--runas=%s' % user
620 12c97404 Nikos Skalkotos
        winexe = subprocess.Popen(
621 670ea7e3 Nikos Skalkotos
            ['winexe', '-U', user, runas, "//%s" % addr, command],
622 12c97404 Nikos Skalkotos
            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
623 12c97404 Nikos Skalkotos
624 0db17fcf Nikos Skalkotos
        stdout, stderr = winexe.communicate()
625 12c97404 Nikos Skalkotos
        rc = winexe.poll()
626 12c97404 Nikos Skalkotos
627 0db17fcf Nikos Skalkotos
        if rc != 0 and fatal:
628 0db17fcf Nikos Skalkotos
            reason = stderr if len(stderr) else stdout
629 c6b84dc4 Nikos Skalkotos
            self.out.output("Command: `%s' failed. Reason: %s" %
630 c6b84dc4 Nikos Skalkotos
                            (command, reason))
631 0db17fcf Nikos Skalkotos
            raise FatalError("Command: `%s' failed. Reason: %s" %
632 0db17fcf Nikos Skalkotos
                             (command, reason))
633 0db17fcf Nikos Skalkotos
634 0db17fcf Nikos Skalkotos
        return (stdout, stderr, rc)
635 12c97404 Nikos Skalkotos
636 aa2062ba Nikos Skalkotos
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai :