summaryrefslogtreecommitdiffstats
path: root/scripts/lib/mic/imager
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/lib/mic/imager')
-rw-r--r--scripts/lib/mic/imager/__init__.py0
-rw-r--r--scripts/lib/mic/imager/baseimager.py1265
-rw-r--r--scripts/lib/mic/imager/direct.py472
-rw-r--r--scripts/lib/mic/imager/fs.py99
-rw-r--r--scripts/lib/mic/imager/livecd.py750
-rw-r--r--scripts/lib/mic/imager/liveusb.py308
-rw-r--r--scripts/lib/mic/imager/loop.py418
-rw-r--r--scripts/lib/mic/imager/raw.py501
8 files changed, 3813 insertions, 0 deletions
diff --git a/scripts/lib/mic/imager/__init__.py b/scripts/lib/mic/imager/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/scripts/lib/mic/imager/__init__.py
diff --git a/scripts/lib/mic/imager/baseimager.py b/scripts/lib/mic/imager/baseimager.py
new file mode 100644
index 0000000000..4d6be29a0e
--- /dev/null
+++ b/scripts/lib/mic/imager/baseimager.py
@@ -0,0 +1,1265 @@
1#!/usr/bin/python -tt
2#
3# Copyright (c) 2007 Red Hat Inc.
4# Copyright (c) 2009, 2010, 2011 Intel, Inc.
5#
6# This program is free software; you can redistribute it and/or modify it
7# under the terms of the GNU General Public License as published by the Free
8# Software Foundation; version 2 of the License
9#
10# This program is distributed in the hope that it will be useful, but
11# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13# for more details.
14#
15# You should have received a copy of the GNU General Public License along
16# with this program; if not, write to the Free Software Foundation, Inc., 59
17# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
19from __future__ import with_statement
20import os, sys
21import stat
22import tempfile
23import shutil
24import subprocess
25import re
26import tarfile
27import glob
28
29import rpm
30
31from mic import kickstart
32from mic import msger
33from mic.utils.errors import CreatorError, Abort
34from mic.utils import misc, grabber, runner, fs_related as fs
35
36class BaseImageCreator(object):
37 """Installs a system to a chroot directory.
38
39 ImageCreator is the simplest creator class available; it will install and
40 configure a system image according to the supplied kickstart file.
41
42 e.g.
43
44 import mic.imgcreate as imgcreate
45 ks = imgcreate.read_kickstart("foo.ks")
46 imgcreate.ImageCreator(ks, "foo").create()
47
48 """
49
50 def __del__(self):
51 self.cleanup()
52
53 def __init__(self, createopts = None, pkgmgr = None):
54 """Initialize an ImageCreator instance.
55
56 ks -- a pykickstart.KickstartParser instance; this instance will be
57 used to drive the install by e.g. providing the list of packages
58 to be installed, the system configuration and %post scripts
59
60 name -- a name for the image; used for e.g. image filenames or
61 filesystem labels
62 """
63
64 self.pkgmgr = pkgmgr
65
66 self.__builddir = None
67 self.__bindmounts = []
68
69 self.ks = None
70 self.name = "target"
71 self.tmpdir = "/var/tmp/wic"
72 self.cachedir = "/var/tmp/wic/cache"
73 self.workdir = "/var/tmp/wic/build"
74
75 self.destdir = "."
76 self.installerfw_prefix = "INSTALLERFW_"
77 self.target_arch = "noarch"
78 self._local_pkgs_path = None
79 self.pack_to = None
80 self.repourl = {}
81
82 # If the kernel is save to the destdir when copy_kernel cmd is called.
83 self._need_copy_kernel = False
84 # setup tmpfs tmpdir when enabletmpfs is True
85 self.enabletmpfs = False
86
87 if createopts:
88 # Mapping table for variables that have different names.
89 optmap = {"pkgmgr" : "pkgmgr_name",
90 "outdir" : "destdir",
91 "arch" : "target_arch",
92 "local_pkgs_path" : "_local_pkgs_path",
93 "copy_kernel" : "_need_copy_kernel",
94 }
95
96 # update setting from createopts
97 for key in createopts.keys():
98 if key in optmap:
99 option = optmap[key]
100 else:
101 option = key
102 setattr(self, option, createopts[key])
103
104 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
105
106 if 'release' in createopts and createopts['release']:
107 self.name = createopts['release'] + '_' + self.name
108
109 if self.pack_to:
110 if '@NAME@' in self.pack_to:
111 self.pack_to = self.pack_to.replace('@NAME@', self.name)
112 (tar, ext) = os.path.splitext(self.pack_to)
113 if ext in (".gz", ".bz2") and tar.endswith(".tar"):
114 ext = ".tar" + ext
115 if ext not in misc.pack_formats:
116 self.pack_to += ".tar"
117
118 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
119
120 # Output image file names
121 self.outimage = []
122
123 # A flag to generate checksum
124 self._genchecksum = False
125
126 self._alt_initrd_name = None
127
128 self._recording_pkgs = []
129
130 # available size in root fs, init to 0
131 self._root_fs_avail = 0
132
133 # Name of the disk image file that is created.
134 self._img_name = None
135
136 self.image_format = None
137
138 # Save qemu emulator file name in order to clean up it finally
139 self.qemu_emulator = None
140
141 # No ks provided when called by convertor, so skip the dependency check
142 if self.ks:
143 # If we have btrfs partition we need to check necessary tools
144 for part in self.ks.handler.partition.partitions:
145 if part.fstype and part.fstype == "btrfs":
146 self._dep_checks.append("mkfs.btrfs")
147 break
148
149 if self.target_arch and self.target_arch.startswith("arm"):
150 for dep in self._dep_checks:
151 if dep == "extlinux":
152 self._dep_checks.remove(dep)
153
154 if not os.path.exists("/usr/bin/qemu-arm") or \
155 not misc.is_statically_linked("/usr/bin/qemu-arm"):
156 self._dep_checks.append("qemu-arm-static")
157
158 if os.path.exists("/proc/sys/vm/vdso_enabled"):
159 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
160 vdso_value = vdso_fh.read().strip()
161 vdso_fh.close()
162 if (int)(vdso_value) == 1:
163 msger.warning("vdso is enabled on your host, which might "
164 "cause problems with arm emulations.\n"
165 "\tYou can disable vdso with following command before "
166 "starting image build:\n"
167 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
168
169 # make sure the specified tmpdir and cachedir exist
170 if not os.path.exists(self.tmpdir):
171 os.makedirs(self.tmpdir)
172 if not os.path.exists(self.cachedir):
173 os.makedirs(self.cachedir)
174
175
176 #
177 # Properties
178 #
179 def __get_instroot(self):
180 if self.__builddir is None:
181 raise CreatorError("_instroot is not valid before calling mount()")
182 return self.__builddir + "/install_root"
183 _instroot = property(__get_instroot)
184 """The location of the install root directory.
185
186 This is the directory into which the system is installed. Subclasses may
187 mount a filesystem image here or copy files to/from here.
188
189 Note, this directory does not exist before ImageCreator.mount() is called.
190
191 Note also, this is a read-only attribute.
192
193 """
194
195 def __get_outdir(self):
196 if self.__builddir is None:
197 raise CreatorError("_outdir is not valid before calling mount()")
198 return self.__builddir + "/out"
199 _outdir = property(__get_outdir)
200 """The staging location for the final image.
201
202 This is where subclasses should stage any files that are part of the final
203 image. ImageCreator.package() will copy any files found here into the
204 requested destination directory.
205
206 Note, this directory does not exist before ImageCreator.mount() is called.
207
208 Note also, this is a read-only attribute.
209
210 """
211
212
213 #
214 # Hooks for subclasses
215 #
216 def _mount_instroot(self, base_on = None):
217 """Mount or prepare the install root directory.
218
219 This is the hook where subclasses may prepare the install root by e.g.
220 mounting creating and loopback mounting a filesystem image to
221 _instroot.
222
223 There is no default implementation.
224
225 base_on -- this is the value passed to mount() and can be interpreted
226 as the subclass wishes; it might e.g. be the location of
227 a previously created ISO containing a system image.
228
229 """
230 pass
231
232 def _unmount_instroot(self):
233 """Undo anything performed in _mount_instroot().
234
235 This is the hook where subclasses must undo anything which was done
236 in _mount_instroot(). For example, if a filesystem image was mounted
237 onto _instroot, it should be unmounted here.
238
239 There is no default implementation.
240
241 """
242 pass
243
244 def _create_bootconfig(self):
245 """Configure the image so that it's bootable.
246
247 This is the hook where subclasses may prepare the image for booting by
248 e.g. creating an initramfs and bootloader configuration.
249
250 This hook is called while the install root is still mounted, after the
251 packages have been installed and the kickstart configuration has been
252 applied, but before the %post scripts have been executed.
253
254 There is no default implementation.
255
256 """
257 pass
258
259 def _stage_final_image(self):
260 """Stage the final system image in _outdir.
261
262 This is the hook where subclasses should place the image in _outdir
263 so that package() can copy it to the requested destination directory.
264
265 By default, this moves the install root into _outdir.
266
267 """
268 shutil.move(self._instroot, self._outdir + "/" + self.name)
269
270 def get_installed_packages(self):
271 return self._pkgs_content.keys()
272
273 def _save_recording_pkgs(self, destdir):
274 """Save the list or content of installed packages to file.
275 """
276 pkgs = self._pkgs_content.keys()
277 pkgs.sort() # inplace op
278
279 if not os.path.exists(destdir):
280 os.makedirs(destdir)
281
282 content = None
283 if 'vcs' in self._recording_pkgs:
284 vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
285 content = '\n'.join(sorted(vcslst))
286 elif 'name' in self._recording_pkgs:
287 content = '\n'.join(pkgs)
288 if content:
289 namefile = os.path.join(destdir, self.name + '.packages')
290 f = open(namefile, "w")
291 f.write(content)
292 f.close()
293 self.outimage.append(namefile);
294
295 # if 'content', save more details
296 if 'content' in self._recording_pkgs:
297 contfile = os.path.join(destdir, self.name + '.files')
298 f = open(contfile, "w")
299
300 for pkg in pkgs:
301 content = pkg + '\n'
302
303 pkgcont = self._pkgs_content[pkg]
304 content += ' '
305 content += '\n '.join(pkgcont)
306 content += '\n'
307
308 content += '\n'
309 f.write(content)
310 f.close()
311 self.outimage.append(contfile)
312
313 if 'license' in self._recording_pkgs:
314 licensefile = os.path.join(destdir, self.name + '.license')
315 f = open(licensefile, "w")
316
317 f.write('Summary:\n')
318 for license in reversed(sorted(self._pkgs_license, key=\
319 lambda license: len(self._pkgs_license[license]))):
320 f.write(" - %s: %s\n" \
321 % (license, len(self._pkgs_license[license])))
322
323 f.write('\nDetails:\n')
324 for license in reversed(sorted(self._pkgs_license, key=\
325 lambda license: len(self._pkgs_license[license]))):
326 f.write(" - %s:\n" % (license))
327 for pkg in sorted(self._pkgs_license[license]):
328 f.write(" - %s\n" % (pkg))
329 f.write('\n')
330
331 f.close()
332 self.outimage.append(licensefile)
333
334 def _get_required_packages(self):
335 """Return a list of required packages.
336
337 This is the hook where subclasses may specify a set of packages which
338 it requires to be installed.
339
340 This returns an empty list by default.
341
342 Note, subclasses should usually chain up to the base class
343 implementation of this hook.
344
345 """
346 return []
347
348 def _get_excluded_packages(self):
349 """Return a list of excluded packages.
350
351 This is the hook where subclasses may specify a set of packages which
352 it requires _not_ to be installed.
353
354 This returns an empty list by default.
355
356 Note, subclasses should usually chain up to the base class
357 implementation of this hook.
358
359 """
360 return []
361
362 def _get_local_packages(self):
363 """Return a list of rpm path to be local installed.
364
365 This is the hook where subclasses may specify a set of rpms which
366 it requires to be installed locally.
367
368 This returns an empty list by default.
369
370 Note, subclasses should usually chain up to the base class
371 implementation of this hook.
372
373 """
374 if self._local_pkgs_path:
375 if os.path.isdir(self._local_pkgs_path):
376 return glob.glob(
377 os.path.join(self._local_pkgs_path, '*.rpm'))
378 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
379 return [self._local_pkgs_path]
380
381 return []
382
383 def _get_fstab(self):
384 """Return the desired contents of /etc/fstab.
385
386 This is the hook where subclasses may specify the contents of
387 /etc/fstab by returning a string containing the desired contents.
388
389 A sensible default implementation is provided.
390
391 """
392 s = "/dev/root / %s %s 0 0\n" \
393 % (self._fstype,
394 "defaults,noatime" if not self._fsopts else self._fsopts)
395 s += self._get_fstab_special()
396 return s
397
398 def _get_fstab_special(self):
399 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
400 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
401 s += "proc /proc proc defaults 0 0\n"
402 s += "sysfs /sys sysfs defaults 0 0\n"
403 return s
404
405 def _set_part_env(self, pnum, prop, value):
406 """ This is a helper function which generates an environment variable
407 for a property "prop" with value "value" of a partition number "pnum".
408
409 The naming convention is:
410 * Variables start with INSTALLERFW_PART
411 * Then goes the partition number, the order is the same as
412 specified in the KS file
413 * Then goes the property name
414 """
415
416 if value == None:
417 value = ""
418 else:
419 value = str(value)
420
421 name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
422 return { name : value }
423
424 def _get_post_scripts_env(self, in_chroot):
425 """Return an environment dict for %post scripts.
426
427 This is the hook where subclasses may specify some environment
428 variables for %post scripts by return a dict containing the desired
429 environment.
430
431 in_chroot -- whether this %post script is to be executed chroot()ed
432 into _instroot.
433 """
434
435 env = {}
436 pnum = 0
437
438 for p in kickstart.get_partitions(self.ks):
439 env.update(self._set_part_env(pnum, "SIZE", p.size))
440 env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
441 env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
442 env.update(self._set_part_env(pnum, "LABEL", p.label))
443 env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts))
444 env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
445 env.update(self._set_part_env(pnum, "ALIGN", p.align))
446 env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
447 env.update(self._set_part_env(pnum, "DEVNODE",
448 "/dev/%s%d" % (p.disk, pnum + 1)))
449 pnum += 1
450
451 # Count of paritions
452 env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
453
454 # Partition table format
455 ptable_format = self.ks.handler.bootloader.ptable
456 env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
457
458 # The kerned boot parameters
459 kernel_opts = self.ks.handler.bootloader.appendLine
460 env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
461
462 # Name of the distribution
463 env[self.installerfw_prefix + "DISTRO_NAME"] = self.distro_name
464
465 # Name of the image creation tool
466 env[self.installerfw_prefix + "INSTALLER_NAME"] = "wic"
467
468 # The real current location of the mounted file-systems
469 if in_chroot:
470 mount_prefix = "/"
471 else:
472 mount_prefix = self._instroot
473 env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
474
475 # These are historical variables which lack the common name prefix
476 if not in_chroot:
477 env["INSTALL_ROOT"] = self._instroot
478 env["IMG_NAME"] = self._name
479
480 return env
481
482 def __get_imgname(self):
483 return self.name
484 _name = property(__get_imgname)
485 """The name of the image file.
486
487 """
488
489 def _get_kernel_versions(self):
490 """Return a dict detailing the available kernel types/versions.
491
492 This is the hook where subclasses may override what kernel types and
493 versions should be available for e.g. creating the booloader
494 configuration.
495
496 A dict should be returned mapping the available kernel types to a list
497 of the available versions for those kernels.
498
499 The default implementation uses rpm to iterate over everything
500 providing 'kernel', finds /boot/vmlinuz-* and returns the version
501 obtained from the vmlinuz filename. (This can differ from the kernel
502 RPM's n-v-r in the case of e.g. xen)
503
504 """
505 def get_kernel_versions(instroot):
506 ret = {}
507 versions = set()
508 files = glob.glob(instroot + "/boot/vmlinuz-*")
509 for file in files:
510 version = os.path.basename(file)[8:]
511 if version is None:
512 continue
513 versions.add(version)
514 ret["kernel"] = list(versions)
515 return ret
516
517 def get_version(header):
518 version = None
519 for f in header['filenames']:
520 if f.startswith('/boot/vmlinuz-'):
521 version = f[14:]
522 return version
523
524 if self.ks is None:
525 return get_kernel_versions(self._instroot)
526
527 ts = rpm.TransactionSet(self._instroot)
528
529 ret = {}
530 for header in ts.dbMatch('provides', 'kernel'):
531 version = get_version(header)
532 if version is None:
533 continue
534
535 name = header['name']
536 if not name in ret:
537 ret[name] = [version]
538 elif not version in ret[name]:
539 ret[name].append(version)
540
541 return ret
542
543
544 #
545 # Helpers for subclasses
546 #
547 def _do_bindmounts(self):
548 """Mount various system directories onto _instroot.
549
550 This method is called by mount(), but may also be used by subclasses
551 in order to re-mount the bindmounts after modifying the underlying
552 filesystem.
553
554 """
555 for b in self.__bindmounts:
556 b.mount()
557
558 def _undo_bindmounts(self):
559 """Unmount the bind-mounted system directories from _instroot.
560
561 This method is usually only called by unmount(), but may also be used
562 by subclasses in order to gain access to the filesystem obscured by
563 the bindmounts - e.g. in order to create device nodes on the image
564 filesystem.
565
566 """
567 self.__bindmounts.reverse()
568 for b in self.__bindmounts:
569 b.unmount()
570
571 def _chroot(self):
572 """Chroot into the install root.
573
574 This method may be used by subclasses when executing programs inside
575 the install root e.g.
576
577 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
578
579 """
580 os.chroot(self._instroot)
581 os.chdir("/")
582
583 def _mkdtemp(self, prefix = "tmp-"):
584 """Create a temporary directory.
585
586 This method may be used by subclasses to create a temporary directory
587 for use in building the final image - e.g. a subclass might create
588 a temporary directory in order to bundle a set of files into a package.
589
590 The subclass may delete this directory if it wishes, but it will be
591 automatically deleted by cleanup().
592
593 The absolute path to the temporary directory is returned.
594
595 Note, this method should only be called after mount() has been called.
596
597 prefix -- a prefix which should be used when creating the directory;
598 defaults to "tmp-".
599
600 """
601 self.__ensure_builddir()
602 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
603
604 def _mkstemp(self, prefix = "tmp-"):
605 """Create a temporary file.
606
607 This method may be used by subclasses to create a temporary file
608 for use in building the final image - e.g. a subclass might need
609 a temporary location to unpack a compressed file.
610
611 The subclass may delete this file if it wishes, but it will be
612 automatically deleted by cleanup().
613
614 A tuple containing a file descriptor (returned from os.open() and the
615 absolute path to the temporary directory is returned.
616
617 Note, this method should only be called after mount() has been called.
618
619 prefix -- a prefix which should be used when creating the file;
620 defaults to "tmp-".
621
622 """
623 self.__ensure_builddir()
624 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
625
626 def _mktemp(self, prefix = "tmp-"):
627 """Create a temporary file.
628
629 This method simply calls _mkstemp() and closes the returned file
630 descriptor.
631
632 The absolute path to the temporary file is returned.
633
634 Note, this method should only be called after mount() has been called.
635
636 prefix -- a prefix which should be used when creating the file;
637 defaults to "tmp-".
638
639 """
640
641 (f, path) = self._mkstemp(prefix)
642 os.close(f)
643 return path
644
645
646 #
647 # Actual implementation
648 #
649 def __ensure_builddir(self):
650 if not self.__builddir is None:
651 return
652
653 try:
654 self.workdir = os.path.join(self.tmpdir, "build")
655 if not os.path.exists(self.workdir):
656 os.makedirs(self.workdir)
657 self.__builddir = tempfile.mkdtemp(dir = self.workdir,
658 prefix = "imgcreate-")
659 except OSError, (err, msg):
660 raise CreatorError("Failed create build directory in %s: %s" %
661 (self.tmpdir, msg))
662
663 def get_cachedir(self, cachedir = None):
664 if self.cachedir:
665 return self.cachedir
666
667 self.__ensure_builddir()
668 if cachedir:
669 self.cachedir = cachedir
670 else:
671 self.cachedir = self.__builddir + "/wic-cache"
672 fs.makedirs(self.cachedir)
673 return self.cachedir
674
675 def __sanity_check(self):
676 """Ensure that the config we've been given is sane."""
677 if not (kickstart.get_packages(self.ks) or
678 kickstart.get_groups(self.ks)):
679 raise CreatorError("No packages or groups specified")
680
681 kickstart.convert_method_to_repo(self.ks)
682
683 if not kickstart.get_repos(self.ks):
684 raise CreatorError("No repositories specified")
685
686 def __write_fstab(self):
687 fstab_contents = self._get_fstab()
688 if fstab_contents:
689 fstab = open(self._instroot + "/etc/fstab", "w")
690 fstab.write(fstab_contents)
691 fstab.close()
692
693 def __create_minimal_dev(self):
694 """Create a minimal /dev so that we don't corrupt the host /dev"""
695 origumask = os.umask(0000)
696 devices = (('null', 1, 3, 0666),
697 ('urandom',1, 9, 0666),
698 ('random', 1, 8, 0666),
699 ('full', 1, 7, 0666),
700 ('ptmx', 5, 2, 0666),
701 ('tty', 5, 0, 0666),
702 ('zero', 1, 5, 0666))
703
704 links = (("/proc/self/fd", "/dev/fd"),
705 ("/proc/self/fd/0", "/dev/stdin"),
706 ("/proc/self/fd/1", "/dev/stdout"),
707 ("/proc/self/fd/2", "/dev/stderr"))
708
709 for (node, major, minor, perm) in devices:
710 if not os.path.exists(self._instroot + "/dev/" + node):
711 os.mknod(self._instroot + "/dev/" + node,
712 perm | stat.S_IFCHR,
713 os.makedev(major,minor))
714
715 for (src, dest) in links:
716 if not os.path.exists(self._instroot + dest):
717 os.symlink(src, self._instroot + dest)
718
719 os.umask(origumask)
720
721 def __setup_tmpdir(self):
722 if not self.enabletmpfs:
723 return
724
725 runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
726
727 def __clean_tmpdir(self):
728 if not self.enabletmpfs:
729 return
730
731 runner.show('umount -l %s' % self.workdir)
732
733 def mount(self, base_on = None, cachedir = None):
734 """Setup the target filesystem in preparation for an install.
735
736 This function sets up the filesystem which the ImageCreator will
737 install into and configure. The ImageCreator class merely creates an
738 install root directory, bind mounts some system directories (e.g. /dev)
739 and writes out /etc/fstab. Other subclasses may also e.g. create a
740 sparse file, format it and loopback mount it to the install root.
741
742 base_on -- a previous install on which to base this install; defaults
743 to None, causing a new image to be created
744
745 cachedir -- a directory in which to store the Yum cache; defaults to
746 None, causing a new cache to be created; by setting this
747 to another directory, the same cache can be reused across
748 multiple installs.
749
750 """
751 self.__setup_tmpdir()
752 self.__ensure_builddir()
753
754 self._mount_instroot(base_on)
755
756 def unmount(self):
757 """Unmounts the target filesystem.
758
759 The ImageCreator class detaches the system from the install root, but
760 other subclasses may also detach the loopback mounted filesystem image
761 from the install root.
762
763 """
764 self._unmount_instroot()
765
766
767 def cleanup(self):
768 """Unmounts the target filesystem and deletes temporary files.
769
770 This method calls unmount() and then deletes any temporary files and
771 directories that were created on the host system while building the
772 image.
773
774 Note, make sure to call this method once finished with the creator
775 instance in order to ensure no stale files are left on the host e.g.:
776
777 creator = ImageCreator(ks, name)
778 try:
779 creator.create()
780 finally:
781 creator.cleanup()
782
783 """
784 if not self.__builddir:
785 return
786
787 self.unmount()
788
789 shutil.rmtree(self.__builddir, ignore_errors = True)
790 self.__builddir = None
791
792 self.__clean_tmpdir()
793
794 def __is_excluded_pkg(self, pkg):
795 if pkg in self._excluded_pkgs:
796 self._excluded_pkgs.remove(pkg)
797 return True
798
799 for xpkg in self._excluded_pkgs:
800 if xpkg.endswith('*'):
801 if pkg.startswith(xpkg[:-1]):
802 return True
803 elif xpkg.startswith('*'):
804 if pkg.endswith(xpkg[1:]):
805 return True
806
807 return None
808
809 def __select_packages(self, pkg_manager):
810 skipped_pkgs = []
811 for pkg in self._required_pkgs:
812 e = pkg_manager.selectPackage(pkg)
813 if e:
814 if kickstart.ignore_missing(self.ks):
815 skipped_pkgs.append(pkg)
816 elif self.__is_excluded_pkg(pkg):
817 skipped_pkgs.append(pkg)
818 else:
819 raise CreatorError("Failed to find package '%s' : %s" %
820 (pkg, e))
821
822 for pkg in skipped_pkgs:
823 msger.warning("Skipping missing package '%s'" % (pkg,))
824
825 def __select_groups(self, pkg_manager):
826 skipped_groups = []
827 for group in self._required_groups:
828 e = pkg_manager.selectGroup(group.name, group.include)
829 if e:
830 if kickstart.ignore_missing(self.ks):
831 skipped_groups.append(group)
832 else:
833 raise CreatorError("Failed to find group '%s' : %s" %
834 (group.name, e))
835
836 for group in skipped_groups:
837 msger.warning("Skipping missing group '%s'" % (group.name,))
838
839 def __deselect_packages(self, pkg_manager):
840 for pkg in self._excluded_pkgs:
841 pkg_manager.deselectPackage(pkg)
842
843 def __localinst_packages(self, pkg_manager):
844 for rpm_path in self._get_local_packages():
845 pkg_manager.installLocal(rpm_path)
846
847 def __preinstall_packages(self, pkg_manager):
848 if not self.ks:
849 return
850
851 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
852 for pkg in self._preinstall_pkgs:
853 pkg_manager.preInstall(pkg)
854
855 def __attachment_packages(self, pkg_manager):
856 if not self.ks:
857 return
858
859 self._attachment = []
860 for item in kickstart.get_attachment(self.ks):
861 if item.startswith('/'):
862 fpaths = os.path.join(self._instroot, item.lstrip('/'))
863 for fpath in glob.glob(fpaths):
864 self._attachment.append(fpath)
865 continue
866
867 filelist = pkg_manager.getFilelist(item)
868 if filelist:
869 # found rpm in rootfs
870 for pfile in pkg_manager.getFilelist(item):
871 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
872 self._attachment.append(fpath)
873 continue
874
875 # try to retrieve rpm file
876 (url, proxies) = pkg_manager.package_url(item)
877 if not url:
878 msger.warning("Can't get url from repo for %s" % item)
879 continue
880 fpath = os.path.join(self.cachedir, os.path.basename(url))
881 if not os.path.exists(fpath):
882 # download pkgs
883 try:
884 fpath = grabber.myurlgrab(url, fpath, proxies, None)
885 except CreatorError:
886 raise
887
888 tmpdir = self._mkdtemp()
889 misc.extract_rpm(fpath, tmpdir)
890 for (root, dirs, files) in os.walk(tmpdir):
891 for fname in files:
892 fpath = os.path.join(root, fname)
893 self._attachment.append(fpath)
894
895 def install(self, repo_urls=None):
896 """Install packages into the install root.
897
898 This function installs the packages listed in the supplied kickstart
899 into the install root. By default, the packages are installed from the
900 repository URLs specified in the kickstart.
901
902 repo_urls -- a dict which maps a repository name to a repository URL;
903 if supplied, this causes any repository URLs specified in
904 the kickstart to be overridden.
905
906 """
907
908 # initialize pkg list to install
909 if self.ks:
910 self.__sanity_check()
911
912 self._required_pkgs = \
913 kickstart.get_packages(self.ks, self._get_required_packages())
914 self._excluded_pkgs = \
915 kickstart.get_excluded(self.ks, self._get_excluded_packages())
916 self._required_groups = kickstart.get_groups(self.ks)
917 else:
918 self._required_pkgs = None
919 self._excluded_pkgs = None
920 self._required_groups = None
921
922 pkg_manager = self.get_pkg_manager()
923 pkg_manager.setup()
924
925 if hasattr(self, 'install_pkgs') and self.install_pkgs:
926 if 'debuginfo' in self.install_pkgs:
927 pkg_manager.install_debuginfo = True
928
929 for repo in kickstart.get_repos(self.ks, repo_urls):
930 (name, baseurl, mirrorlist, inc, exc,
931 proxy, proxy_username, proxy_password, debuginfo,
932 source, gpgkey, disable, ssl_verify, nocache,
933 cost, priority) = repo
934
935 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
936 proxy_username, proxy_password, inc, exc, ssl_verify,
937 nocache, cost, priority)
938
939 if kickstart.exclude_docs(self.ks):
940 rpm.addMacro("_excludedocs", "1")
941 rpm.addMacro("_dbpath", "/var/lib/rpm")
942 rpm.addMacro("__file_context_path", "%{nil}")
943 if kickstart.inst_langs(self.ks) != None:
944 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
945
946 try:
947 self.__preinstall_packages(pkg_manager)
948 self.__select_packages(pkg_manager)
949 self.__select_groups(pkg_manager)
950 self.__deselect_packages(pkg_manager)
951 self.__localinst_packages(pkg_manager)
952
953 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
954 checksize = self._root_fs_avail
955 if checksize:
956 checksize -= BOOT_SAFEGUARD
957 if self.target_arch:
958 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
959 pkg_manager.runInstall(checksize)
960 except CreatorError, e:
961 raise
962 except KeyboardInterrupt:
963 raise
964 else:
965 self._pkgs_content = pkg_manager.getAllContent()
966 self._pkgs_license = pkg_manager.getPkgsLicense()
967 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
968 self.__attachment_packages(pkg_manager)
969 finally:
970 pkg_manager.close()
971
972 # hook post install
973 self.postinstall()
974
975 # do some clean up to avoid lvm info leakage. this sucks.
976 for subdir in ("cache", "backup", "archive"):
977 lvmdir = self._instroot + "/etc/lvm/" + subdir
978 try:
979 for f in os.listdir(lvmdir):
980 os.unlink(lvmdir + "/" + f)
981 except:
982 pass
983
984 def postinstall(self):
985 self.copy_attachment()
986
987 def __run_post_scripts(self):
988 msger.info("Running scripts ...")
989 if os.path.exists(self._instroot + "/tmp"):
990 shutil.rmtree(self._instroot + "/tmp")
991 os.mkdir (self._instroot + "/tmp", 0755)
992 for s in kickstart.get_post_scripts(self.ks):
993 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
994 dir = self._instroot + "/tmp")
995
996 s.script = s.script.replace("\r", "")
997 os.write(fd, s.script)
998 os.close(fd)
999 os.chmod(path, 0700)
1000
1001 env = self._get_post_scripts_env(s.inChroot)
1002
1003 if not s.inChroot:
1004 preexec = None
1005 script = path
1006 else:
1007 preexec = self._chroot
1008 script = "/tmp/" + os.path.basename(path)
1009
1010 try:
1011 try:
1012 subprocess.call([s.interp, script],
1013 preexec_fn = preexec,
1014 env = env,
1015 stdout = sys.stdout,
1016 stderr = sys.stderr)
1017 except OSError, (err, msg):
1018 raise CreatorError("Failed to execute %%post script "
1019 "with '%s' : %s" % (s.interp, msg))
1020 finally:
1021 os.unlink(path)
1022
1023 def __save_repo_keys(self, repodata):
1024 if not repodata:
1025 return None
1026
1027 gpgkeydir = "/etc/pki/rpm-gpg"
1028 fs.makedirs(self._instroot + gpgkeydir)
1029 for repo in repodata:
1030 if repo["repokey"]:
1031 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1032 shutil.copy(repo["repokey"], self._instroot + repokey)
1033
1034 def configure(self, repodata = None):
1035 """Configure the system image according to the kickstart.
1036
1037 This method applies the (e.g. keyboard or network) configuration
1038 specified in the kickstart and executes the kickstart %post scripts.
1039
1040 If necessary, it also prepares the image to be bootable by e.g.
1041 creating an initrd and bootloader configuration.
1042
1043 """
1044 ksh = self.ks.handler
1045
1046 msger.info('Applying configurations ...')
1047 try:
1048 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1049 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1050 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1051 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1052 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1053 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1054 kickstart.UserConfig(self._instroot).apply(ksh.user)
1055 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1056 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1057 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1058 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1059 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1060 self.__save_repo_keys(repodata)
1061 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1062 except:
1063 msger.warning("Failed to apply configuration to image")
1064 raise
1065
1066 self._create_bootconfig()
1067 self.__run_post_scripts()
1068
1069 def launch_shell(self, launch):
1070 """Launch a shell in the install root.
1071
1072 This method is launches a bash shell chroot()ed in the install root;
1073 this can be useful for debugging.
1074
1075 """
1076 if launch:
1077 msger.info("Launching shell. Exit to continue.")
1078 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1079
1080 def do_genchecksum(self, image_name):
1081 if not self._genchecksum:
1082 return
1083
1084 md5sum = misc.get_md5sum(image_name)
1085 with open(image_name + ".md5sum", "w") as f:
1086 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1087 self.outimage.append(image_name+".md5sum")
1088
1089 def package(self, destdir = "."):
1090 """Prepares the created image for final delivery.
1091
1092 In its simplest form, this method merely copies the install root to the
1093 supplied destination directory; other subclasses may choose to package
1094 the image by e.g. creating a bootable ISO containing the image and
1095 bootloader configuration.
1096
1097 destdir -- the directory into which the final image should be moved;
1098 this defaults to the current directory.
1099
1100 """
1101 self._stage_final_image()
1102
1103 if not os.path.exists(destdir):
1104 fs.makedirs(destdir)
1105
1106 if self._recording_pkgs:
1107 self._save_recording_pkgs(destdir)
1108
1109 # For image formats with two or multiple image files, it will be
1110 # better to put them under a directory
1111 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1112 destdir = os.path.join(destdir, "%s-%s" \
1113 % (self.name, self.image_format))
1114 msger.debug("creating destination dir: %s" % destdir)
1115 fs.makedirs(destdir)
1116
1117 # Ensure all data is flushed to _outdir
1118 runner.quiet('sync')
1119
1120 misc.check_space_pre_cp(self._outdir, destdir)
1121 for f in os.listdir(self._outdir):
1122 shutil.move(os.path.join(self._outdir, f),
1123 os.path.join(destdir, f))
1124 self.outimage.append(os.path.join(destdir, f))
1125 self.do_genchecksum(os.path.join(destdir, f))
1126
1127 def print_outimage_info(self):
1128 msg = "The new image can be found here:\n"
1129 self.outimage.sort()
1130 for file in self.outimage:
1131 msg += ' %s\n' % os.path.abspath(file)
1132
1133 msger.info(msg)
1134
1135 def check_depend_tools(self):
1136 for tool in self._dep_checks:
1137 fs.find_binary_path(tool)
1138
1139 def package_output(self, image_format, destdir = ".", package="none"):
1140 if not package or package == "none":
1141 return
1142
1143 destdir = os.path.abspath(os.path.expanduser(destdir))
1144 (pkg, comp) = os.path.splitext(package)
1145 if comp:
1146 comp=comp.lstrip(".")
1147
1148 if pkg == "tar":
1149 if comp:
1150 dst = "%s/%s-%s.tar.%s" %\
1151 (destdir, self.name, image_format, comp)
1152 else:
1153 dst = "%s/%s-%s.tar" %\
1154 (destdir, self.name, image_format)
1155
1156 msger.info("creating %s" % dst)
1157 tar = tarfile.open(dst, "w:" + comp)
1158
1159 for file in self.outimage:
1160 msger.info("adding %s to %s" % (file, dst))
1161 tar.add(file,
1162 arcname=os.path.join("%s-%s" \
1163 % (self.name, image_format),
1164 os.path.basename(file)))
1165 if os.path.isdir(file):
1166 shutil.rmtree(file, ignore_errors = True)
1167 else:
1168 os.remove(file)
1169
1170 tar.close()
1171
1172 '''All the file in outimage has been packaged into tar.* file'''
1173 self.outimage = [dst]
1174
1175 def release_output(self, config, destdir, release):
1176 """ Create release directory and files
1177 """
1178
1179 def _rpath(fn):
1180 """ release path """
1181 return os.path.join(destdir, fn)
1182
1183 outimages = self.outimage
1184
1185 # new ks
1186 new_kspath = _rpath(self.name+'.ks')
1187 with open(config) as fr:
1188 with open(new_kspath, "w") as wf:
1189 # When building a release we want to make sure the .ks
1190 # file generates the same build even when --release not used.
1191 wf.write(fr.read().replace("@BUILD_ID@", release))
1192 outimages.append(new_kspath)
1193
1194 # save log file, logfile is only available in creator attrs
1195 if hasattr(self, 'logfile') and not self.logfile:
1196 log_path = _rpath(self.name + ".log")
1197 # touch the log file, else outimages will filter it out
1198 with open(log_path, 'w') as wf:
1199 wf.write('')
1200 msger.set_logfile(log_path)
1201 outimages.append(_rpath(self.name + ".log"))
1202
1203 # rename iso and usbimg
1204 for f in os.listdir(destdir):
1205 if f.endswith(".iso"):
1206 newf = f[:-4] + '.img'
1207 elif f.endswith(".usbimg"):
1208 newf = f[:-7] + '.img'
1209 else:
1210 continue
1211 os.rename(_rpath(f), _rpath(newf))
1212 outimages.append(_rpath(newf))
1213
1214 # generate MD5SUMS
1215 with open(_rpath("MD5SUMS"), "w") as wf:
1216 for f in os.listdir(destdir):
1217 if f == "MD5SUMS":
1218 continue
1219
1220 if os.path.isdir(os.path.join(destdir, f)):
1221 continue
1222
1223 md5sum = misc.get_md5sum(_rpath(f))
1224 # There needs to be two spaces between the sum and
1225 # filepath to match the syntax with md5sum.
1226 # This way also md5sum -c MD5SUMS can be used by users
1227 wf.write("%s *%s\n" % (md5sum, f))
1228
1229 outimages.append("%s/MD5SUMS" % destdir)
1230
1231 # Filter out the nonexist file
1232 for fp in outimages[:]:
1233 if not os.path.exists("%s" % fp):
1234 outimages.remove(fp)
1235
1236 def copy_kernel(self):
1237 """ Copy kernel files to the outimage directory.
1238 NOTE: This needs to be called before unmounting the instroot.
1239 """
1240
1241 if not self._need_copy_kernel:
1242 return
1243
1244 if not os.path.exists(self.destdir):
1245 os.makedirs(self.destdir)
1246
1247 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1248 kernelfilename = "%s/%s-%s" % (self.destdir,
1249 self.name,
1250 os.path.basename(kernel))
1251 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1252 kernelfilename))
1253 shutil.copy(kernel, kernelfilename)
1254 self.outimage.append(kernelfilename)
1255
1256 def copy_attachment(self):
1257 """ Subclass implement it to handle attachment files
1258 NOTE: This needs to be called before unmounting the instroot.
1259 """
1260 pass
1261
1262 def get_pkg_manager(self):
1263 return self.pkgmgr(target_arch = self.target_arch,
1264 instroot = self._instroot,
1265 cachedir = self.cachedir)
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
diff --git a/scripts/lib/mic/imager/fs.py b/scripts/lib/mic/imager/fs.py
new file mode 100644
index 0000000000..d53b29cb47
--- /dev/null
+++ b/scripts/lib/mic/imager/fs.py
@@ -0,0 +1,99 @@
1#!/usr/bin/python -tt
2#
3# Copyright (c) 2011 Intel, Inc.
4#
5# This program is free software; you can redistribute it and/or modify it
6# under the terms of the GNU General Public License as published by the Free
7# Software Foundation; version 2 of the License
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12# for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc., 59
16# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18import os
19
20from mic import msger
21from mic.utils import runner, misc
22from mic.utils.errors import CreatorError
23from mic.utils.fs_related import find_binary_path
24from mic.imager.baseimager import BaseImageCreator
25
26class FsImageCreator(BaseImageCreator):
27 def __init__(self, cfgmgr = None, pkgmgr = None):
28 self.zips = {
29 "tar.bz2" : ""
30 }
31 BaseImageCreator.__init__(self, cfgmgr, pkgmgr)
32 self._fstype = None
33 self._fsopts = None
34 self._include_src = False
35
36 def package(self, destdir = "."):
37
38 ignores = ["/dev/fd",
39 "/dev/stdin",
40 "/dev/stdout",
41 "/dev/stderr",
42 "/etc/mtab"]
43
44 if not os.path.exists(destdir):
45 os.makedirs(destdir)
46
47 if self._recording_pkgs:
48 self._save_recording_pkgs(destdir)
49
50 if not self.pack_to:
51 fsdir = os.path.join(destdir, self.name)
52
53 misc.check_space_pre_cp(self._instroot, destdir)
54 msger.info("Copying %s to %s ..." % (self._instroot, fsdir))
55 runner.show(['cp', "-af", self._instroot, fsdir])
56
57 for exclude in ignores:
58 if os.path.exists(fsdir + exclude):
59 os.unlink(fsdir + exclude)
60
61 self.outimage.append(fsdir)
62
63 else:
64 (tar, comp) = os.path.splitext(self.pack_to)
65 try:
66 tarcreat = {'.tar': '-cf',
67 '.gz': '-czf',
68 '.bz2': '-cjf',
69 '.tgz': '-czf',
70 '.tbz': '-cjf'}[comp]
71 except KeyError:
72 raise CreatorError("Unsupported comression for this image type:"
73 " '%s', try '.tar', '.tar.gz', etc" % comp)
74
75 dst = os.path.join(destdir, self.pack_to)
76 msger.info("Pack rootfs to %s. Please wait..." % dst)
77
78 tar = find_binary_path('tar')
79 tar_cmdline = [tar, "--numeric-owner",
80 "--preserve-permissions",
81 "--preserve-order",
82 "--one-file-system",
83 "--directory",
84 self._instroot]
85 for ignore_entry in ignores:
86 if ignore_entry.startswith('/'):
87 ignore_entry = ignore_entry[1:]
88
89 tar_cmdline.append("--exclude=%s" % (ignore_entry))
90
91 tar_cmdline.extend([tarcreat, dst, "."])
92
93 rc = runner.show(tar_cmdline)
94 if rc:
95 raise CreatorError("Failed compress image with tar.bz2. "
96 "Cmdline: %s" % (" ".join(tar_cmdline)))
97
98 self.outimage.append(dst)
99
diff --git a/scripts/lib/mic/imager/livecd.py b/scripts/lib/mic/imager/livecd.py
new file mode 100644
index 0000000000..a992ee0706
--- /dev/null
+++ b/scripts/lib/mic/imager/livecd.py
@@ -0,0 +1,750 @@
1#!/usr/bin/python -tt
2#
3# Copyright (c) 2011 Intel, Inc.
4#
5# This program is free software; you can redistribute it and/or modify it
6# under the terms of the GNU General Public License as published by the Free
7# Software Foundation; version 2 of the License
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12# for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc., 59
16# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18import os, sys
19import glob
20import shutil
21
22from mic import kickstart, msger
23from mic.utils import fs_related, rpmmisc, runner, misc
24from mic.utils.errors import CreatorError
25from mic.imager.loop import LoopImageCreator
26
27
28class LiveImageCreatorBase(LoopImageCreator):
29 """A base class for LiveCD image creators.
30
31 This class serves as a base class for the architecture-specific LiveCD
32 image creator subclass, LiveImageCreator.
33
34 LiveImageCreator creates a bootable ISO containing the system image,
35 bootloader, bootloader configuration, kernel and initramfs.
36 """
37
38 def __init__(self, creatoropts = None, pkgmgr = None):
39 """Initialise a LiveImageCreator instance.
40
41 This method takes the same arguments as ImageCreator.__init__().
42 """
43 LoopImageCreator.__init__(self, creatoropts, pkgmgr)
44
45 #Controls whether to use squashfs to compress the image.
46 self.skip_compression = False
47
48 #Controls whether an image minimizing snapshot should be created.
49 #
50 #This snapshot can be used when copying the system image from the ISO in
51 #order to minimize the amount of data that needs to be copied; simply,
52 #it makes it possible to create a version of the image's filesystem with
53 #no spare space.
54 self.skip_minimize = False
55
56 #A flag which indicates i act as a convertor default false
57 self.actasconvertor = False
58
59 #The bootloader timeout from kickstart.
60 if self.ks:
61 self._timeout = kickstart.get_timeout(self.ks, 10)
62 else:
63 self._timeout = 10
64
65 #The default kernel type from kickstart.
66 if self.ks:
67 self._default_kernel = kickstart.get_default_kernel(self.ks,
68 "kernel")
69 else:
70 self._default_kernel = None
71
72 if self.ks:
73 parts = kickstart.get_partitions(self.ks)
74 if len(parts) > 1:
75 raise CreatorError("Can't support multi partitions in ks file "
76 "for this image type")
77 # FIXME: rename rootfs img to self.name,
78 # else can't find files when create iso
79 self._instloops[0]['name'] = self.name + ".img"
80
81 self.__isodir = None
82
83 self.__modules = ["=ata",
84 "sym53c8xx",
85 "aic7xxx",
86 "=usb",
87 "=firewire",
88 "=mmc",
89 "=pcmcia",
90 "mptsas"]
91 if self.ks:
92 self.__modules.extend(kickstart.get_modules(self.ks))
93
94 self._dep_checks.extend(["isohybrid",
95 "unsquashfs",
96 "mksquashfs",
97 "dd",
98 "genisoimage"])
99
100 #
101 # Hooks for subclasses
102 #
103 def _configure_bootloader(self, isodir):
104 """Create the architecture specific booloader configuration.
105
106 This is the hook where subclasses must create the booloader
107 configuration in order to allow a bootable ISO to be built.
108
109 isodir -- the directory where the contents of the ISO are to
110 be staged
111 """
112 raise CreatorError("Bootloader configuration is arch-specific, "
113 "but not implemented for this arch!")
114 def _get_menu_options(self):
115 """Return a menu options string for syslinux configuration.
116 """
117 if self.ks is None:
118 return "liveinst autoinst"
119 r = kickstart.get_menu_args(self.ks)
120 return r
121
122 def _get_kernel_options(self):
123 """Return a kernel options string for bootloader configuration.
124
125 This is the hook where subclasses may specify a set of kernel
126 options which should be included in the images bootloader
127 configuration.
128
129 A sensible default implementation is provided.
130 """
131
132 if self.ks is None:
133 r = "ro rd.live.image"
134 else:
135 r = kickstart.get_kernel_args(self.ks)
136
137 return r
138
139 def _get_mkisofs_options(self, isodir):
140 """Return the architecture specific mkisosfs options.
141
142 This is the hook where subclasses may specify additional arguments
143 to mkisofs, e.g. to enable a bootable ISO to be built.
144
145 By default, an empty list is returned.
146 """
147 return []
148
149 #
150 # Helpers for subclasses
151 #
152 def _has_checkisomd5(self):
153 """Check whether checkisomd5 is available in the install root."""
154 def _exists(path):
155 return os.path.exists(self._instroot + path)
156
157 if _exists("/usr/bin/checkisomd5") and os.path.exists("/usr/bin/implantisomd5"):
158 return True
159
160 return False
161
162 def __restore_file(self,path):
163 try:
164 os.unlink(path)
165 except:
166 pass
167 if os.path.exists(path + '.rpmnew'):
168 os.rename(path + '.rpmnew', path)
169
170 def _mount_instroot(self, base_on = None):
171 LoopImageCreator._mount_instroot(self, base_on)
172 self.__write_initrd_conf(self._instroot + "/etc/sysconfig/mkinitrd")
173 self.__write_dracut_conf(self._instroot + "/etc/dracut.conf.d/02livecd.conf")
174
175 def _unmount_instroot(self):
176 self.__restore_file(self._instroot + "/etc/sysconfig/mkinitrd")
177 self.__restore_file(self._instroot + "/etc/dracut.conf.d/02livecd.conf")
178 LoopImageCreator._unmount_instroot(self)
179
180 def __ensure_isodir(self):
181 if self.__isodir is None:
182 self.__isodir = self._mkdtemp("iso-")
183 return self.__isodir
184
185 def _get_isodir(self):
186 return self.__ensure_isodir()
187
188 def _set_isodir(self, isodir = None):
189 self.__isodir = isodir
190
191 def _create_bootconfig(self):
192 """Configure the image so that it's bootable."""
193 self._configure_bootloader(self.__ensure_isodir())
194
195 def _get_post_scripts_env(self, in_chroot):
196 env = LoopImageCreator._get_post_scripts_env(self, in_chroot)
197
198 if not in_chroot:
199 env["LIVE_ROOT"] = self.__ensure_isodir()
200
201 return env
202 def __write_dracut_conf(self, path):
203 if not os.path.exists(os.path.dirname(path)):
204 fs_related.makedirs(os.path.dirname(path))
205 f = open(path, "a")
206 f.write('add_dracutmodules+=" dmsquash-live pollcdrom "')
207 f.close()
208
209 def __write_initrd_conf(self, path):
210 content = ""
211 if not os.path.exists(os.path.dirname(path)):
212 fs_related.makedirs(os.path.dirname(path))
213 f = open(path, "w")
214
215 content += 'LIVEOS="yes"\n'
216 content += 'PROBE="no"\n'
217 content += 'MODULES+="squashfs ext3 ext2 vfat msdos "\n'
218 content += 'MODULES+="sr_mod sd_mod ide-cd cdrom "\n'
219
220 for module in self.__modules:
221 if module == "=usb":
222 content += 'MODULES+="ehci_hcd uhci_hcd ohci_hcd "\n'
223 content += 'MODULES+="usb_storage usbhid "\n'
224 elif module == "=firewire":
225 content += 'MODULES+="firewire-sbp2 firewire-ohci "\n'
226 content += 'MODULES+="sbp2 ohci1394 ieee1394 "\n'
227 elif module == "=mmc":
228 content += 'MODULES+="mmc_block sdhci sdhci-pci "\n'
229 elif module == "=pcmcia":
230 content += 'MODULES+="pata_pcmcia "\n'
231 else:
232 content += 'MODULES+="' + module + ' "\n'
233 f.write(content)
234 f.close()
235
236 def __create_iso(self, isodir):
237 iso = self._outdir + "/" + self.name + ".iso"
238 genisoimage = fs_related.find_binary_path("genisoimage")
239 args = [genisoimage,
240 "-J", "-r",
241 "-hide-rr-moved", "-hide-joliet-trans-tbl",
242 "-V", self.fslabel,
243 "-o", iso]
244
245 args.extend(self._get_mkisofs_options(isodir))
246
247 args.append(isodir)
248
249 if runner.show(args) != 0:
250 raise CreatorError("ISO creation failed!")
251
252 """ It should be ok still even if you haven't isohybrid """
253 isohybrid = None
254 try:
255 isohybrid = fs_related.find_binary_path("isohybrid")
256 except:
257 pass
258
259 if isohybrid:
260 args = [isohybrid, "-partok", iso ]
261 if runner.show(args) != 0:
262 raise CreatorError("Hybrid ISO creation failed!")
263
264 self.__implant_md5sum(iso)
265
266 def __implant_md5sum(self, iso):
267 """Implant an isomd5sum."""
268 if os.path.exists("/usr/bin/implantisomd5"):
269 implantisomd5 = "/usr/bin/implantisomd5"
270 else:
271 msger.warning("isomd5sum not installed; not setting up mediacheck")
272 implantisomd5 = ""
273 return
274
275 runner.show([implantisomd5, iso])
276
277 def _stage_final_image(self):
278 try:
279 fs_related.makedirs(self.__ensure_isodir() + "/LiveOS")
280
281 minimal_size = self._resparse()
282
283 if not self.skip_minimize:
284 fs_related.create_image_minimizer(self.__isodir + \
285 "/LiveOS/osmin.img",
286 self._image,
287 minimal_size)
288
289 if self.skip_compression:
290 shutil.move(self._image, self.__isodir + "/LiveOS/ext3fs.img")
291 else:
292 fs_related.makedirs(os.path.join(
293 os.path.dirname(self._image),
294 "LiveOS"))
295 shutil.move(self._image,
296 os.path.join(os.path.dirname(self._image),
297 "LiveOS", "ext3fs.img"))
298 fs_related.mksquashfs(os.path.dirname(self._image),
299 self.__isodir + "/LiveOS/squashfs.img")
300
301 self.__create_iso(self.__isodir)
302
303 if self.pack_to:
304 isoimg = os.path.join(self._outdir, self.name + ".iso")
305 packimg = os.path.join(self._outdir, self.pack_to)
306 misc.packing(packimg, isoimg)
307 os.unlink(isoimg)
308
309 finally:
310 shutil.rmtree(self.__isodir, ignore_errors = True)
311 self.__isodir = None
312
313class x86LiveImageCreator(LiveImageCreatorBase):
314 """ImageCreator for x86 machines"""
315 def _get_mkisofs_options(self, isodir):
316 return [ "-b", "isolinux/isolinux.bin",
317 "-c", "isolinux/boot.cat",
318 "-no-emul-boot", "-boot-info-table",
319 "-boot-load-size", "4" ]
320
321 def _get_required_packages(self):
322 return ["syslinux", "syslinux-extlinux"] + \
323 LiveImageCreatorBase._get_required_packages(self)
324
325 def _get_isolinux_stanzas(self, isodir):
326 return ""
327
328 def __find_syslinux_menu(self):
329 for menu in ["vesamenu.c32", "menu.c32"]:
330 if os.path.isfile(self._instroot + "/usr/share/syslinux/" + menu):
331 return menu
332
333 raise CreatorError("syslinux not installed : "
334 "no suitable /usr/share/syslinux/*menu.c32 found")
335
336 def __find_syslinux_mboot(self):
337 #
338 # We only need the mboot module if we have any xen hypervisors
339 #
340 if not glob.glob(self._instroot + "/boot/xen.gz*"):
341 return None
342
343 return "mboot.c32"
344
345 def __copy_syslinux_files(self, isodir, menu, mboot = None):
346 files = ["isolinux.bin", menu]
347 if mboot:
348 files += [mboot]
349
350 for f in files:
351 path = self._instroot + "/usr/share/syslinux/" + f
352
353 if not os.path.isfile(path):
354 raise CreatorError("syslinux not installed : "
355 "%s not found" % path)
356
357 shutil.copy(path, isodir + "/isolinux/")
358
359 def __copy_syslinux_background(self, isodest):
360 background_path = self._instroot + \
361 "/usr/share/branding/default/syslinux/syslinux-vesa-splash.jpg"
362
363 if not os.path.exists(background_path):
364 return False
365
366 shutil.copyfile(background_path, isodest)
367
368 return True
369
370 def __copy_kernel_and_initramfs(self, isodir, version, index):
371 bootdir = self._instroot + "/boot"
372 isDracut = False
373
374 if self._alt_initrd_name:
375 src_initrd_path = os.path.join(bootdir, self._alt_initrd_name)
376 else:
377 if os.path.exists(bootdir + "/initramfs-" + version + ".img"):
378 src_initrd_path = os.path.join(bootdir, "initramfs-" +version+ ".img")
379 isDracut = True
380 else:
381 src_initrd_path = os.path.join(bootdir, "initrd-" +version+ ".img")
382
383 try:
384 msger.debug("copy %s to %s" % (bootdir + "/vmlinuz-" + version, isodir + "/isolinux/vmlinuz" + index))
385 shutil.copyfile(bootdir + "/vmlinuz-" + version,
386 isodir + "/isolinux/vmlinuz" + index)
387
388 msger.debug("copy %s to %s" % (src_initrd_path, isodir + "/isolinux/initrd" + index + ".img"))
389 shutil.copyfile(src_initrd_path,
390 isodir + "/isolinux/initrd" + index + ".img")
391 except:
392 raise CreatorError("Unable to copy valid kernels or initrds, "
393 "please check the repo.")
394
395 is_xen = False
396 if os.path.exists(bootdir + "/xen.gz-" + version[:-3]):
397 shutil.copyfile(bootdir + "/xen.gz-" + version[:-3],
398 isodir + "/isolinux/xen" + index + ".gz")
399 is_xen = True
400
401 return (is_xen,isDracut)
402
403 def __is_default_kernel(self, kernel, kernels):
404 if len(kernels) == 1:
405 return True
406
407 if kernel == self._default_kernel:
408 return True
409
410 if kernel.startswith("kernel-") and kernel[7:] == self._default_kernel:
411 return True
412
413 return False
414
415 def __get_basic_syslinux_config(self, **args):
416 return """
417default %(menu)s
418timeout %(timeout)d
419
420%(background)s
421menu title Welcome to %(distroname)s!
422menu color border 0 #ffffffff #00000000
423menu color sel 7 #ff000000 #ffffffff
424menu color title 0 #ffffffff #00000000
425menu color tabmsg 0 #ffffffff #00000000
426menu color unsel 0 #ffffffff #00000000
427menu color hotsel 0 #ff000000 #ffffffff
428menu color hotkey 7 #ffffffff #ff000000
429menu color timeout_msg 0 #ffffffff #00000000
430menu color timeout 0 #ffffffff #00000000
431menu color cmdline 0 #ffffffff #00000000
432menu hidden
433menu clear
434""" % args
435
436 def __get_image_stanza(self, is_xen, isDracut, **args):
437 if isDracut:
438 args["rootlabel"] = "live:CDLABEL=%(fslabel)s" % args
439 else:
440 args["rootlabel"] = "CDLABEL=%(fslabel)s" % args
441 if not is_xen:
442 template = """label %(short)s
443 menu label %(long)s
444 kernel vmlinuz%(index)s
445 append initrd=initrd%(index)s.img root=%(rootlabel)s rootfstype=iso9660 %(liveargs)s %(extra)s
446"""
447 else:
448 template = """label %(short)s
449 menu label %(long)s
450 kernel mboot.c32
451 append xen%(index)s.gz --- vmlinuz%(index)s root=%(rootlabel)s rootfstype=iso9660 %(liveargs)s %(extra)s --- initrd%(index)s.img
452"""
453 return template % args
454
455 def __get_image_stanzas(self, isodir):
456 versions = []
457 kernels = self._get_kernel_versions()
458 for kernel in kernels:
459 for version in kernels[kernel]:
460 versions.append(version)
461
462 if not versions:
463 raise CreatorError("Unable to find valid kernels, "
464 "please check the repo")
465
466 kernel_options = self._get_kernel_options()
467
468 """ menu can be customized highly, the format is:
469
470 short_name1:long_name1:extra_opts1;short_name2:long_name2:extra_opts2
471
472 e.g.: autoinst:InstallationOnly:systemd.unit=installer-graphical.service
473 but in order to keep compatible with old format, these are still ok:
474
475 liveinst autoinst
476 liveinst;autoinst
477 liveinst::;autoinst::
478 """
479 oldmenus = {"basic": {
480 "short": "basic",
481 "long": "Installation Only (Text based)",
482 "extra": "basic nosplash 4"
483 },
484 "liveinst": {
485 "short": "liveinst",
486 "long": "Installation Only",
487 "extra": "liveinst nosplash 4"
488 },
489 "autoinst": {
490 "short": "autoinst",
491 "long": "Autoinstall (Deletes all existing content)",
492 "extra": "autoinst nosplash 4"
493 },
494 "netinst": {
495 "short": "netinst",
496 "long": "Network Installation",
497 "extra": "netinst 4"
498 },
499 "verify": {
500 "short": "check",
501 "long": "Verify and",
502 "extra": "check"
503 }
504 }
505 menu_options = self._get_menu_options()
506 menus = menu_options.split(";")
507 for i in range(len(menus)):
508 menus[i] = menus[i].split(":")
509 if len(menus) == 1 and len(menus[0]) == 1:
510 """ Keep compatible with the old usage way """
511 menus = menu_options.split()
512 for i in range(len(menus)):
513 menus[i] = [menus[i]]
514
515 cfg = ""
516
517 default_version = None
518 default_index = None
519 index = "0"
520 netinst = None
521 for version in versions:
522 (is_xen, isDracut) = self.__copy_kernel_and_initramfs(isodir, version, index)
523 if index == "0":
524 self._isDracut = isDracut
525
526 default = self.__is_default_kernel(kernel, kernels)
527
528 if default:
529 long = "Boot %s" % self.distro_name
530 elif kernel.startswith("kernel-"):
531 long = "Boot %s(%s)" % (self.name, kernel[7:])
532 else:
533 long = "Boot %s(%s)" % (self.name, kernel)
534
535 oldmenus["verify"]["long"] = "%s %s" % (oldmenus["verify"]["long"],
536 long)
537 # tell dracut not to ask for LUKS passwords or activate mdraid sets
538 if isDracut:
539 kern_opts = kernel_options + " rd.luks=0 rd.md=0 rd.dm=0"
540 else:
541 kern_opts = kernel_options
542
543 cfg += self.__get_image_stanza(is_xen, isDracut,
544 fslabel = self.fslabel,
545 liveargs = kern_opts,
546 long = long,
547 short = "linux" + index,
548 extra = "",
549 index = index)
550
551 if default:
552 cfg += "menu default\n"
553 default_version = version
554 default_index = index
555
556 for menu in menus:
557 if not menu[0]:
558 continue
559 short = menu[0] + index
560
561 if len(menu) >= 2:
562 long = menu[1]
563 else:
564 if menu[0] in oldmenus.keys():
565 if menu[0] == "verify" and not self._has_checkisomd5():
566 continue
567 if menu[0] == "netinst":
568 netinst = oldmenus[menu[0]]
569 continue
570 long = oldmenus[menu[0]]["long"]
571 extra = oldmenus[menu[0]]["extra"]
572 else:
573 long = short.upper() + " X" + index
574 extra = ""
575
576 if len(menu) >= 3:
577 extra = menu[2]
578
579 cfg += self.__get_image_stanza(is_xen, isDracut,
580 fslabel = self.fslabel,
581 liveargs = kernel_options,
582 long = long,
583 short = short,
584 extra = extra,
585 index = index)
586
587 index = str(int(index) + 1)
588
589 if not default_version:
590 default_version = versions[0]
591 if not default_index:
592 default_index = "0"
593
594 if netinst:
595 cfg += self.__get_image_stanza(is_xen, isDracut,
596 fslabel = self.fslabel,
597 liveargs = kernel_options,
598 long = netinst["long"],
599 short = netinst["short"],
600 extra = netinst["extra"],
601 index = default_index)
602
603 return cfg
604
605 def __get_memtest_stanza(self, isodir):
606 memtest = glob.glob(self._instroot + "/boot/memtest86*")
607 if not memtest:
608 return ""
609
610 shutil.copyfile(memtest[0], isodir + "/isolinux/memtest")
611
612 return """label memtest
613 menu label Memory Test
614 kernel memtest
615"""
616
617 def __get_local_stanza(self, isodir):
618 return """label local
619 menu label Boot from local drive
620 localboot 0xffff
621"""
622
623 def _configure_syslinux_bootloader(self, isodir):
624 """configure the boot loader"""
625 fs_related.makedirs(isodir + "/isolinux")
626
627 menu = self.__find_syslinux_menu()
628
629 self.__copy_syslinux_files(isodir, menu,
630 self.__find_syslinux_mboot())
631
632 background = ""
633 if self.__copy_syslinux_background(isodir + "/isolinux/splash.jpg"):
634 background = "menu background splash.jpg"
635
636 cfg = self.__get_basic_syslinux_config(menu = menu,
637 background = background,
638 name = self.name,
639 timeout = self._timeout * 10,
640 distroname = self.distro_name)
641
642 cfg += self.__get_image_stanzas(isodir)
643 cfg += self.__get_memtest_stanza(isodir)
644 cfg += self.__get_local_stanza(isodir)
645 cfg += self._get_isolinux_stanzas(isodir)
646
647 cfgf = open(isodir + "/isolinux/isolinux.cfg", "w")
648 cfgf.write(cfg)
649 cfgf.close()
650
651 def __copy_efi_files(self, isodir):
652 if not os.path.exists(self._instroot + "/boot/efi/EFI/redhat/grub.efi"):
653 return False
654 shutil.copy(self._instroot + "/boot/efi/EFI/redhat/grub.efi",
655 isodir + "/EFI/boot/grub.efi")
656 shutil.copy(self._instroot + "/boot/grub/splash.xpm.gz",
657 isodir + "/EFI/boot/splash.xpm.gz")
658
659 return True
660
661 def __get_basic_efi_config(self, **args):
662 return """
663default=0
664splashimage=/EFI/boot/splash.xpm.gz
665timeout %(timeout)d
666hiddenmenu
667
668""" %args
669
670 def __get_efi_image_stanza(self, **args):
671 return """title %(long)s
672 kernel /EFI/boot/vmlinuz%(index)s root=CDLABEL=%(fslabel)s rootfstype=iso9660 %(liveargs)s %(extra)s
673 initrd /EFI/boot/initrd%(index)s.img
674""" %args
675
676 def __get_efi_image_stanzas(self, isodir, name):
677 # FIXME: this only supports one kernel right now...
678
679 kernel_options = self._get_kernel_options()
680 checkisomd5 = self._has_checkisomd5()
681
682 cfg = ""
683
684 for index in range(0, 9):
685 # we don't support xen kernels
686 if os.path.exists("%s/EFI/boot/xen%d.gz" %(isodir, index)):
687 continue
688 cfg += self.__get_efi_image_stanza(fslabel = self.fslabel,
689 liveargs = kernel_options,
690 long = name,
691 extra = "", index = index)
692 if checkisomd5:
693 cfg += self.__get_efi_image_stanza(
694 fslabel = self.fslabel,
695 liveargs = kernel_options,
696 long = "Verify and Boot " + name,
697 extra = "check",
698 index = index)
699 break
700
701 return cfg
702
703 def _configure_efi_bootloader(self, isodir):
704 """Set up the configuration for an EFI bootloader"""
705 fs_related.makedirs(isodir + "/EFI/boot")
706
707 if not self.__copy_efi_files(isodir):
708 shutil.rmtree(isodir + "/EFI")
709 return
710
711 for f in os.listdir(isodir + "/isolinux"):
712 os.link("%s/isolinux/%s" %(isodir, f),
713 "%s/EFI/boot/%s" %(isodir, f))
714
715
716 cfg = self.__get_basic_efi_config(name = self.name,
717 timeout = self._timeout)
718 cfg += self.__get_efi_image_stanzas(isodir, self.name)
719
720 cfgf = open(isodir + "/EFI/boot/grub.conf", "w")
721 cfgf.write(cfg)
722 cfgf.close()
723
724 # first gen mactel machines get the bootloader name wrong apparently
725 if rpmmisc.getBaseArch() == "i386":
726 os.link(isodir + "/EFI/boot/grub.efi",
727 isodir + "/EFI/boot/boot.efi")
728 os.link(isodir + "/EFI/boot/grub.conf",
729 isodir + "/EFI/boot/boot.conf")
730
731 # for most things, we want them named boot$efiarch
732 efiarch = {"i386": "ia32", "x86_64": "x64"}
733 efiname = efiarch[rpmmisc.getBaseArch()]
734 os.rename(isodir + "/EFI/boot/grub.efi",
735 isodir + "/EFI/boot/boot%s.efi" %(efiname,))
736 os.link(isodir + "/EFI/boot/grub.conf",
737 isodir + "/EFI/boot/boot%s.conf" %(efiname,))
738
739
740 def _configure_bootloader(self, isodir):
741 self._configure_syslinux_bootloader(isodir)
742 self._configure_efi_bootloader(isodir)
743
744arch = rpmmisc.getBaseArch()
745if arch in ("i386", "x86_64"):
746 LiveCDImageCreator = x86LiveImageCreator
747elif arch.startswith("arm"):
748 LiveCDImageCreator = LiveImageCreatorBase
749else:
750 raise CreatorError("Architecture not supported!")
diff --git a/scripts/lib/mic/imager/liveusb.py b/scripts/lib/mic/imager/liveusb.py
new file mode 100644
index 0000000000..a909928a4c
--- /dev/null
+++ b/scripts/lib/mic/imager/liveusb.py
@@ -0,0 +1,308 @@
1#!/usr/bin/python -tt
2#
3# Copyright (c) 2011 Intel, Inc.
4#
5# This program is free software; you can redistribute it and/or modify it
6# under the terms of the GNU General Public License as published by the Free
7# Software Foundation; version 2 of the License
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12# for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc., 59
16# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18import os
19import shutil
20import re
21
22from mic import msger
23from mic.utils import misc, fs_related, runner
24from mic.utils.errors import CreatorError, MountError
25from mic.utils.partitionedfs import PartitionedMount
26from mic.imager.livecd import LiveCDImageCreator
27
28
29class LiveUSBImageCreator(LiveCDImageCreator):
30 def __init__(self, *args):
31 LiveCDImageCreator.__init__(self, *args)
32
33 self._dep_checks.extend(["kpartx", "parted"])
34
35 # remove dependency of genisoimage in parent class
36 if "genisoimage" in self._dep_checks:
37 self._dep_checks.remove("genisoimage")
38
39 def _create_usbimg(self, isodir):
40 overlaysizemb = 64 #default
41 #skipcompress = self.skip_compression?
42 fstype = "vfat"
43 homesizemb=0
44 swapsizemb=0
45 homefile="home.img"
46 plussize=128
47 kernelargs=None
48
49 if fstype == 'vfat':
50 if overlaysizemb > 2047:
51 raise CreatorError("Can't have an overlay of 2048MB or "
52 "greater on VFAT")
53
54 if homesizemb > 2047:
55 raise CreatorError("Can't have an home overlay of 2048MB or "
56 "greater on VFAT")
57
58 if swapsizemb > 2047:
59 raise CreatorError("Can't have an swap overlay of 2048MB or "
60 "greater on VFAT")
61
62 livesize = misc.get_file_size(isodir + "/LiveOS")
63
64 usbimgsize = (overlaysizemb + \
65 homesizemb + \
66 swapsizemb + \
67 livesize + \
68 plussize) * 1024L * 1024L
69
70 disk = fs_related.SparseLoopbackDisk("%s/%s.usbimg" \
71 % (self._outdir, self.name),
72 usbimgsize)
73 usbmnt = self._mkdtemp("usb-mnt")
74 usbloop = PartitionedMount(usbmnt)
75 usbloop.add_disk('/dev/sdb', disk)
76
77 usbloop.add_partition(usbimgsize/1024/1024,
78 "/dev/sdb",
79 "/",
80 fstype,
81 boot=True)
82
83 usbloop.mount()
84
85 try:
86 fs_related.makedirs(usbmnt + "/LiveOS")
87
88 if os.path.exists(isodir + "/LiveOS/squashfs.img"):
89 shutil.copyfile(isodir + "/LiveOS/squashfs.img",
90 usbmnt + "/LiveOS/squashfs.img")
91 else:
92 fs_related.mksquashfs(os.path.dirname(self._image),
93 usbmnt + "/LiveOS/squashfs.img")
94
95 if os.path.exists(isodir + "/LiveOS/osmin.img"):
96 shutil.copyfile(isodir + "/LiveOS/osmin.img",
97 usbmnt + "/LiveOS/osmin.img")
98
99 if fstype == "vfat" or fstype == "msdos":
100 uuid = usbloop.partitions[0]['mount'].uuid
101 label = usbloop.partitions[0]['mount'].fslabel
102 usblabel = "UUID=%s-%s" % (uuid[0:4], uuid[4:8])
103 overlaysuffix = "-%s-%s-%s" % (label, uuid[0:4], uuid[4:8])
104 else:
105 diskmount = usbloop.partitions[0]['mount']
106 usblabel = "UUID=%s" % diskmount.uuid
107 overlaysuffix = "-%s-%s" % (diskmount.fslabel, diskmount.uuid)
108
109 args = ['cp', "-Rf", isodir + "/isolinux", usbmnt + "/syslinux"]
110 rc = runner.show(args)
111 if rc:
112 raise CreatorError("Can't copy isolinux directory %s" \
113 % (isodir + "/isolinux/*"))
114
115 if os.path.isfile("/usr/share/syslinux/isolinux.bin"):
116 syslinux_path = "/usr/share/syslinux"
117 elif os.path.isfile("/usr/lib/syslinux/isolinux.bin"):
118 syslinux_path = "/usr/lib/syslinux"
119 else:
120 raise CreatorError("syslinux not installed : "
121 "cannot find syslinux installation path")
122
123 for f in ("isolinux.bin", "vesamenu.c32"):
124 path = os.path.join(syslinux_path, f)
125 if os.path.isfile(path):
126 args = ['cp', path, usbmnt + "/syslinux/"]
127 rc = runner.show(args)
128 if rc:
129 raise CreatorError("Can't copy syslinux file " + path)
130 else:
131 raise CreatorError("syslinux not installed: "
132 "syslinux file %s not found" % path)
133
134 fd = open(isodir + "/isolinux/isolinux.cfg", "r")
135 text = fd.read()
136 fd.close()
137 pattern = re.compile('CDLABEL=[^ ]*')
138 text = pattern.sub(usblabel, text)
139 pattern = re.compile('rootfstype=[^ ]*')
140 text = pattern.sub("rootfstype=" + fstype, text)
141 if kernelargs:
142 text = text.replace("rd.live.image", "rd.live.image " + kernelargs)
143
144 if overlaysizemb > 0:
145 msger.info("Initializing persistent overlay file")
146 overfile = "overlay" + overlaysuffix
147 if fstype == "vfat":
148 args = ['dd',
149 "if=/dev/zero",
150 "of=" + usbmnt + "/LiveOS/" + overfile,
151 "count=%d" % overlaysizemb,
152 "bs=1M"]
153 else:
154 args = ['dd',
155 "if=/dev/null",
156 "of=" + usbmnt + "/LiveOS/" + overfile,
157 "count=1",
158 "bs=1M",
159 "seek=%d" % overlaysizemb]
160 rc = runner.show(args)
161 if rc:
162 raise CreatorError("Can't create overlay file")
163 text = text.replace("rd.live.image", "rd.live.image rd.live.overlay=" + usblabel)
164 text = text.replace(" ro ", " rw ")
165
166 if swapsizemb > 0:
167 msger.info("Initializing swap file")
168 swapfile = usbmnt + "/LiveOS/" + "swap.img"
169 args = ['dd',
170 "if=/dev/zero",
171 "of=" + swapfile,
172 "count=%d" % swapsizemb,
173 "bs=1M"]
174 rc = runner.show(args)
175 if rc:
176 raise CreatorError("Can't create swap file")
177 args = ["mkswap", "-f", swapfile]
178 rc = runner.show(args)
179 if rc:
180 raise CreatorError("Can't mkswap on swap file")
181
182 if homesizemb > 0:
183 msger.info("Initializing persistent /home")
184 homefile = usbmnt + "/LiveOS/" + homefile
185 if fstype == "vfat":
186 args = ['dd',
187 "if=/dev/zero",
188 "of=" + homefile,
189 "count=%d" % homesizemb,
190 "bs=1M"]
191 else:
192 args = ['dd',
193 "if=/dev/null",
194 "of=" + homefile,
195 "count=1",
196 "bs=1M",
197 "seek=%d" % homesizemb]
198 rc = runner.show(args)
199 if rc:
200 raise CreatorError("Can't create home file")
201
202 mkfscmd = fs_related.find_binary_path("/sbin/mkfs." + fstype)
203 if fstype == "ext2" or fstype == "ext3":
204 args = [mkfscmd, "-F", "-j", homefile]
205 else:
206 args = [mkfscmd, homefile]
207 rc = runner.show(args)
208 if rc:
209 raise CreatorError("Can't mke2fs home file")
210 if fstype == "ext2" or fstype == "ext3":
211 tune2fs = fs_related.find_binary_path("tune2fs")
212 args = [tune2fs,
213 "-c0",
214 "-i0",
215 "-ouser_xattr,acl",
216 homefile]
217 rc = runner.show(args)
218 if rc:
219 raise CreatorError("Can't tune2fs home file")
220
221 if fstype == "vfat" or fstype == "msdos":
222 syslinuxcmd = fs_related.find_binary_path("syslinux")
223 syslinuxcfg = usbmnt + "/syslinux/syslinux.cfg"
224 args = [syslinuxcmd,
225 "-d",
226 "syslinux",
227 usbloop.partitions[0]["device"]]
228
229 elif fstype == "ext2" or fstype == "ext3":
230 extlinuxcmd = fs_related.find_binary_path("extlinux")
231 syslinuxcfg = usbmnt + "/syslinux/extlinux.conf"
232 args = [extlinuxcmd,
233 "-i",
234 usbmnt + "/syslinux"]
235
236 else:
237 raise CreatorError("Invalid file system type: %s" % (fstype))
238
239 os.unlink(usbmnt + "/syslinux/isolinux.cfg")
240 fd = open(syslinuxcfg, "w")
241 fd.write(text)
242 fd.close()
243 rc = runner.show(args)
244 if rc:
245 raise CreatorError("Can't install boot loader.")
246
247 finally:
248 usbloop.unmount()
249 usbloop.cleanup()
250
251 # Need to do this after image is unmounted and device mapper is closed
252 msger.info("set MBR")
253 mbrfile = "/usr/lib/syslinux/mbr.bin"
254 if not os.path.exists(mbrfile):
255 mbrfile = "/usr/share/syslinux/mbr.bin"
256 if not os.path.exists(mbrfile):
257 raise CreatorError("mbr.bin file didn't exist.")
258 mbrsize = os.path.getsize(mbrfile)
259 outimg = "%s/%s.usbimg" % (self._outdir, self.name)
260
261 args = ['dd',
262 "if=" + mbrfile,
263 "of=" + outimg,
264 "seek=0",
265 "conv=notrunc",
266 "bs=1",
267 "count=%d" % (mbrsize)]
268 rc = runner.show(args)
269 if rc:
270 raise CreatorError("Can't set MBR.")
271
272 def _stage_final_image(self):
273 try:
274 isodir = self._get_isodir()
275 fs_related.makedirs(isodir + "/LiveOS")
276
277 minimal_size = self._resparse()
278
279 if not self.skip_minimize:
280 fs_related.create_image_minimizer(isodir + "/LiveOS/osmin.img",
281 self._image,
282 minimal_size)
283
284 if self.skip_compression:
285 shutil.move(self._image,
286 isodir + "/LiveOS/ext3fs.img")
287 else:
288 fs_related.makedirs(os.path.join(
289 os.path.dirname(self._image),
290 "LiveOS"))
291 shutil.move(self._image,
292 os.path.join(os.path.dirname(self._image),
293 "LiveOS", "ext3fs.img"))
294 fs_related.mksquashfs(os.path.dirname(self._image),
295 isodir + "/LiveOS/squashfs.img")
296
297 self._create_usbimg(isodir)
298
299 if self.pack_to:
300 usbimg = os.path.join(self._outdir, self.name + ".usbimg")
301 packimg = os.path.join(self._outdir, self.pack_to)
302 misc.packing(packimg, usbimg)
303 os.unlink(usbimg)
304
305 finally:
306 shutil.rmtree(isodir, ignore_errors = True)
307 self._set_isodir(None)
308
diff --git a/scripts/lib/mic/imager/loop.py b/scripts/lib/mic/imager/loop.py
new file mode 100644
index 0000000000..4d05ef271d
--- /dev/null
+++ b/scripts/lib/mic/imager/loop.py
@@ -0,0 +1,418 @@
1#!/usr/bin/python -tt
2#
3# Copyright (c) 2011 Intel, Inc.
4#
5# This program is free software; you can redistribute it and/or modify it
6# under the terms of the GNU General Public License as published by the Free
7# Software Foundation; version 2 of the License
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12# for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc., 59
16# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18import os
19import glob
20import shutil
21
22from mic import kickstart, msger
23from mic.utils.errors import CreatorError, MountError
24from mic.utils import misc, runner, fs_related as fs
25from mic.imager.baseimager import BaseImageCreator
26
27
28# The maximum string length supported for LoopImageCreator.fslabel
29FSLABEL_MAXLEN = 32
30
31
32def save_mountpoints(fpath, loops, arch = None):
33 """Save mount points mapping to file
34
35 :fpath, the xml file to store partition info
36 :loops, dict of partition info
37 :arch, image arch
38 """
39
40 if not fpath or not loops:
41 return
42
43 from xml.dom import minidom
44 doc = minidom.Document()
45 imgroot = doc.createElement("image")
46 doc.appendChild(imgroot)
47 if arch:
48 imgroot.setAttribute('arch', arch)
49 for loop in loops:
50 part = doc.createElement("partition")
51 imgroot.appendChild(part)
52 for (key, val) in loop.items():
53 if isinstance(val, fs.Mount):
54 continue
55 part.setAttribute(key, str(val))
56
57 with open(fpath, 'w') as wf:
58 wf.write(doc.toprettyxml(indent=' '))
59
60 return
61
62def load_mountpoints(fpath):
63 """Load mount points mapping from file
64
65 :fpath, file path to load
66 """
67
68 if not fpath:
69 return
70
71 from xml.dom import minidom
72 mount_maps = []
73 with open(fpath, 'r') as rf:
74 dom = minidom.parse(rf)
75 imgroot = dom.documentElement
76 for part in imgroot.getElementsByTagName("partition"):
77 p = dict(part.attributes.items())
78
79 try:
80 mp = (p['mountpoint'], p['label'], p['name'],
81 int(p['size']), p['fstype'])
82 except KeyError:
83 msger.warning("Wrong format line in file: %s" % fpath)
84 except ValueError:
85 msger.warning("Invalid size '%s' in file: %s" % (p['size'], fpath))
86 else:
87 mount_maps.append(mp)
88
89 return mount_maps
90
91class LoopImageCreator(BaseImageCreator):
92 """Installs a system into a loopback-mountable filesystem image.
93
94 LoopImageCreator is a straightforward ImageCreator subclass; the system
95 is installed into an ext3 filesystem on a sparse file which can be
96 subsequently loopback-mounted.
97
98 When specifying multiple partitions in kickstart file, each partition
99 will be created as a separated loop image.
100 """
101
102 def __init__(self, creatoropts=None, pkgmgr=None,
103 compress_image=None,
104 shrink_image=False):
105 """Initialize a LoopImageCreator instance.
106
107 This method takes the same arguments as ImageCreator.__init__()
108 with the addition of:
109
110 fslabel -- A string used as a label for any filesystems created.
111 """
112
113 BaseImageCreator.__init__(self, creatoropts, pkgmgr)
114
115 self.compress_image = compress_image
116 self.shrink_image = shrink_image
117
118 self.__fslabel = None
119 self.fslabel = self.name
120
121 self.__blocksize = 4096
122 if self.ks:
123 self.__fstype = kickstart.get_image_fstype(self.ks,
124 "ext3")
125 self.__fsopts = kickstart.get_image_fsopts(self.ks,
126 "defaults,noatime")
127
128 allloops = []
129 for part in sorted(kickstart.get_partitions(self.ks),
130 key=lambda p: p.mountpoint):
131 if part.fstype == "swap":
132 continue
133
134 label = part.label
135 mp = part.mountpoint
136 if mp == '/':
137 # the base image
138 if not label:
139 label = self.name
140 else:
141 mp = mp.rstrip('/')
142 if not label:
143 msger.warning('no "label" specified for loop img at %s'
144 ', use the mountpoint as the name' % mp)
145 label = mp.split('/')[-1]
146
147 imgname = misc.strip_end(label, '.img') + '.img'
148 allloops.append({
149 'mountpoint': mp,
150 'label': label,
151 'name': imgname,
152 'size': part.size or 4096L * 1024 * 1024,
153 'fstype': part.fstype or 'ext3',
154 'extopts': part.extopts or None,
155 'loop': None, # to be created in _mount_instroot
156 })
157 self._instloops = allloops
158
159 else:
160 self.__fstype = None
161 self.__fsopts = None
162 self._instloops = []
163
164 self.__imgdir = None
165
166 if self.ks:
167 self.__image_size = kickstart.get_image_size(self.ks,
168 4096L * 1024 * 1024)
169 else:
170 self.__image_size = 0
171
172 self._img_name = self.name + ".img"
173
174 def get_image_names(self):
175 if not self._instloops:
176 return None
177
178 return [lo['name'] for lo in self._instloops]
179
180 def _set_fstype(self, fstype):
181 self.__fstype = fstype
182
183 def _set_image_size(self, imgsize):
184 self.__image_size = imgsize
185
186
187 #
188 # Properties
189 #
190 def __get_fslabel(self):
191 if self.__fslabel is None:
192 return self.name
193 else:
194 return self.__fslabel
195 def __set_fslabel(self, val):
196 if val is None:
197 self.__fslabel = None
198 else:
199 self.__fslabel = val[:FSLABEL_MAXLEN]
200 #A string used to label any filesystems created.
201 #
202 #Some filesystems impose a constraint on the maximum allowed size of the
203 #filesystem label. In the case of ext3 it's 16 characters, but in the case
204 #of ISO9660 it's 32 characters.
205 #
206 #mke2fs silently truncates the label, but mkisofs aborts if the label is
207 #too long. So, for convenience sake, any string assigned to this attribute
208 #is silently truncated to FSLABEL_MAXLEN (32) characters.
209 fslabel = property(__get_fslabel, __set_fslabel)
210
211 def __get_image(self):
212 if self.__imgdir is None:
213 raise CreatorError("_image is not valid before calling mount()")
214 return os.path.join(self.__imgdir, self._img_name)
215 #The location of the image file.
216 #
217 #This is the path to the filesystem image. Subclasses may use this path
218 #in order to package the image in _stage_final_image().
219 #
220 #Note, this directory does not exist before ImageCreator.mount() is called.
221 #
222 #Note also, this is a read-only attribute.
223 _image = property(__get_image)
224
225 def __get_blocksize(self):
226 return self.__blocksize
227 def __set_blocksize(self, val):
228 if self._instloops:
229 raise CreatorError("_blocksize must be set before calling mount()")
230 try:
231 self.__blocksize = int(val)
232 except ValueError:
233 raise CreatorError("'%s' is not a valid integer value "
234 "for _blocksize" % val)
235 #The block size used by the image's filesystem.
236 #
237 #This is the block size used when creating the filesystem image. Subclasses
238 #may change this if they wish to use something other than a 4k block size.
239 #
240 #Note, this attribute may only be set before calling mount().
241 _blocksize = property(__get_blocksize, __set_blocksize)
242
243 def __get_fstype(self):
244 return self.__fstype
245 def __set_fstype(self, val):
246 if val != "ext2" and val != "ext3":
247 raise CreatorError("Unknown _fstype '%s' supplied" % val)
248 self.__fstype = val
249 #The type of filesystem used for the image.
250 #
251 #This is the filesystem type used when creating the filesystem image.
252 #Subclasses may change this if they wish to use something other ext3.
253 #
254 #Note, only ext2 and ext3 are currently supported.
255 #
256 #Note also, this attribute may only be set before calling mount().
257 _fstype = property(__get_fstype, __set_fstype)
258
259 def __get_fsopts(self):
260 return self.__fsopts
261 def __set_fsopts(self, val):
262 self.__fsopts = val
263 #Mount options of filesystem used for the image.
264 #
265 #This can be specified by --fsoptions="xxx,yyy" in part command in
266 #kickstart file.
267 _fsopts = property(__get_fsopts, __set_fsopts)
268
269
270 #
271 # Helpers for subclasses
272 #
273 def _resparse(self, size=None):
274 """Rebuild the filesystem image to be as sparse as possible.
275
276 This method should be used by subclasses when staging the final image
277 in order to reduce the actual space taken up by the sparse image file
278 to be as little as possible.
279
280 This is done by resizing the filesystem to the minimal size (thereby
281 eliminating any space taken up by deleted files) and then resizing it
282 back to the supplied size.
283
284 size -- the size in, in bytes, which the filesystem image should be
285 resized to after it has been minimized; this defaults to None,
286 causing the original size specified by the kickstart file to
287 be used (or 4GiB if not specified in the kickstart).
288 """
289 minsize = 0
290 for item in self._instloops:
291 if item['name'] == self._img_name:
292 minsize = item['loop'].resparse(size)
293 else:
294 item['loop'].resparse(size)
295
296 return minsize
297
298 def _base_on(self, base_on=None):
299 if base_on and self._image != base_on:
300 shutil.copyfile(base_on, self._image)
301
302 def _check_imgdir(self):
303 if self.__imgdir is None:
304 self.__imgdir = self._mkdtemp()
305
306
307 #
308 # Actual implementation
309 #
310 def _mount_instroot(self, base_on=None):
311
312 if base_on and os.path.isfile(base_on):
313 self.__imgdir = os.path.dirname(base_on)
314 imgname = os.path.basename(base_on)
315 self._base_on(base_on)
316 self._set_image_size(misc.get_file_size(self._image))
317
318 # here, self._instloops must be []
319 self._instloops.append({
320 "mountpoint": "/",
321 "label": self.name,
322 "name": imgname,
323 "size": self.__image_size or 4096L,
324 "fstype": self.__fstype or "ext3",
325 "extopts": None,
326 "loop": None
327 })
328
329 self._check_imgdir()
330
331 for loop in self._instloops:
332 fstype = loop['fstype']
333 mp = os.path.join(self._instroot, loop['mountpoint'].lstrip('/'))
334 size = loop['size'] * 1024L * 1024L
335 imgname = loop['name']
336
337 if fstype in ("ext2", "ext3", "ext4"):
338 MyDiskMount = fs.ExtDiskMount
339 elif fstype == "btrfs":
340 MyDiskMount = fs.BtrfsDiskMount
341 elif fstype in ("vfat", "msdos"):
342 MyDiskMount = fs.VfatDiskMount
343 else:
344 msger.error('Cannot support fstype: %s' % fstype)
345
346 loop['loop'] = MyDiskMount(fs.SparseLoopbackDisk(
347 os.path.join(self.__imgdir, imgname),
348 size),
349 mp,
350 fstype,
351 self._blocksize,
352 loop['label'])
353
354 if fstype in ("ext2", "ext3", "ext4"):
355 loop['loop'].extopts = loop['extopts']
356
357 try:
358 msger.verbose('Mounting image "%s" on "%s"' % (imgname, mp))
359 fs.makedirs(mp)
360 loop['loop'].mount()
361 except MountError, e:
362 raise
363
364 def _unmount_instroot(self):
365 for item in reversed(self._instloops):
366 try:
367 item['loop'].cleanup()
368 except:
369 pass
370
371 def _stage_final_image(self):
372
373 if self.pack_to or self.shrink_image:
374 self._resparse(0)
375 else:
376 self._resparse()
377
378 for item in self._instloops:
379 imgfile = os.path.join(self.__imgdir, item['name'])
380 if item['fstype'] == "ext4":
381 runner.show('/sbin/tune2fs -O ^huge_file,extents,uninit_bg %s '
382 % imgfile)
383 if self.compress_image:
384 misc.compressing(imgfile, self.compress_image)
385
386 if not self.pack_to:
387 for item in os.listdir(self.__imgdir):
388 shutil.move(os.path.join(self.__imgdir, item),
389 os.path.join(self._outdir, item))
390 else:
391 msger.info("Pack all loop images together to %s" % self.pack_to)
392 dstfile = os.path.join(self._outdir, self.pack_to)
393 misc.packing(dstfile, self.__imgdir)
394
395 if self.pack_to:
396 mountfp_xml = os.path.splitext(self.pack_to)[0]
397 mountfp_xml = misc.strip_end(mountfp_xml, '.tar') + ".xml"
398 else:
399 mountfp_xml = self.name + ".xml"
400 # save mount points mapping file to xml
401 save_mountpoints(os.path.join(self._outdir, mountfp_xml),
402 self._instloops,
403 self.target_arch)
404
405 def copy_attachment(self):
406 if not hasattr(self, '_attachment') or not self._attachment:
407 return
408
409 self._check_imgdir()
410
411 msger.info("Copying attachment files...")
412 for item in self._attachment:
413 if not os.path.exists(item):
414 continue
415 dpath = os.path.join(self.__imgdir, os.path.basename(item))
416 msger.verbose("Copy attachment %s to %s" % (item, dpath))
417 shutil.copy(item, dpath)
418
diff --git a/scripts/lib/mic/imager/raw.py b/scripts/lib/mic/imager/raw.py
new file mode 100644
index 0000000000..838191a6f1
--- /dev/null
+++ b/scripts/lib/mic/imager/raw.py
@@ -0,0 +1,501 @@
1#!/usr/bin/python -tt
2#
3# Copyright (c) 2011 Intel, Inc.
4#
5# This program is free software; you can redistribute it and/or modify it
6# under the terms of the GNU General Public License as published by the Free
7# Software Foundation; version 2 of the License
8#
9# This program is distributed in the hope that it will be useful, but
10# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12# for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc., 59
16# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18import os
19import stat
20import shutil
21
22from mic import kickstart, msger
23from mic.utils import fs_related, runner, misc
24from mic.utils.partitionedfs import PartitionedMount
25from mic.utils.errors import CreatorError, MountError
26from mic.imager.baseimager import BaseImageCreator
27
28
29class RawImageCreator(BaseImageCreator):
30 """Installs a system into a file containing a partitioned disk image.
31
32 ApplianceImageCreator is an advanced ImageCreator subclass; a sparse file
33 is formatted with a partition table, each partition loopback mounted
34 and the system installed into an virtual disk. The disk image can
35 subsequently be booted in a virtual machine or accessed with kpartx
36 """
37
38 def __init__(self, creatoropts=None, pkgmgr=None, compress_image=None, generate_bmap=None, fstab_entry="uuid"):
39 """Initialize a ApplianceImageCreator instance.
40
41 This method takes the same arguments as ImageCreator.__init__()
42 """
43 BaseImageCreator.__init__(self, creatoropts, pkgmgr)
44
45 self.__instloop = None
46 self.__imgdir = None
47 self.__disks = {}
48 self.__disk_format = "raw"
49 self._disk_names = []
50 self._ptable_format = self.ks.handler.bootloader.ptable
51 self.vmem = 512
52 self.vcpu = 1
53 self.checksum = False
54 self.use_uuid = fstab_entry == "uuid"
55 self.appliance_version = None
56 self.appliance_release = None
57 self.compress_image = compress_image
58 self.bmap_needed = generate_bmap
59 self._need_extlinux = not kickstart.use_installerfw(self.ks, "extlinux")
60 #self.getsource = False
61 #self.listpkg = False
62
63 self._dep_checks.extend(["sync", "kpartx", "parted"])
64 if self._need_extlinux:
65 self._dep_checks.extend(["extlinux"])
66
67 def configure(self, repodata = None):
68 import subprocess
69 def chroot():
70 os.chroot(self._instroot)
71 os.chdir("/")
72
73 if os.path.exists(self._instroot + "/usr/bin/Xorg"):
74 subprocess.call(["/bin/chmod", "u+s", "/usr/bin/Xorg"],
75 preexec_fn = chroot)
76
77 BaseImageCreator.configure(self, repodata)
78
79 def _get_fstab(self):
80 if kickstart.use_installerfw(self.ks, "fstab"):
81 # The fstab file will be generated by installer framework scripts
82 # instead.
83 return None
84
85 s = ""
86 for mp in self.__instloop.mountOrder:
87 p = None
88 for p1 in self.__instloop.partitions:
89 if p1['mountpoint'] == mp:
90 p = p1
91 break
92
93 if self.use_uuid and p['uuid']:
94 device = "UUID=%s" % p['uuid']
95 else:
96 device = "/dev/%s%-d" % (p['disk_name'], p['num'])
97
98 s += "%(device)s %(mountpoint)s %(fstype)s %(fsopts)s 0 0\n" % {
99 'device': device,
100 'mountpoint': p['mountpoint'],
101 'fstype': p['fstype'],
102 'fsopts': "defaults,noatime" if not p['fsopts'] else p['fsopts']}
103
104 if p['mountpoint'] == "/":
105 for subvol in self.__instloop.subvolumes:
106 if subvol['mountpoint'] == "/":
107 continue
108 s += "%(device)s %(mountpoint)s %(fstype)s %(fsopts)s 0 0\n" % {
109 'device': "/dev/%s%-d" % (p['disk_name'], p['num']),
110 'mountpoint': subvol['mountpoint'],
111 'fstype': p['fstype'],
112 'fsopts': "defaults,noatime" if not subvol['fsopts'] else subvol['fsopts']}
113
114 s += "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
115 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
116 s += "proc /proc proc defaults 0 0\n"
117 s += "sysfs /sys sysfs defaults 0 0\n"
118 return s
119
120 def _create_mkinitrd_config(self):
121 """write to tell which modules to be included in initrd"""
122
123 mkinitrd = ""
124 mkinitrd += "PROBE=\"no\"\n"
125 mkinitrd += "MODULES+=\"ext3 ata_piix sd_mod libata scsi_mod\"\n"
126 mkinitrd += "rootfs=\"ext3\"\n"
127 mkinitrd += "rootopts=\"defaults\"\n"
128
129 msger.debug("Writing mkinitrd config %s/etc/sysconfig/mkinitrd" \
130 % self._instroot)
131 os.makedirs(self._instroot + "/etc/sysconfig/",mode=644)
132 cfg = open(self._instroot + "/etc/sysconfig/mkinitrd", "w")
133 cfg.write(mkinitrd)
134 cfg.close()
135
136 def _get_parts(self):
137 if not self.ks:
138 raise CreatorError("Failed to get partition info, "
139 "please check your kickstart setting.")
140
141 # Set a default partition if no partition is given out
142 if not self.ks.handler.partition.partitions:
143 partstr = "part / --size 1900 --ondisk sda --fstype=ext3"
144 args = partstr.split()
145 pd = self.ks.handler.partition.parse(args[1:])
146 if pd not in self.ks.handler.partition.partitions:
147 self.ks.handler.partition.partitions.append(pd)
148
149 # partitions list from kickstart file
150 return kickstart.get_partitions(self.ks)
151
152 def get_disk_names(self):
153 """ Returns a list of physical target disk names (e.g., 'sdb') which
154 will be created. """
155
156 if self._disk_names:
157 return self._disk_names
158
159 #get partition info from ks handler
160 parts = self._get_parts()
161
162 for i in range(len(parts)):
163 if parts[i].disk:
164 disk_name = parts[i].disk
165 else:
166 raise CreatorError("Failed to create disks, no --ondisk "
167 "specified in partition line of ks file")
168
169 if parts[i].mountpoint and not parts[i].fstype:
170 raise CreatorError("Failed to create disks, no --fstype "
171 "specified for partition with mountpoint "
172 "'%s' in the ks file")
173
174 self._disk_names.append(disk_name)
175
176 return self._disk_names
177
178 def _full_name(self, name, extention):
179 """ Construct full file name for a file we generate. """
180 return "%s-%s.%s" % (self.name, name, extention)
181
182 def _full_path(self, path, name, extention):
183 """ Construct full file path to a file we generate. """
184 return os.path.join(path, self._full_name(name, extention))
185
186 #
187 # Actual implemention
188 #
189 def _mount_instroot(self, base_on = None):
190 parts = self._get_parts()
191 self.__instloop = PartitionedMount(self._instroot)
192
193 for p in parts:
194 self.__instloop.add_partition(int(p.size),
195 p.disk,
196 p.mountpoint,
197 p.fstype,
198 p.label,
199 fsopts = p.fsopts,
200 boot = p.active,
201 align = p.align,
202 part_type = p.part_type)
203
204 self.__instloop.layout_partitions(self._ptable_format)
205
206 # Create the disks
207 self.__imgdir = self._mkdtemp()
208 for disk_name, disk in self.__instloop.disks.items():
209 full_path = self._full_path(self.__imgdir, disk_name, "raw")
210 msger.debug("Adding disk %s as %s with size %s bytes" \
211 % (disk_name, full_path, disk['min_size']))
212
213 disk_obj = fs_related.SparseLoopbackDisk(full_path,
214 disk['min_size'])
215 self.__disks[disk_name] = disk_obj
216 self.__instloop.add_disk(disk_name, disk_obj)
217
218 self.__instloop.mount()
219 self._create_mkinitrd_config()
220
221 def _get_required_packages(self):
222 required_packages = BaseImageCreator._get_required_packages(self)
223 if self._need_extlinux:
224 if not self.target_arch or not self.target_arch.startswith("arm"):
225 required_packages += ["syslinux", "syslinux-extlinux"]
226 return required_packages
227
228 def _get_excluded_packages(self):
229 return BaseImageCreator._get_excluded_packages(self)
230
231 def _get_syslinux_boot_config(self):
232 rootdev = None
233 root_part_uuid = None
234 for p in self.__instloop.partitions:
235 if p['mountpoint'] == "/":
236 rootdev = "/dev/%s%-d" % (p['disk_name'], p['num'])
237 root_part_uuid = p['partuuid']
238
239 return (rootdev, root_part_uuid)
240
241 def _create_syslinux_config(self):
242
243 splash = os.path.join(self._instroot, "boot/extlinux")
244 if os.path.exists(splash):
245 splashline = "menu background splash.jpg"
246 else:
247 splashline = ""
248
249 (rootdev, root_part_uuid) = self._get_syslinux_boot_config()
250 options = self.ks.handler.bootloader.appendLine
251
252 #XXX don't hardcode default kernel - see livecd code
253 syslinux_conf = ""
254 syslinux_conf += "prompt 0\n"
255 syslinux_conf += "timeout 1\n"
256 syslinux_conf += "\n"
257 syslinux_conf += "default vesamenu.c32\n"
258 syslinux_conf += "menu autoboot Starting %s...\n" % self.distro_name
259 syslinux_conf += "menu hidden\n"
260 syslinux_conf += "\n"
261 syslinux_conf += "%s\n" % splashline
262 syslinux_conf += "menu title Welcome to %s!\n" % self.distro_name
263 syslinux_conf += "menu color border 0 #ffffffff #00000000\n"
264 syslinux_conf += "menu color sel 7 #ffffffff #ff000000\n"
265 syslinux_conf += "menu color title 0 #ffffffff #00000000\n"
266 syslinux_conf += "menu color tabmsg 0 #ffffffff #00000000\n"
267 syslinux_conf += "menu color unsel 0 #ffffffff #00000000\n"
268 syslinux_conf += "menu color hotsel 0 #ff000000 #ffffffff\n"
269 syslinux_conf += "menu color hotkey 7 #ffffffff #ff000000\n"
270 syslinux_conf += "menu color timeout_msg 0 #ffffffff #00000000\n"
271 syslinux_conf += "menu color timeout 0 #ffffffff #00000000\n"
272 syslinux_conf += "menu color cmdline 0 #ffffffff #00000000\n"
273
274 versions = []
275 kernels = self._get_kernel_versions()
276 symkern = "%s/boot/vmlinuz" % self._instroot
277
278 if os.path.lexists(symkern):
279 v = os.path.realpath(symkern).replace('%s-' % symkern, "")
280 syslinux_conf += "label %s\n" % self.distro_name.lower()
281 syslinux_conf += "\tmenu label %s (%s)\n" % (self.distro_name, v)
282 syslinux_conf += "\tlinux ../vmlinuz\n"
283 if self._ptable_format == 'msdos':
284 rootstr = rootdev
285 else:
286 if not root_part_uuid:
287 raise MountError("Cannot find the root GPT partition UUID")
288 rootstr = "PARTUUID=%s" % root_part_uuid
289 syslinux_conf += "\tappend ro root=%s %s\n" % (rootstr, options)
290 syslinux_conf += "\tmenu default\n"
291 else:
292 for kernel in kernels:
293 for version in kernels[kernel]:
294 versions.append(version)
295
296 footlabel = 0
297 for v in versions:
298 syslinux_conf += "label %s%d\n" \
299 % (self.distro_name.lower(), footlabel)
300 syslinux_conf += "\tmenu label %s (%s)\n" % (self.distro_name, v)
301 syslinux_conf += "\tlinux ../vmlinuz-%s\n" % v
302 syslinux_conf += "\tappend ro root=%s %s\n" \
303 % (rootdev, options)
304 if footlabel == 0:
305 syslinux_conf += "\tmenu default\n"
306 footlabel += 1;
307
308 msger.debug("Writing syslinux config %s/boot/extlinux/extlinux.conf" \
309 % self._instroot)
310 cfg = open(self._instroot + "/boot/extlinux/extlinux.conf", "w")
311 cfg.write(syslinux_conf)
312 cfg.close()
313
314 def _install_syslinux(self):
315 for name in self.__disks.keys():
316 loopdev = self.__disks[name].device
317
318 # Set MBR
319 mbrfile = "%s/usr/share/syslinux/" % self._instroot
320 if self._ptable_format == 'gpt':
321 mbrfile += "gptmbr.bin"
322 else:
323 mbrfile += "mbr.bin"
324
325 msger.debug("Installing syslinux bootloader '%s' to %s" % \
326 (mbrfile, loopdev))
327
328 mbrsize = os.stat(mbrfile)[stat.ST_SIZE]
329 rc = runner.show(['dd', 'if=%s' % mbrfile, 'of=' + loopdev])
330 if rc != 0:
331 raise MountError("Unable to set MBR to %s" % loopdev)
332
333
334 # Ensure all data is flushed to disk before doing syslinux install
335 runner.quiet('sync')
336
337 fullpathsyslinux = fs_related.find_binary_path("extlinux")
338 rc = runner.show([fullpathsyslinux,
339 "-i",
340 "%s/boot/extlinux" % self._instroot])
341 if rc != 0:
342 raise MountError("Unable to install syslinux bootloader to %s" \
343 % loopdev)
344
345 def _create_bootconfig(self):
346 #If syslinux is available do the required configurations.
347 if self._need_extlinux \
348 and os.path.exists("%s/usr/share/syslinux/" % (self._instroot)) \
349 and os.path.exists("%s/boot/extlinux/" % (self._instroot)):
350 self._create_syslinux_config()
351 self._install_syslinux()
352
353 def _unmount_instroot(self):
354 if not self.__instloop is None:
355 try:
356 self.__instloop.cleanup()
357 except MountError, err:
358 msger.warning("%s" % err)
359
360 def _resparse(self, size = None):
361 return self.__instloop.resparse(size)
362
363 def _get_post_scripts_env(self, in_chroot):
364 env = BaseImageCreator._get_post_scripts_env(self, in_chroot)
365
366 # Export the file-system UUIDs and partition UUIDs (AKA PARTUUIDs)
367 for p in self.__instloop.partitions:
368 env.update(self._set_part_env(p['ks_pnum'], "UUID", p['uuid']))
369 env.update(self._set_part_env(p['ks_pnum'], "PARTUUID", p['partuuid']))
370
371 return env
372
373 def _stage_final_image(self):
374 """Stage the final system image in _outdir.
375 write meta data
376 """
377 self._resparse()
378
379 if self.compress_image:
380 for imgfile in os.listdir(self.__imgdir):
381 if imgfile.endswith('.raw') or imgfile.endswith('bin'):
382 imgpath = os.path.join(self.__imgdir, imgfile)
383 misc.compressing(imgpath, self.compress_image)
384
385 if self.pack_to:
386 dst = os.path.join(self._outdir, self.pack_to)
387 msger.info("Pack all raw images to %s" % dst)
388 misc.packing(dst, self.__imgdir)
389 else:
390 msger.debug("moving disks to stage location")
391 for imgfile in os.listdir(self.__imgdir):
392 src = os.path.join(self.__imgdir, imgfile)
393 dst = os.path.join(self._outdir, imgfile)
394 msger.debug("moving %s to %s" % (src,dst))
395 shutil.move(src,dst)
396 self._write_image_xml()
397
398 def _write_image_xml(self):
399 imgarch = "i686"
400 if self.target_arch and self.target_arch.startswith("arm"):
401 imgarch = "arm"
402 xml = "<image>\n"
403
404 name_attributes = ""
405 if self.appliance_version:
406 name_attributes += " version='%s'" % self.appliance_version
407 if self.appliance_release:
408 name_attributes += " release='%s'" % self.appliance_release
409 xml += " <name%s>%s</name>\n" % (name_attributes, self.name)
410 xml += " <domain>\n"
411 # XXX don't hardcode - determine based on the kernel we installed for
412 # grub baremetal vs xen
413 xml += " <boot type='hvm'>\n"
414 xml += " <guest>\n"
415 xml += " <arch>%s</arch>\n" % imgarch
416 xml += " </guest>\n"
417 xml += " <os>\n"
418 xml += " <loader dev='hd'/>\n"
419 xml += " </os>\n"
420
421 i = 0
422 for name in self.__disks.keys():
423 full_name = self._full_name(name, self.__disk_format)
424 xml += " <drive disk='%s' target='hd%s'/>\n" \
425 % (full_name, chr(ord('a') + i))
426 i = i + 1
427
428 xml += " </boot>\n"
429 xml += " <devices>\n"
430 xml += " <vcpu>%s</vcpu>\n" % self.vcpu
431 xml += " <memory>%d</memory>\n" %(self.vmem * 1024)
432 for network in self.ks.handler.network.network:
433 xml += " <interface/>\n"
434 xml += " <graphics/>\n"
435 xml += " </devices>\n"
436 xml += " </domain>\n"
437 xml += " <storage>\n"
438
439 if self.checksum is True:
440 for name in self.__disks.keys():
441 diskpath = self._full_path(self._outdir, name, \
442 self.__disk_format)
443 full_name = self._full_name(name, self.__disk_format)
444
445 msger.debug("Generating disk signature for %s" % full_name)
446
447 xml += " <disk file='%s' use='system' format='%s'>\n" \
448 % (full_name, self.__disk_format)
449
450 hashes = misc.calc_hashes(diskpath, ('sha1', 'sha256'))
451
452 xml += " <checksum type='sha1'>%s</checksum>\n" \
453 % hashes[0]
454 xml += " <checksum type='sha256'>%s</checksum>\n" \
455 % hashes[1]
456 xml += " </disk>\n"
457 else:
458 for name in self.__disks.keys():
459 full_name = self._full_name(name, self.__disk_format)
460 xml += " <disk file='%s' use='system' format='%s'/>\n" \
461 % (full_name, self.__disk_format)
462
463 xml += " </storage>\n"
464 xml += "</image>\n"
465
466 msger.debug("writing image XML to %s/%s.xml" %(self._outdir, self.name))
467 cfg = open("%s/%s.xml" % (self._outdir, self.name), "w")
468 cfg.write(xml)
469 cfg.close()
470
471 def generate_bmap(self):
472 """ Generate block map file for the image. The idea is that while disk
473 images we generate may be large (e.g., 4GiB), they may actually contain
474 only little real data, e.g., 512MiB. This data are files, directories,
475 file-system meta-data, partition table, etc. In other words, when
476 flashing the image to the target device, you do not have to copy all the
477 4GiB of data, you can copy only 512MiB of it, which is 4 times faster.
478
479 This function generates the block map file for an arbitrary image that
480 mic has generated. The block map file is basically an XML file which
481 contains a list of blocks which have to be copied to the target device.
482 The other blocks are not used and there is no need to copy them. """
483
484 if self.bmap_needed is None:
485 return
486
487 from mic.utils import BmapCreate
488 msger.info("Generating the map file(s)")
489
490 for name in self.__disks.keys():
491 image = self._full_path(self.__imgdir, name, self.__disk_format)
492 bmap_file = self._full_path(self._outdir, name, "bmap")
493
494 msger.debug("Generating block map file '%s'" % bmap_file)
495
496 try:
497 creator = BmapCreate.BmapCreate(image, bmap_file)
498 creator.generate()
499 del creator
500 except BmapCreate.Error as err:
501 raise CreatorError("Failed to create bmap file: %s" % str(err))