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) | ||
