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.
25 import mic.utils.fs_related as fs_related
26 import urlgrabber.progress as progress
27 from baseimager import BaseImageCreator
28 from mic.utils.partitionedfs import PartitionedMount
29 from mic.utils.errors import CreatorError, MountError
30 from mic import kickstart
33 class RawImageCreator(BaseImageCreator):
34 """Installs a system into a file containing a partitioned disk image.
36 ApplianceImageCreator is an advanced ImageCreator subclass; a sparse file
37 is formatted with a partition table, each partition loopback mounted
38 and the system installed into an virtual disk. The disk image can
39 subsequently be booted in a virtual machine or accessed with kpartx
42 def __init__(self, *args):
43 """Initialize a ApplianceImageCreator instance.
45 This method takes the same arguments as ImageCreator.__init__()
47 BaseImageCreator.__init__(self, *args)
49 self.__instloop = None
52 self.__disk_format = "raw"
56 self.appliance_version = None
57 self.appliance_release = None
58 #self.getsource = False
61 self._dep_checks.extend(["sync", "kpartx", "parted", "extlinux"])
63 def configure(self, repodata = None):
66 os.chroot(self._instroot)
69 if os.path.exists(self._instroot + "/usr/bin/Xorg"):
70 subprocess.call(["/bin/chmod", "u+s", "/usr/bin/Xorg"], preexec_fn = chroot)
71 BaseImageCreator.configure(self, repodata)
75 for mp in self.__instloop.mountOrder:
77 for p1 in self.__instloop.partitions:
78 if p1['mountpoint'] == mp:
82 s += "%(device)s %(mountpoint)s %(fstype)s %(fsopts)s 0 0\n" % {
83 'device': "/dev/%s%-d" % (p['disk'], p['num']),
84 'mountpoint': p['mountpoint'],
85 'fstype': p['fstype'],
86 'fsopts': "defaults,noatime" if not p['fsopts'] else p['fsopts']}
88 if p['mountpoint'] == "/":
89 for subvol in self.__instloop.subvolumes:
90 if subvol['mountpoint'] == "/":
92 s += "%(device)s %(mountpoint)s %(fstype)s %(fsopts)s 0 0\n" % {
93 'device': "/dev/%s%-d" % (p['disk'], p['num']),
94 'mountpoint': subvol['mountpoint'],
95 'fstype': p['fstype'],
96 'fsopts': "defaults,noatime" if not subvol['fsopts'] else subvol['fsopts']}
98 s += "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
99 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
100 s += "proc /proc proc defaults 0 0\n"
101 s += "sysfs /sys sysfs defaults 0 0\n"
104 def _create_mkinitrd_config(self):
105 #write to tell which modules to be included in initrd
108 mkinitrd += "PROBE=\"no\"\n"
109 mkinitrd += "MODULES+=\"ext3 ata_piix sd_mod libata scsi_mod\"\n"
110 mkinitrd += "rootfs=\"ext3\"\n"
111 mkinitrd += "rootopts=\"defaults\"\n"
113 msger.debug("Writing mkinitrd config %s/etc/sysconfig/mkinitrd" % self._instroot)
114 os.makedirs(self._instroot + "/etc/sysconfig/",mode=644)
115 cfg = open(self._instroot + "/etc/sysconfig/mkinitrd", "w")
120 # Actual implementation
122 def _mount_instroot(self, base_on = None):
123 self.__imgdir = self._mkdtemp()
125 #Set a default partition if no partition is given out
126 if not self.ks.handler.partition.partitions:
127 partstr = "part / --size 1900 --ondisk sda --fstype=ext3"
128 args = partstr.split()
129 pd = self.ks.handler.partition.parse(args[1:])
130 if pd not in self.ks.handler.partition.partitions:
131 self.ks.handler.partition.partitions.append(pd)
133 #list of partitions from kickstart file
134 parts = kickstart.get_partitions(self.ks)
136 #list of disks where a disk is an dict with name: and size
139 for i in range(len(parts)):
143 raise CreatorError("Failed to create disks, no --ondisk specified in partition line of ks file")
145 if not parts[i].fstype:
146 raise CreatorError("Failed to create disks, no --fstype specified in partition line of ks file")
148 size = parts[i].size * 1024L * 1024L
151 for j in range(len(disks)):
152 if disks[j]['name'] == disk:
153 disks[j]['size'] = disks[j]['size'] + size
159 disks.append({ 'name': disk, 'size': size })
163 msger.debug("Adding disk %s as %s/%s-%s.raw" % (item['name'], self.__imgdir,self.name, item['name']))
164 disk = fs_related.SparseLoopbackDisk("%s/%s-%s.raw" % (self.__imgdir,self.name, item['name']),item['size'])
165 self.__disks[item['name']] = disk
167 self.__instloop = PartitionedMount(self.__disks, self._instroot)
170 self.__instloop.add_partition(int(p.size), p.disk, p.mountpoint, p.fstype, fsopts = p.fsopts, boot = p.active)
173 self.__instloop.mount()
174 except MountError, e:
175 raise CreatorError("Failed mount disks : %s" % e)
177 self._create_mkinitrd_config()
179 def _get_required_packages(self):
180 required_packages = BaseImageCreator._get_required_packages(self)
181 if not self.target_arch or not self.target_arch.startswith("arm"):
182 required_packages += ["syslinux", "syslinux-extlinux"]
183 return required_packages
185 def _get_excluded_packages(self):
186 return BaseImageCreator._get_excluded_packages(self)
188 def _get_syslinux_boot_config(self):
192 for p in self.__instloop.partitions:
193 if p['mountpoint'] == "/boot":
194 bootdevnum = p['num'] - 1
195 elif p['mountpoint'] == "/" and bootdevnum is None:
196 bootdevnum = p['num'] - 1
198 if p['mountpoint'] == "/":
199 rootdevnum = p['num'] - 1
200 rootdev = "/dev/%s%-d" % (p['disk'], p['num'])
203 if bootdevnum == rootdevnum:
206 return (bootdevnum, rootdevnum, rootdev, prefix)
208 def _create_syslinux_config(self):
210 splash = "%s/usr/lib/anaconda-runtime/syslinux-vesa-splash.jpg" % self._instroot
211 if os.path.exists(splash):
212 shutil.copy(splash, "%s%s/splash.jpg" % (self._instroot, "/boot/extlinux"))
213 splashline = "menu background splash.jpg"
217 (bootdevnum, rootdevnum, rootdev, prefix) = self._get_syslinux_boot_config()
218 options = self.ks.handler.bootloader.appendLine
220 #XXX don't hardcode default kernel - see livecd code
222 syslinux_conf += "prompt 0\n"
223 syslinux_conf += "timeout 1\n"
224 syslinux_conf += "\n"
225 syslinux_conf += "default vesamenu.c32\n"
226 syslinux_conf += "menu autoboot Starting %s...\n" % self.distro_name
227 syslinux_conf += "menu hidden\n"
228 syslinux_conf += "\n"
229 syslinux_conf += "%s\n" % splashline
230 syslinux_conf += "menu title Welcome to %s!\n" % self.distro_name
231 syslinux_conf += "menu color border 0 #ffffffff #00000000\n"
232 syslinux_conf += "menu color sel 7 #ffffffff #ff000000\n"
233 syslinux_conf += "menu color title 0 #ffffffff #00000000\n"
234 syslinux_conf += "menu color tabmsg 0 #ffffffff #00000000\n"
235 syslinux_conf += "menu color unsel 0 #ffffffff #00000000\n"
236 syslinux_conf += "menu color hotsel 0 #ff000000 #ffffffff\n"
237 syslinux_conf += "menu color hotkey 7 #ffffffff #ff000000\n"
238 syslinux_conf += "menu color timeout_msg 0 #ffffffff #00000000\n"
239 syslinux_conf += "menu color timeout 0 #ffffffff #00000000\n"
240 syslinux_conf += "menu color cmdline 0 #ffffffff #00000000\n"
243 kernels = self._get_kernel_versions()
244 for kernel in kernels:
245 for version in kernels[kernel]:
246 versions.append(version)
250 shutil.copy("%s/boot/vmlinuz-%s" %(self._instroot, v),
251 "%s%s/vmlinuz-%s" % (self._instroot, "/boot/extlinux/", v))
252 syslinux_conf += "label %s%d\n" % (self.distro_name.lower(), footlabel)
253 syslinux_conf += "\tmenu label %s (%s)\n" % (self.distro_name, v)
254 syslinux_conf += "\tkernel vmlinuz-%s\n" % v
255 syslinux_conf += "\tappend ro root=%s quiet vga=current %s\n" % (rootdev, options)
257 syslinux_conf += "\tmenu default\n"
260 msger.debug("Writing syslinux config %s/boot/extlinux/extlinux.conf" % self._instroot)
261 cfg = open(self._instroot + "/boot/extlinux/extlinux.conf", "w")
262 cfg.write(syslinux_conf)
265 def _install_syslinux(self):
267 for name in self.__disks.keys():
268 loopdev = self.__disks[name].device
271 msger.debug("Installing syslinux bootloader to %s" % loopdev)
273 (bootdevnum, rootdevnum, rootdev, prefix) = self._get_syslinux_boot_config()
277 mbrsize = os.stat("%s/usr/share/syslinux/mbr.bin" % self._instroot)[stat.ST_SIZE]
278 ddcmd = fs_related.find_binary_path("dd")
279 rc = subprocess.call([ddcmd, "if=%s/usr/share/syslinux/mbr.bin" % self._instroot, "of=" + loopdev])
281 raise MountError("Unable to set MBR to %s" % loopdev)
284 parted = fs_related.find_binary_path("parted")
285 dev_null = os.open("/dev/null", os.O_WRONLY)
286 rc = subprocess.call([parted, "-s", loopdev, "set", "%d" % (bootdevnum + 1), "boot", "on"],
287 stdout = dev_null, stderr = dev_null)
289 #XXX disabled return code check because parted always fails to
290 #reload part table with loop devices. Annoying because we can't
291 #distinguish this failure from real partition failures :-(
292 if rc != 0 and 1 == 0:
293 raise MountError("Unable to set bootable flag to %sp%d" % (loopdev, (bootdevnum + 1)))
296 #Ensure all data is flushed to disk before doing syslinux install
297 subprocess.call(["sync"])
299 fullpathsyslinux = fs_related.find_binary_path("extlinux")
300 rc = subprocess.call([fullpathsyslinux, "-i", "%s/boot/extlinux" % self._instroot])
302 raise MountError("Unable to install syslinux bootloader to %sp%d" % (loopdev, (bootdevnum + 1)))
304 def _create_bootconfig(self):
305 #If syslinux is available do the required configurations.
306 if os.path.exists("%s/usr/share/syslinux/" % (self._instroot)) \
307 and os.path.exists("%s/boot/extlinux/" % (self._instroot)):
308 self._create_syslinux_config()
309 self._install_syslinux()
311 def _unmount_instroot(self):
312 if not self.__instloop is None:
313 self.__instloop.cleanup()
315 def _resparse(self, size = None):
316 return self.__instloop.resparse(size)
318 def _stage_final_image(self):
319 """Stage the final system image in _outdir.
324 msger.debug("moving disks to stage location")
325 for name in self.__disks.keys():
326 src = "%s/%s-%s.raw" % (self.__imgdir, self.name,name)
327 self._img_name = "%s-%s.%s" % (self.name, name, self.__disk_format)
328 dst = "%s/%s" % (self._outdir, self._img_name)
329 msger.debug("moving %s to %s" % (src,dst))
331 self._write_image_xml()
333 def _write_image_xml(self):
335 if self.target_arch and self.target_arch.startswith("arm"):
340 if self.appliance_version:
341 name_attributes += " version='%s'" % self.appliance_version
342 if self.appliance_release:
343 name_attributes += " release='%s'" % self.appliance_release
344 xml += " <name%s>%s</name>\n" % (name_attributes, self.name)
346 # XXX don't hardcode - determine based on the kernel we installed for grub
348 xml += " <boot type='hvm'>\n"
350 xml += " <arch>%s</arch>\n" % imgarch
353 xml += " <loader dev='hd'/>\n"
357 for name in self.__disks.keys():
358 xml += " <drive disk='%s-%s.%s' target='hd%s'/>\n" % (self.name,name, self.__disk_format,chr(ord('a')+i))
362 xml += " <devices>\n"
363 xml += " <vcpu>%s</vcpu>\n" % self.vcpu
364 xml += " <memory>%d</memory>\n" %(self.vmem * 1024)
365 for network in self.ks.handler.network.network:
366 xml += " <interface/>\n"
367 xml += " <graphics/>\n"
368 xml += " </devices>\n"
369 xml += " </domain>\n"
370 xml += " <storage>\n"
372 if self.checksum is True:
373 for name in self.__disks.keys():
374 diskpath = "%s/%s-%s.%s" % (self._outdir,self.name,name, self.__disk_format)
375 disk_size = os.path.getsize(diskpath)
377 meter = progress.TextMeter()
378 meter.start(size=disk_size, text="Generating disk signature for %s-%s.%s" % (self.name,name,self.__disk_format))
379 xml += " <disk file='%s-%s.%s' use='system' format='%s'>\n" % (self.name,name, self.__disk_format, self.__disk_format)
384 m2 = hashlib.sha256()
389 f = open(diskpath,"r")
391 chunk = f.read(65536)
397 meter.update(meter_ct)
398 meter_ct = meter_ct + 65536
400 sha1checksum = m1.hexdigest()
401 xml += """ <checksum type='sha1'>%s</checksum>\n""" % sha1checksum
404 sha256checksum = m2.hexdigest()
405 xml += """ <checksum type='sha256'>%s</checksum>\n""" % sha256checksum
408 for name in self.__disks.keys():
409 xml += " <disk file='%s-%s.%s' use='system' format='%s'/>\n" % (self.name,name, self.__disk_format, self.__disk_format)
411 xml += " </storage>\n"
414 msger.debug("writing image XML to %s/%s.xml" % (self._outdir, self.name))
415 cfg = open("%s/%s.xml" % (self._outdir, self.name), "w")
418 #print "Wrote: %s.xml" % self.name