Introduce the --generate-bmap option
[tools/mic.git] / plugins / imager / raw_plugin.py
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
18 import os
19 import shutil
20 import re
21 import tempfile
22
23 from mic import chroot, msger, rt_util
24 from mic.utils import misc, fs_related, errors, runner, cmdln
25 from mic.conf import configmgr
26 from mic.plugin import pluginmgr
27 from mic.utils.partitionedfs import PartitionedMount
28
29 import mic.imager.raw as raw
30
31 from mic.pluginbase import ImagerPlugin
32 class RawPlugin(ImagerPlugin):
33     name = 'raw'
34
35     @classmethod
36     @cmdln.option("--compress-disk-image", dest="compress_image", type='choice',
37                   choices=("gz", "bz2"), default=None,
38                   help="Same with --compress-image")
39     @cmdln.option("--compress-image", dest="compress_image", type='choice',
40                   choices=("gz", "bz2"), default = None,
41                   help="Compress all raw images before package")
42     @cmdln.option("--generate-bmap", action="store_true", default = None,
43                   help="also generate the block map file")
44     def do_create(self, subcmd, opts, *args):
45         """${cmd_name}: create raw image
46
47         Usage:
48             ${name} ${cmd_name} <ksfile> [OPTS]
49
50         ${cmd_option_list}
51         """
52
53         if len(args) != 1:
54             raise errors.Usage("Extra arguments given")
55
56         creatoropts = configmgr.create
57         ksconf = args[0]
58
59         if configmgr.bootstrap['enable']:
60             configmgr._ksconf = ksconf
61             rt_util.bootstrap_mic()
62
63         recording_pkgs = []
64         if len(creatoropts['record_pkgs']) > 0:
65             recording_pkgs = creatoropts['record_pkgs']
66
67         if creatoropts['release'] is not None:
68             if 'name' not in recording_pkgs:
69                 recording_pkgs.append('name')
70
71         configmgr._ksconf = ksconf
72
73         # Called After setting the configmgr._ksconf as the creatoropts['name'] is reset there.
74         if creatoropts['release'] is not None:
75             creatoropts['outdir'] = "%s/%s/images/%s/" % (creatoropts['outdir'], creatoropts['release'], creatoropts['name'])
76
77         # try to find the pkgmgr
78         pkgmgr = None
79         backends = pluginmgr.get_plugins('backend')
80         if 'auto' == creatoropts['pkgmgr']:
81             for key in configmgr.prefer_backends:
82                 if key in backends:
83                     pkgmgr = backends[key]
84                     break
85         else:
86             for key in backends.keys():
87                 if key == creatoropts['pkgmgr']:
88                     pkgmgr = backends[key]
89                     break
90
91         if not pkgmgr:
92             raise errors.CreatorError("Can't find backend: %s, "
93                                       "available choices: %s" %
94                                       (creatoropts['pkgmgr'],
95                                        ','.join(backends.keys())))
96
97         creator = raw.RawImageCreator(creatoropts, pkgmgr, opts.compress_image,
98                                       opts.generate_bmap)
99
100         if len(recording_pkgs) > 0:
101             creator._recording_pkgs = recording_pkgs
102
103         images = ["%s-%s.raw" % (creator.name, part['name'])
104                   for part in creator.get_diskinfo()]
105         self.check_image_exists(creator.destdir,
106                                 creator.pack_to,
107                                 images,
108                                 creatoropts['release'])
109
110         try:
111             creator.check_depend_tools()
112             creator.mount(None, creatoropts["cachedir"])
113             creator.install()
114             creator.configure(creatoropts["repomd"])
115             creator.copy_kernel()
116             creator.unmount()
117             creator.package(creatoropts["outdir"])
118             if creatoropts['release'] is not None:
119                 creator.release_output(ksconf, creatoropts['outdir'], creatoropts['release'])
120             creator.print_outimage_info()
121
122         except errors.CreatorError:
123             raise
124         finally:
125             creator.cleanup()
126
127         msger.info("Finished.")
128         return 0
129
130     @classmethod
131     def do_chroot(cls, target, cmd):
132         img = target
133         imgsize = misc.get_file_size(img) * 1024L * 1024L
134         partedcmd = fs_related.find_binary_path("parted")
135         disk = fs_related.SparseLoopbackDisk(img, imgsize)
136         imgmnt = misc.mkdtemp()
137         imgloop = PartitionedMount({'/dev/sdb':disk}, imgmnt, skipformat = True)
138         img_fstype = "ext3"
139
140         msger.info("Partition Table:")
141         partnum = []
142         for line in runner.outs([partedcmd, "-s", img, "print"]).splitlines():
143             # no use strip to keep line output here
144             if "Number" in line:
145                 msger.raw(line)
146             if line.strip() and line.strip()[0].isdigit():
147                 partnum.append(line.strip()[0])
148                 msger.raw(line)
149
150         rootpart = None
151         if len(partnum) > 1:
152             rootpart = msger.choice("please choose root partition", partnum)
153
154         # Check the partitions from raw disk.
155         # if choose root part, the mark it as mounted
156         if rootpart:
157             root_mounted = True
158         else:
159             root_mounted = False
160         partition_mounts = 0
161         for line in runner.outs([partedcmd,"-s",img,"unit","B","print"]).splitlines():
162             line = line.strip()
163
164             # Lines that start with number are the partitions,
165             # because parted can be translated we can't refer to any text lines.
166             if not line or not line[0].isdigit():
167                 continue
168
169             # Some vars have extra , as list seperator.
170             line = line.replace(",","")
171
172             # Example of parted output lines that are handled:
173             # Number  Start        End          Size         Type     File system     Flags
174             #  1      512B         3400000511B  3400000000B  primary
175             #  2      3400531968B  3656384511B  255852544B   primary  linux-swap(v1)
176             #  3      3656384512B  3720347647B  63963136B    primary  fat16           boot, lba
177
178             partition_info = re.split("\s+",line)
179
180             size = partition_info[3].split("B")[0]
181
182             if len(partition_info) < 6 or partition_info[5] in ["boot"]:
183                 # No filesystem can be found from partition line. Assuming
184                 # btrfs, because that is the only MeeGo fs that parted does
185                 # not recognize properly.
186                 # TODO: Can we make better assumption?
187                 fstype = "btrfs"
188             elif partition_info[5] in ["ext2","ext3","ext4","btrfs"]:
189                 fstype = partition_info[5]
190             elif partition_info[5] in ["fat16","fat32"]:
191                 fstype = "vfat"
192             elif "swap" in partition_info[5]:
193                 fstype = "swap"
194             else:
195                 raise errors.CreatorError("Could not recognize partition fs type '%s'." % partition_info[5])
196
197             if rootpart and rootpart == line[0]:
198                 mountpoint = '/'
199             elif not root_mounted and fstype in ["ext2","ext3","ext4","btrfs"]:
200                 # TODO: Check that this is actually the valid root partition from /etc/fstab
201                 mountpoint = "/"
202                 root_mounted = True
203             elif fstype == "swap":
204                 mountpoint = "swap"
205             else:
206                 # TODO: Assing better mount points for the rest of the partitions.
207                 partition_mounts += 1
208                 mountpoint = "/media/partition_%d" % partition_mounts
209
210             if "boot" in partition_info:
211                 boot = True
212             else:
213                 boot = False
214
215             msger.verbose("Size: %s Bytes, fstype: %s, mountpoint: %s, boot: %s" % (size, fstype, mountpoint, boot))
216             # TODO: add_partition should take bytes as size parameter.
217             imgloop.add_partition((int)(size)/1024/1024, "/dev/sdb", mountpoint, fstype = fstype, boot = boot)
218
219         try:
220             imgloop.mount()
221
222         except errors.MountError:
223             imgloop.cleanup()
224             raise
225
226         try:
227             if len(cmd) != 0:
228                 cmdline = ' '.join(cmd)
229             else:
230                 cmdline = "/bin/bash"
231             envcmd = fs_related.find_binary_inchroot("env", imgmnt)
232             if envcmd:
233                 cmdline = "%s HOME=/root %s" % (envcmd, cmdline)
234             chroot.chroot(imgmnt, None, cmdline)
235         except:
236             raise errors.CreatorError("Failed to chroot to %s." %img)
237         finally:
238             chroot.cleanup_after_chroot("img", imgloop, None, imgmnt)
239
240     @classmethod
241     def do_unpack(cls, srcimg):
242         srcimgsize = (misc.get_file_size(srcimg)) * 1024L * 1024L
243         srcmnt = misc.mkdtemp("srcmnt")
244         disk = fs_related.SparseLoopbackDisk(srcimg, srcimgsize)
245         srcloop = PartitionedMount({'/dev/sdb':disk}, srcmnt, skipformat = True)
246
247         srcloop.add_partition(srcimgsize/1024/1024, "/dev/sdb", "/", "ext3", boot=False)
248         try:
249             srcloop.mount()
250
251         except errors.MountError:
252             srcloop.cleanup()
253             raise
254
255         image = os.path.join(tempfile.mkdtemp(dir = "/var/tmp", prefix = "tmp"), "target.img")
256         args = ['dd', "if=%s" % srcloop.partitions[0]['device'], "of=%s" % image]
257
258         msger.info("`dd` image ...")
259         rc = runner.show(args)
260         srcloop.cleanup()
261         shutil.rmtree(os.path.dirname(srcmnt), ignore_errors = True)
262
263         if rc != 0:
264             raise errors.CreatorError("Failed to dd")
265         else:
266             return image