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