Add block map functionality
[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.generate_bmap()
118             creator.package(creatoropts["outdir"])
119             if creatoropts['release'] is not None:
120                 creator.release_output(ksconf, creatoropts['outdir'], creatoropts['release'])
121             creator.print_outimage_info()
122
123         except errors.CreatorError:
124             raise
125         finally:
126             creator.cleanup()
127
128         msger.info("Finished.")
129         return 0
130
131     @classmethod
132     def do_chroot(cls, target, cmd):
133         img = target
134         imgsize = misc.get_file_size(img) * 1024L * 1024L
135         partedcmd = fs_related.find_binary_path("parted")
136         disk = fs_related.SparseLoopbackDisk(img, imgsize)
137         imgmnt = misc.mkdtemp()
138         imgloop = PartitionedMount({'/dev/sdb':disk}, imgmnt, skipformat = True)
139         img_fstype = "ext3"
140
141         msger.info("Partition Table:")
142         partnum = []
143         for line in runner.outs([partedcmd, "-s", img, "print"]).splitlines():
144             # no use strip to keep line output here
145             if "Number" in line:
146                 msger.raw(line)
147             if line.strip() and line.strip()[0].isdigit():
148                 partnum.append(line.strip()[0])
149                 msger.raw(line)
150
151         rootpart = None
152         if len(partnum) > 1:
153             rootpart = msger.choice("please choose root partition", partnum)
154
155         # Check the partitions from raw disk.
156         # if choose root part, the mark it as mounted
157         if rootpart:
158             root_mounted = True
159         else:
160             root_mounted = False
161         partition_mounts = 0
162         for line in runner.outs([partedcmd,"-s",img,"unit","B","print"]).splitlines():
163             line = line.strip()
164
165             # Lines that start with number are the partitions,
166             # because parted can be translated we can't refer to any text lines.
167             if not line or not line[0].isdigit():
168                 continue
169
170             # Some vars have extra , as list seperator.
171             line = line.replace(",","")
172
173             # Example of parted output lines that are handled:
174             # Number  Start        End          Size         Type     File system     Flags
175             #  1      512B         3400000511B  3400000000B  primary
176             #  2      3400531968B  3656384511B  255852544B   primary  linux-swap(v1)
177             #  3      3656384512B  3720347647B  63963136B    primary  fat16           boot, lba
178
179             partition_info = re.split("\s+",line)
180
181             size = partition_info[3].split("B")[0]
182
183             if len(partition_info) < 6 or partition_info[5] in ["boot"]:
184                 # No filesystem can be found from partition line. Assuming
185                 # btrfs, because that is the only MeeGo fs that parted does
186                 # not recognize properly.
187                 # TODO: Can we make better assumption?
188                 fstype = "btrfs"
189             elif partition_info[5] in ["ext2","ext3","ext4","btrfs"]:
190                 fstype = partition_info[5]
191             elif partition_info[5] in ["fat16","fat32"]:
192                 fstype = "vfat"
193             elif "swap" in partition_info[5]:
194                 fstype = "swap"
195             else:
196                 raise errors.CreatorError("Could not recognize partition fs type '%s'." % partition_info[5])
197
198             if rootpart and rootpart == line[0]:
199                 mountpoint = '/'
200             elif not root_mounted and fstype in ["ext2","ext3","ext4","btrfs"]:
201                 # TODO: Check that this is actually the valid root partition from /etc/fstab
202                 mountpoint = "/"
203                 root_mounted = True
204             elif fstype == "swap":
205                 mountpoint = "swap"
206             else:
207                 # TODO: Assing better mount points for the rest of the partitions.
208                 partition_mounts += 1
209                 mountpoint = "/media/partition_%d" % partition_mounts
210
211             if "boot" in partition_info:
212                 boot = True
213             else:
214                 boot = False
215
216             msger.verbose("Size: %s Bytes, fstype: %s, mountpoint: %s, boot: %s" % (size, fstype, mountpoint, boot))
217             # TODO: add_partition should take bytes as size parameter.
218             imgloop.add_partition((int)(size)/1024/1024, "/dev/sdb", mountpoint, fstype = fstype, boot = boot)
219
220         try:
221             imgloop.mount()
222
223         except errors.MountError:
224             imgloop.cleanup()
225             raise
226
227         try:
228             if len(cmd) != 0:
229                 cmdline = ' '.join(cmd)
230             else:
231                 cmdline = "/bin/bash"
232             envcmd = fs_related.find_binary_inchroot("env", imgmnt)
233             if envcmd:
234                 cmdline = "%s HOME=/root %s" % (envcmd, cmdline)
235             chroot.chroot(imgmnt, None, cmdline)
236         except:
237             raise errors.CreatorError("Failed to chroot to %s." %img)
238         finally:
239             chroot.cleanup_after_chroot("img", imgloop, None, imgmnt)
240
241     @classmethod
242     def do_unpack(cls, srcimg):
243         srcimgsize = (misc.get_file_size(srcimg)) * 1024L * 1024L
244         srcmnt = misc.mkdtemp("srcmnt")
245         disk = fs_related.SparseLoopbackDisk(srcimg, srcimgsize)
246         srcloop = PartitionedMount({'/dev/sdb':disk}, srcmnt, skipformat = True)
247
248         srcloop.add_partition(srcimgsize/1024/1024, "/dev/sdb", "/", "ext3", boot=False)
249         try:
250             srcloop.mount()
251
252         except errors.MountError:
253             srcloop.cleanup()
254             raise
255
256         image = os.path.join(tempfile.mkdtemp(dir = "/var/tmp", prefix = "tmp"), "target.img")
257         args = ['dd', "if=%s" % srcloop.partitions[0]['device'], "of=%s" % image]
258
259         msger.info("`dd` image ...")
260         rc = runner.show(args)
261         srcloop.cleanup()
262         shutil.rmtree(os.path.dirname(srcmnt), ignore_errors = True)
263
264         if rc != 0:
265             raise errors.CreatorError("Failed to dd")
266         else:
267             return image