Added option to ImageHandler() to open file read-only. am: 4e71bfd373

Change-Id: I534585479213ff9df7f2960473568e1609781735
diff --git a/aftltool.py b/aftltool.py
index 0fe2d07..192ad9b 100755
--- a/aftltool.py
+++ b/aftltool.py
@@ -1169,7 +1169,7 @@
     """
     # Reads and parses the vbmeta image.
     try:
-      image = avbtool.ImageHandler(image_filename)
+      image = avbtool.ImageHandler(image_filename, read_only=True)
     except (IOError, ValueError) as e:
       sys.stderr.write('The image does not contain a valid VBMeta structure: '
                        '{}.\n'.format(e))
@@ -1212,7 +1212,7 @@
       return None
 
     try:
-      image = avbtool.ImageHandler(image_filename)
+      image = avbtool.ImageHandler(image_filename, read_only=True)
     except ValueError as e:
       sys.stderr.write('The image does not contain a valid VBMeta structure: '
                        '{}.\n'.format(e))
diff --git a/avbtool.py b/avbtool.py
index 4b9c594..13bb288 100755
--- a/avbtool.py
+++ b/avbtool.py
@@ -732,11 +732,12 @@
   NUM_CHUNKS_AND_BLOCKS_FORMAT = '<II'
   NUM_CHUNKS_AND_BLOCKS_OFFSET = 16
 
-  def __init__(self, image_filename):
+  def __init__(self, image_filename, read_only=False):
     """Initializes an image handler.
 
     Arguments:
       image_filename: The name of the file to operate on.
+      read_only: True if file is only opened for read-only operations.
 
     Raises:
       ValueError: If data in the file is invalid.
@@ -745,6 +746,7 @@
     self._num_total_blocks = 0
     self._num_total_chunks = 0
     self._file_pos = 0
+    self._read_only = read_only
     self._read_header()
 
   def _read_header(self):
@@ -759,7 +761,10 @@
     self.is_sparse = False
     self.block_size = 4096
     self._file_pos = 0
-    self._image = open(self.filename, 'r+b')
+    if self._read_only:
+      self._image = open(self.filename, 'rb')
+    else:
+      self._image = open(self.filename, 'r+b')
     self._image.seek(0, os.SEEK_END)
     self.image_size = self._image.tell()
 
@@ -885,9 +890,15 @@
 
     Arguments:
       num_bytes: Size in number of bytes of the DONT_CARE chunk.
+
+    Raises
+      OSError: If ImageHandler was initialized in read-only mode.
     """
     assert num_bytes % self.block_size == 0
 
+    if self._read_only:
+      raise OSError('ImageHandler is in read-only mode.')
+
     if not self.is_sparse:
       self._image.seek(0, os.SEEK_END)
       # This is more efficient that writing NUL bytes since it'll add
@@ -916,9 +927,15 @@
 
     Arguments:
       data: Data to append as bytes.
+
+    Raises
+      OSError: If ImageHandler was initialized in read-only mode.
     """
     assert len(data) % self.block_size == 0
 
+    if self._read_only:
+      raise OSError('ImageHandler is in read-only mode.')
+
     if not self.is_sparse:
       self._image.seek(0, os.SEEK_END)
       self._image.write(data)
@@ -947,11 +964,17 @@
     Arguments:
       fill_data: Fill data to append - must be four bytes.
       size: Number of chunk - must be a multiple of four and the block size.
+
+    Raises
+      OSError: If ImageHandler was initialized in read-only mode.
     """
     assert len(fill_data) == 4
     assert size % 4 == 0
     assert size % self.block_size == 0
 
+    if self._read_only:
+      raise OSError('ImageHandler is in read-only mode.')
+
     if not self.is_sparse:
       self._image.seek(0, os.SEEK_END)
       self._image.write(fill_data * (size//4))
@@ -1051,7 +1074,11 @@
 
     Raises:
       ValueError: If desired size isn't a multiple of the block size.
+      OSError: If ImageHandler was initialized in read-only mode.
     """
+    if self._read_only:
+      raise OSError('ImageHandler is in read-only mode.')
+
     if not self.is_sparse:
       self._image.truncate(size)
       self._read_header()
@@ -1467,7 +1494,7 @@
       image = image_containing_descriptor
     else:
       image_filename = os.path.join(image_dir, self.partition_name + image_ext)
-      image = ImageHandler(image_filename)
+      image = ImageHandler(image_filename, read_only=True)
     # Generate the hashtree and checks that it matches what's in the file.
     digest_size = len(hashlib.new(self.hash_algorithm).digest())
     digest_padding = round_to_pow2(digest_size) - digest_size
@@ -1635,7 +1662,7 @@
       image = image_containing_descriptor
     else:
       image_filename = os.path.join(image_dir, self.partition_name + image_ext)
-      image = ImageHandler(image_filename)
+      image = ImageHandler(image_filename, read_only=True)
     data = image.read(self.image_size)
     ha = hashlib.new(self.hash_algorithm)
     ha.update(self.salt)
@@ -2169,7 +2196,7 @@
     Raises:
       AvbError: If there's no footer in the image.
     """
-    image = ImageHandler(image_filename)
+    image = ImageHandler(image_filename, read_only=True)
     (footer, _, _, _) = self._parse_image(image)
     if not footer:
       raise AvbError('Given image does not have a footer.')
@@ -2365,7 +2392,7 @@
       image_filename: Image file to get information from (file object).
       output: Output file to write human-readable information to (file object).
     """
-    image = ImageHandler(image_filename)
+    image = ImageHandler(image_filename, read_only=True)
     o = output
     (footer, header, descriptors, image_size) = self._parse_image(image)
 
@@ -2460,7 +2487,7 @@
       print('Verifying image {} using embedded public key'.format(
           image_filename))
 
-    image = ImageHandler(image_filename)
+    image = ImageHandler(image_filename, read_only=True)
     (footer, header, descriptors, _) = self._parse_image(image)
     offset = 0
     if footer:
@@ -2553,7 +2580,7 @@
     Raises:
       AvbError: If getting the partition digests from the image fails.
     """
-    image = ImageHandler(image_filename)
+    image = ImageHandler(image_filename, read_only=True)
     (_, _, descriptors, _) = self._parse_image(image)
 
     for desc in descriptors:
@@ -2590,7 +2617,7 @@
     image_dir = os.path.dirname(image_filename)
     image_ext = os.path.splitext(image_filename)[1]
 
-    image = ImageHandler(image_filename)
+    image = ImageHandler(image_filename, read_only=True)
     (footer, header, descriptors, _) = self._parse_image(image)
     offset = 0
     if footer:
@@ -2607,7 +2634,7 @@
       if isinstance(desc, AvbChainPartitionDescriptor):
         ch_image_filename = os.path.join(image_dir,
                                          desc.partition_name + image_ext)
-        ch_image = ImageHandler(ch_image_filename)
+        ch_image = ImageHandler(ch_image_filename, read_only=True)
         (ch_footer, ch_header, _, _) = self._parse_image(ch_image)
         ch_offset = 0
         ch_size = (ch_header.SIZE + ch_header.authentication_data_block_size +
@@ -2630,7 +2657,7 @@
       output: Output file to write human-readable information to (file object).
     """
 
-    image = ImageHandler(image_filename)
+    image = ImageHandler(image_filename, read_only=True)
     _, _, descriptors, _ = self._parse_image(image)
 
     image_dir = os.path.dirname(image_filename)
@@ -2641,7 +2668,7 @@
       if isinstance(desc, AvbChainPartitionDescriptor):
         ch_image_filename = os.path.join(image_dir,
                                          desc.partition_name + image_ext)
-        ch_image = ImageHandler(ch_image_filename)
+        ch_image = ImageHandler(ch_image_filename, read_only=True)
         _, _, ch_descriptors, _ = self._parse_image(ch_image)
         for ch_desc in ch_descriptors:
           if isinstance(ch_desc, AvbKernelCmdlineDescriptor):
@@ -2871,7 +2898,8 @@
       # Use the bump logic in AvbVBMetaHeader to calculate the max required
       # version of all included descriptors.
       for image in include_descriptors_from_image:
-        (_, image_header, _, _) = self._parse_image(ImageHandler(image.name))
+        (_, image_header, _, _) = self._parse_image(ImageHandler(
+            image.name, read_only=True))
         tmp_header.bump_required_libavb_version_minor(
             image_header.required_libavb_version_minor)