Skip to content

Commit 4286630

Browse files
authored
Merge pull request #47 from stackhpc/upstream/2023.1-2024-09-02
Synchronise 2023.1 with upstream
2 parents a10e14c + 1427857 commit 4286630

File tree

6 files changed

+345
-31
lines changed

6 files changed

+345
-31
lines changed

glance/async_/flows/plugins/image_conversion.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,24 +90,15 @@ def _execute(self, action, file_path, **kwargs):
9090
dest_path = "%(path)s.%(target)s" % {'path': self.src_path,
9191
'target': target_format}
9292
self.dest_path = dest_path
93-
9493
source_format = action.image_disk_format
95-
inspector_cls = format_inspector.get_inspector(source_format)
96-
if not inspector_cls:
97-
# We cannot convert from disk_format types that qemu-img doesn't
98-
# support (like iso, ploop, etc). The ones it supports overlaps
99-
# with the ones we have inspectors for, so reject conversion for
100-
# any format we don't have an inspector for.
101-
raise RuntimeError(
102-
'Unable to convert from format %s' % source_format)
10394

10495
# Use our own cautious inspector module (if we have one for this
10596
# format) to make sure a file is the format the submitter claimed
10697
# it is and that it passes some basic safety checks _before_ we run
10798
# qemu-img on it.
10899
# See https://bugs.launchpad.net/nova/+bug/2059809 for details.
109100
try:
110-
inspector = inspector_cls.from_file(self.src_path)
101+
inspector = format_inspector.detect_file_format(self.src_path)
111102
if not inspector.safety_check():
112103
LOG.error('Image failed %s safety check; aborting conversion',
113104
source_format)
@@ -122,6 +113,24 @@ def _execute(self, action, file_path, **kwargs):
122113
LOG.exception('Unknown error inspecting image format: %s', e)
123114
raise RuntimeError('Unable to inspect image')
124115

116+
if str(inspector) == 'iso':
117+
if source_format == 'iso':
118+
# NOTE(abhishekk): Excluding conversion and preserving image
119+
# disk_format as `iso` only
120+
LOG.debug("Avoiding conversion of an image %s having"
121+
" `iso` disk format.", self.image_id)
122+
return file_path
123+
124+
# NOTE(abhishekk): Raising error as image detected as ISO but
125+
# claimed as different format
126+
LOG.error('Image claimed to be %s format but format '
127+
'inspection found: ISO', source_format)
128+
raise RuntimeError("Image has disallowed configuration")
129+
elif str(inspector) != source_format:
130+
LOG.error('Image claimed to be %s format failed format '
131+
'inspection', source_format)
132+
raise RuntimeError('Image format mismatch')
133+
125134
try:
126135
stdout, stderr = putils.trycmd("qemu-img", "info",
127136
"-f", source_format,

glance/common/format_inspector.py

Lines changed: 94 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import struct
2525

2626
from oslo_log import log as logging
27+
from oslo_utils import units
2728

2829
LOG = logging.getLogger(__name__)
2930

@@ -839,6 +840,84 @@ def __str__(self):
839840
return 'vdi'
840841

841842

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+
842921
class InfoWrapper(object):
843922
"""A file-like object that wraps another and updates a format inspector.
844923
@@ -891,6 +970,7 @@ def close(self):
891970
'vmdk': VMDKInspector,
892971
'vdi': VDIInspector,
893972
'qed': QEDInspector,
973+
'iso': ISOInspector,
894974
}
895975

896976

@@ -906,14 +986,16 @@ def get_inspector(format_name):
906986

907987
def detect_file_format(filename):
908988
"""Attempts to detect the format of a file.
909-
910989
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
912991
them are sure they don't match.
913992
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.
915996
"""
916997
inspectors = {k: v() for k, v in ALL_FORMATS.items()}
998+
detections = []
917999
with open(filename, 'rb') as f:
9181000
for chunk in chunked_reader(f):
9191001
for format, inspector in list(inspectors.items()):
@@ -925,10 +1007,16 @@ def detect_file_format(filename):
9251007
continue
9261008
if (inspector.format_match and inspector.complete and
9271009
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)
9301013
if all(i.complete for i in inspectors.values()):
9311014
# If all the inspectors are sure they are not a match, avoid
9321015
# reading to the end of the file to settle on 'raw'.
9331016
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

Comments
 (0)