File indexing completed on 2026-04-05 08:35:41
0001
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
0056 if filepath.exists():
0057 checks["file_exists"] = True
0058 else:
0059 errors.append(f"File does not exist: {filepath}")
0060
0061
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
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
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
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
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
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
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
0122 if tfile:
0123 tfile.Close()
0124
0125
0126 if all(checks.values()):
0127 return True, "All validation checks passed", checks
0128 else:
0129
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
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
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
0187 sys.exit(0 if all_valid else 1)
0188
0189
0190 if __name__ == "__main__":
0191 main()