Updated chroot's cleanup function
[tools/mic.git] / mic / chroot.py
1 #/usr/bin/python -t
2 import os
3 import sys
4 import glob
5 import shutil
6 import shlex
7 import subprocess
8 import mic.utils.fs_related as fs_related
9 import mic.utils.misc as misc
10 import mic.utils.errors as errors
11
12 def cleanup_after_chroot(targettype,imgmount,tmpdir,tmpmnt):
13     if imgmount and targettype == "img":
14         imgmount.cleanup()
15     if tmpdir:
16         shutil.rmtree(tmpdir, ignore_errors = True)
17     if tmpmnt:
18         shutil.rmtree(tmpmnt, ignore_errors = True)
19
20 def check_bind_mounts(chrootdir, bindmounts):
21     chrootmounts = []
22     mounts = bindmounts.split(";")
23     for mount in mounts:
24         if mount == "":
25             continue
26         srcdst = mount.split(":")
27         if len(srcdst) == 1:
28            srcdst.append("none")
29         if not os.path.isdir(srcdst[0]):
30             return False
31         if srcdst[1] == "" or srcdst[1] == "none":
32             srcdst[1] = None
33         if srcdst[0] in ("/proc", "/proc/sys/fs/binfmt_misc", "/", "/sys", "/dev", "/dev/pts", "/dev/shm", "/var/lib/dbus", "/var/run/dbus", "/var/lock"):
34             continue
35         if chrootdir:
36             if not srcdst[1]:
37                 srcdst[1] = os.path.abspath(os.path.expanduser(srcdst[0]))
38             else:
39                 srcdst[1] = os.path.abspath(os.path.expanduser(srcdst[1]))
40             tmpdir = chrootdir + "/" + srcdst[1]
41             if os.path.isdir(tmpdir):
42                 print "Warning: dir %s has existed."  % tmpdir
43     return True
44
45 def cleanup_mounts(chrootdir):
46     checkpoints = ["/proc/sys/fs/binfmt_misc", "/proc", "/sys", "/dev/pts", "/dev/shm", "/dev", "/var/lib/dbus", "/var/run/dbus", "/var/lock"]
47     dev_null = os.open("/dev/null", os.O_WRONLY)
48     umountcmd = misc.find_binary_path("umount")
49     for point in checkpoints:
50         print point
51         args = [ umountcmd, "-l", chrootdir + point ]
52         subprocess.call(args, stdout=dev_null, stderr=dev_null)
53     catcmd = misc.find_binary_path("cat")
54     args = [ catcmd, "/proc/mounts" ]
55     proc_mounts = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=dev_null)
56     outputs = proc_mounts.communicate()[0].strip().split("\n")
57     for line in outputs:
58         if line.find(os.path.abspath(chrootdir)) >= 0:
59             if os.path.abspath(chrootdir) == line.split()[1]:
60                 continue
61             point = line.split()[1]
62             print point
63             args = [ umountcmd, "-l", point ]
64             ret = subprocess.call(args, stdout=dev_null, stderr=dev_null)
65             if ret != 0:
66                 print "ERROR: failed to unmount %s" % point
67                 os.close(dev_null)
68                 return ret
69     os.close(dev_null)
70     return 0
71
72 def setup_chrootenv(chrootdir, bindmounts = None):##move to mic/utils/misc
73     global chroot_lockfd, chroot_lock
74     def get_bind_mounts(chrootdir, bindmounts):
75         chrootmounts = []
76         if bindmounts in ("", None):
77             bindmounts = ""
78         mounts = bindmounts.split(";")
79         for mount in mounts:
80             if mount == "":
81                 continue
82             srcdst = mount.split(":")
83             srcdst[0] = os.path.abspath(os.path.expanduser(srcdst[0]))
84             if len(srcdst) == 1:
85                srcdst.append("none")
86             if not os.path.isdir(srcdst[0]):
87                 continue
88             if srcdst[0] in ("/proc", "/proc/sys/fs/binfmt_misc", "/", "/sys", "/dev", "/dev/pts", "/dev/shm", "/var/lib/dbus", "/var/run/dbus", "/var/lock"):
89                 pwarning("%s will be mounted by default." % srcdst[0])
90                 continue
91             if srcdst[1] == "" or srcdst[1] == "none":
92                 srcdst[1] = None
93             else:
94                 srcdst[1] = os.path.abspath(os.path.expanduser(srcdst[1]))
95                 if os.path.isdir(chrootdir + "/" + srcdst[1]):
96                     pwarning("%s has existed in %s , skip it." % (srcdst[1], chrootdir))
97                     continue
98             chrootmounts.append(fs_related.BindChrootMount(srcdst[0], chrootdir, srcdst[1]))
99     
100         """Default bind mounts"""
101         chrootmounts.append(fs_related.BindChrootMount("/proc", chrootdir, None))
102         chrootmounts.append(fs_related.BindChrootMount("/proc/sys/fs/binfmt_misc", chrootdir, None))
103         chrootmounts.append(fs_related.BindChrootMount("/sys", chrootdir, None))
104         chrootmounts.append(fs_related.BindChrootMount("/dev", chrootdir, None))
105         chrootmounts.append(fs_related.BindChrootMount("/dev/pts", chrootdir, None))
106         chrootmounts.append(fs_related.BindChrootMount("/dev/shm", chrootdir, None))
107         chrootmounts.append(fs_related.BindChrootMount("/var/lib/dbus", chrootdir, None))
108         chrootmounts.append(fs_related.BindChrootMount("/var/run/dbus", chrootdir, None))
109         chrootmounts.append(fs_related.BindChrootMount("/var/lock", chrootdir, None))
110         chrootmounts.append(fs_related.BindChrootMount("/", chrootdir, "/parentroot", "ro"))
111         for kernel in os.listdir("/lib/modules"):
112             chrootmounts.append(fs_related.BindChrootMount("/lib/modules/" + kernel, chrootdir, None, "ro"))
113     
114         return chrootmounts
115
116     def bind_mount(chrootmounts):
117         for b in chrootmounts:
118             print "bind_mount: %s -> %s" % (b.src, b.dest)
119             b.mount()
120
121     def setup_resolv(chrootdir):
122         shutil.copyfile("/etc/resolv.conf", chrootdir + "/etc/resolv.conf")
123
124     globalmounts = get_bind_mounts(chrootdir, bindmounts)
125     bind_mount(globalmounts)
126     setup_resolv(chrootdir)
127     mtab = "/etc/mtab"
128     dstmtab = chrootdir + mtab
129     if not os.path.islink(dstmtab):
130         shutil.copyfile(mtab, dstmtab)
131     chroot_lock = os.path.join(chrootdir, ".chroot.lock")
132     chroot_lockfd = open(chroot_lock, "w")
133     return globalmounts    
134
135 def cleanup_chrootenv(chrootdir, bindmounts = None, globalmounts = []):
136     global chroot_lockfd, chroot_lock
137     def bind_unmount(chrootmounts):
138         chrootmounts.reverse()
139         for b in chrootmounts:
140             print "bind_unmount: %s -> %s" % (b.src, b.dest)
141             b.unmount()
142
143     def cleanup_resolv(chrootdir):
144         fd = open(chrootdir + "/etc/resolv.conf", "w")
145         fd.truncate(0)
146         fd.close()
147
148     def kill_processes(chrootdir):
149         for file in glob.glob("/proc/*/root"):
150             try:
151                 if os.readlink(file) == chrootdir:
152                     pid = int(file.split("/")[2])
153                     os.kill(pid, 9)
154             except:
155                 pass
156
157     def cleanup_mountdir(chrootdir, bindmounts):
158         if bindmounts == "" or bindmounts == None:
159             return
160         chrootmounts = []
161         mounts = bindmounts.split(";")
162         for mount in mounts:
163             if mount == "":
164                 continue
165             srcdst = mount.split(":")
166             if len(srcdst) == 1:
167                srcdst.append("none")
168             if srcdst[1] == "" or srcdst[1] == "none":
169                 srcdst[1] = srcdst[0]
170             srcdst[1] = os.path.abspath(os.path.expanduser(srcdst[1]))
171             tmpdir = chrootdir + "/" + srcdst[1]
172             if os.path.isdir(tmpdir):
173                 if len(os.listdir(tmpdir)) == 0:
174                     shutil.rmtree(tmpdir, ignore_errors = True)
175                 else:
176                     print "Warning: dir %s isn't empty." % tmpdir
177     
178     chroot_lockfd.close()
179     bind_unmount(globalmounts)
180     if not fs_related.my_fuser(chroot_lock):
181         tmpdir = chrootdir + "/parentroot"
182         if len(os.listdir(tmpdir)) == 0:
183             shutil.rmtree(tmpdir, ignore_errors = True)
184         cleanup_resolv(chrootdir)
185         if os.path.exists(chrootdir + "/etc/mtab"):
186             os.unlink(chrootdir + "/etc/mtab")
187         kill_processes(chrootdir)
188     cleanup_mountdir(chrootdir, bindmounts)
189
190 def chroot(chrootdir, bindmounts = None, execute = "/bin/bash"):
191     def mychroot():
192         os.chroot(chrootdir)
193         os.chdir("/")
194
195     dev_null = os.open("/dev/null", os.O_WRONLY)
196     files_to_check = ["/bin/bash", "/sbin/init"]
197     
198     architecture_found = False
199
200     """ Register statically-linked qemu-arm if it is an ARM fs """
201     qemu_emulator = None
202
203     for ftc in files_to_check:
204         ftc = "%s/%s" % (chrootdir,ftc)
205         
206         # Return code of 'file' is "almost always" 0 based on some man pages
207         # so we need to check the file existance first.
208         if not os.path.exists(ftc):
209             continue
210
211         filecmd = misc.find_binary_path("file")
212         initp1 = subprocess.Popen([filecmd, ftc], stdout=subprocess.PIPE, stderr=dev_null)
213         fileOutput = initp1.communicate()[0].strip().split("\n")
214         
215         for i in range(len(fileOutput)):
216             if fileOutput[i].find("ARM") > 0:
217                 qemu_emulator = misc.setup_qemu_emulator(chrootdir, "arm")
218                 architecture_found = True
219                 break
220             if fileOutput[i].find("Intel") > 0:
221                 architecture_found = True
222                 break
223                 
224         if architecture_found:
225             break
226                 
227     os.close(dev_null)
228     if not architecture_found:
229         raise errors.CreatorError("Failed to get architecture from any of the following files %s from chroot." % files_to_check)
230
231     try:
232         print "Launching shell. Exit to continue."
233         print "----------------------------------"
234         globalmounts = setup_chrootenv(chrootdir, bindmounts)
235         args = shlex.split(execute)
236         subprocess.call(args, preexec_fn = mychroot)
237     except OSError, (err, msg):
238         raise errors.CreatorError("Failed to chroot: %s" % msg)
239     finally:
240         cleanup_chrootenv(chrootdir, bindmounts, globalmounts)
241         if qemu_emulator:
242             os.unlink(chrootdir + qemu_emulator)