ef6b244eb1fd08d2feb2fcdf9aa1b9eca746cdd9
[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 shutil
21 import subprocess
22
23 from mic import msger
24 from mic.conf import configmgr
25 from mic.utils import misc, errors, runner, fs_related
26
27 #####################################################################
28 ### GLOBAL CONSTANTS
29 #####################################################################
30
31 chroot_bindmounts = None
32 chroot_lockfd = -1
33 chroot_lock = ""
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               )
44
45 #####################################################################
46 ### GLOBAL ROUTINE
47 #####################################################################
48
49 def get_bindmounts(chrootdir, bindmounts):
50     global chroot_bindmounts
51
52     if chroot_bindmounts:
53         return chroot_bindmounts
54
55     chrootmounts = []
56     bindmounts = bindmounts or ""
57
58     for mount in bindmounts.split(";"):
59         if not mount:
60             continue
61
62         srcdst = mount.split(":")
63         srcdst[0] = os.path.abspath(os.path.expanduser(srcdst[0]))
64         if len(srcdst) == 1:
65             srcdst.append("none")
66
67         # if some bindmount is not existed, but it's created inside
68         # chroot, this is not expected
69         if not os.path.exists(srcdst[0]):
70             os.makedirs(srcdst[0])
71
72         if not os.path.isdir(srcdst[0]):
73             continue
74
75         if srcdst[0] in BIND_MOUNTS or srcdst[0] == '/':
76             msger.verbose("%s will be mounted by default." % srcdst[0])
77             continue
78
79         if srcdst[1] == "" or srcdst[1] == "none":
80             srcdst[1] = None
81         else:
82             srcdst[1] = os.path.abspath(os.path.expanduser(srcdst[1]))
83             if os.path.isdir(chrootdir + "/" + srcdst[1]):
84                 msger.warning("%s has existed in %s , skip it."\
85                               % (srcdst[1], chrootdir))
86                 continue
87
88         chrootmounts.append(fs_related.BindChrootMount(srcdst[0],
89                                                        chrootdir,
90                                                        srcdst[1]))
91
92     """Default bind mounts"""
93     for pt in BIND_MOUNTS:
94         if not os.path.exists(pt):
95             continue
96         chrootmounts.append(fs_related.BindChrootMount(pt,
97                                                        chrootdir,
98                                                        None))
99
100     for kernel in os.listdir("/lib/modules"):
101         chrootmounts.append(fs_related.BindChrootMount(
102                                             "/lib/modules/"+kernel,
103                                             chrootdir,
104                                             None,
105                                             "ro"))
106     chroot_bindmounts = chrootmounts
107     return chroot_bindmounts
108
109 #####################################################################
110 ### SETUP CHROOT ENVIRONMENT
111 #####################################################################
112
113 def bind_mount(chrootmounts):
114     for b in chrootmounts:
115         msger.verbose("bind_mount: %s -> %s" % (b.src, b.dest))
116         b.mount()
117
118 def setup_resolv(chrootdir):
119     try:
120         shutil.copyfile("/etc/resolv.conf", chrootdir + "/etc/resolv.conf")
121     except:
122         pass
123
124 def setup_mtab(chrootdir):
125     mtab = "/etc/mtab"
126     dstmtab = chrootdir + mtab
127     if not os.path.islink(dstmtab):
128         shutil.copyfile(mtab, dstmtab)
129
130 def setup_chrootenv(chrootdir, bindmounts = None):
131     # bind mounting
132     bind_mount(get_bindmounts(chrootdir, bindmounts))
133     # setup resolv.conf
134     setup_resolv(chrootdir)
135     # update /etc/mtab
136     setup_mtab(chrootdir)
137
138     # lock
139     chroot_lock = os.path.join(chrootdir, ".chroot.lock")
140     chroot_lockfd = open(chroot_lock, "w")
141
142     return None
143
144 ######################################################################
145 ### CLEANUP CHROOT ENVIRONMENT
146 ######################################################################
147
148 def bind_unmount(chrootmounts):
149     for b in reversed(chrootmounts):
150         msger.verbose("bind_unmount: %s -> %s" % (b.src, b.dest))
151         b.unmount()
152
153 def cleanup_resolv(chrootdir):
154     try:
155         fd = open(chrootdir + "/etc/resolv.conf", "w")
156         fd.truncate(0)
157         fd.close()
158     except:
159         pass
160
161 def kill_processes(chrootdir):
162     import glob
163     for fp in glob.glob("/proc/*/root"):
164         try:
165             if os.readlink(fp) == chrootdir:
166                 pid = int(fp.split("/")[2])
167                 os.kill(pid, 9)
168         except:
169             pass
170
171 def cleanup_mtab(chrootdir):
172     if os.path.exists(chrootdir + "/etc/mtab"):
173         os.unlink(chrootdir + "/etc/mtab")
174
175 def cleanup_mounts(chrootdir):
176     umountcmd = misc.find_binary_path("umount")
177     mounts = open('/proc/mounts').readlines()
178     for line in reversed(mounts):
179         if chrootdir not in line:
180             continue
181
182         point = line.split()[1]
183
184         # '/' to avoid common name prefix
185         if chrootdir == point or point.startswith(chrootdir + '/'):
186             args = [ umountcmd, "-l", point ]
187             ret = runner.quiet(args)
188             if ret != 0:
189                 msger.warning("failed to unmount %s" % point)
190             if os.path.isdir(point) and len(os.listdir(point)) == 0:
191                 shutil.rmtree(point)
192             else:
193                 msger.warning("%s is not directory or is not empty" % point)
194
195 def cleanup_chrootenv(chrootdir, bindmounts=None, globalmounts=()):
196     # unlock
197     chroot_lockfd.close()
198     # kill processes
199     kill_processes(chrootdir)
200     # clean mtab
201     cleanup_mtab(chrootdir)
202     # clean resolv.conf
203     cleanup_resolv(chrootdir)
204     # bind umounting
205     bind_unmount(get_bindmounts(chrootdir, bindmounts))
206     # clean up mounts
207     cleanup_mounts(chrootdir)
208
209     return None
210
211 #####################################################################
212 ### CHROOT STUFF
213 #####################################################################
214
215 def cleanup_after_chroot(targettype, imgmount, tmpdir, tmpmnt):
216     if imgmount and targettype == "img":
217         imgmount.cleanup()
218
219     if tmpdir:
220         shutil.rmtree(tmpdir, ignore_errors = True)
221
222     if tmpmnt:
223         shutil.rmtree(tmpmnt, ignore_errors = True)
224
225 def chroot(chrootdir, bindmounts = None, execute = "/bin/bash"):
226     def mychroot():
227         os.chroot(chrootdir)
228         os.chdir("/")
229
230     if configmgr.chroot['saveto']:
231         savefs = True
232         saveto = configmgr.chroot['saveto']
233         wrnmsg = "Can't save chroot fs for dir %s exists" % saveto
234         if saveto == chrootdir:
235             savefs = False
236             wrnmsg = "Dir %s is being used to chroot" % saveto
237         elif os.path.exists(saveto):
238             if msger.ask("Dir %s already exists, cleanup and continue?" %
239                          saveto):
240                 shutil.rmtree(saveto, ignore_errors = True)
241                 savefs = True
242             else:
243                 savefs = False
244
245         if savefs:
246             msger.info("Saving image to directory %s" % saveto)
247             fs_related.makedirs(os.path.dirname(os.path.abspath(saveto)))
248             runner.quiet("cp -af %s %s" % (chrootdir, saveto))
249             devs = ['dev/fd',
250                     'dev/stdin',
251                     'dev/stdout',
252                     'dev/stderr',
253                     'etc/mtab']
254             ignlst = [os.path.join(saveto, x) for x in devs]
255             map(os.unlink, filter(os.path.exists, ignlst))
256         else:
257             msger.warning(wrnmsg)
258
259     files_to_check = ["/bin/bash", "/sbin/init"]
260
261     architecture_found = False
262
263     """ Register statically-linked qemu-arm if it is an ARM fs """
264     qemu_emulator = None
265
266     for ftc in files_to_check:
267         ftc = "%s/%s" % (chrootdir,ftc)
268
269         # Return code of 'file' is "almost always" 0 based on some man pages
270         # so we need to check the file existance first.
271         if not os.path.exists(ftc):
272             continue
273
274         for line in runner.outs(['file', ftc]).splitlines():
275             if 'ARM' in line:
276                 qemu_emulator = misc.setup_qemu_emulator(chrootdir, "arm")
277                 architecture_found = True
278                 break
279
280             if 'Intel' in line:
281                 architecture_found = True
282                 break
283
284         if architecture_found:
285             break
286
287     if not architecture_found:
288         raise errors.CreatorError("Failed to get architecture from any of the "
289                                   "following files %s from chroot." \
290                                   % files_to_check)
291
292     try:
293         msger.info("Launching shell. Exit to continue.\n"
294                    "----------------------------------")
295         globalmounts = setup_chrootenv(chrootdir, bindmounts)
296         subprocess.call(execute, preexec_fn = mychroot, shell=True)
297
298     except OSError, err:
299         raise errors.CreatorError("chroot err: %s" % str(err))
300
301     finally:
302         cleanup_chrootenv(chrootdir, bindmounts, globalmounts)
303         if qemu_emulator:
304             os.unlink(chrootdir + qemu_emulator)