Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pycdlib/headervd.py
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,12 @@ def add_rr_ce_entry(self, length):
block = rockridge.RockRidgeContinuationBlock(0, self.log_block_size)
self.rr_ce_blocks.append(block)
offset = block.add_entry(length)
if offset is None:
# A brand new block had no room, so this entry is larger than a
# whole logical block. Callers split their entries into areas
# that each fit before getting here, so this means the caller
# got that wrong rather than that the ISO cannot be built.
raise pycdlibexception.PyCdlibInternalError('Rock Ridge Continuation Entry of length %d is too large to fit into a Continuation Block of size %d' % (length, self.log_block_size))
added_block = True

return (added_block, block, offset)
Expand Down
147 changes: 105 additions & 42 deletions pycdlib/pycdlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import Any, BinaryIO, Callable, Deque, Dict, Generator, IO, List, Optional, Tuple, Union # noqa: F401
from typing import Any, BinaryIO, Callable, Deque, Dict, Generator, IO, List, Optional, Set, Tuple, Union # noqa: F401

# There are a number of specific ways that numerical data is stored in the
# ISO9660/Ecma-119 standard. In the text these are reference by the section
Expand Down Expand Up @@ -363,11 +363,12 @@ def _reassign_vd_dirrecord_extents(vd, current_extent):
file_list.append(dir_record.inode)

if dir_record_rock_ridge is not None:
if dir_record_rock_ridge.dr_entries.ce_record is not None and dir_record_rock_ridge.ce_block is not None:
if dir_record_rock_ridge.ce_block.extent_location() < 0:
dir_record_rock_ridge.ce_block.set_extent_location(current_extent)
current_extent += 1
dir_record_rock_ridge.dr_entries.ce_record.update_extent(dir_record_rock_ridge.ce_block.extent_location())
if dir_record_rock_ridge.dr_entries.ce_record is not None and dir_record_rock_ridge.ce_areas:
for ce_area in dir_record_rock_ridge.ce_areas:
if ce_area.block.extent_location() < 0:
ce_area.block.set_extent_location(current_extent)
current_extent += 1
dir_record_rock_ridge.dr_entries.ce_record.update_extent(dir_record_rock_ridge.ce_areas[0].extent_location())
if dir_record_rock_ridge.cl_to_moved_dr is not None:
child_link_recs.append(dir_record)

Expand Down Expand Up @@ -533,16 +534,15 @@ def _find_dr_record_by_name(vd, path, encoding):
class PyCdlib:
"""The main class for manipulating ISOs."""
__slots__ = ('_initialized', '_cdfp', 'pvds', 'svds', 'vdsts', 'brs', 'pvd',
'rock_ridge', '_always_consistent', '_has_udf', 'joliet_vd',
'eltorito_boot_catalog', 'isohybrid_mbr', '_managing_fp', 'xa',
'_needs_reshuffle', '_rr_moved_record', '_rr_moved_name',
'_rr_moved_rr_name', 'enhanced_vd', 'version_vd', 'inodes',
'interchange_level', '_write_check_list', '_track_writes',
'udf_beas', 'udf_nsr', 'udf_teas', 'udf_anchors',
'udf_main_descs', 'udf_reserve_descs',
'udf_logical_volume_integrity', 'udf_boots',
'udf_logical_volume_integrity_terminator', 'udf_root',
'udf_file_set', 'udf_file_set_terminator',
'rock_ridge', '_always_consistent', '_has_udf', 'joliet_vd', 'eltorito_boot_catalog', 'isohybrid_mbr',
'_managing_fp', 'xa', '_needs_reshuffle', '_rr_moved_record',
'_rr_moved_name', '_rr_moved_rr_name', 'enhanced_vd',
'version_vd', 'inodes', 'interchange_level',
'_write_check_list', '_track_writes', 'udf_beas', 'udf_nsr',
'udf_teas', 'udf_anchors', 'udf_main_descs',
'udf_reserve_descs', 'udf_logical_volume_integrity',
'udf_boots', 'udf_logical_volume_integrity_terminator',
'udf_root', 'udf_file_set', 'udf_file_set_terminator',
'logical_block_size')

def _initialize(self):
Expand Down Expand Up @@ -1163,19 +1163,46 @@ def _walk_directories(self, vd, extent_to_ptr, extent_to_inode,

rr_ce = ''
if new_record.rock_ridge is not None and new_record.rock_ridge.dr_entries.ce_record is not None:
ce_record = new_record.rock_ridge.dr_entries.ce_record
ce_record = new_record.rock_ridge.dr_entries.ce_record # type: Optional[rockridge.RRCERecord]
orig_pos = cdfp.tell()
self._seek_to_extent(ce_record.bl_cont_area)
cdfp.seek(ce_record.offset_cont_area, os.SEEK_CUR)
con_block = cdfp.read(ce_record.len_cont_area)
new_record.rock_ridge.parse(con_block, False,
new_record.rock_ridge.bytes_to_skip,
True, new_record.file_identifier())
# A continuation area may itself end with a CE record
# pointing at a further area, chaining as many times as
# needed to hold the entries. Follow the whole chain,
# remembering where we have been so that an ISO whose CE
# records form a cycle cannot spin us forever.
seen_ce_areas = set() # type: Set[Tuple[int, int, int]]
num_ce_areas = 0
while ce_record is not None:
area = (ce_record.bl_cont_area,
ce_record.offset_cont_area,
ce_record.len_cont_area)
if area in seen_ce_areas:
raise pycdlibexception.PyCdlibInvalidISO('Rock Ridge Continuation Entries form a loop')
seen_ce_areas.add(area)
num_ce_areas += 1

self._seek_to_extent(ce_record.bl_cont_area)
cdfp.seek(ce_record.offset_cont_area, os.SEEK_CUR)
con_block = cdfp.read(ce_record.len_cont_area)
new_record.rock_ridge.parse(con_block, False,
new_record.rock_ridge.bytes_to_skip,
True, new_record.file_identifier())
block = self.pvd.track_rr_ce_entry(ce_record.bl_cont_area,
ce_record.offset_cont_area,
ce_record.len_cont_area)
new_record.rock_ridge.add_ce_area(block,
ce_record.offset_cont_area,
ce_record.len_cont_area)

# Parsing an area stores any CE it contained in
# ce_entries; take it as the next link and clear it so
# the following area can carry one of its own, and so
# that no stale link is left behind at the end.
ce_entries = new_record.rock_ridge.ce_entries
ce_record = ce_entries.ce_record if ce_entries is not None else None
if ce_entries is not None:
ce_entries.ce_record = None
cdfp.seek(orig_pos)
block = self.pvd.track_rr_ce_entry(ce_record.bl_cont_area,
ce_record.offset_cont_area,
ce_record.len_cont_area)
new_record.rock_ridge.update_ce_block(block)

rr_ce = new_record.rock_ridge.rr_version if new_record.rock_ridge else ''
# The PX record could be in the continuation blob, so
Expand Down Expand Up @@ -2787,12 +2814,35 @@ def _write_directory_records(self, vd, outfp, progress):

if child.rock_ridge is not None:
if child.rock_ridge.dr_entries.ce_record is not None:
# The child has a continue block, so write it out here.
ce_rec = child.rock_ridge.dr_entries.ce_record
outfp.seek(ce_rec.bl_cont_area * self.logical_block_size + ce_rec.offset_cont_area)
rec = child.rock_ridge.record_ce_entries()
self._outfp_write_with_check(outfp, rec)
progress.call(len(rec))
# The child has continuation areas, so write them out
# here. There is usually just the one, but where the
# entries do not fit they are chained across several.
ce_areas = child.rock_ridge.ce_areas
if ce_areas:
ce_rec = child.rock_ridge.dr_entries.ce_record
for ce_area, rec in zip(ce_areas, child.rock_ridge.record_ce_areas()):
extent = ce_area.extent_location()
if extent < 0:
# Reshuffling deliberately skips the dot and
# dotdot records, so their Continuation
# Blocks never get an extent assigned and
# they keep the one they were parsed with.
# Their entries are small and always fit in
# a single area, so there is no chain here.
extent = ce_rec.bl_cont_area
outfp.seek(extent * self.logical_block_size + ce_area.offset)
self._outfp_write_with_check(outfp, rec)
progress.call(len(rec))
else:
# The Rock Ridge 'ER' area of the root gets an
# extent of its own rather than a slot in a
# Continuation Block, so it has no area tracked
# against it; it is always small enough for one.
ce_rec = child.rock_ridge.dr_entries.ce_record
outfp.seek(ce_rec.bl_cont_area * self.logical_block_size + ce_rec.offset_cont_area)
rec = child.rock_ridge.record_ce_entries()
self._outfp_write_with_check(outfp, rec)
progress.call(len(rec))

if child.rock_ridge.child_link_record_exists():
continue
Expand Down Expand Up @@ -3076,12 +3126,25 @@ def _update_rr_ce_entry(self, rec):
The number of additional bytes needed for this Rock Ridge CE entry.
"""
if rec.rock_ridge is not None and rec.rock_ridge.dr_entries.ce_record is not None:
celen = rec.rock_ridge.dr_entries.ce_record.len_cont_area
added_block, block, offset = self.pvd.add_rr_ce_entry(celen)
rec.rock_ridge.update_ce_block(block)
rec.rock_ridge.dr_entries.ce_record.update_offset(offset)
if added_block:
return self.logical_block_size
# The entries may need more than one area to hold them, in which
# case each area but the last ends with a CE record linking to the
# next. Allocate them all; they need not be adjacent, or even in
# the same Continuation Block.
rec.rock_ridge.clear_ce_areas()
num_bytes_to_add = 0
for celen in rec.rock_ridge.ce_area_lengths(self.logical_block_size):
added_block, block, offset = self.pvd.add_rr_ce_entry(celen)
rec.rock_ridge.add_ce_area(block, offset, celen)
if added_block:
num_bytes_to_add += self.logical_block_size

# The CE record in the directory record describes the first area
# only; the rest are reached by following the chain.
first = rec.rock_ridge.ce_areas[0]
rec.rock_ridge.dr_entries.ce_record.update_offset(first.offset)
rec.rock_ridge.dr_entries.ce_record.update_len(first.length)

return num_bytes_to_add

return 0

Expand Down Expand Up @@ -5446,9 +5509,9 @@ def rm_directory(self, iso_path=None, rr_name=None, joliet_path=None, # pylint:
# child_link record because it is a 'fake' record that has no
# size.

if child.rock_ridge is not None and child.rock_ridge.dr_entries.ce_record is not None and child.rock_ridge.ce_block is not None:
child.rock_ridge.ce_block.remove_entry(child.rock_ridge.dr_entries.ce_record.offset_cont_area,
child.rock_ridge.dr_entries.ce_record.len_cont_area)
if child.rock_ridge is not None and child.rock_ridge.dr_entries.ce_record is not None:
for ce_area in child.rock_ridge.ce_areas:
ce_area.block.remove_entry(ce_area.offset, ce_area.length)

if joliet_path is not None:
num_bytes_to_remove += self._rm_joliet_dir(self._normalize_joliet_path(joliet_path))
Expand Down
Loading
Loading