def add_child(self, child, logical_block_size, allow_duplicate=False): ''' A method to add a child to this object. Note that this is called both during parsing and when adding a new object to the system, so it it shouldn't have any functionality that is not appropriate for both. Parameters: child - The child directory record object to add. logical_block_size - The size of a logical block for this volume descriptor. allow_duplicate - Whether to allow duplicate names, as there are situations where duplicate children are allowed. Returns: True if adding this child caused the directory to overflow into another extent, False otherwise. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError( "Directory Record not yet initialized") if not self.isdir: raise pycdlibexception.PyCdlibInvalidInput( "Trying to add a child to a record that is not a directory") # First ensure that this is not a duplicate. For speed purposes, we # recognize that bisect_left will always choose an index to the *left* # of a duplicate child. Thus, to check for duplicates we only need to # see if the child to be added is a duplicate with the entry that # bisect_left returned. index = bisect.bisect_left(self.children, child) if index != len(self.children) and self.children[ index].file_ident == child.file_ident: if not self.children[index].is_associated_file( ) and not child.is_associated_file(): if not (self.rock_ridge is not None and self.file_identifier() == b"RR_MOVED"): if not allow_duplicate: raise pycdlibexception.PyCdlibInvalidInput( "Parent %s already has a child named %s" % (self.file_identifier(), child.file_identifier())) else: self.children[index].data_continuation = child index += 1 self.children.insert(index, child) # We now have to check if we need to add another logical block. # We have to iterate over the entire list again, because where we # placed this last entry may rearrange the empty spaces in the blocks # that we've already allocated. num_extents, dirrecord_unused = self._recalculate_extents_and_offsets( index, logical_block_size) overflowed = False if num_extents * logical_block_size > self.data_length: overflowed = True # When we overflow our data length, we always add a full block. self.data_length += logical_block_size # We also have to make sure to update the length of the dot child, # as that should always reflect the length. self.children[0].data_length = self.data_length return overflowed
def new(self, sector_count, load_seg, media_name, system_type, bootable): # type: (int, int, str, int, bool) -> None ''' A method to create a new El Torito Entry. Parameters: sector_count - The number of sectors to assign to this El Torito Entry. load_seg - The load segment address of the boot image. media_name - The name of the media type, one of 'noemul', 'floppy', or 'hdemul'. system_type - The partition type to assign to the entry. bootable - Whether this entry is bootable. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError( 'El Torito Entry already initialized') if media_name == 'noemul': media_type = self.MEDIA_NO_EMUL elif media_name == 'floppy': if sector_count == 2400: media_type = self.MEDIA_12FLOPPY elif sector_count == 2880: media_type = self.MEDIA_144FLOPPY elif sector_count == 5760: media_type = self.MEDIA_288FLOPPY else: raise pycdlibexception.PyCdlibInvalidInput( 'Invalid sector count for floppy media type; must be 2400, 2880, or 5760' ) # With floppy booting, the sector_count always ends up being 1 sector_count = 1 elif media_name == 'hdemul': media_type = self.MEDIA_HD_EMUL # With HD emul booting, the sector_count always ends up being 1 sector_count = 1 else: raise pycdlibexception.PyCdlibInvalidInput( "Invalid media name '%s'" % (media_name)) if bootable: self.boot_indicator = 0x88 else: self.boot_indicator = 0 self.boot_media_type = media_type self.load_segment = load_seg self.system_type = system_type self.sector_count = sector_count self.load_rba = 0 # This will get set later self.selection_criteria_type = 0 # FIXME: allow the user to set this self.selection_criteria = b''.ljust(19, b'\x00') self._initialized = True
def _rr_path_to_iso_path(self, rr_path): # type: (str) -> str ''' An internal method to convert an ISO9660 path to a Rock Ridge one. This is accomplished by find the Rock Ridge directory record, then reconstructing the ISO path by walking up to the root. Parameters: rr_path - The Rock Ridge path to generate an ISO9660 path for. Returns: The ISO9660 path corresponding to the Rock Ridge path. ''' if rr_path[0] != '/': raise pycdlibexception.PyCdlibInvalidInput( "rr_path must start with '/'") record = self.pycdlib_obj._find_rr_record(utils.normpath(rr_path)) # pylint: disable=protected-access if record.is_root: iso_path = b'/' else: iso_path = b'' parent = record # type: Optional[dr.DirectoryRecord] while parent is not None: if not parent.is_root: iso_path = b'/' + parent.file_identifier() + iso_path parent = parent.parent return iso_path.decode('utf-8')
def __init__(self, pycdlib_obj): # type: (pycdlib.PyCdlib) -> None if not pycdlib_obj.has_rock_ridge(): raise pycdlibexception.PyCdlibInvalidInput( 'Can only instantiate a Rock Ridge facade for a Rock Ridge ISO' ) self.pycdlib_obj = pycdlib_obj
def iso_path_to_rr_name(iso_path, interchange_level, is_dir): # type: (str, int, bool) -> str ''' A method to take an absolute ISO path and generate a corresponding Rock Ridge basename. Parameters: iso_path - The absolute iso_path to generate a Rock Ridge name from. interchange_level - The interchange level at which to operate. is_dir - Whether this will be a directory or not. Returns: The Rock Ridge name as a string. ''' if iso_path[0] != '/': raise pycdlibexception.PyCdlibInvalidInput( "iso_path must start with '/'") namesplit = utils.split_path(utils.normpath(iso_path)) iso_name = namesplit.pop() if is_dir: rr_name = utils.mangle_dir_for_iso9660(iso_name.decode('utf-8'), interchange_level) else: basename, ext = utils.mangle_file_for_iso9660(iso_name.decode('utf-8'), interchange_level) rr_name = '.'.join([basename, ext]) return rr_name
def encode_space_pad(instr, length, encoding): ''' A function to pad out an input string with spaces to the length specified. The space is first encoded into the specified encoding, then appended to the input string until the length is reached. Parameters: instr - The input string to encode and pad. length - The length to pad the input string to. encoding - The encoding to use. Returns: The input string encoded in the encoding and padded with encoded spaces. ''' output = instr.decode('utf-8').encode(encoding) if len(output) > length: raise pycdlibexception.PyCdlibInvalidInput("Input string too long!") encoded_space = ' '.encode(encoding) left = length - len(output) while left > 0: output += encoded_space left -= len(encoded_space) if left < 0: output = output[:left] return output
def readinto(self, b): # type: (bytes) -> int if not self._open: raise pycdlibexception.PyCdlibInvalidInput( 'I/O operation on closed file.') readsize = self._length - self._offset if readsize > 0: if sys.version_info >= (3, 0): # Python 3 mv = memoryview(b) # type: ignore m = mv.cast('B') # type: ignore readsize = min(readsize, len(m)) data = self._fp.read(readsize) n = len(data) m[:n] = data else: # Python 2 readsize = min(readsize, len(b)) data = self._fp.read(readsize) n = len(data) try: b[:n] = data except TypeError as err: if not isinstance(b, array.array): raise err b[:n] = array.array(b'b', data) else: n = 0 return n
def read(self, size=None): # type: (Optional[int]) -> bytes ''' Read and return up to size bytes. Parameters: size - Optional parameter to read size number of bytes; if None or negative, all remaining bytes in the file will be read Returns: The number of bytes requested or the rest of the data left in the file, whichever is smaller. If the file is at or past EOF, returns an empty bytestring. ''' if not self._open: raise pycdlibexception.PyCdlibInvalidInput( 'I/O operation on closed file.') if self._offset >= self._length: return b'' if size is None or size < 0: data = self.readall() else: readsize = min(self._length - self._offset, size) data = self._fp.read(readsize) self._offset += readsize return data
def readinto(self, b): # type: (Union[bytearray, memoryview, array.array[Any], mmap]) -> int if not self._open: raise pycdlibexception.PyCdlibInvalidInput('I/O operation on closed file.') readsize = self._length - self._offset if readsize > 0: if have_py_3: mv = memoryview(b) m = mv.cast('B') readsize = min(readsize, len(m)) data = self._fp.read(readsize) n = len(data) m[:n] = data else: readsize = min(readsize, len(b)) data = self._fp.read(readsize) n = len(data) try: b[:n] = data except TypeError as err: if not isinstance(b, array.array): raise err b[:n] = array.array(b'b', data) # type: ignore else: n = 0 return n
def __init__(self, drobj, logical_block_size): if drobj.isdir: raise pycdlibexception.PyCdlibInvalidInput( "Cannot write out a directory") self.drobj = drobj while self.drobj.target is not None: self.drobj = self.drobj.target self.logical_block_size = logical_block_size
def length(self): # type: () -> int """ Return the length of the current file. Parameters: None. Returns: The length of the file. """ if not self._open: raise pycdlibexception.PyCdlibInvalidInput('I/O operation on closed file.') return self._length
def seekable(self): # type: () -> bool """ Determine whether this file is seekable. Parameters: None. Returns: True in all cases. """ if not self._open: raise pycdlibexception.PyCdlibInvalidInput('I/O operation on closed file.') return True
def tell(self): # type: () -> int """ Return the current stream position. Parameters: None. Returns: The current stream position. """ if not self._open: raise pycdlibexception.PyCdlibInvalidInput('I/O operation on closed file.') return self._offset
def readable(self): # type: () -> bool ''' A method to determine whether this file is readable. Parameters: None. Returns: True in all cases. ''' if not self._open: raise pycdlibexception.PyCdlibInvalidInput( 'I/O operation on closed file.') return True
def add_section(self, ino, sector_count, load_seg, media_name, system_type, efi, bootable): # type: (inode.Inode, int, int, str, int, bool, bool) -> None ''' A method to add an section header and entry to this Boot Catalog. Parameters: ino - The Inode object to associate with the new Entry. sector_count - The number of sectors to assign to the new Entry. load_seg - The load segment address of the boot image. media_name - The name of the media type, one of 'noemul', 'floppy', or 'hdemul'. system_type - The type of partition this entry should be. efi - Whether this section is an EFI section. bootable - Whether this entry should be bootable. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError( 'El Torito Boot Catalog not yet initialized') # The Eltorito Boot Catalog can only be 2048 bytes (1 extent). By # default, the first 64 bytes are used by the Validation Entry and the # Initial Entry, which leaves 1984 bytes. Each section takes up 32 # bytes for the Section Header and 32 bytes for the Section Entry, for # a total of 64 bytes, so we can have a maximum of 1984/64 = 31 # sections. if len(self.sections) == 31: raise pycdlibexception.PyCdlibInvalidInput( 'Too many Eltorito sections') sec = EltoritoSectionHeader() platform_id = self.validation_entry.platform_id if efi: platform_id = 0xef sec.new(b'\x00' * 28, platform_id) secentry = EltoritoEntry() secentry.new(sector_count, load_seg, media_name, system_type, bootable) secentry.set_inode(ino) ino.linked_records.append(secentry) sec.add_new_entry(secentry) if self.sections: self.sections[-1].set_record_not_last() self.sections.append(sec)
def seek(self, offset, whence=0): # type: (int, int) -> int """ Change the stream position to byte offset offset. The offset is interpreted relative to the position indicated by whence. Valid values for whence are: * 0 -- start of stream (the default); offset should be zero or positive * 1 -- current stream position; offset may be negative * 2 -- end of stream; offset is usually negative Parameters: offset - The byte offset to seek to. whence - The position in the file to start from (0 for start, 1 for current, 2 for end) Returns: The new absolute position. """ if not self._open: raise pycdlibexception.PyCdlibInvalidInput('I/O operation on closed file.') if isinstance(offset, float): raise pycdlibexception.PyCdlibInvalidInput('an integer is required') if whence == 0: # From beginning of file if offset < 0: raise pycdlibexception.PyCdlibInvalidInput('Invalid offset value (must be positive)') if offset < self._length: self._fp.seek(self._startpos + offset, 0) self._offset = offset elif whence == 1: # From current file position if self._offset + offset < 0: raise pycdlibexception.PyCdlibInvalidInput('Invalid offset value (cannot seek before start of file)') if self._offset + offset < self._length: self._fp.seek(self._startpos + self._offset + offset, 0) self._offset += offset elif whence == 2: # From end of file if offset < 0 and abs(offset) > self._length: raise pycdlibexception.PyCdlibInvalidInput('Invalid offset value (cannot seek before start of file)') if self._length + offset < self._length: self._fp.seek(self._startpos + self._length + offset, 0) self._offset = self._length + offset else: raise pycdlibexception.PyCdlibInvalidInput('Invalid value for whence (options are 0, 1, and 2)') return self._offset
def split_path(iso_path): # type: (bytes) -> List[bytes] ''' A function to take a fully-qualified iso path and split it into components. Parameters: iso_path - The path to split. Returns: The components of the path as a list. ''' if not starts_with_slash(iso_path): raise pycdlibexception.PyCdlibInvalidInput('Must be a path starting with /') # Split the path along the slashes. Since our paths are always absolute, # the front is blank. return iso_path.split(b'/')[1:]
def _rr_path_to_iso_path_and_rr_name(self, rr_path, is_dir): # type: (str, bool) -> Tuple[str, str] ''' An internal method to split a Rock Ridge absolute path into an absolute ISO9660 path and a Rock Ridge name. This is accomplished by finding the Rock Ridge directory record of the parent, then reconstructing the ISO parent path by walking up to the root. Parameters: rr_path - The absolute Rock Ridge path to generate for. is_dir - Whether this path is a directory or a file. Returns: A tuple where the first element is the absolute ISO9660 path of the parent, and the second element is the Rock Ridge name. ''' if rr_path[0] != '/': raise pycdlibexception.PyCdlibInvalidInput( "rr_path must start with '/'") namesplit = utils.split_path(utils.normpath(rr_path)) rr_name = namesplit.pop() rr_parent_path = b'/' + b'/'.join(namesplit) parent_record = self.pycdlib_obj._find_rr_record(rr_parent_path) # pylint: disable=protected-access if parent_record.is_root: iso_parent_path = b'/' else: iso_parent_path = b'' parent = parent_record # type: Optional[dr.DirectoryRecord] while parent is not None: if not parent.is_root: iso_parent_path = b'/' + parent.file_identifier( ) + iso_parent_path parent = parent.parent if is_dir: iso_name = utils.mangle_dir_for_iso9660( rr_name.decode('utf-8'), self.pycdlib_obj.interchange_level) else: basename, ext = utils.mangle_file_for_iso9660( rr_name.decode('utf-8'), self.pycdlib_obj.interchange_level) iso_name = '.'.join([basename, ext]) iso_path = iso_parent_path.decode('utf-8') + '/' + iso_name return iso_path, rr_name.decode('utf-8')
def add_parsed_entry(self, entry): ''' A method to add a parsed entry to the list of entries of this header. If the number of parsed entries exceeds what was expected from the initial parsing of the header, this method will throw an Exception. Parameters: entry - The EltoritoEntry object to add to the list of entries. Returns: Nothing. ''' if not self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Section Header not yet initialized') if len(self.section_entries) >= self.num_section_entries: raise pycdlibexception.PyCdlibInvalidInput('Eltorito section had more entries than expected by section header; ISO is corrupt') self.section_entries.append(entry)
def new(self, platform_id): # type: (int) -> None ''' Create a new El Torito Validation Entry. Parameters: platform_id - The platform ID to set for this validation entry. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError('El Torito Validation Entry already initialized') if platform_id not in (0, 1, 2, 0xef): raise pycdlibexception.PyCdlibInvalidInput('Invalid platform ID (must be one of 0, 1, 2, or 0xef)') self.platform_id = platform_id self.id_string = b'\x00' * 24 # FIXME: let the user set this self.checksum = 0 self.checksum = self._checksum(self._record()) self._initialized = True
def normpath(path): # type: (str) -> bytes ''' Normalize the given path, eliminating double slashes, etc. This function is a copy of the built-in python normpath, except we do *not* allow double slashes at the start. Parameters: path - The path to normalize. Returns: The normalized path. ''' sep = '/' empty = '' dot = '.' dotdot = '..' if path == empty: return dot.encode('utf-8') initial_slashes = path.startswith(sep) comps = path.split(sep) new_comps = [] # type: List[str] for comp in comps: if comp in (empty, dot): continue if comp != dotdot or (not initial_slashes and not new_comps) or ( new_comps and new_comps[-1] == dotdot): new_comps.append(comp) elif new_comps: new_comps.pop() newpath = sep * initial_slashes + sep.join(new_comps) if sys.version_info >= (3, 0): newpath_bytes = newpath.encode('utf-8') else: newpath_bytes = newpath.decode('utf-8').encode('utf-8') if not starts_with_slash(newpath_bytes): raise pycdlibexception.PyCdlibInvalidInput( 'Must be a path starting with /') return newpath_bytes
def readall(self): # type: () -> bytes """ Read and return the remaining bytes in the file. Parameters: None. Returns: The rest of the data left in the file. If the file is at or past EOF, returns an empty bytestring. """ if not self._open: raise pycdlibexception.PyCdlibInvalidInput('I/O operation on closed file.') readsize = self._length - self._offset if readsize > 0: data = self._fp.read(readsize) self._offset += readsize else: data = b'' return data
def hdmbrcheck(disk_mbr, sector_count, bootable): # type: (bytes, int, bool) -> int ''' A function to sanity check an El Torito Hard Drive Master Boot Record (HDMBR). On success, it returns the system_type (also known as the partition type) that should be fed into the rest of the El Torito methods. On failure, it raises an exception. Parameters: disk_mbr - The data to look in. sector_count - The number of sectors expected in the MBR. bootable - Whether this MBR is bootable. Returns: The system (or partition) type the should be fed into the rest of El Torito. ''' # The MBR that we want to see to do hd emulation boot for El Torito is a standard # x86 MBR, documented here: # https://en.wikipedia.org/wiki/Master_boot_record#Sector_layout # # In brief, it should consist of 512 bytes laid out like: # Offset 0x0 - 0x1BD: Bootstrap code area # Offset 0x1BE - 0x1CD: Partition entry 1 # Offset 0x1CE - 0x1DD: Partition entry 2 # Offset 0x1DE - 0x1ED: Partition entry 3 # Offset 0x1EE - 0x1FD: Partition entry 4 # Offset 0x1FE: 0x55 # Offset 0x1FF: 0xAA # # Each partition entry above should consist of: # Offset 0x0: Active (bit 7 set) or inactive (all zeros) # Offset 0x1 - 0x3: CHS address of first sector in partition # Offset 0x1: Head # Offset 0x2: Sector in bits 0-5, bits 6-7 are high bits of of cylinder # Offset 0x3: bits 0-7 of cylinder # Offset 0x4: Partition type (almost all of these are valid, see https://en.wikipedia.org/wiki/Partition_type) # Offset 0x5 - 0x7: CHS address of last sector in partition (same format as first sector) # Offset 0x8 - 0xB: LBA of first sector in partition # Offset 0xC - 0xF: number of sectors in partition PARTITION_TYPE_UNUSED = 0x0 PARTITION_STATUS_ACTIVE = 0x80 (bootstrap_unused, part1, part2, part3, part4, keybyte1, keybyte2) = struct.unpack_from('=446s16s16s16s16sBB', disk_mbr, 0) if keybyte1 != 0x55 or keybyte2 != 0xAA: raise pycdlibexception.PyCdlibInvalidInput('Invalid magic on HD MBR') parts = [part1, part2, part3, part4] system_type = PARTITION_TYPE_UNUSED for part in parts: (status, s_head, s_seccyl, s_cyl, parttype, e_head, e_seccyl, e_cyl, lba_unused, num_sectors_unused) = struct.unpack('=BBBBBBBBLL', part) if parttype == PARTITION_TYPE_UNUSED: continue if system_type != PARTITION_TYPE_UNUSED: raise pycdlibexception.PyCdlibInvalidInput( 'Boot image has multiple partitions') if bootable and status != PARTITION_STATUS_ACTIVE: # genisoimage prints a warning in this case, but we have no other # warning prints in the whole codebase, and an exception will probably # make us too fragile. So we leave the code but don't do anything. with open(os.devnull, 'w') as devnull: print('Warning: partition not marked active', file=devnull) cyl = ((s_seccyl & 0xC0) << 10) | s_cyl sec = s_seccyl & 0x3f if cyl != 0 or s_head != 1 or sec != 1: # genisoimage prints a warning in this case, but we have no other # warning prints in the whole codebase, and an exception will probably # make us too fragile. So we leave the code but don't do anything. with open(os.devnull, 'w') as devnull: print('Warning: partition does not start at 0/1/1', file=devnull) cyl = ((e_seccyl & 0xC0) << 10) | e_cyl sec = e_seccyl & 0x3f geometry_sectors = (cyl + 1) * (e_head + 1) * sec if sector_count != geometry_sectors: # genisoimage prints a warning in this case, but we have no other # warning prints in the whole codebase, and an exception will probably # make us too fragile. So we leave the code but don't do anything. with open(os.devnull, 'w') as devnull: print('Warning: image size does not match geometry', file=devnull) system_type = parttype if system_type == PARTITION_TYPE_UNUSED: raise pycdlibexception.PyCdlibInvalidInput( 'Boot image has no partitions') return system_type
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
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 _add_child(self, child, logical_block_size, allow_duplicate, check_overflow): ''' An internal method to add a child to this object. Note that this is called both during parsing and when adding a new object to the system, so it it shouldn't have any functionality that is not appropriate for both. Parameters: child - The child directory record object to add. logical_block_size - The size of a logical block for this volume descriptor. allow_duplicate - Whether to allow duplicate names, as there are situations where duplicate children are allowed. check_overflow - Whether to check for overflow; if we are parsing, we don't want to do this. Returns: True if adding this child caused the directory to overflow into another extent, False otherwise. ''' if not self.isdir: raise pycdlibexception.PyCdlibInvalidInput( 'Trying to add a child to a record that is not a directory') # First ensure that this is not a duplicate. For speed purposes, we # recognize that bisect_left will always choose an index to the *left* # of a duplicate child. Thus, to check for duplicates we only need to # see if the child to be added is a duplicate with the entry that # bisect_left returned. index = bisect.bisect_left(self.children, child) if index != len(self.children) and self.children[ index].file_ident == child.file_ident: if not self.children[index].is_associated_file( ) and not child.is_associated_file(): if not (self.rock_ridge is not None and self.file_identifier() == b'RR_MOVED'): if not allow_duplicate: raise pycdlibexception.PyCdlibInvalidInput( 'Failed adding duplicate name to parent') else: self.children[index].data_continuation = child index += 1 self.children.insert(index, child) if child.rock_ridge is not None and not child.is_dot( ) and not child.is_dotdot(): lo = 0 hi = len(self.rr_children) while lo < hi: mid = (lo + hi) // 2 if self.rr_children[mid].rock_ridge.name( ) < child.rock_ridge.name(): lo = mid + 1 else: hi = mid rr_index = lo self.rr_children.insert(rr_index, child) # We now have to check if we need to add another logical block. # We have to iterate over the entire list again, because where we # placed this last entry may rearrange the empty spaces in the blocks # that we've already allocated. num_extents, offset_unused = self._recalculate_extents_and_offsets( index, logical_block_size) overflowed = False if check_overflow and (num_extents * logical_block_size > self.data_length): overflowed = True # When we overflow our data length, we always add a full block. self.data_length += logical_block_size # We also have to make sure to update the length of the dot child, # as that should always reflect the length. self.children[0].data_length = self.data_length # We also have to update all of the dotdot entries. If this is # the root directory record (no parent), we first update the root # dotdot entry. In all cases, we update the dotdot entry of all # children that are directories. if self.parent is None: self.children[1].data_length = self.data_length for c in self.children: if not c.is_dir(): continue if len(c.children) > 1: c.children[1].data_length = self.data_length return overflowed
def new(self, efi, mac, part_entry, mbr_id, part_offset, geometry_sectors, geometry_heads, part_type): # type: (bool, bool, int, Optional[int], int, int, int, int) -> None ''' Add ISO hybridization to an ISO. Parameters: efi - Whether this ISO should be setup for EFI boot. mac - Whether this ISO should be made bootable for the Macintosh. part_entry - The partition entry for the hybridization. mbr_id - The mbr_id to use for the hybridization. part_offset - The partition offset to use for the hybridization. geometry_sectors - The number of sectors to use for the hybridization. geometry_heads - The number of heads to use for the hybridization. part_type - The partition type for the hybridization. Returns: Nothing. ''' if self._initialized: raise pycdlibexception.PyCdlibInternalError( 'This IsoHybrid object is already initialized') if geometry_sectors < 1 or geometry_sectors > 63: raise pycdlibexception.PyCdlibInvalidInput( 'Geometry sectors can only be between 1 and 63, inclusive') if geometry_heads < 1 or geometry_heads > 256: raise pycdlibexception.PyCdlibInvalidInput( 'Geometry heads can only be between 1 and 256, inclusive') if mac and part_type != 0: raise pycdlibexception.PyCdlibInvalidInput( 'When generating for Mac, partition type must be 0') isohybrid_data_hd0 = b'\x33\xed\xfa\x8e\xd5\xbc\x00\x7c\xfb\xfc\x66\x31\xdb\x66\x31\xc9\x66\x53\x66\x51\x06\x57\x8e\xdd\x8e\xc5\x52\xbe\x00\x7c\xbf\x00\x06\xb9\x00\x01\xf3\xa5\xea\x4b\x06\x00\x00\x52\xb4\x41\xbb\xaa\x55\x31\xc9\x30\xf6\xf9\xcd\x13\x72\x16\x81\xfb\x55\xaa\x75\x10\x83\xe1\x01\x74\x0b\x66\xc7\x06\xf1\x06\xb4\x42\xeb\x15\xeb\x00\x5a\x51\xb4\x08\xcd\x13\x83\xe1\x3f\x5b\x51\x0f\xb6\xc6\x40\x50\xf7\xe1\x53\x52\x50\xbb\x00\x7c\xb9\x04\x00\x66\xa1\xb0\x07\xe8\x44\x00\x0f\x82\x80\x00\x66\x40\x80\xc7\x02\xe2\xf2\x66\x81\x3e\x40\x7c\xfb\xc0\x78\x70\x75\x09\xfa\xbc\xec\x7b\xea\x44\x7c\x00\x00\xe8\x83\x00\x69\x73\x6f\x6c\x69\x6e\x75\x78\x2e\x62\x69\x6e\x20\x6d\x69\x73\x73\x69\x6e\x67\x20\x6f\x72\x20\x63\x6f\x72\x72\x75\x70\x74\x2e\x0d\x0a\x66\x60\x66\x31\xd2\x66\x03\x06\xf8\x7b\x66\x13\x16\xfc\x7b\x66\x52\x66\x50\x06\x53\x6a\x01\x6a\x10\x89\xe6\x66\xf7\x36\xe8\x7b\xc0\xe4\x06\x88\xe1\x88\xc5\x92\xf6\x36\xee\x7b\x88\xc6\x08\xe1\x41\xb8\x01\x02\x8a\x16\xf2\x7b\xcd\x13\x8d\x64\x10\x66\x61\xc3\xe8\x1e\x00\x4f\x70\x65\x72\x61\x74\x69\x6e\x67\x20\x73\x79\x73\x74\x65\x6d\x20\x6c\x6f\x61\x64\x20\x65\x72\x72\x6f\x72\x2e\x0d\x0a\x5e\xac\xb4\x0e\x8a\x3e\x62\x04\xb3\x07\xcd\x10\x3c\x0a\x75\xf1\xcd\x18\xf4\xeb\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' self.mbr = isohybrid_data_hd0 self.rba = 0 # This will be set later self.mbr_id = mbr_id if self.mbr_id is None: self.mbr_id = random.getrandbits(32) self.part_entry = part_entry self.bhead = (part_offset // geometry_sectors) % geometry_heads self.bsect = (part_offset % geometry_sectors) + 1 self.bcyle = part_offset // (geometry_heads * geometry_sectors) self.bsect += (self.bcyle & 0x300) >> 2 self.bcyle &= 0xff self.ptype = part_type self.ehead = geometry_heads - 1 self.part_offset = part_offset self.geometry_heads = geometry_heads self.geometry_sectors = geometry_sectors self.mac = mac if self.mac: self.header = self.MAC_AFP self.mac_lba = 0 # this will be set later self.mac_count = 0 # this will be set later else: self.header = self.ORIG_HEADER self.efi = efi if self.efi: self.efi_lba = 0 # this will be set later self.efi_count = 0 # this will be set later self.primary_gpt.new(self.mac) self.secondary_gpt.new(self.mac) self._initialized = True
def __init__(self, pycdlib_obj): # type: (pycdlib.PyCdlib) -> None if not pycdlib_obj.has_udf(): raise pycdlibexception.PyCdlibInvalidInput( 'Can only instantiate a UDF facade for a UDF ISO') self.pycdlib_obj = pycdlib_obj