def _reshuffle_extents(self):
        # type: () -> None
        '''
        An internal method that is one of the keys of PyCdlib's ability to keep
        the in-memory metadata consistent at all times.  After making any
        changes to the ISO, most API calls end up calling this method.  This
        method will run through the entire ISO, assigning extents to each of
        the pieces of the ISO that exist.  This includes the Primary Volume
        Descriptor (which is fixed at extent 16), the Boot Records (including
        El Torito), the Supplementary Volume Descriptors (including Joliet),
        the Volume Descriptor Terminators, the Version Descriptor, the Primary
        Volume Descriptor Path Table Records (little and big endian), the
        Supplementary Volume Descriptor Path Table Records (little and big
        endian), the Primary Volume Descriptor directory records, the
        Supplementary Volume Descriptor directory records, the Rock Ridge ER
        sector, the El Torito Boot Catalog, the El Torito Initial Entry, and
        finally the data for the files.

        Parameters:
         None.
        Returns:
         Nothing.
        '''
        current_extent = 16
        for pvd in self.pvds:
            pvd.set_extent_location(current_extent)
            current_extent += 1

        for br in self.brs:
            br.set_extent_location(current_extent)
            current_extent += 1

        for svd in self.svds:
            svd.set_extent_location(current_extent)
            current_extent += 1

        for vdst in self.vdsts:
            vdst.set_extent_location(current_extent)
            current_extent += 1

        if self._has_udf:
            self.udf_bea.set_extent_location(current_extent)
            current_extent += 1

            self.udf_nsr.set_extent_location(current_extent)
            current_extent += 1

            self.udf_tea.set_extent_location(current_extent)
            current_extent += 1

        if self.version_vd is not None:
            # Save off an extent for the version descriptor
            self.version_vd.set_extent_location(current_extent)
            current_extent += 1

        part_start = 0

        log_block_size = self.pvd.logical_block_size()

        udf_files = []  # type: List[inode.Inode]
        linked_inodes = {}  # type: Dict[int, bool]
        if self._has_udf:
            if current_extent > 32:
                # There is no *requirement* in the UDF specification that the
                # UDF Volume Descriptor Sequence starts at extent 32.  It can
                # start anywhere between extents 16 and 256, as long as the
                # ISO9660 volume descriptors, the UDF Bridge Volume Recognition
                # Sequence, Main Volume Descriptor Sequence, Reserve Volume
                # Descriptor Sequence, and Logical Volume Integrity Sequence all
                # all fit, in that order.  The only way that all of these volume
                # descriptors would not fit between extents 16 and 32 is in the
                # case of many duplicate PVDs, many VDSTs, or similar.  Since
                # that is unlikely, for now we maintain compatibility with
                # genisoimage and force the UDF Main Descriptor Sequence to
                # start at 32.  We can change this later if needed.
                raise pycdlibexception.PyCdlibInternalError('Too many ISO9660 volume descriptors to fit UDF')
            current_extent = 32
            _assign_udf_desc_extents(self.udf_main_descs, current_extent)

            # ECMA TR-071 2.6 says that the volume sequence will be exactly 16
            # extents long, and we know we started at 32, so make it exactly 48.
            current_extent = 48
            _assign_udf_desc_extents(self.udf_reserve_descs, current_extent)

            # ECMA TR-071 2.6 says that the volume sequence will be exactly 16
            # extents long, and we know we started at 48, so make it exactly 64.
            current_extent = 64

            self.udf_logical_volume_integrity.set_extent_location(current_extent)
            self.udf_main_descs.logical_volume.set_integrity_location(current_extent)
            self.udf_reserve_descs.logical_volume.set_integrity_location(current_extent)
            current_extent += 1

            self.udf_logical_volume_integrity_terminator.set_extent_location(current_extent)
            current_extent += 1

            # Now assign the first UDF anchor at 256
            if len(self.udf_anchors) != 2:
                raise pycdlibexception.PyCdlibInternalError('Expected 2 UDF anchors')

            # We know that the first anchor is hard-coded at extent 256.  We
            # will have to assign the other one at the end, since it is the
            # last extent
            current_extent = 256
            self.udf_anchors[0].set_extent_location(current_extent,
                                                    self.udf_main_descs.pvd.extent_location(),
                                                    self.udf_reserve_descs.pvd.extent_location())
            current_extent += 1

            # Now assign the UDF File Set Descriptor to the beginning of the partition.
            part_start = current_extent
            self.udf_file_set.set_extent_location(part_start)
            self.udf_main_descs.partition.set_start_location(part_start)
            self.udf_reserve_descs.partition.set_start_location(part_start)
            current_extent += 1

            self.udf_file_set_terminator.set_extent_location(current_extent,
                                                             current_extent - part_start)
            current_extent += 1

            # Assignment of extents to UDF is somewhat complicated.  UDF
            # filesystems are laid out by having one extent containing a
            # File Entry that describes a directory or a file, followed by
            # an extent that contains the entries in the case of a directory.
            # All File Entries and entries containing File Identifier
            # descriptors are laid out ahead of File Entries for files.  The
            # implementation below alternates assignment to File Entries and
            # File Descriptors for all directories, and then assigns to all
            # files.  Note that data for files is assigned in the 'normal'
            # file assignment below.

            # First assign directories.
            if self.udf_root is None:
                raise pycdlibexception.PyCdlibInternalError('ISO has UDF but no UDF root; this should never happen')
            udf_file_assign_list = []
            udf_file_entries = collections.deque([(self.udf_root, None)])  # type: Deque[Tuple[udfmod.UDFFileEntry, Optional[udfmod.UDFFileIdentifierDescriptor]]]
            while udf_file_entries:
                udf_file_entry, fi_desc = udf_file_entries.popleft()

                # In theory we should check for and skip the work for files and
                # symlinks, but they will never be added to 'udf_file_entries'
                # to begin with so we can safely ignore them.

                # Set the location that the File Entry lives at, and update
                # the File Identifier Descriptor that points to it (for all
                # but the root).
                udf_file_entry.set_extent_location(current_extent,
                                                   current_extent - part_start)
                if fi_desc is not None:
                    fi_desc.set_icb(current_extent, current_extent - part_start)
                current_extent += 1

                # Now assign where the File Entry points to; for files this
                # is overwritten later, but for directories this tells us where
                # to find the extent containing the list of File Identifier
                # Descriptors that are in this directory.
                udf_file_entry.set_data_location(current_extent, current_extent - part_start)
                offset = 0
                for d in udf_file_entry.fi_descs:
                    if offset >= log_block_size:
                        # The offset has spilled over into a new extent.
                        # Increase the current extent by one, and update the
                        # offset.  Note that the offset does not go to 0, since
                        # UDF allows File Identifier Descs to span extents.
                        # Instead, it is the current offset minus the size of a
                        # block (say 2050 - 2048, leaving us at offset 2).
                        current_extent += 1
                        offset = offset - log_block_size

                    d.set_extent_location(current_extent, current_extent - part_start)
                    if not d.is_parent() and d.file_entry is not None:
                        if d.is_dir():
                            udf_file_entries.append((d.file_entry, d))
                        else:
                            udf_file_assign_list.append((d.file_entry, d))
                    offset += udfmod.UDFFileIdentifierDescriptor.length(len(d.fi))

                if offset > log_block_size:
                    current_extent += 1

                current_extent += 1

            # Now assign files (this includes symlinks).
            udf_file_entry_inodes_assigned = {}  # type: Dict[int, bool]
            for udf_file_assign_entry, fi_desc in udf_file_assign_list:
                if udf_file_assign_entry is None:
                    continue

                if udf_file_assign_entry.inode is not None and id(udf_file_assign_entry.inode) in udf_file_entry_inodes_assigned:
                    continue

                udf_file_assign_entry.set_extent_location(current_extent, current_extent - part_start)
                fi_desc.set_icb(current_extent, current_extent - part_start)

                if udf_file_assign_entry.inode is not None:
                    # The data location for files will be set later.
                    if udf_file_assign_entry.inode.get_data_length() > 0:
                        udf_files.append(udf_file_assign_entry.inode)
                    for rec in udf_file_assign_entry.inode.linked_records:
                        if isinstance(rec, udfmod.UDFFileEntry):
                            rec.set_extent_location(current_extent, current_extent - part_start)
                            if rec.file_ident is not None:
                                rec.file_ident.set_icb(current_extent, current_extent - part_start)

                    udf_file_entry_inodes_assigned[id(udf_file_assign_entry.inode)] = True

                current_extent += 1

            self.udf_logical_volume_integrity.logical_volume_contents_use.unique_id = current_extent

        # Next up, put the path table records in the right place.
        for pvd in self.pvds:
            pvd.path_table_location_le = current_extent
        current_extent += self.pvd.path_table_num_extents

        for pvd in self.pvds:
            pvd.path_table_location_be = current_extent
        current_extent += self.pvd.path_table_num_extents

        if self.enhanced_vd is not None:
            self.enhanced_vd.path_table_location_le = self.pvd.path_table_location_le
            self.enhanced_vd.path_table_location_be = self.pvd.path_table_location_be

        if self.joliet_vd is not None:
            self.joliet_vd.path_table_location_le = current_extent
            current_extent += self.joliet_vd.path_table_num_extents
            self.joliet_vd.path_table_location_be = current_extent
            current_extent += self.joliet_vd.path_table_num_extents

        self.pvd.clear_rr_ce_entries()
        current_extent, pvd_files = _reassign_vd_dirrecord_extents(self.pvd,
                                                                   current_extent)

        joliet_files = []  # type: List[inode.Inode]
        if self.joliet_vd is not None:
            current_extent, joliet_files = _reassign_vd_dirrecord_extents(self.joliet_vd,
                                                                          current_extent)

        # The rock ridge 'ER' sector must be after all of the directory
        # entries but before the file contents.
        rr = self.pvd.root_directory_record().children[0].rock_ridge
        if rr is not None and rr.dr_entries.ce_record is not None:
            rr.dr_entries.ce_record.update_extent(current_extent)
            current_extent += 1

        def _set_inode(ino, current_extent, part_start):
            # type: (inode.Inode, int, int) -> None
            '''
            Internal method to set the location of an inode and update the
            metadata of all records attached to it.

            Parameters:
             ino - The inode to update.
             current_extent - The extent to set the inode to.
             part_start - The start of the partition that the inode is on.
            Returns:
             Nothing.
            '''
            ino.set_extent_location(current_extent)
            for rec in ino.linked_records:
                rec.set_data_location(current_extent, current_extent - part_start)

        if self.eltorito_boot_catalog is not None:
            self.eltorito_boot_catalog.update_catalog_extent(current_extent)
            for rec in self.eltorito_boot_catalog.dirrecords:
                rec.set_data_location(current_extent, current_extent - part_start)
            current_extent += utils.ceiling_div(self.eltorito_boot_catalog.dirrecords[0].get_data_length(),
                                                log_block_size)

            entries_to_update = [self.eltorito_boot_catalog.initial_entry]
            for sec in self.eltorito_boot_catalog.sections:
                for entry in sec.section_entries:
                    entries_to_update.append(entry)

            for entry in entries_to_update:
                if id(entry.inode) in linked_inodes:
                    continue

                entry.set_data_location(current_extent, current_extent - part_start)
                if self.isohybrid_mbr is not None:
                    self.isohybrid_mbr.update_rba(current_extent)

                _set_inode(entry.inode, current_extent, part_start)
                linked_inodes[id(entry.inode)] = True
                current_extent += utils.ceiling_div(entry.inode.get_data_length(),
                                                    log_block_size)

        for ino in pvd_files + joliet_files + udf_files:
            if id(ino) in linked_inodes:
                # We've already assigned an extent because it was linked to an
                # earlier entry.
                continue

            _set_inode(ino, current_extent, part_start)

            linked_inodes[id(ino)] = True

            current_extent += utils.ceiling_div(ino.get_data_length(),
                                                log_block_size)

        if self.enhanced_vd is not None:
            loc = self.pvd.root_directory_record().extent_location()
            self.enhanced_vd.root_directory_record().set_data_location(loc, loc)

        if self.udf_anchors:
            self.udf_anchors[1].set_extent_location(current_extent,
                                                    self.udf_main_descs.pvd.extent_location(),
                                                    self.udf_reserve_descs.pvd.extent_location())

        if current_extent > self.pvd.space_size:
            raise pycdlibexception.PyCdlibInternalError('Assigned an extent beyond the ISO (%d > %d)' % (current_extent, self.pvd.space_size))

        self._needs_reshuffle = False