@@ -325,10 +325,11 @@ func (header *Header) Readable() error {
325
325
switch i {
326
326
case IncompatibleFeaturesDirtyBit , IncompatibleFeaturesCorruptBit :
327
327
log .Warnf ("unexpected incompatible feature bit: %q" , IncompatibleFeaturesNames [i ])
328
+ case IncompatibleFeaturesExtendedL2EntriesBit :
329
+ log .Warnf ("Support for %q is experimental" , IncompatibleFeaturesNames [i ])
328
330
case IncompatibleFeaturesCompressionTypeBit :
329
331
// NOP
330
- case IncompatibleFeaturesExternalDataFileBit ,
331
- IncompatibleFeaturesExtendedL2EntriesBit :
332
+ case IncompatibleFeaturesExternalDataFileBit :
332
333
return fmt .Errorf ("%w: incompatible feature: %q" , ErrUnsupportedFeature , IncompatibleFeaturesNames [i ])
333
334
default :
334
335
return fmt .Errorf ("%w: incompatible feature bit %d" , ErrUnsupportedFeature , i )
@@ -466,13 +467,13 @@ func (x l2TableEntry) compressed() bool {
466
467
return (x >> 62 )& 0b1 == 0b1
467
468
}
468
469
469
- /*
470
470
// extendedL2TableEntry is not supported yet
471
471
type extendedL2TableEntry struct {
472
- l2TableEntry
473
- ext uint64
472
+ L2TableEntry l2TableEntry
473
+ // the following bitmaps are meaningless for compressed clusters
474
+ ZeroStatusBitmap uint32 // 1: reads as zeros, 0: no effect
475
+ AllocStatusBitmap uint32 // 1: allocated, 0: not allocated
474
476
}
475
- */
476
477
477
478
func readL2Table (ra io.ReaderAt , offset uint64 , clusterSize int ) ([]l2TableEntry , error ) {
478
479
if offset == 0 {
@@ -487,6 +488,19 @@ func readL2Table(ra io.ReaderAt, offset uint64, clusterSize int) ([]l2TableEntry
487
488
return l2Table , nil
488
489
}
489
490
491
+ func readExtendedL2Table (ra io.ReaderAt , offset uint64 , clusterSize int ) ([]extendedL2TableEntry , error ) {
492
+ if offset == 0 {
493
+ return nil , errors .New ("invalid extended L2 table offset: 0" )
494
+ }
495
+ r := io .NewSectionReader (ra , int64 (offset ), int64 (clusterSize ))
496
+ entries := clusterSize / 16
497
+ extL2Table := make ([]extendedL2TableEntry , entries )
498
+ if err := binary .Read (r , binary .BigEndian , & extL2Table ); err != nil {
499
+ return nil , err
500
+ }
501
+ return extL2Table , nil
502
+ }
503
+
490
504
type standardClusterDescriptor uint64
491
505
492
506
func (desc standardClusterDescriptor ) allZero () bool {
@@ -662,9 +676,16 @@ func (img *Qcow2) Readable() error {
662
676
return img .errUnreadable
663
677
}
664
678
679
+ func (img * Qcow2 ) extendedL2 () bool {
680
+ return img .Header .HeaderFieldsV3 != nil && img .Header .HeaderFieldsV3 .IncompatibleFeatures & (1 << IncompatibleFeaturesExtendedL2EntriesBit ) != 0
681
+ }
682
+
665
683
// readAtAligned requires that off and off+len(p)-1 belong to the same cluster.
666
684
func (img * Qcow2 ) readAtAligned (p []byte , off int64 ) (int , error ) {
667
685
l2Entries := img .clusterSize / 8
686
+ if img .extendedL2 () {
687
+ l2Entries = img .clusterSize / 16
688
+ }
668
689
l1Index := int ((off / int64 (img .clusterSize )) / int64 (l2Entries ))
669
690
if l1Index >= len (img .l1Table ) {
670
691
return 0 , fmt .Errorf ("index %d exceeds the L1 table length %d" , l1Index , img .l1Table )
@@ -674,20 +695,40 @@ func (img *Qcow2) readAtAligned(p []byte, off int64) (int, error) {
674
695
if l2TableOffset == 0 {
675
696
return img .readAtAlignedUnallocated (p , off )
676
697
}
677
- l2Table , err := readL2Table (img .ra , l2TableOffset , img .clusterSize )
678
- if err != nil {
679
- return 0 , fmt .Errorf ("failed to read L2 table for L1 entry %v (index %d): %w" , l1Entry , l1Index , err )
680
- }
681
698
l2Index := int ((off / int64 (img .clusterSize )) % int64 (l2Entries ))
682
- if l2Index >= len (l2Table ) {
683
- return 0 , fmt .Errorf ("index %d exceeds the L2 table length %d" , l2Index , l2Table )
699
+ var (
700
+ extL2Entry * extendedL2TableEntry
701
+ l2Entry l2TableEntry
702
+ )
703
+ if img .extendedL2 () {
704
+ // TODO
705
+ extL2Table , err := readExtendedL2Table (img .ra , l2TableOffset , img .clusterSize )
706
+ if err != nil {
707
+ return 0 , fmt .Errorf ("failed to read extended L2 table for L1 entry %v (index %d): %w" , l1Entry , l1Index , err )
708
+ }
709
+ if l2Index >= len (extL2Table ) {
710
+ return 0 , fmt .Errorf ("index %d exceeds the extended L2 table length %d" , l2Index , extL2Table )
711
+ }
712
+ extL2Entry = & extL2Table [l2Index ]
713
+ l2Entry = extL2Entry .L2TableEntry
714
+ } else {
715
+ l2Table , err := readL2Table (img .ra , l2TableOffset , img .clusterSize )
716
+ if err != nil {
717
+ return 0 , fmt .Errorf ("failed to read L2 table for L1 entry %v (index %d): %w" , l1Entry , l1Index , err )
718
+ }
719
+ if l2Index >= len (l2Table ) {
720
+ return 0 , fmt .Errorf ("index %d exceeds the L2 table length %d" , l2Index , l2Table )
721
+ }
722
+ l2Entry = l2Table [l2Index ]
684
723
}
685
- l2Entry := l2Table [l2Index ]
686
724
desc := l2Entry .clusterDescriptor ()
687
- if desc == 0 {
725
+ if desc == 0 && ! img . extendedL2 () {
688
726
return img .readAtAlignedUnallocated (p , off )
689
727
}
690
- var n int
728
+ var (
729
+ n int
730
+ err error
731
+ )
691
732
if l2Entry .compressed () {
692
733
compressedDesc := compressedClusterDescriptor (desc )
693
734
n , err = img .readAtAlignedCompressed (p , off , compressedDesc )
@@ -696,9 +737,16 @@ func (img *Qcow2) readAtAligned(p []byte, off int64) (int, error) {
696
737
}
697
738
} else {
698
739
standardDesc := standardClusterDescriptor (desc )
699
- n , err = img .readAtAlignedStandard (p , off , standardDesc )
700
- if err != nil {
701
- err = fmt .Errorf ("failed to read standard cluster (len=%d, off-%d, desc=0x%X): %w" , len (p ), off , desc , err )
740
+ if img .extendedL2 () {
741
+ n , err = img .readAtAlignedStandardExtendedL2 (p , off , standardDesc , * extL2Entry )
742
+ if err != nil {
743
+ err = fmt .Errorf ("failed to read standard cluster with Extended L2 (len=%d, off=%d, desc=0x%X): %w" , len (p ), off , desc , err )
744
+ }
745
+ } else {
746
+ n , err = img .readAtAlignedStandard (p , off , standardDesc )
747
+ if err != nil {
748
+ err = fmt .Errorf ("failed to read standard cluster (len=%d, off=%d, desc=0x%X): %w" , len (p ), off , desc , err )
749
+ }
702
750
}
703
751
}
704
752
return n , err
@@ -744,6 +792,57 @@ func (img *Qcow2) readAtAlignedStandard(p []byte, off int64, desc standardCluste
744
792
return n , err
745
793
}
746
794
795
+ // readAtAlignedStandardExtendedL2 is experimental
796
+ //
797
+ // TODO: read multiple subclusters at once
798
+ //
799
+ // clusterNo = LBA / clusterSize
800
+ // subclusterNo = (LBA % clusterSize) / subclusterSize
801
+ func (img * Qcow2 ) readAtAlignedStandardExtendedL2 (p []byte , off int64 , desc standardClusterDescriptor , extL2Entry extendedL2TableEntry ) (int , error ) {
802
+ var n int
803
+ subclusterSize := img .clusterSize / 32
804
+ hostClusterOffset := desc .hostClusterOffset ()
805
+ subclusterNo := (int (off ) % img .clusterSize ) / subclusterSize
806
+ for i := subclusterNo ; i < 32 ; i ++ {
807
+ pIdxBegin := n
808
+ pIdxEnd := n + subclusterSize
809
+ if pIdxEnd > len (p ) {
810
+ pIdxEnd = len (p )
811
+ }
812
+ if pIdxEnd <= pIdxBegin {
813
+ break
814
+ }
815
+ var (
816
+ currentN int
817
+ err error
818
+ )
819
+ if ((extL2Entry .AllocStatusBitmap >> i ) & 0b1 ) == 0b1 {
820
+ currentRawOff := int64 (hostClusterOffset ) + (off % int64 (img .clusterSize )) + int64 (n )
821
+ currentN , err = img .ra .ReadAt (p [pIdxBegin :pIdxEnd ], currentRawOff )
822
+ if err != nil {
823
+ return n , fmt .Errorf ("failed to read from the raw offset %d: %w" , currentRawOff , err )
824
+ }
825
+ } else {
826
+ currentOff := off + int64 (n )
827
+ if ((extL2Entry .ZeroStatusBitmap >> i ) & 0b1 ) == 0b1 {
828
+ currentN , err = img .readZero (p [pIdxBegin :pIdxEnd ], currentOff )
829
+ if err != nil {
830
+ return n , fmt .Errorf ("failed to read zero: %w" , err )
831
+ }
832
+ } else {
833
+ currentN , err = img .readAtAlignedUnallocated (p [pIdxBegin :pIdxEnd ], currentOff )
834
+ if err != nil && ! errors .Is (err , io .EOF ) {
835
+ return n , fmt .Errorf ("failed to read unallocated: %w" , err )
836
+ }
837
+ }
838
+ }
839
+ if currentN > 0 {
840
+ n += currentN
841
+ }
842
+ }
843
+ return n , nil
844
+ }
845
+
747
846
func (img * Qcow2 ) readAtAlignedCompressed (p []byte , off int64 , desc compressedClusterDescriptor ) (int , error ) {
748
847
hostClusterOffset := desc .hostClusterOffset (int (img .Header .ClusterBits ))
749
848
if hostClusterOffset == 0 {
0 commit comments