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 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 _new(self, vd, name, parent, seqnum, isdir, length, xa): ''' Internal method to create a new Directory Record. Parameters: vd - The Volume Descriptor this record is part of. name - The name for this directory record. parent - The parent of this directory record. seqnum - The sequence number to associate with this directory record. isdir - Whether this directory record represents a directory. length - The length of the data for this directory record. xa - True if this is an Extended Attribute record. Returns: Nothing. ''' # Adding a new time should really be done when we are going to write # the ISO (in record()). Ecma-119 9.1.5 says: # # 'This field shall indicate the date and the time of the day at which # the information in the Extent described by the Directory Record was # recorded.' # # We create it here just to have something in the field, but we'll # redo the whole thing when we are mastering. self.date = dates.DirectoryRecordDate() self.date.new() if length > 2**32 - 1: raise pycdlibexception.PyCdlibInvalidInput( 'Maximum supported file length is 2^32-1') self.data_length = length self.file_ident = name self.isdir = isdir self.seqnum = seqnum # For a new directory record entry, there is no original_extent_loc, # so we leave it at None. self.orig_extent_loc = None self.len_fi = len(self.file_ident) self.dr_len = struct.calcsize(self.FMT) + self.len_fi # From Ecma-119, 9.1.6, the file flag bits are: # # Bit 0 - Existence - 0 for existence known, 1 for hidden # Bit 1 - Directory - 0 for file, 1 for directory # Bit 2 - Associated File - 0 for not associated, 1 for associated # Bit 3 - Record - 0=structure not in xattr, 1=structure in xattr # Bit 4 - Protection - 0=no owner and group, 1=owner and group in xattr # Bit 5 - Reserved # Bit 6 - Reserved # Bit 7 - Multi-extent - 0=final directory record, 1=not final directory record self.file_flags = 0 if self.isdir: self.file_flags |= (1 << self.FILE_FLAG_DIRECTORY_BIT) self.file_unit_size = 0 # FIXME: we don't support setting file unit size for now self.interleave_gap_size = 0 # FIXME: we don't support setting interleave gap size for now self.xattr_len = 0 # FIXME: we don't support xattrs for now self.parent = parent if parent is None: # If no parent, then this is the root self.is_root = True if xa: self.xa_record = XARecord() self.xa_record.new() self.dr_len += XARecord.length() self.dr_len += (self.dr_len % 2) 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.vd = vd self._initialized = True
def _new(self, mangledname, parent, seqnum, isdir, length, xa): ''' Internal method to create a new Directory Record. Parameters: mangledname - The ISO9660 name for this directory record. parent - The parent of this directory record. seqnum - The sequence number to associate with this directory record. isdir - Whether this directory record represents a directory. length - The length of the data for this directory record. xa - True if this is an Extended Attribute record. Returns: Nothing. ''' # Adding a new time should really be done when we are going to write # the ISO (in record()). Ecma-119 9.1.5 says: # # "This field shall indicate the date and the time of the day at which # the information in the Extent described by the Directory Record was # recorded." # # We create it here just to have something in the field, but we'll # redo the whole thing when we are mastering. self.date = dates.DirectoryRecordDate() self.date.new() if length > 2**32 - 1: raise pycdlibexception.PyCdlibInvalidInput( "Maximum supported file length is 2^32-1") self.data_length = length # FIXME: if the length of the item is more than 2^32 - 1, and the # interchange level is 3, we should make duplicate directory record # entries so we can represent the whole file (see # http://wiki.osdev.org/ISO_9660, Size Limitations for a discussion of # this). self.file_ident = mangledname self.isdir = isdir self.seqnum = seqnum # For a new directory record entry, there is no original_extent_loc, # so we leave it at None. self.orig_extent_loc = None self.len_fi = len(self.file_ident) self.dr_len = struct.calcsize(self.FMT) + self.len_fi # From Ecma-119, 9.1.6, the file flag bits are: # # Bit 0 - Existence - 0 for existence known, 1 for hidden # Bit 1 - Directory - 0 for file, 1 for directory # Bit 2 - Associated File - 0 for not associated, 1 for associated # Bit 3 - Record - 0 for structure not in xattr, 1 for structure in xattr # Bit 4 - Protection - 0 for no owner and group in xattr, 1 for owner and group in xattr # Bit 5 - Reserved # Bit 6 - Reserved # Bit 7 - Multi-extent - 0 for final directory record, 1 for not final directory record # FIXME: We probably want to allow the existence, associated file, xattr # record, and multi-extent bits to be set by the caller. self.file_flags = 0 if self.isdir: self.file_flags |= (1 << self.FILE_FLAG_DIRECTORY_BIT) self.file_unit_size = 0 # FIXME: we don't support setting file unit size for now self.interleave_gap_size = 0 # FIXME: we don't support setting interleave gap size for now self.xattr_len = 0 # FIXME: we don't support xattrs for now self.children = [] self.parent = parent self.is_root = False if parent is None: # If no parent, then this is the root self.is_root = True self.xa_record = None if xa: self.xa_record = XARecord() self.xa_record.new() self.dr_len += XARecord.length() self.dr_len += (self.dr_len % 2) self.rock_ridge = None 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