Revert "Drop mic raw image format support"
[tools/mic.git] / mic / chroot.py
1 #!/usr/bin/python -tt
2 #
3 # Copyright (c) 2009, 2010, 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 from __future__ import with_statement
19 import os
20 import re
21 import shutil
22 import subprocess
23
24 from mic import msger
25 from mic.conf import configmgr
26 from mic.utils import misc, errors, runner, fs_related, lock
27
28 #####################################################################
29 ### GLOBAL CONSTANTS
30 #####################################################################
31
32 chroot_bindmounts = None
33 chroot_lock = None
34 BIND_MOUNTS = (
35                 "/proc",
36                 "/proc/sys/fs/binfmt_misc",
37                 "/sys",
38                 "/dev",
39                 "/dev/pts",
40                 "/var/lib/dbus",
41                 "/var/run/dbus",
42                 "/var/lock",
43                 "/lib/modules",
44               )
45
46 #####################################################################
47 ### GLOBAL ROUTINE
48 #####################################################################
49
50 def ELF_arch(chrootdir):
51     """ detect the architecture of an ELF file """
52     #FIXME: if chkfiles are symlink, it will be complex
53     chkfiles = ('/bin/bash', '/sbin/init')
54     # regular expression to arch mapping
55     mapping = {
56                 r"Intel 80[0-9]86": "i686",
57                 r"x86-64": "x86_64",
58                 r"ARM": "arm",
59               }
60
61     for path in chkfiles:
62         cpath = os.path.join(chrootdir, path.lstrip('/'))
63         if not os.path.exists(cpath):
64             continue
65
66         outs = runner.outs(['file', cpath])
67         for ptn in mapping.keys():
68             if re.search(ptn, outs):
69                 return mapping[ptn]
70
71     raise errors.CreatorError("Failed to detect architecture of chroot: %s" %
72                               chrootdir)
73
74 def get_bindmounts(chrootdir, bindmounts = None):
75     """ calculate all bind mount entries for global usage """
76     # bindmounts should be a string like '/dev:/dev'
77     # FIXME: refine the bindmounts from string to dict
78     global chroot_bindmounts
79
80     def totuple(string):
81         """ convert string contained ':' to a tuple """
82         if ':' in string:
83             src, dst = string.split(':', 1)
84         else:
85             src = string
86             dst = None
87
88         return (src or None, dst or None)
89
90     if chroot_bindmounts:
91         return chroot_bindmounts
92
93     chroot_bindmounts = []
94     bindmounts = bindmounts or ""
95     mountlist = []
96
97     for mount in bindmounts.split(";"):
98         if not mount:
99             continue
100
101         (src, dst) = totuple(mount)
102
103         if src in BIND_MOUNTS or src == '/':
104             continue
105
106         if not os.path.exists(src):
107             os.makedirs(src)
108
109         if dst and os.path.isdir("%s/%s" % (chrootdir, dst)):
110             msger.warning("%s existed in %s , skip it." % (dst, chrootdir))
111             continue
112
113         mountlist.append(totuple(mount))
114
115     for mntpoint in BIND_MOUNTS:
116         if os.path.isdir(mntpoint):
117             mountlist.append(tuple((mntpoint, None)))
118
119     for pair in mountlist:
120         if pair[0] == "/lib/modules":
121             opt = "ro"
122         else:
123             opt = None
124         bmount = fs_related.BindChrootMount(pair[0], chrootdir, pair[1], opt)
125         chroot_bindmounts.append(bmount)
126
127     return chroot_bindmounts
128
129 #####################################################################
130 ### SETUP CHROOT ENVIRONMENT
131 #####################################################################
132
133 def bind_mount(chrootmounts):
134     """ perform bind mounting """
135     for mnt in chrootmounts:
136         msger.verbose("bind_mount: %s -> %s" % (mnt.src, mnt.dest))
137         mnt.mount()
138
139 def setup_resolv(chrootdir):
140     """ resolve network """
141     try:
142         shutil.copyfile("/etc/resolv.conf", chrootdir + "/etc/resolv.conf")
143     except (OSError, IOError):
144         pass
145
146 def setup_mtab(chrootdir):
147     """ adjust mount table """
148     try:
149         mtab = "/etc/mtab"
150         dstmtab = chrootdir + mtab
151         if not os.path.islink(dstmtab):
152             shutil.copyfile(mtab, dstmtab)
153     except (OSError, IOError):
154         pass
155
156 def setup_chrootenv(chrootdir, bindmounts = None):
157     """ setup chroot environment """
158     global chroot_lock
159
160     # acquire the lock
161     if not chroot_lock:
162         lockpath = os.path.join(chrootdir, '.chroot.lock')
163         chroot_lock = lock.SimpleLockfile(lockpath)
164     chroot_lock.acquire()
165     # bind mounting
166     bind_mount(get_bindmounts(chrootdir, bindmounts))
167     # setup resolv.conf
168     setup_resolv(chrootdir)
169     # update /etc/mtab
170     setup_mtab(chrootdir)
171
172     return None
173
174 ######################################################################
175 ### CLEANUP CHROOT ENVIRONMENT
176 ######################################################################
177
178 def bind_unmount(chrootmounts):
179     """ perform bind unmounting """
180     for mnt in reversed(chrootmounts):
181         msger.verbose("bind_unmount: %s -> %s" % (mnt.src, mnt.dest))
182         mnt.unmount()
183
184 def cleanup_resolv(chrootdir):
185     """ clear resolv.conf """
186     try:
187         fdes = open(chrootdir + "/etc/resolv.conf", "w")
188         fdes.truncate(0)
189         fdes.close()
190     except (OSError, IOError):
191         pass
192
193 def kill_proc_inchroot(chrootdir):
194     """ kill all processes running inside chrootdir """
195     import glob
196     for fpath in glob.glob("/proc/*/root"):
197         try:
198             if os.readlink(fpath) == chrootdir:
199                 pid = int(fpath.split("/")[2])
200                 os.kill(pid, 9)
201         except (OSError, ValueError):
202             pass
203
204 def cleanup_mtab(chrootdir):
205     """ remove mtab file """
206     if os.path.exists(chrootdir + "/etc/mtab"):
207         os.unlink(chrootdir + "/etc/mtab")
208
209 def cleanup_mounts(chrootdir):
210     """ clean up all mount entries owned by chrootdir """
211     umountcmd = misc.find_binary_path("umount")
212     mounts = open('/proc/mounts').readlines()
213     for line in reversed(mounts):
214         if chrootdir not in line:
215             continue
216
217         point = line.split()[1]
218
219         # '/' to avoid common name prefix
220         if chrootdir == point or point.startswith(chrootdir + '/'):
221             args = [ umountcmd, "-l", point ]
222             ret = runner.quiet(args)
223             if ret != 0:
224                 msger.warning("failed to unmount %s" % point)
225             if os.path.isdir(point) and len(os.listdir(point)) == 0:
226                 shutil.rmtree(point)
227             else:
228                 msger.warning("%s is not directory or is not empty" % point)
229
230 def cleanup_chrootenv(chrootdir, bindmounts=None, globalmounts=()):
231     """ clean up chroot environment """
232     global chroot_lock
233
234     # kill processes
235     kill_proc_inchroot(chrootdir)
236     # clean mtab
237     cleanup_mtab(chrootdir)
238     # clean resolv.conf
239     cleanup_resolv(chrootdir)
240     # bind umounting
241     bind_unmount(get_bindmounts(chrootdir, bindmounts))
242     # FIXME: need to clean up mounts?
243     #cleanup_mounts(chrootdir)
244
245     # release the lock
246     if chroot_lock:
247         chroot_lock.release()
248         chroot_lock = None
249
250     return None
251
252 #####################################################################
253 ### CHROOT STUFF
254 #####################################################################
255
256 def savefs_before_chroot(chrootdir, saveto = None):
257     """ backup chrootdir to another directory before chrooting in """
258     if configmgr.chroot['saveto']:
259         savefs = True
260         saveto = configmgr.chroot['saveto']
261         wrnmsg = "Can't save chroot fs for dir %s exists" % saveto
262         if saveto == chrootdir:
263             savefs = False
264             wrnmsg = "Dir %s is being used to chroot" % saveto
265         elif os.path.exists(saveto):
266             if msger.ask("Dir %s already exists, cleanup and continue?" %
267                          saveto):
268                 shutil.rmtree(saveto, ignore_errors = True)
269                 savefs = True
270             else:
271                 savefs = False
272
273         if savefs:
274             msger.info("Saving image to directory %s" % saveto)
275             fs_related.makedirs(os.path.dirname(os.path.abspath(saveto)))
276             runner.quiet("cp -af %s %s" % (chrootdir, saveto))
277             devs = ['dev/fd',
278                     'dev/stdin',
279                     'dev/stdout',
280                     'dev/stderr',
281                     'etc/mtab']
282             ignlst = [os.path.join(saveto, x) for x in devs]
283             map(os.unlink, filter(os.path.exists, ignlst))
284         else:
285             msger.warning(wrnmsg)
286
287 def cleanup_after_chroot(targettype, imgmount, tmpdir, tmpmnt):
288     """ clean up all temporary directories after chrooting """
289     if imgmount and targettype == "img":
290         imgmount.cleanup()
291
292     if tmpdir:
293         shutil.rmtree(tmpdir, ignore_errors = True)
294
295     if tmpmnt:
296         shutil.rmtree(tmpmnt, ignore_errors = True)
297
298 def chroot(chrootdir, bindmounts = None, execute = "/bin/bash"):
299     """ chroot the chrootdir and execute the command """
300     def mychroot():
301         """ pre-execute function """
302         os.chroot(chrootdir)
303         os.chdir("/")
304
305     arch = ELF_arch(chrootdir)
306     if arch == "arm":
307         qemu_emulator = misc.setup_qemu_emulator(chrootdir, "arm")
308     else:
309         qemu_emulator = None
310
311     savefs_before_chroot(chrootdir, None)
312
313     globalmounts = None
314
315     try:
316         msger.info("Launching shell. Exit to continue.\n"
317                    "----------------------------------")
318
319         globalmounts = setup_chrootenv(chrootdir, bindmounts)
320         subprocess.call(execute, preexec_fn = mychroot, shell=True)
321
322     except OSError, err:
323         raise errors.CreatorError("chroot err: %s" % str(err))
324
325     finally:
326         cleanup_chrootenv(chrootdir, bindmounts, globalmounts)
327         if qemu_emulator:
328             os.unlink(chrootdir + qemu_emulator)