24
24
import struct
25
25
26
26
from oslo_log import log as logging
27
+ from oslo_utils import units
27
28
28
29
LOG = logging .getLogger (__name__ )
29
30
@@ -839,6 +840,84 @@ def __str__(self):
839
840
return 'vdi'
840
841
841
842
843
+ class ISOInspector (FileInspector ):
844
+ """ISO 9660 and UDF format
845
+ we need to check the first 32KB + descriptor size
846
+ to look for the ISO 9660 or UDF signature.
847
+ http://wiki.osdev.org/ISO_9660
848
+ http://wiki.osdev.org/UDF
849
+ mkisofs --help | grep udf
850
+ The Universal Disc Format or UDF is the filesystem used on DVDs and
851
+ Blu-Ray discs.UDF is an extension of ISO 9660 and shares the same
852
+ header structure and initial layout.
853
+ Like the CDFS(ISO 9660) file system,
854
+ the UDF file system uses a 2048 byte sector size,
855
+ and it designates that the first 16 sectors can be used by the OS
856
+ to store proprietary data or boot logic.
857
+ That means we need to check the first 32KB + descriptor size
858
+ to look for the ISO 9660 or UDF signature.
859
+ both formats have an extent based layout, so we can't determine
860
+ ahead of time where the descriptor will be located.
861
+ fortunately, the ISO 9660 and UDF formats have a Primary Volume Descriptor
862
+ located at the beginning of the image, which contains the volume size.
863
+ """
864
+ def __init__ (self , * a , ** k ):
865
+ super (ISOInspector , self ).__init__ (* a , ** k )
866
+ self .new_region ('system_area' , CaptureRegion (0 , 32 * units .Ki ))
867
+ self .new_region ('header' , CaptureRegion (32 * units .Ki , 2 * units .Ki ))
868
+
869
+ @property
870
+ def format_match (self ):
871
+ if not self .complete :
872
+ return False
873
+ signature = self .region ('header' ).data [1 :6 ]
874
+ assert len (signature ) == 5
875
+ # These signatures are for ISO9660, UDF and UDF.
876
+ return signature in (b'CD001' , b'NSR02' , b'NSR03' )
877
+
878
+ @property
879
+ def virtual_size (self ):
880
+ if not self .complete :
881
+ return 0
882
+ if not self .format_match :
883
+ return 0
884
+ # the header size is 2KB or 1 sector
885
+ # the first header field is the descriptor type which is 1 byte
886
+ # the second field is the standard identifier which is 5 bytes
887
+ # the third field is the version which is 1 byte
888
+ # the rest of the header contains type specific data is 2041 bytes
889
+ # see http://wiki.osdev.org/ISO_9660#The_Primary_Volume_Descriptor
890
+ # we need to check that the descriptor type is 1
891
+ # to ensure that this is a primary volume descriptor
892
+ descriptor_type = self .region ('header' ).data [0 ]
893
+ if descriptor_type != 1 :
894
+ return 0
895
+ # The size in bytes of a logical block is stored at offset 128
896
+ # and is 2 bytes long encoded in both little and big endian
897
+ # int16_LSB-MSB so the field is 4 bytes long
898
+ logical_block_size_data = self .region ('header' ).data [128 :132 ]
899
+ assert len (logical_block_size_data ) == 4
900
+ # given the encoding we only need to read half the field so we
901
+ # can use the first 2 bytes which are the little endian part
902
+ # this is normally 2048 or 2KB but we need to check as it can be
903
+ # different according to the ISO 9660 standard.
904
+ logical_block_size , = struct .unpack ('<H' , logical_block_size_data [:2 ])
905
+ # The volume space size is the total number of logical blocks
906
+ # and is stored at offset 80 and is 8 bytes long
907
+ # as with the logical block size the field is encoded in both
908
+ # little and big endian as an int32_LSB-MSB
909
+ volume_space_size_data = self .region ('header' ).data [80 :88 ]
910
+ assert len (volume_space_size_data ) == 8
911
+ # given the encoding we only need to read half the field so we
912
+ # can use the first 4 bytes which are the little endian part
913
+ volume_space_size , = struct .unpack ('<L' , volume_space_size_data [:4 ])
914
+ # the virtual size is the volume space size * logical block size
915
+ return volume_space_size * logical_block_size
916
+
917
+ def __str__ (self ):
918
+ return 'iso'
919
+
920
+
842
921
class InfoWrapper (object ):
843
922
"""A file-like object that wraps another and updates a format inspector.
844
923
@@ -891,6 +970,7 @@ def close(self):
891
970
'vmdk' : VMDKInspector ,
892
971
'vdi' : VDIInspector ,
893
972
'qed' : QEDInspector ,
973
+ 'iso' : ISOInspector ,
894
974
}
895
975
896
976
@@ -906,14 +986,16 @@ def get_inspector(format_name):
906
986
907
987
def detect_file_format (filename ):
908
988
"""Attempts to detect the format of a file.
909
-
910
989
This runs through a file one time, running all the known inspectors in
911
- parallel. It stops reading the file once one of them matches or all of
990
+ parallel. It stops reading the file once all of them matches or all of
912
991
them are sure they don't match.
913
992
914
- Returns the FileInspector that matched, if any. None if 'raw'.
993
+ :param filename: The path to the file to inspect.
994
+ :returns: A FormatInspector instance matching the file.
995
+ :raises: ImageFormatError if multiple formats are detected.
915
996
"""
916
997
inspectors = {k : v () for k , v in ALL_FORMATS .items ()}
998
+ detections = []
917
999
with open (filename , 'rb' ) as f :
918
1000
for chunk in chunked_reader (f ):
919
1001
for format , inspector in list (inspectors .items ()):
@@ -925,10 +1007,16 @@ def detect_file_format(filename):
925
1007
continue
926
1008
if (inspector .format_match and inspector .complete and
927
1009
format != 'raw' ):
928
- # First complete match (other than raw) wins
929
- return inspector
1010
+ # record all match (other than raw)
1011
+ detections .append (inspector )
1012
+ inspectors .pop (format )
930
1013
if all (i .complete for i in inspectors .values ()):
931
1014
# If all the inspectors are sure they are not a match, avoid
932
1015
# reading to the end of the file to settle on 'raw'.
933
1016
break
934
- return inspectors ['raw' ]
1017
+
1018
+ if len (detections ) > 1 :
1019
+ all_formats = [str (inspector ) for inspector in detections ]
1020
+ raise ImageFormatError (
1021
+ 'Multiple formats detected: %s' % ', ' .join (all_formats ))
1022
+ return inspectors ['raw' ] if not detections else detections [0 ]
0 commit comments