@@ -62,13 +62,32 @@ func (i *Image) RootDir() (*File, error) {
6262 return nil , os .ErrNotExist
6363}
6464
65+ // RootDir returns the label of the first Primary Volume
66+ func (i * Image ) Label () (string , error ) {
67+ for _ , vd := range i .volumeDescriptors {
68+ if vd .Type () == volumeTypePrimary {
69+ return string (vd .Primary .VolumeIdentifier ), nil
70+ }
71+ }
72+ return "" , os .ErrNotExist
73+ }
74+
75+ // extent describes a single contiguous region of data on the disc.
76+ type extent struct {
77+ Location int32
78+ Length uint32
79+ }
80+
6581// File is a os.FileInfo-compatible wrapper around an ISO9660 directory entry
6682type File struct {
6783 ra io.ReaderAt
6884 de * DirectoryEntry
6985 children []* File
7086 isRootDir bool
7187 susp * SUSPMetadata
88+ // extents holds all extents for multi-extent files (ECMA-119 9.1.6).
89+ // For single-extent files this is nil and de.ExtentLocation/ExtentLength are used directly.
90+ extents []extent
7291}
7392
7493var _ os.FileInfo = & File {}
@@ -79,6 +98,12 @@ func (f *File) hasRockRidge() bool {
7998
8099// IsDir returns true if the entry is a directory or false otherwise
81100func (f * File ) IsDir () bool {
101+ if f .hasRockRidge () {
102+ if mode , err := f .de .SystemUseEntries .GetPosixAttr (); err == nil {
103+ return mode & os .ModeDir != 0
104+ }
105+ }
106+
82107 return f .de .FileFlags & dirFlagDir != 0
83108}
84109
@@ -92,8 +117,15 @@ func (f *File) ModTime() time.Time {
92117 return time .Time (f .de .RecordingDateTime )
93118}
94119
95- // Mode returns os.FileMode flag set with the os.ModeDir flag enabled in case of directories
120+ // Mode returns file mode when available.
121+ // Otherwise it returns os.FileMode flag set with the os.ModeDir flag enabled in case of directories.
96122func (f * File ) Mode () os.FileMode {
123+ if f .hasRockRidge () {
124+ if mode , err := f .de .SystemUseEntries .GetPosixAttr (); err == nil {
125+ return mode
126+ }
127+ }
128+
97129 var mode os.FileMode
98130 if f .IsDir () {
99131 mode |= os .ModeDir
@@ -135,8 +167,16 @@ func (f *File) Name() string {
135167 return fileIdentifier
136168}
137169
138- // Size returns the size in bytes of the extent occupied by the file or directory
170+ // Size returns the size in bytes of the extent occupied by the file or directory.
171+ // For multi-extent files, this returns the total size across all extents.
139172func (f * File ) Size () int64 {
173+ if len (f .extents ) > 0 {
174+ var total int64
175+ for _ , ext := range f .extents {
176+ total += int64 (ext .Length )
177+ }
178+ return total
179+ }
140180 return int64 (f .de .ExtentLength )
141181}
142182
@@ -158,6 +198,11 @@ func (f *File) GetAllChildren() ([]*File, error) {
158198
159199 baseOffset := uint32 (f .de .ExtentLocation ) * sectorSize
160200
201+ // pendingExtents collects extents for a multi-extent file (ECMA-119 9.1.6).
202+ // When we see directory records with the multi-extent flag set, we accumulate
203+ // their extents here until we reach the final record (without the flag).
204+ var pendingExtents []extent
205+
161206 buffer := make ([]byte , sectorSize )
162207 for bytesProcessed := uint32 (0 ); bytesProcessed < uint32 (f .de .ExtentLength ); bytesProcessed += sectorSize {
163208 if _ , err := f .ra .ReadAt (buffer , int64 (baseOffset + bytesProcessed )); err != nil {
@@ -212,12 +257,34 @@ func (f *File) GetAllChildren() ([]*File, error) {
212257
213258 i += entryLength
214259
215- newFile := & File {ra : f .ra ,
260+ // Check for multi-extent flag (ECMA-119 9.1.6, bit 7 of FileFlags).
261+ // When set, this directory record is not the final one for this file.
262+ // Consecutive records should have their extents concatenated.
263+ if newDE .FileFlags & dirFlagMultiExtent != 0 {
264+ pendingExtents = append (pendingExtents , extent {
265+ Location : newDE .ExtentLocation ,
266+ Length : newDE .ExtentLength ,
267+ })
268+ continue
269+ }
270+
271+ newFile := & File {
272+ ra : f .ra ,
216273 de : newDE ,
217274 children : nil ,
218275 susp : f .susp .Clone (),
219276 }
220277
278+ // If we accumulated multi-extent records, finalize them now.
279+ if len (pendingExtents ) > 0 {
280+ pendingExtents = append (pendingExtents , extent {
281+ Location : newDE .ExtentLocation ,
282+ Length : newDE .ExtentLength ,
283+ })
284+ newFile .extents = pendingExtents
285+ pendingExtents = nil
286+ }
287+
221288 f .children = append (f .children , newFile )
222289 }
223290 }
@@ -264,11 +331,22 @@ func (f *File) GetDotEntry() (*File, error) {
264331
265332// Reader returns a reader that allows to read the file's data.
266333// If File is a directory, it returns nil.
334+ // For multi-extent files (ECMA-119 9.1.6), the returned reader
335+ // seamlessly reads across all extents.
267336func (f * File ) Reader () io.Reader {
268337 if f .IsDir () {
269338 return nil
270339 }
271340
341+ if len (f .extents ) > 1 {
342+ readers := make ([]io.Reader , len (f .extents ))
343+ for i , ext := range f .extents {
344+ offset := int64 (ext .Location ) * int64 (sectorSize )
345+ readers [i ] = io .NewSectionReader (f .ra , offset , int64 (ext .Length ))
346+ }
347+ return io .MultiReader (readers ... )
348+ }
349+
272350 baseOffset := int64 (f .de .ExtentLocation ) * int64 (sectorSize )
273351 return io .NewSectionReader (f .ra , baseOffset , int64 (f .de .ExtentLength ))
274352}
0 commit comments