Updated chroot's cleanup function
[tools/mic.git] / mic / imager / liveusb.py
1 #
2 # liveusb.py : LiveUSBImageCreator class for creating Live USB images
3 #
4 # Copyright 2007, Red Hat  Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; version 2 of the License.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU Library General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 import os
19 import os.path
20 import glob
21 import shutil
22 import subprocess
23 import logging
24 import re
25 import time
26
27 import mic.utils.fs_related as fs_related
28 import mic.utils.misc as misc
29 from livecd import LiveCDImageCreator
30 from mic.utils.errors import *
31 from mic.utils.partitionedfs import PartitionedMount
32
33 class LiveUSBImageCreator(LiveCDImageCreator):
34     def __init__(self, *args):
35         LiveCDImageCreator.__init__(self, *args)
36
37         self._dep_checks.extend(["kpartx", "parted"])
38         # remove dependency of genisoimage in parent class
39         if "genisoimage" in self._dep_checks:
40             self._dep_checks.remove("genisoimage")
41
42     def _create_usbimg(self, isodir):
43         overlaysizemb = 64 #default
44         #skipcompress = self.skip_compression?
45         fstype = "vfat"
46         homesizemb=0
47         swapsizemb=0
48         homefile="home.img"
49         plussize=128
50         kernelargs=None
51
52         if overlaysizemb > 2047 and fstype == "vfat":
53             raise CreatorError("Can't have an overlay of 2048MB or greater on VFAT")
54         if homesizemb > 2047 and fstype == "vfat":
55             raise CreatorError("Can't have an home overlay of 2048MB or greater on VFAT")
56         if swapsizemb > 2047 and fstype == "vfat":
57             raise CreatorError("Can't have an swap overlay of 2048MB or greater on VFAT")
58
59         livesize = misc.get_file_size(isodir + "/LiveOS")
60         mountcmd = fs_related.find_binary_path("mount")
61         umountcmd = fs_related.find_binary_path("umount")
62         ddcmd = fs_related.find_binary_path("dd")
63         #if skipcompress:
64         #    tmpmnt = self._mkdtemp("squashfs-mnt")
65         #    rc = subprocess.call([mountcmd, "-o", "loop", isodir + "/LiveOS/squashfs.img", tmpmnt]);
66         #    if rc:
67         #        raise CreatorError("Can't mount %s" % (isodir + "/LiveOS/squashfs.img"))
68         #    livesize = misc.get_file_size(tmpmnt + "/LiveOS/ext3fs.img")
69         #    rc = subprocess.call([umountcmd, tmpmnt]);
70         #    if rc:
71         #        raise CreatorError("Can't umount %s" % (tmpmnt))
72         usbimgsize = (overlaysizemb + homesizemb + swapsizemb + livesize + plussize) * 1024L * 1024L
73         disk = fs_related.SparseLoopbackDisk("%s/%s.usbimg" % (self._outdir, self.name), usbimgsize)
74         usbmnt = self._mkdtemp("usb-mnt")
75         usbloop = PartitionedMount({'/dev/sdb':disk}, usbmnt)
76
77         usbloop.add_partition(usbimgsize/1024/1024, "/dev/sdb", "/", fstype, boot=True)
78
79         try:
80             usbloop.mount()
81         except MountError, e:
82             raise CreatorError("Failed mount disks : %s" % e)
83
84         try:
85             fs_related.makedirs(usbmnt + "/LiveOS")
86             #if skipcompress:
87             #    if os.path.exists(isodir + "/LiveOS/squashfs.img"):
88             #        rc = subprocess.call([mountcmd, "-o", "loop", isodir + "/LiveOS/squashfs.img", tmpmnt]);
89             #        if rc:
90             #            raise CreatorError("Can't mount %s" % (isodir + "/LiveOS/squashfs.img"))
91             #        shutil.copyfile(tmpmnt + "/LiveOS/ext3fs.img", usbmnt + "/LiveOS/ext3fs.img")
92             #        rc = subprocess.call([umountcmd, tmpmnt]);
93             #        if rc:
94             #            raise CreatorError("Can't umount %s" % (tmpmnt))
95             #    else:
96             #        shutil.copyfile(isodir + "/LiveOS/ext3fs.img", usbmnt + "/LiveOS/ext3fs.img")
97             #else:
98             if os.path.exists(isodir + "/LiveOS/squashfs.img"):
99                 shutil.copyfile(isodir + "/LiveOS/squashfs.img", usbmnt + "/LiveOS/squashfs.img")
100             else:
101                 fs_related.mksquashfs(os.path.dirname(self._image), usbmnt + "/LiveOS/squashfs.img")
102
103             if os.path.exists(isodir + "/LiveOS/osmin.img"):
104                 shutil.copyfile(isodir + "/LiveOS/osmin.img", usbmnt + "/LiveOS/osmin.img")
105
106             if fstype == "vfat" or fstype == "msdos":
107                 uuid = usbloop.partitions[0]['mount'].uuid
108                 label = usbloop.partitions[0]['mount'].fslabel
109                 usblabel = "UUID=%s-%s" % (uuid[0:4], uuid[4:8])
110                 overlaysuffix = "-%s-%s-%s" % (label, uuid[0:4], uuid[4:8])
111             else:
112                 diskmount = usbloop.partitions[0]['mount']
113                 usblabel = "UUID=%s" % diskmount.uuid
114                 overlaysuffix = "-%s-%s" % (diskmount.fslabel, diskmount.uuid)
115
116             copycmd = fs_related.find_binary_path("cp")
117             args = [copycmd, "-Rf", isodir + "/isolinux", usbmnt + "/syslinux"]
118             rc = subprocess.call(args)
119             if rc:
120                 raise CreatorError("Can't copy isolinux directory %s" % (isodir + "/isolinux/*"))
121
122             if os.path.isfile("/usr/share/syslinux/isolinux.bin"):
123                 syslinux_path = "/usr/share/syslinux"
124             elif  os.path.isfile("/usr/lib/syslinux/isolinux.bin"):
125                 syslinux_path = "/usr/lib/syslinux"
126             else:
127                 raise CreatorError("syslinux not installed : "
128                                "cannot find syslinux installation path")
129
130             for f in ("isolinux.bin", "vesamenu.c32"):
131                 path = os.path.join(syslinux_path, f)
132                 if os.path.isfile(path):
133                     args = [copycmd, path, usbmnt + "/syslinux/"]
134                     rc = subprocess.call(args)
135                     if rc:
136                         raise CreatorError("Can't copy syslinux file %s" % (path))
137                 else:
138                     raise CreatorError("syslinux not installed : "
139                                "syslinux file %s not found" % path)
140
141             fd = open(isodir + "/isolinux/isolinux.cfg", "r")
142             text = fd.read()
143             fd.close()
144             pattern = re.compile('CDLABEL=[^ ]*')
145             text = pattern.sub(usblabel, text)
146             pattern = re.compile('rootfstype=[^ ]*')
147             text = pattern.sub("rootfstype=" + fstype, text)
148             if kernelargs:
149                 text = text.replace("liveimg", "liveimg " + kernelargs)
150
151             if overlaysizemb > 0:
152                 print "Initializing persistent overlay file"
153                 overfile = "overlay" + overlaysuffix
154                 if fstype == "vfat":
155                     args = [ddcmd, "if=/dev/zero", "of=" + usbmnt + "/LiveOS/" + overfile, "count=%d" % overlaysizemb, "bs=1M"]
156                 else:
157                     args = [ddcmd, "if=/dev/null", "of=" + usbmnt + "/LiveOS/" + overfile, "count=1", "bs=1M", "seek=%d" % overlaysizemb]
158                 rc = subprocess.call(args)
159                 if rc:
160                     raise CreatorError("Can't create overlay file")
161                 text = text.replace("liveimg", "liveimg overlay=" + usblabel)
162                 text = text.replace(" ro ", " rw ")
163
164             if swapsizemb > 0:
165                 print "Initializing swap file"
166                 swapfile = usbmnt + "/LiveOS/" + "swap.img"
167                 args = [ddcmd, "if=/dev/zero", "of=" + swapfile, "count=%d" % swapsizemb, "bs=1M"]
168                 rc = subprocess.call(args)
169                 if rc:
170                     raise CreatorError("Can't create swap file")
171                 args = ["mkswap", "-f", swapfile]
172                 rc = subprocess.call(args)
173                 if rc:
174                     raise CreatorError("Can't mkswap on swap file")
175
176             if homesizemb > 0:
177                 print "Initializing persistent /home"
178                 homefile = usbmnt + "/LiveOS/" + homefile
179                 if fstype == "vfat":
180                     args = [ddcmd, "if=/dev/zero", "of=" + homefile, "count=%d" % homesizemb, "bs=1M"]
181                 else:
182                     args = [ddcmd, "if=/dev/null", "of=" + homefile, "count=1", "bs=1M", "seek=%d" % homesizemb]
183                 rc = subprocess.call(args)
184                 if rc:
185                     raise CreatorError("Can't create home file")
186
187                 mkfscmd = fs_related.find_binary_path("/sbin/mkfs." + fstype)
188                 if fstype == "ext2" or fstype == "ext3":
189                     args = [mkfscmd, "-F", "-j", homefile]
190                 else:
191                     args = [mkfscmd, homefile]
192                 rc = subprocess.call(args, stdout=sys.stdout, stderr=sys.stderr)
193                 if rc:
194                     raise CreatorError("Can't mke2fs home file")
195                 if fstype == "ext2" or fstype == "ext3":
196                     tune2fs = fs_related.find_binary_path("tune2fs")
197                     args = [tune2fs, "-c0", "-i0", "-ouser_xattr,acl", homefile]
198                     rc = subprocess.call(args, stdout=sys.stdout, stderr=sys.stderr)
199                     if rc:
200                          raise CreatorError("Can't tune2fs home file")
201
202             if fstype == "vfat" or fstype == "msdos":
203                 syslinuxcmd = fs_related.find_binary_path("syslinux")
204                 syslinuxcfg = usbmnt + "/syslinux/syslinux.cfg"
205                 args = [syslinuxcmd, "-d", "syslinux", usbloop.partitions[0]["device"]]
206             elif fstype == "ext2" or fstype == "ext3":
207                 extlinuxcmd = fs_related.find_binary_path("extlinux")
208                 syslinuxcfg = usbmnt + "/syslinux/extlinux.conf"
209                 args = [extlinuxcmd, "-i", usbmnt + "/syslinux"]
210             else:
211                 raise CreatorError("Invalid file system type: %s" % (fstype))
212
213             os.unlink(usbmnt + "/syslinux/isolinux.cfg")
214             fd = open(syslinuxcfg, "w")
215             fd.write(text)
216             fd.close()
217             rc = subprocess.call(args)
218             if rc:
219                 raise CreatorError("Can't install boot loader.")
220
221         finally:
222             usbloop.unmount()
223             usbloop.cleanup()
224
225         #Need to do this after image is unmounted and device mapper is closed
226         print "set MBR"
227         mbrfile = "/usr/lib/syslinux/mbr.bin"
228         if not os.path.exists(mbrfile):
229             mbrfile = "/usr/share/syslinux/mbr.bin"
230             if not os.path.exists(mbrfile):
231                 raise CreatorError("mbr.bin file didn't exist.")
232         mbrsize = os.path.getsize(mbrfile)
233         outimg = "%s/%s.usbimg" % (self._outdir, self.name)
234         args = [ddcmd, "if=" + mbrfile, "of=" + outimg, "seek=0", "conv=notrunc", "bs=1", "count=%d" % (mbrsize)]
235         rc = subprocess.call(args)
236         if rc:
237             raise CreatorError("Can't set MBR.")
238
239     def _stage_final_image(self):
240         try:
241             isodir = self._get_isodir()
242             fs_related.makedirs(isodir + "/LiveOS")
243
244             minimal_size = self._resparse()
245
246             if not self.skip_minimize:
247                 fs_related.create_image_minimizer(isodir + "/LiveOS/osmin.img",
248                                        self._image, minimal_size)
249
250             if self.skip_compression:
251                 shutil.move(self._image, isodir + "/LiveOS/ext3fs.img")
252             else:
253                 fs_related.makedirs(os.path.join(os.path.dirname(self._image), "LiveOS"))
254                 shutil.move(self._image,
255                             os.path.join(os.path.dirname(self._image),
256                                          "LiveOS", "ext3fs.img"))
257                 fs_related.mksquashfs(os.path.dirname(self._image),
258                            isodir + "/LiveOS/squashfs.img")
259
260                 self._create_usbimg(isodir)
261
262         finally:
263             shutil.rmtree(isodir, ignore_errors = True)
264             self._set_isodir(None)
265
266     def _base_on(self, base_on):
267         """Support Image Convertor"""
268         if self.actasconvertor:
269             if os.path.exists(base_on) and not os.path.isfile(base_on):
270                 ddcmd = fs_related.find_binary_path("dd")
271                 args = [ ddcmd, "if=%s" % base_on, "of=%s" % self._image ]
272                 print "dd %s -> %s" % (base_on, self._image)
273                 rc = subprocess.call(args)
274                 if rc != 0:
275                     raise CreatorError("Failed to dd from %s to %s" % (base_on, self._image))
276                 self._set_image_size(misc.get_file_size(self._image) * 1024L * 1024L)
277             if os.path.isfile(base_on):
278                 print "Copying file system..."
279                 shutil.copyfile(base_on, self._image)
280                 self._set_image_size(misc.get_file_size(self._image) * 1024L * 1024L)
281             return
282
283         #helper function to extract ext3 file system from a live usb image
284         usbimgsize = misc.get_file_size(base_on) * 1024L * 1024L
285         disk = fs_related.SparseLoopbackDisk(base_on, usbimgsize)
286         usbimgmnt = self._mkdtemp("usbimgmnt-")
287         usbloop = PartitionedMount({'/dev/sdb':disk}, usbimgmnt, skipformat = True)
288         usbimg_fstype = "vfat"
289         usbloop.add_partition(usbimgsize/1024/1024, "/dev/sdb", "/", usbimg_fstype, boot=False)
290         try:
291             usbloop.mount()
292         except MountError, e:
293             usbloop.cleanup()
294             raise CreatorError("Failed to loopback mount '%s' : %s" %
295                                (base_on, e))
296
297         #legacy LiveOS filesystem layout support, remove for F9 or F10
298         if os.path.exists(usbimgmnt + "/squashfs.img"):
299             squashimg = usbimgmnt + "/squashfs.img"
300         else:
301             squashimg = usbimgmnt + "/LiveOS/squashfs.img"
302
303         tmpoutdir = self._mkdtemp()
304         #unsquashfs requires outdir mustn't exist
305         shutil.rmtree(tmpoutdir, ignore_errors = True)
306         self._uncompress_squashfs(squashimg, tmpoutdir)
307
308         try:
309             # legacy LiveOS filesystem layout support, remove for F9 or F10
310             if os.path.exists(tmpoutdir + "/os.img"):
311                 os_image = tmpoutdir + "/os.img"
312             else:
313                 os_image = tmpoutdir + "/LiveOS/ext3fs.img"
314
315             if not os.path.exists(os_image):
316                 raise CreatorError("'%s' is not a valid live CD ISO : neither "
317                                    "LiveOS/ext3fs.img nor os.img exist" %
318                                    base_on)
319
320             print "Copying file system..."
321             shutil.copyfile(os_image, self._image)
322             self._set_image_size(misc.get_file_size(self._image) * 1024L * 1024L)
323         finally:
324             shutil.rmtree(tmpoutdir, ignore_errors = True)
325             usbloop.cleanup()