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