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