From ac0cbd09bf188f76d7065bb2ea04c4514efc7584 Mon Sep 17 00:00:00 2001 From: Christopher <1289128+dragonfire1119@users.noreply.github.com> Date: Fri, 28 Nov 2025 09:42:08 -0600 Subject: [PATCH] Add more timeouts (#69) * Add timeout handling for dpkg queries Updated run.sh to use 'timeout' for all dpkg -l queries to prevent script hangs if the dpkg database is locked. Enhanced test-script.sh with a new test_dpkg_hang function and integrated it into the bug fix test suite to verify proper timeout handling for dpkg database hangs. * Update script version to 2025.11.1 Bump displayed version in casaos-fix-docker-api-version/run.sh from 2025.11.0 to 2025.11.1 for consistency across script output and help messages. * Refactor variable assignment for POSIX compliance Updated variable assignment in run.sh and test-script.sh to use separate declaration and assignment lines, improving POSIX shell compatibility and readability. * Improve test-script.sh mock dpkg usage and variable handling Refactored test_dpkg_hang to declare local variables upfront, avoid SC2155 issues, and use explicit paths for the mock dpkg command. Updated PATH handling and hash table clearing to ensure the mock is used, and improved the test script to override dpkg for more reliable testing. * Remove local keyword from test_script variable The 'local' keyword was removed from the 'test_script' variable declaration in test-script.sh to ensure compatibility and correct scoping outside of functions. --- casaos-fix-docker-api-version/run.sh | 25 +-- casaos-fix-docker-api-version/test-script.sh | 187 ++++++++++++++++++- 2 files changed, 194 insertions(+), 18 deletions(-) diff --git a/casaos-fix-docker-api-version/run.sh b/casaos-fix-docker-api-version/run.sh index 70a1fa6..2846dc1 100755 --- a/casaos-fix-docker-api-version/run.sh +++ b/casaos-fix-docker-api-version/run.sh @@ -59,7 +59,7 @@ print_info() { } echo "==========================================" -echo "BigBear CasaOS Docker Version Fix Script 2025.11.0" +echo "BigBear CasaOS Docker Version Fix Script 2025.11.1" echo "==========================================" echo "" echo "Here are some links:" @@ -90,7 +90,7 @@ display_versions() { # Also show installed package versions for clarity echo "Installed Docker packages:" - dpkg -l | grep -E "docker-ce|containerd.io" | awk '{print $2, $3}' 2>/dev/null || echo "Unable to query package versions" + timeout 10 dpkg -l 2>/dev/null | grep -E "docker-ce|containerd.io" | awk '{print $2, $3}' || echo "Unable to query package versions" echo "" else echo "Docker command not found" @@ -317,8 +317,9 @@ check_containerd_version() { return 1 fi - # Get installed version - local version=$(dpkg -l | grep containerd.io | awk '{print $3}' | head -n1) + # Get installed version (use timeout to prevent hanging if dpkg is locked) + local version + version=$(timeout 10 dpkg -l 2>/dev/null | grep containerd.io | awk '{print $3}' | head -n1) echo "$version" return 0 } @@ -961,8 +962,8 @@ remove_standalone_docker_compose() { if command -v docker-compose &>/dev/null; then echo "Standalone docker-compose found. Checking installation method..." - # Check if docker-compose was installed via package manager - if dpkg -l | grep -qw docker-compose 2>/dev/null; then + # Check if docker-compose was installed via package manager (use timeout to prevent hang) + if timeout 10 dpkg -l 2>/dev/null | grep -qw docker-compose; then echo "Removing docker-compose installed via package manager..." $SUDO apt-get remove -y docker-compose else @@ -1085,7 +1086,7 @@ downgrade_docker() { echo "" # Check if Docker is already installed and remove it to ensure clean downgrade - if dpkg -l | grep -qE "docker-ce|containerd.io"; then + if timeout 10 dpkg -l 2>/dev/null | grep -qE "docker-ce|containerd.io"; then echo "Removing existing Docker packages to ensure clean installation..." # Stop Docker services before removal @@ -1572,7 +1573,7 @@ main() { case "$1" in apply-override|override) echo "==========================================" - echo "BigBear CasaOS Docker Version Fix Script 2025.11.0" + echo "BigBear CasaOS Docker Version Fix Script 2025.11.1" echo "==========================================" echo "" apply_docker_api_override @@ -1580,7 +1581,7 @@ main() { ;; remove-override|no-override) echo "==========================================" - echo "BigBear CasaOS Docker Version Fix Script 2025.11.0" + echo "BigBear CasaOS Docker Version Fix Script 2025.11.1" echo "==========================================" echo "" remove_docker_api_override @@ -1588,7 +1589,7 @@ main() { ;; help|--help|-h) echo "==========================================" - echo "BigBear CasaOS Docker Version Fix Script 2025.11.0" + echo "BigBear CasaOS Docker Version Fix Script 2025.11.1" echo "==========================================" echo "" show_usage @@ -2021,7 +2022,7 @@ main() { echo "==========================================" echo "" echo "Installed Docker Package Versions:" - dpkg -l | grep -E "docker-ce|containerd.io" | awk '{print " " $2 " = " $3}' 2>/dev/null || echo "Unable to query package versions" + timeout 10 dpkg -l 2>/dev/null | grep -E "docker-ce|containerd.io" | awk '{print " " $2 " = " $3}' || echo "Unable to query package versions" echo "" echo "Docker Version Information:" timeout 10 $SUDO docker version 2>&1 || echo "Unable to get Docker version" @@ -2077,7 +2078,7 @@ main() { # Check installed packages echo "4. Installed Docker packages:" - dpkg -l | grep -E "docker-ce|containerd.io" | awk '{print " " $2 " = " $3}' 2>/dev/null || echo " Unable to query packages" + timeout 10 dpkg -l 2>/dev/null | grep -E "docker-ce|containerd.io" | awk '{print " " $2 " = " $3}' || echo " Unable to query packages" echo "" # Check Docker service status diff --git a/casaos-fix-docker-api-version/test-script.sh b/casaos-fix-docker-api-version/test-script.sh index 50976e9..60c8235 100755 --- a/casaos-fix-docker-api-version/test-script.sh +++ b/casaos-fix-docker-api-version/test-script.sh @@ -1291,6 +1291,7 @@ show_usage() { echo " test-snap - Test Snap daemon hang handling (timeout check)" echo " test-netns - Test network namespace cleanup (device busy error)" echo " test-casaos - Test CasaOS version check hang handling (timeout check)" + echo " test-dpkg - Test dpkg database hang handling (timeout check)" echo " test-bugfixes - Run all bug fix tests" echo "" echo "Alternative fix for newer distros (Ubuntu 24.04+, Debian trixie):" @@ -1316,6 +1317,7 @@ show_usage() { echo " $0 test-snap # Test Snap hang handling" echo " $0 test-netns # Test network namespace cleanup" echo " $0 test-casaos # Test CasaOS version check hang handling" + echo " $0 test-dpkg # Test dpkg database hang handling" echo " $0 test-bugfixes # Run all bug fix tests" echo "" echo "Alternative fix (for distros without Docker 28.x):" @@ -1592,6 +1594,159 @@ TESTEOF return 0 } +# Function to test dpkg database hang handling +test_dpkg_hang() { + print_header "Testing dpkg Database Hang Handling" + print_info "This test verifies that the script handles a hanging 'dpkg -l' command" + print_info "It should timeout in 10 seconds and not hang forever" + echo "" + + # Declare local variables upfront to avoid SC2155 issues with $? + local mock_dir old_path which_dpkg start_time exit_code end_time duration output test_script + + # Create mock dpkg command + mock_dir="/tmp/mock-dpkg-$$" + mkdir -p "$mock_dir" + + cat > "$mock_dir/dpkg" << 'EOF' +#!/bin/sh +# Simulate dpkg database lock (hangs waiting for lock) +sleep 20 +echo "docker-ce 28.0.0 amd64 Docker Engine" +exit 0 +EOF + chmod +x "$mock_dir/dpkg" + + # Save PATH and update it + old_path="$PATH" + export PATH="$mock_dir:$PATH" + # Clear bash's command hash table so it finds our mock + hash -r + + print_info "Testing: timeout 10 dpkg -l (using mock at $mock_dir/dpkg)" + + # Verify mock is being used + which_dpkg=$(which dpkg 2>/dev/null) + print_info "Using dpkg at: $which_dpkg" + + # Test the timeout command directly using explicit path to mock + start_time=$(date +%s) + timeout 10 "$mock_dir/dpkg" -l >/dev/null 2>&1 + exit_code=$? + end_time=$(date +%s) + duration=$((end_time - start_time)) + + # Restore PATH + export PATH="$old_path" + hash -r + + print_info "Command completed in $duration seconds with exit code $exit_code" + + # Analyze results + if [ $exit_code -eq 124 ]; then + print_success "✓ Timeout occurred (exit code 124)" + else + print_error "✗ Expected exit code 124 (timeout), got $exit_code" + rm -rf "$mock_dir" + return 1 + fi + + if [ $duration -le 12 ]; then + print_success "✓ Timeout triggered in $duration seconds (expected ~10s)" + else + print_error "✗ Timeout took $duration seconds (expected ~10s)" + rm -rf "$mock_dir" + return 1 + fi + + # Now test the actual function behavior by sourcing and calling it + print_info "Testing actual display_versions function..." + + # Extract just the function from run.sh - but use explicit mock path + test_script="/tmp/test-dpkg-func-$$.sh" + cat > "$test_script" << TESTEOF +#!/bin/bash +set -o pipefail + +SUDO="" +if [ "\$EUID" -ne 0 ]; then + SUDO="sudo" +fi + +# Override dpkg to use our mock +dpkg() { + "$mock_dir/dpkg" "\$@" +} + +# Function to display current versions (simplified for testing) +display_versions() { + echo "Current Docker versions:" + if command -v docker &>/dev/null; then + echo "(docker version output skipped for test)" + echo "" + + # Also show installed package versions for clarity + echo "Installed Docker packages:" + timeout 10 dpkg -l 2>/dev/null | grep -E "docker-ce|containerd.io" | awk '{print \$2, \$3}' || echo "Unable to query package versions" + echo "" + else + echo "Docker command not found" + echo "" + fi +} + +echo "Calling display_versions..." +display_versions +TESTEOF + + chmod +x "$test_script" + + # Create a mock docker command too (just to pass the command -v check) + cat > "$mock_dir/docker" << 'EOF' +#!/bin/sh +echo "Docker version 28.0.0" +exit 0 +EOF + chmod +x "$mock_dir/docker" + + # Run with mocked dpkg (and docker for the command -v check) + export PATH="$mock_dir:$PATH" + hash -r + start_time=$(date +%s) + output=$($test_script 2>&1) + end_time=$(date +%s) + duration=$((end_time - start_time)) + export PATH="$old_path" + hash -r + + print_info "Function test completed in $duration seconds" + + if [ $duration -le 12 ]; then + print_success "✓ Function completed in $duration seconds (expected ~10s)" + else + print_error "✗ Function took $duration seconds (expected ~10s)" + echo "Output: $output" + rm -rf "$mock_dir" "$test_script" + return 1 + fi + + if echo "$output" | grep -q "Unable to query package versions"; then + print_success "✓ Function detected timeout and printed fallback message" + else + print_error "✗ Function did not handle timeout correctly" + echo "Output: $output" + rm -rf "$mock_dir" "$test_script" + return 1 + fi + + # Clean up + rm -rf "$mock_dir" "$test_script" + + print_header "Test Result: PASSED" + print_success "dpkg database hang handling works correctly" + return 0 +} + # Function to run all bug fix tests test_all_bugfixes() { print_header "Running All Bug Fix Tests" @@ -1602,13 +1757,14 @@ test_all_bugfixes() { print_info " 4. Network namespace cleanup" print_info " 5. Unresponsive Docker daemon handling" print_info " 6. CasaOS version check hang handling" + print_info " 7. dpkg database hang handling" echo "" local results=() local failed=0 # Test GPG conflicts - print_info "=== Test 1 of 6: GPG Key Conflicts ===" + print_info "=== Test 1 of 7: GPG Key Conflicts ===" echo "" if test_gpg_key_conflicts; then results+=("✓ GPG key conflict handling: PASSED") @@ -1624,7 +1780,7 @@ test_all_bugfixes() { fi # Test GPG download resilience - print_info "=== Test 2 of 6: GPG Download Resilience ===" + print_info "=== Test 2 of 7: GPG Download Resilience ===" echo "" if test_gpg_download_resilience; then results+=("✓ GPG download resilience: PASSED") @@ -1640,7 +1796,7 @@ test_all_bugfixes() { fi # Test Snap hang - print_info "=== Test 3 of 6: Snap Daemon Hang ===" + print_info "=== Test 3 of 7: Snap Daemon Hang ===" echo "" if test_snap_hang; then results+=("✓ Snap daemon hang handling: PASSED") @@ -1656,7 +1812,7 @@ test_all_bugfixes() { fi # Test netns cleanup - print_info "=== Test 4 of 6: Network Namespace Cleanup ===" + print_info "=== Test 4 of 7: Network Namespace Cleanup ===" echo "" if test_netns_cleanup; then results+=("✓ Network namespace cleanup: PASSED") @@ -1672,7 +1828,7 @@ test_all_bugfixes() { fi # Test unresponsive daemon - print_info "=== Test 5 of 6: Unresponsive Docker Daemon ===" + print_info "=== Test 5 of 7: Unresponsive Docker Daemon ===" echo "" if test_unresponsive_daemon; then results+=("✓ Unresponsive daemon handling: PASSED") @@ -1688,7 +1844,7 @@ test_all_bugfixes() { fi # Test CasaOS hang - print_info "=== Test 6 of 6: CasaOS Version Check Hang ===" + print_info "=== Test 6 of 7: CasaOS Version Check Hang ===" echo "" if test_casaos_hang; then results+=("✓ CasaOS version check hang handling: PASSED") @@ -1697,6 +1853,22 @@ test_all_bugfixes() { failed=$((failed + 1)) fi + echo "" + if [ "$NON_INTERACTIVE" != "true" ]; then + read -p "Press Enter to continue to next test..." -r + echo "" + fi + + # Test dpkg hang + print_info "=== Test 7 of 7: dpkg Database Hang ===" + echo "" + if test_dpkg_hang; then + results+=("✓ dpkg database hang handling: PASSED") + else + results+=("✗ dpkg database hang handling: FAILED") + failed=$((failed + 1)) + fi + # Print summary print_header "Bug Fix Test Summary" echo "Results:" @@ -1761,6 +1933,9 @@ main() { test-casaos|casaos|test-casaos-hang) test_casaos_hang ;; + test-dpkg|dpkg|test-dpkg-hang) + test_dpkg_hang + ;; test-bugfixes|bugfixes|bug-fixes) test_all_bugfixes ;;