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