35abc0b79fd677669c74362f45d0f8d0f0481414
[tools/mic.git] / mic / chroot.py
1 #!/usr/bin/python -tt
2 #
3 # Copyright 2009, 2010, 2011 Intel, Inc.
4 #
5 # This copyrighted material is made available to anyone wishing to use, modify,
6 # copy, or redistribute it subject to the terms and conditions of the GNU
7 # General Public License v.2.  This program is distributed in the hope that it
8 # will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the
9 # implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
10 # See the GNU General Public License for more details.
11 #
12 # You should have received a copy of the GNU General Public License along with
13 # this program; if not, write to the Free Software Foundation, Inc., 51
14 # Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  Any Red Hat
15 # trademarks that are incorporated in the source code or documentation are not
16 # subject to the GNU General Public License and may only be used or replicated
17 # with the express permission of Red Hat, Inc.
18 #
19
20 from __future__ import with_statement
21 import os, sys
22 import shutil
23 import subprocess
24
25 import mic.utils.fs_related as fs_related
26 import mic.utils.misc as misc
27 import mic.utils.errors as errors
28 from mic import msger
29
30 chroot_lockfd = -1
31 chroot_lock = ""
32 BIND_MOUNTS = (
33                 "/proc",
34                 "/proc/sys/fs/binfmt_misc",
35                 "/sys",
36                 "/dev",
37                 "/dev/pts",
38                 "/dev/shm",
39                 "/var/lib/dbus",
40                 "/var/run/dbus",
41                 "/var/lock",
42               )
43
44 def cleanup_after_chroot(targettype,imgmount,tmpdir,tmpmnt):
45     if imgmount and targettype == "img":
46         imgmount.cleanup()
47
48     if tmpdir:
49         shutil.rmtree(tmpdir, ignore_errors = True)
50
51     if tmpmnt:
52         shutil.rmtree(tmpmnt, ignore_errors = True)
53
54 def check_bind_mounts(chrootdir, bindmounts):
55     chrootmounts = []
56     for mount in bindmounts.split(";"):
57         if not mount:
58             continue
59
60         srcdst = mount.split(":")
61         if len(srcdst) == 1:
62            srcdst.append("none")
63
64         if not os.path.isdir(srcdst[0]):
65             return False
66
67         if srcdst[1] == "" or srcdst[1] == "none":
68             srcdst[1] = None
69
70         if srcdst[0] in BIND_MOUNTS or srcdst[0] == '/':
71             continue
72
73         if chrootdir:
74             if not srcdst[1]:
75                 srcdst[1] = os.path.abspath(os.path.expanduser(srcdst[0]))
76             else:
77                 srcdst[1] = os.path.abspath(os.path.expanduser(srcdst[1]))
78
79             tmpdir = chrootdir + "/" + srcdst[1]
80             if os.path.isdir(tmpdir):
81                 msger.warning("Warning: dir %s has existed."  % tmpdir)
82
83     return True
84
85 def cleanup_mounts(chrootdir):
86     umountcmd = misc.find_binary_path("umount")
87     for point in BIND_MOUNTS:
88         args = [ umountcmd, "-l", chrootdir + point ]
89         runner.quiet(args)
90
91     abs_chrootdir = os.path.abspath(chrootdir)
92     with open('/proc/mounts') as f:
93         for line in f:
94             if abs_chrootdir in line:
95                 point = line.split()[1]
96
97                 if abs_chrootdir == point:
98                     continue
99
100                 args = [ umountcmd, "-l", point ]
101                 ret = runner.quiet(args)
102                 if ret != 0:
103                     msger.warning("failed to unmount %s" % point)
104                     return ret
105
106     return 0
107
108 def setup_chrootenv(chrootdir, bindmounts = None):
109     global chroot_lockfd, chroot_lock
110
111     def get_bind_mounts(chrootdir, bindmounts):
112         chrootmounts = []
113         if bindmounts in ("", None):
114             bindmounts = ""
115
116         for mount in bindmounts.split(";"):
117             if not mount:
118                 continue
119
120             srcdst = mount.split(":")
121             srcdst[0] = os.path.abspath(os.path.expanduser(srcdst[0]))
122             if len(srcdst) == 1:
123                srcdst.append("none")
124
125             if not os.path.isdir(srcdst[0]):
126                 continue
127
128             if srcdst[0] in BIND_MOUNTS or srcdst[0] == '/':
129                 msger.warning("%s will be mounted by default." % srcdst[0])
130                 continue
131
132             if srcdst[1] == "" or srcdst[1] == "none":
133                 srcdst[1] = None
134             else:
135                 srcdst[1] = os.path.abspath(os.path.expanduser(srcdst[1]))
136                 if os.path.isdir(chrootdir + "/" + srcdst[1]):
137                     msger.warning("%s has existed in %s , skip it." % (srcdst[1], chrootdir))
138                     continue
139
140             chrootmounts.append(fs_related.BindChrootMount(srcdst[0], chrootdir, srcdst[1]))
141
142         """Default bind mounts"""
143         for pt in BIND_MOUNTS:
144             chrootmounts.append(fs_related.BindChrootMount(pt, chrootdir, None))
145
146         chrootmounts.append(fs_related.BindChrootMount("/", chrootdir, "/parentroot", "ro"))
147
148         for kernel in os.listdir("/lib/modules"):
149             chrootmounts.append(fs_related.BindChrootMount("/lib/modules/" + kernel, chrootdir, None, "ro"))
150
151         return chrootmounts
152
153     def bind_mount(chrootmounts):
154         for b in chrootmounts:
155             msger.info("bind_mount: %s -> %s" % (b.src, b.dest))
156             b.mount()
157
158     def setup_resolv(chrootdir):
159         shutil.copyfile("/etc/resolv.conf", chrootdir + "/etc/resolv.conf")
160
161     globalmounts = get_bind_mounts(chrootdir, bindmounts)
162     bind_mount(globalmounts)
163
164     setup_resolv(chrootdir)
165
166     mtab = "/etc/mtab"
167     dstmtab = chrootdir + mtab
168     if not os.path.islink(dstmtab):
169         shutil.copyfile(mtab, dstmtab)
170
171     chroot_lock = os.path.join(chrootdir, ".chroot.lock")
172     chroot_lockfd = open(chroot_lock, "w")
173
174     return globalmounts
175
176 def cleanup_chrootenv(chrootdir, bindmounts = None, globalmounts = []):
177     global chroot_lockfd, chroot_lock
178
179     def bind_unmount(chrootmounts):
180         chrootmounts.reverse()
181         for b in chrootmounts:
182             msger.info("bind_unmount: %s -> %s" % (b.src, b.dest))
183             b.unmount()
184
185     def cleanup_resolv(chrootdir):
186         fd = open(chrootdir + "/etc/resolv.conf", "w")
187         fd.truncate(0)
188         fd.close()
189
190     def kill_processes(chrootdir):
191         import glob
192         for fp in glob.glob("/proc/*/root"):
193             try:
194                 if os.readlink(fp) == chrootdir:
195                     pid = int(fp.split("/")[2])
196                     os.kill(pid, 9)
197             except:
198                 pass
199
200     def cleanup_mountdir(chrootdir, bindmounts):
201         if bindmounts == "" or bindmounts == None:
202             return
203         chrootmounts = []
204         for mount in bindmounts.split(";"):
205             if not mount:
206                 continue
207
208             srcdst = mount.split(":")
209
210             if len(srcdst) == 1:
211                srcdst.append("none")
212
213             if srcdst[1] == "" or srcdst[1] == "none":
214                 srcdst[1] = srcdst[0]
215
216             srcdst[1] = os.path.abspath(os.path.expanduser(srcdst[1]))
217             tmpdir = chrootdir + "/" + srcdst[1]
218             if os.path.isdir(tmpdir):
219                 if len(os.listdir(tmpdir)) == 0:
220                     shutil.rmtree(tmpdir, ignore_errors = True)
221                 else:
222                     msger.warning("Warning: dir %s isn't empty." % tmpdir)
223
224     chroot_lockfd.close()
225     bind_unmount(globalmounts)
226
227     if not fs_related.my_fuser(chroot_lock):
228         tmpdir = chrootdir + "/parentroot"
229         if len(os.listdir(tmpdir)) == 0:
230             shutil.rmtree(tmpdir, ignore_errors = True)
231
232         cleanup_resolv(chrootdir)
233
234         if os.path.exists(chrootdir + "/etc/mtab"):
235             os.unlink(chrootdir + "/etc/mtab")
236
237         kill_processes(chrootdir)
238
239     cleanup_mountdir(chrootdir, bindmounts)
240
241 def chroot(chrootdir, bindmounts = None, execute = "/bin/bash"):
242     def mychroot():
243         os.chroot(chrootdir)
244         os.chdir("/")
245
246     dev_null = os.open("/dev/null", os.O_WRONLY)
247     files_to_check = ["/bin/bash", "/sbin/init"]
248
249     architecture_found = False
250
251     """ Register statically-linked qemu-arm if it is an ARM fs """
252     qemu_emulator = None
253
254     for ftc in files_to_check:
255         ftc = "%s/%s" % (chrootdir,ftc)
256
257         # Return code of 'file' is "almost always" 0 based on some man pages
258         # so we need to check the file existance first.
259         if not os.path.exists(ftc):
260             continue
261
262         filecmd = misc.find_binary_path("file")
263
264         for line in subprocess.Popen([filecmd, ftc],
265                                      stdout=subprocess.PIPE,
266                                      stderr=dev_null).communicate()[0].strip().splitlines():
267             if 'ARM' in line:
268                 qemu_emulator = misc.setup_qemu_emulator(chrootdir, "arm")
269                 architecture_found = True
270                 break
271
272             if 'Intel' in line:
273                 architecture_found = True
274                 break
275
276         if architecture_found:
277             break
278
279     os.close(dev_null)
280     if not architecture_found:
281         raise errors.CreatorError("Failed to get architecture from any of the following files %s from chroot." % files_to_check)
282
283     try:
284         msger.info("Launching shell. Exit to continue.\n----------------------------------")
285         globalmounts = setup_chrootenv(chrootdir, bindmounts)
286         subprocess.call(execute, preexec_fn = mychroot, shell=True)
287
288     except OSError, (err, msg):
289         raise errors.CreatorError("Failed to chroot: %s" % msg)
290
291     finally:
292         cleanup_chrootenv(chrootdir, bindmounts, globalmounts)
293         if qemu_emulator:
294             os.unlink(chrootdir + qemu_emulator)