2016-06-17 22:47:34 +03:00
|
|
|
import os
|
|
|
|
import pty
|
2016-10-10 21:10:08 +03:00
|
|
|
import re
|
2018-06-04 22:39:14 +03:00
|
|
|
import signal
|
2016-06-17 22:47:34 +03:00
|
|
|
import termios
|
2018-06-04 22:39:14 +03:00
|
|
|
import time
|
2016-06-17 22:47:34 +03:00
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
2015-09-10 01:05:34 +03:00
|
|
|
EOF = b'\x04'
|
|
|
|
|
|
|
|
|
|
|
|
def ttyflags(fd):
|
2015-09-11 00:05:05 +03:00
|
|
|
"""normalize tty i/o for testing"""
|
2015-09-12 01:57:22 +03:00
|
|
|
# see:
|
|
|
|
# http://www.gnu.org/software/libc/manual/html_mono/libc.html#Output-Modes
|
2016-06-17 22:47:34 +03:00
|
|
|
attrs = termios.tcgetattr(fd)
|
|
|
|
attrs[1] &= ~termios.OPOST # don't munge output
|
|
|
|
attrs[3] &= ~termios.ECHO # don't echo input
|
|
|
|
termios.tcsetattr(fd, termios.TCSANOW, attrs)
|
2015-09-10 01:05:34 +03:00
|
|
|
|
|
|
|
|
2015-09-11 00:05:05 +03:00
|
|
|
def readall(fd):
|
|
|
|
"""read until EOF"""
|
2015-09-11 04:04:33 +03:00
|
|
|
result = b''
|
2015-09-11 00:05:05 +03:00
|
|
|
while True:
|
|
|
|
try:
|
2016-06-17 22:47:34 +03:00
|
|
|
chunk = os.read(fd, 1 << 10)
|
2015-09-11 00:05:05 +03:00
|
|
|
except OSError as error:
|
|
|
|
if error.errno == 5: # linux pty EOF
|
|
|
|
return result
|
|
|
|
else:
|
|
|
|
raise
|
2016-08-01 20:24:00 +03:00
|
|
|
if chunk == b'':
|
2015-09-11 00:05:05 +03:00
|
|
|
return result
|
|
|
|
else:
|
|
|
|
result += chunk
|
|
|
|
|
|
|
|
|
2015-09-17 05:42:09 +03:00
|
|
|
# disable debug output so it doesn't break our assertion
|
2016-06-17 22:47:34 +03:00
|
|
|
@pytest.mark.usefixtures('debug_disabled')
|
|
|
|
def test_tty():
|
2016-10-10 21:10:08 +03:00
|
|
|
"""Ensure processes under dumb-init can write successfully, given a tty."""
|
2015-09-10 01:05:34 +03:00
|
|
|
pid, fd = pty.fork()
|
|
|
|
if pid == 0:
|
2016-06-17 22:47:34 +03:00
|
|
|
os.execvp('dumb-init', ('dumb-init', 'tac'))
|
2015-09-10 01:05:34 +03:00
|
|
|
else:
|
2016-10-10 21:10:08 +03:00
|
|
|
# write to tac via the pty and verify its output
|
|
|
|
ttyflags(fd)
|
|
|
|
assert os.write(fd, b'1\n2\n3\n') == 6
|
|
|
|
assert os.write(fd, EOF * 2) == 2
|
|
|
|
output = readall(fd)
|
|
|
|
assert os.waitpid(pid, 0) == (pid, 0)
|
|
|
|
|
|
|
|
assert output == b'3\n2\n1\n', repr(output)
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.usefixtures('both_debug_modes')
|
|
|
|
@pytest.mark.usefixtures('both_setsid_modes')
|
|
|
|
def test_child_gets_controlling_tty_if_we_had_one():
|
|
|
|
"""If dumb-init has a controlling TTY, it should give it to the child.
|
|
|
|
|
|
|
|
To test this, we make a new TTY then exec "dumb-init bash" and ensure that
|
|
|
|
the shell has working job control.
|
|
|
|
"""
|
|
|
|
pid, sfd = pty.fork()
|
|
|
|
if pid == 0:
|
2016-10-11 22:24:10 +03:00
|
|
|
os.execvp('dumb-init', ('dumb-init', 'bash', '-m'))
|
2016-10-10 21:10:08 +03:00
|
|
|
else:
|
|
|
|
ttyflags(sfd)
|
|
|
|
|
|
|
|
# We might get lots of extra output from the shell, so print something
|
|
|
|
# we can match on easily.
|
|
|
|
assert os.write(sfd, b'echo "flags are: [[$-]]"\n') == 25
|
|
|
|
assert os.write(sfd, b'exit 0\n') == 7
|
|
|
|
output = readall(sfd)
|
|
|
|
assert os.waitpid(pid, 0) == (pid, 0), output
|
|
|
|
|
2019-02-21 00:00:02 +03:00
|
|
|
m = re.search(b'flags are: \\[\\[([a-zA-Z]+)\\]\\]\n', output)
|
2016-10-10 21:10:08 +03:00
|
|
|
assert m, output
|
|
|
|
|
|
|
|
# "m" is job control
|
|
|
|
flags = m.group(1)
|
|
|
|
assert b'm' in flags
|
2018-06-04 22:39:14 +03:00
|
|
|
|
|
|
|
|
|
|
|
def test_sighup_sigcont_ignored_if_was_session_leader():
|
|
|
|
"""The first SIGHUP/SIGCONT should be ignored if dumb-init is the session leader.
|
|
|
|
|
|
|
|
Due to TTY quirks (#136), when dumb-init is the session leader and forks,
|
|
|
|
it needs to avoid forwarding the first SIGHUP and SIGCONT to the child.
|
|
|
|
Otherwise, the child might receive the SIGHUP post-exec and terminate
|
|
|
|
itself.
|
|
|
|
|
|
|
|
You can "force" this race by adding a `sleep(1)` before the signal handling
|
|
|
|
loop in dumb-init's code, but it's hard to reproduce the race reliably in a
|
|
|
|
test otherwise. Because of this, we're stuck just asserting debug messages.
|
|
|
|
"""
|
|
|
|
pid, fd = pty.fork()
|
|
|
|
if pid == 0:
|
|
|
|
# child
|
|
|
|
os.execvp('dumb-init', ('dumb-init', '-v', 'sleep', '20'))
|
|
|
|
else:
|
|
|
|
# parent
|
|
|
|
ttyflags(fd)
|
|
|
|
|
|
|
|
# send another SIGCONT to make sure only the first is ignored
|
|
|
|
time.sleep(0.5)
|
|
|
|
os.kill(pid, signal.SIGHUP)
|
|
|
|
|
|
|
|
output = readall(fd).decode('UTF-8')
|
|
|
|
|
|
|
|
assert 'Ignoring tty hand-off signal {}.'.format(signal.SIGHUP) in output
|
|
|
|
assert 'Ignoring tty hand-off signal {}.'.format(signal.SIGCONT) in output
|
|
|
|
|
|
|
|
assert '[dumb-init] Forwarded signal {} to children.'.format(signal.SIGHUP) in output
|
|
|
|
assert '[dumb-init] Forwarded signal {} to children.'.format(signal.SIGCONT) not in output
|