-
Notifications
You must be signed in to change notification settings - Fork 4
/
virt-stub
executable file
·204 lines (175 loc) · 6.45 KB
/
virt-stub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#!/usr/bin/python3 -S
import fcntl
import json
import os
import subprocess
import urllib.parse
import tempfile
import termios
import time
import threading
import types
conf = json.loads(
urllib.parse.unquote(os.environ['VIDO_CONFIG']),
object_hook=lambda x: types.SimpleNamespace(**x))
# Open this early so that clear_dirs/rw_dirs won't interfere
exit_status_file = open(conf.ipc + '/exit-status', 'w')
if os.stat('/').st_dev == os.stat('/proc').st_dev:
# Do this before scanning mountinfo
# Skip in userns (already mounted, and already namespaced)
subprocess.check_call('mount -nt proc proc /proc'.split())
assert os.stat('/').st_dev != os.stat('/proc').st_dev
mounted = []
def read_mounts():
with open('/proc/self/mountinfo') as mounts:
for line in mounts:
items = line.split()
idx = items.index('-')
fstype = items[idx + 1]
opts1 = items[5].split(',')
opts2 = items[idx + 3].split(',')
readonly = 'ro' in opts1 + opts2
intpath = items[3]
mpoint = items[4]
dev = items[idx + 2]
mounted.append((fstype, mpoint))
if conf.pivot_root and conf.virt == 'userns':
# mount --move is disallowed (we may reveal a directory that was
# meant to be hidden), use bind mounts instead
slash0 = tempfile.mkdtemp(prefix='vido-', suffix='.new-slash')
delta0 = tempfile.mkdtemp(prefix='vido-', suffix='.delta-slash')
subprocess.check_call('mount -nt tmpfs tmpfs --'.split() + [delta0])
read_mounts()
subprocess.check_call(
'mount -nt overlay overlay --make-unbindable'.split()
+ ['-olowerdir={},upperdir={}'.format('/', delta0), slash0])
bound = set()
for (fstype, mpoint) in mounted:
if mpoint == '/':
continue
if mpoint in bound:
continue
if any(mpoint.startswith(mp + '/') for mp in bound):
continue
bound.add(mpoint)
#print('Binding', mpoint)
subprocess.check_call(
'mount -n --rbind --'.split() + [mpoint, slash0 + mpoint])
slash1 = tempfile.mkdtemp(
prefix='vido-', suffix='.old-slash', dir=slash0)
os.chdir(slash0)
subprocess.check_call(['/sbin/pivot_root', '.', slash1])
os.chroot('.')
slash1 = '/' + os.path.relpath(slash1, start=slash0)
elif conf.pivot_root:
slash0 = tempfile.mkdtemp(prefix='vido-', suffix='.new-slash')
delta0 = tempfile.mkdtemp(prefix='vido-', suffix='.delta-slash')
subprocess.check_call('mount -nt tmpfs tmpfs --'.split() + [delta0])
read_mounts()
subprocess.check_call(
'mount -nt overlay overlay'.split()
+ ['-olowerdir={},upperdir={}'.format('/', delta0), slash0])
slash1 = tempfile.mkdtemp(
prefix='vido-', suffix='.old-slash', dir=slash0)
os.chdir(slash0)
subprocess.check_call(['/sbin/pivot_root', '.', slash1])
os.chroot('.')
slash1 = '/' + os.path.relpath(slash1, start=slash0)
moved = set()
for (fstype, mpoint) in mounted:
if mpoint == '/':
continue
if mpoint in moved:
continue
if any(mpoint.startswith(mp + '/') for mp in moved):
continue
moved.add(mpoint)
#print('Moving', mpoint)
subprocess.check_call(
'mount -n --move --'.split() + [slash1 + mpoint, mpoint])
else:
read_mounts()
mounted = set(mounted)
def ensure_mount(fstype, mpoint):
if (fstype, mpoint) in mounted:
return
subprocess.check_call('mount -nt'.split() + [fstype, '--', fstype, mpoint])
def ensure_dir(path):
try:
os.mkdir(path)
except FileExistsError:
pass
# Or set CONFIG_DEVTMPFS_MOUNT
try:
ensure_mount('devtmpfs', '/dev')
except subprocess.CalledProcessError:
# XXX Debian/Ubuntu ship uml kernels without devtmpfs
if conf.virt != 'uml':
raise
if conf.watchdog:
if not os.path.exists('/dev/watchdog'):
subprocess.check_call('/sbin/modprobe -v ib700wdt'.split(), stdout=subprocess.DEVNULL)
assert os.path.exists('/dev/watchdog')
wd = open('/dev/watchdog', 'wb', buffering=0)
class Watchdog(threading.Thread):
daemon = True
def run(self):
while True:
wd.write(b'.')
time.sleep(.5)
Watchdog().start()
if not os.path.lexists('/dev/fd'):
subprocess.check_call('ln -sT /proc/self/fd /dev/fd'.split())
if conf.virt != 'userns':
ensure_mount('sysfs', '/sys')
ensure_mount('tmpfs', '/run')
ensure_dir('/run/lock')
ensure_dir('/run/shm')
# Set controlling tty
os.setsid()
fcntl.ioctl(0, termios.TIOCSCTTY, 0)
# Reset windows size
# If this is resized after boot, call `resize` manually.
# The guest won't get SIGWINCH.
try:
subprocess.call(['resize'], stdout=subprocess.DEVNULL)
except FileNotFoundError:
# Not installed
pass
# At least the Debian and Ubuntu UML package uses this
if conf.virt == 'uml':
subprocess.call(
'mount -nt hostfs -o /usr/lib/uml/modules hostfs /lib/modules'.split(),
stderr=subprocess.DEVNULL)
env = vars(conf.env)
for dn in conf.rw_dirs:
tdn = tempfile.mkdtemp(prefix='vido-', suffix='.delta')
subprocess.check_call('mount -nt tmpfs tmpfs --'.split() + [tdn])
subprocess.check_call(
'mount -nt overlay overlay'.split()
+ ['-olowerdir={},upperdir={}'.format(dn, tdn), dn])
for dn in conf.clear_dirs:
subprocess.check_call('mount -nt tmpfs tmpfs --'.split() + [dn])
for fn in conf.clear_files:
tfd, tfn = tempfile.mkstemp(prefix='vido-', suffix='.clear-file')
subprocess.check_call('mount -nB'.split() + [tfn, fn])
if conf.allow_sudo:
tfd, tfn = tempfile.mkstemp(prefix='vido-', suffix='.sudoers')
with os.fdopen(tfd, 'w') as tf:
# user host=(newuser:newgrp) flags:cmd
tf.write('ALL ALL=(ALL:ALL) NOPASSWD:ALL\n')
subprocess.check_call('mount -nB'.split() + [tfn, '/etc/sudoers'])
if hasattr(conf, 'net'):
subprocess.check_call(
'ip addr add dev eth0'.split() + [conf.net.host])
subprocess.check_call('ip link set eth0 up'.split())
subprocess.check_call(
'ip route add default via'.split() + [conf.net.router]
+ 'dev eth0'.split())
for i, disk in enumerate(conf.disks):
env['VIDO_DISK{}'.format(i)] = disk
rcode = subprocess.call(conf.cmd, cwd=conf.cwd, env=env)
exit_status_file.write('%d' % rcode)
exit_status_file.close()
if conf.virt != 'userns':
subprocess.check_call('/sbin/poweroff -f'.split())