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