summaryrefslogtreecommitdiffstats
path: root/scripts/lib/mic/imager/direct.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/lib/mic/imager/direct.py')
-rw-r--r--scripts/lib/mic/imager/direct.py472
1 files changed, 472 insertions, 0 deletions
diff --git a/scripts/lib/mic/imager/direct.py b/scripts/lib/mic/imager/direct.py
new file mode 100644
index 0000000000..d24bc684fe
--- /dev/null
+++ b/scripts/lib/mic/imager/direct.py
@@ -0,0 +1,472 @@
1# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3#
4# Copyright (c) 2013, Intel Corporation.
5# All rights reserved.
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
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 General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19#
20# DESCRIPTION
21# This implements the 'direct' image creator class for 'wic', based
22# loosely on the raw image creator from 'mic'
23#
24# AUTHORS
25# Tom Zanussi <tom.zanussi (at] linux.intel.com>
26#
27
28import os
29import stat
30import shutil
31
32from mic import kickstart, msger
33from mic.utils import fs_related, runner, misc
34from mic.utils.partitionedfs import PartitionedMount
35from mic.utils.errors import CreatorError, MountError
36from mic.imager.baseimager import BaseImageCreator
37from mic.utils.oe.misc import *
38
39class DirectImageCreator(BaseImageCreator):
40 """
41 Installs a system into a file containing a partitioned disk image.
42
43 DirectImageCreator is an advanced ImageCreator subclass; an image
44 file is formatted with a partition table, each partition created
45 from a rootfs or other OpenEmbedded build artifact and dd'ed into
46 the virtual disk. The disk image can subsequently be dd'ed onto
47 media and used on actual hardware.
48 """
49
50 def __init__(self, oe_builddir, image_output_dir, rootfs_dir, bootimg_dir,
51 kernel_dir, native_sysroot, hdddir, staging_data_dir,
52 creatoropts=None, pkgmgr=None, compress_image=None,
53 generate_bmap=None, fstab_entry="uuid"):
54 """
55 Initialize a DirectImageCreator instance.
56
57 This method takes the same arguments as ImageCreator.__init__()
58 """
59 BaseImageCreator.__init__(self, creatoropts, pkgmgr)
60
61 self.__instimage = None
62 self.__imgdir = None
63 self.__disks = {}
64 self.__disk_format = "direct"
65 self._disk_names = []
66 self._ptable_format = self.ks.handler.bootloader.ptable
67 self.use_uuid = fstab_entry == "uuid"
68 self.compress_image = compress_image
69 self.bmap_needed = generate_bmap
70
71 self.oe_builddir = oe_builddir
72 if image_output_dir:
73 self.tmpdir = image_output_dir
74 self.cachedir = "%s/cache" % image_output_dir
75 self.rootfs_dir = rootfs_dir
76 self.bootimg_dir = bootimg_dir
77 self.kernel_dir = kernel_dir
78 self.native_sysroot = native_sysroot
79 self.hdddir = hdddir
80 self.staging_data_dir = staging_data_dir
81 self.boot_type = ""
82
83 def __write_fstab(self):
84 """overriden to generate fstab (temporarily) in rootfs. This
85 is called from mount_instroot, make sure it doesn't get called
86 from BaseImage.mount()"""
87
88 image_rootfs = self.rootfs_dir
89
90 parts = self._get_parts()
91
92 fstab = image_rootfs + "/etc/fstab"
93
94 self._save_fstab(fstab)
95 fstab_lines = self._get_fstab(fstab, parts)
96 self._update_fstab(fstab_lines, parts)
97 self._write_fstab(fstab, fstab_lines)
98
99 return fstab
100
101 def _update_fstab(self, fstab_lines, parts):
102 """Assume partition order same as in wks"""
103 for num, p in enumerate(parts, 1):
104 if p.mountpoint == "/" or p.mountpoint == "/boot":
105 continue
106 if self._ptable_format == 'msdos' and num > 3:
107 device_name = "/dev/" + p.disk + str(num + 1)
108 else:
109 device_name = "/dev/" + p.disk + str(num)
110 fstab_entry = device_name + "\t" + p.mountpoint + "\t" + p.fstype + "\tdefaults\t0\t0\n"
111 fstab_lines.append(fstab_entry)
112
113 def _write_fstab(self, fstab, fstab_lines):
114 fstab = open(fstab, "w")
115 for line in fstab_lines:
116 fstab.write(line)
117 fstab.close()
118
119 def _save_fstab(self, fstab):
120 """Save the current fstab in rootfs"""
121 shutil.copyfile(fstab, fstab + ".orig")
122
123 def _restore_fstab(self, fstab):
124 """Restore the saved fstab in rootfs"""
125 shutil.move(fstab + ".orig", fstab)
126
127 def _get_fstab(self, fstab, parts):
128 """Return the desired contents of /etc/fstab."""
129 f = open(fstab, "r")
130 fstab_contents = f.readlines()
131 f.close()
132
133 return fstab_contents
134
135 def _get_parts(self):
136 if not self.ks:
137 raise CreatorError("Failed to get partition info, "
138 "please check your kickstart setting.")
139
140 # Set a default partition if no partition is given out
141 if not self.ks.handler.partition.partitions:
142 partstr = "part / --size 1900 --ondisk sda --fstype=ext3"
143 args = partstr.split()
144 pd = self.ks.handler.partition.parse(args[1:])
145 if pd not in self.ks.handler.partition.partitions:
146 self.ks.handler.partition.partitions.append(pd)
147
148 # partitions list from kickstart file
149 return kickstart.get_partitions(self.ks)
150
151 def get_disk_names(self):
152 """ Returns a list of physical target disk names (e.g., 'sdb') which
153 will be created. """
154
155 if self._disk_names:
156 return self._disk_names
157
158 #get partition info from ks handler
159 parts = self._get_parts()
160
161 for i in range(len(parts)):
162 if parts[i].disk:
163 disk_name = parts[i].disk
164 else:
165 raise CreatorError("Failed to create disks, no --ondisk "
166 "specified in partition line of ks file")
167
168 if parts[i].mountpoint and not parts[i].fstype:
169 raise CreatorError("Failed to create disks, no --fstype "
170 "specified for partition with mountpoint "
171 "'%s' in the ks file")
172
173 self._disk_names.append(disk_name)
174
175 return self._disk_names
176
177 def _full_name(self, name, extention):
178 """ Construct full file name for a file we generate. """
179 return "%s-%s.%s" % (self.name, name, extention)
180
181 def _full_path(self, path, name, extention):
182 """ Construct full file path to a file we generate. """
183 return os.path.join(path, self._full_name(name, extention))
184
185 def get_boot_type(self):
186 """ Determine the boot type from fstype and mountpoint. """
187 parts = self._get_parts()
188
189 boot_type = ""
190
191 for p in parts:
192 if p.mountpoint == "/boot":
193 if p.fstype == "msdos":
194 boot_type = "pcbios"
195 else:
196 boot_type = p.fstype
197 return boot_type
198
199 #
200 # Actual implemention
201 #
202 def _mount_instroot(self, base_on = None):
203 """
204 For 'wic', we already have our build artifacts and don't want
205 to loop mount anything to install into, we just create
206 filesystems from the artifacts directly and combine them into
207 a partitioned image.
208
209 We still want to reuse as much of the basic mic machinery
210 though; despite the fact that we don't actually do loop or any
211 other kind of mounting we still want to do many of the same
212 things to prepare images, so we basically just adapt to the
213 basic framework and reinterpret what 'mounting' means in our
214 context.
215
216 _instroot would normally be something like
217 /var/tmp/wic/build/imgcreate-s_9AKQ/install_root, for
218 installing packages, etc. We don't currently need to do that,
219 so we simplify life by just using /var/tmp/wic/build as our
220 workdir.
221 """
222 parts = self._get_parts()
223
224 self.__instimage = PartitionedMount(self._instroot)
225
226 fstab = self.__write_fstab()
227
228 self.boot_type = self.get_boot_type()
229
230 if not self.bootimg_dir:
231 if self.boot_type == "pcbios":
232 self.bootimg_dir = self.staging_data_dir
233 elif self.boot_type == "efi":
234 self.bootimg_dir = self.hdddir
235
236 if self.boot_type == "pcbios":
237 self._create_syslinux_config()
238 elif self.boot_type == "efi":
239 self._create_grubefi_config()
240 else:
241 raise CreatorError("Failed to detect boot type (no /boot partition?), "
242 "please check your kickstart setting.")
243
244 for p in parts:
245 if p.fstype == "efi":
246 p.fstype = "msdos"
247 # need to create the filesystems in order to get their
248 # sizes before we can add them and do the layout.
249 # PartitionedMount.mount() actually calls __format_disks()
250 # to create the disk images and carve out the partitions,
251 # then self.install() calls PartitionedMount.install()
252 # which calls __install_partitition() for each partition
253 # to dd the fs into the partitions. It would be nice to
254 # be able to use e.g. ExtDiskMount etc to create the
255 # filesystems, since that's where existing e.g. mkfs code
256 # is, but those are only created after __format_disks()
257 # which needs the partition sizes so needs them created
258 # before its called. Well, the existing setup is geared
259 # to installing packages into mounted filesystems - maybe
260 # when/if we need to actually do package selection we
261 # should modify things to use those objects, but for now
262 # we can avoid that.
263 p.prepare(self.workdir, self.oe_builddir, self.boot_type,
264 self.rootfs_dir, self.bootimg_dir, self.kernel_dir,
265 self.native_sysroot)
266
267 self.__instimage.add_partition(int(p.size),
268 p.disk,
269 p.mountpoint,
270 p.source_file,
271 p.fstype,
272 p.label,
273 fsopts = p.fsopts,
274 boot = p.active,
275 align = p.align,
276 part_type = p.part_type)
277 self._restore_fstab(fstab)
278 self.__instimage.layout_partitions(self._ptable_format)
279
280 self.__imgdir = self.workdir
281 for disk_name, disk in self.__instimage.disks.items():
282 full_path = self._full_path(self.__imgdir, disk_name, "direct")
283 msger.debug("Adding disk %s as %s with size %s bytes" \
284 % (disk_name, full_path, disk['min_size']))
285 disk_obj = fs_related.DiskImage(full_path, disk['min_size'])
286 self.__disks[disk_name] = disk_obj
287 self.__instimage.add_disk(disk_name, disk_obj)
288
289 self.__instimage.mount()
290
291 def install(self, repo_urls=None):
292 """
293 Install fs images into partitions
294 """
295 for disk_name, disk in self.__instimage.disks.items():
296 full_path = self._full_path(self.__imgdir, disk_name, "direct")
297 msger.debug("Installing disk %s as %s with size %s bytes" \
298 % (disk_name, full_path, disk['min_size']))
299 self.__instimage.install(full_path)
300
301 def configure(self, repodata = None):
302 """
303 Configure the system image according to kickstart.
304
305 For now, it just prepares the image to be bootable by e.g.
306 creating and installing a bootloader configuration.
307 """
308 if self.boot_type == "pcbios":
309 self._install_syslinux()
310
311 def print_outimage_info(self):
312 """
313 Print the image(s) and artifacts used, for the user.
314 """
315 msg = "The new image(s) can be found here:\n"
316
317 for disk_name, disk in self.__instimage.disks.items():
318 full_path = self._full_path(self.__imgdir, disk_name, "direct")
319 msg += ' %s\n\n' % full_path
320
321 msg += 'The following build artifacts were used to create the image(s):\n'
322 msg += ' ROOTFS_DIR: %s\n' % self.rootfs_dir
323 msg += ' BOOTIMG_DIR: %s\n' % self.bootimg_dir
324 msg += ' KERNEL_DIR: %s\n' % self.kernel_dir
325 msg += ' NATIVE_SYSROOT: %s\n' % self.native_sysroot
326
327 msger.info(msg)
328
329 def _get_boot_config(self):
330 """
331 Return the rootdev/root_part_uuid (if specified by
332 --part-type)
333
334 Assume partition order same as in wks
335 """
336 rootdev = None
337 root_part_uuid = None
338 parts = self._get_parts()
339 for num, p in enumerate(parts, 1):
340 if p.mountpoint == "/":
341 if self._ptable_format == 'msdos' and num > 3:
342 rootdev = "/dev/%s%-d" % (p.disk, num + 1)
343 else:
344 rootdev = "/dev/%s%-d" % (p.disk, num)
345 root_part_uuid = p.part_type
346
347 return (rootdev, root_part_uuid)
348
349 def _create_syslinux_config(self):
350 hdddir = "%s/hdd/boot" % self.workdir
351 rm_cmd = "rm -rf " + self.workdir
352 exec_cmd(rm_cmd)
353
354 install_cmd = "install -d %s" % hdddir
355 tmp = exec_cmd(install_cmd)
356
357 splash = os.path.join(self.workdir, "/hdd/boot/splash.jpg")
358 if os.path.exists(splash):
359 splashline = "menu background splash.jpg"
360 else:
361 splashline = ""
362
363 (rootdev, root_part_uuid) = self._get_boot_config()
364 options = self.ks.handler.bootloader.appendLine
365
366 syslinux_conf = ""
367 syslinux_conf += "PROMPT 0\n"
368 timeout = kickstart.get_timeout(self.ks)
369 if not timeout:
370 timeout = 0
371 syslinux_conf += "TIMEOUT " + str(timeout) + "\n"
372 syslinux_conf += "\n"
373 syslinux_conf += "ALLOWOPTIONS 1\n"
374 syslinux_conf += "SERIAL 0 115200\n"
375 syslinux_conf += "\n"
376 if splashline:
377 syslinux_conf += "%s\n" % splashline
378 syslinux_conf += "DEFAULT boot\n"
379 syslinux_conf += "LABEL boot\n"
380
381 kernel = "/vmlinuz"
382 syslinux_conf += "KERNEL " + kernel + "\n"
383
384 if self._ptable_format == 'msdos':
385 rootstr = rootdev
386 else:
387 if not root_part_uuid:
388 raise MountError("Cannot find the root GPT partition UUID")
389 rootstr = "PARTUUID=%s" % root_part_uuid
390
391 syslinux_conf += "APPEND label=boot root=%s %s\n" % (rootstr, options)
392
393 msger.debug("Writing syslinux config %s/hdd/boot/syslinux.cfg" \
394 % self.workdir)
395 cfg = open("%s/hdd/boot/syslinux.cfg" % self.workdir, "w")
396 cfg.write(syslinux_conf)
397 cfg.close()
398
399 def _create_grubefi_config(self):
400 hdddir = "%s/hdd/boot" % self.workdir
401 rm_cmd = "rm -rf %s" % self.workdir
402 exec_cmd(rm_cmd)
403
404 install_cmd = "install -d %s/EFI/BOOT" % hdddir
405 tmp = exec_cmd(install_cmd)
406
407 splash = os.path.join(self.workdir, "/EFI/boot/splash.jpg")
408 if os.path.exists(splash):
409 splashline = "menu background splash.jpg"
410 else:
411 splashline = ""
412
413 (rootdev, root_part_uuid) = self._get_boot_config()
414 options = self.ks.handler.bootloader.appendLine
415
416 grubefi_conf = ""
417 grubefi_conf += "serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1\n"
418 grubefi_conf += "default=boot\n"
419 timeout = kickstart.get_timeout(self.ks)
420 if not timeout:
421 timeout = 0
422 grubefi_conf += "timeout=%s\n" % timeout
423 grubefi_conf += "menuentry 'boot'{\n"
424
425 kernel = "/vmlinuz"
426
427 if self._ptable_format == 'msdos':
428 rootstr = rootdev
429 else:
430 if not root_part_uuid:
431 raise MountError("Cannot find the root GPT partition UUID")
432 rootstr = "PARTUUID=%s" % root_part_uuid
433
434 grubefi_conf += "linux %s root=%s rootwait %s\n" \
435 % (kernel, rootstr, options)
436 grubefi_conf += "}\n"
437 if splashline:
438 syslinux_conf += "%s\n" % splashline
439
440 msger.debug("Writing grubefi config %s/hdd/boot/EFI/BOOT/grub.cfg" \
441 % self.workdir)
442 cfg = open("%s/hdd/boot/EFI/BOOT/grub.cfg" % self.workdir, "w")
443 cfg.write(grubefi_conf)
444 cfg.close()
445
446 def _install_syslinux(self):
447 mbrfile = "%s/syslinux/" % self.bootimg_dir
448 if self._ptable_format == 'gpt':
449 mbrfile += "gptmbr.bin"
450 else:
451 mbrfile += "mbr.bin"
452
453 if not os.path.exists(mbrfile):
454 msger.error("Couldn't find %s. If using the -e option, do you have the right MACHINE set in local.conf? If not, is the bootimg_dir path correct?" % mbrfile)
455
456 for disk_name, disk in self.__instimage.disks.items():
457 full_path = self._full_path(self.__imgdir, disk_name, "direct")
458 msger.debug("Installing MBR on disk %s as %s with size %s bytes" \
459 % (disk_name, full_path, disk['min_size']))
460
461 rc = runner.show(['dd', 'if=%s' % mbrfile,
462 'of=%s' % full_path, 'conv=notrunc'])
463 if rc != 0:
464 raise MountError("Unable to set MBR to %s" % full_path)
465
466 def _unmount_instroot(self):
467 if not self.__instimage is None:
468 try:
469 self.__instimage.cleanup()
470 except MountError, err:
471 msger.warning("%s" % err)
472