ia64/xen-unstable

view tools/python/xen/xend/XendCheckpoint.py @ 15296:b0109d3dc3dd

Avoid using /tmp for qemu state files since an unprivileged user
in dom0 could potentially exploit the fact.

Thanks to Dan Berrange <berrange@redhat.com>

Signed-off-by: Steven Hand
author Steven Hand <steven@xensource.com>
date Fri Jun 08 17:37:58 2007 +0100 (2007-06-08)
parents 55135bf6eb44
children 713bac7cba46
line source
1 # Copyright (C) 2005 Christian Limpach <Christian.Limpach@cl.cam.ac.uk>
2 # Copyright (C) 2005 XenSource Ltd
4 # This file is subject to the terms and conditions of the GNU General
5 # Public License. See the file "COPYING" in the main directory of
6 # this archive for more details.
8 import os
9 import re
10 import string
11 import threading
12 import fcntl
13 from struct import pack, unpack, calcsize
15 from xen.util.xpopen import xPopen3
16 import xen.util.auxbin
17 import xen.lowlevel.xc
19 from xen.xend import balloon, sxp
20 from xen.xend.XendError import XendError, VmError
21 from xen.xend.XendLogging import log
22 from xen.xend.XendConfig import XendConfig
23 from xen.xend.XendConstants import *
25 SIGNATURE = "LinuxGuestRecord"
26 QEMU_SIGNATURE = "QemuDeviceModelRecord"
27 dm_batch = 512
28 XC_SAVE = "xc_save"
29 XC_RESTORE = "xc_restore"
32 sizeof_int = calcsize("i")
33 sizeof_unsigned_int = calcsize("I")
34 sizeof_unsigned_long = calcsize("L")
37 xc = xen.lowlevel.xc.xc()
40 def write_exact(fd, buf, errmsg):
41 if os.write(fd, buf) != len(buf):
42 raise XendError(errmsg)
45 def read_exact(fd, size, errmsg):
46 buf = ''
47 while size != 0:
48 readstr = os.read(fd, size)
49 if not len(readstr):
50 log.error("read_exact: EOF trying to read %d (buf='%s')" % \
51 (size, buf))
52 raise XendError(errmsg)
53 size = size - len(readstr)
54 buf = buf + readstr
55 return buf
58 def save(fd, dominfo, network, live, dst, checkpoint=False):
59 write_exact(fd, SIGNATURE, "could not write guest state file: signature")
61 config = sxp.to_string(dominfo.sxpr())
63 domain_name = dominfo.getName()
64 # Rename the domain temporarily, so that we don't get a name clash if this
65 # domain is migrating (live or non-live) to the local host. Doing such a
66 # thing is useful for debugging.
67 dominfo.setName('migrating-' + domain_name)
69 try:
70 dominfo.migrateDevices(network, dst, DEV_MIGRATE_STEP1, domain_name)
72 write_exact(fd, pack("!i", len(config)),
73 "could not write guest state file: config len")
74 write_exact(fd, config, "could not write guest state file: config")
76 image_cfg = dominfo.info.get('image', {})
77 hvm = dominfo.info.is_hvm()
79 # xc_save takes three customization parameters: maxit, max_f, and
80 # flags the last controls whether or not save is 'live', while the
81 # first two further customize behaviour when 'live' save is
82 # enabled. Passing "0" simply uses the defaults compiled into
83 # libxenguest; see the comments and/or code in xc_linux_save() for
84 # more information.
85 cmd = [xen.util.auxbin.pathTo(XC_SAVE), str(fd),
86 str(dominfo.getDomid()), "0", "0",
87 str(int(live) | (int(hvm) << 2)) ]
88 log.debug("[xc_save]: %s", string.join(cmd))
90 def saveInputHandler(line, tochild):
91 log.debug("In saveInputHandler %s", line)
92 if line == "suspend":
93 log.debug("Suspending %d ...", dominfo.getDomid())
94 dominfo.shutdown('suspend')
95 dominfo.waitForShutdown()
96 dominfo.migrateDevices(network, dst, DEV_MIGRATE_STEP2,
97 domain_name)
98 log.info("Domain %d suspended.", dominfo.getDomid())
99 dominfo.migrateDevices(network, dst, DEV_MIGRATE_STEP3,
100 domain_name)
101 #send signal to device model for save
102 if hvm:
103 log.info("release_devices for hvm domain")
104 dominfo._releaseDevices(True)
105 tochild.write("done\n")
106 tochild.flush()
107 log.debug('Written done')
109 forkHelper(cmd, fd, saveInputHandler, False)
111 # put qemu device model state
112 if hvm:
113 write_exact(fd, QEMU_SIGNATURE, "could not write qemu signature")
114 qemu_fd = os.open("/var/lib/xen/qemu-save.%d" % dominfo.getDomid(),
115 os.O_RDONLY)
116 while True:
117 buf = os.read(qemu_fd, dm_batch)
118 if len(buf):
119 write_exact(fd, buf, "could not write device model state")
120 else:
121 break
122 os.close(qemu_fd)
123 os.remove("/var/lib/xen/qemu-save.%d" % dominfo.getDomid())
125 if checkpoint:
126 dominfo.resumeDomain()
127 else:
128 dominfo.destroyDomain()
129 dominfo.testDeviceComplete()
130 try:
131 dominfo.setName(domain_name)
132 except VmError:
133 # Ignore this. The name conflict (hopefully) arises because we
134 # are doing localhost migration; if we are doing a suspend of a
135 # persistent VM, we need the rename, and don't expect the
136 # conflict. This needs more thought.
137 pass
139 except Exception, exn:
140 log.exception("Save failed on domain %s (%s).", domain_name,
141 dominfo.getDomid())
143 dominfo.resumeDomain()
144 log.debug("XendCheckpoint.save: resumeDomain")
146 try:
147 dominfo.setName(domain_name)
148 except:
149 log.exception("Failed to reset the migrating domain's name")
152 def restore(xd, fd, dominfo = None, paused = False):
153 signature = read_exact(fd, len(SIGNATURE),
154 "not a valid guest state file: signature read")
155 if signature != SIGNATURE:
156 raise XendError("not a valid guest state file: found '%s'" %
157 signature)
159 l = read_exact(fd, sizeof_int,
160 "not a valid guest state file: config size read")
161 vmconfig_size = unpack("!i", l)[0]
162 vmconfig_buf = read_exact(fd, vmconfig_size,
163 "not a valid guest state file: config read")
165 p = sxp.Parser()
166 p.input(vmconfig_buf)
167 if not p.ready:
168 raise XendError("not a valid guest state file: config parse")
170 vmconfig = p.get_val()
172 if dominfo:
173 dominfo.resume()
174 else:
175 dominfo = xd.restore_(vmconfig)
177 store_port = dominfo.getStorePort()
178 console_port = dominfo.getConsolePort()
180 assert store_port
181 assert console_port
183 nr_pfns = (dominfo.getMemoryTarget() + 3) / 4
185 # if hvm, pass mem size to calculate the store_mfn
186 image_cfg = dominfo.info.get('image', {})
187 is_hvm = dominfo.info.is_hvm()
188 if is_hvm:
189 apic = int(dominfo.info['platform'].get('apic', 0))
190 pae = int(dominfo.info['platform'].get('pae', 0))
191 log.info("restore hvm domain %d, apic=%d, pae=%d",
192 dominfo.domid, apic, pae)
193 else:
194 apic = 0
195 pae = 0
197 try:
198 shadow = dominfo.info['shadow_memory']
199 log.debug("restore:shadow=0x%x, _static_max=0x%x, _static_min=0x%x, ",
200 dominfo.info['shadow_memory'],
201 dominfo.info['memory_static_max'],
202 dominfo.info['memory_static_min'])
204 balloon.free(xc.pages_to_kib(nr_pfns) + shadow * 1024)
206 shadow_cur = xc.shadow_mem_control(dominfo.getDomid(), shadow)
207 dominfo.info['shadow_memory'] = shadow_cur
209 xc.domain_setmaxmem(dominfo.getDomid(), dominfo.getMemoryMaximum())
211 cmd = map(str, [xen.util.auxbin.pathTo(XC_RESTORE),
212 fd, dominfo.getDomid(),
213 store_port, console_port, int(is_hvm), pae, apic])
214 log.debug("[xc_restore]: %s", string.join(cmd))
216 handler = RestoreInputHandler()
218 forkHelper(cmd, fd, handler.handler, True)
220 # We don't want to pass this fd to any other children -- we
221 # might need to recover ths disk space that backs it.
222 try:
223 flags = fcntl.fcntl(fd, fcntl.F_GETFD)
224 flags |= fcntl.FD_CLOEXEC
225 fcntl.fcntl(fd, fcntl.F_SETFD, flags)
226 except:
227 pass
229 if handler.store_mfn is None:
230 raise XendError('Could not read store MFN')
232 if not is_hvm and handler.console_mfn is None:
233 raise XendError('Could not read console MFN')
235 # get qemu state and create a tmp file for dm restore
236 if is_hvm:
237 qemu_signature = read_exact(fd, len(QEMU_SIGNATURE),
238 "invalid device model signature read")
239 if qemu_signature != QEMU_SIGNATURE:
240 raise XendError("not a valid device model state: found '%s'" %
241 qemu_signature)
242 qemu_fd = os.open("/var/lib/xen/qemu-save.%d" % dominfo.getDomid(),
243 os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
244 while True:
245 buf = os.read(fd, dm_batch)
246 if len(buf):
247 write_exact(qemu_fd, buf,
248 "could not write dm state to tmp file")
249 else:
250 break
251 os.close(qemu_fd)
254 os.read(fd, 1) # Wait for source to close connection
256 dominfo.completeRestore(handler.store_mfn, handler.console_mfn)
258 #
259 # We shouldn't hold the domains_lock over a waitForDevices
260 # As this function sometime gets called holding this lock,
261 # we must release it and re-acquire it appropriately
262 #
263 from xen.xend import XendDomain
265 lock = True;
266 try:
267 XendDomain.instance().domains_lock.release()
268 except:
269 lock = False;
271 try:
272 dominfo.waitForDevices() # Wait for backends to set up
273 except Exception, exn:
274 log.exception(exn)
276 if lock:
277 XendDomain.instance().domains_lock.acquire()
279 if not paused:
280 dominfo.unpause()
282 return dominfo
283 except:
284 dominfo.destroy()
285 raise
288 class RestoreInputHandler:
289 def __init__(self):
290 self.store_mfn = None
291 self.console_mfn = None
294 def handler(self, line, _):
295 m = re.match(r"^(store-mfn) (\d+)$", line)
296 if m:
297 self.store_mfn = int(m.group(2))
298 else:
299 m = re.match(r"^(console-mfn) (\d+)$", line)
300 if m:
301 self.console_mfn = int(m.group(2))
304 def forkHelper(cmd, fd, inputHandler, closeToChild):
305 child = xPopen3(cmd, True, -1, [fd, xc.handle()])
307 if closeToChild:
308 child.tochild.close()
310 thread = threading.Thread(target = slurp, args = (child.childerr,))
311 thread.start()
313 try:
314 try:
315 while 1:
316 line = child.fromchild.readline()
317 if line == "":
318 break
319 else:
320 line = line.rstrip()
321 log.debug('%s', line)
322 inputHandler(line, child.tochild)
324 except IOError, exn:
325 raise XendError('Error reading from child process for %s: %s' %
326 (cmd, exn))
327 finally:
328 child.fromchild.close()
329 if not closeToChild:
330 child.tochild.close()
331 thread.join()
332 child.childerr.close()
333 status = child.wait()
335 if status >> 8 == 127:
336 raise XendError("%s failed: popen failed" % string.join(cmd))
337 elif status != 0:
338 raise XendError("%s failed" % string.join(cmd))
341 def slurp(infile):
342 while 1:
343 line = infile.readline()
344 if line == "":
345 break
346 else:
347 line = line.strip()
348 m = re.match(r"^ERROR: (.*)", line)
349 if m is None:
350 log.info('%s', line)
351 else:
352 log.error('%s', m.group(1))