mirror of
https://github.com/checkpoint-restore/criu
synced 2025-08-28 21:07:43 +00:00
As discussed on the mailing list, current .py files formatting does not conform to the world standard, so we should better reformat it. For this the yapf tool is used. The command I used was yapf -i $(find -name *.py) Signed-off-by: Pavel Emelyanov <xemul@virtuozzo.com>
774 lines
22 KiB
Python
Executable File
774 lines
22 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
import sys
|
|
import os
|
|
import socket
|
|
import argparse
|
|
import subprocess
|
|
import signal
|
|
import fcntl
|
|
import stat
|
|
|
|
criu_bin = '../../criu/criu'
|
|
|
|
sk_type_s = {
|
|
socket.SOCK_STREAM: "S",
|
|
socket.SOCK_DGRAM: "D",
|
|
}
|
|
|
|
# Actions that can be done by test. Actions are not only syscall
|
|
# names to call, but also arguments with which to do it
|
|
#
|
|
# Each action consists of
|
|
# - arguments, e.g. type of socket, or socket id to work on
|
|
# - act() method which just generates an record
|
|
# - do() method, that actually does what's required
|
|
# - show() method to return the string description of what's done
|
|
|
|
|
|
def mk_socket(st, typ):
|
|
st.sk_id += 1
|
|
sk = sock(st.sk_id, typ)
|
|
st.add_socket(sk)
|
|
return sk
|
|
|
|
|
|
class act_socket:
|
|
def __init__(self, typ):
|
|
self.typ = typ
|
|
|
|
def act(self, st):
|
|
sk = mk_socket(st, self.typ)
|
|
self.sk_id = sk.sk_id
|
|
|
|
def do(self, st):
|
|
sk = socket.socket(socket.AF_UNIX, self.typ, 0)
|
|
st.real_sockets[self.sk_id] = sk
|
|
|
|
def show(self):
|
|
return 'socket(%s) = %d' % (sk_type_s[self.typ], self.sk_id)
|
|
|
|
|
|
class act_close:
|
|
def __init__(self, sk_id):
|
|
self.sk_id = sk_id
|
|
|
|
def act(self, st):
|
|
sk = st.get_socket(self.sk_id)
|
|
st.del_socket(sk)
|
|
for ic in sk.icons:
|
|
sk = st.get_socket(ic)
|
|
st.del_socket(sk)
|
|
|
|
def do(self, st):
|
|
sk = st.real_sockets.pop(self.sk_id)
|
|
sk.close()
|
|
|
|
def show(self):
|
|
return 'close(%d)' % self.sk_id
|
|
|
|
|
|
class act_listen:
|
|
def __init__(self, sk_id):
|
|
self.sk_id = sk_id
|
|
|
|
def act(self, st):
|
|
sk = st.get_socket(self.sk_id)
|
|
sk.listen = True
|
|
|
|
def do(self, st):
|
|
sk = st.real_sockets[self.sk_id]
|
|
sk.listen(10)
|
|
|
|
def show(self):
|
|
return 'listen(%d)' % self.sk_id
|
|
|
|
|
|
class act_bind:
|
|
def __init__(self, sk_id, name_id):
|
|
self.sk_id = sk_id
|
|
self.name_id = name_id
|
|
|
|
def act(self, st):
|
|
sk = st.get_socket(self.sk_id)
|
|
sk.name = self.name_id
|
|
|
|
def do(self, st):
|
|
sk = st.real_sockets[self.sk_id]
|
|
sk.bind(sock.real_name_for(self.name_id))
|
|
|
|
def show(self):
|
|
return 'bind(%d, $name-%d)' % (self.sk_id, self.name_id)
|
|
|
|
|
|
class act_connect:
|
|
def __init__(self, sk_id, listen_sk_id):
|
|
self.sk_id = sk_id
|
|
self.lsk_id = listen_sk_id
|
|
|
|
def act(self, st):
|
|
sk = st.get_socket(self.sk_id)
|
|
if st.sk_type == socket.SOCK_STREAM:
|
|
lsk = st.get_socket(self.lsk_id)
|
|
psk = mk_socket(st, socket.SOCK_STREAM)
|
|
psk.visible = False
|
|
sk.peer = psk.sk_id
|
|
psk.peer = sk.sk_id
|
|
psk.name = lsk.name
|
|
lsk.icons.append(psk.sk_id)
|
|
lsk.icons_seq += 1
|
|
else:
|
|
sk.peer = self.lsk_id
|
|
psk = st.get_socket(self.lsk_id)
|
|
psk.icons_seq += 1
|
|
|
|
def do(self, st):
|
|
sk = st.real_sockets[self.sk_id]
|
|
sk.connect(sock.real_name_for(self.lsk_id))
|
|
|
|
def show(self):
|
|
return 'connect(%d, $name-%d)' % (self.sk_id, self.lsk_id)
|
|
|
|
|
|
class act_accept:
|
|
def __init__(self, sk_id):
|
|
self.sk_id = sk_id
|
|
|
|
def act(self, st):
|
|
lsk = st.get_socket(self.sk_id)
|
|
iid = lsk.icons.pop(0)
|
|
nsk = st.get_socket(iid)
|
|
nsk.visible = True
|
|
self.nsk_id = nsk.sk_id
|
|
|
|
def do(self, st):
|
|
sk = st.real_sockets[self.sk_id]
|
|
nsk, ai = sk.accept()
|
|
if self.nsk_id in st.real_sockets:
|
|
raise Exception("SK ID conflict")
|
|
st.real_sockets[self.nsk_id] = nsk
|
|
|
|
def show(self):
|
|
return 'accept(%d) = %d' % (self.sk_id, self.nsk_id)
|
|
|
|
|
|
class act_sendmsg:
|
|
def __init__(self, sk_id, to_id):
|
|
self.sk_id = sk_id
|
|
self.to_id = to_id
|
|
self.direct_send = None
|
|
|
|
def act(self, st):
|
|
sk = st.get_socket(self.sk_id)
|
|
msg = (sk.sk_id, sk.outseq)
|
|
self.msg_id = sk.outseq
|
|
sk.outseq += 1
|
|
psk = st.get_socket(self.to_id)
|
|
psk.inqueue.append(msg)
|
|
self.direct_send = (sk.peer == psk.sk_id)
|
|
|
|
def do(self, st):
|
|
sk = st.real_sockets[self.sk_id]
|
|
msgv = act_sendmsg.msgval(self.msg_id)
|
|
if self.direct_send:
|
|
sk.send(msgv)
|
|
else:
|
|
sk.sendto(msgv, sock.real_name_for(self.to_id))
|
|
|
|
def show(self):
|
|
return 'send(%d, %d, $message-%d)' % (self.sk_id, self.to_id,
|
|
self.msg_id)
|
|
|
|
@staticmethod
|
|
def msgval(msgid, pref=''):
|
|
return '%sMSG%d' % (pref, msgid)
|
|
|
|
|
|
#
|
|
# Description of a socket
|
|
#
|
|
class sock:
|
|
def __init__(self, sk_id, sock_type):
|
|
# ID of a socket. Since states and sockets are cloned
|
|
# while we scan the tree of states the only valid way
|
|
# to address a socket is to find one by ID.
|
|
self.sk_id = sk_id
|
|
# The socket.SOCK_FOO value
|
|
self.sk_type = sock_type
|
|
# Sockets that haven't yet been accept()-ed are in the
|
|
# state, but user cannot operate on them. Also this
|
|
# invisibility contributes to state description since
|
|
# connection to not accepted socket is not the same
|
|
# as connection to accepted one.
|
|
self.visible = True
|
|
# The listen() was called.
|
|
self.listen = False
|
|
# The bind() was called. Also set by accept(), the name
|
|
# inherits from listener.
|
|
self.name = None
|
|
# The connect() was called. Set on two sockets when the
|
|
# connect() is called.
|
|
self.peer = None
|
|
# Progress on accepting connections. Used to check when
|
|
# it's OK to close the socket (see comment below).
|
|
self.icons_seq = 0
|
|
# List of IDs of sockets that can be accept()-ed
|
|
self.icons = []
|
|
# Number to generate message contents.
|
|
self.outseq = 0
|
|
# Incoming queue of messages.
|
|
self.inqueue = []
|
|
|
|
def clone(self):
|
|
sk = sock(self.sk_id, self.sk_type)
|
|
sk.visible = self.visible
|
|
sk.listen = self.listen
|
|
sk.name = self.name
|
|
sk.peer = self.peer
|
|
sk.icons_seq = self.icons_seq
|
|
sk.icons = list(self.icons)
|
|
sk.outseq = self.outseq
|
|
sk.inqueue = list(self.inqueue)
|
|
return sk
|
|
|
|
def get_actions(self, st):
|
|
if not self.visible:
|
|
return []
|
|
|
|
if st.sk_type == socket.SOCK_STREAM:
|
|
return self.get_stream_actions(st)
|
|
else:
|
|
return self.get_dgram_actions(st)
|
|
|
|
def get_send_action(self, to, st):
|
|
# However, if peer has a message from us at
|
|
# the queue tail, sending a new one doesn't
|
|
# really make sense
|
|
want_msg = True
|
|
if len(to.inqueue) != 0:
|
|
lmsg = to.inqueue[-1]
|
|
if lmsg[0] == self.sk_id:
|
|
want_msg = False
|
|
if want_msg:
|
|
return [act_sendmsg(self.sk_id, to.sk_id)]
|
|
else:
|
|
return []
|
|
|
|
def get_stream_actions(self, st):
|
|
act_list = []
|
|
|
|
# Any socket can be closed, but closing a socket
|
|
# that hasn't contributed to some new states is
|
|
# just waste of time, so we close only connected
|
|
# sockets or listeners that has at least one
|
|
# incoming connection pendig or served
|
|
|
|
if self.listen:
|
|
if self.icons:
|
|
act_list.append(act_accept(self.sk_id))
|
|
if self.icons_seq:
|
|
act_list.append(act_close(self.sk_id))
|
|
elif self.peer:
|
|
act_list.append(act_close(self.sk_id))
|
|
# Connected sockets can send and receive messages
|
|
# But receiving seem not to produce any new states,
|
|
# so only sending
|
|
# Also sending to a closed socket doesn't work
|
|
psk = st.get_socket(self.peer, True)
|
|
if psk:
|
|
act_list += self.get_send_action(psk, st)
|
|
else:
|
|
for psk in st.sockets:
|
|
if psk.listen and psk.name:
|
|
act_list.append(act_connect(self.sk_id, psk.sk_id))
|
|
|
|
# Listen on not-bound socket is prohibited as
|
|
# well as binding a listening socket
|
|
if not self.name:
|
|
# TODO: support for file paths (see real_name_for)
|
|
# TODO: these names can overlap each other
|
|
act_list.append(act_bind(self.sk_id, self.sk_id))
|
|
else:
|
|
act_list.append(act_listen(self.sk_id))
|
|
|
|
return act_list
|
|
|
|
def get_dgram_actions(self, st):
|
|
act_list = []
|
|
|
|
# Dgram socket can bind at any time
|
|
if not self.name:
|
|
act_list.append(act_bind(self.sk_id, self.sk_id))
|
|
|
|
# Can connect to peer-less sockets
|
|
for psk in st.sockets:
|
|
if psk == self:
|
|
continue
|
|
if psk.peer != None and psk.peer != self.sk_id:
|
|
# Peer by someone else, can do nothing
|
|
continue
|
|
|
|
# Peer-less psk or having us as peer
|
|
# We can connect to or send messages
|
|
if psk.name and self.peer != psk.sk_id:
|
|
act_list.append(act_connect(self.sk_id, psk.sk_id))
|
|
|
|
if psk.name or self.peer == psk.sk_id:
|
|
act_list += self.get_send_action(psk, st)
|
|
|
|
if self.outseq != 0 or self.icons_seq != 0:
|
|
act_list.append(act_close(self.sk_id))
|
|
|
|
return act_list
|
|
|
|
@staticmethod
|
|
def name_of(sk):
|
|
if not sk:
|
|
return 'X'
|
|
elif not sk.visible:
|
|
return 'H'
|
|
elif sk.name:
|
|
return 'B'
|
|
else:
|
|
return 'A'
|
|
|
|
@staticmethod
|
|
def real_name_for(sk_id):
|
|
return "\0" + "CRSK%d" % sk_id
|
|
|
|
# The describe() generates a string that represents
|
|
# a state of a socket. Called by state.describe(), see
|
|
# comment there about what description is.
|
|
def describe(self, st):
|
|
dsc = '%s' % sk_type_s[self.sk_type]
|
|
dsc += sock.name_of(self)
|
|
|
|
if self.listen:
|
|
dsc += 'L'
|
|
if self.peer:
|
|
psk = st.get_socket(self.peer, True)
|
|
dsc += '-C%s' % sock.name_of(psk)
|
|
if self.icons:
|
|
i_dsc = ''
|
|
for c in self.icons:
|
|
psk = st.get_socket(c)
|
|
psk = st.get_socket(psk.peer, True)
|
|
i_dsc += sock.name_of(psk)
|
|
dsc += '-I%s' % i_dsc
|
|
if self.inqueue:
|
|
froms = set()
|
|
for m in self.inqueue:
|
|
froms.add(m[0])
|
|
q_dsc = ''
|
|
for f in froms:
|
|
fsk = st.get_socket(f, True)
|
|
q_dsc += sock.name_of(fsk)
|
|
dsc += '-M%s' % q_dsc
|
|
return dsc
|
|
|
|
|
|
class state:
|
|
def __init__(self, max_sockets, sk_type):
|
|
self.sockets = []
|
|
self.sk_id = 0
|
|
self.steps = []
|
|
self.real_sockets = {}
|
|
self.sockets_left = max_sockets
|
|
self.sk_type = sk_type
|
|
|
|
def add_socket(self, sk):
|
|
self.sockets.append(sk)
|
|
|
|
def del_socket(self, sk):
|
|
self.sockets.remove(sk)
|
|
|
|
def get_socket(self, sk_id, can_be_null=False):
|
|
for sk in self.sockets:
|
|
if sk.sk_id == sk_id:
|
|
return sk
|
|
|
|
if not can_be_null:
|
|
raise Exception("%d socket not in list" % sk_id)
|
|
|
|
return None
|
|
|
|
def get_actions(self):
|
|
act_list = []
|
|
|
|
# Any socket in the state we can change it
|
|
for sk in self.sockets:
|
|
act_list += sk.get_actions(self)
|
|
|
|
if self.sockets_left > 0:
|
|
act_list.append(act_socket(self.sk_type))
|
|
self.sockets_left -= 1
|
|
|
|
return act_list
|
|
|
|
def clone(self):
|
|
nst = state(self.sockets_left, self.sk_type)
|
|
for sk in self.sockets:
|
|
nst.sockets.append(sk.clone())
|
|
nst.sk_id = self.sk_id
|
|
nst.steps = list(self.steps)
|
|
return nst
|
|
|
|
# Generates textual description of a state. Different states
|
|
# may have same descriptions, e.g. if we have two sockets and
|
|
# only one of them is in listen state, we don't care which
|
|
# one in which. At the same time really different states
|
|
# shouldn't map to the same string.
|
|
def describe(self):
|
|
sks = [x.describe(self) for x in self.sockets]
|
|
sks = sorted(sks)
|
|
return '_'.join(sks)
|
|
|
|
|
|
def set_nonblock(sk):
|
|
fd = sk.fileno()
|
|
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
|
|
fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
|
|
|
|
|
CHK_FAIL_UNKNOWN = 10
|
|
CHK_FAIL_SOCKET = 11
|
|
CHK_FAIL_STAT = 12
|
|
CHK_FAIL_LISTEN = 13
|
|
CHK_FAIL_NAME = 14
|
|
CHK_FAIL_ACCEPT = 15
|
|
CHK_FAIL_RECV_0 = 16
|
|
CHK_FAIL_RECV_MIX = 17
|
|
CHK_FAIL_CONNECT = 18
|
|
CHK_FAIL_CONNECT2 = 19
|
|
CHK_FAIL_KILLED = 20
|
|
CHK_FAIL_DUMP = 21
|
|
CHK_FAIL_RESTORE = 22
|
|
|
|
CHK_PASS = 42
|
|
|
|
fail_desc = {
|
|
CHK_FAIL_UNKNOWN: 'Aliens invaded the test',
|
|
CHK_FAIL_LISTEN: 'Listen state lost on restore',
|
|
CHK_FAIL_NAME: 'Name lost on restore',
|
|
CHK_FAIL_ACCEPT: 'Incoming connection lost on restore',
|
|
CHK_FAIL_RECV_0: 'Message lost on restore',
|
|
CHK_FAIL_RECV_MIX: 'Message misorder on restore',
|
|
CHK_FAIL_CONNECT: 'Connectivity broken on restore',
|
|
CHK_FAIL_CONNECT2: 'Connectivity broken the hard way on restore',
|
|
CHK_FAIL_KILLED: 'Test process died unexpectedly',
|
|
CHK_FAIL_DUMP: 'Cannot dump',
|
|
CHK_FAIL_RESTORE: 'Cannot restore',
|
|
}
|
|
|
|
|
|
def chk_real_state(st):
|
|
# Before enything else -- check that we still have
|
|
# all the sockets at hands
|
|
for sk in st.sockets:
|
|
if not sk.visible:
|
|
continue
|
|
|
|
# In theory we can have key-not-found exception here,
|
|
# but this has nothing to do with sockets restore,
|
|
# since it's just bytes in memory, so ... we assume
|
|
# that we have object here and just check for it in
|
|
# the fdtable
|
|
rsk = st.real_sockets[sk.sk_id]
|
|
try:
|
|
s_st = os.fstat(rsk.fileno())
|
|
except:
|
|
print('FAIL: Socket %d lost' % sk.sk_id)
|
|
return CHK_FAIL_SOCKET
|
|
if not stat.S_ISSOCK(s_st.st_mode):
|
|
print('FAIL: Not a socket %d at %d' % (sk.sk_id, rsk.fileno()))
|
|
return CHK_FAIL_STAT
|
|
|
|
# First -- check the listen states and names
|
|
for sk in st.sockets:
|
|
if not sk.visible:
|
|
continue
|
|
|
|
rsk = st.real_sockets[sk.sk_id]
|
|
r_listen = rsk.getsockopt(socket.SOL_SOCKET, socket.SO_ACCEPTCONN)
|
|
if (sk.listen and r_listen == 0) or (not sk.listen and r_listen == 1):
|
|
print("FAIL: Socket %d listen %d, expected %d" %
|
|
(sk.sk_id, r_listen, sk.listen and 1 or 0))
|
|
return CHK_FAIL_LISTEN
|
|
|
|
if sk.name:
|
|
r_name = rsk.getsockname()
|
|
w_name = sock.real_name_for(sk.name)
|
|
if r_name != w_name:
|
|
print('FAIL: Socket %d name mismatch [%s], want [%s]' %
|
|
(sk.sk_id, r_name, w_name))
|
|
return CHK_FAIL_NAME
|
|
|
|
# Second -- check (accept) pending connections
|
|
for sk in st.sockets:
|
|
if not sk.listen:
|
|
continue
|
|
|
|
rsk = st.real_sockets[sk.sk_id]
|
|
set_nonblock(rsk)
|
|
|
|
while sk.icons:
|
|
# Do act_accept to change the state properly
|
|
# and not write the code twice
|
|
acc = act_accept(sk.sk_id)
|
|
acc.act(st)
|
|
try:
|
|
acc.do(st)
|
|
except:
|
|
print('FAIL: Cannot accept pending connection for %d' %
|
|
sk.sk_id)
|
|
return CHK_FAIL_ACCEPT
|
|
|
|
print(' `- did %s' % acc.show())
|
|
|
|
# Third -- check inqueues
|
|
for sk in st.sockets:
|
|
if not sk.inqueue:
|
|
continue
|
|
|
|
rsk = st.real_sockets[sk.sk_id]
|
|
set_nonblock(rsk)
|
|
|
|
while sk.inqueue:
|
|
msg = sk.inqueue.pop(0)
|
|
try:
|
|
r_msg, m_from = rsk.recvfrom(128)
|
|
except:
|
|
print('FAIL: No message in queue for %d' % sk.sk_id)
|
|
return CHK_FAIL_RECV_0
|
|
|
|
w_msg = act_sendmsg.msgval(msg[1])
|
|
if r_msg != w_msg:
|
|
print('FAIL: Message misorder: %s want %s (from %d)' %
|
|
(r_msg, w_msg, msg[0]))
|
|
return CHK_FAIL_RECV_MIX
|
|
|
|
# TODO -- check sender
|
|
print(' `- recvd %d.%d msg %s -> %d' %
|
|
(msg[0], msg[1], m_from, sk.sk_id))
|
|
|
|
# Finally, after all sockets are visible and all inqueues are
|
|
# drained -- check the sockets connectivity
|
|
for sk in st.sockets:
|
|
if not sk.peer:
|
|
continue
|
|
|
|
# Closed connection with one peer alive. Cannot check.
|
|
if not sk.peer in st.real_sockets:
|
|
continue
|
|
|
|
rsk = st.real_sockets[sk.sk_id]
|
|
psk = st.real_sockets[sk.peer]
|
|
set_nonblock(psk)
|
|
msgv = act_sendmsg.msgval(3 * sk.sk_id + 5 * sk.peer,
|
|
'C') # just random
|
|
|
|
try:
|
|
rsk.send(msgv)
|
|
rmsg = psk.recv(128)
|
|
except:
|
|
print('FAIL: Connectivity %d -> %d lost' % (sk.sk_id, sk.peer))
|
|
return CHK_FAIL_CONNECT
|
|
|
|
# If sockets are not connected the recv above
|
|
# would generate exception and the check would
|
|
# fail. But just in case we've screwed the queues
|
|
# the hard way -- also check for the message being
|
|
# delivered for real
|
|
if rmsg != msgv:
|
|
print('FAIL: Connectivity %d -> %d not verified' %
|
|
(sk.sk_id, sk.peer))
|
|
return CHK_FAIL_CONNECT2
|
|
|
|
print(' `- checked %d -> %d with %s' % (sk.sk_id, sk.peer, msgv))
|
|
|
|
return CHK_PASS
|
|
|
|
|
|
def chk_state(st, opts):
|
|
print("Will check state")
|
|
|
|
sigsk_name = "\0" + "CRSIGSKC"
|
|
signal_sk = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM, 0)
|
|
signal_sk.bind(sigsk_name)
|
|
|
|
# FIXME Ideally call to criu should be performed by the run_state's
|
|
# pid!=0 branch, but for simplicity we fork the kid which has the
|
|
# same set of sockets we do, then dump it. Then restore and notify
|
|
# via dgram socket to check its state. Current task still has all
|
|
# the same sockets :) so we close them not to produce bind() name
|
|
# conflicts on restore
|
|
|
|
pid = os.fork()
|
|
if pid == 0:
|
|
msg = signal_sk.recv(64)
|
|
ret = chk_real_state(st)
|
|
sys.exit(ret)
|
|
|
|
signal_sk.close()
|
|
for rsk in st.real_sockets.values():
|
|
rsk.close()
|
|
|
|
print("`- dump")
|
|
img_path = "sti_" + st.describe()
|
|
try:
|
|
os.mkdir(img_path)
|
|
subprocess.check_call([
|
|
criu_bin, "dump", "-t",
|
|
"%d" % pid, "-D", img_path, "-v4", "-o", "dump.log", "-j"
|
|
])
|
|
except:
|
|
print("Dump failed")
|
|
os.kill(pid, signal.SIGKILL)
|
|
return CHK_FAIL_DUMP
|
|
|
|
print("`- restore")
|
|
try:
|
|
os.waitpid(pid, 0)
|
|
subprocess.check_call([
|
|
criu_bin, "restore", "-D", img_path, "-v4", "-o", "rst.log", "-j",
|
|
"-d", "-S"
|
|
])
|
|
except:
|
|
print("Restore failed")
|
|
return CHK_FAIL_RESTORE
|
|
|
|
print("`- check")
|
|
signal_sk = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM, 0)
|
|
try:
|
|
signal_sk.sendto('check', sigsk_name)
|
|
except:
|
|
# Probably the peer has died before us or smth else went wrong
|
|
os.kill(pid, signal.SIGKILL)
|
|
|
|
wp, status = os.waitpid(pid, 0)
|
|
if os.WIFEXITED(status):
|
|
status = os.WEXITSTATUS(status)
|
|
if status != CHK_PASS:
|
|
print("`- exited with %d" % status)
|
|
return status
|
|
elif os.WIFSIGNALED(status):
|
|
status = os.WTERMSIG(status)
|
|
print("`- killed with %d" % status)
|
|
return CHK_FAIL_KILLED
|
|
else:
|
|
return CHK_FAIL_UNKNOWN
|
|
|
|
return CHK_PASS
|
|
|
|
|
|
def run_state(st, opts):
|
|
print("Will run state")
|
|
pid = os.fork()
|
|
if pid != 0:
|
|
wpid, status = os.wait()
|
|
if os.WIFEXITED(status):
|
|
status = os.WEXITSTATUS(status)
|
|
elif os.WIFSIGNALED(status):
|
|
status = CHK_FAIL_KILLED
|
|
else:
|
|
status = CHK_FAIL_UNKNOWN
|
|
return status
|
|
|
|
# Try the states in subprocess so that once
|
|
# it exits the created sockets are removed
|
|
for step in st.steps:
|
|
step.do(st)
|
|
|
|
if not opts.run:
|
|
ret = chk_state(st, opts)
|
|
else:
|
|
ret = chk_real_state(st)
|
|
|
|
sys.exit(ret)
|
|
|
|
|
|
def proceed(st, seen, failed, opts, depth=0):
|
|
desc = st.describe()
|
|
if not desc:
|
|
pass
|
|
elif not desc in seen:
|
|
# When scanning the tree we run and try only states that
|
|
# differ, but don't stop tree traversal on them. This is
|
|
# because sometimes we can get into the already seen state
|
|
# using less steps and it's better to proceed as we have
|
|
# depth to move forward and generate more states.
|
|
seen[desc] = len(st.steps)
|
|
print('%s' % desc)
|
|
for s in st.steps:
|
|
print('\t%s' % s.show())
|
|
|
|
if not opts.gen:
|
|
ret = run_state(st, opts)
|
|
if ret != CHK_PASS:
|
|
failed.add((desc, ret))
|
|
if not opts.keep:
|
|
return False
|
|
else:
|
|
# Don't even proceed with this state if we've already
|
|
# seen one but get there with less steps
|
|
seen_score = seen[desc]
|
|
if len(st.steps) > seen_score:
|
|
return True
|
|
else:
|
|
seen[desc] = len(st.steps)
|
|
|
|
if depth >= opts.depth:
|
|
return True
|
|
|
|
actions = st.get_actions()
|
|
for act in actions:
|
|
nst = st.clone()
|
|
act.act(nst)
|
|
nst.steps.append(act)
|
|
if not proceed(nst, seen, failed, opts, depth + 1):
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
p = argparse.ArgumentParser("CRIU test suite")
|
|
p.add_argument("--depth", help="Depth of generated tree", default='8')
|
|
p.add_argument("--sockets", help="Maximum number of sockets", default='1')
|
|
p.add_argument("--dgram", help="Use SOCK_DGRAM sockets", action='store_true')
|
|
p.add_argument("--stream", help="Use SOCK_STREAM sockets", action='store_true')
|
|
p.add_argument("--gen",
|
|
help="Only generate and show states",
|
|
action='store_true')
|
|
p.add_argument("--run",
|
|
help="Run the states, but don't C/R",
|
|
action='store_true')
|
|
p.add_argument("--keep", help="Don't stop on error", action='store_true')
|
|
opts = p.parse_args()
|
|
opts.depth = int(opts.depth)
|
|
|
|
# XXX: does it make any sense to mix two types in one go?
|
|
if opts.stream and opts.dgram:
|
|
print('Choose only one type')
|
|
sys.exit(1)
|
|
|
|
if opts.stream:
|
|
sk_type = socket.SOCK_STREAM
|
|
elif opts.dgram:
|
|
sk_type = socket.SOCK_DGRAM
|
|
else:
|
|
print('Choose some type')
|
|
sys.exit(1)
|
|
|
|
st = state(int(opts.sockets), sk_type)
|
|
seen = {}
|
|
failed = set()
|
|
proceed(st, seen, failed, opts)
|
|
|
|
if len(failed) == 0:
|
|
print('PASS (%d states)' % len(seen))
|
|
else:
|
|
print('FAIL %d/%d' % (len(failed), len(seen)))
|
|
for f in failed:
|
|
print("\t%-50s: %s" %
|
|
(f[0], fail_desc.get(f[1], 'unknown reason %d' % f[1])))
|