import re
import tbot
from tbot.machine import linux
import tbottest.initconfig as ini
from tbottest.tc.common import lnx_create_random
from tbottest.tc.common import lnx_compare_files
def _write_random_and_verify(lnx: linux.LinuxShell, tmpf: str, dev: str, t: dict) -> None:
lnx_create_random(lnx, tmpf, int(t["cnt"]) * int(t["bs"]))
lnx.exec0(
"dd",
f"if={tmpf}",
f"of={dev}",
f"bs={t['bs']}",
f"count={t['cnt']}",
f"seek={t['seek']}",
)
try:
lnx_compare_files(
lnx,
tmpf,
0,
dev,
int(t["seek"]) * int(t["bs"]),
int(t["cnt"]) * int(t["bs"]),
)
except Exception:
lnx.interactive()
def _hexdump(lnx: linux.LinuxShell, path: str, skipflag: str, length: int) -> str:
return lnx.exec0("hexdump", "-e", '"%03.2x"', skipflag, "0", "-n", str(length), path)
[docs]
def lnx_mtd_nvram(
lnx: linux.LinuxShell,
dev: str = "/dev/mtd0",
tests=None,
) -> None:
"""
write and reread random data on device dev
offsets, bytesize and length is defined in
array tests which contain dictionary of form
.. code-block:: python
{"bs" : "1", "cnt" : "2", "seek" : "0"}
example:
.. code-block:: python
tests = [
{"bs" : "1", "cnt" : "2", "seek" : "0"},
{"bs" : "1", "cnt" : "2", "seek" : "5"},
{"bs" : "1", "cnt" : "20", "seek" : "5"},
{"bs" : "1", "cnt" : "26", "seek" : "0"},
]
"""
if tests is None:
raise RuntimeError("please define tests")
tmpf = "/tmp/gnlmpf"
lnx.exec0("date", linux.Raw(">"), tmpf)
lnx.exec0("cat", tmpf)
for t in tests:
_write_random_and_verify(lnx, tmpf, dev, t)
[docs]
@tbot.testcase
def lnx_mtd_nvram_reboot(
dev: str = "/dev/mtd0",
tests=None,
) -> None:
"""
prerequisite: Board boots into linux
fill device with random data, as defined in tests an
reboot and check if the nvram contains the same data
after the reboot.
.. code-block:: python
{"bs" : "1", "cnt" : "2", "seek" : "0"}
example:
.. code-block:: python
tests = [
{"bs" : "1", "cnt" : "2", "seek" : "0"},
{"bs" : "1", "cnt" : "2", "seek" : "5"},
{"bs" : "1", "cnt" : "20", "seek" : "5"},
{"bs" : "1", "cnt" : "26", "seek" : "0"},
]
"""
if tests is None:
raise RuntimeError("please define tests")
tmpf = "/tmp/gnlmpf"
# -s is the skip-offset flag for both busybox and non-busybox hexdump
option = "-s"
for t in tests:
length = int(t["cnt"]) * int(t["bs"])
with tbot.ctx.request(tbot.role.BoardLinux) as lnx:
_write_random_and_verify(lnx, tmpf, dev, t)
out = _hexdump(lnx, tmpf, option, length)
with tbot.ctx.request(tbot.role.BoardLinux, reset=True) as lnx:
lnx.exec0(
"dd",
f"if={dev}",
f"of={tmpf}",
f"bs={t['bs']}",
f"count={t['cnt']}",
f"skip={t['seek']}",
)
outn = _hexdump(lnx, tmpf, option, length)
if out != outn:
tbot.log.message(
tbot.log.c(f"content differ:\noriginal:\n{out}\nnew\n{outn}").red
)
raise RuntimeError("files have not same content")
def _proc_mtd(lnx: linux.LinuxShell) -> list:
"""
Parse /proc/mtd into a list of (index, size in bytes, name).
"""
parts = []
for line in lnx.exec0("cat", "/proc/mtd").splitlines():
m = re.match(r'^mtd(\d+):\s+([0-9a-fA-F]+)\s+[0-9a-fA-F]+\s+"([^"]*)"', line)
if m:
parts.append((int(m.group(1)), int(m.group(2), 16), m.group(3)))
if not parts:
raise RuntimeError("no MTD partitions found in /proc/mtd")
return parts
[docs]
@tbot.testcase
def lnx_mtd_dump(
ethdevice: str = "eth0",
subdir: str = "dump",
) -> None:
"""
prerequisite: Board boots into linux, reachable with ssh as root
Works with the board's linux on the console or, with -fssh, over ssh.
Over ssh the md5sums on the board are not disturbed by whatever
userspace writes to the console.
Dump every MTD partition of the board into the lab host's tftp
directory, below subdir, and verify each dump against the board.
The board side only reads, through the /dev/mtdrN nodes, which the
kernel opens read only. The data goes from the board to the lab host
with ssh ("ssh root@<ipaddr> cat /dev/mtdrN"), straight into a file on
the lab host. The lab host needs sshpass: the root password from
linux_password is handed over with "sshpass -e" in the SSHPASS variable
of a subshell, set with tbot's PasswordAuthenticator.export_sshpass(),
so it shows up neither in the command log, nor in a channel trace, nor
in the process list. The board computes md5sum of every partition over
its console, the lab host does the same on the files, and sizes and sums
have to match.
Files written: mtd<N>-<name>.bin per partition, proc-mtd.txt with the
board's /proc/mtd, and md5sums.txt.
"""
with tbot.ctx() as cx:
lab = cx.request(tbot.role.LabHost)
lnx = cx.request(tbot.role.BoardLinux)
ip = lab.ethdevices[ini.generic_get_boardname()][ethdevice]["ipaddr"]
# The linux classes that log in on the console carry the password,
# the ssh one (-fssh) does not; then take it from the board ini.
password = getattr(lnx, "password", None) or ini.init_get_config(
ini.IniConfig().config_parser, "linux_password", "None"
)
if not password:
raise RuntimeError("no linux_password in the board ini")
dumpdir = lab.tftp_dir() / subdir
lab.exec0("mkdir", "-p", dumpdir)
procmtd = lnx.exec0("cat", "/proc/mtd")
(dumpdir / "proc-mtd.txt").write_text(procmtd)
sums = []
with lab.subshell():
linux.auth.PasswordAuthenticator(password).export_sshpass(lab)
for idx, size, name in _proc_mtd(lnx):
sums.append(_dump_one(lab, lnx, ip, dumpdir, idx, size, name))
(dumpdir / "md5sums.txt").write_text("\n".join(sums) + "\n")
def _dump_one(lab, lnx, ip, dumpdir, idx: int, size: int, name: str) -> str:
"""
Dump one MTD partition to the lab host and verify it. Needs SSHPASS
set in the lab host's shell. Returns the md5sums.txt line.
"""
dev = f"/dev/mtdr{idx}"
fname = f"mtd{idx}-{name}.bin"
dst = dumpdir / fname
boardsum = lnx.exec0("md5sum", dev).split()[0]
lab.exec0(
"sshpass",
"-e",
"ssh",
"-o",
"StrictHostKeyChecking=no",
"-o",
"UserKnownHostsFile=/dev/null",
f"root@{ip}",
f"cat {dev}",
linux.RedirStdout(dst),
)
labsize = int(lab.exec0("stat", "-c", "%s", dst).strip())
if labsize != size:
raise RuntimeError(f"{fname}: {labsize} bytes, partition has {size}")
labsum = lab.exec0("md5sum", dst).split()[0]
if labsum != boardsum:
raise RuntimeError(f"{fname}: md5sum {labsum}, board has {boardsum}")
tbot.log.message(f"{fname}: {size} bytes, md5sum {labsum}, verified")
return f"{labsum} {fname}"