Improved debugging and error checking using xmlstarlet

main
Anthony 2025-03-28 01:20:04 +08:00
parent 8533225d00
commit 4505179db6
2 changed files with 173 additions and 37 deletions

View File

@ -349,35 +349,63 @@ verify_cleanup_state() {
validate_xml_structure() {
local file="$1"
local validation_errors=0
# Check for basic XML structure
if ! xmllint --noout "$file" 2>/dev/null; then
# Check for basic XML structure with detailed error reporting
if ! xmlstarlet val --err --well-formed "$file" 2> >(while IFS= read -r line; do
log_error "XML Structure Error: $line"
validation_errors=$((validation_errors + 1))
done); then
log_error "Invalid XML structure in $file"
return 1
validation_errors=$((validation_errors + 1))
fi
# Check for required root elements
if ! xmlstarlet sel -t -v "//httpServerConfig" "$file" >/dev/null 2>&1; then
if ! xmlstarlet sel -Q -t -v "//httpServerConfig" "$file" >/dev/null 2>&1; then
log_error "Missing httpServerConfig root element in $file"
return 1
validation_errors=$((validation_errors + 1))
fi
# Check for required sections
# Check for required sections with detailed reporting
local required_sections=("listenerList" "virtualHostList")
for section in "${required_sections[@]}"; do
if ! xmlstarlet sel -t -v "//$section" "$file" >/dev/null 2>&1; then
if ! xmlstarlet sel -Q -t -v "//$section" "$file" >/dev/null 2>&1; then
log_error "Missing required section '$section' in $file"
return 1
# Additional check for malformed section
if xmlstarlet sel -Q -t -c "//*[contains(name(), '$section')]" "$file" >/dev/null 2>&1; then
log_error "Found malformed <$section> element (possibly with incorrect case or attributes)"
fi
validation_errors=$((validation_errors + 1))
fi
done
# Check for orphaned elements
if xmlstarlet sel -t -v "//vhostMapList[not(parent::listener)]" "$file" >/dev/null 2>&1; then
# Check for orphaned elements with detailed reporting
if xmlstarlet sel -Q -t -v "//vhostMapList[not(parent::listener)]" "$file" >/dev/null 2>&1; then
log_error "Found orphaned vhostMapList elements in $file"
return 1
# Report the orphaned elements
xmlstarlet sel -Q -t -m "//vhostMapList[not(parent::listener)]" -v "concat('Orphaned vhostMapList: ', .)" -n "$file" 2>/dev/null | while read -r line; do
log_error "$line"
done
validation_errors=$((validation_errors + 1))
fi
return 0
# Check for orphaned listeners
if xmlstarlet sel -Q -t -v "//listener[not(parent::listenerList)]" "$file" >/dev/null 2>&1; then
log_error "Found orphaned listener elements in $file"
# Report the orphaned listeners
xmlstarlet sel -Q -t -m "//listener[not(parent::listenerList)]" -v "concat('Orphaned Listener: ', name)" -n "$file" 2>/dev/null | while read -r line; do
log_error "$line"
done
validation_errors=$((validation_errors + 1))
fi
if [[ $validation_errors -eq 0 ]]; then
log_success "XML structure validation passed for '$file'"
return 0
else
log_error "Found $validation_errors XML structure issue(s) in '$file'"
return 1
fi
}
cleanup_xml() {
@ -424,14 +452,18 @@ cleanup_xml() {
validate_xml() {
log "Validating XML configuration..."
# First check with xmlstarlet
if ! sudo xmlstarlet val --well-formed "$CONF_FILE" >/dev/null 2>&1; then
# First check with xmlstarlet with detailed error reporting
if ! xmlstarlet val --err --well-formed "$CONF_FILE" 2> >(while IFS= read -r line; do
log_error "XML Validation Error: $line"
done); then
log "❌ ERROR: XML configuration is not well-formed according to xmlstarlet. Check backups."
return 1
fi
# Then check with xmllint for additional validation
if ! sudo xmllint --noout "$CONF_FILE" 2>/dev/null; then
if ! xmllint --noout "$CONF_FILE" 2> >(while IFS= read -r line; do
log_error "XML Validation Error (xmllint): $line"
done); then
log "❌ ERROR: XML configuration is invalid according to xmllint. Check backups."
return 1
fi

View File

@ -303,6 +303,115 @@ apply_and_restart() {
fi
}
validate_xml_structure() {
local file_to_check="$1"
log_verbose "Performing comprehensive XML validation on '$file_to_check'..."
local validation_errors=0
# 1. Check if file is well-formed with detailed error reporting
if ! xmlstarlet val --err --well-formed "$file_to_check" 2> >(while IFS= read -r line; do
log_error "XML Validation Error: $line"
validation_errors=$((validation_errors + 1))
done); then
log_error "File '$file_to_check' is not well-formed XML"
validation_errors=$((validation_errors + 1))
fi
# 2. Check for required root elements
if ! xmlstarlet sel -Q -t -c "//httpServerConfig" "$file_to_check" 2>/dev/null; then
log_error "Missing required root element <httpServerConfig>"
validation_errors=$((validation_errors + 1))
fi
# 3. Check for required sections with detailed error reporting
local required_sections=("listenerList" "virtualHostList" "extProcessorList")
for section in "${required_sections[@]}"; do
if ! xmlstarlet sel -Q -t -c "//$section" "$file_to_check" 2>/dev/null; then
log_error "Missing required section <$section>"
# Additional check for malformed section
if xmlstarlet sel -Q -t -c "//*[contains(name(), '$section')]" "$file_to_check" 2>/dev/null; then
log_error "Found malformed <$section> element (possibly with incorrect case or attributes)"
fi
validation_errors=$((validation_errors + 1))
fi
done
# 4. Check for orphaned elements with detailed reporting
if xmlstarlet sel -Q -t -c "//listener[not(parent::listenerList)]" "$file_to_check" 2>/dev/null; then
log_error "Found orphaned <listener> elements"
# Report the orphaned listener names
xmlstarlet sel -Q -t -m "//listener[not(parent::listenerList)]" -v "concat('Orphaned Listener: ', name)" -n "$file_to_check" 2>/dev/null | while read -r line; do
log_error "$line"
done
validation_errors=$((validation_errors + 1))
fi
if xmlstarlet sel -Q -t -c "//virtualHost[not(parent::virtualHostList)]" "$file_to_check" 2>/dev/null; then
log_error "Found orphaned <virtualHost> elements"
# Report the orphaned virtualHost names
xmlstarlet sel -Q -t -m "//virtualHost[not(parent::virtualHostList)]" -v "concat('Orphaned VirtualHost: ', name)" -n "$file_to_check" 2>/dev/null | while read -r line; do
log_error "$line"
done
validation_errors=$((validation_errors + 1))
fi
# 5. Validate listener structure with detailed reporting
xmlstarlet sel -Q -t -m "//listener" -v "concat('Checking listener: ', name)" -n "$file_to_check" 2>/dev/null | while read -r listener; do
if [[ -n "$listener" ]]; then
log_verbose "$listener"
# Check if listener has required attributes
if ! xmlstarlet sel -Q -t -c "//listener[name='${listener#Checking listener: }']/address" "$file_to_check" 2>/dev/null; then
log_error "Listener '${listener#Checking listener: }' is missing required <address> element"
validation_errors=$((validation_errors + 1))
fi
# Additional check for SSL listeners
if [[ "${listener#Checking listener: }" == HTTPS-* ]]; then
if ! xmlstarlet sel -Q -t -c "//listener[name='${listener#Checking listener: }']/certFile" "$file_to_check" 2>/dev/null; then
log_error "HTTPS Listener '${listener#Checking listener: }' is missing required <certFile> element"
validation_errors=$((validation_errors + 1))
fi
if ! xmlstarlet sel -Q -t -c "//listener[name='${listener#Checking listener: }']/keyFile" "$file_to_check" 2>/dev/null; then
log_error "HTTPS Listener '${listener#Checking listener: }' is missing required <keyFile> element"
validation_errors=$((validation_errors + 1))
fi
fi
fi
done
# Return result
if [[ "$validation_errors" -eq 0 ]]; then
log_success "XML structure validation passed for '$file_to_check'"
return 0
else
log_error "Found $validation_errors XML structure issue(s) in '$file_to_check'"
return 1
fi
}
cleanup_xml() {
local file_to_clean="$1"
log "Performing XML cleanup on '$file_to_clean'..."
# 1. Remove empty nodes
xmlstarlet ed -L -d '//*[not(node())]' "$file_to_clean" || log "Warning: Failed to remove empty nodes"
# 2. Remove orphaned elements
xmlstarlet ed -L -d '//listener[not(parent::listenerList)]' "$file_to_clean" || log "Warning: Failed to remove orphaned listeners"
xmlstarlet ed -L -d '//virtualHost[not(parent::virtualHostList)]' "$file_to_clean" || log "Warning: Failed to remove orphaned virtualHosts"
# 3. Clean up whitespace
xmlstarlet ed -L -d '//text()[normalize-space()=""]' "$file_to_clean" || log "Warning: Failed to clean whitespace"
# 4. Validate after cleanup
if ! validate_xml_structure "$file_to_clean"; then
log_error "XML validation failed after cleanup"
return 1
fi
log "XML cleanup completed for '$file_to_clean'"
return 0
}
# === Main Script Logic ===
main() {
# Setup logging first
@ -324,30 +433,28 @@ main() {
log "Backing up '$CONF_FILE' to '$BACKUP_FILE'..."
sudo cp -a "$CONF_FILE" "$BACKUP_FILE"
# Initial Validation
log_verbose "Running initial xmllint validation on '$CONF_FILE'..."
if sudo xmllint --noout "$CONF_FILE" 2>/dev/null; then
log_success "Initial Check: '$CONF_FILE' is valid XML"
INITIAL_FILE_VALID=1
# Initial Validation using xmlstarlet
log_verbose "Running initial xmlstarlet validation on '$CONF_FILE'..."
if xmlstarlet val --well-formed "$CONF_FILE" 2>/dev/null; then
log_success "Initial Check: '$CONF_FILE' is well-formed XML"
if validate_xml_structure "$CONF_FILE"; then
log_success "Initial Check: '$CONF_FILE' has valid structure"
INITIAL_FILE_VALID=1
else
log_error "Initial Check: '$CONF_FILE' has structural issues"
fi
else
log_error "Initial Check: '$CONF_FILE' is invalid XML. Will attempt recovery/cleanup"
log_error "Initial Check: '$CONF_FILE' is not well-formed XML"
fi
# Recovery Attempt
if [[ "$INITIAL_FILE_VALID" -eq 0 ]]; then
log "Attempting automatic recovery with 'xmllint --recover'..."
sudo rm -f "$RECOVERY_TMP_FILE"
if xmllint --recover "$CONF_FILE" 2>/dev/null > "$RECOVERY_TMP_FILE"; then
if sudo xmllint --noout "$RECOVERY_TMP_FILE" 2>/dev/null; then
log_success "'xmllint --recover' produced structurally valid XML output"
RECOVERY_PRODUCED_VALID_XML=1
else
log_error "'xmllint --recover' ran but the output is STILL invalid XML. Discarding recovery"
sudo rm -f "$RECOVERY_TMP_FILE"
fi
log "Attempting XML cleanup..."
if cleanup_xml "$CONF_FILE"; then
log_success "XML cleanup completed successfully"
INITIAL_FILE_VALID=1
else
log_error "'xmllint --recover' command failed or produced errors. Discarding recovery"
sudo rm -f "$RECOVERY_TMP_FILE"
log_error "XML cleanup failed"
fi
fi
@ -361,11 +468,8 @@ main() {
if [[ "$INITIAL_FILE_VALID" -eq 1 ]]; then
FINAL_CONFIG_SOURCE_FILE="$CONF_FILE"
FINAL_CONFIG_SOURCE_DESC="original"
elif [[ "$RECOVERY_PRODUCED_VALID_XML" -eq 1 ]]; then
FINAL_CONFIG_SOURCE_FILE="$RECOVERY_TMP_FILE"
FINAL_CONFIG_SOURCE_DESC="recovered"
else
log_error "Neither original nor recovered file is valid. Falling back to default or minimal config"
log_error "Neither original nor cleaned file is valid. Falling back to default or minimal config"
FINAL_EXIT_CODE=2
fi