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