Improved debugging and error checking using xmlstarlet
parent
8533225d00
commit
4505179db6
|
@ -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
|
||||
|
||||
# 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
|
||||
|
|
|
@ -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 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' is invalid XML. Will attempt recovery/cleanup"
|
||||
log_error "Initial Check: '$CONF_FILE' has structural issues"
|
||||
fi
|
||||
else
|
||||
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
|
||||
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' ran but the output is STILL invalid XML. Discarding recovery"
|
||||
sudo rm -f "$RECOVERY_TMP_FILE"
|
||||
fi
|
||||
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
|
||||
|
||||
|
|
Loading…
Reference in New Issue