2 # raw.py: RawImageCreator class
4 # Copyright 2007-2008, Red Hat Inc.
5 # Copyright 2008, Daniel P. Berrange
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; version 2 of the License.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU Library General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29 import mic.utils.kickstart as kickstart
30 import mic.utils.fs_related as fs_related
31 import urlgrabber.progress as progress
32 from baseimager import BaseImageCreator
33 from mic.utils.partitionedfs import PartitionedMount
34 from mic.utils.errors import *
37 class RawImageCreator(BaseImageCreator):
38 """Installs a system into a file containing a partitioned disk image.
40 ApplianceImageCreator is an advanced ImageCreator subclass; a sparse file
41 is formatted with a partition table, each partition loopback mounted
42 and the system installed into an virtual disk. The disk image can
43 subsequently be booted in a virtual machine or accessed with kpartx
46 def __init__(self, *args):
47 """Initialize a ApplianceImageCreator instance.
49 This method takes the same arguments as ImageCreator.__init__()
51 BaseImageCreator.__init__(self, *args)
53 self.__instloop = None
56 self.__disk_format = "raw"
60 self.appliance_version = None
61 self.appliance_release = None
62 #self.getsource = False
65 self._dep_checks.extend(["sync", "kpartx", "parted", "extlinux"])
67 def configure(self, repodata = None):
70 os.chroot(self._instroot)
73 if os.path.exists(self._instroot + "/usr/bin/Xorg"):
74 subprocess.call(["/bin/chmod", "u+s", "/usr/bin/Xorg"], preexec_fn = chroot)
75 BaseImageCreator.configure(self, repodata)
79 for mp in self.__instloop.mountOrder:
81 for p1 in self.__instloop.partitions:
82 if p1['mountpoint'] == mp:
86 s += "%(device)s %(mountpoint)s %(fstype)s %(fsopts)s 0 0\n" % {
87 'device': "/dev/%s%-d" % (p['disk'], p['num']),
88 'mountpoint': p['mountpoint'],
89 'fstype': p['fstype'],
90 'fsopts': "defaults,noatime" if not p['fsopts'] else p['fsopts']}
92 if p['mountpoint'] == "/":
93 for subvol in self.__instloop.subvolumes:
94 if subvol['mountpoint'] == "/":
96 s += "%(device)s %(mountpoint)s %(fstype)s %(fsopts)s 0 0\n" % {
97 'device': "/dev/%s%-d" % (p['disk'], p['num']),
98 'mountpoint': subvol['mountpoint'],
99 'fstype': p['fstype'],
100 'fsopts': "defaults,noatime" if not subvol['fsopts'] else subvol['fsopts']}
102 s += "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
103 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
104 s += "proc /proc proc defaults 0 0\n"
105 s += "sysfs /sys sysfs defaults 0 0\n"
108 def _create_mkinitrd_config(self):
109 #write to tell which modules to be included in initrd
112 mkinitrd += "PROBE=\"no\"\n"
113 mkinitrd += "MODULES+=\"ext3 ata_piix sd_mod libata scsi_mod\"\n"
114 mkinitrd += "rootfs=\"ext3\"\n"
115 mkinitrd += "rootopts=\"defaults\"\n"
117 logging.debug("Writing mkinitrd config %s/etc/sysconfig/mkinitrd" % self._instroot)
118 os.makedirs(self._instroot + "/etc/sysconfig/",mode=644)
119 cfg = open(self._instroot + "/etc/sysconfig/mkinitrd", "w")
124 # Actual implementation
126 def _mount_instroot(self, base_on = None):
127 self.__imgdir = self._mkdtemp()
129 #Set a default partition if no partition is given out
130 if not self.ks.handler.partition.partitions:
131 partstr = "part / --size 1900 --ondisk sda --fstype=ext3"
132 args = partstr.split()
133 pd = self.ks.handler.partition.parse(args[1:])
134 if pd not in self.ks.handler.partition.partitions:
135 self.ks.handler.partition.partitions.append(pd)
137 #list of partitions from kickstart file
138 parts = kickstart.get_partitions(self.ks)
140 #list of disks where a disk is an dict with name: and size
143 for i in range(len(parts)):
147 raise CreatorError("Failed to create disks, no --ondisk specified in partition line of ks file")
149 if not parts[i].fstype:
150 raise CreatorError("Failed to create disks, no --fstype specified in partition line of ks file")
152 size = parts[i].size * 1024L * 1024L
155 for j in range(len(disks)):
156 if disks[j]['name'] == disk:
157 disks[j]['size'] = disks[j]['size'] + size
163 disks.append({ 'name': disk, 'size': size })
167 logging.debug("Adding disk %s as %s/%s-%s.raw" % (item['name'], self.__imgdir,self.name, item['name']))
168 disk = fs_related.SparseLoopbackDisk("%s/%s-%s.raw" % (self.__imgdir,self.name, item['name']),item['size'])
169 self.__disks[item['name']] = disk
171 self.__instloop = PartitionedMount(self.__disks, self._instroot)
174 self.__instloop.add_partition(int(p.size), p.disk, p.mountpoint, p.fstype, fsopts = p.fsopts, boot = p.active)
177 self.__instloop.mount()
178 except MountError, e:
179 raise CreatorError("Failed mount disks : %s" % e)
181 self._create_mkinitrd_config()
183 def _get_required_packages(self):
184 required_packages = BaseImageCreator._get_required_packages(self)
185 if not self.target_arch or not self.target_arch.startswith("arm"):
186 required_packages += ["syslinux", "syslinux-extlinux"]
187 return required_packages
189 def _get_excluded_packages(self):
190 return BaseImageCreator._get_excluded_packages(self)
192 def _get_syslinux_boot_config(self):
196 for p in self.__instloop.partitions:
197 if p['mountpoint'] == "/boot":
198 bootdevnum = p['num'] - 1
199 elif p['mountpoint'] == "/" and bootdevnum is None:
200 bootdevnum = p['num'] - 1
202 if p['mountpoint'] == "/":
203 rootdevnum = p['num'] - 1
204 rootdev = "/dev/%s%-d" % (p['disk'], p['num'])
207 if bootdevnum == rootdevnum:
210 return (bootdevnum, rootdevnum, rootdev, prefix)
212 def _create_syslinux_config(self):
214 splash = "%s/usr/lib/anaconda-runtime/syslinux-vesa-splash.jpg" % self._instroot
215 if os.path.exists(splash):
216 shutil.copy(splash, "%s%s/splash.jpg" % (self._instroot, "/boot/extlinux"))
217 splashline = "menu background splash.jpg"
221 (bootdevnum, rootdevnum, rootdev, prefix) = self._get_syslinux_boot_config()
222 options = self.ks.handler.bootloader.appendLine
224 #XXX don't hardcode default kernel - see livecd code
226 syslinux_conf += "prompt 0\n"
227 syslinux_conf += "timeout 1\n"
228 syslinux_conf += "\n"
229 syslinux_conf += "default vesamenu.c32\n"
230 syslinux_conf += "menu autoboot Starting %s...\n" % self.distro_name
231 syslinux_conf += "menu hidden\n"
232 syslinux_conf += "\n"
233 syslinux_conf += "%s\n" % splashline
234 syslinux_conf += "menu title Welcome to %s!\n" % self.distro_name
235 syslinux_conf += "menu color border 0 #ffffffff #00000000\n"
236 syslinux_conf += "menu color sel 7 #ffffffff #ff000000\n"
237 syslinux_conf += "menu color title 0 #ffffffff #00000000\n"
238 syslinux_conf += "menu color tabmsg 0 #ffffffff #00000000\n"
239 syslinux_conf += "menu color unsel 0 #ffffffff #00000000\n"
240 syslinux_conf += "menu color hotsel 0 #ff000000 #ffffffff\n"
241 syslinux_conf += "menu color hotkey 7 #ffffffff #ff000000\n"
242 syslinux_conf += "menu color timeout_msg 0 #ffffffff #00000000\n"
243 syslinux_conf += "menu color timeout 0 #ffffffff #00000000\n"
244 syslinux_conf += "menu color cmdline 0 #ffffffff #00000000\n"
247 kernels = self._get_kernel_versions()
248 for kernel in kernels:
249 for version in kernels[kernel]:
250 versions.append(version)
254 shutil.copy("%s/boot/vmlinuz-%s" %(self._instroot, v),
255 "%s%s/vmlinuz-%s" % (self._instroot, "/boot/extlinux/", v))
256 syslinux_conf += "label %s%d\n" % (self.distro_name.lower(), footlabel)
257 syslinux_conf += "\tmenu label %s (%s)\n" % (self.distro_name, v)
258 syslinux_conf += "\tkernel vmlinuz-%s\n" % v
259 syslinux_conf += "\tappend ro root=%s quiet vga=current %s\n" % (rootdev, options)
261 syslinux_conf += "\tmenu default\n"
264 logging.debug("Writing syslinux config %s/boot/extlinux/extlinux.conf" % self._instroot)
265 cfg = open(self._instroot + "/boot/extlinux/extlinux.conf", "w")
266 cfg.write(syslinux_conf)
269 def _install_syslinux(self):
271 for name in self.__disks.keys():
272 loopdev = self.__disks[name].device
275 logging.debug("Installing syslinux bootloader to %s" % loopdev)
277 (bootdevnum, rootdevnum, rootdev, prefix) = self._get_syslinux_boot_config()
281 mbrsize = os.stat("%s/usr/share/syslinux/mbr.bin" % self._instroot)[stat.ST_SIZE]
282 ddcmd = fs_related.find_binary_path("dd")
283 rc = subprocess.call([ddcmd, "if=%s/usr/share/syslinux/mbr.bin" % self._instroot, "of=" + loopdev])
285 raise MountError("Unable to set MBR to %s" % loopdev)
288 parted = fs_related.find_binary_path("parted")
289 dev_null = os.open("/dev/null", os.O_WRONLY)
290 rc = subprocess.call([parted, "-s", loopdev, "set", "%d" % (bootdevnum + 1), "boot", "on"],
291 stdout = dev_null, stderr = dev_null)
293 #XXX disabled return code check because parted always fails to
294 #reload part table with loop devices. Annoying because we can't
295 #distinguish this failure from real partition failures :-(
296 if rc != 0 and 1 == 0:
297 raise MountError("Unable to set bootable flag to %sp%d" % (loopdev, (bootdevnum + 1)))
300 #Ensure all data is flushed to disk before doing syslinux install
301 subprocess.call(["sync"])
303 fullpathsyslinux = fs_related.find_binary_path("extlinux")
304 rc = subprocess.call([fullpathsyslinux, "-i", "%s/boot/extlinux" % self._instroot])
306 raise MountError("Unable to install syslinux bootloader to %sp%d" % (loopdev, (bootdevnum + 1)))
308 def _create_bootconfig(self):
309 #If syslinux is available do the required configurations.
310 if os.path.exists("%s/usr/share/syslinux/" % (self._instroot)) \
311 and os.path.exists("%s/boot/extlinux/" % (self._instroot)):
312 self._create_syslinux_config()
313 self._install_syslinux()
315 def _unmount_instroot(self):
316 if not self.__instloop is None:
317 self.__instloop.cleanup()
319 def _resparse(self, size = None):
320 return self.__instloop.resparse(size)
322 def _stage_final_image(self):
323 """Stage the final system image in _outdir.
328 logging.debug("moving disks to stage location")
329 for name in self.__disks.keys():
330 src = "%s/%s-%s.raw" % (self.__imgdir, self.name,name)
331 self._img_name = "%s-%s.%s" % (self.name, name, self.__disk_format)
332 dst = "%s/%s" % (self._outdir, self._img_name)
333 logging.debug("moving %s to %s" % (src,dst))
335 self._write_image_xml()
337 def _write_image_xml(self):
339 if self.target_arch and self.target_arch.startswith("arm"):
344 if self.appliance_version:
345 name_attributes += " version='%s'" % self.appliance_version
346 if self.appliance_release:
347 name_attributes += " release='%s'" % self.appliance_release
348 xml += " <name%s>%s</name>\n" % (name_attributes, self.name)
350 # XXX don't hardcode - determine based on the kernel we installed for grub
352 xml += " <boot type='hvm'>\n"
354 xml += " <arch>%s</arch>\n" % imgarch
357 xml += " <loader dev='hd'/>\n"
361 for name in self.__disks.keys():
362 xml += " <drive disk='%s-%s.%s' target='hd%s'/>\n" % (self.name,name, self.__disk_format,chr(ord('a')+i))
366 xml += " <devices>\n"
367 xml += " <vcpu>%s</vcpu>\n" % self.vcpu
368 xml += " <memory>%d</memory>\n" %(self.vmem * 1024)
369 for network in self.ks.handler.network.network:
370 xml += " <interface/>\n"
371 xml += " <graphics/>\n"
372 xml += " </devices>\n"
373 xml += " </domain>\n"
374 xml += " <storage>\n"
376 if self.checksum is True:
377 for name in self.__disks.keys():
378 diskpath = "%s/%s-%s.%s" % (self._outdir,self.name,name, self.__disk_format)
379 disk_size = os.path.getsize(diskpath)
381 meter = progress.TextMeter()
382 meter.start(size=disk_size, text="Generating disk signature for %s-%s.%s" % (self.name,name,self.__disk_format))
383 xml += " <disk file='%s-%s.%s' use='system' format='%s'>\n" % (self.name,name, self.__disk_format, self.__disk_format)
388 m2 = hashlib.sha256()
393 f = open(diskpath,"r")
395 chunk = f.read(65536)
401 meter.update(meter_ct)
402 meter_ct = meter_ct + 65536
404 sha1checksum = m1.hexdigest()
405 xml += """ <checksum type='sha1'>%s</checksum>\n""" % sha1checksum
408 sha256checksum = m2.hexdigest()
409 xml += """ <checksum type='sha256'>%s</checksum>\n""" % sha256checksum
412 for name in self.__disks.keys():
413 xml += " <disk file='%s-%s.%s' use='system' format='%s'/>\n" % (self.name,name, self.__disk_format, self.__disk_format)
415 xml += " </storage>\n"
418 logging.debug("writing image XML to %s/%s.xml" % (self._outdir, self.name))
419 cfg = open("%s/%s.xml" % (self._outdir, self.name), "w")
422 #print "Wrote: %s.xml" % self.name