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.py1263
-rw-r--r--scripts/lib/mic/imager/direct.py384
-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, 3723 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..b7212493b4
--- /dev/null
+++ b/scripts/lib/mic/imager/baseimager.py
@@ -0,0 +1,1263 @@
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
29from mic import kickstart
30from mic import msger
31from mic.utils.errors import CreatorError, Abort
32from mic.utils import misc, runner, fs_related as fs
33
34class BaseImageCreator(object):
35 """Installs a system to a chroot directory.
36
37 ImageCreator is the simplest creator class available; it will install and
38 configure a system image according to the supplied kickstart file.
39
40 e.g.
41
42 import mic.imgcreate as imgcreate
43 ks = imgcreate.read_kickstart("foo.ks")
44 imgcreate.ImageCreator(ks, "foo").create()
45
46 """
47
48 def __del__(self):
49 self.cleanup()
50
51 def __init__(self, createopts = None, pkgmgr = None):
52 """Initialize an ImageCreator instance.
53
54 ks -- a pykickstart.KickstartParser instance; this instance will be
55 used to drive the install by e.g. providing the list of packages
56 to be installed, the system configuration and %post scripts
57
58 name -- a name for the image; used for e.g. image filenames or
59 filesystem labels
60 """
61
62 self.pkgmgr = pkgmgr
63
64 self.__builddir = None
65 self.__bindmounts = []
66
67 self.ks = None
68 self.name = "target"
69 self.tmpdir = "/var/tmp/wic"
70 self.cachedir = "/var/tmp/wic/cache"
71 self.workdir = "/var/tmp/wic/build"
72
73 self.destdir = "."
74 self.installerfw_prefix = "INSTALLERFW_"
75 self.target_arch = "noarch"
76 self._local_pkgs_path = None
77 self.pack_to = None
78 self.repourl = {}
79
80 # If the kernel is save to the destdir when copy_kernel cmd is called.
81 self._need_copy_kernel = False
82 # setup tmpfs tmpdir when enabletmpfs is True
83 self.enabletmpfs = False
84
85 if createopts:
86 # Mapping table for variables that have different names.
87 optmap = {"pkgmgr" : "pkgmgr_name",
88 "outdir" : "destdir",
89 "arch" : "target_arch",
90 "local_pkgs_path" : "_local_pkgs_path",
91 "copy_kernel" : "_need_copy_kernel",
92 }
93
94 # update setting from createopts
95 for key in createopts.keys():
96 if key in optmap:
97 option = optmap[key]
98 else:
99 option = key
100 setattr(self, option, createopts[key])
101
102 self.destdir = os.path.abspath(os.path.expanduser(self.destdir))
103
104 if 'release' in createopts and createopts['release']:
105 self.name = createopts['release'] + '_' + self.name
106
107 if self.pack_to:
108 if '@NAME@' in self.pack_to:
109 self.pack_to = self.pack_to.replace('@NAME@', self.name)
110 (tar, ext) = os.path.splitext(self.pack_to)
111 if ext in (".gz", ".bz2") and tar.endswith(".tar"):
112 ext = ".tar" + ext
113 if ext not in misc.pack_formats:
114 self.pack_to += ".tar"
115
116 self._dep_checks = ["ls", "bash", "cp", "echo", "modprobe"]
117
118 # Output image file names
119 self.outimage = []
120
121 # A flag to generate checksum
122 self._genchecksum = False
123
124 self._alt_initrd_name = None
125
126 self._recording_pkgs = []
127
128 # available size in root fs, init to 0
129 self._root_fs_avail = 0
130
131 # Name of the disk image file that is created.
132 self._img_name = None
133
134 self.image_format = None
135
136 # Save qemu emulator file name in order to clean up it finally
137 self.qemu_emulator = None
138
139 # No ks provided when called by convertor, so skip the dependency check
140 if self.ks:
141 # If we have btrfs partition we need to check necessary tools
142 for part in self.ks.handler.partition.partitions:
143 if part.fstype and part.fstype == "btrfs":
144 self._dep_checks.append("mkfs.btrfs")
145 break
146
147 if self.target_arch and self.target_arch.startswith("arm"):
148 for dep in self._dep_checks:
149 if dep == "extlinux":
150 self._dep_checks.remove(dep)
151
152 if not os.path.exists("/usr/bin/qemu-arm") or \
153 not misc.is_statically_linked("/usr/bin/qemu-arm"):
154 self._dep_checks.append("qemu-arm-static")
155
156 if os.path.exists("/proc/sys/vm/vdso_enabled"):
157 vdso_fh = open("/proc/sys/vm/vdso_enabled","r")
158 vdso_value = vdso_fh.read().strip()
159 vdso_fh.close()
160 if (int)(vdso_value) == 1:
161 msger.warning("vdso is enabled on your host, which might "
162 "cause problems with arm emulations.\n"
163 "\tYou can disable vdso with following command before "
164 "starting image build:\n"
165 "\techo 0 | sudo tee /proc/sys/vm/vdso_enabled")
166
167 # make sure the specified tmpdir and cachedir exist
168 if not os.path.exists(self.tmpdir):
169 os.makedirs(self.tmpdir)
170 if not os.path.exists(self.cachedir):
171 os.makedirs(self.cachedir)
172
173
174 #
175 # Properties
176 #
177 def __get_instroot(self):
178 if self.__builddir is None:
179 raise CreatorError("_instroot is not valid before calling mount()")
180 return self.__builddir + "/install_root"
181 _instroot = property(__get_instroot)
182 """The location of the install root directory.
183
184 This is the directory into which the system is installed. Subclasses may
185 mount a filesystem image here or copy files to/from here.
186
187 Note, this directory does not exist before ImageCreator.mount() is called.
188
189 Note also, this is a read-only attribute.
190
191 """
192
193 def __get_outdir(self):
194 if self.__builddir is None:
195 raise CreatorError("_outdir is not valid before calling mount()")
196 return self.__builddir + "/out"
197 _outdir = property(__get_outdir)
198 """The staging location for the final image.
199
200 This is where subclasses should stage any files that are part of the final
201 image. ImageCreator.package() will copy any files found here into the
202 requested destination directory.
203
204 Note, this directory does not exist before ImageCreator.mount() is called.
205
206 Note also, this is a read-only attribute.
207
208 """
209
210
211 #
212 # Hooks for subclasses
213 #
214 def _mount_instroot(self, base_on = None):
215 """Mount or prepare the install root directory.
216
217 This is the hook where subclasses may prepare the install root by e.g.
218 mounting creating and loopback mounting a filesystem image to
219 _instroot.
220
221 There is no default implementation.
222
223 base_on -- this is the value passed to mount() and can be interpreted
224 as the subclass wishes; it might e.g. be the location of
225 a previously created ISO containing a system image.
226
227 """
228 pass
229
230 def _unmount_instroot(self):
231 """Undo anything performed in _mount_instroot().
232
233 This is the hook where subclasses must undo anything which was done
234 in _mount_instroot(). For example, if a filesystem image was mounted
235 onto _instroot, it should be unmounted here.
236
237 There is no default implementation.
238
239 """
240 pass
241
242 def _create_bootconfig(self):
243 """Configure the image so that it's bootable.
244
245 This is the hook where subclasses may prepare the image for booting by
246 e.g. creating an initramfs and bootloader configuration.
247
248 This hook is called while the install root is still mounted, after the
249 packages have been installed and the kickstart configuration has been
250 applied, but before the %post scripts have been executed.
251
252 There is no default implementation.
253
254 """
255 pass
256
257 def _stage_final_image(self):
258 """Stage the final system image in _outdir.
259
260 This is the hook where subclasses should place the image in _outdir
261 so that package() can copy it to the requested destination directory.
262
263 By default, this moves the install root into _outdir.
264
265 """
266 shutil.move(self._instroot, self._outdir + "/" + self.name)
267
268 def get_installed_packages(self):
269 return self._pkgs_content.keys()
270
271 def _save_recording_pkgs(self, destdir):
272 """Save the list or content of installed packages to file.
273 """
274 pkgs = self._pkgs_content.keys()
275 pkgs.sort() # inplace op
276
277 if not os.path.exists(destdir):
278 os.makedirs(destdir)
279
280 content = None
281 if 'vcs' in self._recording_pkgs:
282 vcslst = ["%s %s" % (k, v) for (k, v) in self._pkgs_vcsinfo.items()]
283 content = '\n'.join(sorted(vcslst))
284 elif 'name' in self._recording_pkgs:
285 content = '\n'.join(pkgs)
286 if content:
287 namefile = os.path.join(destdir, self.name + '.packages')
288 f = open(namefile, "w")
289 f.write(content)
290 f.close()
291 self.outimage.append(namefile);
292
293 # if 'content', save more details
294 if 'content' in self._recording_pkgs:
295 contfile = os.path.join(destdir, self.name + '.files')
296 f = open(contfile, "w")
297
298 for pkg in pkgs:
299 content = pkg + '\n'
300
301 pkgcont = self._pkgs_content[pkg]
302 content += ' '
303 content += '\n '.join(pkgcont)
304 content += '\n'
305
306 content += '\n'
307 f.write(content)
308 f.close()
309 self.outimage.append(contfile)
310
311 if 'license' in self._recording_pkgs:
312 licensefile = os.path.join(destdir, self.name + '.license')
313 f = open(licensefile, "w")
314
315 f.write('Summary:\n')
316 for license in reversed(sorted(self._pkgs_license, key=\
317 lambda license: len(self._pkgs_license[license]))):
318 f.write(" - %s: %s\n" \
319 % (license, len(self._pkgs_license[license])))
320
321 f.write('\nDetails:\n')
322 for license in reversed(sorted(self._pkgs_license, key=\
323 lambda license: len(self._pkgs_license[license]))):
324 f.write(" - %s:\n" % (license))
325 for pkg in sorted(self._pkgs_license[license]):
326 f.write(" - %s\n" % (pkg))
327 f.write('\n')
328
329 f.close()
330 self.outimage.append(licensefile)
331
332 def _get_required_packages(self):
333 """Return a list of required packages.
334
335 This is the hook where subclasses may specify a set of packages which
336 it requires to be installed.
337
338 This returns an empty list by default.
339
340 Note, subclasses should usually chain up to the base class
341 implementation of this hook.
342
343 """
344 return []
345
346 def _get_excluded_packages(self):
347 """Return a list of excluded packages.
348
349 This is the hook where subclasses may specify a set of packages which
350 it requires _not_ to be installed.
351
352 This returns an empty list by default.
353
354 Note, subclasses should usually chain up to the base class
355 implementation of this hook.
356
357 """
358 return []
359
360 def _get_local_packages(self):
361 """Return a list of rpm path to be local installed.
362
363 This is the hook where subclasses may specify a set of rpms which
364 it requires to be installed locally.
365
366 This returns an empty list by default.
367
368 Note, subclasses should usually chain up to the base class
369 implementation of this hook.
370
371 """
372 if self._local_pkgs_path:
373 if os.path.isdir(self._local_pkgs_path):
374 return glob.glob(
375 os.path.join(self._local_pkgs_path, '*.rpm'))
376 elif os.path.splitext(self._local_pkgs_path)[-1] == '.rpm':
377 return [self._local_pkgs_path]
378
379 return []
380
381 def _get_fstab(self):
382 """Return the desired contents of /etc/fstab.
383
384 This is the hook where subclasses may specify the contents of
385 /etc/fstab by returning a string containing the desired contents.
386
387 A sensible default implementation is provided.
388
389 """
390 s = "/dev/root / %s %s 0 0\n" \
391 % (self._fstype,
392 "defaults,noatime" if not self._fsopts else self._fsopts)
393 s += self._get_fstab_special()
394 return s
395
396 def _get_fstab_special(self):
397 s = "devpts /dev/pts devpts gid=5,mode=620 0 0\n"
398 s += "tmpfs /dev/shm tmpfs defaults 0 0\n"
399 s += "proc /proc proc defaults 0 0\n"
400 s += "sysfs /sys sysfs defaults 0 0\n"
401 return s
402
403 def _set_part_env(self, pnum, prop, value):
404 """ This is a helper function which generates an environment variable
405 for a property "prop" with value "value" of a partition number "pnum".
406
407 The naming convention is:
408 * Variables start with INSTALLERFW_PART
409 * Then goes the partition number, the order is the same as
410 specified in the KS file
411 * Then goes the property name
412 """
413
414 if value == None:
415 value = ""
416 else:
417 value = str(value)
418
419 name = self.installerfw_prefix + ("PART%d_" % pnum) + prop
420 return { name : value }
421
422 def _get_post_scripts_env(self, in_chroot):
423 """Return an environment dict for %post scripts.
424
425 This is the hook where subclasses may specify some environment
426 variables for %post scripts by return a dict containing the desired
427 environment.
428
429 in_chroot -- whether this %post script is to be executed chroot()ed
430 into _instroot.
431 """
432
433 env = {}
434 pnum = 0
435
436 for p in kickstart.get_partitions(self.ks):
437 env.update(self._set_part_env(pnum, "SIZE", p.size))
438 env.update(self._set_part_env(pnum, "MOUNTPOINT", p.mountpoint))
439 env.update(self._set_part_env(pnum, "FSTYPE", p.fstype))
440 env.update(self._set_part_env(pnum, "LABEL", p.label))
441 env.update(self._set_part_env(pnum, "FSOPTS", p.fsopts))
442 env.update(self._set_part_env(pnum, "BOOTFLAG", p.active))
443 env.update(self._set_part_env(pnum, "ALIGN", p.align))
444 env.update(self._set_part_env(pnum, "TYPE_ID", p.part_type))
445 env.update(self._set_part_env(pnum, "DEVNODE",
446 "/dev/%s%d" % (p.disk, pnum + 1)))
447 pnum += 1
448
449 # Count of paritions
450 env[self.installerfw_prefix + "PART_COUNT"] = str(pnum)
451
452 # Partition table format
453 ptable_format = self.ks.handler.bootloader.ptable
454 env[self.installerfw_prefix + "PTABLE_FORMAT"] = ptable_format
455
456 # The kerned boot parameters
457 kernel_opts = self.ks.handler.bootloader.appendLine
458 env[self.installerfw_prefix + "KERNEL_OPTS"] = kernel_opts
459
460 # Name of the distribution
461 env[self.installerfw_prefix + "DISTRO_NAME"] = self.distro_name
462
463 # Name of the image creation tool
464 env[self.installerfw_prefix + "INSTALLER_NAME"] = "wic"
465
466 # The real current location of the mounted file-systems
467 if in_chroot:
468 mount_prefix = "/"
469 else:
470 mount_prefix = self._instroot
471 env[self.installerfw_prefix + "MOUNT_PREFIX"] = mount_prefix
472
473 # These are historical variables which lack the common name prefix
474 if not in_chroot:
475 env["INSTALL_ROOT"] = self._instroot
476 env["IMG_NAME"] = self._name
477
478 return env
479
480 def __get_imgname(self):
481 return self.name
482 _name = property(__get_imgname)
483 """The name of the image file.
484
485 """
486
487 def _get_kernel_versions(self):
488 """Return a dict detailing the available kernel types/versions.
489
490 This is the hook where subclasses may override what kernel types and
491 versions should be available for e.g. creating the booloader
492 configuration.
493
494 A dict should be returned mapping the available kernel types to a list
495 of the available versions for those kernels.
496
497 The default implementation uses rpm to iterate over everything
498 providing 'kernel', finds /boot/vmlinuz-* and returns the version
499 obtained from the vmlinuz filename. (This can differ from the kernel
500 RPM's n-v-r in the case of e.g. xen)
501
502 """
503 def get_kernel_versions(instroot):
504 ret = {}
505 versions = set()
506 files = glob.glob(instroot + "/boot/vmlinuz-*")
507 for file in files:
508 version = os.path.basename(file)[8:]
509 if version is None:
510 continue
511 versions.add(version)
512 ret["kernel"] = list(versions)
513 return ret
514
515 def get_version(header):
516 version = None
517 for f in header['filenames']:
518 if f.startswith('/boot/vmlinuz-'):
519 version = f[14:]
520 return version
521
522 if self.ks is None:
523 return get_kernel_versions(self._instroot)
524
525 ts = rpm.TransactionSet(self._instroot)
526
527 ret = {}
528 for header in ts.dbMatch('provides', 'kernel'):
529 version = get_version(header)
530 if version is None:
531 continue
532
533 name = header['name']
534 if not name in ret:
535 ret[name] = [version]
536 elif not version in ret[name]:
537 ret[name].append(version)
538
539 return ret
540
541
542 #
543 # Helpers for subclasses
544 #
545 def _do_bindmounts(self):
546 """Mount various system directories onto _instroot.
547
548 This method is called by mount(), but may also be used by subclasses
549 in order to re-mount the bindmounts after modifying the underlying
550 filesystem.
551
552 """
553 for b in self.__bindmounts:
554 b.mount()
555
556 def _undo_bindmounts(self):
557 """Unmount the bind-mounted system directories from _instroot.
558
559 This method is usually only called by unmount(), but may also be used
560 by subclasses in order to gain access to the filesystem obscured by
561 the bindmounts - e.g. in order to create device nodes on the image
562 filesystem.
563
564 """
565 self.__bindmounts.reverse()
566 for b in self.__bindmounts:
567 b.unmount()
568
569 def _chroot(self):
570 """Chroot into the install root.
571
572 This method may be used by subclasses when executing programs inside
573 the install root e.g.
574
575 subprocess.call(["/bin/ls"], preexec_fn = self.chroot)
576
577 """
578 os.chroot(self._instroot)
579 os.chdir("/")
580
581 def _mkdtemp(self, prefix = "tmp-"):
582 """Create a temporary directory.
583
584 This method may be used by subclasses to create a temporary directory
585 for use in building the final image - e.g. a subclass might create
586 a temporary directory in order to bundle a set of files into a package.
587
588 The subclass may delete this directory if it wishes, but it will be
589 automatically deleted by cleanup().
590
591 The absolute path to the temporary directory is returned.
592
593 Note, this method should only be called after mount() has been called.
594
595 prefix -- a prefix which should be used when creating the directory;
596 defaults to "tmp-".
597
598 """
599 self.__ensure_builddir()
600 return tempfile.mkdtemp(dir = self.__builddir, prefix = prefix)
601
602 def _mkstemp(self, prefix = "tmp-"):
603 """Create a temporary file.
604
605 This method may be used by subclasses to create a temporary file
606 for use in building the final image - e.g. a subclass might need
607 a temporary location to unpack a compressed file.
608
609 The subclass may delete this file if it wishes, but it will be
610 automatically deleted by cleanup().
611
612 A tuple containing a file descriptor (returned from os.open() and the
613 absolute path to the temporary directory is returned.
614
615 Note, this method should only be called after mount() has been called.
616
617 prefix -- a prefix which should be used when creating the file;
618 defaults to "tmp-".
619
620 """
621 self.__ensure_builddir()
622 return tempfile.mkstemp(dir = self.__builddir, prefix = prefix)
623
624 def _mktemp(self, prefix = "tmp-"):
625 """Create a temporary file.
626
627 This method simply calls _mkstemp() and closes the returned file
628 descriptor.
629
630 The absolute path to the temporary file is returned.
631
632 Note, this method should only be called after mount() has been called.
633
634 prefix -- a prefix which should be used when creating the file;
635 defaults to "tmp-".
636
637 """
638
639 (f, path) = self._mkstemp(prefix)
640 os.close(f)
641 return path
642
643
644 #
645 # Actual implementation
646 #
647 def __ensure_builddir(self):
648 if not self.__builddir is None:
649 return
650
651 try:
652 self.workdir = os.path.join(self.tmpdir, "build")
653 if not os.path.exists(self.workdir):
654 os.makedirs(self.workdir)
655 self.__builddir = tempfile.mkdtemp(dir = self.workdir,
656 prefix = "imgcreate-")
657 except OSError, (err, msg):
658 raise CreatorError("Failed create build directory in %s: %s" %
659 (self.tmpdir, msg))
660
661 def get_cachedir(self, cachedir = None):
662 if self.cachedir:
663 return self.cachedir
664
665 self.__ensure_builddir()
666 if cachedir:
667 self.cachedir = cachedir
668 else:
669 self.cachedir = self.__builddir + "/wic-cache"
670 fs.makedirs(self.cachedir)
671 return self.cachedir
672
673 def __sanity_check(self):
674 """Ensure that the config we've been given is sane."""
675 if not (kickstart.get_packages(self.ks) or
676 kickstart.get_groups(self.ks)):
677 raise CreatorError("No packages or groups specified")
678
679 kickstart.convert_method_to_repo(self.ks)
680
681 if not kickstart.get_repos(self.ks):
682 raise CreatorError("No repositories specified")
683
684 def __write_fstab(self):
685 fstab_contents = self._get_fstab()
686 if fstab_contents:
687 fstab = open(self._instroot + "/etc/fstab", "w")
688 fstab.write(fstab_contents)
689 fstab.close()
690
691 def __create_minimal_dev(self):
692 """Create a minimal /dev so that we don't corrupt the host /dev"""
693 origumask = os.umask(0000)
694 devices = (('null', 1, 3, 0666),
695 ('urandom',1, 9, 0666),
696 ('random', 1, 8, 0666),
697 ('full', 1, 7, 0666),
698 ('ptmx', 5, 2, 0666),
699 ('tty', 5, 0, 0666),
700 ('zero', 1, 5, 0666))
701
702 links = (("/proc/self/fd", "/dev/fd"),
703 ("/proc/self/fd/0", "/dev/stdin"),
704 ("/proc/self/fd/1", "/dev/stdout"),
705 ("/proc/self/fd/2", "/dev/stderr"))
706
707 for (node, major, minor, perm) in devices:
708 if not os.path.exists(self._instroot + "/dev/" + node):
709 os.mknod(self._instroot + "/dev/" + node,
710 perm | stat.S_IFCHR,
711 os.makedev(major,minor))
712
713 for (src, dest) in links:
714 if not os.path.exists(self._instroot + dest):
715 os.symlink(src, self._instroot + dest)
716
717 os.umask(origumask)
718
719 def __setup_tmpdir(self):
720 if not self.enabletmpfs:
721 return
722
723 runner.show('mount -t tmpfs -o size=4G tmpfs %s' % self.workdir)
724
725 def __clean_tmpdir(self):
726 if not self.enabletmpfs:
727 return
728
729 runner.show('umount -l %s' % self.workdir)
730
731 def mount(self, base_on = None, cachedir = None):
732 """Setup the target filesystem in preparation for an install.
733
734 This function sets up the filesystem which the ImageCreator will
735 install into and configure. The ImageCreator class merely creates an
736 install root directory, bind mounts some system directories (e.g. /dev)
737 and writes out /etc/fstab. Other subclasses may also e.g. create a
738 sparse file, format it and loopback mount it to the install root.
739
740 base_on -- a previous install on which to base this install; defaults
741 to None, causing a new image to be created
742
743 cachedir -- a directory in which to store the Yum cache; defaults to
744 None, causing a new cache to be created; by setting this
745 to another directory, the same cache can be reused across
746 multiple installs.
747
748 """
749 self.__setup_tmpdir()
750 self.__ensure_builddir()
751
752 self._mount_instroot(base_on)
753
754 def unmount(self):
755 """Unmounts the target filesystem.
756
757 The ImageCreator class detaches the system from the install root, but
758 other subclasses may also detach the loopback mounted filesystem image
759 from the install root.
760
761 """
762 self._unmount_instroot()
763
764
765 def cleanup(self):
766 """Unmounts the target filesystem and deletes temporary files.
767
768 This method calls unmount() and then deletes any temporary files and
769 directories that were created on the host system while building the
770 image.
771
772 Note, make sure to call this method once finished with the creator
773 instance in order to ensure no stale files are left on the host e.g.:
774
775 creator = ImageCreator(ks, name)
776 try:
777 creator.create()
778 finally:
779 creator.cleanup()
780
781 """
782 if not self.__builddir:
783 return
784
785 self.unmount()
786
787 shutil.rmtree(self.__builddir, ignore_errors = True)
788 self.__builddir = None
789
790 self.__clean_tmpdir()
791
792 def __is_excluded_pkg(self, pkg):
793 if pkg in self._excluded_pkgs:
794 self._excluded_pkgs.remove(pkg)
795 return True
796
797 for xpkg in self._excluded_pkgs:
798 if xpkg.endswith('*'):
799 if pkg.startswith(xpkg[:-1]):
800 return True
801 elif xpkg.startswith('*'):
802 if pkg.endswith(xpkg[1:]):
803 return True
804
805 return None
806
807 def __select_packages(self, pkg_manager):
808 skipped_pkgs = []
809 for pkg in self._required_pkgs:
810 e = pkg_manager.selectPackage(pkg)
811 if e:
812 if kickstart.ignore_missing(self.ks):
813 skipped_pkgs.append(pkg)
814 elif self.__is_excluded_pkg(pkg):
815 skipped_pkgs.append(pkg)
816 else:
817 raise CreatorError("Failed to find package '%s' : %s" %
818 (pkg, e))
819
820 for pkg in skipped_pkgs:
821 msger.warning("Skipping missing package '%s'" % (pkg,))
822
823 def __select_groups(self, pkg_manager):
824 skipped_groups = []
825 for group in self._required_groups:
826 e = pkg_manager.selectGroup(group.name, group.include)
827 if e:
828 if kickstart.ignore_missing(self.ks):
829 skipped_groups.append(group)
830 else:
831 raise CreatorError("Failed to find group '%s' : %s" %
832 (group.name, e))
833
834 for group in skipped_groups:
835 msger.warning("Skipping missing group '%s'" % (group.name,))
836
837 def __deselect_packages(self, pkg_manager):
838 for pkg in self._excluded_pkgs:
839 pkg_manager.deselectPackage(pkg)
840
841 def __localinst_packages(self, pkg_manager):
842 for rpm_path in self._get_local_packages():
843 pkg_manager.installLocal(rpm_path)
844
845 def __preinstall_packages(self, pkg_manager):
846 if not self.ks:
847 return
848
849 self._preinstall_pkgs = kickstart.get_pre_packages(self.ks)
850 for pkg in self._preinstall_pkgs:
851 pkg_manager.preInstall(pkg)
852
853 def __attachment_packages(self, pkg_manager):
854 if not self.ks:
855 return
856
857 self._attachment = []
858 for item in kickstart.get_attachment(self.ks):
859 if item.startswith('/'):
860 fpaths = os.path.join(self._instroot, item.lstrip('/'))
861 for fpath in glob.glob(fpaths):
862 self._attachment.append(fpath)
863 continue
864
865 filelist = pkg_manager.getFilelist(item)
866 if filelist:
867 # found rpm in rootfs
868 for pfile in pkg_manager.getFilelist(item):
869 fpath = os.path.join(self._instroot, pfile.lstrip('/'))
870 self._attachment.append(fpath)
871 continue
872
873 # try to retrieve rpm file
874 (url, proxies) = pkg_manager.package_url(item)
875 if not url:
876 msger.warning("Can't get url from repo for %s" % item)
877 continue
878 fpath = os.path.join(self.cachedir, os.path.basename(url))
879 if not os.path.exists(fpath):
880 # download pkgs
881 try:
882 fpath = grabber.myurlgrab(url, fpath, proxies, None)
883 except CreatorError:
884 raise
885
886 tmpdir = self._mkdtemp()
887 misc.extract_rpm(fpath, tmpdir)
888 for (root, dirs, files) in os.walk(tmpdir):
889 for fname in files:
890 fpath = os.path.join(root, fname)
891 self._attachment.append(fpath)
892
893 def install(self, repo_urls=None):
894 """Install packages into the install root.
895
896 This function installs the packages listed in the supplied kickstart
897 into the install root. By default, the packages are installed from the
898 repository URLs specified in the kickstart.
899
900 repo_urls -- a dict which maps a repository name to a repository URL;
901 if supplied, this causes any repository URLs specified in
902 the kickstart to be overridden.
903
904 """
905
906 # initialize pkg list to install
907 if self.ks:
908 self.__sanity_check()
909
910 self._required_pkgs = \
911 kickstart.get_packages(self.ks, self._get_required_packages())
912 self._excluded_pkgs = \
913 kickstart.get_excluded(self.ks, self._get_excluded_packages())
914 self._required_groups = kickstart.get_groups(self.ks)
915 else:
916 self._required_pkgs = None
917 self._excluded_pkgs = None
918 self._required_groups = None
919
920 pkg_manager = self.get_pkg_manager()
921 pkg_manager.setup()
922
923 if hasattr(self, 'install_pkgs') and self.install_pkgs:
924 if 'debuginfo' in self.install_pkgs:
925 pkg_manager.install_debuginfo = True
926
927 for repo in kickstart.get_repos(self.ks, repo_urls):
928 (name, baseurl, mirrorlist, inc, exc,
929 proxy, proxy_username, proxy_password, debuginfo,
930 source, gpgkey, disable, ssl_verify, nocache,
931 cost, priority) = repo
932
933 yr = pkg_manager.addRepository(name, baseurl, mirrorlist, proxy,
934 proxy_username, proxy_password, inc, exc, ssl_verify,
935 nocache, cost, priority)
936
937 if kickstart.exclude_docs(self.ks):
938 rpm.addMacro("_excludedocs", "1")
939 rpm.addMacro("_dbpath", "/var/lib/rpm")
940 rpm.addMacro("__file_context_path", "%{nil}")
941 if kickstart.inst_langs(self.ks) != None:
942 rpm.addMacro("_install_langs", kickstart.inst_langs(self.ks))
943
944 try:
945 self.__preinstall_packages(pkg_manager)
946 self.__select_packages(pkg_manager)
947 self.__select_groups(pkg_manager)
948 self.__deselect_packages(pkg_manager)
949 self.__localinst_packages(pkg_manager)
950
951 BOOT_SAFEGUARD = 256L * 1024 * 1024 # 256M
952 checksize = self._root_fs_avail
953 if checksize:
954 checksize -= BOOT_SAFEGUARD
955 if self.target_arch:
956 pkg_manager._add_prob_flags(rpm.RPMPROB_FILTER_IGNOREARCH)
957 pkg_manager.runInstall(checksize)
958 except CreatorError, e:
959 raise
960 except KeyboardInterrupt:
961 raise
962 else:
963 self._pkgs_content = pkg_manager.getAllContent()
964 self._pkgs_license = pkg_manager.getPkgsLicense()
965 self._pkgs_vcsinfo = pkg_manager.getVcsInfo()
966 self.__attachment_packages(pkg_manager)
967 finally:
968 pkg_manager.close()
969
970 # hook post install
971 self.postinstall()
972
973 # do some clean up to avoid lvm info leakage. this sucks.
974 for subdir in ("cache", "backup", "archive"):
975 lvmdir = self._instroot + "/etc/lvm/" + subdir
976 try:
977 for f in os.listdir(lvmdir):
978 os.unlink(lvmdir + "/" + f)
979 except:
980 pass
981
982 def postinstall(self):
983 self.copy_attachment()
984
985 def __run_post_scripts(self):
986 msger.info("Running scripts ...")
987 if os.path.exists(self._instroot + "/tmp"):
988 shutil.rmtree(self._instroot + "/tmp")
989 os.mkdir (self._instroot + "/tmp", 0755)
990 for s in kickstart.get_post_scripts(self.ks):
991 (fd, path) = tempfile.mkstemp(prefix = "ks-script-",
992 dir = self._instroot + "/tmp")
993
994 s.script = s.script.replace("\r", "")
995 os.write(fd, s.script)
996 os.close(fd)
997 os.chmod(path, 0700)
998
999 env = self._get_post_scripts_env(s.inChroot)
1000
1001 if not s.inChroot:
1002 preexec = None
1003 script = path
1004 else:
1005 preexec = self._chroot
1006 script = "/tmp/" + os.path.basename(path)
1007
1008 try:
1009 try:
1010 subprocess.call([s.interp, script],
1011 preexec_fn = preexec,
1012 env = env,
1013 stdout = sys.stdout,
1014 stderr = sys.stderr)
1015 except OSError, (err, msg):
1016 raise CreatorError("Failed to execute %%post script "
1017 "with '%s' : %s" % (s.interp, msg))
1018 finally:
1019 os.unlink(path)
1020
1021 def __save_repo_keys(self, repodata):
1022 if not repodata:
1023 return None
1024
1025 gpgkeydir = "/etc/pki/rpm-gpg"
1026 fs.makedirs(self._instroot + gpgkeydir)
1027 for repo in repodata:
1028 if repo["repokey"]:
1029 repokey = gpgkeydir + "/RPM-GPG-KEY-%s" % repo["name"]
1030 shutil.copy(repo["repokey"], self._instroot + repokey)
1031
1032 def configure(self, repodata = None):
1033 """Configure the system image according to the kickstart.
1034
1035 This method applies the (e.g. keyboard or network) configuration
1036 specified in the kickstart and executes the kickstart %post scripts.
1037
1038 If necessary, it also prepares the image to be bootable by e.g.
1039 creating an initrd and bootloader configuration.
1040
1041 """
1042 ksh = self.ks.handler
1043
1044 msger.info('Applying configurations ...')
1045 try:
1046 kickstart.LanguageConfig(self._instroot).apply(ksh.lang)
1047 kickstart.KeyboardConfig(self._instroot).apply(ksh.keyboard)
1048 kickstart.TimezoneConfig(self._instroot).apply(ksh.timezone)
1049 #kickstart.AuthConfig(self._instroot).apply(ksh.authconfig)
1050 kickstart.FirewallConfig(self._instroot).apply(ksh.firewall)
1051 kickstart.RootPasswordConfig(self._instroot).apply(ksh.rootpw)
1052 kickstart.UserConfig(self._instroot).apply(ksh.user)
1053 kickstart.ServicesConfig(self._instroot).apply(ksh.services)
1054 kickstart.XConfig(self._instroot).apply(ksh.xconfig)
1055 kickstart.NetworkConfig(self._instroot).apply(ksh.network)
1056 kickstart.RPMMacroConfig(self._instroot).apply(self.ks)
1057 kickstart.DesktopConfig(self._instroot).apply(ksh.desktop)
1058 self.__save_repo_keys(repodata)
1059 kickstart.MoblinRepoConfig(self._instroot).apply(ksh.repo, repodata, self.repourl)
1060 except:
1061 msger.warning("Failed to apply configuration to image")
1062 raise
1063
1064 self._create_bootconfig()
1065 self.__run_post_scripts()
1066
1067 def launch_shell(self, launch):
1068 """Launch a shell in the install root.
1069
1070 This method is launches a bash shell chroot()ed in the install root;
1071 this can be useful for debugging.
1072
1073 """
1074 if launch:
1075 msger.info("Launching shell. Exit to continue.")
1076 subprocess.call(["/bin/bash"], preexec_fn = self._chroot)
1077
1078 def do_genchecksum(self, image_name):
1079 if not self._genchecksum:
1080 return
1081
1082 md5sum = misc.get_md5sum(image_name)
1083 with open(image_name + ".md5sum", "w") as f:
1084 f.write("%s %s" % (md5sum, os.path.basename(image_name)))
1085 self.outimage.append(image_name+".md5sum")
1086
1087 def package(self, destdir = "."):
1088 """Prepares the created image for final delivery.
1089
1090 In its simplest form, this method merely copies the install root to the
1091 supplied destination directory; other subclasses may choose to package
1092 the image by e.g. creating a bootable ISO containing the image and
1093 bootloader configuration.
1094
1095 destdir -- the directory into which the final image should be moved;
1096 this defaults to the current directory.
1097
1098 """
1099 self._stage_final_image()
1100
1101 if not os.path.exists(destdir):
1102 fs.makedirs(destdir)
1103
1104 if self._recording_pkgs:
1105 self._save_recording_pkgs(destdir)
1106
1107 # For image formats with two or multiple image files, it will be
1108 # better to put them under a directory
1109 if self.image_format in ("raw", "vmdk", "vdi", "nand", "mrstnand"):
1110 destdir = os.path.join(destdir, "%s-%s" \
1111 % (self.name, self.image_format))
1112 msger.debug("creating destination dir: %s" % destdir)
1113 fs.makedirs(destdir)
1114
1115 # Ensure all data is flushed to _outdir
1116 runner.quiet('sync')
1117
1118 misc.check_space_pre_cp(self._outdir, destdir)
1119 for f in os.listdir(self._outdir):
1120 shutil.move(os.path.join(self._outdir, f),
1121 os.path.join(destdir, f))
1122 self.outimage.append(os.path.join(destdir, f))
1123 self.do_genchecksum(os.path.join(destdir, f))
1124
1125 def print_outimage_info(self):
1126 msg = "The new image can be found here:\n"
1127 self.outimage.sort()
1128 for file in self.outimage:
1129 msg += ' %s\n' % os.path.abspath(file)
1130
1131 msger.info(msg)
1132
1133 def check_depend_tools(self):
1134 for tool in self._dep_checks:
1135 fs.find_binary_path(tool)
1136
1137 def package_output(self, image_format, destdir = ".", package="none"):
1138 if not package or package == "none":
1139 return
1140
1141 destdir = os.path.abspath(os.path.expanduser(destdir))
1142 (pkg, comp) = os.path.splitext(package)
1143 if comp:
1144 comp=comp.lstrip(".")
1145
1146 if pkg == "tar":
1147 if comp:
1148 dst = "%s/%s-%s.tar.%s" %\
1149 (destdir, self.name, image_format, comp)
1150 else:
1151 dst = "%s/%s-%s.tar" %\
1152 (destdir, self.name, image_format)
1153
1154 msger.info("creating %s" % dst)
1155 tar = tarfile.open(dst, "w:" + comp)
1156
1157 for file in self.outimage:
1158 msger.info("adding %s to %s" % (file, dst))
1159 tar.add(file,
1160 arcname=os.path.join("%s-%s" \
1161 % (self.name, image_format),
1162 os.path.basename(file)))
1163 if os.path.isdir(file):
1164 shutil.rmtree(file, ignore_errors = True)
1165 else:
1166 os.remove(file)
1167
1168 tar.close()
1169
1170 '''All the file in outimage has been packaged into tar.* file'''
1171 self.outimage = [dst]
1172
1173 def release_output(self, config, destdir, release):
1174 """ Create release directory and files
1175 """
1176
1177 def _rpath(fn):
1178 """ release path """
1179 return os.path.join(destdir, fn)
1180
1181 outimages = self.outimage
1182
1183 # new ks
1184 new_kspath = _rpath(self.name+'.ks')
1185 with open(config) as fr:
1186 with open(new_kspath, "w") as wf:
1187 # When building a release we want to make sure the .ks
1188 # file generates the same build even when --release not used.
1189 wf.write(fr.read().replace("@BUILD_ID@", release))
1190 outimages.append(new_kspath)
1191
1192 # save log file, logfile is only available in creator attrs
1193 if hasattr(self, 'logfile') and not self.logfile:
1194 log_path = _rpath(self.name + ".log")
1195 # touch the log file, else outimages will filter it out
1196 with open(log_path, 'w') as wf:
1197 wf.write('')
1198 msger.set_logfile(log_path)
1199 outimages.append(_rpath(self.name + ".log"))
1200
1201 # rename iso and usbimg
1202 for f in os.listdir(destdir):
1203 if f.endswith(".iso"):
1204 newf = f[:-4] + '.img'
1205 elif f.endswith(".usbimg"):
1206 newf = f[:-7] + '.img'
1207 else:
1208 continue
1209 os.rename(_rpath(f), _rpath(newf))
1210 outimages.append(_rpath(newf))
1211
1212 # generate MD5SUMS
1213 with open(_rpath("MD5SUMS"), "w") as wf:
1214 for f in os.listdir(destdir):
1215 if f == "MD5SUMS":
1216 continue
1217
1218 if os.path.isdir(os.path.join(destdir, f)):
1219 continue
1220
1221 md5sum = misc.get_md5sum(_rpath(f))
1222 # There needs to be two spaces between the sum and
1223 # filepath to match the syntax with md5sum.
1224 # This way also md5sum -c MD5SUMS can be used by users
1225 wf.write("%s *%s\n" % (md5sum, f))
1226
1227 outimages.append("%s/MD5SUMS" % destdir)
1228
1229 # Filter out the nonexist file
1230 for fp in outimages[:]:
1231 if not os.path.exists("%s" % fp):
1232 outimages.remove(fp)
1233
1234 def copy_kernel(self):
1235 """ Copy kernel files to the outimage directory.
1236 NOTE: This needs to be called before unmounting the instroot.
1237 """
1238
1239 if not self._need_copy_kernel:
1240 return
1241
1242 if not os.path.exists(self.destdir):
1243 os.makedirs(self.destdir)
1244
1245 for kernel in glob.glob("%s/boot/vmlinuz-*" % self._instroot):
1246 kernelfilename = "%s/%s-%s" % (self.destdir,
1247 self.name,
1248 os.path.basename(kernel))
1249 msger.info('copy kernel file %s as %s' % (os.path.basename(kernel),
1250 kernelfilename))
1251 shutil.copy(kernel, kernelfilename)
1252 self.outimage.append(kernelfilename)
1253
1254 def copy_attachment(self):
1255 """ Subclass implement it to handle attachment files
1256 NOTE: This needs to be called before unmounting the instroot.
1257 """
1258 pass
1259
1260 def get_pkg_manager(self):
1261 return self.pkgmgr(target_arch = self.target_arch,
1262 instroot = self._instroot,
1263 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..fef9d0ed32
--- /dev/null
+++ b/scripts/lib/mic/imager/direct.py
@@ -0,0 +1,384 @@
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 *
38from mic.plugin import pluginmgr
39
40disk_methods = {
41 "do_install_disk":None,
42}
43
44class DirectImageCreator(BaseImageCreator):
45 """
46 Installs a system into a file containing a partitioned disk image.
47
48 DirectImageCreator is an advanced ImageCreator subclass; an image
49 file is formatted with a partition table, each partition created
50 from a rootfs or other OpenEmbedded build artifact and dd'ed into
51 the virtual disk. The disk image can subsequently be dd'ed onto
52 media and used on actual hardware.
53 """
54
55 def __init__(self, oe_builddir, image_output_dir, rootfs_dir, bootimg_dir,
56 kernel_dir, native_sysroot, hdddir, staging_data_dir,
57 creatoropts=None, pkgmgr=None, compress_image=None,
58 generate_bmap=None, fstab_entry="uuid"):
59 """
60 Initialize a DirectImageCreator instance.
61
62 This method takes the same arguments as ImageCreator.__init__()
63 """
64 BaseImageCreator.__init__(self, creatoropts, pkgmgr)
65
66 self.__instimage = None
67 self.__imgdir = None
68 self.__disks = {}
69 self.__disk_format = "direct"
70 self._disk_names = []
71 self._ptable_format = self.ks.handler.bootloader.ptable
72 self.use_uuid = fstab_entry == "uuid"
73 self.compress_image = compress_image
74 self.bmap_needed = generate_bmap
75
76 self.oe_builddir = oe_builddir
77 if image_output_dir:
78 self.tmpdir = image_output_dir
79 self.cachedir = "%s/cache" % image_output_dir
80 self.rootfs_dir = rootfs_dir
81 self.bootimg_dir = bootimg_dir
82 self.kernel_dir = kernel_dir
83 self.native_sysroot = native_sysroot
84 self.hdddir = hdddir
85 self.staging_data_dir = staging_data_dir
86
87 def __write_fstab(self, image_rootfs):
88 """overriden to generate fstab (temporarily) in rootfs. This
89 is called from mount_instroot, make sure it doesn't get called
90 from BaseImage.mount()"""
91 if image_rootfs is None:
92 return None
93
94 fstab = image_rootfs + "/etc/fstab"
95 if not os.path.isfile(fstab):
96 return None
97
98 parts = self._get_parts()
99
100 self._save_fstab(fstab)
101 fstab_lines = self._get_fstab(fstab, parts)
102 self._update_fstab(fstab_lines, parts)
103 self._write_fstab(fstab, fstab_lines)
104
105 return fstab
106
107 def _update_fstab(self, fstab_lines, parts):
108 """Assume partition order same as in wks"""
109 for num, p in enumerate(parts, 1):
110 if not p.mountpoint or p.mountpoint == "/" or p.mountpoint == "/boot":
111 continue
112 if self._ptable_format == 'msdos' and num > 3:
113 device_name = "/dev/" + p.disk + str(num + 1)
114 else:
115 device_name = "/dev/" + p.disk + str(num)
116 fstab_entry = device_name + "\t" + p.mountpoint + "\t" + p.fstype + "\tdefaults\t0\t0\n"
117 fstab_lines.append(fstab_entry)
118
119 def _write_fstab(self, fstab, fstab_lines):
120 fstab = open(fstab, "w")
121 for line in fstab_lines:
122 fstab.write(line)
123 fstab.close()
124
125 def _save_fstab(self, fstab):
126 """Save the current fstab in rootfs"""
127 shutil.copyfile(fstab, fstab + ".orig")
128
129 def _restore_fstab(self, fstab):
130 """Restore the saved fstab in rootfs"""
131 if fstab is None:
132 return
133 shutil.move(fstab + ".orig", fstab)
134
135 def _get_fstab(self, fstab, parts):
136 """Return the desired contents of /etc/fstab."""
137 f = open(fstab, "r")
138 fstab_contents = f.readlines()
139 f.close()
140
141 return fstab_contents
142
143 def set_bootimg_dir(self, bootimg_dir):
144 """
145 Accessor for bootimg_dir, the actual location used for the source
146 of the bootimg. Should be set by source plugins (only if they
147 change the default bootimg source) so the correct info gets
148 displayed for print_outimage_info().
149 """
150 self.bootimg_dir = bootimg_dir
151
152 def _get_parts(self):
153 if not self.ks:
154 raise CreatorError("Failed to get partition info, "
155 "please check your kickstart setting.")
156
157 # Set a default partition if no partition is given out
158 if not self.ks.handler.partition.partitions:
159 partstr = "part / --size 1900 --ondisk sda --fstype=ext3"
160 args = partstr.split()
161 pd = self.ks.handler.partition.parse(args[1:])
162 if pd not in self.ks.handler.partition.partitions:
163 self.ks.handler.partition.partitions.append(pd)
164
165 # partitions list from kickstart file
166 return kickstart.get_partitions(self.ks)
167
168 def get_disk_names(self):
169 """ Returns a list of physical target disk names (e.g., 'sdb') which
170 will be created. """
171
172 if self._disk_names:
173 return self._disk_names
174
175 #get partition info from ks handler
176 parts = self._get_parts()
177
178 for i in range(len(parts)):
179 if parts[i].disk:
180 disk_name = parts[i].disk
181 else:
182 raise CreatorError("Failed to create disks, no --ondisk "
183 "specified in partition line of ks file")
184
185 if parts[i].mountpoint and not parts[i].fstype:
186 raise CreatorError("Failed to create disks, no --fstype "
187 "specified for partition with mountpoint "
188 "'%s' in the ks file")
189
190 self._disk_names.append(disk_name)
191
192 return self._disk_names
193
194 def _full_name(self, name, extention):
195 """ Construct full file name for a file we generate. """
196 return "%s-%s.%s" % (self.name, name, extention)
197
198 def _full_path(self, path, name, extention):
199 """ Construct full file path to a file we generate. """
200 return os.path.join(path, self._full_name(name, extention))
201
202 def get_default_source_plugin(self):
203 """
204 The default source plugin i.e. the plugin that's consulted for
205 overall image generation tasks outside of any particular
206 partition. For convenience, we just hang it off the
207 bootloader handler since it's the one non-partition object in
208 any setup. By default the default plugin is set to the same
209 plugin as the /boot partition; since we hang it off the
210 bootloader object, the default can be explicitly set using the
211 --source bootloader param.
212 """
213 return self.ks.handler.bootloader.source
214
215 #
216 # Actual implemention
217 #
218 def _mount_instroot(self, base_on = None):
219 """
220 For 'wic', we already have our build artifacts and don't want
221 to loop mount anything to install into, we just create
222 filesystems from the artifacts directly and combine them into
223 a partitioned image.
224
225 We still want to reuse as much of the basic mic machinery
226 though; despite the fact that we don't actually do loop or any
227 other kind of mounting we still want to do many of the same
228 things to prepare images, so we basically just adapt to the
229 basic framework and reinterpret what 'mounting' means in our
230 context.
231
232 _instroot would normally be something like
233 /var/tmp/wic/build/imgcreate-s_9AKQ/install_root, for
234 installing packages, etc. We don't currently need to do that,
235 so we simplify life by just using /var/tmp/wic/build as our
236 workdir.
237 """
238 parts = self._get_parts()
239
240 self.__instimage = PartitionedMount(self._instroot)
241
242 for p in parts:
243 # as a convenience, set source to the boot partition source
244 # instead of forcing it to be set via bootloader --source
245 if not self.ks.handler.bootloader.source and p.mountpoint == "/boot":
246 self.ks.handler.bootloader.source = p.source
247
248 for p in parts:
249 # need to create the filesystems in order to get their
250 # sizes before we can add them and do the layout.
251 # PartitionedMount.mount() actually calls __format_disks()
252 # to create the disk images and carve out the partitions,
253 # then self.install() calls PartitionedMount.install()
254 # which calls __install_partitition() for each partition
255 # to dd the fs into the partitions. It would be nice to
256 # be able to use e.g. ExtDiskMount etc to create the
257 # filesystems, since that's where existing e.g. mkfs code
258 # is, but those are only created after __format_disks()
259 # which needs the partition sizes so needs them created
260 # before its called. Well, the existing setup is geared
261 # to installing packages into mounted filesystems - maybe
262 # when/if we need to actually do package selection we
263 # should modify things to use those objects, but for now
264 # we can avoid that.
265
266 p.install_pkgs(self, self.workdir, self.oe_builddir, self.rootfs_dir,
267 self.bootimg_dir, self.kernel_dir, self.native_sysroot)
268
269 p.prepare(self, self.workdir, self.oe_builddir, self.rootfs_dir,
270 self.bootimg_dir, self.kernel_dir, self.native_sysroot)
271
272 fstab = self.__write_fstab(p.get_rootfs())
273 self._restore_fstab(fstab)
274
275 self.__instimage.add_partition(int(p.size),
276 p.disk,
277 p.mountpoint,
278 p.source_file,
279 p.fstype,
280 p.label,
281 fsopts = p.fsopts,
282 boot = p.active,
283 align = p.align,
284 part_type = p.part_type)
285 self.__instimage.layout_partitions(self._ptable_format)
286
287 self.__imgdir = self.workdir
288 for disk_name, disk in self.__instimage.disks.items():
289 full_path = self._full_path(self.__imgdir, disk_name, "direct")
290 msger.debug("Adding disk %s as %s with size %s bytes" \
291 % (disk_name, full_path, disk['min_size']))
292 disk_obj = fs_related.DiskImage(full_path, disk['min_size'])
293 self.__disks[disk_name] = disk_obj
294 self.__instimage.add_disk(disk_name, disk_obj)
295
296 self.__instimage.mount()
297
298 def install(self, repo_urls=None):
299 """
300 Install fs images into partitions
301 """
302 for disk_name, disk in self.__instimage.disks.items():
303 full_path = self._full_path(self.__imgdir, disk_name, "direct")
304 msger.debug("Installing disk %s as %s with size %s bytes" \
305 % (disk_name, full_path, disk['min_size']))
306 self.__instimage.install(full_path)
307
308 def configure(self, repodata = None):
309 """
310 Configure the system image according to kickstart.
311
312 For now, it just prepares the image to be bootable by e.g.
313 creating and installing a bootloader configuration.
314 """
315 source_plugin = self.get_default_source_plugin()
316 if source_plugin:
317 self._source_methods = pluginmgr.get_source_plugin_methods(source_plugin, disk_methods)
318 for disk_name, disk in self.__instimage.disks.items():
319 self._source_methods["do_install_disk"](disk, disk_name, self,
320 self.workdir,
321 self.oe_builddir,
322 self.bootimg_dir,
323 self.kernel_dir,
324 self.native_sysroot)
325
326 def print_outimage_info(self):
327 """
328 Print the image(s) and artifacts used, for the user.
329 """
330 msg = "The new image(s) can be found here:\n"
331
332 parts = self._get_parts()
333
334 for disk_name, disk in self.__instimage.disks.items():
335 full_path = self._full_path(self.__imgdir, disk_name, "direct")
336 msg += ' %s\n\n' % full_path
337
338 msg += 'The following build artifacts were used to create the image(s):\n'
339 for p in parts:
340 if p.get_rootfs() is None:
341 continue
342 if p.mountpoint == '/':
343 str = ':'
344 else:
345 str = '["%s"]:' % p.label
346 msg += ' ROOTFS_DIR%s%s\n' % (str.ljust(20), p.get_rootfs())
347
348 msg += ' BOOTIMG_DIR: %s\n' % self.bootimg_dir
349 msg += ' KERNEL_DIR: %s\n' % self.kernel_dir
350 msg += ' NATIVE_SYSROOT: %s\n' % self.native_sysroot
351
352 msger.info(msg)
353
354 def _get_boot_config(self):
355 """
356 Return the rootdev/root_part_uuid (if specified by
357 --part-type)
358
359 Assume partition order same as in wks
360 """
361 rootdev = None
362 root_part_uuid = None
363 parts = self._get_parts()
364 for num, p in enumerate(parts, 1):
365 if p.mountpoint == "/":
366 part = ''
367 if p.disk.startswith('mmcblk'):
368 part = 'p'
369
370 if self._ptable_format == 'msdos' and num > 3:
371 rootdev = "/dev/%s%s%-d" % (p.disk, part, num + 1)
372 else:
373 rootdev = "/dev/%s%s%-d" % (p.disk, part, num)
374 root_part_uuid = p.part_type
375
376 return (rootdev, root_part_uuid)
377
378 def _unmount_instroot(self):
379 if not self.__instimage is None:
380 try:
381 self.__instimage.cleanup()
382 except MountError, err:
383 msger.warning("%s" % err)
384
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..e36f4a76c6
--- /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, 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 = "i386"
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))