Add support for timeout and totaltimeout to livecd-iso-to-disk (#531566)
[livecd.git] / tools / edit-livecd
blobc98e6524dbdf4b7aea2f747e78ab92c30d7d5747
1 #!/usr/bin/python -tt
3 # edit livecd: Edit a livecd to insert files
5 # Copyright 2009, Red Hat Inc.
6 # Written by Perry Myers <pmyers at redhat.com> & David Huff <dhuff at redhat.com>
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; version 2 of the License.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Library General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 import os
23 import sys
24 import tempfile
25 import shutil
26 import subprocess
27 import optparse
28 import logging
30 from imgcreate.debug import *
31 from imgcreate.fs import *
32 from imgcreate.live import *
34 class ExistingSparseLoopbackDisk(SparseLoopbackDisk):
35 """don't want to expand the disk"""
36 def __init__(self, lofile, size):
37 SparseLoopbackDisk.__init__(self, lofile, size)
39 def create(self):
40 #self.expand(create = True)
41 LoopbackDisk.create(self)
43 class LiveImageEditor(LiveImageCreator):
44 """class for editing LiveCD images.
46 We need an instance of LiveImageCreator however we do not have a kickstart
47 file nor do we need to create a new image. We just want to reuse some of
48 LiveImageCreators methods on an existing livecd image.
50 """
52 def __init__(self, name):
53 """Initialize a LiveImageEditor instance.
55 creates a dummy instance of LiveImageCreator
56 We do not initialize any sub classes b/c we have no ks file.
58 """
59 self.name = name
61 self.tmpdir = "/var/tmp"
62 """The directory in which all temporary files will be created."""
64 self.compress_type = "gzip"
65 """mksquashfs compressor to use."""
67 self.skip_compression = False
68 """Controls whether to use squashfs to compress the image."""
70 self.skip_minimize = False
71 """Controls whether an image minimizing snapshot should be created."""
73 self._isofstype = "iso9660"
74 self.__isodir = None
76 self._ImageCreator__builddir = None
77 """working directory"""
79 self._ImageCreator_instroot = None
80 """where the extfs.img is mounted for modification"""
82 self._ImageCreator_outdir = None
83 """where final iso gets written"""
85 self._ImageCreator__bindmounts = []
87 self._LoopImageCreator__imagedir = None
88 """dir for the extfs.img"""
90 self._LoopImageCreator__blocksize = 4096
91 self._LoopImageCreator__fslabel = None
92 self._LoopImageCreator__instloop = None
93 self._LoopImageCreator__fstype = None
94 self._LoopImageCreator__image_size = None
96 self._LiveImageCreatorBase__isodir = None
97 """directory where the iso is staged"""
99 # properties
100 def __get_image(self):
101 if self._LoopImageCreator__imagedir is None:
102 self.__ensure_builddir()
103 self._LoopImageCreator__imagedir = tempfile.mkdtemp(dir = os.path.abspath(self.tmpdir), prefix = self.name + "-")
104 return self._LoopImageCreator__imagedir + "/ext3fs.img"
105 _image = property(__get_image)
106 """The location of the image file"""
109 def _get_fstype(self):
110 dev_null = os.open("/dev/null", os.O_WRONLY)
111 try:
112 out = subprocess.Popen(["/sbin/blkid", self._image],
113 stdout = subprocess.PIPE,
114 stderr = dev_null).communicate()[0]
115 for word in out.split():
116 if word.startswith("TYPE"):
117 self._LoopImageCreator__fstype = word.split("=")[1].strip("\"")
119 except IOError, e:
120 raise CreatorError("Failed to determine fsimage TYPE: %s" % e )
123 def _get_fslable(self):
124 dev_null = os.open("/dev/null", os.O_WRONLY)
125 try:
126 out = subprocess.Popen(["/sbin/e2label", self._image],
127 stdout = subprocess.PIPE,
128 stderr = dev_null).communicate()[0]
130 self._LoopImageCreator__fslable = out.strip()
132 except IOError, e:
133 raise CreatorError("Failed to determine fsimage TYPE: %s" % e )
136 def __ensure_builddir(self):
137 if not self._ImageCreator__builddir is None:
138 return
140 try:
141 self._ImageCreator__builddir = tempfile.mkdtemp(dir = os.path.abspath(self.tmpdir),
142 prefix = "edit-livecd-")
143 except OSError, (err, msg):
144 raise CreatorError("Failed create build directory in %s: %s" %
145 (self.tmpdir, msg))
148 def _run_script(self, script):
150 (fd, path) = tempfile.mkstemp(prefix = "script-",
151 dir = self._instroot + "/tmp")
153 logging.debug("copying script to install root: %s" % path)
154 shutil.copy(os.path.abspath(script), path)
155 os.close(fd)
156 os.chmod(path, 0700)
158 script = "/tmp/" + os.path.basename(path)
161 try:
162 subprocess.call([script], preexec_fn = self._chroot)
163 except OSError, e:
164 raise CreatorError("Failed to execute script %s, %s " % (script, e))
165 finally:
166 os.unlink(path)
169 def mount(self, base_on, cachedir = None):
170 """mount existing file system.
172 we have to override mount b/c we are not creating an new install root
173 nor do we need to setup the file system, ie makedirs(/etc/, /boot, ...),
174 nor do we want to overwrite fstab, or create selinuxfs
176 We also need to get some info about the image before we
177 can mount it.
181 if not base_on:
182 raise CreatorError("No base livecd image specified")
184 self.__ensure_builddir()
186 self._ImageCreator_instroot = self._ImageCreator__builddir + "/install_root"
187 self._LoopImageCreator__imagedir = self._ImageCreator__builddir + "/ex"
188 self._ImageCreator_outdir = self._ImageCreator__builddir + "/out"
190 makedirs(self._ImageCreator_instroot)
191 makedirs(self._LoopImageCreator__imagedir)
192 makedirs(self._ImageCreator_outdir)
194 LiveImageCreator._base_on(self, base_on)
196 self._LoopImageCreator__image_size = os.stat(self._image)[stat.ST_SIZE]
197 self._get_fstype()
198 self._get_fslable()
199 self.fslabel = self._LoopImageCreator__fslable
201 self._LoopImageCreator__instloop = ExtDiskMount(ExistingSparseLoopbackDisk(self._image,
202 self._LoopImageCreator__image_size),
203 self._ImageCreator_instroot,
204 self._fstype,
205 self._LoopImageCreator__blocksize,
206 self.fslabel)
207 try:
208 self._LoopImageCreator__instloop.mount()
209 except MountError, e:
210 raise CreatorError("Failed to loopback mount '%s' : %s" %
211 (self._image, e))
213 cachesrc = cachedir or (self._ImageCreator__builddir + "/yum-cache")
214 makedirs(cachesrc)
216 for (f, dest) in [("/sys", None), ("/proc", None),
217 ("/dev/pts", None), ("/dev/shm", None),
218 (cachesrc, "/var/cache/yum")]:
219 self._ImageCreator__bindmounts.append(BindChrootMount(f, self._instroot, dest))
221 self._do_bindmounts()
223 os.symlink("../proc/mounts", self._instroot + "/etc/mtab")
225 self.__copy_cd_root(base_on)
228 def __copy_cd_root(self, base_on):
229 """helper function to root content of the base liveCD to ISOdir"""
231 isoloop = DiskMount(LoopbackDisk(base_on, 0), self._mkdtemp())
232 self._LiveImageCreatorBase__isodir = self._ImageCreator__builddir + "/iso"
234 try:
235 isoloop.mount()
236 # legacy LiveOS filesystem layout support, remove for F9 or F10
237 if os.path.exists(isoloop.mountdir + "/squashfs.img"):
238 squashimg = isoloop.mountdir + "/squashfs.img"
239 else:
240 squashimg = isoloop.mountdir + "/LiveOS/squashfs.img"
242 #copy over everything but squashimg
243 shutil.copytree(isoloop.mountdir,
244 self._LiveImageCreatorBase__isodir,
245 ignore=shutil.ignore_patterns("squashfs.img", "osmin.img"))
246 except MountError, e:
247 raise CreatorError("Failed to loopback mount '%s' : %s" %
248 (base_on, e))
250 finally:
251 isoloop.cleanup()
254 def parse_options(args):
255 parser = optparse.OptionParser(usage = "%prog [-s=<script.sh>] <LIVECD.iso>")
257 parser.add_option("-n", "--name", type="string", dest="name",
258 help="name of new livecd (don't include .iso will be added)")
260 parser.add_option("-o", "--output", type="string", dest="output",
261 help="specify the output dir")
263 parser.add_option("-s", "--script", type="string", dest="script",
264 help="specify script to run chrooted in the livecd fsimage")
266 parser.add_option("-t", "--tmpdir", type="string",
267 dest="tmpdir", default="/var/tmp",
268 help="Temporary directory to use (default: /var/tmp)")
270 parser.add_option("", "--skip-compression", action="store_true", dest="skip_compression")
272 parser.add_option("", "--skip-minimize", action="store_true", dest="skip_minimize")
274 setup_logging(parser)
276 (options, args) = parser.parse_args()
278 if len(args) != 1:
279 parser.print_usage()
280 sys.exit(1)
282 return (args[0], options)
284 def rebuild_iso_symlinks(isodir):
285 # remove duplicate files and rebuild symlinks to reduce iso size
286 efi_vmlinuz = "%s/EFI/boot/vmlinuz0" % isodir
287 isolinux_vmlinuz = "%s/isolinux/vmlinuz0" % isodir
288 efi_initrd = "%s/EFI/boot/initrd0.img" % isodir
289 isolinux_initrd = "%s/isolinux/initrd0.img" % isodir
291 os.remove(efi_vmlinuz)
292 os.remove(efi_initrd)
293 os.symlink(isolinux_vmlinuz,efi_vmlinuz)
294 os.symlink(isolinux_initrd,efi_initrd)
297 def main():
298 (livecd, options) = parse_options(sys.argv[1:])
300 if os.geteuid () != 0:
301 print >> sys.stderr, "You must run edit-livecd as root"
302 return 1
304 if options.name:
305 name = options.name
306 else:
307 name = os.path.basename(livecd) + ".edited"
309 if options.output:
310 output = options.output
311 else:
312 output = os.path.dirname(livecd)
315 editor = LiveImageEditor(name)
316 editor.tmpdir = os.path.abspath(options.tmpdir)
317 editor.skip_compression = options.skip_compression
318 editor.skip_minimize = options.skip_minimize
320 try:
321 editor.mount(livecd, cachedir = None)
322 if options.script:
323 print "Running edit script '%s'" % options.script
324 editor._run_script(options.script)
325 else:
326 print "Launching shell. Exit to continue."
327 print "----------------------------------"
328 editor.launch_shell()
329 rebuild_iso_symlinks(editor._LiveImageCreatorBase__isodir)
330 editor.unmount()
331 editor.package(output)
332 except CreatorError, e:
333 logging.error(u"Error editing Live CD : %s" % e)
334 return 1
335 finally:
336 editor.cleanup()
338 return 0
341 if __name__ == "__main__":
342 sys.exit(main())
345 arch = rpmUtils.arch.getBaseArch()
346 if arch in ("i386", "x86_64"):
347 LiveImageCreator = x86LiveImageCreator
348 elif arch in ("ppc",):
349 LiveImageCreator = ppcLiveImageCreator
350 elif arch in ("ppc64",):
351 LiveImageCreator = ppc64LiveImageCreator
352 else:
353 raise CreatorError("Architecture not supported!")