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