8792cadbc1923baba361b765921bda7effbeb99e
[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     #FIXME: if chkfiles are symlink, it will be complex
52     chkfiles = ('/bin/bash', '/sbin/init')
53     # regular expression to arch mapping
54     mapping = {
55                 r"Intel 80[0-9]86": "i686",
56                 r"x86-64": "x86_64",
57                 r"ARM": "arm",
58               }
59
60     for path in chkfiles:
61         cpath = os.path.join(chrootdir, path.lstrip('/'))
62         if not os.path.exists(cpath):
63             continue
64
65         outs = runner.outs(['file', cpath])
66         for ptn in mapping.keys():
67             if re.search(ptn, outs):
68                 return mapping[ptn]
69
70     raise errors.CreatorError("Failed to detect architecture of chroot: %s" %
71                               chrootdir)
72
73 def get_bindmounts(chrootdir, bindmounts = None):
74     # bindmounts should be a string like '/dev:/dev'
75     # FIXME: refine the bindmounts from string to dict
76     global chroot_bindmounts
77
78     def totuple(string):
79         if ':' in string:
80             src, dst = string.split(':', 1)
81         else:
82             src = string
83             dst = None
84
85         return (src or None, dst or None)
86
87     if chroot_bindmounts:
88         return chroot_bindmounts
89
90     chroot_bindmounts = []
91     bindmounts = bindmounts or ""
92     mountlist = []
93
94     for mount in bindmounts.split(";"):
95         if not mount:
96             continue
97
98         (src, dst) = totuple(mount)
99
100         if src in BIND_MOUNTS or src == '/':
101             continue
102
103         if not os.path.exists(src):
104             os.makedirs(src)
105
106         if dst and os.path.isdir("%s/%s" % (chrootdir, dst)):
107             msger.warning("%s existed in %s , skip it." % (dst, chrootdir))
108             continue
109
110         mountlist.append(totuple(mount))
111
112     for mntpoint in BIND_MOUNTS:
113         if os.path.isdir(mntpoint):
114             mountlist.append(tuple((mntpoint, None)))
115
116     for pair in mountlist:
117         bmount = fs_related.BindChrootMount(pair[0], chrootdir, pair[1])
118         chroot_bindmounts.append(bmount)
119
120     return chroot_bindmounts
121
122 #####################################################################
123 ### SETUP CHROOT ENVIRONMENT
124 #####################################################################
125
126 def bind_mount(chrootmounts):
127     for b in chrootmounts:
128         msger.verbose("bind_mount: %s -> %s" % (b.src, b.dest))
129         b.mount()
130
131 def setup_resolv(chrootdir):
132     try:
133         shutil.copyfile("/etc/resolv.conf", chrootdir + "/etc/resolv.conf")
134     except:
135         pass
136
137 def setup_mtab(chrootdir):
138     mtab = "/etc/mtab"
139     dstmtab = chrootdir + mtab
140     if not os.path.islink(dstmtab):
141         shutil.copyfile(mtab, dstmtab)
142
143 def setup_chrootenv(chrootdir, bindmounts = None):
144     global chroot_lock
145
146     # acquire the lock
147     if not chroot_lock:
148         lockpath = os.path.join(chrootdir, '.chroot.lock')
149         chroot_lock = lock.SimpleLockfile(lockpath)
150     chroot_lock.acquire()
151     # bind mounting
152     bind_mount(get_bindmounts(chrootdir, bindmounts))
153     # setup resolv.conf
154     setup_resolv(chrootdir)
155     # update /etc/mtab
156     setup_mtab(chrootdir)
157
158     # lock
159     chroot_lock = os.path.join(chrootdir, ".chroot.lock")
160     chroot_lockfd = open(chroot_lock, "w")
161
162     return None
163
164 ######################################################################
165 ### CLEANUP CHROOT ENVIRONMENT
166 ######################################################################
167
168 def bind_unmount(chrootmounts):
169     for b in reversed(chrootmounts):
170         msger.verbose("bind_unmount: %s -> %s" % (b.src, b.dest))
171         b.unmount()
172
173 def cleanup_resolv(chrootdir):
174     try:
175         fd = open(chrootdir + "/etc/resolv.conf", "w")
176         fd.truncate(0)
177         fd.close()
178     except:
179         pass
180
181 def kill_proc_inchroot(chrootdir):
182     import glob
183     for fp in glob.glob("/proc/*/root"):
184         try:
185             if os.readlink(fp) == chrootdir:
186                 pid = int(fp.split("/")[2])
187                 os.kill(pid, 9)
188         except:
189             pass
190
191 def cleanup_mtab(chrootdir):
192     if os.path.exists(chrootdir + "/etc/mtab"):
193         os.unlink(chrootdir + "/etc/mtab")
194
195 def cleanup_mounts(chrootdir):
196     umountcmd = misc.find_binary_path("umount")
197     mounts = open('/proc/mounts').readlines()
198     for line in reversed(mounts):
199         if chrootdir not in line:
200             continue
201
202         point = line.split()[1]
203
204         # '/' to avoid common name prefix
205         if chrootdir == point or point.startswith(chrootdir + '/'):
206             args = [ umountcmd, "-l", point ]
207             ret = runner.quiet(args)
208             if ret != 0:
209                 msger.warning("failed to unmount %s" % point)
210             if os.path.isdir(point) and len(os.listdir(point)) == 0:
211                 shutil.rmtree(point)
212             else:
213                 msger.warning("%s is not directory or is not empty" % point)
214
215 def cleanup_chrootenv(chrootdir, bindmounts=None, globalmounts=()):
216     # kill processes
217     kill_proc_inchroot(chrootdir)
218     # clean mtab
219     cleanup_mtab(chrootdir)
220     # clean resolv.conf
221     cleanup_resolv(chrootdir)
222     # bind umounting
223     bind_unmount(get_bindmounts(chrootdir, bindmounts))
224     # clean up mounts
225     cleanup_mounts(chrootdir)
226     # release the lock
227     chroot_lock.release()
228
229     return None
230
231 #####################################################################
232 ### CHROOT STUFF
233 #####################################################################
234
235 def savefs_before_chroot(chrootdir, saveto = None):
236     if configmgr.chroot['saveto']:
237         savefs = True
238         saveto = configmgr.chroot['saveto']
239         wrnmsg = "Can't save chroot fs for dir %s exists" % saveto
240         if saveto == chrootdir:
241             savefs = False
242             wrnmsg = "Dir %s is being used to chroot" % saveto
243         elif os.path.exists(saveto):
244             if msger.ask("Dir %s already exists, cleanup and continue?" %
245                          saveto):
246                 shutil.rmtree(saveto, ignore_errors = True)
247                 savefs = True
248             else:
249                 savefs = False
250
251         if savefs:
252             msger.info("Saving image to directory %s" % saveto)
253             fs_related.makedirs(os.path.dirname(os.path.abspath(saveto)))
254             runner.quiet("cp -af %s %s" % (chrootdir, saveto))
255             devs = ['dev/fd',
256                     'dev/stdin',
257                     'dev/stdout',
258                     'dev/stderr',
259                     'etc/mtab']
260             ignlst = [os.path.join(saveto, x) for x in devs]
261             map(os.unlink, filter(os.path.exists, ignlst))
262         else:
263             msger.warning(wrnmsg)
264
265 def cleanup_after_chroot(targettype, imgmount, tmpdir, tmpmnt):
266     if imgmount and targettype == "img":
267         imgmount.cleanup()
268
269     if tmpdir:
270         shutil.rmtree(tmpdir, ignore_errors = True)
271
272     if tmpmnt:
273         shutil.rmtree(tmpmnt, ignore_errors = True)
274
275 def chroot(chrootdir, bindmounts = None, execute = "/bin/bash"):
276     def mychroot():
277         os.chroot(chrootdir)
278         os.chdir("/")
279
280     arch = ELF_arch(chrootdir)
281     if arch == "arm":
282         qemu_emulator = misc.setup_qemu_emulator(chrootdir, "arm")
283     else:
284         qemu_emulator = None
285
286     savefs_before_chroot(chrootdir, None)
287
288     try:
289         msger.info("Launching shell. Exit to continue.\n"
290                    "----------------------------------")
291         globalmounts = setup_chrootenv(chrootdir, bindmounts)
292         subprocess.call(execute, preexec_fn = mychroot, shell=True)
293
294     except OSError, err:
295         raise errors.CreatorError("chroot err: %s" % str(err))
296
297     finally:
298         cleanup_chrootenv(chrootdir, bindmounts, globalmounts)
299         if qemu_emulator:
300             os.unlink(chrootdir + qemu_emulator)