Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-04-05 08:35:41

0001 #!/usr/bin/env python3
0002 """
0003 Script to validate ROOT files and check for corruption.
0004 """
0005 
0006 import sys
0007 import argparse
0008 from pathlib import Path
0009 
0010 try:
0011     import ROOT
0012 except ImportError:
0013     print("ERROR: ROOT/PyROOT is not available. Please ensure ROOT is installed and properly configured.")
0014     sys.exit(1)
0015 
0016 
0017 def validate_rootfile(filepath):
0018     """
0019     Validate a ROOT file for corruption.
0020 
0021     Performs the following checks (ALL must pass for file to be valid):
0022     1. File exists on filesystem
0023     2. File is not empty (size > 0)
0024     3. File can be opened by ROOT
0025     4. File is not a zombie (corrupted header)
0026     5. File is open for reading
0027     6. File was not recovered via kRecovered bit (indicates improper closure)
0028     7. File contains at least one key/object
0029     8. All objects in file can be read
0030 
0031     Args:
0032         filepath: Path to the ROOT file
0033 
0034     Returns:
0035         tuple: (is_valid, message, checks_passed)
0036             - is_valid: True if all checks passed
0037             - message: Success or error message
0038             - checks_passed: dict with individual check results
0039     """
0040     filepath = Path(filepath)
0041     checks = {
0042         "file_exists": False,
0043         "non_empty": False,
0044         "can_open": False,
0045         "not_zombie": False,
0046         "is_open": False,
0047         "not_recovered_bit": False,
0048         "has_keys": False,
0049         "objects_readable": False
0050     }
0051 
0052     errors = []
0053     tfile = None
0054 
0055     # Check 1: File exists
0056     if filepath.exists():
0057         checks["file_exists"] = True
0058     else:
0059         errors.append(f"File does not exist: {filepath}")
0060 
0061     # Check 2: File is not empty (only if it exists)
0062     if checks["file_exists"]:
0063         if filepath.stat().st_size > 0:
0064             checks["non_empty"] = True
0065         else:
0066             errors.append(f"File is empty: {filepath}")
0067 
0068     # Check 3: File can be opened by ROOT (only if previous checks passed)
0069     if checks["file_exists"] and checks["non_empty"]:
0070         try:
0071             tfile = ROOT.TFile.Open(str(filepath))
0072             if tfile:
0073                 checks["can_open"] = True
0074             else:
0075                 errors.append(f"Failed to open file: {filepath}")
0076         except (OSError, Exception) as e:
0077             errors.append(f"Failed to open file: {filepath} ({str(e)})")
0078 
0079     # Check 4: File is not a zombie (only if file opened)
0080     if tfile and checks["can_open"]:
0081         if not tfile.IsZombie():
0082             checks["not_zombie"] = True
0083         else:
0084             errors.append(f"File is zombie (corrupted): {filepath}")
0085 
0086     # Check 5: File is open for reading (only if file opened and not zombie)
0087     if tfile and checks["can_open"] and checks["not_zombie"]:
0088         if tfile.IsOpen():
0089             checks["is_open"] = True
0090         else:
0091             errors.append(f"File is not open: {filepath}")
0092 
0093     # Check 6: File was not recovered (kRecovered bit check - only if file is open)
0094     if tfile and checks["is_open"]:
0095         if not tfile.TestBit(ROOT.TFile.kRecovered):
0096             checks["not_recovered_bit"] = True
0097         else:
0098             errors.append("File was recovered (it was likely not closed properly)")
0099 
0100     # Check 8: File contains at least one key/object (only if file is open)
0101     if tfile and checks["is_open"]:
0102         keys = tfile.GetListOfKeys()
0103         if keys and keys.GetEntries() > 0:
0104             checks["has_keys"] = True
0105         else:
0106             errors.append(f"File contains no keys/objects: {filepath}")
0107 
0108     # Check 9: All objects in file can be read (only if file has keys)
0109     if tfile and checks["has_keys"]:
0110         keys = tfile.GetListOfKeys()
0111         all_readable = True
0112         for key in keys:
0113             obj = key.ReadObj()
0114             if not obj:
0115                 errors.append(f"Failed to read object '{key.GetName()}' from file: {filepath}")
0116                 all_readable = False
0117                 break
0118         if all_readable:
0119             checks["objects_readable"] = True
0120 
0121     # Close file if it was opened
0122     if tfile:
0123         tfile.Close()
0124 
0125     # Determine if file is valid
0126     if all(checks.values()):
0127         return True, "All validation checks passed", checks
0128     else:
0129         # Return the first error message, or a generic message if no specific error
0130         error_msg = errors[0] if errors else "Some validation checks failed"
0131         return False, error_msg, checks
0132 
0133 
0134 def main():
0135     parser = argparse.ArgumentParser(
0136         description="Validate ROOT files for corruption",
0137         formatter_class=argparse.RawDescriptionHelpFormatter
0138     )
0139     parser.add_argument(
0140         "files",
0141         nargs="+",
0142         help="ROOT file(s) to validate"
0143     )
0144     parser.add_argument(
0145         "-v", "--verbose",
0146         action="store_true",
0147         help="Verbose output"
0148     )
0149     parser.add_argument(
0150         "-q", "--quiet",
0151         action="store_true",
0152         help="Only report invalid files"
0153     )
0154 
0155     args = parser.parse_args()
0156 
0157     # Suppress ROOT messages unless verbose
0158     if not args.verbose:
0159         ROOT.gErrorIgnoreLevel = ROOT.kError
0160 
0161     all_valid = True
0162     results = []
0163 
0164     for filepath in args.files:
0165         is_valid, message, checks = validate_rootfile(filepath)
0166         results.append((filepath, is_valid, message, checks))
0167 
0168         if not is_valid:
0169             all_valid = False
0170             print(f"❌ INVALID: {filepath}")
0171             print(f"   Reason: {message}")
0172             print(f"   Checks passed: {sum(checks.values())}/{len(checks)}")
0173             for check_name, passed in checks.items():
0174                 status = "✓" if passed else "✗"
0175                 print(f"     {status} {check_name}")
0176         elif not args.quiet:
0177             print(f"✓ VALID: {filepath}")
0178             print(f"   All checks passed: {sum(checks.values())}/{len(checks)}")
0179 
0180     # Summary
0181     if len(args.files) > 1 and not args.quiet:
0182         valid_count = sum(1 for _, is_valid, _, _ in results if is_valid)
0183         invalid_count = len(results) - valid_count
0184         print(f"\nSummary: {valid_count}/{len(results)} files valid, {invalid_count} invalid")
0185 
0186     # Exit with error code if any files are invalid
0187     sys.exit(0 if all_valid else 1)
0188 
0189 
0190 if __name__ == "__main__":
0191     main()