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