summaryrefslogtreecommitdiffstats
path: root/scripts/lib/wic/imager/direct.py
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/lib/wic/imager/direct.py')
-rw-r--r--scripts/lib/wic/imager/direct.py402
1 files changed, 0 insertions, 402 deletions
diff --git a/scripts/lib/wic/imager/direct.py b/scripts/lib/wic/imager/direct.py
deleted file mode 100644
index ff06b504ce..0000000000
--- a/scripts/lib/wic/imager/direct.py
+++ /dev/null
@@ -1,402 +0,0 @@
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'
22#
23# AUTHORS
24# Tom Zanussi <tom.zanussi (at] linux.intel.com>
25#
26
27import os
28import shutil
29import uuid
30import tempfile
31
32from wic import msger
33from wic.utils.oe.misc import get_bitbake_var
34from wic.utils.partitionedfs import Image
35from wic.utils.errors import CreatorError, ImageError
36from wic.plugin import pluginmgr
37from wic.utils.oe.misc import exec_cmd, exec_native_cmd
38
39disk_methods = {
40 "do_install_disk":None,
41}
42
43class DiskImage():
44 """
45 A Disk backed by a file.
46 """
47 def __init__(self, device, size):
48 self.size = size
49 self.device = device
50 self.created = False
51
52 def exists(self):
53 return os.path.exists(self.device)
54
55 def create(self):
56 if self.created:
57 return
58 # create sparse disk image
59 with open(self.device, 'w') as sparse:
60 os.ftruncate(sparse.fileno(), self.size)
61
62 self.created = True
63
64class DirectImageCreator:
65 """
66 Installs a system into a file containing a partitioned disk image.
67
68 DirectImageCreator is an advanced ImageCreator subclass; an image
69 file is formatted with a partition table, each partition created
70 from a rootfs or other OpenEmbedded build artifact and dd'ed into
71 the virtual disk. The disk image can subsequently be dd'ed onto
72 media and used on actual hardware.
73 """
74
75 def __init__(self, image_name, ksobj, oe_builddir, image_output_dir,
76 rootfs_dir, bootimg_dir, kernel_dir, native_sysroot,
77 compressor, bmap=False):
78 """
79 Initialize a DirectImageCreator instance.
80
81 This method takes the same arguments as ImageCreator.__init__()
82 """
83 self.name = image_name
84 self.outdir = image_output_dir
85 self.workdir = tempfile.mktemp(prefix='wic')
86 self.ks = ksobj
87
88 self.__image = None
89 self.__disks = {}
90 self.__disk_format = "direct"
91 self._disk_names = []
92 self.ptable_format = self.ks.bootloader.ptable
93
94 self.oe_builddir = oe_builddir
95 self.rootfs_dir = rootfs_dir
96 self.bootimg_dir = bootimg_dir
97 self.kernel_dir = kernel_dir
98 self.native_sysroot = native_sysroot
99 self.compressor = compressor
100 self.bmap = bmap
101
102 def _get_part_num(self, num, parts):
103 """calculate the real partition number, accounting for partitions not
104 in the partition table and logical partitions
105 """
106 realnum = 0
107 for pnum, part in enumerate(parts, 1):
108 if not part.no_table:
109 realnum += 1
110 if pnum == num:
111 if part.no_table:
112 return 0
113 if self.ptable_format == 'msdos' and realnum > 3:
114 # account for logical partition numbering, ex. sda5..
115 return realnum + 1
116 return realnum
117
118 def _write_fstab(self, image_rootfs):
119 """overriden to generate fstab (temporarily) in rootfs. This is called
120 from _create, make sure it doesn't get called from
121 BaseImage.create()
122 """
123 if not image_rootfs:
124 return
125
126 fstab_path = image_rootfs + "/etc/fstab"
127 if not os.path.isfile(fstab_path):
128 return
129
130 with open(fstab_path) as fstab:
131 fstab_lines = fstab.readlines()
132
133 if self._update_fstab(fstab_lines, self._get_parts()):
134 shutil.copyfile(fstab_path, fstab_path + ".orig")
135
136 with open(fstab_path, "w") as fstab:
137 fstab.writelines(fstab_lines)
138
139 return fstab_path
140
141 def _update_fstab(self, fstab_lines, parts):
142 """Assume partition order same as in wks"""
143 updated = False
144 for num, part in enumerate(parts, 1):
145 pnum = self._get_part_num(num, parts)
146 if not pnum or not part.mountpoint \
147 or part.mountpoint in ("/", "/boot"):
148 continue
149
150 # mmc device partitions are named mmcblk0p1, mmcblk0p2..
151 prefix = 'p' if part.disk.startswith('mmcblk') else ''
152 device_name = "/dev/%s%s%d" % (part.disk, prefix, pnum)
153
154 opts = part.fsopts if part.fsopts else "defaults"
155 line = "\t".join([device_name, part.mountpoint, part.fstype,
156 opts, "0", "0"]) + "\n"
157
158 fstab_lines.append(line)
159 updated = True
160
161 return updated
162
163 def set_bootimg_dir(self, bootimg_dir):
164 """
165 Accessor for bootimg_dir, the actual location used for the source
166 of the bootimg. Should be set by source plugins (only if they
167 change the default bootimg source) so the correct info gets
168 displayed for print_outimage_info().
169 """
170 self.bootimg_dir = bootimg_dir
171
172 def _get_parts(self):
173 if not self.ks:
174 raise CreatorError("Failed to get partition info, "
175 "please check your kickstart setting.")
176
177 # Set a default partition if no partition is given out
178 if not self.ks.partitions:
179 partstr = "part / --size 1900 --ondisk sda --fstype=ext3"
180 args = partstr.split()
181 part = self.ks.parse(args[1:])
182 if part not in self.ks.partitions:
183 self.ks.partitions.append(part)
184
185 # partitions list from kickstart file
186 return self.ks.partitions
187
188 def _full_name(self, name, extention):
189 """ Construct full file name for a file we generate. """
190 return "%s-%s.%s" % (self.name, name, extention)
191
192 def _full_path(self, path, name, extention):
193 """ Construct full file path to a file we generate. """
194 return os.path.join(path, self._full_name(name, extention))
195
196 def get_default_source_plugin(self):
197 """
198 The default source plugin i.e. the plugin that's consulted for
199 overall image generation tasks outside of any particular
200 partition. For convenience, we just hang it off the
201 bootloader handler since it's the one non-partition object in
202 any setup. By default the default plugin is set to the same
203 plugin as the /boot partition; since we hang it off the
204 bootloader object, the default can be explicitly set using the
205 --source bootloader param.
206 """
207 return self.ks.bootloader.source
208
209 #
210 # Actual implemention
211 #
212 def create(self):
213 """
214 For 'wic', we already have our build artifacts - we just create
215 filesystems from the artifacts directly and combine them into
216 a partitioned image.
217 """
218 parts = self._get_parts()
219
220 self._image = Image(self.native_sysroot)
221
222 disk_ids = {}
223 for num, part in enumerate(parts, 1):
224 # as a convenience, set source to the boot partition source
225 # instead of forcing it to be set via bootloader --source
226 if not self.ks.bootloader.source and part.mountpoint == "/boot":
227 self.ks.bootloader.source = part.source
228
229 # generate parition UUIDs
230 if not part.uuid and part.use_uuid:
231 if self.ptable_format == 'gpt':
232 part.uuid = str(uuid.uuid4())
233 else: # msdos partition table
234 if part.disk not in disk_ids:
235 disk_ids[part.disk] = int.from_bytes(os.urandom(4), 'little')
236 disk_id = disk_ids[part.disk]
237 part.uuid = '%0x-%02d' % (disk_id, self._get_part_num(num, parts))
238
239 fstab_path = self._write_fstab(self.rootfs_dir.get("ROOTFS_DIR"))
240
241 for part in parts:
242 # get rootfs size from bitbake variable if it's not set in .ks file
243 if not part.size:
244 # and if rootfs name is specified for the partition
245 image_name = self.rootfs_dir.get(part.rootfs_dir)
246 if image_name and os.path.sep not in image_name:
247 # Bitbake variable ROOTFS_SIZE is calculated in
248 # Image._get_rootfs_size method from meta/lib/oe/image.py
249 # using IMAGE_ROOTFS_SIZE, IMAGE_ROOTFS_ALIGNMENT,
250 # IMAGE_OVERHEAD_FACTOR and IMAGE_ROOTFS_EXTRA_SPACE
251 rsize_bb = get_bitbake_var('ROOTFS_SIZE', image_name)
252 if rsize_bb:
253 part.size = int(round(float(rsize_bb)))
254 # need to create the filesystems in order to get their
255 # sizes before we can add them and do the layout.
256 # Image.create() actually calls __format_disks() to create
257 # the disk images and carve out the partitions, then
258 # self.assemble() calls Image.assemble() which calls
259 # __write_partitition() for each partition to dd the fs
260 # into the partitions.
261 part.prepare(self, self.workdir, self.oe_builddir, self.rootfs_dir,
262 self.bootimg_dir, self.kernel_dir, self.native_sysroot)
263
264
265 self._image.add_partition(part.disk_size,
266 part.disk,
267 part.mountpoint,
268 part.source_file,
269 part.fstype,
270 part.label,
271 fsopts=part.fsopts,
272 boot=part.active,
273 align=part.align,
274 no_table=part.no_table,
275 part_type=part.part_type,
276 uuid=part.uuid,
277 system_id=part.system_id)
278
279 if fstab_path:
280 shutil.move(fstab_path + ".orig", fstab_path)
281
282 self._image.layout_partitions(self.ptable_format)
283
284 for disk_name, disk in self._image.disks.items():
285 full_path = self._full_path(self.workdir, disk_name, "direct")
286 msger.debug("Adding disk %s as %s with size %s bytes" \
287 % (disk_name, full_path, disk['min_size']))
288 disk_obj = DiskImage(full_path, disk['min_size'])
289 #self._disks[disk_name] = disk_obj
290 self._image.add_disk(disk_name, disk_obj, disk_ids.get(disk_name))
291
292 self._image.create()
293
294 def assemble(self):
295 """
296 Assemble partitions into disk image(s)
297 """
298 for disk_name, disk in self._image.disks.items():
299 full_path = self._full_path(self.workdir, disk_name, "direct")
300 msger.debug("Assembling disk %s as %s with size %s bytes" \
301 % (disk_name, full_path, disk['min_size']))
302 self._image.assemble(full_path)
303
304 def finalize(self):
305 """
306 Finalize the disk image.
307
308 For example, prepare the image to be bootable by e.g.
309 creating and installing a bootloader configuration.
310 """
311 source_plugin = self.get_default_source_plugin()
312 if source_plugin:
313 self._source_methods = pluginmgr.get_source_plugin_methods(source_plugin, disk_methods)
314 for disk_name, disk in self._image.disks.items():
315 self._source_methods["do_install_disk"](disk, disk_name, self,
316 self.workdir,
317 self.oe_builddir,
318 self.bootimg_dir,
319 self.kernel_dir,
320 self.native_sysroot)
321
322 for disk_name, disk in self._image.disks.items():
323 full_path = self._full_path(self.workdir, disk_name, "direct")
324 # Generate .bmap
325 if self.bmap:
326 msger.debug("Generating bmap file for %s" % disk_name)
327 exec_native_cmd("bmaptool create %s -o %s.bmap" % (full_path, full_path),
328 self.native_sysroot)
329 # Compress the image
330 if self.compressor:
331 msger.debug("Compressing disk %s with %s" % (disk_name, self.compressor))
332 exec_cmd("%s %s" % (self.compressor, full_path))
333
334 def print_outimage_info(self):
335 """
336 Print the image(s) and artifacts used, for the user.
337 """
338 msg = "The new image(s) can be found here:\n"
339
340 parts = self._get_parts()
341
342 for disk_name in self._image.disks:
343 extension = "direct" + {"gzip": ".gz",
344 "bzip2": ".bz2",
345 "xz": ".xz",
346 "": ""}.get(self.compressor)
347 full_path = self._full_path(self.outdir, disk_name, extension)
348 msg += ' %s\n\n' % full_path
349
350 msg += 'The following build artifacts were used to create the image(s):\n'
351 for part in parts:
352 if part.rootfs_dir is None:
353 continue
354 if part.mountpoint == '/':
355 suffix = ':'
356 else:
357 suffix = '["%s"]:' % (part.mountpoint or part.label)
358 msg += ' ROOTFS_DIR%s%s\n' % (suffix.ljust(20), part.rootfs_dir)
359
360 msg += ' BOOTIMG_DIR: %s\n' % self.bootimg_dir
361 msg += ' KERNEL_DIR: %s\n' % self.kernel_dir
362 msg += ' NATIVE_SYSROOT: %s\n' % self.native_sysroot
363
364 msger.info(msg)
365
366 @property
367 def rootdev(self):
368 """
369 Get root device name to use as a 'root' parameter
370 in kernel command line.
371
372 Assume partition order same as in wks
373 """
374 parts = self._get_parts()
375 for num, part in enumerate(parts, 1):
376 if part.mountpoint == "/":
377 if part.uuid:
378 return "PARTUUID=%s" % part.uuid
379 else:
380 suffix = 'p' if part.disk.startswith('mmcblk') else ''
381 pnum = self._get_part_num(num, parts)
382 return "/dev/%s%s%-d" % (part.disk, suffix, pnum)
383
384 def cleanup(self):
385 if self._image:
386 try:
387 self._image.cleanup()
388 except ImageError as err:
389 msger.warning("%s" % err)
390
391 # Move results to the output dir
392 if not os.path.exists(self.outdir):
393 os.makedirs(self.outdir)
394
395 for fname in os.listdir(self.workdir):
396 path = os.path.join(self.workdir, fname)
397 if os.path.isfile(path):
398 shutil.move(path, os.path.join(self.outdir, fname))
399
400 # remove work directory
401 shutil.rmtree(self.workdir, ignore_errors=True)
402