Skip to content

Commit

Permalink
bareboxdriver: refactor login expect loop and add password support
Browse files Browse the repository at this point in the history
The fake console had to be extended to respond to echo commands.

Also clarify the equivalent comment in the ShellDriver.

Signed-off-by: Jan Luebbe <jlu@pengutronix.de>
  • Loading branch information
jluebbe committed Jun 6, 2018
1 parent 2933466 commit 016f605
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 27 deletions.
2 changes: 2 additions & 0 deletions doc/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,8 @@ Arguments:
- interrupt (str, default="\\n"): string to interrupt autoboot (use "\\x03" for CTRL-C)
- startstring (regex, default="[\n]barebox 20\d+"): string that indicates that Barebox is starting
- bootstring (regex, default="Linux version \d"): succesfully jumped into the kernel
- password (str): optional, password to use for access to the shell
- login_timeout (int): optional, timeout for access to the shell

ExternalConsoleDriver
~~~~~~~~~~~~~~~~~~~~~
Expand Down
80 changes: 67 additions & 13 deletions labgrid/driver/bareboxdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from ..factory import target_factory
from ..protocol import CommandProtocol, ConsoleProtocol, LinuxBootProtocol
from ..step import step
from ..util import gen_marker
from ..util import gen_marker, Timeout
from .common import Driver
from .commandmixin import CommandMixin

Expand All @@ -24,13 +24,17 @@ class BareboxDriver(CommandMixin, Driver, CommandProtocol, LinuxBootProtocol):
prompt (str): The default Barebox Prompt
startstring (str): string that indicates that Barebox is starting
bootstring (str): string that indicates that the Kernel is booting
password (str): optional, password to use for access to the shell
login_timeout (int): optional, timeout for access to the shell
"""
bindings = {"console": ConsoleProtocol, }
prompt = attr.ib(default="", validator=attr.validators.instance_of(str))
autoboot = attr.ib(default="stop autoboot", validator=attr.validators.instance_of(str))
interrupt = attr.ib(default="\n", validator=attr.validators.instance_of(str))
startstring = attr.ib(default=r"[\n]barebox 20\d+", validator=attr.validators.instance_of(str))
bootstring = attr.ib(default=r"Linux version \d", validator=attr.validators.instance_of(str))
password = attr.ib(default="", validator=attr.validators.instance_of(str))
login_timeout = attr.ib(default=60, validator=attr.validators.instance_of(int))

def __attrs_post_init__(self):
super().__attrs_post_init__()
Expand All @@ -43,9 +47,8 @@ def __attrs_post_init__(self):
def on_activate(self):
"""Activate the BareboxDriver
This function checks for a prompt and awaits it if not already active
This function tries to login if not already active
"""
self._check_prompt()
if self._status == 0:
self._await_prompt()

Expand Down Expand Up @@ -95,7 +98,7 @@ def run(self, cmd: str, *, step, timeout: int = 30): # pylint: disable=unused-a
def reset(self):
"""Reset the board via a CPU reset
"""
self.status = 0
self._status = 0
self.console.sendline("reset")
self._await_prompt()

Expand All @@ -113,22 +116,73 @@ def _check_prompt(self):
Internal function to check if we have a valid prompt.
It sets the internal _status to 1 or 0 based on the prompt detection.
"""
self.console.sendline("")
marker = gen_marker()
# hide marker from expect
hidden_marker = '"{}""{}"'.format(marker[:4], marker[4:])
self.console.sendline("echo {}".format(hidden_marker))
try:
self.console.expect("{}".format(marker), timeout=2)
self.console.expect(self.prompt, timeout=1)
self._status = 1
except TIMEOUT:
self._status = 0
raise

@step()
def _await_prompt(self):
"""Await autoboot line and stop it to get to the prompt"""
self.console.expect(self.startstring)
index, _, _, _ = self.console.expect([self.prompt, self.autoboot])
if index == 0:
self._status = 1
else:
self.console.write(self.interrupt.encode('ASCII'))
self._check_prompt()
"""Awaits the prompt and enters the shell"""

timeout = Timeout(float(self.login_timeout))

# We call console.expect with a short timeout here to detect if the
# console is idle, which would result in a timeout without any changes
# to the before property. So we store the last before value we've seen.
# Because pexpect keeps any read data in it's buffer when a timeout
# occours, we can't lose any data this way.
last_before = None
password_entered = False

expectations = [self.prompt, self.autoboot, "Password: ", TIMEOUT]
while True:
index, before, _, _ = self.console.expect(
expectations,
timeout=2
)

if index == 0:
# we got a prompt. no need for any further action to activate
# this driver.
self._status = 1
break

elif index == 1:
# we need to interrupt autoboot
self.console.write(self.interrupt.encode('ASCII'))

elif index == 2:
# we need to enter the password
if not self.password:
raise Exception("Password entry needed but no password set")
if password_entered:
# we already sent the password, but got the pw prompt again
raise Exception("Password was not correct")
self.console.sendline(self.password)
password_entered = True

elif index == 3:
# expect hit a timeout while waiting for a match
if before == last_before:
# we did not receive anything during the previous expect cycle
# let's assume the target is idle and we can safely issue a
# newline to check the state
self.console.sendline("")

if timeout.expired:
raise TIMEOUT("Timeout of {} seconds exceeded during waiting for login".format(self.login_timeout))

last_before = before

self._check_prompt()

@Driver.check_active
def await_boot(self):
Expand Down
20 changes: 14 additions & 6 deletions labgrid/driver/fake.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import re

import attr

Expand All @@ -17,12 +18,19 @@ class FakeConsoleDriver(ConsoleExpectMixin, Driver, ConsoleProtocol):
def __attrs_post_init__(self):
super().__attrs_post_init__()
self.logger = logging.getLogger("{}({})".format(self, self.target))

def _read(self, *args):
pass

def _write(self, *args):
pass
self.rxq = []
self.txq = []

def _read(self, *args, **kwargs):
if self.rxq:
return self.rxq.pop()
return b''

def _write(self, data, *args):
self.txq.append(data)
mo = re.match(rb'^echo "(\w+)""(\w+)"\n$', data)
if mo:
self.rxq.insert(0, b''.join(mo.groups())+b'\n')

def open(self):
pass
Expand Down
13 changes: 5 additions & 8 deletions labgrid/driver/shelldriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,11 @@ def _await_login(self):
if self.console_ready != "":
expectations.append(self.console_ready)

# Use console.expect with a short timeout in a loop increases the
# chance to split a match into two consecutive chunks of the stream.
# This may lead to a missed match.
# Hopefully we will recover from all those situations by sending a
# newline after self.await_login_timeout.

# the returned 'before' of the expect will keep all characters.
# thus we need to remember what we had seen.
# We call console.expect with a short timeout here to detect if the
# console is idle, which results in a timeout without any changes to
# the before property. So we store the last before value we've seen.
# Because pexpect keeps any read data in it's buffer when a timeout
# occours, we can't lose any data this way.
last_before = None

while True:
Expand Down

0 comments on commit 016f605

Please sign in to comment.