예제 #1
0
    def store(self, data):
        """
        Store given data to the file system.

        This method is supposed to be called only before a VM is started.
        Contingent stale data, if present, is removed before `data` is stored.
        The method is not intended to be used for an atomic live data
        replacement and such a use is not guaranteed to work properly.

        :param data: encoded data as previously returned from `retrieve()`
        :type data: string
        :raises: exception.ExternalDataFailed if data could not be decoded
        """
        byte_data = data.encode('ascii')
        # Remove line-ends; this is for backward compatibility with legacy
        # base64 methods used in oVirt 4.4.4 and could possibly be dropped the
        # in future
        byte_data = byte_data.translate(None, delete=b'\n')
        # Detect if the content is compressed and decompress it if yes. For
        # backward compatibility we don't treat it as error if content is not
        # compressed.
        #
        # Base64 (RFC 3548) uses the alphabet [A-Za-z0-9+/] for encoding
        # data and a special character '=' for padding at the end of
        # output. Since the '=' character cannot appear at the beginning of
        # base64 encoded data we use the string '=X=' to store the format of
        # encoded data. Currently used values of X are:
        #   0   bzip2 compressed data
        #
        data_format = None
        if len(byte_data) > 3 and byte_data[0] == ord('=') and \
                byte_data[2] == ord('='):
            data_format = byte_data[1]
            byte_data = byte_data[3:]
        # Decode base64
        error = None
        try:
            decoded_data = base64.b64decode(byte_data, validate=True)
        except binascii.Error as e:
            error = e
        if error is not None:
            raise exception.ExternalDataFailed(
                'Failed to decode base64 data', exception=error)
        # Uncompress
        if data_format == ord('0'):
            error = None
            try:
                final_data = bz2.decompress(decoded_data)
            except Exception as e:
                error = e
            if error is not None:
                raise exception.ExternalDataFailed(
                    'Failed to decompress bzip2 content', exception=error)
        elif data_format is None:
            final_data = decoded_data
        else:
            raise exception.ExternalDataFailed(
                'Invalid data format', data_format=data_format)
        self._store(final_data)
예제 #2
0
def write_tpm_data(vm_id, tpm_data):
    """
    Write TPM data for the given VM.

    :param vm_id: VM id
    :type vm_id: string
    :param tpm_data: encoded TPM data as previously obtained from
      `read_tpm_data()`
    :type tpm_data: ProtectedPassword
    """
    tpm_data = password.unprotect(tpm_data)
    # Permit only archives with plain files and directories to prevent various
    # kinds of attacks.
    with tempfile.TemporaryDirectory() as d:
        accessor = filedata.DirectoryData(os.path.join(d, 'check'))
        accessor.store(tpm_data)
        for root, dirs, files in os.walk(d):
            for f in files:
                path = os.path.join(root, f)
                if not os.path.isfile(path):
                    logging.error("Special file in TPM data: %s", path)
                    raise exception.ExternalDataFailed(
                        reason="Cannot write TPM data with non-regular files",
                        path=path)
    # OK, write the data to the target location
    accessor = filedata.DirectoryData(filedata.tpm_path(vm_id))
    accessor.store(tpm_data)
예제 #3
0
def nvram_path(vm_id):
    """
    Return path to NVRAM file for a VM or a path where to store a template for
    NVRAM of the VM.

    :param vm_id: VM id
    :type vm_id: string
    :returns: path to the NVRAM file
    :rtype: string
    :raises: exception.ExternalDataFailed -- if the VM id has invalid format,
      OSError -- when NVRAM directory cannot be created
    """
    if _VM_ID_REGEXP.match(vm_id) is None:
        raise exception.ExternalDataFailed("Invalid VM id", vm_id=vm_id)
    if not os.path.exists(constants.P_LIBVIRT_NVRAM):
        # The directory is normally created by libvirt, but this may not
        # have happened yet. We can try to create it on our own. The
        # parents however should be part of libvirt RPM and if they
        # are missing it is not our problem.
        uid = pwd.getpwnam(constants.QEMU_PROCESS_USER).pw_uid
        gid = grp.getgrnam(constants.QEMU_PROCESS_GROUP).gr_gid
        os.mkdir(constants.P_LIBVIRT_NVRAM, mode=0o755)
        os.chown(constants.P_LIBVIRT_NVRAM, uid, gid)
    path = os.path.join(constants.P_LIBVIRT_NVRAM, vm_id + ".fd")
    return path
예제 #4
0
 def _retrieve(self):
     with open(self._path, 'rb') as f:
         data = f.read()
     if len(data) == 0 and not self._allow_empty:
         raise exception.ExternalDataFailed(
             'File with zero size is not allowed', path=self._path)
     return data
예제 #5
0
    def retrieve(self, last_modified=-1):
        """
        Retrieve and return data from the file system.

        If the data is not newer than `last_modified`, don't retrieve it.

        :param last_modified: retrieve data only when `last_modified()` returns
          a value newer than this one
        :type last_modified: float
        :returns: encoded data, which can be later used as a `store()`
          argument; None if data is unchanged
        :rtype: string or None
        :raises: `ExternalDataFailed` if the data doesn't exist
        """
        if not self._exists():
            logging.debug("Data path doesn't exist: %s", self._path)
            raise exception.ExternalDataFailed(
                reason="Data path doesn't exist", path=self._path
            )
        currently_modified = self.last_modified()
        if currently_modified <= last_modified and \
           last_modified <= time.time():  # last_modified in future? no!
            return None
        data = self._retrieve()
        data_format = ''
        if self._compress:
            # Compress data with bzip2. See the store() method for description
            # of the prepended string.
            data_format = '=0='
            data = bz2.compress(data, compresslevel=9)
        return data_format + base64.b64encode(data).decode('ascii')
예제 #6
0
def tpm_path(vm_id):
    """
    Return path to TPM data for a VM with the given id.

    :param vm_id: VM id
    :type vm_id: string
    :returns: path to the TPM data directory
    :rtype: string
    :raises: exception.ExternalDataFailed -- if the VM id has invalid format
    """
    # vm_id is used as a subdirectory path by supervdsm, so we must be safe
    # here
    if _VM_ID_REGEXP.match(vm_id) is None:
        raise exception.ExternalDataFailed("Invalid VM id", vm_id=vm_id)
    return os.path.join(constants.P_LIBVIRT_SWTPM, vm_id)
예제 #7
0
 def _update_internal(self, force=False):
     error = None
     try:
         new_data = self._monitor.data(force=force)
     except Exception as e:
         self._log.error("Failed to read %s data: %s", self._kind, e)
         if isinstance(e, exception.ExternalDataFailed):
             raise
         else:
             # Let's not leak data from the exception
             error = e
     if error is not None:
         raise exception.ExternalDataFailed(
             reason="Failed to read %s data" % self._kind,
             exception=error
         )
     monitor_hash = self._monitor.data_hash()
     if new_data is None:
         # Data is unchanged, we can report it
         stable_data = self._data.current_data
         if stable_data is not self._data.stable_data:
             data = self._data._replace(
                 stable_data=stable_data,
                 engine_hash=ExternalData.secure_hash(stable_data)
             )
         else:
             # No change at all
             data = self._data
     elif force or monitor_hash == self._data.monitor_hash:
         # New stable data, replace old data completely
         data = ExternalData.Data(
             stable_data=new_data,
             current_data=new_data,
             monitor_hash=monitor_hash,
             engine_hash=ExternalData.secure_hash(new_data)
         )
     else:
         # New unstable data, store it but don't report it
         data = self._data._replace(
             current_data=new_data,
             monitor_hash=monitor_hash
         )
     return data
예제 #8
0
 def _retrieve(self):
     if len(os.listdir(self._path)) == 0 and not self._allow_empty:
         raise exception.ExternalDataFailed(
             'Empty directory is not allowed', path=self._path)
     return _make_tar_archive(self._path)