15from basf2
import B2ERROR
23 Interface to binary root file content
28 Open a root file and read its header
35 raise Exception(f
"{filename} is not a root file")
76 if hasattr(self,
'rootfile'):
79 def normalize(self, end=None, seekfree=None, nbytesfree=None, nbytesname=None, seekinfo=None):
81 Set UUID to zero and adjust pointers that are given
as arguments
94 self.
uuid = b
'\x00' * 18
97 + self.
version.to_bytes(4, byteorder) \
98 + self.
begin.to_bytes(4, byteorder) \
102 + self.
nfree.to_bytes(4, byteorder) \
104 + self.
units.to_bytes(1, byteorder) \
105 + self.
compress.to_bytes(4, byteorder) \
110 self.
header += b
'\x00' * nzero
126 Obtain key header and data payload
from given data
and extract header information
135 self.nbytes, pos = self.
get_int(keydata, pos)
137 self.version, pos = self.
get_int(keydata, pos, 2)
139 self.objlen, pos = self.
get_int(keydata, pos)
141 self.
datime, pos = keydata[pos:pos+4], pos+4
143 self.keylen, pos = self.
get_int(keydata, pos, 2)
145 self.cycle, pos = self.
get_int(keydata, pos, 2)
151 self.classname, pos = self.
get_string(keydata, pos)
153 self.name, pos = self.
get_string(keydata, pos)
155 self.title, pos = self.
get_string(keydata, pos)
165 Set the key datime to zero adjust the pointer to itself if given
as argument
180 Build the binary header information from the data members
183 self.header = self.nbytes.to_bytes(4, byteorder) \
184 + self.version.to_bytes(2, byteorder) \
185 + self.objlen.to_bytes(4, byteorder) \
187 + self.keylen.to_bytes(2, byteorder) \
188 + self.cycle.to_bytes(2, byteorder) \
190 + self.seekpdir.to_bytes(self.wordlen, byteorder) \
191 + len(self.classname).to_bytes(1, byteorder) + self.classname \
192 + len(self.name).to_bytes(1, byteorder) + self.name \
193 + len(self.title).to_bytes(1, byteorder) + self.title
197 Helper function to read an int from binary data
200 return (int.from_bytes(data[pos:pos+wordlen], byteorder), pos+wordlen)
204 Helper function to read a string from binary data
208 return (data[pos+1:pos+1+strlen], pos+1+strlen)
216 large = pos > 0x80000000
217 nbytes = int.from_bytes(self.rootfile.read(4), byteorder)
219 raise StopIteration()
221 result = self.
Key(self.
rootfile.read(nbytes), large)
223 result.showname = b
'FreeSegments'
225 result.showname = b
'StreamerInfo'
227 result.showname = b
'KeysList'
229 if result.showname == b
'TFile':
230 result.filename, pos = result.get_string(result.data, 0)
231 result.filetitle, pos = result.get_string(result.data, pos)
232 result.version = int.from_bytes(result.data[pos:pos+2], byteorder)
233 result.nbyteskeys = int.from_bytes(result.data[pos+10:pos+14], byteorder)
234 result.nbytesname = int.from_bytes(result.data[pos+14:pos+18], byteorder)
235 if result.version > 1000:
236 result.seekdir = int.from_bytes(result.data[pos+18:pos+26], byteorder)
237 result.seekparent = int.from_bytes(result.data[pos+26:pos+34], byteorder)
238 self.
seekkeys = int.from_bytes(result.data[pos+34:pos+42], byteorder)
240 result.seekdir = int.from_bytes(result.data[pos+18:pos+22], byteorder)
241 result.seekparent = int.from_bytes(result.data[pos+22:pos+26], byteorder)
242 self.
seekkeys = int.from_bytes(result.data[pos+26:pos+30], byteorder)
246def normalize_file(filename, output=None, in_place=False, name=None, root_version=None):
248 Reset the non-reproducible root file metadata: UUID and datimes.
249 It can also reset the initial file name stored
in the file itself, but
250 (WARNING!) this may corrupt the root file.
258 rootfile.version = root_version
262 newrootfile = open(output,
'wb')
264 newrootfile = tempfile.TemporaryFile()
266 basename, ext = os.path.splitext(filename)
267 newrootfile = open(basename +
'_normalized' + ext,
'wb')
270 newrootfile.write(rootfile.header)
274 newname = name.encode()
280 seekfree = rootfile.seekfree
281 nbytesfree = rootfile.nbytesfree
282 nbytesname = rootfile.nbytesname
283 seekinfo = rootfile.seekinfo
291 key.normalize(pos=newrootfile.tell())
294 if key.showname == b
'TFile':
297 namelen = len(key.name)
299 key.name = key.filename = newname
300 offset = len(key.name) - namelen
303 key.nbytes += 2*offset
308 key.recreate_header()
311 buffer = len(key.name).to_bytes(1, byteorder) + key.name
312 buffer += len(key.title).to_bytes(1, byteorder) + key.title
313 buffer += key.version.to_bytes(2, byteorder)
314 buffer += (0).to_bytes(8, byteorder)
315 buffer += (key.nbyteskeys + offset).to_bytes(4, byteorder)
316 buffer += (key.nbytesname + 2*offset).to_bytes(4, byteorder)
317 wordlen = 8
if key.version > 1000
else 4
318 buffer += key.seekdir.to_bytes(wordlen, byteorder)
319 buffer += key.seekparent.to_bytes(wordlen, byteorder)
320 seekkeyspos = newrootfile.tell() + len(key.header) + len(buffer)
321 buffer += (rootfile.seekkeys + 2*offset).to_bytes(wordlen, byteorder)
322 buffer += (0).to_bytes(18, byteorder)
323 if key.version <= 1000:
324 buffer += (0).to_bytes(12, byteorder)
329 if key.classname == b
'TTree' and offset != 0:
330 B2ERROR(
'Changing the name of root files containing a tree is not supported.')
332 os.remove(newrootfile.name)
337 if key.showname == b
'KeysList':
338 seekkeys = newrootfile.tell()
339 buffer = len(keylist).to_bytes(4, byteorder)
340 for filekey
in keylist:
341 buffer += filekey.header
344 swap = (infokey
is None)
347 if key.showname == b
'FreeSegments':
348 seekfree = newrootfile.tell()
349 pointer = int.from_bytes(key.data[2:6], byteorder) + 4*offset
350 key.data = key.data[:2] + pointer.to_bytes(4, byteorder) + key.data[6:]
353 if key.showname
in [b
'KeysList', b
'FreeSegments']
and newname:
357 key.recreate_header()
360 elif key.showname
not in [b
'StreamerInfo', b
'']:
364 if key.showname == b
'StreamerInfo':
365 seekinfo = newrootfile.tell()
369 if swap
and key.showname == b
'KeysList':
372 seekinfo = newrootfile.tell()
373 infokey.normalize(pos=seekinfo)
374 newrootfile.write(infokey.header)
375 newrootfile.write(infokey.data)
376 seekkeys = newrootfile.tell()
377 keyskey.normalize(pos=seekkeys)
378 newrootfile.write(keyskey.header)
379 newrootfile.write(keyskey.data)
382 newrootfile.write(key.header)
383 newrootfile.write(key.data)
386 rootfile.normalize(end=newrootfile.tell(), seekfree=seekfree, nbytesfree=nbytesfree+offset,
387 nbytesname=nbytesname+2*offset, seekinfo=seekinfo)
389 newrootfile.write(rootfile.header)
392 newrootfile.seek(seekkeyspos)
393 newrootfile.write(seekkeys.to_bytes(wordlen, byteorder))
399 shutil.copyfileobj(newrootfile, open(filename,
'wb'))