def find_and_open_bmap(args): """ This is a helper function for 'open_files()' which discovers and opens the bmap file, then returns the corresponding file object and the bmap file path. If the user specified the bmap file explicitly, we just open the provided path. Otherwise, we try to discover the bmap file at the same place where the image file is located. We search for a file with the same path and basename, but with a ".bmap" extension. Additionally, this function makes sure that the returned file object corresponds to a local file, not a remote file. We do this by creating a temporary local copy of the bmap file. The reason is that further on we may need to check the GPG signature of the file, which requires it to be a local file. On top of that, the BmapCopy class requires the bmap file to be memory-mappable ('mmap()'). """ if args.nobmap: return (None, None) if args.bmap: try: bmap_obj = TransRead.TransRead(args.bmap) except TransRead.Error as err: error_out("cannot open bmap file '%s':\n%s", args.bmap, err) bmap_path = args.bmap else: # Automatically discover the bmap file image_path = args.image while True: bmap_path = image_path + ".bmap" try: bmap_obj = TransRead.TransRead(bmap_path) log.info("discovered bmap file '%s'" % bmap_path) break except TransRead.Error: pass image_path, ext = os.path.splitext(image_path) if ext == '': return (None, None) if not bmap_obj.is_url: return (bmap_obj, bmap_path) try: # Create a temporary file for the bmap tmp_obj = tempfile.NamedTemporaryFile("wb+") except IOError as err: error_out("cannot create a temporary file for bmap:\n%s", err) shutil.copyfileobj(bmap_obj, tmp_obj) tmp_obj.flush() tmp_obj.seek(0) bmap_obj.close() return (tmp_obj, bmap_path)
def copy_and_verify_image(image, dest, bmap, image_chksum, image_size): """ Copy image 'image' using bmap file 'bmap' to the destination file 'dest' and verify the resulting image checksum. """ f_image = TransRead.TransRead(image) f_dest = open(dest, "w+") if (bmap): f_bmap = open(bmap, "r") else: f_bmap = None writer = BmapCopy.BmapCopy(f_image, f_dest, f_bmap, image_size) # Randomly decide whether we want the progress bar or not if bool(random.getrandbits(1)): writer.set_progress_indicator(sys.stdout, None) writer.copy(bool(random.getrandbits(1)), bool(random.getrandbits(1))) # Compare the original file and the copy are identical assert calculate_chksum(dest) == image_chksum if f_bmap: f_bmap.close() f_dest.close() f_image.close()
def calculate_chksum(file_path): """Calculates checksum for the contents of file 'file_path'.""" file_obj = TransRead.TransRead(file_path) hash_obj = hashlib.new("sha256") chunk_size = 1024*1024 while True: chunk = file_obj.read(chunk_size) if not chunk: break hash_obj.update(chunk) file_obj.close() return hash_obj.hexdigest()
def test(self): """The test entry point.""" test_data_dir = os.path.join(os.path.dirname(__file__), _TEST_DATA_SUBDIR) image_path = os.path.join(test_data_dir, _IMAGE_NAME) # Construct the list of bmap files to test self._bmap_paths = [] for dentry in os.listdir(test_data_dir): dentry_path = os.path.join(test_data_dir, dentry) if os.path.isfile(dentry_path) and dentry.startswith(_BMAP_TEMPL): self._bmap_paths.append(dentry_path) # Create and open a temporary file for uncompressed image and its copy self._f_image = tempfile.NamedTemporaryFile("wb+", prefix=_IMAGE_NAME, suffix=".image") self._f_copy = tempfile.NamedTemporaryFile("wb+", prefix=_IMAGE_NAME, suffix=".copy") # Uncompress the test image into 'self._f_image' f_tmp_img = TransRead.TransRead(image_path) shutil.copyfileobj(f_tmp_img, self._f_image) f_tmp_img.close() self._f_image.flush() image_chksum = helpers.calculate_chksum(self._f_image.name) image_size = os.path.getsize(self._f_image.name) # Test the current version of BmapCopy for bmap_path in self._bmap_paths: helpers.copy_and_verify_image(image_path, self._f_copy.name, bmap_path, image_chksum, image_size) # Test the older versions of BmapCopy self._test_older_bmapcopy() self._f_copy.close() self._f_image.close()
def open_files(args): """ This is a helper function for 'copy_command()' which the image, bmap, and the destination files. Returns a tuple of 5 elements: 1 file-like object for the image 2 file object for the destination file 3 file-like object for the bmap 4 full path to the bmap file 5 image size in bytes 6 'True' if the destination file is a block device, otherwise 'False' """ # Open the image file using the TransRead module, which will automatically # recognize whether it is compressed or whether file path is an URL, etc. try: image_obj = TransRead.TransRead(args.image) except TransRead.Error as err: error_out("cannot open image:\n%s" % err) # Open the bmap file. Try to discover the bmap file automatically if it # was not specified. (bmap_obj, bmap_path) = find_and_open_bmap(args) if bmap_path == args.image: # Most probably the specified the bmap file instead of the image file # by mistake. log.warning("image has the same path as the bmap file, dropping bmap") bmap_obj.close() bmap_obj = None bmap_path = None args.nobmap = True # If the destination file is under "/dev", but does not exist, print a # warning. This is done in order to be more user-friendly, because # sometimes users mean to write to a block device, them misspell its name. # We just create the "/dev/misspelled" file, write the data there, and # report success. Later on the user finds out that the image was not really # written to the device, and gets confused. Similar confusion may happen if # the destination file is not a special device for some reasons. if os.path.normpath(args.dest).startswith("/dev/"): if not os.path.exists(args.dest): log.warning("\"%s\" does not exist, creating a regular file " "\"%s\"" % (args.dest, args.dest)) elif stat.S_ISREG(os.stat(args.dest).st_mode): log.warning("\"%s\" is under \"/dev\", but it is a regular file, " "not a device node" % args.dest) # Try to open the destination file. If it does not exist, a new regular # file will be created. If it exists and it is a regular file - it'll be # truncated. If this is a block device, it'll just be opened. try: dest_obj = open(args.dest, 'wb+') except IOError as err: error_out("cannot open destination file '%s':\n%s", args.dest, err) # Check whether the destination file is a block device dest_is_blkdev = stat.S_ISBLK(os.fstat(dest_obj.fileno()).st_mode) if dest_is_blkdev: dest_obj.close() dest_obj = open_block_device(args.dest) return (image_obj, dest_obj, bmap_obj, bmap_path, image_obj.size, dest_is_blkdev)
def verify_detached_bmap_signature(args, bmap_obj, bmap_path): """ This is a helper function for 'verify_bmap_signature()' which handles the detached signature case. """ if args.no_sig_verify: return None if args.bmap_sig: try: sig_obj = TransRead.TransRead(args.bmap_sig) except TransRead.Error as err: error_out("cannot open bmap signature file '%s':\n%s", args.bmap_sig, err) sig_path = args.bmap_sig else: # Check if there is a stand-alone signature file try: sig_path = bmap_path + ".asc" sig_obj = TransRead.TransRead(sig_path) except TransRead.Error: try: sig_path = bmap_path + ".sig" sig_obj = TransRead.TransRead(sig_path) except TransRead.Error: # No signatures found return None log.info("discovered signature file for bmap '%s'" % sig_path) # If the stand-alone signature file is not local, make a local copy if sig_obj.is_url: try: tmp_obj = tempfile.NamedTemporaryFile("wb+") except IOError as err: error_out("cannot create a temporary file for the signature:\n%s", err) shutil.copyfileobj(sig_obj, tmp_obj) tmp_obj.seek(0) sig_obj.close() sig_obj = tmp_obj try: import gpgme except ImportError: error_out("cannot verify the signature because the python \"gpgme\" " "module is not installed on your system\nPlease, either " "install the module or use --no-sig-verify") try: context = gpgme.Context() signature = io.FileIO(sig_obj.name) signed_data = io.FileIO(bmap_obj.name) sigs = context.verify(signature, signed_data, None) except gpgme.GpgmeError as err: error_out("failure when trying to verify GPG signature: %s\n" "Make sure file \"%s\" has proper GPG format", err[2].lower(), sig_path) sig_obj.close() if len(sigs) == 0: log.warning("the \"%s\" signature file does not actually contain " "any valid signatures" % sig_path) else: report_verification_results(context, sigs) return None