generic-poky/scripts/lib/mic/chroot.py

344 lines
11 KiB
Python

#!/usr/bin/python -tt
#
# Copyright (c) 2009, 2010, 2011 Intel, Inc.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; version 2 of the License
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc., 59
# Temple Place - Suite 330, Boston, MA 02111-1307, USA.
from __future__ import with_statement
import os
import shutil
import subprocess
from mic import msger
from mic.conf import configmgr
from mic.utils import misc, errors, runner, fs_related
chroot_lockfd = -1
chroot_lock = ""
BIND_MOUNTS = (
"/proc",
"/proc/sys/fs/binfmt_misc",
"/sys",
"/dev",
"/dev/pts",
"/dev/shm",
"/var/lib/dbus",
"/var/run/dbus",
"/var/lock",
)
def cleanup_after_chroot(targettype,imgmount,tmpdir,tmpmnt):
if imgmount and targettype == "img":
imgmount.cleanup()
if tmpdir:
shutil.rmtree(tmpdir, ignore_errors = True)
if tmpmnt:
shutil.rmtree(tmpmnt, ignore_errors = True)
def check_bind_mounts(chrootdir, bindmounts):
chrootmounts = []
for mount in bindmounts.split(";"):
if not mount:
continue
srcdst = mount.split(":")
if len(srcdst) == 1:
srcdst.append("none")
if not os.path.isdir(srcdst[0]):
return False
if srcdst[1] == "" or srcdst[1] == "none":
srcdst[1] = None
if srcdst[0] in BIND_MOUNTS or srcdst[0] == '/':
continue
if chrootdir:
if not srcdst[1]:
srcdst[1] = os.path.abspath(os.path.expanduser(srcdst[0]))
else:
srcdst[1] = os.path.abspath(os.path.expanduser(srcdst[1]))
tmpdir = chrootdir + "/" + srcdst[1]
if os.path.isdir(tmpdir):
msger.warning("Warning: dir %s has existed." % tmpdir)
return True
def cleanup_mounts(chrootdir):
umountcmd = misc.find_binary_path("umount")
abs_chrootdir = os.path.abspath(chrootdir)
mounts = open('/proc/mounts').readlines()
for line in reversed(mounts):
if abs_chrootdir not in line:
continue
point = line.split()[1]
# '/' to avoid common name prefix
if abs_chrootdir == point or point.startswith(abs_chrootdir + '/'):
args = [ umountcmd, "-l", point ]
ret = runner.quiet(args)
if ret != 0:
msger.warning("failed to unmount %s" % point)
return 0
def setup_chrootenv(chrootdir, bindmounts = None, mountparent = True):
global chroot_lockfd, chroot_lock
def get_bind_mounts(chrootdir, bindmounts, mountparent = True):
chrootmounts = []
if bindmounts in ("", None):
bindmounts = ""
for mount in bindmounts.split(";"):
if not mount:
continue
srcdst = mount.split(":")
srcdst[0] = os.path.abspath(os.path.expanduser(srcdst[0]))
if len(srcdst) == 1:
srcdst.append("none")
# if some bindmount is not existed, but it's created inside
# chroot, this is not expected
if not os.path.exists(srcdst[0]):
os.makedirs(srcdst[0])
if not os.path.isdir(srcdst[0]):
continue
if srcdst[0] in BIND_MOUNTS or srcdst[0] == '/':
msger.verbose("%s will be mounted by default." % srcdst[0])
continue
if srcdst[1] == "" or srcdst[1] == "none":
srcdst[1] = None
else:
srcdst[1] = os.path.abspath(os.path.expanduser(srcdst[1]))
if os.path.isdir(chrootdir + "/" + srcdst[1]):
msger.warning("%s has existed in %s , skip it."\
% (srcdst[1], chrootdir))
continue
chrootmounts.append(fs_related.BindChrootMount(srcdst[0],
chrootdir,
srcdst[1]))
"""Default bind mounts"""
for pt in BIND_MOUNTS:
if not os.path.exists(pt):
continue
chrootmounts.append(fs_related.BindChrootMount(pt,
chrootdir,
None))
if mountparent:
chrootmounts.append(fs_related.BindChrootMount("/",
chrootdir,
"/parentroot",
"ro"))
for kernel in os.listdir("/lib/modules"):
chrootmounts.append(fs_related.BindChrootMount(
"/lib/modules/"+kernel,
chrootdir,
None,
"ro"))
return chrootmounts
def bind_mount(chrootmounts):
for b in chrootmounts:
msger.verbose("bind_mount: %s -> %s" % (b.src, b.dest))
b.mount()
def setup_resolv(chrootdir):
try:
shutil.copyfile("/etc/resolv.conf", chrootdir + "/etc/resolv.conf")
except:
pass
globalmounts = get_bind_mounts(chrootdir, bindmounts, mountparent)
bind_mount(globalmounts)
setup_resolv(chrootdir)
mtab = "/etc/mtab"
dstmtab = chrootdir + mtab
if not os.path.islink(dstmtab):
shutil.copyfile(mtab, dstmtab)
chroot_lock = os.path.join(chrootdir, ".chroot.lock")
chroot_lockfd = open(chroot_lock, "w")
return globalmounts
def cleanup_chrootenv(chrootdir, bindmounts=None, globalmounts=()):
global chroot_lockfd, chroot_lock
def bind_unmount(chrootmounts):
for b in reversed(chrootmounts):
msger.verbose("bind_unmount: %s -> %s" % (b.src, b.dest))
b.unmount()
def cleanup_resolv(chrootdir):
try:
fd = open(chrootdir + "/etc/resolv.conf", "w")
fd.truncate(0)
fd.close()
except:
pass
def kill_processes(chrootdir):
import glob
for fp in glob.glob("/proc/*/root"):
try:
if os.readlink(fp) == chrootdir:
pid = int(fp.split("/")[2])
os.kill(pid, 9)
except:
pass
def cleanup_mountdir(chrootdir, bindmounts):
if bindmounts == "" or bindmounts == None:
return
chrootmounts = []
for mount in bindmounts.split(";"):
if not mount:
continue
srcdst = mount.split(":")
if len(srcdst) == 1:
srcdst.append("none")
if srcdst[0] == "/":
continue
if srcdst[1] == "" or srcdst[1] == "none":
srcdst[1] = srcdst[0]
srcdst[1] = os.path.abspath(os.path.expanduser(srcdst[1]))
tmpdir = chrootdir + "/" + srcdst[1]
if os.path.isdir(tmpdir):
if len(os.listdir(tmpdir)) == 0:
shutil.rmtree(tmpdir, ignore_errors = True)
else:
msger.warning("Warning: dir %s isn't empty." % tmpdir)
chroot_lockfd.close()
bind_unmount(globalmounts)
if not fs_related.my_fuser(chroot_lock):
tmpdir = chrootdir + "/parentroot"
if os.path.exists(tmpdir) and len(os.listdir(tmpdir)) == 0:
shutil.rmtree(tmpdir, ignore_errors = True)
cleanup_resolv(chrootdir)
if os.path.exists(chrootdir + "/etc/mtab"):
os.unlink(chrootdir + "/etc/mtab")
kill_processes(chrootdir)
cleanup_mountdir(chrootdir, bindmounts)
def chroot(chrootdir, bindmounts = None, execute = "/bin/bash"):
def mychroot():
os.chroot(chrootdir)
os.chdir("/")
if configmgr.chroot['saveto']:
savefs = True
saveto = configmgr.chroot['saveto']
wrnmsg = "Can't save chroot fs for dir %s exists" % saveto
if saveto == chrootdir:
savefs = False
wrnmsg = "Dir %s is being used to chroot" % saveto
elif os.path.exists(saveto):
if msger.ask("Dir %s already exists, cleanup and continue?" %
saveto):
shutil.rmtree(saveto, ignore_errors = True)
savefs = True
else:
savefs = False
if savefs:
msger.info("Saving image to directory %s" % saveto)
fs_related.makedirs(os.path.dirname(os.path.abspath(saveto)))
runner.quiet("cp -af %s %s" % (chrootdir, saveto))
devs = ['dev/fd',
'dev/stdin',
'dev/stdout',
'dev/stderr',
'etc/mtab']
ignlst = [os.path.join(saveto, x) for x in devs]
map(os.unlink, filter(os.path.exists, ignlst))
else:
msger.warning(wrnmsg)
dev_null = os.open("/dev/null", os.O_WRONLY)
files_to_check = ["/bin/bash", "/sbin/init"]
architecture_found = False
""" Register statically-linked qemu-arm if it is an ARM fs """
qemu_emulator = None
for ftc in files_to_check:
ftc = "%s/%s" % (chrootdir,ftc)
# Return code of 'file' is "almost always" 0 based on some man pages
# so we need to check the file existance first.
if not os.path.exists(ftc):
continue
for line in runner.outs(['file', ftc]).splitlines():
if 'ARM' in line:
qemu_emulator = misc.setup_qemu_emulator(chrootdir, "arm")
architecture_found = True
break
if 'Intel' in line:
architecture_found = True
break
if architecture_found:
break
os.close(dev_null)
if not architecture_found:
raise errors.CreatorError("Failed to get architecture from any of the "
"following files %s from chroot." \
% files_to_check)
try:
msger.info("Launching shell. Exit to continue.\n"
"----------------------------------")
globalmounts = setup_chrootenv(chrootdir, bindmounts)
subprocess.call(execute, preexec_fn = mychroot, shell=True)
except OSError, err:
raise errors.CreatorError("chroot err: %s" % str(err))
finally:
cleanup_chrootenv(chrootdir, bindmounts, globalmounts)
if qemu_emulator:
os.unlink(chrootdir + qemu_emulator)