diff options
Diffstat (limited to 'scripts/lib/mic/chroot.py')
-rw-r--r-- | scripts/lib/mic/chroot.py | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/scripts/lib/mic/chroot.py b/scripts/lib/mic/chroot.py new file mode 100644 index 0000000000..99fb9a2c17 --- /dev/null +++ b/scripts/lib/mic/chroot.py | |||
@@ -0,0 +1,343 @@ | |||
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 | chroot_lockfd = -1 | ||
28 | chroot_lock = "" | ||
29 | BIND_MOUNTS = ( | ||
30 | "/proc", | ||
31 | "/proc/sys/fs/binfmt_misc", | ||
32 | "/sys", | ||
33 | "/dev", | ||
34 | "/dev/pts", | ||
35 | "/dev/shm", | ||
36 | "/var/lib/dbus", | ||
37 | "/var/run/dbus", | ||
38 | "/var/lock", | ||
39 | ) | ||
40 | |||
41 | def cleanup_after_chroot(targettype,imgmount,tmpdir,tmpmnt): | ||
42 | if imgmount and targettype == "img": | ||
43 | imgmount.cleanup() | ||
44 | |||
45 | if tmpdir: | ||
46 | shutil.rmtree(tmpdir, ignore_errors = True) | ||
47 | |||
48 | if tmpmnt: | ||
49 | shutil.rmtree(tmpmnt, ignore_errors = True) | ||
50 | |||
51 | def check_bind_mounts(chrootdir, bindmounts): | ||
52 | chrootmounts = [] | ||
53 | for mount in bindmounts.split(";"): | ||
54 | if not mount: | ||
55 | continue | ||
56 | |||
57 | srcdst = mount.split(":") | ||
58 | if len(srcdst) == 1: | ||
59 | srcdst.append("none") | ||
60 | |||
61 | if not os.path.isdir(srcdst[0]): | ||
62 | return False | ||
63 | |||
64 | if srcdst[1] == "" or srcdst[1] == "none": | ||
65 | srcdst[1] = None | ||
66 | |||
67 | if srcdst[0] in BIND_MOUNTS or srcdst[0] == '/': | ||
68 | continue | ||
69 | |||
70 | if chrootdir: | ||
71 | if not srcdst[1]: | ||
72 | srcdst[1] = os.path.abspath(os.path.expanduser(srcdst[0])) | ||
73 | else: | ||
74 | srcdst[1] = os.path.abspath(os.path.expanduser(srcdst[1])) | ||
75 | |||
76 | tmpdir = chrootdir + "/" + srcdst[1] | ||
77 | if os.path.isdir(tmpdir): | ||
78 | msger.warning("Warning: dir %s has existed." % tmpdir) | ||
79 | |||
80 | return True | ||
81 | |||
82 | def cleanup_mounts(chrootdir): | ||
83 | umountcmd = misc.find_binary_path("umount") | ||
84 | abs_chrootdir = os.path.abspath(chrootdir) | ||
85 | mounts = open('/proc/mounts').readlines() | ||
86 | for line in reversed(mounts): | ||
87 | if abs_chrootdir not in line: | ||
88 | continue | ||
89 | |||
90 | point = line.split()[1] | ||
91 | |||
92 | # '/' to avoid common name prefix | ||
93 | if abs_chrootdir == point or point.startswith(abs_chrootdir + '/'): | ||
94 | args = [ umountcmd, "-l", point ] | ||
95 | ret = runner.quiet(args) | ||
96 | if ret != 0: | ||
97 | msger.warning("failed to unmount %s" % point) | ||
98 | |||
99 | return 0 | ||
100 | |||
101 | def setup_chrootenv(chrootdir, bindmounts = None, mountparent = True): | ||
102 | global chroot_lockfd, chroot_lock | ||
103 | |||
104 | def get_bind_mounts(chrootdir, bindmounts, mountparent = True): | ||
105 | chrootmounts = [] | ||
106 | if bindmounts in ("", None): | ||
107 | bindmounts = "" | ||
108 | |||
109 | for mount in bindmounts.split(";"): | ||
110 | if not mount: | ||
111 | continue | ||
112 | |||
113 | srcdst = mount.split(":") | ||
114 | srcdst[0] = os.path.abspath(os.path.expanduser(srcdst[0])) | ||
115 | if len(srcdst) == 1: | ||
116 | srcdst.append("none") | ||
117 | |||
118 | # if some bindmount is not existed, but it's created inside | ||
119 | # chroot, this is not expected | ||
120 | if not os.path.exists(srcdst[0]): | ||
121 | os.makedirs(srcdst[0]) | ||
122 | |||
123 | if not os.path.isdir(srcdst[0]): | ||
124 | continue | ||
125 | |||
126 | if srcdst[0] in BIND_MOUNTS or srcdst[0] == '/': | ||
127 | msger.verbose("%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."\ | ||
136 | % (srcdst[1], chrootdir)) | ||
137 | continue | ||
138 | |||
139 | chrootmounts.append(fs_related.BindChrootMount(srcdst[0], | ||
140 | chrootdir, | ||
141 | srcdst[1])) | ||
142 | |||
143 | """Default bind mounts""" | ||
144 | for pt in BIND_MOUNTS: | ||
145 | if not os.path.exists(pt): | ||
146 | continue | ||
147 | chrootmounts.append(fs_related.BindChrootMount(pt, | ||
148 | chrootdir, | ||
149 | None)) | ||
150 | |||
151 | if mountparent: | ||
152 | chrootmounts.append(fs_related.BindChrootMount("/", | ||
153 | chrootdir, | ||
154 | "/parentroot", | ||
155 | "ro")) | ||
156 | |||
157 | for kernel in os.listdir("/lib/modules"): | ||
158 | chrootmounts.append(fs_related.BindChrootMount( | ||
159 | "/lib/modules/"+kernel, | ||
160 | chrootdir, | ||
161 | None, | ||
162 | "ro")) | ||
163 | |||
164 | return chrootmounts | ||
165 | |||
166 | def bind_mount(chrootmounts): | ||
167 | for b in chrootmounts: | ||
168 | msger.verbose("bind_mount: %s -> %s" % (b.src, b.dest)) | ||
169 | b.mount() | ||
170 | |||
171 | def setup_resolv(chrootdir): | ||
172 | try: | ||
173 | shutil.copyfile("/etc/resolv.conf", chrootdir + "/etc/resolv.conf") | ||
174 | except: | ||
175 | pass | ||
176 | |||
177 | globalmounts = get_bind_mounts(chrootdir, bindmounts, mountparent) | ||
178 | bind_mount(globalmounts) | ||
179 | |||
180 | setup_resolv(chrootdir) | ||
181 | |||
182 | mtab = "/etc/mtab" | ||
183 | dstmtab = chrootdir + mtab | ||
184 | if not os.path.islink(dstmtab): | ||
185 | shutil.copyfile(mtab, dstmtab) | ||
186 | |||
187 | chroot_lock = os.path.join(chrootdir, ".chroot.lock") | ||
188 | chroot_lockfd = open(chroot_lock, "w") | ||
189 | |||
190 | return globalmounts | ||
191 | |||
192 | def cleanup_chrootenv(chrootdir, bindmounts=None, globalmounts=()): | ||
193 | global chroot_lockfd, chroot_lock | ||
194 | |||
195 | def bind_unmount(chrootmounts): | ||
196 | for b in reversed(chrootmounts): | ||
197 | msger.verbose("bind_unmount: %s -> %s" % (b.src, b.dest)) | ||
198 | b.unmount() | ||
199 | |||
200 | def cleanup_resolv(chrootdir): | ||
201 | try: | ||
202 | fd = open(chrootdir + "/etc/resolv.conf", "w") | ||
203 | fd.truncate(0) | ||
204 | fd.close() | ||
205 | except: | ||
206 | pass | ||
207 | |||
208 | def kill_processes(chrootdir): | ||
209 | import glob | ||
210 | for fp in glob.glob("/proc/*/root"): | ||
211 | try: | ||
212 | if os.readlink(fp) == chrootdir: | ||
213 | pid = int(fp.split("/")[2]) | ||
214 | os.kill(pid, 9) | ||
215 | except: | ||
216 | pass | ||
217 | |||
218 | def cleanup_mountdir(chrootdir, bindmounts): | ||
219 | if bindmounts == "" or bindmounts == None: | ||
220 | return | ||
221 | chrootmounts = [] | ||
222 | for mount in bindmounts.split(";"): | ||
223 | if not mount: | ||
224 | continue | ||
225 | |||
226 | srcdst = mount.split(":") | ||
227 | |||
228 | if len(srcdst) == 1: | ||
229 | srcdst.append("none") | ||
230 | |||
231 | if srcdst[0] == "/": | ||
232 | continue | ||
233 | |||
234 | if srcdst[1] == "" or srcdst[1] == "none": | ||
235 | srcdst[1] = srcdst[0] | ||
236 | |||
237 | srcdst[1] = os.path.abspath(os.path.expanduser(srcdst[1])) | ||
238 | tmpdir = chrootdir + "/" + srcdst[1] | ||
239 | if os.path.isdir(tmpdir): | ||
240 | if len(os.listdir(tmpdir)) == 0: | ||
241 | shutil.rmtree(tmpdir, ignore_errors = True) | ||
242 | else: | ||
243 | msger.warning("Warning: dir %s isn't empty." % tmpdir) | ||
244 | |||
245 | chroot_lockfd.close() | ||
246 | bind_unmount(globalmounts) | ||
247 | |||
248 | if not fs_related.my_fuser(chroot_lock): | ||
249 | tmpdir = chrootdir + "/parentroot" | ||
250 | if os.path.exists(tmpdir) and len(os.listdir(tmpdir)) == 0: | ||
251 | shutil.rmtree(tmpdir, ignore_errors = True) | ||
252 | |||
253 | cleanup_resolv(chrootdir) | ||
254 | |||
255 | if os.path.exists(chrootdir + "/etc/mtab"): | ||
256 | os.unlink(chrootdir + "/etc/mtab") | ||
257 | |||
258 | kill_processes(chrootdir) | ||
259 | |||
260 | cleanup_mountdir(chrootdir, bindmounts) | ||
261 | |||
262 | def chroot(chrootdir, bindmounts = None, execute = "/bin/bash"): | ||
263 | def mychroot(): | ||
264 | os.chroot(chrootdir) | ||
265 | os.chdir("/") | ||
266 | |||
267 | if configmgr.chroot['saveto']: | ||
268 | savefs = True | ||
269 | saveto = configmgr.chroot['saveto'] | ||
270 | wrnmsg = "Can't save chroot fs for dir %s exists" % saveto | ||
271 | if saveto == chrootdir: | ||
272 | savefs = False | ||
273 | wrnmsg = "Dir %s is being used to chroot" % saveto | ||
274 | elif os.path.exists(saveto): | ||
275 | if msger.ask("Dir %s already exists, cleanup and continue?" % | ||
276 | saveto): | ||
277 | shutil.rmtree(saveto, ignore_errors = True) | ||
278 | savefs = True | ||
279 | else: | ||
280 | savefs = False | ||
281 | |||
282 | if savefs: | ||
283 | msger.info("Saving image to directory %s" % saveto) | ||
284 | fs_related.makedirs(os.path.dirname(os.path.abspath(saveto))) | ||
285 | runner.quiet("cp -af %s %s" % (chrootdir, saveto)) | ||
286 | devs = ['dev/fd', | ||
287 | 'dev/stdin', | ||
288 | 'dev/stdout', | ||
289 | 'dev/stderr', | ||
290 | 'etc/mtab'] | ||
291 | ignlst = [os.path.join(saveto, x) for x in devs] | ||
292 | map(os.unlink, filter(os.path.exists, ignlst)) | ||
293 | else: | ||
294 | msger.warning(wrnmsg) | ||
295 | |||
296 | dev_null = os.open("/dev/null", os.O_WRONLY) | ||
297 | files_to_check = ["/bin/bash", "/sbin/init"] | ||
298 | |||
299 | architecture_found = False | ||
300 | |||
301 | """ Register statically-linked qemu-arm if it is an ARM fs """ | ||
302 | qemu_emulator = None | ||
303 | |||
304 | for ftc in files_to_check: | ||
305 | ftc = "%s/%s" % (chrootdir,ftc) | ||
306 | |||
307 | # Return code of 'file' is "almost always" 0 based on some man pages | ||
308 | # so we need to check the file existance first. | ||
309 | if not os.path.exists(ftc): | ||
310 | continue | ||
311 | |||
312 | for line in runner.outs(['file', ftc]).splitlines(): | ||
313 | if 'ARM' in line: | ||
314 | qemu_emulator = misc.setup_qemu_emulator(chrootdir, "arm") | ||
315 | architecture_found = True | ||
316 | break | ||
317 | |||
318 | if 'Intel' in line: | ||
319 | architecture_found = True | ||
320 | break | ||
321 | |||
322 | if architecture_found: | ||
323 | break | ||
324 | |||
325 | os.close(dev_null) | ||
326 | if not architecture_found: | ||
327 | raise errors.CreatorError("Failed to get architecture from any of the " | ||
328 | "following files %s from chroot." \ | ||
329 | % files_to_check) | ||
330 | |||
331 | try: | ||
332 | msger.info("Launching shell. Exit to continue.\n" | ||
333 | "----------------------------------") | ||
334 | globalmounts = setup_chrootenv(chrootdir, bindmounts) | ||
335 | subprocess.call(execute, preexec_fn = mychroot, shell=True) | ||
336 | |||
337 | except OSError, err: | ||
338 | raise errors.CreatorError("chroot err: %s" % str(err)) | ||
339 | |||
340 | finally: | ||
341 | cleanup_chrootenv(chrootdir, bindmounts, globalmounts) | ||
342 | if qemu_emulator: | ||
343 | os.unlink(chrootdir + qemu_emulator) | ||