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.
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
)
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.
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.
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"
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"""
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
)
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("\"")
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
)
126 out
= subprocess
.Popen(["/sbin/e2label", self
._image
],
127 stdout
= subprocess
.PIPE
,
128 stderr
= dev_null
).communicate()[0]
130 self
._LoopImageCreator
__fslable
= out
.strip()
133 raise CreatorError("Failed to determine fsimage TYPE: %s" % e
)
136 def __ensure_builddir(self
):
137 if not self
._ImageCreator
__builddir
is None:
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" %
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
)
158 script
= "/tmp/" + os
.path
.basename(path
)
162 subprocess
.call([script
], preexec_fn
= self
._chroot
)
164 raise CreatorError("Failed to execute script %s, %s " % (script
, e
))
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
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
]
199 self
.fslabel
= self
._LoopImageCreator
__fslable
201 self
._LoopImageCreator
__instloop
= ExtDiskMount(ExistingSparseLoopbackDisk(self
._image
,
202 self
._LoopImageCreator
__image
_size
),
203 self
._ImageCreator
_instroot
,
205 self
._LoopImageCreator
__blocksize
,
208 self
._LoopImageCreator
__instloop
.mount()
209 except MountError
, e
:
210 raise CreatorError("Failed to loopback mount '%s' : %s" %
213 cachesrc
= cachedir
or (self
._ImageCreator
__builddir
+ "/yum-cache")
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"
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"
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" %
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()
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
)
298 (livecd
, options
) = parse_options(sys
.argv
[1:])
300 if os
.geteuid () != 0:
301 print >> sys
.stderr
, "You must run edit-livecd as root"
307 name
= os
.path
.basename(livecd
) + ".edited"
310 output
= options
.output
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
321 editor
.mount(livecd
, cachedir
= None)
323 print "Running edit script '%s'" % options
.script
324 editor
._run
_script
(options
.script
)
326 print "Launching shell. Exit to continue."
327 print "----------------------------------"
328 editor
.launch_shell()
329 rebuild_iso_symlinks(editor
._LiveImageCreatorBase
__isodir
)
331 editor
.package(output
)
332 except CreatorError
, e
:
333 logging
.error(u
"Error editing Live CD : %s" % e
)
341 if __name__
== "__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
353 raise CreatorError("Architecture not supported!")