Beispiel #1
0
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()
Beispiel #4
0
    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()
Beispiel #5
0
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)
Beispiel #6
0
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