Skip to content

Commit 01ae7c9

Browse files
committed
utils: lib_hwprobe.sh: local & docker hw-probe orchestration, extract, URL parse
Signed-off-by: Srikanth Muppandam <smuppand@qti.qualcomm.com>
1 parent 40567ee commit 01ae7c9

1 file changed

Lines changed: 347 additions & 0 deletions

File tree

Runner/utils/lib_hwprobe.sh

Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
#!/bin/sh
2+
# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
3+
# SPDX-License-Identifier: BSD-3-Clause-Clear
4+
# hw-probe helpers. Requires lib_common.sh, lib_apt.sh, lib_docker.sh
5+
6+
HWPROBE_PKG="hw-probe"
7+
HWPROBE_DEPS="lshw smartmontools nvme-cli hdparm pciutils usbutils dmidecode ethtool lsscsi iproute2"
8+
# Flag for callers (e.g., run.sh) to know if we installed hw-probe in this run.
9+
# shellcheck disable=SC2034 # used by run.sh; not read within this file
10+
HWPROBE_INSTALLED_THIS_RUN=${HWPROBE_INSTALLED_THIS_RUN:-0}
11+
export HWPROBE_INSTALLED_THIS_RUN
12+
13+
hwprobe_installed() { apt_pkg_installed "$HWPROBE_PKG"; }
14+
15+
# Mark if we installed hw-probe in this run (for optional uninstall)
16+
HWPROBE_INSTALLED_THIS_RUN=0
17+
18+
hwprobe_offline_ready_local() {
19+
if ! hwprobe_installed; then
20+
return 1
21+
fi
22+
for p in $HWPROBE_DEPS; do
23+
if ! apt_pkg_installed "$p"; then
24+
return 1
25+
fi
26+
done
27+
return 0
28+
}
29+
30+
hwprobe_install_latest() {
31+
apt_ensure_deps "$HWPROBE_DEPS" || return $?
32+
if ! hwprobe_installed; then
33+
apt_install_pkgs "$HWPROBE_PKG" || return $?
34+
# shellcheck disable=SC2034 # consumed by run.sh; assignment intentional
35+
HWPROBE_INSTALLED_THIS_RUN=1
36+
export HWPROBE_INSTALLED_THIS_RUN
37+
fi
38+
return 0
39+
}
40+
41+
hwprobe_install_version() {
42+
ver="$1"
43+
apt_ensure_deps "$HWPROBE_DEPS" || return $?
44+
if ! hwprobe_installed; then
45+
apt_install_pkg_version "$HWPROBE_PKG" "$ver" || return $?
46+
# shellcheck disable=SC2034 # consumed by run.sh; assignment intentional
47+
HWPROBE_INSTALLED_THIS_RUN=1
48+
export HWPROBE_INSTALLED_THIS_RUN
49+
else
50+
apt_install_pkg_version "$HWPROBE_PKG" "$ver" || return $?
51+
fi
52+
return 0
53+
}
54+
55+
hwprobe_update() { apt_upgrade_pkg "$HWPROBE_PKG"; }
56+
hwprobe_list_versions() { apt_list_versions "$HWPROBE_PKG"; }
57+
58+
hwprobe_build_local_cmd() {
59+
upload="$1"; out="$2"; extra="$3"
60+
cmd="hw-probe -all -save \"$out\""
61+
[ "$upload" = "yes" ] && cmd="$cmd -upload"
62+
[ -n "$extra" ] && cmd="$cmd $extra"
63+
printf '%s\n' "$cmd"
64+
}
65+
66+
_hwprobe_extract_txz() {
67+
saved="$1"; dest="$2"
68+
case "$saved" in
69+
*.txz|*.tar.xz) : ;;
70+
*) log_warn "Not a txz/tar.xz, skipping extract: $saved"; return 1 ;;
71+
esac
72+
[ -f "$saved" ] || { log_warn "Cannot extract: file not found: $saved"; return 1; }
73+
ensure_dir "$dest" || { log_warn "Cannot create extract dir: $dest"; return 1; }
74+
75+
if tar -tJf "$saved" >/dev/null 2>&1 && tar -xJf "$saved" -C "$dest"; then
76+
log_info "Extracted to: $dest"; return 0
77+
fi
78+
if command -v bsdtar >/dev/null 2>&1 && bsdtar -xf "$saved" -C "$dest"; then
79+
log_info "Extracted to: $dest"; return 0
80+
fi
81+
if command -v xz >/dev/null 2>&1 && xz -dc "$saved" | tar -xf - -C "$dest"; then
82+
log_info "Extracted to: $dest"; return 0
83+
fi
84+
log_warn "Failed to extract '$saved' (need tar -J, or bsdtar, or xz)."
85+
return 1
86+
}
87+
88+
hwprobe_run_local() {
89+
upload="$1"; out="$2"; extra="$3"; extract="$4"
90+
91+
ensure_dir "$out" || return 1
92+
93+
if ! hwprobe_installed; then
94+
if network_is_ok; then
95+
log_warn "hw-probe not installed; installing latest..."
96+
hwprobe_install_latest || return $?
97+
else
98+
log_skip "Offline: hw-probe not installed; skipping local run."
99+
return 2
100+
fi
101+
fi
102+
103+
if ! need_root_or_skip; then
104+
return 2
105+
fi
106+
107+
cmd="$(hwprobe_build_local_cmd "$upload" "$out" "$extra")"
108+
log_info "cmd(root): $cmd"
109+
tmp="${out%/}/.hw-probe-run-$(nowstamp).log"
110+
111+
sh -c "$SUDO $cmd" >"$tmp" 2>&1
112+
rc=$?
113+
cat "$tmp"
114+
115+
url="$(sed -n 's/^.*Probe URL: *\([^ ]*linux-hardware\.org[^ ]*\).*$/\1/p' "$tmp" | tail -n 1)"
116+
[ -n "$url" ] && log_info "Probe uploaded: $url"
117+
118+
saved="$(sed -n 's/^Saved to:[[:space:]]*//p' "$tmp" | tail -n 1)"
119+
if [ -z "$saved" ] || [ ! -f "$saved" ]; then
120+
newest="$(find "$out" -mindepth 1 -maxdepth 1 -type f -printf '%T@ %p\n' 2>/dev/null | sort -nr | head -n 1 | cut -d' ' -f2-)"
121+
[ -n "$newest" ] && saved="$newest"
122+
fi
123+
124+
if [ "$rc" -eq 0 ] && [ -n "$saved" ] && [ -f "$saved" ]; then
125+
log_info "Latest saved artifact: $saved"
126+
log_info "List: tar -tJf \"$saved\""
127+
log_info "Extract: mkdir -p \"$out/extracted\" && tar -xJf \"$saved\" -C \"$out/extracted\""
128+
if [ "$extract" = "yes" ]; then
129+
dest="${out%/}/extracted-$(nowstamp)"
130+
_hwprobe_extract_txz "$saved" "$dest" || true
131+
fi
132+
fi
133+
134+
log_info "Local report directory: $out"
135+
return "$rc"
136+
}
137+
138+
hwprobe_run_docker() {
139+
upload="$1"; out="$2"; extra="$3"; extract="$4"
140+
141+
ensure_dir "$out" || return 1
142+
OUT_ABS="$(cd "$out" 2>/dev/null && pwd)" || return 1
143+
144+
# Track whether docker existed before, so the caller can optionally uninstall.
145+
if docker_is_installed; then export __DOCKER_WAS_INSTALLED=1; else export __DOCKER_WAS_INSTALLED=0; fi
146+
147+
docker_install || return $?
148+
if ! docker_can_run; then
149+
log_skip "Docker present but cannot run (needs group or passwordless sudo)."
150+
return 2
151+
fi
152+
153+
DCMD="$(docker_cmd)"
154+
IMAGE="linuxhw/hw-probe"
155+
CNAME="hwprobe-$(nowstamp)-$$"
156+
157+
# Ensure we have the image (best-effort pull if online)
158+
if network_is_ok; then
159+
log_info "cmd: $DCMD pull $IMAGE || true"
160+
$DCMD pull "$IMAGE" || true
161+
else
162+
if ! docker_image_exists "$IMAGE"; then
163+
log_skip "Offline and Docker image '$IMAGE' not present locally; skipping docker run."
164+
return 2
165+
fi
166+
log_warn "Offline: skipping docker pull; using local image '$IMAGE'."
167+
fi
168+
169+
# Build a tiny script on the HOST, inside the bind mount, to avoid quoting issues.
170+
INNER="$OUT_ABS/.inner.sh"
171+
{
172+
echo 'set -ex'
173+
echo 'echo "--- container: whoami ---"; whoami || true'
174+
echo 'echo "--- container: uname -a ---"; uname -a || true'
175+
echo 'echo "--- container: pre-touch ---"; touch /out/__pre_touch_from_container || true'
176+
echo 'echo "--- container: hw-probe -V ---"; hw-probe -V || true'
177+
echo 'if [ -f /etc/alpine-release ] && command -v apk >/dev/null 2>&1; then'
178+
echo ' apk add --no-cache kmod-libs 2>/dev/null || true'
179+
echo 'fi'
180+
printf 'echo "--- container: run hw-probe ---"; env DDCUTIL_DISABLE=1 hw-probe -all -save /out -log-level maximal'
181+
[ "$upload" = "yes" ] && printf ' -upload'
182+
[ -n "$extra" ] && printf ' %s' "$extra"
183+
echo
184+
echo 'echo "--- container: list /out ---"; ls -la /out || true'
185+
echo 'echo "--- container: post-touch ---"; touch /out/__post_touch_from_container || true'
186+
echo 'echo "--- container: done ---"'
187+
} > "$INNER"
188+
chmod 755 "$INNER" 2>/dev/null || true
189+
190+
TS="$(nowstamp)"
191+
DLOG="${OUT_ABS%/}/.hw-probe-docker-${TS}.log"
192+
193+
# Preview exact docker run (multi-line, readable)
194+
log_info "cmd:"
195+
log_info " $DCMD run --name $CNAME \\"
196+
log_info " --privileged --net=host --pid=host \\"
197+
log_info " -v /dev:/dev \\"
198+
log_info " -v /sys:/sys:ro \\"
199+
log_info " -v /run/udev:/run/udev:ro \\"
200+
log_info " -v /lib/modules:/lib/modules:ro \\"
201+
log_info " -v /etc/os-release:/etc/os-release:ro \\"
202+
log_info " -v /var/log:/var/log:ro \\"
203+
log_info " -v \"$OUT_ABS\":/out \\"
204+
log_info " --entrypoint /bin/sh \\"
205+
log_info " \"$IMAGE\" -lc \"/out/.inner.sh 2>&1 | tee -a /out/container.log\""
206+
207+
# Run (no --rm so we can inspect/cp afterwards). Capture all stdout/stderr to DLOG.
208+
(
209+
echo "== docker run start: $(date -u) =="
210+
$DCMD run --name "$CNAME" \
211+
--privileged --net=host --pid=host \
212+
-v /dev:/dev \
213+
-v /sys:/sys:ro \
214+
-v /run/udev:/run/udev:ro \
215+
-v /lib/modules:/lib/modules:ro \
216+
-v /etc/os-release:/etc/os-release:ro \
217+
-v /var/log:/var/log:ro \
218+
-v "$OUT_ABS":/out \
219+
--entrypoint /bin/sh \
220+
"$IMAGE" -lc "/out/.inner.sh 2>&1 | tee -a /out/container.log"
221+
rc=$?
222+
echo "== docker run end: $(date -u) rc=$rc =="
223+
exit $rc
224+
) >"$DLOG" 2>&1
225+
226+
run_rc=$?
227+
228+
# Always show docker logs if available (helpful, but container.log is authoritative).
229+
log_info "--- docker logs ($CNAME) ---"
230+
$DCMD logs "$CNAME" 2>/dev/null | sed 's/^/[docker] /' || log_warn "No docker logs (possibly empty)."
231+
232+
# Show container state
233+
log_info "--- docker inspect state ($CNAME) ---"
234+
$DCMD inspect --format='[state] Status={{.State.Status}} ExitCode={{.State.ExitCode}} OOMKilled={{.State.OOMKilled}} Error={{.State.Error}}' "$CNAME" 2>/dev/null | sed 's/^/[inspect] /' || true
235+
236+
# Mirror our captured host log + the container.log written via tee inside container
237+
if [ -s "$DLOG" ]; then
238+
sed -e 's/^/[runner] /' "$DLOG" || true
239+
fi
240+
if [ -s "$OUT_ABS/container.log" ]; then
241+
log_info "--- container.log (host copy) ---"
242+
sed -e 's/^/[container] /' "$OUT_ABS/container.log" | tail -n 200 || true
243+
else
244+
log_warn "container.log not found in $OUT_ABS (script may not have run)."
245+
fi
246+
247+
# Locate artifacts on the host
248+
saved="$(find "$OUT_ABS" -maxdepth 1 -type f -name '*.txz' -printf '%T@ %p\n' 2>/dev/null | sort -nr | head -n1 | cut -d' ' -f2-)"
249+
pre_marker="$OUT_ABS/__pre_touch_from_container"
250+
post_marker="$OUT_ABS/__post_touch_from_container"
251+
252+
# If nothing visible on the host, try docker cp of /out
253+
if [ -z "$saved" ] && [ ! -e "$pre_marker" ] && [ ! -e "$post_marker" ]; then
254+
log_warn "No .txz and no markers on host. Trying docker cp fallback from container /out ..."
255+
TMP_EXTRACT="$OUT_ABS/.from_container_$TS"
256+
mkdir -p "$TMP_EXTRACT" 2>/dev/null || true
257+
if $DCMD cp "$CNAME":/out/. "$TMP_EXTRACT"/ >/dev/null 2>&1; then
258+
log_info "Copied /out from container to $TMP_EXTRACT"
259+
find "$TMP_EXTRACT" -mindepth 1 -maxdepth 1 -printf '%p\n' 2>/dev/null | sed 's/^/[cp] /' || true
260+
saved="$(find "$TMP_EXTRACT" -maxdepth 1 -type f -name '*.txz' -printf '%T@ %p\n' 2>/dev/null | sort -nr | head -n1 | cut -d' ' -f2-)"
261+
[ -n "$saved" ] && { mv -f "$saved" "$OUT_ABS"/ 2>/dev/null || true; saved="$OUT_ABS/$(basename "$saved")"; }
262+
for m in "$TMP_EXTRACT"/__pre_touch_from_container "$TMP_EXTRACT"/__post_touch_from_container; do
263+
if [ -e "$m" ]; then
264+
mv -f "$m" "$OUT_ABS"/ 2>/dev/null || true
265+
fi
266+
done
267+
if [ -s "$TMP_EXTRACT/container.log" ]; then
268+
cp -f "$TMP_EXTRACT/container.log" "$OUT_ABS"/ 2>/dev/null || true
269+
fi
270+
else
271+
log_warn "docker cp failed or /out was empty."
272+
fi
273+
fi
274+
275+
# Show marker status so we know if container could write to the mount
276+
if [ -e "$pre_marker" ] || [ -e "$post_marker" ]; then
277+
log_info "Mount sanity: pre_marker=$( [ -e "$pre_marker" ] && echo present || echo missing ), post_marker=$( [ -e "$post_marker" ] && echo present || echo missing )"
278+
fi
279+
280+
# Parse possible Probe URL (container.log usually has it)
281+
url=""
282+
[ -f "$OUT_ABS/container.log" ] && url="$(sed -n 's/^.*Probe URL: *\([^ ]*linux-hardware\.org[^ ]*\).*$/\1/p' "$OUT_ABS/container.log" | tail -n 1)"
283+
[ -z "$url" ] && url="$(sed -n 's/^.*Probe URL: *\([^ ]*linux-hardware\.org[^ ]*\).*$/\1/p' "$DLOG" | tail -n 1)"
284+
[ -n "$url" ] && log_info "Probe uploaded: $url"
285+
286+
# Tidy up container (image cleanup handled elsewhere if --uninstall yes)
287+
$DCMD rm -f "$CNAME" >/dev/null 2>&1 || true
288+
289+
# Best-effort chown to current user so artifacts aren’t root-owned on host
290+
if command -v id >/dev/null 2>&1; then
291+
uid="$(id -u 2>/dev/null || echo)"
292+
gid="$(id -g 2>/dev/null || echo)"
293+
if [ -n "$uid" ] && [ -n "$gid" ]; then
294+
if need_root_or_skip; then
295+
log_info "Fixing ownership of $OUT_ABS -> $uid:$gid"
296+
sh -c "$SUDO chown -R \"$uid\":\"$gid\" \"$OUT_ABS\" 2>/dev/null || true"
297+
else
298+
log_warn "Outputs are root-owned. re-run with sudo to chown, or copy elsewhere."
299+
fi
300+
fi
301+
fi
302+
303+
if [ -n "$saved" ] && [ -f "$saved" ]; then
304+
log_info "Latest saved artifact: $saved"
305+
log_info "List: tar -tJf \"$saved\""
306+
log_info "Extract: mkdir -p \"$OUT_ABS/extracted\" && tar -xJf \"$saved\" -C \"$OUT_ABS/extracted\""
307+
if [ "$extract" = "yes" ]; then
308+
dest="${OUT_ABS%/}/extracted-$(nowstamp)"
309+
_hwprobe_extract_txz "$saved" "$dest" || true
310+
fi
311+
log_info "Docker run complete. Report directory: $OUT_ABS"
312+
return 0
313+
fi
314+
315+
log_fail "No .txz artifact produced (docker run rc=$run_rc)."
316+
log_info "Docker run complete. Report directory: $OUT_ABS"
317+
return 1
318+
}
319+
320+
# ---- New: uninstall hw-probe package ----
321+
hwprobe_uninstall_pkg() {
322+
if ! hwprobe_installed; then
323+
return 0
324+
fi
325+
apt_remove_pkgs "$HWPROBE_PKG" || return $?
326+
apt_autoremove || true
327+
return 0
328+
}
329+
330+
hwprobe_uninstall() {
331+
if apt_pkg_installed "$HWPROBE_PKG"; then
332+
need_root
333+
log_info "cmd(root): apt-get remove -y $HWPROBE_PKG"
334+
sh -c "$SUDO DEBIAN_FRONTEND=noninteractive apt-get remove -y $HWPROBE_PKG"
335+
else
336+
log_info "hw-probe not installed; nothing to uninstall."
337+
fi
338+
}
339+
340+
docker_image_prune_hwprobe() {
341+
DCMD="$(docker_cmd)"
342+
if docker_image_exists "linuxhw/hw-probe"; then
343+
log_info "Cleaning up Docker image linuxhw/hw-probe (best-effort)"
344+
# shellcheck disable=SC2086
345+
$DCMD rmi -f linuxhw/hw-probe >/dev/null 2>&1 || true
346+
fi
347+
}

0 commit comments

Comments
 (0)