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