def record(self): ''' A method to generate the string representing this Directory Record. Parameters: None. Returns: String representing this Directory Record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError( 'Directory Record not yet initialized') # Ecma-119 9.1.5 says the date should reflect the time when the # record was written, so we make a new date now and use that to # write out the record. self.date = dates.DirectoryRecordDate() self.date.new() padlen = struct.calcsize(self.FMT) + self.len_fi padstr = b'\x00' * (padlen % 2) extent_loc = self._extent_location() xa_rec = b'' if self.xa_record is not None: xa_rec = b'\x00' * self.xa_pad_size + self.xa_record.record() rr_rec = b'' if self.rock_ridge is not None: rr_rec = self.rock_ridge.record_dr_entries() outlist = [ struct.pack(self.FMT, self.dr_len, self.xattr_len, extent_loc, utils.swab_32bit(extent_loc), self.data_length, utils.swab_32bit(self.data_length), self.date.record(), self.file_flags, self.file_unit_size, self.interleave_gap_size, self.seqnum, utils.swab_16bit(self.seqnum), self.len_fi) + self.file_ident + padstr + xa_rec + rr_rec ] outlist.append(b'\x00' * (len(outlist[0]) % 2)) return b''.join(outlist)
def record(self): ''' A method to generate the string representing this Directory Record. Parameters: None. Returns: String representing this Directory Record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError("Directory Record not yet initialized") # Ecma-119 9.1.5 says the date should reflect the time when the # record was written, so we make a new date now and use that to # write out the record. self.date = dates.DirectoryRecordDate() self.date.new() padlen = struct.calcsize(self.FMT) + self.len_fi padstr = b'\x00' * (padlen % 2) extent_loc = self._extent_location() xa_rec = b"" if self.xa_record is not None: xa_rec = b'\x00' * self.xa_pad_size + self.xa_record.record() rr_rec = b"" if self.rock_ridge is not None: rr_rec = self.rock_ridge.record_dr_entries() outlist = [struct.pack(self.FMT, self.dr_len, self.xattr_len, extent_loc, utils.swab_32bit(extent_loc), self.data_length, utils.swab_32bit(self.data_length), self.date.record(), self.file_flags, self.file_unit_size, self.interleave_gap_size, self.seqnum, utils.swab_16bit(self.seqnum), self.len_fi) + self.file_ident + padstr + xa_rec + rr_rec] outlist.append(b'\x00' * (len(outlist[0]) % 2)) return b"".join(outlist)
def record_big_endian(self): ''' A method to generate a string representing the big endian version of this Path Table Record. Parameters: None. Returns: A string representing the big endian version of this Path Table Record. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError("Path Table Record not yet initialized") return self._record(utils.swab_32bit(self.extent_location), utils.swab_16bit(self.parent_directory_num))
def record_big_endian(self): # type: () -> bytes """ Generate a string representing the big endian version of this Path Table Record. Parameters: None. Returns: A string representing the big endian version of this Path Table Record. """ if not self._initialized: raise pycdlibexception.PyCdlibInternalError( 'Path Table Record not initialized') return self._record(utils.swab_32bit(self.extent_location), utils.swab_16bit(self.parent_directory_num))
def equal_to_be(self, be_record): ''' A method to compare a little-endian path table record to its big-endian counterpart. This is used to ensure that the ISO is sane. Parameters: be_record - The big-endian object to compare with the little-endian object. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError("This Path Table Record is not yet initialized") if be_record.len_di != self.len_di or \ be_record.xattr_length != self.xattr_length or \ utils.swab_32bit(be_record.extent_location) != self.extent_location or \ utils.swab_16bit(be_record.parent_directory_num) != self.parent_directory_num or \ be_record.directory_identifier != self.directory_identifier: return False return True
def equal_to_be(self, be_record): # type: (PathTableRecord) -> bool ''' Compare a little-endian path table record to its big-endian counterpart. This is used to ensure that the ISO is sane. Parameters: be_record - The big-endian object to compare with the little-endian object. Returns: True if this record is equal to the big-endian record passed in, False otherwise. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError( 'Path Table Record not initialized') if be_record.len_di != self.len_di or \ be_record.xattr_length != self.xattr_length or \ utils.swab_32bit(be_record.extent_location) != self.extent_location or \ utils.swab_16bit(be_record.parent_directory_num) != self.parent_directory_num or \ be_record.directory_identifier != self.directory_identifier: return False return True
def parse(self, vd, record, parent): ''' Parse a directory record out of a string. Parameters: vd - The Volume Descriptor this record is part of. record - The string to parse for this record. parent - The parent of this record. Returns: True if this Directory Record has Rock Ridge extensions, False otherwise. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError( 'Directory Record already initialized') if len(record) > 255: # Since the length is supposed to be 8 bits, this should never # happen. raise pycdlibexception.PyCdlibInvalidISO( 'Directory record longer than 255 bytes!') (self.dr_len, self.xattr_len, extent_location_le, extent_location_be, data_length_le, data_length_be_unused, dr_date, self.file_flags, self.file_unit_size, self.interleave_gap_size, seqnum_le, seqnum_be, self.len_fi) = struct.unpack_from(self.FMT, record[:33], 0) # In theory we should have a check here that checks to make sure that # the length of the record we were passed in matches the data record # length. However, we have seen ISOs in the wild where this is # incorrect, so we elide the check here. if extent_location_le != utils.swab_32bit(extent_location_be): raise pycdlibexception.PyCdlibInvalidISO( 'Little-endian (%d) and big-endian (%d) extent location disagree' % (extent_location_le, utils.swab_32bit(extent_location_be))) self.orig_extent_loc = extent_location_le # Theoretically, we should check to make sure that the little endian # data length is the same as the big endian data length. In practice, # though, we've seen ISOs where this is wrong. Skip the check, and just # pick the little-endian as the 'actual' size, and hope for the best. self.data_length = data_length_le if seqnum_le != utils.swab_16bit(seqnum_be): raise pycdlibexception.PyCdlibInvalidISO( 'Little-endian and big-endian seqnum disagree') self.seqnum = seqnum_le self.date = dates.DirectoryRecordDate() self.date.parse(dr_date) # OK, we've unpacked what we can from the beginning of the string. Now # we have to use the len_fi to get the rest. self.parent = parent self.vd = vd if self.parent is None: self.is_root = True # A root directory entry should always be exactly 34 bytes. # However, we have seen ISOs in the wild that get this wrong, so we # elide a check for it. self.file_ident = bytes(bytearray([record[33]])) # A root directory entry should always have 0 as the identifier. # However, we have seen ISOs in the wild that don't have this set # properly to 0. In that case, we override what we parsed out from # the original with the correct value (\x00), and hope for the best. if self.file_ident != b'\x00': self.file_ident = b'\x00' self.isdir = True else: record_offset = 33 self.file_ident = record[record_offset:record_offset + self.len_fi] record_offset += self.len_fi if self.file_flags & (1 << self.FILE_FLAG_DIRECTORY_BIT): self.isdir = True if self.len_fi % 2 == 0: record_offset += 1 if len(record[record_offset:]) >= XARecord.length(): xa_rec = XARecord() try: xa_rec.parse(record[record_offset:record_offset + XARecord.length()]) self.xa_record = xa_rec record_offset += XARecord.length() except pycdlibexception.PyCdlibInvalidISO: # We've seen some ISOs in the wild (Windows 98 SE) that # put the XA record all the way at the back, with some # padding. Try again from the back. try: xa_rec.parse(record[-XARecord.length():]) self.xa_record = xa_rec self.xa_pad_size = len( record) - record_offset - XARecord.length() record_offset = len(record) except pycdlibexception.PyCdlibInvalidISO: pass if len(record[record_offset:] ) >= 2 and record[record_offset:record_offset + 2] in ( b'SP', b'RR', b'CE', b'PX', b'ER', b'ES', b'PN', b'SL', b'NM', b'CL', b'PL', b'TF', b'SF', b'RE'): self.rock_ridge = rockridge.RockRidge() is_first_dir_record_of_root = self.file_ident == b'\x00' and parent.parent is None if is_first_dir_record_of_root: bytes_to_skip = 0 elif parent.parent is None: bytes_to_skip = parent.children[0].rock_ridge.bytes_to_skip else: bytes_to_skip = parent.rock_ridge.bytes_to_skip self.rock_ridge.parse(record[record_offset:], is_first_dir_record_of_root, bytes_to_skip, False) if self.xattr_len != 0: if self.file_flags & (1 << self.FILE_FLAG_RECORD_BIT): raise pycdlibexception.PyCdlibInvalidISO( 'Record Bit not allowed with Extended Attributes') if self.file_flags & (1 << self.FILE_FLAG_PROTECTION_BIT): raise pycdlibexception.PyCdlibInvalidISO( 'Protection Bit not allowed with Extended Attributes') if self.rock_ridge is None: ret = None else: ret = self.rock_ridge.rr_version if self.is_root: self._printable_name = b'/' elif self.file_ident == b'\x00': self._printable_name = b'.' elif self.file_ident == b'\x01': self._printable_name = b'..' else: self._printable_name = self.file_ident self._initialized = True return ret
def parse(self, record, data_fp, parent): ''' Parse a directory record out of a string. Parameters: record - The string to parse for this record. data_fp - The file object to associate with this record. parent - The parent of this record. Returns: True if this Directory Record has Rock Ridge extensions, False otherwise. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError("Directory Record already initialized") if len(record) > 255: # Since the length is supposed to be 8 bits, this should never # happen. raise pycdlibexception.PyCdlibInvalidISO("Directory record longer than 255 bytes!") (self.dr_len, self.xattr_len, extent_location_le, extent_location_be, data_length_le, data_length_be_unused, dr_date, self.file_flags, self.file_unit_size, self.interleave_gap_size, seqnum_le, seqnum_be, self.len_fi) = struct.unpack_from(self.FMT, record[:33], 0) # In theory we should have a check here that checks to make sure that # the length of the record we were passed in matches the data record # length. However, we have seen ISOs in the wild where this is # incorrect, so we elide the check here. if extent_location_le != utils.swab_32bit(extent_location_be): raise pycdlibexception.PyCdlibInvalidISO("Little-endian (%d) and big-endian (%d) extent location disagree" % (extent_location_le, utils.swab_32bit(extent_location_be))) self.orig_extent_loc = extent_location_le self.new_extent_loc = None # Theoretically, we should check to make sure that the little endian # data length is the same as the big endian data length. In practice, # though, we've seen ISOs where this is wrong. Skip the check, and just # pick the little-endian as the "actual" size, and hope for the best. self.data_length = data_length_le if seqnum_le != utils.swab_16bit(seqnum_be): raise pycdlibexception.PyCdlibInvalidISO("Little-endian and big-endian seqnum disagree") self.seqnum = seqnum_le self.date = dates.DirectoryRecordDate() self.date.parse(dr_date) # OK, we've unpacked what we can from the beginning of the string. Now # we have to use the len_fi to get the rest. self.children = [] self.is_root = False self.isdir = False self.parent = parent self.data_fp = data_fp self.rock_ridge = None self.xa_record = None if self.parent is None: self.is_root = True # A root directory entry should always be exactly 34 bytes. # However, we have seen ISOs in the wild that get this wrong, so we # elide a check for it. self.file_ident = bytes(bytearray([record[33]])) # A root directory entry should always have 0 as the identifier. if self.file_ident != b'\x00': raise pycdlibexception.PyCdlibInvalidISO("Invalid root directory entry identifier") self.isdir = True else: record_offset = 33 self.file_ident = record[record_offset:record_offset + self.len_fi] record_offset += self.len_fi if self.file_flags & (1 << self.FILE_FLAG_DIRECTORY_BIT): self.isdir = True else: self.original_data_location = self.DATA_ON_ORIGINAL_ISO if self.len_fi % 2 == 0: record_offset += 1 if len(record[record_offset:]) >= XARecord.length(): xa_rec = XARecord() try: xa_rec.parse(record[record_offset:record_offset + XARecord.length()]) self.xa_record = xa_rec record_offset += XARecord.length() except pycdlibexception.PyCdlibInvalidISO: # We've seen some ISOs in the wild (Windows 98 SE) that # put the XA record all the way at the back, with some # padding. Try again from the back. try: xa_rec.parse(record[-XARecord.length():]) self.xa_record = xa_rec self.xa_pad_size = len(record) - record_offset - XARecord.length() record_offset = len(record) except pycdlibexception.PyCdlibInvalidISO: pass if len(record[record_offset:]) >= 2 and record[record_offset:record_offset + 2] in [b'SP', b'RR', b'CE', b'PX', b'ER', b'ES', b'PN', b'SL', b'NM', b'CL', b'PL', b'TF', b'SF', b'RE']: self.rock_ridge = rockridge.RockRidge() is_first_dir_record_of_root = self.file_ident == b'\x00' and parent.parent is None if is_first_dir_record_of_root: bytes_to_skip = 0 elif parent.parent is None: bytes_to_skip = parent.children[0].rock_ridge.bytes_to_skip else: bytes_to_skip = parent.rock_ridge.bytes_to_skip self.rock_ridge.parse(record[record_offset:], is_first_dir_record_of_root, bytes_to_skip, False) if self.xattr_len != 0: if self.file_flags & (1 << self.FILE_FLAG_RECORD_BIT): raise pycdlibexception.PyCdlibInvalidISO("Record Bit not allowed with Extended Attributes") if self.file_flags & (1 << self.FILE_FLAG_PROTECTION_BIT): raise pycdlibexception.PyCdlibInvalidISO("Protection Bit not allowed with Extended Attributes") self._initialized = True if self.rock_ridge is None: ret = None else: ret = self.rock_ridge.rr_version if self.is_root: self._printable_name = b'/' elif self.file_ident == b'\x00': self._printable_name = b'.' elif self.file_ident == b'\x01': self._printable_name = b'..' else: self._printable_name = self.file_ident return ret