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