fix pylint
[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         bmount = fs_related.BindChrootMount(pair[0], chrootdir, pair[1])
121         chroot_bindmounts.append(bmount)
122
123     return chroot_bindmounts
124
125 #####################################################################
126 ### SETUP CHROOT ENVIRONMENT
127 #####################################################################
128
129 def bind_mount(chrootmounts):
130     """ perform bind mounting """
131     for mnt in chrootmounts:
132         msger.verbose("bind_mount: %s -> %s" % (mnt.src, mnt.dest))
133         mnt.mount()
134
135 def setup_resolv(chrootdir):
136     """ resolve network """
137     try:
138         shutil.copyfile("/etc/resolv.conf", chrootdir + "/etc/resolv.conf")
139     except (OSError, IOError):
140         pass
141
142 def setup_mtab(chrootdir):
143     """ adjust mount table """
144     mtab = "/etc/mtab"
145     dstmtab = chrootdir + mtab
146     if not os.path.islink(dstmtab):
147         shutil.copyfile(mtab, dstmtab)
148
149 def setup_chrootenv(chrootdir, bindmounts = None):
150     """ setup chroot environment """
151     global chroot_lock
152
153     # acquire the lock
154     if not chroot_lock:
155         lockpath = os.path.join(chrootdir, '.chroot.lock')
156         chroot_lock = lock.SimpleLockfile(lockpath)
157     chroot_lock.acquire()
158     # bind mounting
159     bind_mount(get_bindmounts(chrootdir, bindmounts))
160     # setup resolv.conf
161     setup_resolv(chrootdir)
162     # update /etc/mtab
163     setup_mtab(chrootdir)
164
165     # lock
166     chroot_lock = os.path.join(chrootdir, ".chroot.lock")
167     chroot_lockfd = open(chroot_lock, "w")
168
169     return None
170
171 ######################################################################
172 ### CLEANUP CHROOT ENVIRONMENT
173 ######################################################################
174
175 def bind_unmount(chrootmounts):
176     """ perform bind unmounting """
177     for mnt in reversed(chrootmounts):
178         msger.verbose("bind_unmount: %s -> %s" % (mnt.src, mnt.dest))
179         mnt.unmount()
180
181 def cleanup_resolv(chrootdir):
182     """ clear resolv.conf """
183     try:
184         fdes = open(chrootdir + "/etc/resolv.conf", "w")
185         fdes.truncate(0)
186         fdes.close()
187     except (OSError, IOError):
188         pass
189
190 def kill_proc_inchroot(chrootdir):
191     """ kill all processes running inside chrootdir """
192     import glob
193     for fpath in glob.glob("/proc/*/root"):
194         try:
195             if os.readlink(fpath) == chrootdir:
196                 pid = int(fpath.split("/")[2])
197                 os.kill(pid, 9)
198         except (OSError, ValueError):
199             pass
200
201 def cleanup_mtab(chrootdir):
202     """ remove mtab file """
203     if os.path.exists(chrootdir + "/etc/mtab"):
204         os.unlink(chrootdir + "/etc/mtab")
205
206 def cleanup_mounts(chrootdir):
207     """ clean up all mount entries owned by chrootdir """
208     umountcmd = misc.find_binary_path("umount")
209     mounts = open('/proc/mounts').readlines()
210     for line in reversed(mounts):
211         if chrootdir not in line:
212             continue
213
214         point = line.split()[1]
215
216         # '/' to avoid common name prefix
217         if chrootdir == point or point.startswith(chrootdir + '/'):
218             args = [ umountcmd, "-l", point ]
219             ret = runner.quiet(args)
220             if ret != 0:
221                 msger.warning("failed to unmount %s" % point)
222             if os.path.isdir(point) and len(os.listdir(point)) == 0:
223                 shutil.rmtree(point)
224             else:
225                 msger.warning("%s is not directory or is not empty" % point)
226
227 def cleanup_chrootenv(chrootdir, bindmounts=None, globalmounts=()):
228     """ clean up chroot environment """
229     # kill processes
230     kill_proc_inchroot(chrootdir)
231     # clean mtab
232     cleanup_mtab(chrootdir)
233     # clean resolv.conf
234     cleanup_resolv(chrootdir)
235     # bind umounting
236     bind_unmount(get_bindmounts(chrootdir, bindmounts))
237     # clean up mounts
238     cleanup_mounts(chrootdir)
239     # release the lock
240     chroot_lock.release()
241
242     return None
243
244 #####################################################################
245 ### CHROOT STUFF
246 #####################################################################
247
248 def savefs_before_chroot(chrootdir, saveto = None):
249     """ backup chrootdir to another directory before chrooting in """
250     if configmgr.chroot['saveto']:
251         savefs = True
252         saveto = configmgr.chroot['saveto']
253         wrnmsg = "Can't save chroot fs for dir %s exists" % saveto
254         if saveto == chrootdir:
255             savefs = False
256             wrnmsg = "Dir %s is being used to chroot" % saveto
257         elif os.path.exists(saveto):
258             if msger.ask("Dir %s already exists, cleanup and continue?" %
259                          saveto):
260                 shutil.rmtree(saveto, ignore_errors = True)
261                 savefs = True
262             else:
263                 savefs = False
264
265         if savefs:
266             msger.info("Saving image to directory %s" % saveto)
267             fs_related.makedirs(os.path.dirname(os.path.abspath(saveto)))
268             runner.quiet("cp -af %s %s" % (chrootdir, saveto))
269             devs = ['dev/fd',
270                     'dev/stdin',
271                     'dev/stdout',
272                     'dev/stderr',
273                     'etc/mtab']
274             ignlst = [os.path.join(saveto, x) for x in devs]
275             map(os.unlink, filter(os.path.exists, ignlst))
276         else:
277             msger.warning(wrnmsg)
278
279 def cleanup_after_chroot(targettype, imgmount, tmpdir, tmpmnt):
280     """ clean up all temporary directories after chrooting """
281     if imgmount and targettype == "img":
282         imgmount.cleanup()
283
284     if tmpdir:
285         shutil.rmtree(tmpdir, ignore_errors = True)
286
287     if tmpmnt:
288         shutil.rmtree(tmpmnt, ignore_errors = True)
289
290 def chroot(chrootdir, bindmounts = None, execute = "/bin/bash"):
291     """ chroot the chrootdir and execute the command """
292     def mychroot():
293         """ pre-execute function """
294         os.chroot(chrootdir)
295         os.chdir("/")
296
297     arch = ELF_arch(chrootdir)
298     if arch == "arm":
299         qemu_emulator = misc.setup_qemu_emulator(chrootdir, "arm")
300     else:
301         qemu_emulator = None
302
303     savefs_before_chroot(chrootdir, None)
304
305     try:
306         msger.info("Launching shell. Exit to continue.\n"
307                    "----------------------------------")
308         globalmounts = setup_chrootenv(chrootdir, bindmounts)
309         subprocess.call(execute, preexec_fn = mychroot, shell=True)
310
311     except OSError, err:
312         raise errors.CreatorError("chroot err: %s" % str(err))
313
314     finally:
315         cleanup_chrootenv(chrootdir, bindmounts, globalmounts)
316         if qemu_emulator:
317             os.unlink(chrootdir + qemu_emulator)