@@ -288,8 +288,8 @@ def _do_rm_file(iso, iso_path):
288288
289289
290290def _do_add_fp (iso , data_source , length , manage_fp , iso_path ,
291- joliet_path = None , file_mode = None ):
292- # type: (PyCdlib, Union[BinaryIO, str], int, bool, str, Optional[str], Optional[int]) -> None
291+ rr_name = None , joliet_path = None , file_mode = None ):
292+ # type: (PyCdlib, Union[BinaryIO, str], int, bool, str, Optional[str], Optional[str], Optional[ int]) -> None
293293 """
294294 Implementation of in-place file addition. Operates on an open
295295 :class:`pycdlib.PyCdlib` object's internal state; not a public API.
@@ -302,10 +302,14 @@ def _do_add_fp(iso, data_source, length, manage_fp, iso_path,
302302 extent must have enough slack to hold one more record.
303303
304304 Refuses on:
305- - UDF, Rock Ridge, or El Torito ISOs (out of scope for v1).
305+ - UDF or El Torito ISOs (out of scope for v1).
306306 - A target path whose parent's directory extent would need to
307307 grow to fit the new record (would require relocating the parent,
308308 cascading layout changes).
309+ - Rock Ridge metadata that would require allocating a new
310+ Continuation Entry (CE) block -- this happens when the SUSP
311+ fields don't fit inside the directory record (e.g., very long
312+ ``rr_name``). Short-name Rock Ridge is supported.
309313 """
310314 if not iso ._initialized : # pylint: disable=protected-access
311315 raise pycdlibexception .PyCdlibInvalidInput ('This object is not initialized; call either open() or new() to create an ISO' )
@@ -315,11 +319,16 @@ def _do_add_fp(iso, data_source, length, manage_fp, iso_path,
315319
316320 if iso .udf_root is not None :
317321 raise pycdlibexception .PyCdlibInvalidInput ('InPlaceEditor.add_fp does not support UDF ISOs yet; use PyCdlib.add_fp + write_fp() to produce a new ISO instead' )
318- if iso .rock_ridge :
319- raise pycdlibexception .PyCdlibInvalidInput ('InPlaceEditor.add_fp does not support Rock Ridge ISOs yet; use PyCdlib.add_fp + write_fp() to produce a new ISO instead' )
320322 if iso .eltorito_boot_catalog is not None :
321323 raise pycdlibexception .PyCdlibInvalidInput ('InPlaceEditor.add_fp does not support El Torito ISOs yet; use PyCdlib.add_fp + write_fp() to produce a new ISO instead' )
322324
325+ # Rock Ridge: rr_name must be present iff the ISO is Rock Ridge,
326+ # matching PyCdlib.add_fp's contract.
327+ if iso .rock_ridge and rr_name is None :
328+ raise pycdlibexception .PyCdlibInvalidInput ('Must specify an rr_name when adding a file to a Rock Ridge ISO' )
329+ if not iso .rock_ridge and rr_name is not None :
330+ raise pycdlibexception .PyCdlibInvalidInput ('Cannot specify an rr_name on a non-Rock-Ridge ISO' )
331+
323332 # Resolve the ISO9660 path's parent and leaf name.
324333 iso_path_bytes = utils .normpath (iso_path )
325334 (iso_name , iso_parent ) = iso ._iso_name_and_parent_from_path (iso_path_bytes ) # pylint: disable=protected-access
@@ -336,26 +345,40 @@ def _do_add_fp(iso, data_source, length, manage_fp, iso_path,
336345 # Reserve the new file's data extent at the current end of the ISO.
337346 new_data_extent = iso .pvd .space_size
338347
339- fmode = file_mode if file_mode is not None else 0
348+ # Default Rock Ridge file mode matches PyCdlib's add_fp default
349+ # for managed-fp callers (regular file, r--r--r--).
350+ if file_mode is None :
351+ fmode = 0o0100444 if iso .rock_ridge else 0
352+ else :
353+ fmode = file_mode
340354
341355 # Create the shared Inode that backs every linked record.
342356 ino = inode .Inode ()
343357 ino .new (length , data_source , manage_fp , 0 )
344358 ino .set_extent_location (new_data_extent )
345359
346- # Create the ISO9660 directory record, link it to the inode, and
347- # attempt to add it to the parent's children list. add_child
348- # returns True if the parent would have to grow its data_length to
349- # fit the new record -- in-place is a fixed-allocation primitive,
350- # so we treat that as a hard failure and roll the change back.
360+ # Create the ISO9660 directory record. For a Rock Ridge ISO we
361+ # pass the version and rr_name through so the SUSP entries land in
362+ # the record; for a plain ISO9660 ISO we pass empty placeholders.
363+ rr_version = iso .rock_ridge if iso .rock_ridge else ''
364+ rr_name_bytes = rr_name .encode ('utf-8' ) if rr_name is not None else b''
365+
351366 new_iso_rec = dr .DirectoryRecord ()
352367 new_iso_rec .new_file (iso .pvd , length , iso_name , iso_parent ,
353368 iso .pvd .sequence_number (),
354- '' , b'' , iso .xa , fmode ,
369+ rr_version , rr_name_bytes , iso .xa , fmode ,
355370 time .time (), None )
356371 new_iso_rec .set_data_location (new_data_extent , 0 )
357372 new_iso_rec .inode = ino
358373
374+ # If Rock Ridge couldn't fit all its SUSP fields inline, the
375+ # record now has a CE (Continuation Entry) referencing a separate
376+ # block. In-place add can't grow the on-disk RR CE allocation, so
377+ # we refuse the add up front. Catch this *before* mutating the
378+ # parent so there's nothing to roll back.
379+ if new_iso_rec .rock_ridge is not None and new_iso_rec .rock_ridge .dr_entries .ce_record is not None :
380+ raise pycdlibexception .PyCdlibInvalidInput ("Adding this file requires a Rock Ridge Continuation Entry (CE) block, which in-place add cannot allocate; use PyCdlib.add_fp + write_fp() to produce a new ISO instead" )
381+
359382 iso_overflowed = iso_parent .add_child (new_iso_rec , iso .logical_block_size )
360383 if iso_overflowed :
361384 iso_parent .remove_child (new_iso_rec , new_iso_rec .index_in_parent , iso .logical_block_size )
@@ -517,8 +540,9 @@ def rm_file(self, iso_path):
517540 """
518541 _do_rm_file (self ._iso , iso_path )
519542
520- def add_fp (self , fp , length , iso_path , joliet_path = None , file_mode = None ):
521- # type: (BinaryIO, int, str, Optional[str], Optional[int]) -> None
543+ def add_fp (self , fp , length , iso_path , rr_name = None , joliet_path = None ,
544+ file_mode = None ):
545+ # type: (BinaryIO, int, str, Optional[str], Optional[str], Optional[int]) -> None
522546 """
523547 Add a new file to the ISO in place, reading the content from
524548 a file-like object.
@@ -529,8 +553,12 @@ def add_fp(self, fp, length, iso_path, joliet_path=None, file_mode=None):
529553 existing extent on disk.
530554
531555 Constraints:
532- - The ISO must not be a UDF, Rock Ridge, or El Torito ISO
533- (out of scope for v1).
556+ - The ISO must not be a UDF or El Torito ISO (out of scope
557+ for v1).
558+ - On a Rock Ridge ISO, ``rr_name`` must be provided. Rock
559+ Ridge SUSP fields whose serialized length would require a
560+ new Continuation Entry block are refused (this typically
561+ means very long ``rr_name`` values; short names are fine).
534562 - The target's parent directory must already exist on the ISO.
535563 - The parent's directory extent must have enough slack to
536564 hold one more record; if not, this raises and you must use
@@ -543,18 +571,22 @@ def add_fp(self, fp, length, iso_path, joliet_path=None, file_mode=None):
543571 length - The length of the new content.
544572 iso_path - The ISO9660 absolute path identifying where to
545573 insert the file.
574+ rr_name - Rock Ridge name; required on Rock Ridge ISOs and
575+ rejected on non-Rock-Ridge ISOs.
546576 joliet_path - The Joliet absolute path; if provided, a Joliet
547577 directory record is also inserted.
548- file_mode - File-mode bits (ignored on non-Rock-Ridge ISOs;
549- since v1 refuses Rock Ridge, this is informational).
578+ file_mode - File-mode bits (Rock Ridge only).
550579 Returns:
551580 Nothing.
552581 """
553582 _do_add_fp (self ._iso , fp , length , False , iso_path ,
554- joliet_path = joliet_path , file_mode = file_mode )
583+ rr_name = rr_name ,
584+ joliet_path = joliet_path ,
585+ file_mode = file_mode )
555586
556- def add_file (self , filename , iso_path , joliet_path = None , file_mode = None ):
557- # type: (str, str, Optional[str], Optional[int]) -> None
587+ def add_file (self , filename , iso_path , rr_name = None , joliet_path = None ,
588+ file_mode = None ):
589+ # type: (str, str, Optional[str], Optional[str], Optional[int]) -> None
558590 """
559591 Add a new file to the ISO in place, reading the content from
560592 a file on the local filesystem.
@@ -569,12 +601,17 @@ def add_file(self, filename, iso_path, joliet_path=None, file_mode=None):
569601 file's content on the ISO.
570602 iso_path - The ISO9660 absolute path identifying where to
571603 insert the file.
604+ rr_name - Rock Ridge name; required on Rock Ridge ISOs and
605+ rejected on non-Rock-Ridge ISOs.
572606 joliet_path - The Joliet absolute path; if provided, a Joliet
573607 directory record is also inserted.
574- file_mode - File-mode bits (ignored on non- Rock- Ridge ISOs ).
608+ file_mode - File-mode bits (Rock Ridge only ).
575609 Returns:
576610 Nothing.
577611 """
578612 import os # pylint: disable=import-outside-toplevel
579613 _do_add_fp (self ._iso , filename , os .stat (filename ).st_size , True ,
580- iso_path , joliet_path = joliet_path , file_mode = file_mode )
614+ iso_path ,
615+ rr_name = rr_name ,
616+ joliet_path = joliet_path ,
617+ file_mode = file_mode )
0 commit comments